import * as ByteSize from "./ByteSize.ts"; import * as Context from "./Context.ts"; import * as Effect from "./Effect.ts"; import * as Layer from "./Layer.ts"; import * as Option from "./Option.ts"; import { type PlatformError } from "./PlatformError.ts"; import type { Scope } from "./Scope.ts"; import * as Sink from "./Sink.ts"; import * as Stream from "./Stream.ts"; declare const TypeId = "~effect/FileSystem"; /** * Core interface for file system operations in Effect. * * **Details** * * The FileSystem interface provides a comprehensive set of file and directory operations * that work cross-platform. All operations return Effect values that can be composed, * transformed, and executed safely with proper error handling. * * **Example** (Accessing file system operations) * * ```ts import.meta.vitest * import { ByteSize, Effect, FileSystem } from "effect" * * const fileSystem = FileSystem.makeNoop({ * exists: () => Effect.succeed(true), * makeDirectory: () => Effect.void, * stat: () => Effect.succeed({ size: ByteSize.bytes(22) } as FileSystem.File.Info), * readFileString: () => Effect.succeed("{\"env\": \"development\"}") * }) * * const program = Effect.gen(function*() { * const fs = yield* FileSystem.FileSystem * * // Basic file operations * const exists = yield* fs.exists("./config.json") * if (!exists) { * yield* fs.writeFileString("./config.json", "{\"env\": \"development\"}") * } * * // Directory operations * yield* fs.makeDirectory("./logs", { recursive: true }) * * // File information * const stats = yield* fs.stat("./config.json") * // Read the file contents * const content = yield* fs.readFileString("./config.json") * return { size: stats.size, content } * }) * * const result = Effect.runSync(Effect.provideService(program, FileSystem.FileSystem, fileSystem)) * ByteSize.toBigInt(result.size) // => 22n * result.content // => "{\"env\": \"development\"}" * ``` * * @stability unstable * @category services * @since 4.0.0 */ export interface FileSystem { readonly [TypeId]: typeof TypeId; /** * Checks whether a file can be accessed. * You can optionally specify the level of access to check for. */ readonly access: (path: string, options?: { readonly ok?: boolean | undefined; readonly readable?: boolean | undefined; readonly writable?: boolean | undefined; }) => Effect.Effect; /** * Copy a file or directory from `fromPath` to `toPath`. * * **Details** * * Equivalent to `cp -r`. */ readonly copy: (fromPath: string, toPath: string, options?: { readonly overwrite?: boolean | undefined; readonly preserveTimestamps?: boolean | undefined; }) => Effect.Effect; /** * Copy a file from `fromPath` to `toPath`. */ readonly copyFile: (fromPath: string, toPath: string) => Effect.Effect; /** * Change the permissions of a file. */ readonly chmod: (path: string, mode: number) => Effect.Effect; /** * Change the owner and group of a file. */ readonly chown: (path: string, uid: number, gid: number) => Effect.Effect; /** * Glob a directory. */ readonly glob: (pattern: string, options?: { readonly root?: string | undefined; readonly exclude?: ReadonlyArray | undefined; }) => Effect.Effect, PlatformError>; /** * Checks whether a path exists. */ readonly exists: (path: string) => Effect.Effect; /** * Create a hard link from `fromPath` to `toPath`. */ readonly link: (fromPath: string, toPath: string) => Effect.Effect; /** * Get information about a file at `path` without following symbolic links. * See `File.Info` for metadata limits. */ readonly lstat: (path: string) => Effect.Effect; /** * Create a directory at `path`. You can optionally specify the mode and * whether to recursively create nested directories. */ readonly makeDirectory: (path: string, options?: { readonly recursive?: boolean | undefined; readonly mode?: number | undefined; }) => Effect.Effect; /** * Create a temporary directory. * * **Details** * * By default the directory will be created inside the system's default * temporary directory, but you can specify a different location by setting * the `directory` option. * * You can also specify a prefix for the directory name by setting the * `prefix` option. */ readonly makeTempDirectory: (options?: { readonly directory?: string | undefined; readonly prefix?: string | undefined; }) => Effect.Effect; /** * Create a temporary directory inside a scope. * * **Details** * * Functionally equivalent to `makeTempDirectory`, but the directory will be * automatically deleted when the scope is closed. */ readonly makeTempDirectoryScoped: (options?: { readonly directory?: string | undefined; readonly prefix?: string | undefined; }) => Effect.Effect; /** * Create a temporary file. * The directory creation is functionally equivalent to `makeTempDirectory`. * The file name will be a randomly generated string. */ readonly makeTempFile: (options?: { readonly directory?: string | undefined; readonly prefix?: string | undefined; readonly suffix?: string | undefined; }) => Effect.Effect; /** * Create a temporary file inside a scope. * * **Details** * * Functionally equivalent to `makeTempFile`, but the file will be * automatically deleted when the scope is closed. */ readonly makeTempFileScoped: (options?: { readonly directory?: string | undefined; readonly prefix?: string | undefined; readonly suffix?: string | undefined; }) => Effect.Effect; /** * Open a file at `path` with the specified `options`. * * **Details** * * The file handle will be automatically closed when the scope is closed. * * `noFollow` rejects symlinks at the final path component, not in parent * directories. Node and Bun support it on POSIX; unsupported platforms, * including Windows and Deno, fail with `BadArgument`. */ readonly open: (path: string, options?: { readonly flag?: OpenFlag | undefined; readonly mode?: number | undefined; readonly noFollow?: boolean | undefined; }) => Effect.Effect; /** * List the contents of a directory. * * **Details** * * You can recursively list the contents of nested directories by setting the * `recursive` option. */ readonly readDirectory: (path: string, options?: { readonly recursive?: boolean | undefined; }) => Effect.Effect, PlatformError>; /** * Read the contents of a file. */ readonly readFile: (path: string) => Effect.Effect; /** * Read the contents of a file. */ readonly readFileString: (path: string, encoding?: string) => Effect.Effect; /** * Read the destination of a symbolic link. */ readonly readLink: (path: string) => Effect.Effect; /** * Resolve a path to its canonicalized absolute pathname. */ readonly realPath: (path: string) => Effect.Effect; /** * Remove a file or directory. */ readonly remove: (path: string, options?: { /** * When `true`, you can recursively remove nested directories. */ readonly recursive?: boolean | undefined; /** * When `true`, exceptions will be ignored if `path` does not exist. */ readonly force?: boolean | undefined; }) => Effect.Effect; /** * Rename a file or directory. */ readonly rename: (oldPath: string, newPath: string) => Effect.Effect; /** * Create a writable `Sink` for the specified `path`. */ readonly sink: (path: string, options?: { readonly flag?: OpenFlag | undefined; readonly mode?: number | undefined; }) => Sink.Sink; /** * Get information about a file at `path`. See `File.Info` for metadata limits. */ readonly stat: (path: string) => Effect.Effect; /** * Create a readable `Stream` for the specified `path`. * * **Details** * * Changing the `bufferSize` option will change the internal buffer size of * the stream. It defaults to `4`. * * The `chunkSize` option will change the size of the chunks emitted by the * stream. It defaults to 64kb. * * Changing `offset` and `bytesToRead` will change the offset and the number * of bytes to read from the file. */ readonly stream: (path: string, options?: { readonly bytesToRead?: ByteSize.Input | undefined; readonly chunkSize?: number | undefined; readonly offset?: ByteSize.Input | undefined; }) => Stream.Stream; /** * Create a symbolic link from `fromPath` to `toPath`. */ readonly symlink: (fromPath: string, toPath: string) => Effect.Effect; /** * Truncate a file to a specified length. If the `length` is not specified, * the file will be truncated to length `0`. */ readonly truncate: (path: string, length?: number) => Effect.Effect; /** * Change the file system timestamps of the file at `path`. */ readonly utimes: (path: string, atime: Date | number, mtime: Date | number) => Effect.Effect; /** * Watch a directory or file for changes. * * **Details** * * By default, only changes to the direct children of the directory are * reported. Set the `recursive` option to `true` to watch for changes in * subdirectories as well. */ readonly watch: (path: string, options?: WatchOptions) => Stream.Stream; /** * Write data to a file at `path`. */ readonly writeFile: (path: string, data: Uint8Array, options?: { readonly flag?: OpenFlag | undefined; readonly mode?: number | undefined; }) => Effect.Effect; /** * Write a string to a file at `path`. */ readonly writeFileString: (path: string, data: string, options?: { readonly flag?: OpenFlag | undefined; readonly mode?: number | undefined; }) => Effect.Effect; } /** * File open flags that determine how a file is opened and what operations are allowed. * * **Details** * * These flags correspond to standard POSIX file open modes and control the file access * permissions and behavior when opening files. * * - `"r"` - Read-only. File must exist. * - `"r+"` - Read/write. File must exist. * - `"w"` - Write-only. Truncates file to zero length or creates new file. * - `"wx"` - Like 'w' but fails if file exists. * - `"w+"` - Read/write. Truncates file to zero length or creates new file. * - `"wx+"` - Like 'w+' but fails if file exists. * - `"a"` - Write-only. Appends to file or creates new file. * - `"ax"` - Like 'a' but fails if file exists. * - `"a+"` - Read/write. Appends to file or creates new file. * - `"ax+"` - Like 'a+' but fails if file exists. * * **Example** (Opening files with flags) * * ```ts import.meta.vitest * import type { FileSystem } from "effect" * * const flags: ReadonlyArray = ["r", "w", "a", "r+"] * flags // => ["r", "w", "a", "r+"] * ``` * * @stability unstable * @category models * @since 4.0.0 */ export type OpenFlag = "r" | "r+" | "w" | "wx" | "w+" | "wx+" | "a" | "ax" | "a+" | "ax+"; /** * Service tag for platform file-system operations. * * **When to use** * * Use to access or provide operations for files, directories, permissions, * streams, and sinks through the Effect context. * * **Details** * * This key is used to provide and access the FileSystem service in the Effect context. * * **Example** (Accessing and providing FileSystem) * * ```ts import.meta.vitest * import { Effect, FileSystem } from "effect" * * const customFs = FileSystem.makeNoop({ * exists: () => Effect.succeed(true), * readFileString: () => Effect.succeed("contents") * }) * * // Access the FileSystem service * const program = Effect.gen(function*() { * const fs = yield* FileSystem.FileSystem * * const exists = yield* fs.exists("./data.txt") * return exists ? yield* fs.readFileString("./data.txt") : undefined * }) * * const withCustomFs = Effect.provideService( * program, * FileSystem.FileSystem, * customFs * ) * Effect.runSync(withCustomFs) // => "contents" * ``` * * @stability unstable * @category services * @since 4.0.0 */ export declare const FileSystem: Context.Service; /** * Creates a FileSystem implementation from a partial implementation. * * **When to use** * * Use to build a concrete `FileSystem` service from platform-specific core * operations while deriving the convenience methods that can be implemented * from them. * * **Details** * * This function takes a partial FileSystem implementation and automatically provides * default implementations for `exists`, `readFileString`, `stream`, `sink`, and * `writeFileString` methods based on the provided core methods. * * @see {@link makeNoop} for a testing stub that accepts method overrides without requiring a complete implementation * @see {@link layerNoop} for providing a no-op `FileSystem` as a `Layer` in tests * * @stability unstable * @category constructors * @since 4.0.0 */ export declare const make: (impl: Omit) => FileSystem; /** * Creates a stub `FileSystem` implementation for tests. * * **Details** * * By default, `exists` returns `false`, `remove` succeeds, many file operations * fail with `PlatformError` `NotFound`, and temporary-directory/file operations * die as not implemented. Pass method overrides to provide the behavior needed * by a specific test without touching the real file system. * * **Example** (Creating a no-op FileSystem) * * ```ts import.meta.vitest * import { Effect, FileSystem, PlatformError } from "effect" * * // Create a test filesystem that only allows reading specific files * const testFs = FileSystem.makeNoop({ * readFileString: (path) => { * if (path === "test-config.json") { * return Effect.succeed("{\"test\": true}") * } * return Effect.fail( * PlatformError.systemError({ * _tag: "NotFound", * module: "FileSystem", * method: "readFileString", * description: "File not found", * pathOrDescriptor: path * }) * ) * }, * exists: (path) => Effect.succeed(path === "test-config.json") * }) * * // Use in tests * const program = Effect.gen(function*() { * const content = yield* testFs.readFileString("test-config.json") * return content * }) * * // Test with the no-op filesystem * const testProgram = Effect.provideService( * program, * FileSystem.FileSystem, * testFs * ) * Effect.runSync(testProgram) // => "{\"test\": true}" * ``` * * @stability unstable * @category constructors * @since 4.0.0 */ export declare const makeNoop: (fileSystem: Partial) => FileSystem; /** * Creates a Layer that provides a no-op FileSystem implementation for testing. * * **Details** * * This is a convenience function that wraps `makeNoop` in a Layer, making it easy * to provide the test filesystem to your Effect programs. * * **Example** (Providing a no-op FileSystem layer) * * ```ts import.meta.vitest * import { Effect, FileSystem } from "effect" * * // Create a test layer with specific behaviors * const testLayer = FileSystem.layerNoop({ * readFileString: (path) => Effect.succeed("mocked content"), * exists: () => Effect.succeed(true) * }) * * const program = Effect.gen(function*() { * const fs = yield* FileSystem.FileSystem * const content = yield* fs.readFileString("any-file.txt") * return content * }) * * // Provide the test layer * const testProgram = Effect.provide(program, testLayer) * Effect.runSync(testProgram) // => "mocked content" * ``` * * @stability unstable * @category layers * @since 4.0.0 */ export declare const layerNoop: (fileSystem: Partial) => Layer.Layer; /** * Runtime type identifier attached to `FileSystem.File` handles and used by * `isFile` to recognize them. * * **Details** * * This marker is part of the runtime representation of file handles. Prefer * `isFile` when narrowing unknown values. * * @see {@link File} for the open file handle shape that carries this marker * @see {@link isFile} for the public guard that checks this marker * * @stability unstable * @category type IDs * @since 4.0.0 */ export declare const FileTypeId = "~effect/FileSystem/File"; /** * Returns `true` if a value is a `File` handle by checking for the * `FileTypeId` marker. * * **When to use** * * Use when accepting an unknown value and you need to narrow it to a `File` * before calling file-handle operations. * * **Details** * * This is a structural marker check. It does not validate the marker value or * the shape of the file handle. * * @see {@link File} for the file-handle interface narrowed by this guard * @see {@link FileTypeId} for the runtime marker checked by this guard * * @stability unstable * @category guards * @since 4.0.0 */ export declare const isFile: (u: unknown) => u is File; /** * Interface representing an open file handle. * * **Details** * * Provides low-level file operations including reading, writing, seeking, * and retrieving file information. File handles are automatically managed * within scoped operations to ensure proper cleanup. * * Each handle tracks its own cursor, which can differ from the POSIX file * offset. In append mode, writes go to the end of the file without moving the * cursor. Outside append mode, `truncate` clamps a cursor past the new length * to that length. * * Positional reads leave the cursor unchanged and may run concurrently on * one handle. Cursor operations (`seek`, reads without a position, and writes) * must not run concurrently with each other. * * **Example** (Working with file handles) * * ```ts import.meta.vitest * import { ByteSize, Effect, FileSystem, Option } from "effect" * * const file: FileSystem.File = { * [FileSystem.FileTypeId]: FileSystem.FileTypeId, * stat: Effect.succeed({ size: ByteSize.bytes(5) } as FileSystem.File.Info), * seek: () => Effect.succeed(BigInt(0)), * sync: Effect.void, * read: (buffer) => Effect.sync(() => { * buffer.set([1, 2, 3, 4, 5]) * return 5 * }), * readAlloc: () => Effect.succeed(Option.none()), * truncate: () => Effect.void, * write: (buffer) => Effect.succeed(buffer.length), * writeAll: () => Effect.void * } * * const program = Effect.gen(function*() { * const stats = yield* file.stat * const buffer = new Uint8Array(5) * const bytesRead = yield* file.read(buffer) * yield* file.writeAll(new TextEncoder().encode("Hello")) * yield* file.sync * return { size: stats.size, bytesRead, buffer: Array.from(buffer) } * }) * * const result = Effect.runSync(program) * ByteSize.toBigInt(result.size) // => 5n * result.bytesRead // => 5 * result.buffer // => [1, 2, 3, 4, 5] * ``` * * @stability unstable * @category models * @since 4.0.0 */ export interface File { readonly [FileTypeId]: typeof FileTypeId; /** * Get information about the open file. See `File.Info` for metadata limits. */ readonly stat: Effect.Effect; /** * Seeks before the start fail with `BadArgument` and leave the cursor unchanged. */ readonly seek: (offset: bigint, from: SeekMode) => Effect.Effect; readonly sync: Effect.Effect; /** * Reads into `buffer`, returning the byte count or `0` at EOF. Advances the * cursor unless `options.position` is set (see `File.ReadOptions`). */ readonly read: (buffer: Uint8Array, options?: File.ReadOptions) => Effect.Effect; /** * Reads up to `size` bytes into a new buffer, returning `Option.none()` at * EOF. Advances the cursor unless `options.position` is set (see * `File.ReadOptions`). */ readonly readAlloc: (size: number, options?: File.ReadOptions) => Effect.Effect, PlatformError>; readonly truncate: (length?: number) => Effect.Effect; readonly write: (buffer: Uint8Array) => Effect.Effect; readonly writeAll: (buffer: Uint8Array) => Effect.Effect; } /** * Namespace containing types associated with open file handles, including file * descriptors, entry kinds, and stat information. * * @stability unstable * @since 4.0.0 */ export declare namespace File { /** * Options for `File.read` and `File.readAlloc`. * * **Details** * * `position` is a byte offset from the start of the file. It leaves the * cursor unchanged, fails with `BadArgument` if negative, and reads nothing * at or past EOF. * * **Example** (Reading a byte range without moving the cursor) * * ```ts import.meta.vitest * import type { FileSystem } from "effect" * * const options: FileSystem.File.ReadOptions = { position: BigInt(1024) } * options.position // => 1024n * ``` * * @category models * @since 4.1.0 */ interface ReadOptions { readonly position?: bigint | undefined; } /** * Enumeration of possible file system entry types. * * **Details** * * Represents the different types of entries that can exist in a file system, * from regular files to special device files and symbolic links. * * @category models * @since 4.0.0 */ type Type = "File" | "Directory" | "SymbolicLink" | "BlockDevice" | "CharacterDevice" | "FIFO" | "Socket" | "Unknown"; /** * Comprehensive file information structure. * * **Details** * * Contains metadata about a file or directory including type, timestamps, * permissions, and size information. This structure is returned by file * stat operations. * * `dev`, `ino`, `rdev`, `nlink`, and `blocks` are `bigint` values, so * 64-bit device identifiers, inode numbers, and counts are not limited to * the safe integer range. `mode`, `uid`, and `gid` are 32-bit values and * remain `number`. Optional metadata is `Option.none()` only when the * platform does not report it. * * Node and Bun report all fields exactly, with `dev`, `ino`, `rdev`, and * `nlink` interpreted as unsigned 64-bit integers. Unsafe `mode` values fail * the stat operation with `BadArgument`. Deno reports these fields as * `number`, so on Deno values above `Number.MAX_SAFE_INTEGER` are already * rounded before they are converted to `bigint`. * * **Example** (Inspecting file information) * * ```ts import.meta.vitest * import { ByteSize, FileSystem, Option } from "effect" * * const info: FileSystem.File.Info = { * type: "File", * mtime: Option.none(), * atime: Option.none(), * birthtime: Option.none(), * dev: 1n, * ino: Option.none(), * mode: 0o644, * nlink: Option.none(), * uid: Option.none(), * gid: Option.none(), * rdev: Option.none(), * size: ByteSize.bytes(5), * blksize: Option.none(), * blocks: Option.none() * } * * info.type // => "File" * ByteSize.toBigInt(info.size) // => 5n * info.mode.toString(8) // => "644" * * const modified = Option.match(info.mtime, { * onNone: () => "unavailable", * onSome: (mtime) => mtime.toISOString() * }) * modified // => "unavailable" * info.type === "File" // => true * ``` * * @category models * @since 4.0.0 */ interface Info { readonly type: Type; readonly mtime: Option.Option; readonly atime: Option.Option; readonly birthtime: Option.Option; readonly dev: bigint; readonly ino: Option.Option; readonly mode: number; readonly nlink: Option.Option; readonly uid: Option.Option; readonly gid: Option.Option; readonly rdev: Option.Option; readonly size: ByteSize.ByteSize; readonly blksize: Option.Option; readonly blocks: Option.Option; } } /** * Specifies the reference point for seeking within an open file. * * **When to use** * * Use with `File` handles when positioning the cursor before a read or write * and the offset must be interpreted from either the start of the file or the * current cursor. * * **Details** * * - `"start"` seeks from the beginning of the file. * - `"current"` seeks from the current cursor position. * * @see {@link File} for the open file handle API whose `seek` method consumes this mode * * @stability unstable * @category models * @since 4.0.0 */ export type SeekMode = "start" | "current"; /** * Options for watching files or directories. * * @stability unstable * @category models * @since 4.0.0 */ export interface WatchOptions { /** * When `true`, changes in subdirectories are also reported. */ readonly recursive?: boolean | undefined; /** * Called once the underlying watcher is installed and can buffer events. * Stream construction alone does not install a watcher. Custom watch * backends must also call this callback after installation, when supplied. */ readonly onReady?: (() => void) | undefined; } /** * Represents file system events emitted when watching files or directories. * * **When to use** * * Use when consuming file system watch streams and pattern matching on `_tag` * to handle created, updated, or removed paths. * * **Details** * * The union covers create, update, and remove events. Each event carries the * reported `path`. * * @see {@link FileSystem} for the service interface whose `watch` operation emits these events * * @stability unstable * @category models * @since 4.0.0 */ export type WatchEvent = WatchEvent.Create | WatchEvent.Update | WatchEvent.Remove; /** * Namespace containing the concrete event shapes emitted by `FileSystem.watch`. * * @stability unstable * @since 4.0.0 */ export declare namespace WatchEvent { /** * Event representing the creation of a new file or directory. * * **Details** * * This event is triggered when a new file or directory is created * in the watched location. * * @category models * @since 4.0.0 */ interface Create { readonly _tag: "Create"; readonly path: string; } /** * Event representing the modification of an existing file or directory. * * **Details** * * This event is triggered when an existing file or directory is * modified in the watched location. * * @category models * @since 4.0.0 */ interface Update { readonly _tag: "Update"; readonly path: string; } /** * Event representing the deletion of a file or directory. * * **Details** * * This event is triggered when a file or directory is deleted * from the watched location. * * @category models * @since 4.0.0 */ interface Remove { readonly _tag: "Remove"; readonly path: string; } } declare const WatchBackend_base: Context.ServiceClass Option.Option>; }>; /** * Service key for file system watch backend implementations. * * **Details** * * This service provides the low-level file watching capabilities that can be * implemented differently on various platforms (e.g., inotify on Linux, * FSEvents on macOS, etc.). * * **Example** (Providing a custom watch backend) * * ```ts import.meta.vitest * import { Effect, FileSystem, Option, Stream } from "effect" * * // Custom watch backend implementation * const customWatchBackend = { * register: (path: string, stat: FileSystem.File.Info) => { * // Implementation would depend on platform * return Option.some(Stream.empty) // Placeholder implementation * } * } * * const program = Effect.gen(function*() { * const backend = yield* FileSystem.WatchBackend * return Option.isSome( * backend.register("./directory", { type: "Directory" } as FileSystem.File.Info) * ) * }) * * const withCustomBackend = Effect.provideService( * program, * FileSystem.WatchBackend, * customWatchBackend * ) * Effect.runSync(withCustomBackend) // => true * ``` * * @stability unstable * @category services * @since 4.0.0 */ export declare class WatchBackend extends WatchBackend_base { } export {}; //# sourceMappingURL=FileSystem.d.ts.map