# Filesystem I/O Filesystem APIs come in asynchronous and synchronous modules: | | Async | Sync | |---|---|---| | Primitives | `avril:std` | `avril:std/sync` | | Convenience operations | `avril:fs` | `avril:fs/sync` | The sync modules use the same function names, without a `Sync` suffix. They return values directly and throw errors synchronously. No CLI flag is needed. ```ts import { readTextFile } from "avril:fs/sync"; const config = JSON.parse(readTextFile("config.json")); ``` When mixing modules, alias imports as needed: ```ts import { readTextFile } from "avril:fs"; import { readTextFile as readTextFileSync } from "avril:fs/sync"; ``` For lexical path manipulation without filesystem access (joining, normalization, parsing, `file:` URL conversion), see [`avril:path`](path.md). ## Synchronous primitives `avril:std/sync` exports: - `open`, `create`, `stat`, `lstat`, `readDir` - `mkdir`, `remove`, `rename`, `copyFile` - `symlink`, `readLink`, `realPath`, `link` - `chmod`, `chown`, `lchown`, `utime`, `lutime`, `truncate` - The same `FileHandle`, `SeekMode`, and `errors` objects as `avril:std`. Options, paths (strings or file URLs), metadata, errors, and platform limitations match the async APIs. `readDir` returns a synchronous iterable; use `for ... of`. `mkdir` creates one directory; use `mkdirs` for parents. Watchers remain in `avril:std` and are asynchronous. ## Handles Both versions of `open` and `create` return the same `FileHandle`, with async methods and synchronous counterparts: - `readSync`, `writeSync`, `seekSync`, `statSync`, `truncateSync` - `syncSync`, `syncDataSync` - `fchmodSync`, `fchownSync`, `futimeSync`, `flockSync`, `funlockSync` `close()` and `[Symbol.dispose]()` are already synchronous. A handle retains its readable/writable Web Streams and can be lent to subprocesses regardless of how it was opened. ```ts import { open, SeekMode } from "avril:std/sync"; const file = open("data.bin", { read: true, write: true }); try { const header = new Uint8Array(16); const count = file.readSync(header); file.seekSync(0, SeekMode.Start); await file.write(new Uint8Array([1, 2, 3])); } finally { file.close(); } ``` Direct reads and writes may be partial. Reads return `null` at EOF, or `0` for an empty buffer. Use the convenience functions for whole-file transfers. Opening non-regular files is not supported. `writev` (and `writevSync`) writes an array of views as one ordered operation: the byte sequence equals the concatenation of its views, empty views are skipped, and unlike `write` the call completes the entire vector before resolving with the total bytes written. No other operation on the handle can interleave between its batches, and an error may leave a prefix written without rolling it back. The whole vector is validated and snapshotted at invocation, so mutating the array or its views afterwards cannot affect any submitted bytes. An empty or all-empty vector returns 0, still performing the usual closed and authority checks. Snapshots retain the entire payload until completion; await writes rather than queueing unbounded batches. Sync and async handle operations share the native queue and file cursor. A sync operation waits behind already-submitted native operations, but does not run JavaScript callbacks or settle their JS promises while waiting. Web Stream writes can defer submission until a later JS turn; await stream writes before mixing them with direct handle operations. Advisory lock waits release the handle queue, as with async `flock`. Locks are exclusive by default. A synchronous lock wait blocks its isolate: a timer or promise callback in that same isolate cannot release the conflicting lock. Another isolate or process must do so. Terminating a worker interrupts its synchronous lock wait. ## Convenience operations `avril:fs/sync` exports `readFile`, `readTextFile`, `writeFile`, `writeTextFile`, `mkdirs`, `makeTempDir`, `makeTempFile`, `copy`, `move`, and `exists`. These follow the async module's behavior: - Whole-file operations handle partial reads and writes and close their handles. - Text is UTF-8. Writes support `append`, `create`, `createNew`, and `mode`. - `mkdirs` creates missing parents and accepts existing directories. - `copy` handles trees and preserves symlinks by default; `{ dereference: true }` follows them and rejects ancestor cycles. Ordinary mode bits are preserved. - `move` tries rename, then copies and removes on cross-device moves. That fallback is not atomic. - `exists` returns false only for missing paths; permission errors still throw. - `makeTempDir` and `makeTempFile` create a uniquely-named empty directory (`0o700`) or file (`0o600`) from `{ dir, prefix, suffix }`, defaulting to the OS temp directory and empty affixes. Names are `prefix` plus random hex plus `suffix`; collisions retry. Missing parents are an error, and the caller removes the result. Prefixes and suffixes reject control characters and `<>:"/\\|?*`. ## Authority and scope Both modes use the same capability checks, including live `pledge()` restrictions on already-open handles and symlink-target checks. The sync modules do not grant additional authority. These are avril filesystem APIs, not a Deno facade. Blocking standard-stream interfaces are also available from `avril:std/sync`; see [standard I/O](stdio.md). There are no synchronous socket APIs or `globalThis.Deno`.