{"id":28281986,"url":"https://github.com/lexborisoff/fs-hooks","last_synced_at":"2026-02-09T01:04:18.678Z","repository":{"id":255437732,"uuid":"850470367","full_name":"LexBorisoff/fs-hooks","owner":"LexBorisoff","description":"Library for working with the file system in Node.js","archived":false,"fork":false,"pushed_at":"2025-01-19T01:31:10.000Z","size":543,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"dev","last_synced_at":"2025-05-21T12:18:38.639Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/LexBorisoff.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-08-31T21:27:33.000Z","updated_at":"2025-01-19T01:30:38.000Z","dependencies_parsed_at":"2024-09-12T08:43:09.788Z","dependency_job_id":"b6217406-8fcd-41e0-992d-2467472d8324","html_url":"https://github.com/LexBorisoff/fs-hooks","commit_stats":null,"previous_names":["lexjs-dev/core","lexborisoff/fs-hooks","lexborisoff/tree-hooks"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/LexBorisoff/fs-hooks","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LexBorisoff%2Ffs-hooks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LexBorisoff%2Ffs-hooks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LexBorisoff%2Ffs-hooks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LexBorisoff%2Ffs-hooks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LexBorisoff","download_url":"https://codeload.github.com/LexBorisoff/fs-hooks/tar.gz/refs/heads/dev","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LexBorisoff%2Ffs-hooks/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260268156,"owners_count":22983598,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-05-21T12:14:17.442Z","updated_at":"2026-02-09T01:04:18.660Z","avatar_url":"https://github.com/LexBorisoff.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `fs-hooks`\n\n![Build](https://img.shields.io/github/actions/workflow/status/LexBorisoff/fs-hooks/release.yml)\n![Codecov](https://img.shields.io/codecov/c/gh/LexBorisoff/fs-hooks)\n![NPM Version](https://img.shields.io/npm/v/fs-hooks)\n![Static Badge](https://img.shields.io/badge/package-ESM--only-ffe536)\n\nThis library allows you to work with the file system in Node.js by defining a tree of files and directories and a set of methods called hooks. A hook is a function that performs some action on a given file or directory from the tree, such as reading/writing to a file, creating/deleting a file/directory, etc. Any action that is often performed on files and directories and that could be abstracted away is a candidate for a hook.\n\n- [Installation](#installation)\n- [Usage](#usage)\n  - [Instantiation](#instantiation)\n  - [Hooks registration](#hooks-registration)\n  - [Using hooks](#using-hooks)\n- [Tree](#tree)\n  - [Creating the tree in the file system](#creating-the-tree-in-the-file-system)\n- [Hooks](#hooks)\n  - [Creating hooks](#creating-hooks)\n  - [Target file and directory objects](#target-file-and-directory-objects)\n  - [Utility methods](#utility-methods)\n- [Core Hooks](#core-hooks)\n  - [File Core Hooks](#file-core-hooks)\n  - [Directory Core Hooks](#directory-core-hooks)\n\n## Installation\n\n```bash\nnpm install fs-hooks\n```\n\n```bash\npnpm add fs-hooks\n```\n\n```bash\nyarn add fs-hooks\n```\n\n## Usage\n\n### Instantiation\n\nTo use fs-hooks, you need to instantiate the `FsHooks` class by providing it two arguments:\n\n- a root path string\n- an object representing the tree of files and directories ([see here](#tree))\n\n```typescript\nimport { FsHooks } from 'fs-hooks';\n\nconst fsHooks = new FsHooks('/path/to/tree/root', {\n  file1: 'File 1 data',\n  dir1: {\n    file2: 'File 2 data',\n  },\n});\n```\n\n\u003e ⚠️ It is important to create the tree in the file system, if it doesn't already exist, before using hooks - [see here](#creating-the-tree-in-the-file-system).\n\n### Hooks registration\n\nTo register hooks, call the `useHooks` method on the created `FsHooks` instance. The method accepts an object that defines arbitrary file and directory hooks.\n\n\u003e ⚡ Learn more about hooks, how they work, and how to define them [here](#hooks).  \n\nThis library exports a pre-defined object with some common hooks, called `coreHooks` exported from `fs-hooks/core`.\n\n```typescript\nimport { coreHooks } from 'fs-hooks/core';\n\n/* statements */\n\nconst hooks = fsHooks.useHooks(coreHooks);\n```\n\n\u003e ⚡ Learn about core hooks [here](#core-hooks).\n\n### Using hooks\n\nThe returned value from calling the `useHooks` method is a function that accepts a callback whose only argument is the tree, and whose return value is a property of that tree that you want to work with (*\"hook into\"*). You are now able to work with the tree by using hooks.\n\n```typescript\nconst file1 = hooks((root) =\u003e root.file1);\nconst dir1 = hooks((root) =\u003e root.dir1);\n\n// file core hooks\nfile1.getPath();\nfile1.read();\nfile1.write('File 1 new data');\nfile1.clear();\n\n// dir core hooks\ndir1.getPath();\ndir1.exists('file2');\ndir1.dirCreate('new-dir');\ndir1.dirDelete('new-dir');\ndir1.fileRead('file2');\ndir1.fileWrite('file2', 'File 2 new data');\ndir1.fileClear('file2');\ndir1.fileCreate('new-file');\ndir1.fileDelete('new-file');\n```\n\n## Tree\n\nThe tree object represents a structure of files and directories that you will be working with via hooks. Each property key is the name of the corresponding file or directory, and its value determines whether the property is a file or a directory:\n\n- A ***file*** is represented as a `string` whose value is the initial content of the file. This content will be written when calling the `createTree` function ([see here](#creating-the-tree-in-the-file-system)).\n- A ***directory*** is represented as an object of type `TreeInterface` and contains files and/or other directories (the tree itself is of type `TreeInterface`).\n\nFor example:\n\n```typescript\nconst fsHooks = new FsHooks('/path/to/tree/root', {\n  file1: 'File 1 data',\n  'file2.html': getHtmlContent(), // returns a string\n  'file3.css': getCssContent(), // returns a string\n  dir1: {}, // empty directory\n  dir2: {\n    file5: 'File 5 data',\n    'file6.js': getJsContent(), // returns a string\n    dir3: {\n      file7: 'File 7 data',\n      'file8.sh': getBashContent(), // returns a string\n    },\n  },\n});\n```\n\nIf you are creating a standalone tree object and want to have type safety, use the `TreeInterface` type exported from `fs-hooks`:\n\n```typescript\nimport { FsHooks, type TreeInterface } from 'fs-hooks';\n\nconst tree = {\n  /* tree definition */\n} satisfies TreeInterface;\n\nconst fsHooks = new FsHooks('/path/to/tree/root', tree);\n```\n\n\u003e ⚠️ It is important to use the `satisfies` keyword instead of annotating the variable, otherwise you will not get the TypeScript autocompletion features when using the hooks! Make sure your TypeScript version supports it.\n\n### Creating the tree in the file system\n\nIf the tree that was provided when instantiating the `FsHooks` class does not exist in the file system, it is important that you create it before using hooks. The tree can be created by calling the `createTree` function that accepts an `FsHooks` instance:\n\n```typescript\nimport { createTree } from 'fs-hooks';\n// import an FsHooks object from your source files, e.g.\nimport fsHooks from './my-hooks.js';\n\ncreateTree(fsHooks);\n```\n\nThe `createTree` function traverses through the tree properties creating files and directories.\n\n- If the file already exists, the function overwrites its contents.\n- If the directory exists, the function skips it.\n\nIt returns an array of `CreateTreeError` errors.\n\n```typescript\nfunction createTree(fsHooks: FsHooks\u003cTreeInterface\u003e): CreateTreeError[]\n```\n\n## Hooks\n\nA hook is a function that performs some action on a given file or directory from the tree. You can create as many or as few hooks as you'd like for the same tree by registering them with the `useHooks` method on an `FsHooks` instance.\n\nThe `useHooks` method accepts an object that has 2 properties:\n\n- `file` - a function that takes a `targetFile` object of type `FileTargetInterface` and returns an object with file hooks.\n- `dir` - a function that takes a `targetDir` object of type `DirTargetInterface` and returns an object with directory hooks.\n\n```typescript\n// describes targetFile\ninterface FileTargetInterface {\n  type: 'file';\n  path: string;\n}\n\n// describes targetDir\ninterface DirTargetInterface\u003cTree extends TreeInterface\u003e {\n  type: 'dir';\n  children: ObjectTreeType\u003cTree\u003e;\n  path: string;\n}\n\ntype ObjectTreeType\u003cTree extends TreeInterface\u003e = {\n  [key in keyof Tree]: Tree[key] extends string\n    ? FileTargetInterface\n    : Tree[key] extends TreeInterface\n      ? DirTargetInterface\u003cTree[key]\u003e\n      : never;\n};\n```\n\n### Creating hooks\n\nHere's a small example of how to create your own hooks:\n\n```typescript\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nconst fsHooks = new FsHooks('/root/path', {\n  dir1: {\n    dir2: {\n      file1: 'File 1 data',\n    },\n  },\n});\n\nconst hooks = fsHooks.useHooks({\n  file: (targetFile) =\u003e ({\n    read() {\n      return fs.readFileSync(targetFile.path, 'utf-8');\n    },\n    /* other file hooks */\n  }),\n  dir: (targetDir) =\u003e ({\n    readFile(fileName: string) {\n      const filePath = path.resolve(targetDir.path, fileName);\n      return fs.readFileSync(filePath, 'utf-8');\n    },\n    /* other directory hooks */\n  }),\n});\n```\n\n### Target file and directory objects\n\nThe `file` method accepts an argument of type `FileTargetInterface` and the `dir` method accepts an argument of type `DirTargetInterface`. These arguments are objects that *represent* the selected file or directory from the tree when you call the function returned from the `useHooks` method. Following the above example, when selecting a file and a directory like this:\n\n```typescript\nconst file1 = hooks((root) =\u003e root.dir1.dir2.file1);\nconst dir1 = hooks((root) =\u003e root.dir1);\n```\n\nthe `targetFile` would have the following value:\n\n```typescript\n{\n  type: 'file',\n  path: '/root/path/dir1/dir2/file1',\n}\n```\n\nand `targetDir` would be as follows:\n\n```typescript\n{\n  type: 'dir',\n  path: '/root/path/dir1',\n  children: {\n    dir2: {\n      type: 'dir',\n      path: '/root/path/dir1/dir2',\n      children: {\n        type: 'file',\n        path: '/root/path/dir1/dir2/file1',\n      }\n    }\n  },\n}\n```\n\n### Utility methods\n\nThe `FsHooks` class has static utility methods that help you create hooks that can be provided to the `useHooks` method. This could be useful when you want to export common hooks, or if some of your hooks need to return the hooks themselves, for example when creating a new file or directory.\n\n```typescript\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { FsHooks } from 'fs-hooks';\n\nexport const fileHooks = FsHooks.fileHooks((targetFile) =\u003e ({\n  read() {\n    return fs.readFileSync(targetFile.path, 'utf-8');\n  }\n  /* other file hooks */\n}));\n\nexport const dirHooks = FsHooks.dirHooks((targetDir) =\u003e ({\n  createFile(fileName: string, data: string = '') {\n    const filePath = path.resolve(targetDir.path, fileName);\n    fs.writeFileSync(filePath, data);\n\n    // 👇 notice that it calls fileHooks\n    return fileHooks({\n      type: 'file',\n      path: filePath,\n    });\n  },\n  createDir(dirName: string) {\n    const dirPath = path.resolve(targetDir.path, dirName);\n    fs.mkdirSync(dirPath);\n\n    // 👇 notice that it calls dirHooks\n    return dirHooks({\n      type: 'dir',\n      path: dirPath,\n      children: {},\n    });\n  },\n  /* other directory hooks */\n}));\n```\n\nThis allows for the following scenario:\n\n```typescript\nimport { FsHooks } from 'fs-hooks';\n\nconst fileHooks = FsHooks.fileHooks((targetFile) =\u003e ({ /* file hooks */ }));\nconst dirHooks = FsHooks.dirHooks((targetDir) =\u003e ({ /* directory hooks */ }));\n\nconst fsHooks = new FsHooks('/path/to/tree/root', {\n  dir1: {\n    dir2: {\n      file1: 'File 1 data',\n    },\n  },\n});\n\nconst hooks = fsHooks.useHooks({\n  file: fileHooks,\n  dir: dirHooks,\n});\n\nconst root = hooks((root) =\u003e root);\nconst newFile1 = root.createFile('new-file1', 'New file 1 data');\nconst data1 = newFile1.read(); // New file 1 data\n\nconst dir1 = hooks((root) =\u003e root.dir1);\nconst newDir1 = dir1.createDir('new-dir1');\nconst newDir2 = newDir1.createDir('new-dir2');\nconst newDir3 = newDir2.createDir('new-dir3');\nconst newFile2 = newDir3.createFile('new-file2', 'New file 2 data');\nconst data2 = newFile2.read(); // New file 2 data\n```\n\n\u003e 💡 The above example is how core hooks are built under the hood.\n\n## Core Hooks\n\nThe library exports a set of common hooks called `coreHooks` from `fs-hooks/core` that can be provided to the `useHooks` method.\n\n```typescript\nimport { FsHooks } from 'fs-hooks';\nimport { coreHooks } from 'fs-hooks/core';\n\nconst fsHooks = new FsHooks('/path/to/tree/root', {\n  /* tree definition */\n});\n\nconst hooks = fsHooks.useHooks(coreHooks);\n```\n\n### File Core Hooks\n\n- [`getPath`](#getpath-file-hook)\n- [`read`](#read)\n- [`write`](#write)\n- [`clear`](#clear)\n\n### `getPath` (file hook)\n\nReturns the path of the target file.\n\n#### *Definition*\n\n```typescript\ngetPath(): string\n```\n\n#### *Example*\n\n```typescript\nconst file = hooks((root) =\u003e root.file);\nconst filePath = file.getPath();\n```\n\n### `read`\n\nReads the contents of the target file.\n\n#### *Returns*\n\n- file data as a `string`\n- `null` if the file cannot be read\n\n#### *Definition*\n\n```typescript\nread(): string | null\n```\n\n#### *Example*\n\n```typescript\nconst file = hooks((root) =\u003e root.file);\nconst fileData = file.read();\n```\n\n### `write`\n\nWrites data to the target file. Data can be of type `string` or `NodeJS.ArrayBufferView`, otherwise it gets stringified.\n\n#### *Definition*\n\n```typescript\nwrite\u003cData\u003e(data: Data): void\n```\n\n#### *Example*\n\n```typescript\nconst file = hooks((root) =\u003e root.file);\nfile.write('New file data');\n```\n\n### `clear`\n\nClears the contents of the target file.\n\n#### *Definition*\n\n```typescript\nclear(): void\n```\n\n#### *Example*\n\n```typescript\nconst file = hooks((root) =\u003e root.file);\nfile.clear();\n```\n\n### Directory Core Hooks\n\n- [`getPath`](#getpath-directory-hook)\n- [`exists`](#exists)\n- [`dirCreate`](#dircreate)\n- [`dirDelete`](#dirdelete)\n- [`fileCreate`](#filecreate)\n- [`fileDelete`](#filedelete)\n- [`fileRead`](#fileread)\n- [`fileWrite`](#filewrite)\n- [`fileClear`](#fileclear)\n\n### `getPath` (directory hook)\n\nReturns the path of the target directory.\n\n#### *Definition*\n\n```typescript\ngetPath(): string\n```\n\n#### *Example*\n\n```typescript\nconst dir = hooks((root) =\u003e root);\nconst dirPath = dir.getPath();\n```\n\n### `exists`\n\nChecks if a file or directory exists inside the target directory.\n\n#### *Definition*\n\n```typescript\nexists(name: string): boolean\n```\n\n#### *Example*\n\n```typescript\nconst dir = hooks((root) =\u003e root);\nconst fileExists = dir.exists('some-file');\nconst dirExists = dir.exists('some-dir');\n```\n\n### `dirCreate`\n\nCreates a new directory inside the target directory.\n\n#### *Returns*\n\n- The created directory hooks\n- The directory hooks if the directory already exists\n- `false` if the directory could not be created\n\n#### *Definition*\n\n```typescript\ndirCreate(dirName: string, recursive: boolean = false): DirHooks | false\n```\n\n#### *Example*\n\n```typescript\nconst dir = hooks((root) =\u003e root);\nconst newDir = dir.dirCreate('new-dir');\n\n// you can access all the directory hooks on the newDir\nnewDir.getPath();\n\n// even create another new directory!\nconst anotherDir = newDir.dirCreate('foo');\n```\n\n\u003e In the above example, `newDir` has all the directory hooks just like accessing a tree directory with the `hooks` function.\n\n#### *Creating a nested directory*\n\nTo create a nested directory, set the `recursive` flag to `true`:\n\n```typescript\nconst dir = hooks((root) =\u003e root);\nconst newDir = dir.dirCreate('nested/new-dir', true);\n```\n\n### `dirDelete`\n\nDeletes a directory inside the target directory.\n\n#### *Definition*\n\n```typescript\ndirDelete(dirName: string): void\n```\n\n#### *Example*\n\n```typescript\nconst dir = hooks((root) =\u003e root);\ndir.dirDelete('some-dir');\n```\n\n### `fileCreate`\n\nCreates a new file inside the target directory.\n\n#### *Returns*\n\n- The created file hooks\n- The file hooks if the file already exists\n- `false` if the file could not be created\n\n#### *Definition*\n\n```typescript\nfileCreate(fileName: string, data: unknown = ''): FileHooks | false\n```\n\n\u003e If the `data` argument is provided and the file already exists, the file will be overwritten. Data can be of type `string` or `NodeJS.ArrayBufferView`, otherwise it gets stringified.\n\n#### *Example*\n\n```typescript\nconst dir = hooks((root) =\u003e root);\nconst newFile = dir.fileCreate('new-file', 'file data');\n\n// you can access all the file hooks on the newFile\nnewFile.getPath();\nnewFile.read();\nnewFile.write('updated file data');\nnewFile.clear();\n```\n\n\u003e In the above example, `newFile` has all the file hooks just like accessing a tree file with the `hooks` function.\n\n### `fileDelete`\n\nDeletes a file inside the target directory.\n\n#### *Definition*\n\n```typescript\nfileDelete(fileName: string): void\n```\n\n#### *Example*\n\n```typescript\nconst dir = hooks((root) =\u003e root);\ndir.fileDelete('some-file');\n```\n\n### `fileRead`\n\nReads the contents of a file inside the target directory.\n\n#### *Returns*\n\n- file data as a `string`\n- `null` if the file cannot be read\n\n#### *Definition*\n\n```typescript\nfileRead(fileName: string): string | null\n```\n\n#### *Example*\n\n```typescript\nconst dir = hooks((root) =\u003e root);\nconst fileData = dir.fileRead('some-file');\n```\n\n### `fileWrite`\n\nWrites new data to a file inside the target directory. Data can be of type `string` or `NodeJS.ArrayBufferView`, otherwise it gets stringified.\n\n#### *Definition*\n\n```typescript\nfileWrite\u003cData\u003e(fileName: string, data: Data): void\n```\n\n#### *Example*\n\n```typescript\nconst dir = hooks((root) =\u003e root);\ndir.fileWrite('some-file', 'some data');\n```\n\n### `fileClear`\n\nClears the contents of a file inside the target directory.\n\n#### *Definition*\n\n```typescript\nfileClear(fileName: string): void\n```\n\n#### *Example*\n\n```typescript\nconst dir = hooks((root) =\u003e root);\ndir.fileClear('some-file');\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flexborisoff%2Ffs-hooks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flexborisoff%2Ffs-hooks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flexborisoff%2Ffs-hooks/lists"}