> ## Documentation Index
> Fetch the complete documentation index at: https://docs.spurdoverse.app/llms.txt
> Use this file to discover all available pages before exploring further.

# fs

> Node-style file I/O rooted at the script's data/ folder.

File I/O shaped like Node's `fs`. Every async function has a `*Sync` twin that blocks until it's done.

```ts theme={null}
import fs from "@native/fs";
```

<Note>
  Relative paths start at the script's `data/` folder. Without the `filesystem`
  [permission](/api/manifest#permissions) everything stays inside it, and paths leading out throw.
  With it, absolute paths work as usual.
</Note>

## Overview

<CardGroup cols={2}>
  <Card title="Reading and writing" icon="file-lines" href="#reading-and-writing">
    `readFile`, `writeFile`, `appendFile`.
  </Card>

  <Card title="Files and directories" icon="folder-tree" href="#files-and-directories">
    `exists`, `stat`, `mkdir`, `rmdir`, `readdir`, …
  </Card>

  <Card title="Watching" icon="eye" href="#watching">
    `watch`, `FSWatcher`.
  </Card>

  <Card title="Paths" icon="route" href="#paths">
    `fs.path.join`, `basename`, …
  </Card>
</CardGroup>

Failures throw (or reject) an `Error` with a Node-style `code` such as `ENOENT`. See [Errors](#errors).

***

## Reading and writing

### readFile

<br />

```ts theme={null}
fs.readFile(path: string, options?: BufferEncoding | { encoding?: BufferEncoding | null } | null): Promise<string | Uint8Array>
fs.readFileSync(path: string, options?: ...): string | Uint8Array
```

Reads a whole file: a `Uint8Array` without an encoding, a string with one.

```ts theme={null}
const raw = await fs.readFile("icon.png");                 // Uint8Array
const config = JSON.parse(fs.readFileSync("config.json", "utf8"));
```

Encodings: `utf8`, `latin1`, `ascii`, `hex`, `base64`, `base64url`, `utf16le`.

### writeFile

<br />

```ts theme={null}
fs.writeFile(path: string, data: string | BufferSource, options?: BufferEncoding | { encoding?: BufferEncoding | null } | null): Promise<void>
fs.writeFileSync(path, data, options?): void
```

Creates or replaces a file. Strings are written as UTF-8 unless you pass an encoding.

```ts theme={null}
await fs.writeFile("output.txt", "Hello, world!");
fs.writeFileSync("dump.bin", new Uint8Array([1, 2, 3]));
fs.writeFileSync("key.bin", "deadbeef", "hex");
```

### appendFile

<br />

```ts theme={null}
fs.appendFile(path: string, data: string | BufferSource, options?): Promise<void>
fs.appendFileSync(path, data, options?): void
```

Same as `writeFile`, but appends, and creates the file when it doesn't exist.

```ts theme={null}
fs.appendFileSync("kills.log", `${new Date().toISOString()} kill\n`);
```

***

## Files and directories

### exists

<br />

```ts theme={null}
fs.exists(path: string): Promise<boolean>
fs.existsSync(path: string): boolean
```

`true` when something exists at `path`.

```ts theme={null}
if (!fs.existsSync("config.json")) fs.writeFileSync("config.json", "{}");
```

### stat

<br />

```ts theme={null}
fs.stat(path: string): Promise<Stats>
fs.statSync(path: string): Stats
```

Throws `ENOENT` when nothing is there.

```ts theme={null}
const st = fs.statSync("config.json");
console.log(st.size, new Date(st.mtimeMs), st.isFile());
```

| `Stats` member | |
| :- | :- |
| `size` | bytes, `0` for directories |
| `atimeMs`, `mtimeMs`, `birthtimeMs` | ms since the Unix epoch |
| `isFile()`, `isDirectory()` | |
| `isSymbolicLink()` | always `false` |

### mkdir

<br />

```ts theme={null}
fs.mkdir(path: string, options?: { recursive?: boolean }): Promise<void>
fs.mkdirSync(path, options?): void
```

Creates a directory. With `recursive` it also creates missing parents and doesn't fail when the
directory already exists.

```ts theme={null}
await fs.mkdir("saves/p1", { recursive: true });
```

### rmdir

<br />

```ts theme={null}
fs.rmdir(path: string, options?: { recursive?: boolean }): Promise<void>
fs.rmdirSync(path, options?): void
```

Removes a directory. Without `recursive` it must be empty (`ENOTEMPTY`).

```ts theme={null}
fs.rmdirSync("cache", { recursive: true });
```

### Other operations

| Function | |
| :- | :- |
| `readdir(path): Promise<string[]>` | entry names (not paths), without `.` and `..` |
| `unlink(path): Promise<void>` | deletes a file |
| `rename(oldPath, newPath): Promise<void>` | moves or renames, replacing an existing file |
| `copyFile(src, dest): Promise<void>` | copies a file, replacing an existing one |
| `truncate(path, length = 0): Promise<void>` | shrinks or zero-extends to `length` bytes |

Each has a `*Sync` twin.

```ts theme={null}
for (const name of await fs.readdir("saves"))
    if (fs.path.extname(name) === ".tmp") await fs.unlink(fs.path.join("saves", name));
```

***

## Watching

### watch

<br />

```ts theme={null}
fs.watch(path: string, listener: (eventType: "rename" | "change", filename: string | null) => void,
         options?: { recursive?: boolean; signal?: AbortSignal }): FSWatcher
```

Calls `listener` for every change: `"rename"` for created, deleted or renamed entries, `"change"` for
modified ones. `filename` is relative to the watched path, and can be `null`.

```ts theme={null}
const watcher = fs.watch("configs", (event, name) => console.log(event, name), { recursive: true });
watcher.close();
```

A listener that throws doesn't stop the watcher. Watchers close on unload, or earlier with `close()`,
`using` or an aborted `signal`.

***

## Paths

`fs.path` has the common helpers of Node's `path` (Windows flavour). String work only, no I/O.

| Function | Example |
| :- | :- |
| `join(...paths)` | `join("saves", "p1", "../p2", "a.json")` → `"saves\\p2\\a.json"` |
| `basename(path, suffix?)` | `basename("saves/p1/a.json", ".json")` → `"a"` |
| `dirname(path)` | `dirname("saves/p1/a.json")` → `"saves/p1"` |
| `extname(path)` | `extname("a.json")` → `".json"`, `extname(".bashrc")` → `""` |
| `isAbsolute(path)` | `true` for `"C:\\x"`, `"\\x"`, `"\\\\server\\share"`; `false` for `"C:x"` |

***

## Errors

Errors carry Node's `code` (`ENOENT`, `EEXIST`, `ENOTEMPTY`, …) and the `path` you passed.

```ts theme={null}
try {
    fs.readFileSync("missing.json", "utf8");
} catch (e) {
    if ((e as { code?: string }).code !== "ENOENT") throw e;
}
```

***

## Example

A JSON settings file that reloads when you edit it.

```ts theme={null}
import fs from "@native/fs";

type Settings = { fov: number; color: string };
const defaults: Settings = { fov: 90, color: "#ff4040" };
let settings = defaults;

function load(): void {
    try {
        settings = { ...defaults, ...JSON.parse(fs.readFileSync("settings.json", "utf8")) };
    } catch (e) {
        if ((e as { code?: string }).code === "ENOENT")
            fs.writeFileSync("settings.json", JSON.stringify(defaults, null, 2));
        else
            console.warn("settings.json:", e);
    }
}

load();
fs.watch(".", (event, name) => {
    if (name === "settings.json") load();
});

on("tick", () => { /* use settings.fov, settings.color */ });
```
