diff --git a/docs/api/file-io.md b/docs/api/file-io.md index 506ec9b051..020259a946 100644 --- a/docs/api/file-io.md +++ b/docs/api/file-io.md @@ -212,6 +212,21 @@ const response = await fetch("https://bun.com"); await Bun.write("index.html", response); ``` +## Renaming files (`Bun.rename()`) + +Atomically rename or move files and directories. Similar to Node.js `fs.promises.rename()` but with an optional third parameter for conflict resolution. + +```ts +// Basic rename (replaces destination if exists) +await Bun.rename("old.txt", "new.txt"); + +// Atomic swap (Linux/macOS only, falls back to replace on Windows) +await Bun.rename("file1.txt", "file2.txt", "swap"); + +// Fail if destination exists +await Bun.rename("source.txt", "dest.txt", "no-replace"); +``` + ## Incremental writing with `FileSink` Bun provides a native incremental file writing API called `FileSink`. To retrieve a `FileSink` instance from a `BunFile`: diff --git a/docs/api/rename.md b/docs/api/rename.md deleted file mode 100644 index 5cb9d58d24..0000000000 --- a/docs/api/rename.md +++ /dev/null @@ -1,246 +0,0 @@ -# Bun.rename() - -Atomically rename a file or directory. This is similar to Node.js's `fs.promises.rename()`, but with an additional optional `conflict` parameter to control what happens when the destination already exists. - -```ts -function rename( - from: PathLike, - to: PathLike, - conflict?: "replace" | "swap" | "no-replace" -): Promise; -``` - -## Parameters - -- **from** - The current file or directory path (string, Buffer, or URL) -- **to** - The new file or directory path (string, Buffer, or URL) -- **conflict** - How to handle conflicts when destination exists: - - `"replace"` (default) - Replace the destination if it exists - - `"swap"` - Atomically swap the files (Linux/macOS only, falls back to replace on Windows) - - `"no-replace"` - Fail if destination already exists - -## Basic Usage - -```ts -// Simple rename - replaces destination if it exists -await Bun.rename("old-file.txt", "new-file.txt"); - -// Rename a directory -await Bun.rename("old-directory", "new-directory"); - -// Use Buffer paths -await Bun.rename(Buffer.from("old.txt"), Buffer.from("new.txt")); -``` - -## Conflict Resolution - -### Replace Mode (Default) - -By default, `Bun.rename()` will replace the destination if it exists: - -```ts -import fs from "fs/promises"; - -// Create source and destination files -await Bun.write("source.txt", "Source content"); -await Bun.write("dest.txt", "Original destination content"); - -// This will replace dest.txt with source.txt -await Bun.rename("source.txt", "dest.txt"); - -console.log(await Bun.file("dest.txt").text()); // "Source content" -// source.txt no longer exists -``` - -### No-Replace Mode - -Prevent overwriting existing files: - -```ts -// Create files -await Bun.write("source.txt", "Source content"); -await Bun.write("dest.txt", "Destination content"); - -try { - // This will throw an error because dest.txt exists - await Bun.rename("source.txt", "dest.txt", "no-replace"); -} catch (error) { - console.log("Error:", error.message); // File already exists -} - -// Both files still exist with their original content -``` - -### Swap Mode (Linux/macOS only) - -Atomically swap two files or directories: - -```ts -// Create two files -await Bun.write("file1.txt", "Content of file 1"); -await Bun.write("file2.txt", "Content of file 2"); - -// Atomically swap the files -await Bun.rename("file1.txt", "file2.txt", "swap"); - -// The files have been swapped -console.log(await Bun.file("file1.txt").text()); // "Content of file 2" -console.log(await Bun.file("file2.txt").text()); // "Content of file 1" -``` - -#### Directory Swapping - -```ts -// Create two directories with different content -await fs.mkdir("dir1"); -await fs.mkdir("dir2"); -await Bun.write("dir1/file.txt", "Content from dir1"); -await Bun.write("dir2/file.txt", "Content from dir2"); - -// Atomically swap the directories -await Bun.rename("dir1", "dir2", "swap"); - -// The directories have been swapped -console.log(await Bun.file("dir1/file.txt").text()); // "Content from dir2" -console.log(await Bun.file("dir2/file.txt").text()); // "Content from dir1" -``` - -## Platform Differences - -### Windows Limitations - -On Windows, some conflict resolution modes have limitations due to platform constraints: - -- **`"swap"`** - Falls back to `"replace"` behavior (atomic swap is not supported on Windows) -- **`"no-replace"`** - May not be fully atomic (checks existence then renames, creating a small race condition window) - -```ts -// On Windows, this behaves like "replace" -await Bun.rename("file1.txt", "file2.txt", "swap"); -``` - -### Unix/Linux/macOS - -On Unix-like systems, all conflict modes are fully supported with atomic operations. - -## Advanced Examples - -### Safe File Updates - -Use `"no-replace"` to safely create new files without overwriting: - -```ts -async function safeWrite(filename: string, content: string) { - const tempFile = `${filename}.tmp`; - - // Write to temporary file - await Bun.write(tempFile, content); - - try { - // Atomically rename if target doesn't exist - await Bun.rename(tempFile, filename, "no-replace"); - console.log(`Created ${filename}`); - } catch (error) { - // Clean up temp file if target already exists - await fs.unlink(tempFile); - throw new Error(`File ${filename} already exists`); - } -} -``` - -### Atomic File Replacement - -Use the default mode for atomic file updates: - -```ts -async function atomicUpdate(filename: string, updater: (content: string) => string) { - // Read current content - const currentContent = await Bun.file(filename).text(); - - // Create updated content in temp file - const tempFile = `${filename}.tmp.${Date.now()}`; - await Bun.write(tempFile, updater(currentContent)); - - // Atomically replace original file - await Bun.rename(tempFile, filename); // Uses "replace" by default -} - -await atomicUpdate("config.json", content => { - const config = JSON.parse(content); - config.version = "2.0.0"; - return JSON.stringify(config, null, 2); -}); -``` - -### Log Rotation with Swap - -Use `"swap"` to implement log rotation: - -```ts -async function rotateLog(currentLog: string, archiveLog: string) { - try { - // If both files exist, swap them - await Bun.rename(currentLog, archiveLog, "swap"); - } catch { - // If archive doesn't exist, just rename current to archive - await Bun.rename(currentLog, archiveLog); - } - - // Create new empty log file - await Bun.write(currentLog, ""); -} - -await rotateLog("app.log", "app.log.old"); -``` - -## Error Handling - -`Bun.rename()` throws errors for various conditions: - -```ts -try { - await Bun.rename("nonexistent.txt", "destination.txt"); -} catch (error) { - if (error.code === "ENOENT") { - console.log("Source file doesn't exist"); - } -} - -try { - await Bun.rename("source.txt", "dest.txt", "no-replace"); -} catch (error) { - if (error.code === "EEXIST") { - console.log("Destination file already exists"); - } -} -``` - -## Performance - -`Bun.rename()` is implemented using the most efficient system calls available: - -- **Linux**: Uses `renameat2()` with appropriate flags for atomic operations -- **macOS**: Uses `renameatx_np()` for extended rename operations -- **Windows**: Uses `MoveFileEx()` with atomic replacement when possible - -The operation is typically very fast since it only updates directory entries rather than copying file data. - -## Comparison with Node.js - -| Feature | Node.js `fs.rename()` | `Bun.rename()` | -|---------|----------------------|----------------| -| Basic rename | ✅ | ✅ | -| Atomic replacement | ✅ (on Unix) | ✅ | -| Conflict resolution | ❌ | ✅ | -| Atomic swap | ❌ | ✅ (Unix only) | -| No-overwrite mode | ❌ | ✅ | -| Cross-filesystem moves | Falls back to copy | Falls back automatically | - -```ts -// Node.js way -import { rename } from "fs/promises"; -await rename("old.txt", "new.txt"); - -// Bun way with more control -await Bun.rename("old.txt", "new.txt", "no-replace"); -``` \ No newline at end of file diff --git a/docs/nav.ts b/docs/nav.ts index 99dd48046b..600e8028f0 100644 --- a/docs/nav.ts +++ b/docs/nav.ts @@ -356,9 +356,6 @@ export default { page("api/file-io", "File I/O", { description: `Read and write files fast with Bun's heavily optimized file system API.`, }), // "`Bun.write`"), - page("api/rename", "`Bun.rename`", { - description: `Atomically rename files and directories with advanced conflict resolution options.`, - }), page("api/redis", "Redis client", { description: `Bun provides a fast, native Redis client with automatic command pipelining for better performance.`, }),