Skip to main content
jg-packages

Search

    Filesystem

    jg-fs

    Filesystem helpers, async and sync: path checks, directories, text and JSON files, atomic writes, and bounded reads.

    v1.4.0Node.jsESMMIT

    npm install jg-fs

    Usage

    The root entry is async, and jg-fs/sync has the same functions, synchronous:

    ts
    import { readJsonFile, writeFileAtomic, writeJsonFile, writeTextFile } from 'jg-fs';
    import { readTextFile } from 'jg-fs/sync';
    import { z } from 'zod';
    
    const Config = z.object({ port: z.number() });
    
    await writeTextFile('notes/today.md', '# Today'); // creates notes/ when it's missing
    const config = await readJsonFile('config.json', { schema: Config }); // validated, and typed by the schema
    await writeJsonFile('config.json', { ...config, port: 3000 }); // atomic: readers never see a partial file
    await writeFileAtomic('state.txt', 'step 2'); // any content, atomically
    const upload = await readJsonFile('uploads/import.json', { maxBytes: 1_048_576 }); // FILE_TOO_LARGE past 1 MB
    
    const readme = readTextFile('README.md');

    List, filter, and sort a directory:

    ts
    import { getDirectoryElements } from 'jg-fs';
    
    const { files, directories } = await getDirectoryElements('content', {
      includeExts: ['.md'],
      sortByKey: 'creation',
      sortOrder: 'desc',
    });

    Read files from untrusted paths with a size limit, without following a symbolic link:

    ts
    import { isPathWithin, readBoundedFile } from 'jg-fs';
    
    if (isPathWithin(uploadsDirectory, path)) {
      const upload = await readBoundedFile(path, 5 * 1024 * 1024);
    }

    Keep a job from running twice at once, across processes on one machine, with a lock:

    ts
    import { acquireLock } from 'jg-fs';
    
    const lock = await acquireLock('.locks/nightly-report'); // LOCK_BUSY while another process holds it
    try {
      await buildNightlyReport();
    } finally {
      await lock.release();
    }

    The lock is a directory holding an owner.json that names the process. When its owner’s process is gone, as after a crash, the next acquireLock on the same host takes it over. Use it on local filesystems only: a lock is never taken over from another host.

    Keep keys, tokens, and sessions private to the user who owns them:

    ts
    import { createPrivateDirectory, readPrivateFile, writePrivateFile } from 'jg-fs';
    
    await createPrivateDirectory(configDirectory); // 0o700; DIRECTORY_NOT_PRIVATE if it exists and isn't
    await writePrivateFile(`${configDirectory}/token.json`, token, { rootPath: configDirectory }); // atomic, 0o600
    const saved = await readPrivateFile(`${configDirectory}/token.json`, {
      maxBytes: 16 * 1024,
      rootPath: configDirectory,
    });

    A private file is a regular file, never a symbolic link, with no symbolic link anywhere in its path (PATH_NOT_CANONICAL), and without group or other permissions (FILE_NOT_PRIVATE). The checks run on the open file, so the content read is the content checked. A shared file is never overwritten, and permissions are never changed for you: fix them, or remove the file. writePrivateFile with isExclusive creates the file and throws FILE_ALREADY_EXISTS when anything is already at the path.

    On Windows, Node.js reports only a file’s read-only attribute as its mode, so the permission checks are skipped there. A private file on Windows depends on its directory’s access control list, such as a directory inside the user’s profile.

    Failures throw Exceptions from jg-exception, with the codes in ERRORS.

    Removing a stuck lock

    A lock left in a state no process can safely take over stays busy, and LOCK_BUSY’s data.reason says why:

    • held: the owner named in owner.json is still running. Wait, or stop that process.
    • other-host: the owner runs on another host, which this host can’t check.
    • ownerless: owner.json is missing or invalid, such as after a crash right after the directory was created. A lock being created or released at that moment looks the same, so try again first.
    • recovery-in-progress: a recovery directory is inside, so another process is taking the lock over, or crashed while doing so.
    • unexpected-entries: the directory holds something besides owner.json, such as a .tmp file left by an interrupted write.

    Before deleting a stuck lock’s directory by hand, confirm that no process is using it: check the pid and hostname in owner.json, when there is one. Then delete the whole directory.

    Generated from TSDoc

    API reference

    Every export of [email protected], 84 in all, with its signature, its errors, and an example.

    jg-fs

    Functionsjg-fs

    acquireLock
    function

    Acquires a lock that only one process at a time can hold, such as one guarding a script that mustn't run twice at once. The lock is a directory at path, created with mode 0o700, holding an owner.json, mode 0o600, that names the process.

    When the lock already exists, it's taken over only from an owner on this host whose process is gone, and only when the directory holds nothing but owner.json. Every other case fails with LOCK_BUSY, whose data.reason says why: held, other-host, ownerless, recovery-in-progress, or unexpected-entries. A lock left behind by a crash at the wrong moment stays busy until it's removed by hand, as the README describes.

    Use it on local filesystems only. POSIX modes don't apply on Windows, and a lock is never taken over from another host.

    Signature
    acquireLock(path: string): Promise<IFileLock>

    Parameters

    pathstring

    The lock's directory, whose parent must exist.

    Returns

    Promise<IFileLock>

    A promise that resolves to the lock, released with release().

    Throws

    • LOCK_BUSYThe lock '<path>' is busy.
    • NOT_A_DIRECTORYThe path '<path>' is not a directory.
    • LOCK_ACQUISITION_FAILEDThe lock '<path>' could not be acquired.
    Example
    import { acquireLock, writeFileAtomic } from 'jg-fs';
    
    const lock = await acquireLock('.locks/nightly-report');
    try {
      await writeFileAtomic('reports/nightly.json', JSON.stringify({ generatedAt: Date.now() }));
    } finally {
      await lock.release();
    }
    assertPrivateDirectory
    function

    Checks that a directory is private to its owner: a directory that isn't a symbolic link, with no symbolic link anywhere in its path, and, on POSIX, without group or other permissions. On Windows, the permission check is skipped, as for files.

    Signature
    assertPrivateDirectory(path: string): Promise<void>

    Parameters

    pathstring

    The directory's path.

    Returns

    Promise<void>

    A promise that resolves once the directory passes every check.

    Throws

    • NOT_A_DIRECTORYif the path doesn't exist, is a symbolic link, or isn't a directory
    • PATH_NOT_CANONICALif the path goes through a symbolic link
    • DIRECTORY_NOT_PRIVATEif other users can access the directory
    Example
    import { assertPrivateDirectory } from 'jg-fs';
    
    await assertPrivateDirectory('/home/ada/.config/app');
    assertPrivateFile
    function

    Checks that a file is private to its owner, such as a key or a session file before it's trusted: a regular file that isn't a symbolic link, with no symbolic link anywhere in its path, and, on POSIX, without group or other permissions. With rootPath, it must be strictly inside that directory; with maxBytes, no larger. The checks run on the open file, so a file swapped in between them is caught.

    On Windows, Node.js reports only a file's read-only attribute as its mode, so the permission check is skipped there: a private file on Windows depends on its directory's access control list.

    Signature
    assertPrivateFile(path: string, options?: IPrivateFileOptions): Promise<void>

    Parameters

    pathstring

    The file's path.

    options?IPrivateFileOptions

    The root it must be inside, and the largest size accepted, in bytes.

    Defaults to {}.

    Returns

    Promise<void>

    A promise that resolves once the file passes every check.

    Throws

    • NON_EXISTENT_FILEif the file doesn't exist
    • NOT_A_FILEif the path's final component is a symbolic link, or it isn't a regular file
    • FILE_NOT_PRIVATEif other users can access the file
    • FILE_TOO_LARGEif the file holds more than maxBytes bytes
    • PATH_NOT_CANONICALif the path or the root goes through a symbolic link
    • PATH_OUTSIDE_ROOTif the file isn't strictly inside the root
    • NOT_A_DIRECTORYif the root doesn't exist or isn't a directory
    Example
    import { assertPrivateFile } from 'jg-fs';
    
    await assertPrivateFile('/home/ada/.config/app/token.json', { rootPath: '/home/ada/.config/app' });
    copyDirectory
    function

    Copies a directory and everything in it, replacing the destination completely.

    Signature
    copyDirectory(srcPath: string, destPath: string): Promise<void>

    Parameters

    srcPathstring

    The directory to copy.

    destPathstring

    Where to copy it.

    Returns

    Promise<void>

    A promise that resolves once the copy is complete.

    Throws

    • NOT_A_DIRECTORYif the srcPath is not a directory
    Example
    import { copyDirectory } from 'jg-fs';
    
    await copyDirectory('templates', 'dist/templates');
    copyFile
    function

    Copies a file, replacing the destination when it exists.

    Signature
    copyFile(srcPath: string, destPath: string): Promise<void>

    Parameters

    srcPathstring

    The file to copy.

    destPathstring

    Where to copy it.

    Returns

    Promise<void>

    A promise that resolves once the copy exists.

    Throws

    • NOT_A_FILEif the srcPath doesn't exist or is not a file
    Example
    import { copyFile } from 'jg-fs';
    
    await copyFile('config.example.json', 'config.json');
    createDirectory
    function

    Creates a directory, with any missing parents. An existing directory is kept, or replaced with an empty one when deleteIfExists is set.

    Signature
    createDirectory(path: string, deleteIfExists?: boolean): Promise<void>

    Parameters

    pathstring

    The directory's path.

    deleteIfExists?boolean

    Whether to delete an existing directory and its contents first.

    Returns

    Promise<void>

    A promise that resolves once the directory exists.

    Throws

    • DIRECTORY_ALREADY_EXISTSif something other than a directory exists at the path
    Example
    import { createDirectory } from 'jg-fs';
    
    await createDirectory('dist/assets'); // creates missing parents too
    await createDirectory('dist', true); // replaces it with an empty directory
    createPrivateDirectory
    function

    Creates a directory, and any missing parents, with 0o700 permissions, and checks that it's private, as assertPrivateDirectory does. An existing directory that isn't private throws: its permissions are never changed silently.

    Signature
    createPrivateDirectory(path: string): Promise<void>

    Parameters

    pathstring

    The directory's path.

    Returns

    Promise<void>

    A promise that resolves once the directory exists and is private.

    Throws

    • NOT_A_DIRECTORYif the path exists and isn't a directory
    • PATH_NOT_CANONICALif the path goes through a symbolic link
    • DIRECTORY_NOT_PRIVATEif other users can access the directory
    Example
    import { createPrivateDirectory } from 'jg-fs';
    
    await createPrivateDirectory('/home/ada/.config/app/sessions');
    deleteDirectory
    function

    Deletes a directory and everything in it. A missing directory is not an error.

    Signature
    deleteDirectory(path: string): Promise<void>

    Parameters

    pathstring

    The directory's path.

    Returns

    Promise<void>

    A promise that resolves once the directory is gone.

    Example
    import { deleteDirectory } from 'jg-fs';
    
    await deleteDirectory('dist');
    deleteFile
    function

    Deletes a file.

    Signature
    deleteFile(path: string): Promise<void>

    Parameters

    pathstring

    The file's path.

    Returns

    Promise<void>

    A promise that resolves once the file is gone.

    Throws

    • NOT_A_FILEif the path doesn't exist or is not a file
    Example
    import { deleteFile } from 'jg-fs';
    
    await deleteFile('cache.json');
    getDirectoryElements
    function

    Lists a directory's elements grouped by kind: directories, files (filtered by extension), and symbolic links, each group sorted. Entries removed while the directory is read are left out, and at most 32 entries are read at once.

    Signature
    getDirectoryElements(
      path: string,
      options?: Partial<IDirectoryElementsOptions>,
    ): Promise<IDirectoryPathElements>

    Parameters

    pathstring

    The directory's path.

    options?Partial<IDirectoryElementsOptions>

    The extensions to include and how to sort.

    Returns

    Promise<IDirectoryPathElements>

    A promise that resolves to the grouped elements.

    Throws

    • NOT_A_DIRECTORYif the path doesn't exist or is not a directory
    Example
    import { getDirectoryElements } from 'jg-fs';
    
    const { files } = await getDirectoryElements('content', { includeExts: ['.md'], sortByKey: 'creation', sortOrder: 'desc' });
    getPathElement
    function

    Reads a path's details without following a symbolic link at the path.

    Signature
    getPathElement(path: string): Promise<IPathElement | null>

    Parameters

    pathstring

    The path.

    Returns

    Promise<IPathElement | null>

    A promise that resolves to the path's details, or null when it doesn't exist.

    Example
    import { getPathElement } from 'jg-fs';
    
    const element = await getPathElement('logo.png');
    element?.size; // the size in bytes, or undefined when the path doesn't exist
    isDirectory
    function

    Checks whether a path exists and is a directory.

    Signature
    isDirectory(path: string): Promise<boolean>

    Parameters

    pathstring

    The path.

    Returns

    Promise<boolean>

    A promise that resolves to whether the path is a directory.

    Example
    import { isDirectory } from 'jg-fs';
    
    await isDirectory('dist'); // true
    isFile
    function

    Checks whether a path exists and is a regular file.

    Signature
    isFile(path: string): Promise<boolean>

    Parameters

    pathstring

    The path.

    Returns

    Promise<boolean>

    A promise that resolves to whether the path is a file.

    Example
    import { isFile } from 'jg-fs';
    
    await isFile('package.json'); // true
    isPathWithin
    function

    Checks whether a path is a root path or nested inside it. The check is lexical: both paths are resolved without touching the filesystem, and symbolic links aren't followed. Sibling paths that share a prefix, such as /data/app and /data/application, aren't nested, and neither is a path on another drive on Windows.

    Signature
    isPathWithin(rootPath: string, targetPath: string): boolean

    Parameters

    rootPathstring

    The root path.

    targetPathstring

    The path to check.

    Returns

    boolean

    Whether the target is the root or inside it.

    Example
    import { isPathWithin } from 'jg-fs';
    
    isPathWithin('/data/app', '/data/app/logs/today.log'); // true
    isPathWithin('/data/app', '/data/app/../secrets'); // false
    isPathWithin('/data/app', '/data/application'); // false
    pathExists
    function

    Checks whether a path exists, whether it's a file, a directory, or anything else.

    Signature
    pathExists(path: string): Promise<boolean>

    Parameters

    pathstring

    The path.

    Returns

    Promise<boolean>

    A promise that resolves to whether the path exists.

    Example
    import { pathExists } from 'jg-fs';
    
    await pathExists('config.json'); // true when the file exists
    readBoundedFile
    function

    Reads a regular file of at most maxBytes bytes. It never follows a symbolic link in the path's last component, though links in parent directories are followed, so callers that need containment combine it with isPathWithin. It reads at most maxBytes + 1 bytes, even when the file grows while it's read.

    Signature
    readBoundedFile(
      path: string,
      maxBytes: number,
    ): Promise<Buffer<ArrayBufferLike>>

    Parameters

    pathstring

    The file's path.

    maxBytesnumber

    The largest size accepted, in bytes.

    Returns

    Promise<Buffer<ArrayBufferLike>>

    A promise that resolves to the content.

    Throws

    • NOT_A_FILEif the path doesn't exist, its final component is a symbolic link, or it isn't a regular file
    • FILE_TOO_LARGEif the file holds more than maxBytes bytes
    Example
    import { join } from 'node:path';
    import { readBoundedFile } from 'jg-fs';
    
    const upload = await readBoundedFile(join('uploads', 'avatar.png'), 5 * 1024 * 1024); // at most 5 MB
    readBufferFile
    function

    Reads a binary file. An empty file reads as an empty Buffer.

    Signature
    readBufferFile(path: string): Promise<Buffer<ArrayBufferLike>>

    Parameters

    pathstring

    The file's path.

    Returns

    Promise<Buffer<ArrayBufferLike>>

    A promise that resolves to the content.

    Throws

    • NOT_A_FILEif the path doesn't exist or is not a file
    Example
    import { readBufferFile } from 'jg-fs';
    
    const image = await readBufferFile('avatar.png');
    readDirectory
    function

    Lists a directory's contents as paths joined to the directory's path.

    Signature
    readDirectory(path: string, recursive?: boolean): Promise<string[]>

    Parameters

    pathstring

    The directory's path.

    recursive?boolean

    Whether to include the contents of subdirectories.

    Defaults to false.

    Returns

    Promise<string[]>

    A promise that resolves to the content paths.

    Throws

    • NOT_A_DIRECTORYif the path doesn't exist or is not a directory
    Example
    import { readDirectory } from 'jg-fs';
    
    await readDirectory('dist'); // ['dist/index.js', 'dist/assets']
    await readDirectory('dist', true); // includes the contents of dist/assets
    readFile
    function

    Reads a file as a string when an encoding is given, or as a Buffer otherwise.

    Signature
    readFile(path: string, options: IReadStringFileOptions): Promise<string>

    Parameters

    pathstring

    The file's path.

    optionsIReadStringFileOptions

    Node.js's readFile options, such as the encoding.

    Returns

    Promise<string>

    A promise that resolves to the content.

    Throws

    • NOT_A_FILEif the path doesn't exist or is not a file
    Signature
    readFile(
      path: string,
      options?: IReadBufferFileOptions,
    ): Promise<Buffer<ArrayBufferLike>>

    Parameters

    pathstring

    The file's path.

    options?IReadBufferFileOptions

    Node.js's readFile options, such as the encoding.

    Returns

    Promise<Buffer<ArrayBufferLike>>

    A promise that resolves to the content.

    Throws

    • NOT_A_FILEif the path doesn't exist or is not a file
    Signature
    readFile(
      path: string,
      options?: IReadFileOptions,
    ): Promise<string | Buffer<ArrayBufferLike>>

    Parameters

    pathstring

    The file's path.

    options?IReadFileOptions

    Node.js's readFile options, such as the encoding.

    Returns

    Promise<string | Buffer<ArrayBufferLike>>

    A promise that resolves to the content.

    Throws

    • NOT_A_FILEif the path doesn't exist or is not a file
    Example
    import { readFile } from 'jg-fs';
    
    await readFile('notes.md', 'utf-8'); // a string
    await readFile('avatar.png'); // a Buffer
    readJsonFile
    function

    Reads and parses a JSON file, and validates it with the schema when there is one. Without a schema, the value is unknown, or the type you pass. With maxBytes, it reads the file as readBoundedFile does, for a file whose size isn't yours to decide.

    Signature
    readJsonFile<T = unknown>(
      path: string,
      options?: IReadJsonFileOptions<T>,
    ): Promise<T>

    Parameters

    pathstring

    The file's path.

    options?IReadJsonFileOptions<T>

    The schema, such as a Zod schema, whose errors propagate unchanged, and the largest file accepted, in bytes.

    Defaults to {}.

    Returns

    Promise<T>

    A promise that resolves to the parsed value.

    Throws

    • NOT_A_FILEif the path doesn't exist or is not a file
    • FILE_TOO_LARGEif the file holds more than maxBytes bytes
    • FILE_CONTENT_IS_EMPTY_OR_INVALIDif the file's JSON content cannot be parsed
    Example 1
    import { readJsonFile } from 'jg-fs';
    
    const config = await readJsonFile('config.json', { schema: Config }); // Config's type
    Example 2
    import { readJsonFile } from 'jg-fs';
    
    const value = await readJsonFile('data.json'); // unknown
    readPrivateFile
    function

    Reads a file after checking that it's private, as assertPrivateFile does, through the same open file, so the content read is the content checked. It reads at most maxBytes bytes, even when the file grows while it's read.

    Signature
    readPrivateFile(
      path: string,
      options: IReadPrivateFileOptions,
    ): Promise<Buffer<ArrayBufferLike>>

    Parameters

    pathstring

    The file's path.

    optionsIReadPrivateFileOptions

    The largest size accepted, in bytes, and the root the file must be inside.

    Returns

    Promise<Buffer<ArrayBufferLike>>

    A promise that resolves to the content.

    Throws

    • NON_EXISTENT_FILEif the file doesn't exist
    • NOT_A_FILEif the path's final component is a symbolic link, or it isn't a regular file
    • FILE_NOT_PRIVATEif other users can access the file
    • FILE_TOO_LARGEif the file holds more than maxBytes bytes
    • PATH_NOT_CANONICALif the path or the root goes through a symbolic link
    • PATH_OUTSIDE_ROOTif the file isn't strictly inside the root
    • NOT_A_DIRECTORYif the root doesn't exist or isn't a directory
    Example
    import { readPrivateFile } from 'jg-fs';
    
    const key = await readPrivateFile('/home/ada/.ssh/id_ed25519', { maxBytes: 16 * 1024 });
    readTextFile
    function

    Reads a UTF-8 text file. An empty file reads as an empty string.

    Signature
    readTextFile(path: string): Promise<string>

    Parameters

    pathstring

    The file's path.

    Returns

    Promise<string>

    A promise that resolves to the text.

    Throws

    • NOT_A_FILEif the path doesn't exist or is not a file
    Example
    import { readTextFile } from 'jg-fs';
    
    const markdown = await readTextFile('README.md');
    writeBufferFile
    function

    Writes a binary file. An empty Buffer writes an empty file.

    Signature
    writeBufferFile(path: string, data: Buffer): Promise<void>

    Parameters

    pathstring

    The file's path.

    dataBuffer

    The content.

    Returns

    Promise<void>

    A promise that resolves once the file is written.

    Throws

    • FILE_CONTENT_IS_EMPTY_OR_INVALIDif the data isn't a Buffer
    Example
    import { writeBufferFile } from 'jg-fs';
    
    const imageBuffer = Buffer.from('iVBORw0KGgo=', 'base64');
    await writeBufferFile('avatar.png', imageBuffer);
    writeFile
    function

    Writes a file, creating its directory first when it doesn't exist.

    Signature
    writeFile(
      path: string,
      data: string | ArrayBufferView<ArrayBufferLike>,
      options?: WriteFileOptions,
    ): Promise<void>

    Parameters

    pathstring

    The file's path.

    datastring | ArrayBufferView<ArrayBufferLike>

    The content.

    options?WriteFileOptions

    Node.js's writeFile options, such as the encoding or mode.

    Returns

    Promise<void>

    A promise that resolves once the file is written.

    Example
    import { writeFile } from 'jg-fs';
    
    const csv = 'id,name\n1,Ada\n';
    await writeFile('out/report.csv', csv, { encoding: 'utf-8' });
    writeFileAtomic
    function

    Writes a file atomically: readers see either the previous content or the complete new content, never a partial write. The data goes to a temporary file in the same directory, which is flushed to disk and renamed over the path; on POSIX, the directory is flushed too. An explicit mode wins; without one, a replaced file keeps its permission bits and a new file gets the platform default. The temporary file is never more widely readable than the final mode. When writing fails before the rename, the temporary file is removed and the destination is untouched. While Windows refuses the rename because another process, such as an antivirus scanner, holds the file, it tries again for about 4 seconds. A symbolic link at the path is replaced by the file.

    Signature
    writeFileAtomic(
      path: string,
      data: string | ArrayBufferView<ArrayBufferLike>,
      mode?: number,
    ): Promise<void>

    Parameters

    pathstring

    The file's path.

    datastring | ArrayBufferView<ArrayBufferLike>

    The content.

    mode?number

    The permission bits of the written file, such as 0o600.

    Returns

    Promise<void>

    A promise that resolves once the file is written and flushed.

    Example
    import { writeFileAtomic } from 'jg-fs';
    
    const state = { step: 2 };
    await writeFileAtomic('state.json', JSON.stringify(state)); // readers never see a partial file
    await writeFileAtomic('secrets.env', 'TOKEN=abc\n', 0o600); // only the owner can read it
    writeJsonFile
    function

    Writes a JSON file atomically, as writeFileAtomic does, so a crash or a full disk never leaves a truncated file that won't parse. A symbolic link at the path keeps pointing at the file, whose target is replaced. A string is written as given; anything else is stringified.

    Signature
    writeJsonFile<T = Record<string, unknown>>(
      path: string,
      data: string | T,
      space?: number,
    ): Promise<void>

    Parameters

    pathstring

    The file's path.

    datastring | T

    The value, or its JSON text.

    space?number

    The indentation for stringified values.

    Defaults to 2.

    Returns

    Promise<void>

    A promise that resolves once the file is written.

    Throws

    • FILE_CONTENT_IS_EMPTY_OR_INVALIDif the JSON content cannot be stringified or is empty
    Example
    import { writeJsonFile } from 'jg-fs';
    
    await writeJsonFile('config.json', { port: 8080 });
    writePrivateFile
    function

    Writes a file that only its owner can read and write, such as a token or a session. The file's directory must have no symbolic link in its path, and with rootPath, the file must be strictly inside that directory.

    By default, it replaces the file atomically with 0o600 permissions, through a temporary file that's never more widely readable; a file already at the path must be private, as assertPrivateFile checks, so a symbolic link or a shared file is never overwritten. With isExclusive, it creates the file and fails when anything is already at the path.

    Signature
    writePrivateFile(
      path: string,
      data: string | ArrayBufferView<ArrayBufferLike>,
      options?: IWritePrivateFileOptions,
    ): Promise<void>

    Parameters

    pathstring

    The file's path.

    datastring | ArrayBufferView<ArrayBufferLike>

    The content.

    options?IWritePrivateFileOptions

    The root the file must be inside, and whether to create it exclusively.

    Defaults to {}.

    Returns

    Promise<void>

    A promise that resolves once the file is written.

    Throws

    • FILE_ALREADY_EXISTSif isExclusive is set and anything already exists at the path
    • NOT_A_FILEif the path's final component is a symbolic link, or it isn't a regular file
    • FILE_NOT_PRIVATEif other users can access the file at the path, or the file written
    • PATH_NOT_CANONICALif the file's directory or the root goes through a symbolic link
    • PATH_OUTSIDE_ROOTif the file isn't strictly inside the root
    • NOT_A_DIRECTORYif the root or the file's directory doesn't exist or isn't a directory
    Example
    import { writePrivateFile } from 'jg-fs';
    
    await writePrivateFile('/home/ada/.config/app/token.json', '{"token":"…"}');
    writeTextFile
    function

    Writes a UTF-8 text file. An empty string writes an empty file.

    Signature
    writeTextFile(path: string, data: string): Promise<void>

    Parameters

    pathstring

    The file's path.

    datastring

    The text.

    Returns

    Promise<void>

    A promise that resolves once the file is written.

    Throws

    • FILE_CONTENT_IS_EMPTY_OR_INVALIDif the data isn't a string
    Example
    import { writeTextFile } from 'jg-fs';
    
    await writeTextFile('notes/today.md', '# Today');

    Constantsjg-fs

    ERRORS
    const

    the codes of every error the package throws, from both the async and the sync entry

    Declaration
    const ERRORS: { NOT_A_DIRECTORY: "NOT_A_DIRECTORY"; NON_EXISTENT_DIRECTORY: "NON_EXISTENT_DIRECTORY"; DIRECTORY_ALREADY_EXISTS: "DIRECTORY_ALREADY_EXISTS"; NON_EXISTENT_FILE: "NON_EXISTENT_FILE"; NOT_A_FILE: "NOT_A_FILE"; FILE_CONTENT_IS_EMPTY_OR_INVALID: "FILE_CONTENT_IS_EMPTY_OR_INVALID"; FILE_TOO_LARGE: "FILE_TOO_LARGE"; LOCK_BUSY: "LOCK_BUSY"; LOCK_NOT_OWNED: "LOCK_NOT_OWNED"; LOCK_RELEASE_FAILED: "LOCK_RELEASE_FAILED"; LOCK_ACQUISITION_FAILED: "LOCK_ACQUISITION_FAILED"; FILE_NOT_PRIVATE: "FILE_NOT_PRIVATE"; DIRECTORY_NOT_PRIVATE: "DIRECTORY_NOT_PRIVATE"; PATH_NOT_CANONICAL: "PATH_NOT_CANONICAL"; PATH_OUTSIDE_ROOT: "PATH_OUTSIDE_ROOT"; FILE_ALREADY_EXISTS: "FILE_ALREADY_EXISTS" }

    Typesjg-fs

    IDirectoryElementsKeySort
    type

    the keys a directory's elements can be sorted by

    Declaration
    type IDirectoryElementsKeySort = 'baseName' | 'size' | 'creation';
    IDirectoryElementsOptions
    type

    how getDirectoryElements filters and sorts a directory's elements

    Declaration
    type IDirectoryElementsOptions = {
        sortByKey: IDirectoryElementsKeySort;
        sortOrder: ISortDirection;
        includeExts: string[];
    };

    Members

    • sortByKeyproperty
      IDirectoryElementsKeySort

      the key the elements are sorted by. Defaults to 'baseName'

    • sortOrderproperty
      ISortDirection

      the sort direction. Defaults to 'asc'

    • includeExtsproperty
      string[]

      the extensions of the files to include, such as ['.json'], matched case-insensitively. Defaults to all

    IDirectoryPathElements
    type

    a directory's elements, grouped by kind

    Declaration
    type IDirectoryPathElements = {
        directories: IPathElement[];
        files: IPathElement[];
        symbolicLinks: IPathElement[];
    };

    Members

    • directoriesproperty
      IPathElement[]
    • filesproperty
      IPathElement[]
    • symbolicLinksproperty
      IPathElement[]
    IFileLock
    type

    a lock acquireLock holds until it's released

    Declaration
    type IFileLock = {
        path: string;
        owner: IFileLockOwner;
        release: () => Promise<void>;
    };

    Members

    • pathproperty
      string

      the lock's directory

    • ownerproperty
      IFileLockOwner

      the process that holds it, as its owner.json names it

    • releaseproperty
      () => Promise<void>

      removes the lock; calling it again after it succeeded does nothing

    IFileLockOwner
    type

    the process that holds a lock, as its lock directory's owner.json names it

    Declaration
    type IFileLockOwner = {
        pid: number;
        nonce: string;
        hostname: string;
    };

    Members

    • pidproperty
      number

      the process id

    • nonceproperty
      string

      a random id that tells this acquisition apart from any other by the same process id

    • hostnameproperty
      string

      the host the process runs on; a lock is never recovered from another host

    IPathElement
    type

    the details of a path, read with lstat, so a symbolic link is described rather than followed

    Declaration
    type IPathElement = {
        path: string;
        baseName: string;
        extName: string;
        isFile: boolean;
        isDirectory: boolean;
        isSymbolicLink: boolean;
        size: number;
        creation: number;
    };

    Members

    • pathproperty
      string

      the path, as given

    • baseNameproperty
      string

      the last segment of the path

    • extNameproperty
      string

      the extension, such as '.json', or an empty string when there is none

    • isFileproperty
      boolean

      whether it's a regular file

    • isDirectoryproperty
      boolean

      whether it's a directory

    • isSymbolicLinkproperty
      boolean

      whether it's a symbolic link, in which case isFile and isDirectory are false

    • sizeproperty
      number

      the size in bytes

    • creationproperty
      number

      when it was created, in milliseconds since the epoch

    IPrivateFileOptions
    type

    the options assertPrivateFile takes

    Declaration
    type IPrivateFileOptions = {
        rootPath?: string | undefined;
        maxBytes?: number | undefined;
    };

    Members

    • rootPathproperty
      string

      a directory the file must be strictly inside; it must exist and contain no symbolic link

    • maxBytesproperty
      number

      the largest size accepted, in bytes

    IReadJsonFileOptions
    type

    what readJsonFile accepts

    Declaration
    type IReadJsonFileOptions<T> = {
        schema?: ISchema<T>;
        maxBytes?: number;
    };

    Members

    • schemaproperty
      ISchema<T>

      validates the parsed content, such as a Zod schema. Its errors propagate unchanged

    • maxBytesproperty
      number

      the largest file accepted, in bytes, for a file whose size isn't yours to decide, such as one a user uploaded. With it, the file is read as readBoundedFile reads it

    IReadPrivateFileOptions
    type

    the options readPrivateFile takes

    Declaration
    type IReadPrivateFileOptions = {
        maxBytes: number;
        rootPath?: string | undefined;
    };

    Members

    • maxBytesproperty
      number

      the largest size accepted, in bytes; it's required, because the file's size isn't the reader's

    • rootPathproperty
      string

      a directory the file must be strictly inside; it must exist and contain no symbolic link

    ISchema
    type

    validates parsed data and returns it typed, which a Zod schema does

    Declaration
    type ISchema<T> = {
        parse(value: unknown): T;
    };

    Members

    • parsemethod
      parse(value: unknown): T
    IWritePrivateFileOptions
    type

    the options writePrivateFile takes

    Declaration
    type IWritePrivateFileOptions = {
        rootPath?: string | undefined;
        isExclusive?: boolean | undefined;
    };

    Members

    • rootPathproperty
      string

      a directory the file must be strictly inside; it must exist and contain no symbolic link

    • isExclusiveproperty
      boolean

      creates the file, and fails when anything already exists at the path, instead of replacing it

    jg-fs/sync

    Functionsjg-fs/sync

    acquireLock
    function

    Acquires a lock that only one process at a time can hold, such as one guarding a script that mustn't run twice at once. The lock is a directory at path, created with mode 0o700, holding an owner.json, mode 0o600, that names the process.

    When the lock already exists, it's taken over only from an owner on this host whose process is gone, and only when the directory holds nothing but owner.json. Every other case fails with LOCK_BUSY, whose data.reason says why: held, other-host, ownerless, recovery-in-progress, or unexpected-entries. A lock left behind by a crash at the wrong moment stays busy until it's removed by hand, as the README describes.

    Use it on local filesystems only. POSIX modes don't apply on Windows, and a lock is never taken over from another host.

    Signature
    acquireLock(path: string): IFileLock

    Parameters

    pathstring

    The lock's directory, whose parent must exist.

    Returns

    IFileLock

    The lock, released with release().

    Throws

    • LOCK_BUSYThe lock '<path>' is busy.
    • NOT_A_DIRECTORYThe path '<path>' is not a directory.
    • LOCK_ACQUISITION_FAILEDThe lock '<path>' could not be acquired.
    Example
    import { acquireLock, writeFileAtomic } from 'jg-fs/sync';
    
    const lock = acquireLock('.locks/nightly-report');
    try {
      writeFileAtomic('reports/nightly.json', JSON.stringify({ generatedAt: Date.now() }));
    } finally {
      lock.release();
    }
    assertPrivateDirectory
    function

    Checks that a directory is private to its owner: a directory that isn't a symbolic link, with no symbolic link anywhere in its path, and, on POSIX, without group or other permissions. On Windows, the permission check is skipped, as for files.

    Signature
    assertPrivateDirectory(path: string): void

    Parameters

    pathstring

    The directory's path.

    Throws

    • NOT_A_DIRECTORYif the path doesn't exist, is a symbolic link, or isn't a directory
    • PATH_NOT_CANONICALif the path goes through a symbolic link
    • DIRECTORY_NOT_PRIVATEif other users can access the directory
    Example
    import { assertPrivateDirectory } from 'jg-fs/sync';
    
    assertPrivateDirectory('/home/ada/.config/app');
    assertPrivateFile
    function

    Checks that a file is private to its owner, such as a key or a session file before it's trusted: a regular file that isn't a symbolic link, with no symbolic link anywhere in its path, and, on POSIX, without group or other permissions. With rootPath, it must be strictly inside that directory; with maxBytes, no larger. The checks run on the open file, so a file swapped in between them is caught.

    On Windows, Node.js reports only a file's read-only attribute as its mode, so the permission check is skipped there: a private file on Windows depends on its directory's access control list.

    Signature
    assertPrivateFile(path: string, options?: IPrivateFileOptions): void

    Parameters

    pathstring

    The file's path.

    options?IPrivateFileOptions

    The root it must be inside, and the largest size accepted, in bytes.

    Defaults to {}.

    Throws

    • NON_EXISTENT_FILEif the file doesn't exist
    • NOT_A_FILEif the path's final component is a symbolic link, or it isn't a regular file
    • FILE_NOT_PRIVATEif other users can access the file
    • FILE_TOO_LARGEif the file holds more than maxBytes bytes
    • PATH_NOT_CANONICALif the path or the root goes through a symbolic link
    • PATH_OUTSIDE_ROOTif the file isn't strictly inside the root
    • NOT_A_DIRECTORYif the root doesn't exist or isn't a directory
    Example
    import { assertPrivateFile } from 'jg-fs/sync';
    
    assertPrivateFile('/home/ada/.config/app/token.json', { rootPath: '/home/ada/.config/app' });
    copyDirectory
    function

    Copies a directory and everything in it, replacing the destination completely.

    Signature
    copyDirectory(srcPath: string, destPath: string): void

    Parameters

    srcPathstring

    The directory to copy.

    destPathstring

    Where to copy it.

    Throws

    • NOT_A_DIRECTORYif the srcPath is not a directory
    Example
    import { copyDirectory } from 'jg-fs/sync';
    
    copyDirectory('templates', 'dist/templates');
    copyFile
    function

    Copies a file, replacing the destination when it exists.

    Signature
    copyFile(srcPath: string, destPath: string): void

    Parameters

    srcPathstring

    The file to copy.

    destPathstring

    Where to copy it.

    Throws

    • NOT_A_FILEif the srcPath doesn't exist or is not a file
    Example
    import { copyFile } from 'jg-fs/sync';
    
    copyFile('config.example.json', 'config.json');
    createDirectory
    function

    Creates a directory, with any missing parents. An existing directory is kept, or replaced with an empty one when deleteIfExists is set.

    Signature
    createDirectory(path: string, deleteIfExists?: boolean): void

    Parameters

    pathstring

    The directory's path.

    deleteIfExists?boolean

    Whether to delete an existing directory and its contents first.

    Throws

    • DIRECTORY_ALREADY_EXISTSif something other than a directory exists at the path
    Example
    import { createDirectory } from 'jg-fs/sync';
    
    createDirectory('dist/assets'); // creates missing parents too
    createDirectory('dist', true); // replaces it with an empty directory
    createPrivateDirectory
    function

    Creates a directory, and any missing parents, with 0o700 permissions, and checks that it's private, as assertPrivateDirectory does. An existing directory that isn't private throws: its permissions are never changed silently.

    Signature
    createPrivateDirectory(path: string): void

    Parameters

    pathstring

    The directory's path.

    Throws

    • NOT_A_DIRECTORYif the path exists and isn't a directory
    • PATH_NOT_CANONICALif the path goes through a symbolic link
    • DIRECTORY_NOT_PRIVATEif other users can access the directory
    Example
    import { createPrivateDirectory } from 'jg-fs/sync';
    
    createPrivateDirectory('/home/ada/.config/app/sessions');
    deleteDirectory
    function

    Deletes a directory and everything in it. A missing directory is not an error.

    Signature
    deleteDirectory(path: string): void

    Parameters

    pathstring

    The directory's path.

    Example
    import { deleteDirectory } from 'jg-fs/sync';
    
    deleteDirectory('dist');
    deleteFile
    function

    Deletes a file.

    Signature
    deleteFile(path: string): void

    Parameters

    pathstring

    The file's path.

    Throws

    • NOT_A_FILEif the path doesn't exist or is not a file
    Example
    import { deleteFile } from 'jg-fs/sync';
    
    deleteFile('cache.json');
    getDirectoryElements
    function

    Lists a directory's elements grouped by kind: directories, files (filtered by extension), and symbolic links, each group sorted. Entries removed while the directory is read are left out.

    Signature
    getDirectoryElements(
      path: string,
      options?: Partial<IDirectoryElementsOptions>,
    ): IDirectoryPathElements

    Parameters

    pathstring

    The directory's path.

    options?Partial<IDirectoryElementsOptions>

    The extensions to include and how to sort.

    Returns

    IDirectoryPathElements

    The grouped elements.

    Throws

    • NOT_A_DIRECTORYif the path doesn't exist or is not a directory
    Example
    import { getDirectoryElements } from 'jg-fs/sync';
    
    const { files } = getDirectoryElements('content', { includeExts: ['.md'], sortByKey: 'creation', sortOrder: 'desc' });
    getPathElement
    function

    Reads a path's details without following a symbolic link at the path.

    Signature
    getPathElement(path: string): IPathElement | null

    Parameters

    pathstring

    The path.

    Returns

    IPathElement | null

    The path's details, or null when it doesn't exist.

    Example
    import { getPathElement } from 'jg-fs/sync';
    
    const element = getPathElement('logo.png');
    element?.size; // the size in bytes, or undefined when the path doesn't exist
    isDirectory
    function

    Checks whether a path exists and is a directory.

    Signature
    isDirectory(path: string): boolean

    Parameters

    pathstring

    The path.

    Returns

    boolean

    Whether the path is a directory.

    Example
    import { isDirectory } from 'jg-fs/sync';
    
    isDirectory('dist'); // true
    isFile
    function

    Checks whether a path exists and is a regular file.

    Signature
    isFile(path: string): boolean

    Parameters

    pathstring

    The path.

    Returns

    boolean

    Whether the path is a file.

    Example
    import { isFile } from 'jg-fs/sync';
    
    isFile('package.json'); // true
    isPathWithin
    function

    Checks whether a path is a root path or nested inside it. The check is lexical: both paths are resolved without touching the filesystem, and symbolic links aren't followed. Sibling paths that share a prefix, such as /data/app and /data/application, aren't nested, and neither is a path on another drive on Windows.

    Signature
    isPathWithin(rootPath: string, targetPath: string): boolean

    Parameters

    rootPathstring

    The root path.

    targetPathstring

    The path to check.

    Returns

    boolean

    Whether the target is the root or inside it.

    Example
    import { isPathWithin } from 'jg-fs';
    
    isPathWithin('/data/app', '/data/app/logs/today.log'); // true
    isPathWithin('/data/app', '/data/app/../secrets'); // false
    isPathWithin('/data/app', '/data/application'); // false
    pathExists
    function

    Checks whether a path exists, whether it's a file, a directory, or anything else.

    Signature
    pathExists(path: string): boolean

    Parameters

    pathstring

    The path.

    Returns

    boolean

    Whether the path exists.

    Example
    import { pathExists } from 'jg-fs/sync';
    
    pathExists('config.json'); // true when the file exists
    readBoundedFile
    function

    Reads a regular file of at most maxBytes bytes. It never follows a symbolic link in the path's last component, though links in parent directories are followed, so callers that need containment combine it with isPathWithin. It reads at most maxBytes + 1 bytes, even when the file grows while it's read.

    Signature
    readBoundedFile(path: string, maxBytes: number): Buffer

    Parameters

    pathstring

    The file's path.

    maxBytesnumber

    The largest size accepted, in bytes.

    Returns

    Buffer

    The content.

    Throws

    • NOT_A_FILEif the path doesn't exist, its final component is a symbolic link, or it isn't a regular file
    • FILE_TOO_LARGEif the file holds more than maxBytes bytes
    Example
    import { join } from 'node:path';
    import { readBoundedFile } from 'jg-fs/sync';
    
    const upload = readBoundedFile(join('uploads', 'avatar.png'), 5 * 1024 * 1024); // at most 5 MB
    readBufferFile
    function

    Reads a binary file. An empty file reads as an empty Buffer.

    Signature
    readBufferFile(path: string): Buffer

    Parameters

    pathstring

    The file's path.

    Returns

    Buffer

    The content.

    Throws

    • NOT_A_FILEif the path doesn't exist or is not a file
    Example
    import { readBufferFile } from 'jg-fs/sync';
    
    const image = readBufferFile('avatar.png');
    readDirectory
    function

    Lists a directory's contents as paths joined to the directory's path.

    Signature
    readDirectory(path: string, recursive?: boolean): string[]

    Parameters

    pathstring

    The directory's path.

    recursive?boolean

    Whether to include the contents of subdirectories.

    Defaults to false.

    Returns

    string[]

    The content paths.

    Throws

    • NOT_A_DIRECTORYif the path doesn't exist or is not a directory
    Example
    import { readDirectory } from 'jg-fs/sync';
    
    readDirectory('dist'); // ['dist/index.js', 'dist/assets']
    readDirectory('dist', true); // includes the contents of dist/assets
    readFile
    function

    Reads a file as a string when an encoding is given, or as a Buffer otherwise.

    Signature
    readFile(path: string, options: IReadStringFileOptions): string

    Parameters

    pathstring

    The file's path.

    optionsIReadStringFileOptions

    Node.js's readFile options, such as the encoding.

    Returns

    string

    The content.

    Throws

    • NOT_A_FILEif the path doesn't exist or is not a file
    Signature
    readFile(path: string, options?: IReadBufferFileOptions): Buffer

    Parameters

    pathstring

    The file's path.

    options?IReadBufferFileOptions

    Node.js's readFile options, such as the encoding.

    Returns

    Buffer

    The content.

    Throws

    • NOT_A_FILEif the path doesn't exist or is not a file
    Signature
    readFile(
      path: string,
      options?: IReadFileOptions,
    ): string | Buffer<ArrayBufferLike>

    Parameters

    pathstring

    The file's path.

    options?IReadFileOptions

    Node.js's readFile options, such as the encoding.

    Returns

    string | Buffer<ArrayBufferLike>

    The content.

    Throws

    • NOT_A_FILEif the path doesn't exist or is not a file
    Example
    import { readFile } from 'jg-fs/sync';
    
    readFile('notes.md', 'utf-8'); // a string
    readFile('avatar.png'); // a Buffer
    readJsonFile
    function

    Reads and parses a JSON file, and validates it with the schema when there is one. Without a schema, the value is unknown, or the type you pass. With maxBytes, it reads the file as readBoundedFile does, for a file whose size isn't yours to decide.

    Signature
    readJsonFile<T = unknown>(path: string, options?: IReadJsonFileOptions<T>): T

    Parameters

    pathstring

    The file's path.

    options?IReadJsonFileOptions<T>

    The schema, such as a Zod schema, whose errors propagate unchanged, and the largest file accepted, in bytes.

    Defaults to {}.

    Returns

    T

    The parsed value.

    Throws

    • NOT_A_FILEif the path doesn't exist or is not a file
    • FILE_TOO_LARGEif the file holds more than maxBytes bytes
    • FILE_CONTENT_IS_EMPTY_OR_INVALIDif the file's JSON content cannot be parsed
    Example 1
    import { readJsonFile } from 'jg-fs/sync';
    
    const config = readJsonFile('config.json', { schema: Config }); // Config's type
    Example 2
    import { readJsonFile } from 'jg-fs/sync';
    
    const value = readJsonFile('data.json'); // unknown
    readPrivateFile
    function

    Reads a file after checking that it's private, as assertPrivateFile does, through the same open file, so the content read is the content checked. It reads at most maxBytes bytes, even when the file grows while it's read.

    Signature
    readPrivateFile(path: string, options: IReadPrivateFileOptions): Buffer

    Parameters

    pathstring

    The file's path.

    optionsIReadPrivateFileOptions

    The largest size accepted, in bytes, and the root the file must be inside.

    Returns

    Buffer

    The content.

    Throws

    • NON_EXISTENT_FILEif the file doesn't exist
    • NOT_A_FILEif the path's final component is a symbolic link, or it isn't a regular file
    • FILE_NOT_PRIVATEif other users can access the file
    • FILE_TOO_LARGEif the file holds more than maxBytes bytes
    • PATH_NOT_CANONICALif the path or the root goes through a symbolic link
    • PATH_OUTSIDE_ROOTif the file isn't strictly inside the root
    • NOT_A_DIRECTORYif the root doesn't exist or isn't a directory
    Example
    import { readPrivateFile } from 'jg-fs/sync';
    
    const key = readPrivateFile('/home/ada/.ssh/id_ed25519', { maxBytes: 16 * 1024 });
    readTextFile
    function

    Reads a UTF-8 text file. An empty file reads as an empty string.

    Signature
    readTextFile(path: string): string

    Parameters

    pathstring

    The file's path.

    Returns

    string

    The text.

    Throws

    • NOT_A_FILEif the path doesn't exist or is not a file
    Example
    import { readTextFile } from 'jg-fs/sync';
    
    const markdown = readTextFile('README.md');
    writeBufferFile
    function

    Writes a binary file. An empty Buffer writes an empty file.

    Signature
    writeBufferFile(path: string, data: Buffer): void

    Parameters

    pathstring

    The file's path.

    dataBuffer

    The content.

    Throws

    • FILE_CONTENT_IS_EMPTY_OR_INVALIDif the data isn't a Buffer
    Example
    import { writeBufferFile } from 'jg-fs/sync';
    
    const imageBuffer = Buffer.from('iVBORw0KGgo=', 'base64');
    writeBufferFile('avatar.png', imageBuffer);
    writeFile
    function

    Writes a file, creating its directory first when it doesn't exist.

    Signature
    writeFile(
      path: string,
      data: string | ArrayBufferView<ArrayBufferLike>,
      options?: WriteFileOptions,
    ): void

    Parameters

    pathstring

    The file's path.

    datastring | ArrayBufferView<ArrayBufferLike>

    The content.

    options?WriteFileOptions

    Node.js's writeFile options, such as the encoding or mode.

    Example
    import { writeFile } from 'jg-fs/sync';
    
    const csv = 'id,name\n1,Ada\n';
    writeFile('out/report.csv', csv, { encoding: 'utf-8' });
    writeFileAtomic
    function

    Writes a file atomically: readers see either the previous content or the complete new content, never a partial write. The data goes to a temporary file in the same directory, which is flushed to disk and renamed over the path; on POSIX, the directory is flushed too. An explicit mode wins; without one, a replaced file keeps its permission bits and a new file gets the platform default. The temporary file is never more widely readable than the final mode. When writing fails before the rename, the temporary file is removed and the destination is untouched. While Windows refuses the rename because another process, such as an antivirus scanner, holds the file, it tries again for about 4 seconds. A symbolic link at the path is replaced by the file.

    Signature
    writeFileAtomic(
      path: string,
      data: string | ArrayBufferView<ArrayBufferLike>,
      mode?: number,
    ): void

    Parameters

    pathstring

    The file's path.

    datastring | ArrayBufferView<ArrayBufferLike>

    The content.

    mode?number

    The permission bits of the written file, such as 0o600.

    Example
    import { writeFileAtomic } from 'jg-fs/sync';
    
    const state = { step: 2 };
    writeFileAtomic('state.json', JSON.stringify(state)); // readers never see a partial file
    writeFileAtomic('secrets.env', 'TOKEN=abc\n', 0o600); // only the owner can read it
    writeJsonFile
    function

    Writes a JSON file atomically, as writeFileAtomic does, so a crash or a full disk never leaves a truncated file that won't parse. A symbolic link at the path keeps pointing at the file, whose target is replaced. A string is written as given; anything else is stringified.

    Signature
    writeJsonFile<T = Record<string, unknown>>(
      path: string,
      data: string | T,
      space?: number,
    ): void

    Parameters

    pathstring

    The file's path.

    datastring | T

    The value, or its JSON text.

    space?number

    The indentation for stringified values.

    Defaults to 2.

    Throws

    • FILE_CONTENT_IS_EMPTY_OR_INVALIDif the JSON content cannot be stringified or is empty
    Example
    import { writeJsonFile } from 'jg-fs/sync';
    
    writeJsonFile('config.json', { port: 8080 });
    writePrivateFile
    function

    Writes a file that only its owner can read and write, such as a token or a session. The file's directory must have no symbolic link in its path, and with rootPath, the file must be strictly inside that directory.

    By default, it replaces the file atomically with 0o600 permissions, through a temporary file that's never more widely readable; a file already at the path must be private, as assertPrivateFile checks, so a symbolic link or a shared file is never overwritten. With isExclusive, it creates the file and fails when anything is already at the path.

    Signature
    writePrivateFile(
      path: string,
      data: string | ArrayBufferView<ArrayBufferLike>,
      options?: IWritePrivateFileOptions,
    ): void

    Parameters

    pathstring

    The file's path.

    datastring | ArrayBufferView<ArrayBufferLike>

    The content.

    options?IWritePrivateFileOptions

    The root the file must be inside, and whether to create it exclusively.

    Defaults to {}.

    Throws

    • FILE_ALREADY_EXISTSif isExclusive is set and anything already exists at the path
    • NOT_A_FILEif the path's final component is a symbolic link, or it isn't a regular file
    • FILE_NOT_PRIVATEif other users can access the file at the path, or the file written
    • PATH_NOT_CANONICALif the file's directory or the root goes through a symbolic link
    • PATH_OUTSIDE_ROOTif the file isn't strictly inside the root
    • NOT_A_DIRECTORYif the root or the file's directory doesn't exist or isn't a directory
    Example
    import { writePrivateFile } from 'jg-fs/sync';
    
    writePrivateFile('/home/ada/.config/app/token.json', '{"token":"…"}');
    writeTextFile
    function

    Writes a UTF-8 text file. An empty string writes an empty file.

    Signature
    writeTextFile(path: string, data: string): void

    Parameters

    pathstring

    The file's path.

    datastring

    The text.

    Throws

    • FILE_CONTENT_IS_EMPTY_OR_INVALIDif the data isn't a string
    Example
    import { writeTextFile } from 'jg-fs/sync';
    
    writeTextFile('notes/today.md', '# Today');

    Constantsjg-fs/sync

    ERRORS
    const

    the codes of every error the package throws, from both the async and the sync entry

    Declaration
    const ERRORS: { NOT_A_DIRECTORY: "NOT_A_DIRECTORY"; NON_EXISTENT_DIRECTORY: "NON_EXISTENT_DIRECTORY"; DIRECTORY_ALREADY_EXISTS: "DIRECTORY_ALREADY_EXISTS"; NON_EXISTENT_FILE: "NON_EXISTENT_FILE"; NOT_A_FILE: "NOT_A_FILE"; FILE_CONTENT_IS_EMPTY_OR_INVALID: "FILE_CONTENT_IS_EMPTY_OR_INVALID"; FILE_TOO_LARGE: "FILE_TOO_LARGE"; LOCK_BUSY: "LOCK_BUSY"; LOCK_NOT_OWNED: "LOCK_NOT_OWNED"; LOCK_RELEASE_FAILED: "LOCK_RELEASE_FAILED"; LOCK_ACQUISITION_FAILED: "LOCK_ACQUISITION_FAILED"; FILE_NOT_PRIVATE: "FILE_NOT_PRIVATE"; DIRECTORY_NOT_PRIVATE: "DIRECTORY_NOT_PRIVATE"; PATH_NOT_CANONICAL: "PATH_NOT_CANONICAL"; PATH_OUTSIDE_ROOT: "PATH_OUTSIDE_ROOT"; FILE_ALREADY_EXISTS: "FILE_ALREADY_EXISTS" }

    Typesjg-fs/sync

    IDirectoryElementsKeySort
    type

    the keys a directory's elements can be sorted by

    Declaration
    type IDirectoryElementsKeySort = 'baseName' | 'size' | 'creation';
    IDirectoryElementsOptions
    type

    how getDirectoryElements filters and sorts a directory's elements

    Declaration
    type IDirectoryElementsOptions = {
        sortByKey: IDirectoryElementsKeySort;
        sortOrder: ISortDirection;
        includeExts: string[];
    };

    Members

    • sortByKeyproperty
      IDirectoryElementsKeySort

      the key the elements are sorted by. Defaults to 'baseName'

    • sortOrderproperty
      ISortDirection

      the sort direction. Defaults to 'asc'

    • includeExtsproperty
      string[]

      the extensions of the files to include, such as ['.json'], matched case-insensitively. Defaults to all

    IDirectoryPathElements
    type

    a directory's elements, grouped by kind

    Declaration
    type IDirectoryPathElements = {
        directories: IPathElement[];
        files: IPathElement[];
        symbolicLinks: IPathElement[];
    };

    Members

    • directoriesproperty
      IPathElement[]
    • filesproperty
      IPathElement[]
    • symbolicLinksproperty
      IPathElement[]
    IFileLock
    type

    a lock acquireLock holds until it's released

    Declaration
    type IFileLock = {
        path: string;
        owner: IFileLockOwner;
        release: () => void;
    };

    Members

    • pathproperty
      string

      the lock's directory

    • ownerproperty
      IFileLockOwner

      the process that holds it, as its owner.json names it

    • releaseproperty
      () => void

      removes the lock; calling it again after it succeeded does nothing

    IFileLockOwner
    type

    the process that holds a lock, as its lock directory's owner.json names it

    Declaration
    type IFileLockOwner = {
        pid: number;
        nonce: string;
        hostname: string;
    };

    Members

    • pidproperty
      number

      the process id

    • nonceproperty
      string

      a random id that tells this acquisition apart from any other by the same process id

    • hostnameproperty
      string

      the host the process runs on; a lock is never recovered from another host

    IPathElement
    type

    the details of a path, read with lstat, so a symbolic link is described rather than followed

    Declaration
    type IPathElement = {
        path: string;
        baseName: string;
        extName: string;
        isFile: boolean;
        isDirectory: boolean;
        isSymbolicLink: boolean;
        size: number;
        creation: number;
    };

    Members

    • pathproperty
      string

      the path, as given

    • baseNameproperty
      string

      the last segment of the path

    • extNameproperty
      string

      the extension, such as '.json', or an empty string when there is none

    • isFileproperty
      boolean

      whether it's a regular file

    • isDirectoryproperty
      boolean

      whether it's a directory

    • isSymbolicLinkproperty
      boolean

      whether it's a symbolic link, in which case isFile and isDirectory are false

    • sizeproperty
      number

      the size in bytes

    • creationproperty
      number

      when it was created, in milliseconds since the epoch

    IPrivateFileOptions
    type

    the options assertPrivateFile takes

    Declaration
    type IPrivateFileOptions = {
        rootPath?: string | undefined;
        maxBytes?: number | undefined;
    };

    Members

    • rootPathproperty
      string

      a directory the file must be strictly inside; it must exist and contain no symbolic link

    • maxBytesproperty
      number

      the largest size accepted, in bytes

    IReadJsonFileOptions
    type

    what readJsonFile accepts

    Declaration
    type IReadJsonFileOptions<T> = {
        schema?: ISchema<T>;
        maxBytes?: number;
    };

    Members

    • schemaproperty
      ISchema<T>

      validates the parsed content, such as a Zod schema. Its errors propagate unchanged

    • maxBytesproperty
      number

      the largest file accepted, in bytes, for a file whose size isn't yours to decide, such as one a user uploaded. With it, the file is read as readBoundedFile reads it

    IReadPrivateFileOptions
    type

    the options readPrivateFile takes

    Declaration
    type IReadPrivateFileOptions = {
        maxBytes: number;
        rootPath?: string | undefined;
    };

    Members

    • maxBytesproperty
      number

      the largest size accepted, in bytes; it's required, because the file's size isn't the reader's

    • rootPathproperty
      string

      a directory the file must be strictly inside; it must exist and contain no symbolic link

    ISchema
    type

    validates parsed data and returns it typed, which a Zod schema does

    Declaration
    type ISchema<T> = {
        parse(value: unknown): T;
    };

    Members

    • parsemethod
      parse(value: unknown): T
    IWritePrivateFileOptions
    type

    the options writePrivateFile takes

    Declaration
    type IWritePrivateFileOptions = {
        rootPath?: string | undefined;
        isExclusive?: boolean | undefined;
    };

    Members

    • rootPathproperty
      string

      a directory the file must be strictly inside; it must exist and contain no symbolic link

    • isExclusiveproperty
      boolean

      creates the file, and fails when anything already exists at the path, instead of replacing it