# FileSystem

The `FileSystem` service is Effect's abstraction over the file system. Every
method returns an `Effect` that fails with a [`PlatformError`](https://effect.plants.sh/error-management/)
instead of throwing, so missing files, permission errors, and bad arguments all
flow through the typed error channel. You depend on the abstract `FileSystem`
interface and provide a concrete implementation — `NodeFileSystem.layer` on
Node.js — at the edge of your program.

```ts
import { NodeFileSystem } from "@effect/platform-node"
import { Context, Effect, FileSystem, Layer, PlatformError } from "effect"

// A small service that persists JSON snapshots to a directory.
class SnapshotStore extends Context.Service<SnapshotStore, {
  readonly save: (name: string, value: unknown) => Effect.Effect<void, PlatformError.PlatformError>
  readonly load: (name: string) => Effect.Effect<unknown, PlatformError.PlatformError>
}>()("app/SnapshotStore") {
  static readonly layer = Layer.effect(
    SnapshotStore,
    Effect.gen(function*() {
      const fs = yield* FileSystem.FileSystem
      const dir = "snapshots"

      // Ensure the target directory exists. `recursive` makes this a no-op
      // when the directory is already present, like `mkdir -p`.
      yield* fs.makeDirectory(dir, { recursive: true })

      const save = (name: string, value: unknown) =>
        // `writeFileString` writes (and creates) the file in one call.
        fs.writeFileString(`${dir}/${name}.json`, JSON.stringify(value, null, 2))

      const load = (name: string) =>
        fs.readFileString(`${dir}/${name}.json`).pipe(
          Effect.map((text) => JSON.parse(text) as unknown)
        )

      return { save, load } as const
    })
  ).pipe(
    // The store needs a concrete FileSystem; wire in the Node implementation.
    Layer.provide(NodeFileSystem.layer)
  )
}

const program = Effect.gen(function*() {
  const store = yield* SnapshotStore
  yield* store.save("user-42", { id: 42, name: "Ada" })
  const restored = yield* store.load("user-42")
  yield* Effect.log(restored)
}).pipe(Effect.provide(SnapshotStore.layer))
```
**Caution:** Every `FileSystem` (and `File`) method reports failure as a
[`PlatformError`](https://effect.plants.sh/error-management/) on the Effect error channel — it never
throws. A `PlatformError` wraps a `reason` that is either a `SystemError` (with
a normalized `_tag` such as `NotFound`, `PermissionDenied`, or `TimedOut`) or a
`BadArgument`. Handle them with `Effect.catchTag("PlatformError", ...)`.

## Getting the service

Request the service with `yield* FileSystem.FileSystem` inside an effect, then
provide a platform layer when you run the program. On Node.js that layer is
`NodeFileSystem.layer` from `@effect/platform-node`:

```ts
import { NodeFileSystem } from "@effect/platform-node"
import { Effect, FileSystem } from "effect"

const program = Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  return yield* fs.readFileString("package.json")
})

// `FileSystem.FileSystem` is the requirement; the Node layer satisfies it.
const runnable = program.pipe(Effect.provide(NodeFileSystem.layer))
```

For tests you do not need a real file system: `FileSystem.layerNoop` provides a
stub whose behavior you override per test (see the reference at the end of this
page).

## Reading and writing whole files

The most common operations come in byte and string flavours. Use the `String`
variants when you are working with text and the raw variants when you need exact
bytes. Both writers accept `flag` (an [`OpenFlag`](#openflag)) and `mode`
options:

```ts
import { Effect, FileSystem } from "effect"

const program = Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem

  // Text helpers return / accept `string`.
  yield* fs.writeFileString("notes.txt", "first line\n")
  const text = yield* fs.readFileString("notes.txt")
  // => "first line\n"

  // `readFileString` takes an optional encoding (passed to TextDecoder).
  const latin1 = yield* fs.readFileString("legacy.txt", "latin1")

  // Byte helpers return / accept `Uint8Array`.
  const bytes: Uint8Array = yield* fs.readFile("notes.txt")
  yield* fs.writeFile("copy.bin", bytes)

  // `flag: "a"` appends instead of truncating; `mode` sets POSIX permissions.
  yield* fs.writeFileString("notes.txt", "second line\n", { flag: "a", mode: 0o644 })

  yield* Effect.log(`${text.length} chars, ${bytes.length} bytes`)
})
```

## Inspecting the file system

`exists`, `stat`, and `readDirectory` let you query the file system before
acting on it. Note that timestamps on `stat` are wrapped in `Option` because not
every platform reports them:

```ts
import { Effect, FileSystem, Option } from "effect"

const describe = Effect.fn("describe")(function*(path: string) {
  const fs = yield* FileSystem.FileSystem

  if (!(yield* fs.exists(path))) {
    return `${path} does not exist`
  }

  // `stat` returns a `File.Info`: type, size (a branded bigint), mode, and
  // optional timestamps.
  const info = yield* fs.stat(path)

  const modified = Option.match(info.mtime, {
    onNone: () => "unknown",
    onSome: (date) => date.toISOString()
  })

  return `${info.type} — ${info.size} bytes — modified ${modified}`
})
```

To enumerate a directory, `readDirectory` returns the entry names; pass
`recursive: true` to walk nested directories:

```ts
import { Effect, FileSystem } from "effect"

const listTree = Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  return yield* fs.readDirectory("src", { recursive: true })
})
```

## Directories and moving things around

`makeDirectory`, `remove`, `copy`, `copyFile`, and `rename` cover the usual
directory and path-management tasks. `recursive` and `force` options mirror the
familiar shell flags:

```ts
import { Effect, FileSystem } from "effect"

const program = Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem

  // `recursive: true` is like `mkdir -p` — creates parents, no error if present.
  yield* fs.makeDirectory("build/artifacts", { recursive: true })

  // `copy` is recursive like `cp -r`; `copyFile` copies a single file.
  yield* fs.copy("src", "build/src")
  yield* fs.copyFile("README.md", "build/README.md")

  // `rename` moves or renames a file or directory.
  yield* fs.rename("build/README.md", "build/README.txt")

  // `recursive` removes nested entries; `force` ignores a missing path.
  yield* fs.remove("build", { recursive: true, force: true })
})
```

## Streaming large files

Reading a multi-gigabyte file into memory with `readFile` is wasteful. The
`stream` method returns a [`Stream`](https://effect.plants.sh/streaming/) of byte chunks so you can
process a file incrementally, and `sink` gives you a writable counterpart. Here
we count the lines of a file without ever holding it all in memory:

```ts
import { Effect, FileSystem, Stream } from "effect"

const countLines = Effect.fn("countLines")(function*(path: string) {
  const fs = yield* FileSystem.FileSystem

  return yield* fs.stream(path).pipe(
    Stream.decodeText(),   // Uint8Array chunks -> string chunks
    Stream.splitLines,     // re-chunk on line boundaries
    Stream.runFold(() => 0, (count) => count + 1)
  )
})
```

You can tune `chunkSize`, `offset`, and `bytesToRead` on `stream` to read a
specific window of a file. Use the [`Size`](#size-sizeinput) helpers (`KiB`,
`MiB`, …) for readable byte counts:

```ts
import { Effect, FileSystem, Stream } from "effect"

const tail64KiB = Effect.fn("tail64KiB")(function*(path: string) {
  const fs = yield* FileSystem.FileSystem
  const { size } = yield* fs.stat(path)

  // Read the last 64 KiB in 8 KiB chunks.
  return fs.stream(path, {
    offset: size - FileSystem.KiB(64),
    bytesToRead: FileSystem.KiB(64),
    chunkSize: FileSystem.KiB(8)
  })
})
```

`sink` is the inverse: pipe a `Stream<Uint8Array>` into a file. Combine it with
`Stream.run` to copy or transform data with backpressure:

```ts
import { Effect, FileSystem, Stream } from "effect"

const copyStreamed = Effect.fn("copyStreamed")(function*(from: string, to: string) {
  const fs = yield* FileSystem.FileSystem
  // Read `from` as a Stream and drain it into `to` via the writable Sink.
  return yield* Stream.run(fs.stream(from), fs.sink(to))
})
```

## Temporary files and scopes

Temporary files and directories are tied to a [`Scope`](https://effect.plants.sh/resource-management/)
through the `*Scoped` variants, so they are deleted automatically when the scope
closes — even if the effect fails:

```ts
import { Effect, FileSystem } from "effect"

const withScratch = Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem

  // Created inside the surrounding scope; removed when the scope closes.
  const dir = yield* fs.makeTempDirectoryScoped({ prefix: "build-" })

  yield* fs.writeFileString(`${dir}/manifest.json`, "{}")
  yield* Effect.log(`working in ${dir}`)
}).pipe(
  // `Effect.scoped` provides and closes the scope, triggering cleanup.
  Effect.scoped
)
```

The non-scoped `makeTempFile` / `makeTempDirectory` return the path and leave
cleanup to you — reach for them only when the temp resource must outlive the
current scope.

Opening a file with `fs.open` is similarly scoped: the returned `File` handle is
closed when the scope ends, so you never leak file descriptors.
**Tip:** Pair these scoped helpers with `Effect.scoped` or a service `Layer.effect` (its
construction effect runs in the layer's scope) so cleanup happens
deterministically. See
[Resource Management](https://effect.plants.sh/resource-management/) for the full model.

## Watching for changes

`watch` returns a `Stream` of [`WatchEvent`](#watchevent) values (`Create`,
`Update`, `Remove`) so you can react to file system activity — handy for dev
tooling and hot reload:

```ts
import { Effect, FileSystem, Stream } from "effect"

const watchSrc = Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem

  yield* fs.watch("src").pipe(
    Stream.runForEach((event) =>
      // event._tag is "Create" | "Update" | "Remove"; event.path is the path.
      Effect.log(`${event._tag}: ${event.path}`)
    )
  )
})
```

## The low-level File handle

When whole-file and streaming helpers are not enough, `open` gives you a
[`File`](#the-file-interface) handle for positioned reads and writes. The handle
is scoped, so it is closed automatically:

```ts
import { Effect, FileSystem } from "effect"

const patch = Effect.fn("patch")(function*(path: string) {
  const fs = yield* FileSystem.FileSystem

  // The handle is closed when the surrounding scope ends.
  const file = yield* fs.open(path, { flag: "r+" })

  // Move the cursor 10 bytes from the start, then overwrite 5 bytes.
  yield* file.seek(10, "start")
  yield* file.writeAll(new TextEncoder().encode("HELLO"))
  yield* file.sync // flush to disk

  // `readAlloc` allocates a buffer and returns Option.none() at EOF.
  yield* file.seek(0, "start")
  return yield* file.readAlloc(FileSystem.KiB(4))
}, Effect.scoped)
```

## Handling errors

Because failures are values, narrow them with `Effect.catchTag`. A
`PlatformError` exposes a human-readable `message` and a structured `reason`
(either a `SystemError` or a `BadArgument`):

```ts
import { Effect, FileSystem } from "effect"

const readOrDefault = Effect.fn("readOrDefault")(function*(path: string) {
  const fs = yield* FileSystem.FileSystem
  return yield* fs.readFileString(path)
}).pipe(
  // Fall back to an empty document when the read fails for any reason.
  Effect.catchTag("PlatformError", (error) =>
    Effect.as(Effect.logWarning(`read failed: ${error.message}`), "")
  )
)
```

See [Manipulate paths with **Path**](https://effect.plants.sh/platform/path/) for building the paths you
pass to these methods in a cross-platform way.

## FileSystem reference

Every method below is accessed off the service value
(`const fs = yield* FileSystem.FileSystem`). All return effects fail with
`PlatformError`.

### access

Checks whether a path can be accessed, optionally at a given access level
(`readable` / `writable`). Fails if the check does not pass.

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  yield* fs.access("config.json", { readable: true })
  // => void (succeeds) | fails with PlatformError if not readable
})
```

### chmod

Changes the permission bits of a file to the given numeric `mode`.

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  yield* fs.chmod("deploy.sh", 0o755) // rwxr-xr-x
  // => void
})
```

### chown

Changes the owner (`uid`) and group (`gid`) of a file.

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  yield* fs.chown("data.db", 1000, 1000)
  // => void
})
```

### copy

Recursively copies a file or directory from `fromPath` to `toPath` (like
`cp -r`). Options: `overwrite`, `preserveTimestamps`.

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  yield* fs.copy("src", "backup/src", { overwrite: true })
  // => void
})
```

### copyFile

Copies a single file from `fromPath` to `toPath`.

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  yield* fs.copyFile("a.txt", "b.txt")
  // => void
})
```

### exists

Returns `true` if a path exists, `false` otherwise. Unlike `access`, a missing
path is not an error.

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  const has = yield* fs.exists("config.json")
  // => true | false
})
```

### link

Creates a hard link at `toPath` pointing to `fromPath`.

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  yield* fs.link("original.txt", "hardlink.txt")
  // => void
})
```

### makeDirectory

Creates a directory. Options: `recursive` (create missing parents, like
`mkdir -p`) and `mode`.

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  yield* fs.makeDirectory("a/b/c", { recursive: true })
  // => void
})
```

### makeTempDirectory

Creates a temporary directory and returns its path. Options: `directory` (parent
location) and `prefix`. You are responsible for removing it.

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  const dir = yield* fs.makeTempDirectory({ prefix: "job-" })
  // => "/tmp/job-Xy3kP1" (path string)
})
```

### makeTempDirectoryScoped

Like `makeTempDirectory`, but the directory is deleted when the surrounding
`Scope` closes. Requires `Scope` in the environment.

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  const dir = yield* fs.makeTempDirectoryScoped({ prefix: "job-" })
  // => path string; removed automatically on scope close
}).pipe(Effect.scoped)
```

### makeTempFile

Creates a temporary file with a randomly generated name and returns its path.
Options: `directory`, `prefix`, `suffix`.

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  const file = yield* fs.makeTempFile({ suffix: ".json" })
  // => "/tmp/Ab12Cd.json" (path string)
})
```

### makeTempFileScoped

Like `makeTempFile`, but the file is deleted when the `Scope` closes.

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  const file = yield* fs.makeTempFileScoped({ prefix: "upload-" })
  // => path string; removed automatically on scope close
}).pipe(Effect.scoped)
```

### open

Opens a file and returns a scoped [`File`](#the-file-interface) handle (closed on
scope exit). Options: `flag` (an [`OpenFlag`](#openflag), default `"r"`) and
`mode`.

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  const file = yield* fs.open("log.txt", { flag: "a" })
  // => File handle (auto-closed when scope ends)
}).pipe(Effect.scoped)
```

### readDirectory

Lists the entry names of a directory. Option: `recursive` to walk nested
directories.

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  const entries = yield* fs.readDirectory("src")
  // => ["index.ts", "util.ts", ...]
})
```

### readFile

Reads an entire file into a `Uint8Array`.

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  const bytes = yield* fs.readFile("image.png")
  // => Uint8Array(...)
})
```

### readFileString

Reads an entire file and decodes it to a `string`. Optional `encoding` is passed
to `TextDecoder` (default UTF-8).

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  const text = yield* fs.readFileString("notes.txt")
  // => "file contents"
})
```

### readLink

Reads the target of a symbolic link.

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  const target = yield* fs.readLink("current")
  // => "releases/v2" (the link's destination)
})
```

### realPath

Resolves a path to its canonical absolute pathname, following symlinks.

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  const abs = yield* fs.realPath("./src/../package.json")
  // => "/home/me/project/package.json"
})
```

### remove

Removes a file or directory. Options: `recursive` (remove nested entries) and
`force` (ignore a missing path).

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  yield* fs.remove("tmp", { recursive: true, force: true })
  // => void
})
```

### rename

Moves or renames a file or directory from `oldPath` to `newPath`.

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  yield* fs.rename("draft.md", "final.md")
  // => void
})
```

### sink

Creates a writable [`Sink`](https://effect.plants.sh/streaming/) that writes incoming `Uint8Array`
chunks to a file. Options: `flag` (default `"w"`) and `mode`.

```ts
import { Effect, FileSystem, Stream } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  const data = Stream.make(new TextEncoder().encode("hello\n"))
  yield* Stream.run(data, fs.sink("out.txt"))
  // => void (file "out.txt" now contains "hello\n")
})
```

### stat

Returns metadata for a path as a [`File.Info`](#fileinfo).

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  const info = yield* fs.stat("package.json")
  // => { type: "File", size: 1234n, mode: 420, mtime: Option.some(...), ... }
})
```

### stream

Creates a readable [`Stream`](https://effect.plants.sh/streaming/) of `Uint8Array` chunks for a file.
Options: `offset` (start position), `bytesToRead` (limit), and `chunkSize`
(default 64 KiB) — all [`SizeInput`](#size-sizeinput) values.

```ts
import { Effect, FileSystem, Stream } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  const bytes = yield* fs.stream("big.log", { chunkSize: FileSystem.KiB(16) }).pipe(
    Stream.runFold(() => 0, (acc, chunk) => acc + chunk.length)
  )
  // => total number of bytes read
})
```

### symlink

Creates a symbolic link at `toPath` pointing to `fromPath`.

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  yield* fs.symlink("releases/v2", "current")
  // => void
})
```

### truncate

Truncates a file to a given `length` (a [`SizeInput`](#size-sizeinput)),
defaulting to `0`.

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  yield* fs.truncate("data.bin", FileSystem.KiB(4))
  // => void (file is now exactly 4 KiB)
})
```

### utimes

Sets the access (`atime`) and modification (`mtime`) timestamps of a file.
Accepts `Date` or epoch-millisecond numbers.

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  const now = new Date()
  yield* fs.utimes("notes.txt", now, now)
  // => void
})
```

### watch

Creates a [`Stream`](https://effect.plants.sh/streaming/) of [`WatchEvent`](#watchevent) values for a
file or directory. Watch behavior is platform-dependent.

```ts
import { Effect, FileSystem, Stream } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  yield* fs.watch("src").pipe(
    Stream.runForEach((e) => Effect.log(`${e._tag} ${e.path}`))
  )
  // => emits { _tag: "Update", path: "src/index.ts" }, ...
})
```

### writeFile

Writes a `Uint8Array` to a file. Options: `flag` (an [`OpenFlag`](#openflag)) and
`mode`.

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  yield* fs.writeFile("out.bin", new Uint8Array([1, 2, 3]))
  // => void
})
```

### writeFileString

Encodes a `string` (UTF-8) and writes it to a file. Same `flag` / `mode` options
as `writeFile`; use `flag: "a"` to append.

```ts
import { Effect, FileSystem } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  yield* fs.writeFileString("log.txt", "line\n", { flag: "a" })
  // => void
})
```

## The File interface

`open` returns a `File` handle for low-level, positioned I/O. It carries a
branded `fd` ([`File.Descriptor`](#filedescriptor)) and the members below.

### file.stat

An effect that returns the handle's [`File.Info`](#fileinfo).

```ts
const info = yield* file.stat
// => { type: "File", size: 42n, ... }
```

### file.seek

Moves the read/write cursor by `offset` ([`SizeInput`](#size-sizeinput)) relative
to a [`SeekMode`](#seekmode) (`"start"` or `"current"`). Cannot fail.

```ts
yield* file.seek(128, "start")   // absolute position 128
yield* file.seek(16, "current")  // 16 bytes forward
// => void
```

### file.read

Reads bytes into the provided `buffer` and returns the number of bytes read as a
[`Size`](#size-sizeinput) (bigint).

```ts
const buffer = new Uint8Array(64)
const bytesRead = yield* file.read(buffer)
// => 64n (or fewer near EOF)
```

### file.readAlloc

Allocates a buffer of the given `size` ([`SizeInput`](#size-sizeinput)), reads
into it, and returns `Option.some(bytes)` — or `Option.none()` at end of file.

```ts
import { Option } from "effect"

const chunk = yield* file.readAlloc(FileSystem.KiB(4))
Option.match(chunk, {
  onNone: () => "EOF",
  onSome: (bytes) => `read ${bytes.length} bytes`
})
// => "read 4096 bytes" | "EOF"
```

### file.write

Writes `buffer` at the current cursor and returns the number of bytes written as
a [`Size`](#size-sizeinput).

```ts
const written = yield* file.write(new TextEncoder().encode("hi"))
// => 2n
```

### file.writeAll

Writes the entire `buffer`, looping internally until all bytes are flushed.

```ts
yield* file.writeAll(new TextEncoder().encode("complete payload"))
// => void
```

### file.truncate

Truncates the open file to `length` ([`SizeInput`](#size-sizeinput)), defaulting
to `0`.

```ts
yield* file.truncate(FileSystem.KiB(1))
// => void
```

### file.sync

An effect that flushes buffered data and metadata to the storage device.

```ts
yield* file.sync
// => void
```

The handle is closed automatically when its `Scope` ends — there is no explicit
`close` method; rely on `Effect.scoped` (or `open` inside a `Layer.effect`).

## Supporting types

### File.Info

The metadata record returned by `stat` and `file.stat`. Timestamps and several
numeric fields are wrapped in `Option` because not all platforms report them.

| Field | Type | Notes |
| --- | --- | --- |
| `type` | [`File.Type`](#filetype) | `"File"`, `"Directory"`, `"SymbolicLink"`, … |
| `size` | [`Size`](#size-sizeinput) | byte count (branded bigint) |
| `mode` | `number` | POSIX permission bits |
| `mtime` / `atime` / `birthtime` | `Option<Date>` | modify / access / creation time |
| `dev` | `number` | device id |
| `ino` | `Option<number>` | inode number |
| `nlink` | `Option<number>` | hard link count |
| `uid` / `gid` | `Option<number>` | owner / group ids |
| `rdev` | `Option<number>` | device id for special files |
| `blksize` | `Option<Size>` | block size for I/O |
| `blocks` | `Option<number>` | number of allocated blocks |

```ts
import { Effect, FileSystem, Option } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  const info: FileSystem.File.Info = yield* fs.stat("notes.txt")
  const created = Option.getOrElse(info.birthtime, () => new Date(0))
  // => info.type === "File", info.size is a bigint
})
```

### File.Type

The kind of a file system entry: `"File"`, `"Directory"`, `"SymbolicLink"`,
`"BlockDevice"`, `"CharacterDevice"`, `"FIFO"`, `"Socket"`, or `"Unknown"`.

```ts
const info = yield* fs.stat("/dev/null")
if (info.type === "CharacterDevice") { /* ... */ }
```

### File.Descriptor

A branded `number` identifying an open file, exposed as `file.fd`. Construct one
with `FileSystem.FileDescriptor` when implementing a custom `FileSystem`.

```ts
import { FileSystem } from "effect"

const fd = FileSystem.FileDescriptor(3)
// => branded 3 (File.Descriptor)
```

### OpenFlag

POSIX-style open mode accepted by `open`, `writeFile`, `writeFileString`, and
`sink`. The full set: `"r"`, `"r+"`, `"w"`, `"wx"`, `"w+"`, `"wx+"`, `"a"`,
`"ax"`, `"a+"`, `"ax+"`. Read more in the inline list below.

- `"r"` — read; file must exist.
- `"r+"` — read/write; file must exist.
- `"w"` — write; truncates or creates.
- `"wx"` — like `"w"` but fails if the file exists.
- `"w+"` — read/write; truncates or creates.
- `"wx+"` — like `"w+"` but fails if the file exists.
- `"a"` — append; creates if missing.
- `"ax"` — like `"a"` but fails if the file exists.
- `"a+"` — read/append; creates if missing.
- `"ax+"` — like `"a+"` but fails if the file exists.

```ts
yield* fs.open("new.txt", { flag: "wx" }) // create exclusively
```

### SeekMode

Reference point for `file.seek`: `"start"` (from the beginning of the file) or
`"current"` (from the current cursor position).

```ts
yield* file.seek(0, "start")    // rewind to beginning
yield* file.seek(16, "current") // advance 16 bytes from the cursor
```

### Size / SizeInput

`Size` is a branded `bigint` byte count. `SizeInput` is `bigint | number | Size`
— anywhere a size is accepted you may pass any of them. Use the constructors for
readable values; all return a `Size`.

```ts
import { FileSystem } from "effect"

FileSystem.Size(1024)  // => 1024n
FileSystem.KiB(64)     // => 65536n  (64 * 1024)
FileSystem.MiB(10)     // => 10485760n
FileSystem.GiB(1)      // => 1073741824n
FileSystem.TiB(1)      // => 1099511627776n
FileSystem.PiB(2)      // => 2251799813685248n
```

### WatchEvent

The events emitted by `watch`. A tagged union of `{ _tag: "Create", path }`,
`{ _tag: "Update", path }`, and `{ _tag: "Remove", path }`.

```ts
import { Effect, FileSystem, Stream } from "effect"

Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  yield* fs.watch(".").pipe(
    Stream.runForEach((event) => {
      switch (event._tag) {
        case "Create": return Effect.log(`created ${event.path}`)
        case "Update": return Effect.log(`changed ${event.path}`)
        case "Remove": return Effect.log(`deleted ${event.path}`)
      }
    })
  )
})
```

## Constructors and testing

### FileSystem.make

Builds a concrete `FileSystem` from a partial implementation, deriving `exists`,
`readFileString`, `stream`, `sink`, and `writeFileString` from the core methods
you provide. Used by platform packages to define their layer.

```ts
import { FileSystem } from "effect"

declare const coreImpl: Omit<
  FileSystem.FileSystem,
  "~effect/platform/FileSystem" | "exists" | "readFileString" | "stream" | "sink" | "writeFileString"
>
const fs = FileSystem.make(coreImpl)
// => a full FileSystem
```

### FileSystem.makeNoop / FileSystem.layerNoop

`makeNoop` returns a stub `FileSystem` for tests: by default `exists` returns
`false`, `remove` succeeds, most reads fail with `NotFound`, and temp operations
die as not implemented. Pass overrides for the methods a test needs.
`layerNoop` wraps the same overrides in a `Layer`.

```ts
import { Effect, FileSystem } from "effect"

const testLayer = FileSystem.layerNoop({
  readFileString: () => Effect.succeed("mocked content"),
  exists: () => Effect.succeed(true)
})

const program = Effect.gen(function*() {
  const fs = yield* FileSystem.FileSystem
  return yield* fs.readFileString("anything.txt")
  // => "mocked content"
}).pipe(Effect.provide(testLayer))
```

### isFile

Type guard that narrows an unknown value to a [`File`](#the-file-interface) by
checking its runtime marker.

```ts
import { FileSystem } from "effect"

declare const value: unknown
if (FileSystem.isFile(value)) {
  // value is FileSystem.File here
}
```