
# Built-in Drivers

**Three. Two are `FullFsDriver`s — every optional method implemented — and the third adapts a key–value store, which cannot be.**

They exist to be useful on their own, and to be the thing you wrap when you want caching, logging, filtering or transformation on top of something that already works.

## Memory

```ts
import { createMemoryDriver } from "mountx/drivers/memory";

const driver = createMemoryDriver();
```

A complete in-memory filesystem: files, directories, symlinks, hardlinks, permissions, timestamps, `statfs`. Nothing is persisted; the tree lives as long as the process does.

```ts
function createMemoryDriver(options?: MemoryDriverOptions): FullFsDriver;

interface MemoryDriverOptions {
  /** Owner of everything created. Defaults to the current process. */
  uid?: number;
  gid?: number;
  /** Bits cleared from every mkdir/open mode. Default 0 — see below. */
  umask?: number;
  /** Mode of the root directory. */
  rootMode?: number;
}
```

### Why `umask` defaults to none

A umask belongs to a _process_, and a driver is not one. Under a mount it is worse than redundant: the kernel has already applied the calling process's umask before the mode reaches `FUSE_MKDIR`/`FUSE_CREATE`, so a second one here masks with the wrong process's value and produces a file the caller did not ask for — `create f 04777` arriving as `04755`, which is how pjdfstest found it.

Set it explicitly for a **loopback** filesystem that wants `node:fs`-like behaviour.

## Node-fs

```ts
import { createNodeFsDriver } from "mountx/drivers/node-fs";

const driver = createNodeFsDriver("/home/me/data");
```

A passthrough to a real directory on the host.

```ts
function createNodeFsDriver(root: string, options?: NodeFsDriverOptions): FullFsDriver;

interface NodeFsDriverOptions {
  /** Report the driver as read-only (does not itself enforce it). */
  readOnly?: boolean;
}
```

**It resolves every path component itself** rather than handing a joined string to `node:fs`, so nothing outside its root can be reached — not through `..`, and not through a symlink pointing out. Symlink resolution is bounded at 40 levels, as the kernel's is.

## Unstorage

```ts
import { createStorage } from "unstorage";
import s3Driver from "unstorage/drivers/s3";
import { createUnstorageDriver } from "mountx/drivers/unstorage";

const storage = createStorage({ driver: s3Driver({/* … */}) });
const driver = createUnstorageDriver(storage);
```

One adapter, and every [unstorage](https://unstorage.unjs.io) driver becomes mountable: S3, Redis, Cloudflare KV, GitHub, an HTTP endpoint, a filesystem, or a `Storage` with several of those mounted into one key space.

```ts
function createUnstorageDriver(storage: Storage, options?: UnstorageDriverOptions): FsDriver;

interface UnstorageDriverOptions {
  /** Owner of everything in the tree. Defaults to the current process. */
  uid?: number;
  gid?: number;
  /** Permission bits reported for a file with no chmod applied. Default 0o644. */
  fileMode?: number;
  /** Permission bits reported for a directory with no chmod applied. Default 0o755. */
  dirMode?: number;
  /** Answer EROFS to everything that would write. */
  readOnly?: boolean;
}
```

`unstorage` is an **optional peer dependency**, imported for its types only — install it yourself, and nothing in mountx's runtime graph changes if you do not.

### How the tree maps onto keys

| filesystem        | store                                        |
| ----------------- | -------------------------------------------- |
| `/a/b/c.txt`      | the key `a:b:c.txt`                          |
| a directory       | a key prefix — there is nothing else to be   |
| a file's contents | `getItemRaw` / `setItemRaw`                  |
| `mtime`, `size`   | the driver's own `getMeta`, where it has one |

The `/` → `:` mapping is unstorage's own convention, not one invented here: its `normalizeKey` already treats the two as the same separator. To serve a subtree, hand over `prefixStorage(storage, "base")` rather than the whole store.

Three characters cannot survive the round trip and are refused with `EINVAL` rather than mangled: `:` **is** the separator, `?` is truncated away by `normalizeKey` (`a?b` becomes `a`), and a key ending in `$` is unstorage's metadata convention and is filtered out of `getKeys` — a file created with one would exist and never be listed.

### What a key–value store cannot do

- **No symlinks, no hardlinks, no `statfs`**, and `rename` is a copy followed by a delete rather than an atomic operation. All four are declared, so the mount answers `ENOSYS`/`ENOTSUP` rather than pretending — see [capabilities](/guide/drivers/custom#capabilities).
- **Empty directories live in the process, not the store.** There is no such thing as an empty directory in a key–value store, so `mkdir` of one is remembered until it holds a file — at which point it is a real prefix and outlives the process like anything else. Nothing is written to mark it, because a marker key would show up as a file to every other consumer of that store.
- **Permissions and timestamps are an overlay**, held in memory for the life of the driver and seeded from `getMeta`. `chmod`, `chown` and `utimes` therefore work — `cp -p` and `tar -x` need them — but a store that cannot hold that metadata does not gain the ability to.
- **A key that is also a prefix** (both `a` and `a:b` exist) has no tree that represents it. The key wins: `a` is a file, and `a/b` is `ENOTDIR`.

Worth knowing before mounting something remote: `readdir` lists every key under the directory's prefix, because the `Storage` interface has no shallow listing, and `stat` falls back to fetching a value to measure it when the underlying driver's `getMeta` reports no `size`.

## Which to use

- **Memory** — tests, demos, scratch space, and the base for a driver whose data comes from somewhere else entirely.
- **Node-fs** — the wrapping target. Add caching, an access-control layer, a virtual overlay, request logging, or on-the-fly transformation to an ordinary directory, and everything you did not override still works.
- **Unstorage** — anything that is already a key–value store, and the shortest route to mounting a remote one.

## Wrapping one

They are plain objects, so a `Proxy` or a spread is all a wrapper needs:

```ts
import { createNodeFsDriver } from "mountx/drivers/node-fs";
import type { FsDriver } from "mountx";

const base = createNodeFsDriver("/home/me/data");

const logged: FsDriver = {
  ...base,
  async stat(path) {
    console.log("stat", path);
    return base.stat(path);
  },
};
```

Keep `capabilities` in mind when you wrap: spreading carries the base driver's declarations along with its methods, which is usually what you want — but a wrapper that _removes_ a method should say so, since [capabilities are inferred from shape](/guide/drivers/custom#capabilities).

## `node:fs/promises` itself

Worth remembering that this is a valid driver with no adaptation at all:

```ts
import * as fs from "node:fs/promises";
import { createLoopback } from "mountx";

const loopback = createLoopback(fs); // compiles, no cast
```

It is unrooted — paths are the host's, not confined to any directory — so it is a conformance oracle rather than something to mount. `createNodeFsDriver` is the rooted version.

## Next

- [Writing a driver](/guide/drivers/custom) — when none of the three is what you have.
- [Mounting](/guide/mounting) — turning any of them into a folder.
