Path
The Path service builds and inspects file system paths without hard-coding
separators or assumptions about the host OS. By depending on the abstract Path
service and providing a platform implementation (NodePath.layer on Node.js),
the same code produces POSIX-style paths on Linux and macOS and Windows-style
paths on Windows. Most methods are pure string operations; the two that can fail
(fromFileUrl and toFileUrl) return an Effect with a BadArgument error.
import { NodePath } from "@effect/platform-node"import { Effect, Path } from "effect"
const program = Effect.gen(function*() { const path = yield* Path.Path
// `join` concatenates segments using the platform separator and collapses // redundant slashes — never build paths with string templates. const config = path.join("home", "ada", "project", "effect.config.ts") // "home/ada/project/effect.config.ts" on POSIX
// `resolve` produces an absolute path, resolving against the current // working directory and processing "." and ".." segments. const absolute = path.resolve("project", "..", "shared", "util.ts")
// Decompose a path into its parts. yield* Effect.log(path.basename(config)) // "effect.config.ts" yield* Effect.log(path.dirname(config)) // "home/ada/project" yield* Effect.log(path.extname(config)) // ".ts"
yield* Effect.log(`${config}\n${absolute}`)}).pipe( // Provide the Node implementation of the Path service. Effect.provide(NodePath.layer))Parsing and formatting
Section titled “Parsing and formatting”parse turns a path string into a structured Path.Parsed object, and
format rebuilds a path from such an object. This pair is the cleanest way to
change one component of a path — for example, swapping a file extension:
import { Effect, Path } from "effect"
// Replace the extension of a path, e.g. ".ts" -> ".js".const changeExtension = Effect.fn("changeExtension")(function*( file: string, ext: string) { const path = yield* Path.Path
const parsed = path.parse(file) // `parsed` is { root, dir, base, ext, name }. When `base` is set it wins, // so clear it and let `format` rebuild from `name` + `ext`. return path.format({ dir: parsed.dir, name: parsed.name, ext })})
const program = Effect.gen(function*() { const result = yield* changeExtension("src/index.ts", ".js") yield* Effect.log(result) // "src/index.js"})Relative and absolute paths
Section titled “Relative and absolute paths”relative computes the path from one location to another, and isAbsolute
tells you whether a path is already rooted. These are useful when displaying
paths relative to a project root or deciding whether to resolve against a base
directory:
import { Effect, Path } from "effect"
const program = Effect.gen(function*() { const path = yield* Path.Path
const from = "/home/ada/project" const to = "/home/ada/project/src/main.ts"
yield* Effect.log(path.relative(from, to)) // "src/main.ts" yield* Effect.log(path.isAbsolute(to)) // true yield* Effect.log(path.normalize("a/./b/../c")) // "a/c"})File URLs
Section titled “File URLs”Converting between paths and file:// URLs is the one place Path can fail —
an invalid URL or non-file: scheme yields a BadArgument. Because these
return effects, the failure is typed and handled like any other:
import { Effect, Path } from "effect"
const program = Effect.gen(function*() { const path = yield* Path.Path
// `import.meta.url` is a file:// URL; convert it to a filesystem path. const filePath = yield* path.fromFileUrl(new URL("file:///home/ada/app.ts")) yield* Effect.log(filePath) // "/home/ada/app.ts"
const url = yield* path.toFileUrl("/home/ada/app.ts") yield* Effect.log(url.href) // "file:///home/ada/app.ts"}).pipe( Effect.catchTag("BadArgument", (error) => Effect.logError(`invalid path or URL: ${error.message}`) ))Choosing a separator style
Section titled “Choosing a separator style”NodePath.layer follows the host operating system, but you can force a specific
style with NodePath.layerPosix (always /) or NodePath.layerWin32 (always
\). This is handy when generating paths for a different target than the
machine you are running on:
import { NodePath } from "@effect/platform-node"import { Effect, Path } from "effect"
const program = Effect.gen(function*() { const path = yield* Path.Path yield* Effect.log(path.join("a", "b", "c"))}).pipe( // Always emit POSIX paths regardless of the host OS. Effect.provide(NodePath.layerPosix))Paths produced here are exactly what the FileSystem
service expects, so the two services compose naturally.