npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@effected/xdg

v0.1.7

Published

XDG Base Directory resolution for Effect: environment paths, app-namespaced directories, native OS conventions and config-file resolvers.

Readme

@effected/xdg

npm License: MIT Node.js %3E%3D24.11.0 TypeScript 7.0

XDG Base Directory resolution for Effect. Xdg reads the environment — $HOME, the four *_HOME variables, $XDG_RUNTIME_DIR, and the $XDG_CONFIG_DIRS / $XDG_DATA_DIRS search paths — once, at layer construction. AppDirs turns that into the config, data, cache, state and runtime directories for one application namespace, with on-demand creation. NativeDirs supplies the macOS and Windows conventions for applications that want them, and XdgConfig plugs the whole thing into @effected/config-file as a resolver chain and a save target.

Pre-release. This package is part of the @effected/* kit, in pre-1.0.0 development against a single pinned Effect v4 beta. Packages graduate to 1.0.0 once Effect 4.0.0 ships. To hold your own effect versions at exactly the ones the kit is built and tested against, install @effected/pnpm-plugin-effect.

Stability: unstable. This package's API surface is not yet considered complete and may change across 0.x releases. Pin an exact version — even a package marked stable before 1.0.0 can introduce a breaking change by accident, and an exact pin turns that into a type-check error rather than a runtime surprise. Full policy: release strategy.

Why @effected/xdg

Path resolution is not IO, and modeling it as IO poisons everything downstream. The environment is fixed for the life of a process, so this package reads it exactly once — when the layer is built — and the service's shape is the resolved value. appDirs.dirs.config is a string, not an Effect<string, XdgEnvError>. Reading a path cannot fail, cannot be observed to do IO and drops straight into config-file's defaultPath slot, which is typed Effect<string, never, R> and would otherwise need an orDie to satisfy.

Two more things follow from taking the spec seriously. The system search paths are half of XDG and are usually skipped: a config lookup here probes the app's own config directory — whichever rung of the precedence below resolved it — and then each $XDG_CONFIG_DIRS entry, namespaced, and it absorbs failure per candidate — an unreadable /etc/xdg means "this candidate did not match", never "abort the search and hide the perfectly readable file below it". And the runtime directory has no fallback ladder, because there is no defensible one: it must be user-owned and mode 0700, so when $XDG_RUNTIME_DIR is unset the key is simply absent rather than pointing somewhere invented.

Install

npm install @effected/xdg effect @effect/platform-node
pnpm add @effected/xdg effect @effect/platform-node

Requires Node.js >=24.11.0.

All @effected/* packages are ESM-only: the exports maps publish only import conditions, so require() — including tools that resolve in CJS mode — fails with Node's ERR_PACKAGE_PATH_NOT_EXPORTED rather than loading a CJS build that does not exist. Import from an ES module.

effect v4 is a peer dependency, and so are @effected/walker (the upward-traversal and search primitives the resolvers are built on) and @effected/config-file (whose ConfigResolver seam XdgConfig implements). Package managers that install peers automatically will pull them in; add them to your manifest explicitly if yours does not. There are no runtime dependencies.

Creating directories needs a FileSystem and a Path implementation, provided once at the edge — from @effect/platform-node on Node. Resolution itself needs neither.

Quick start

Build AppDirs for your namespace, provide it the Xdg environment and the platform layers, and read the paths:

import { AppDirs, Xdg } from "@effected/xdg";
import { NodeFileSystem, NodePath } from "@effect/platform-node";
import { Effect, Layer } from "effect";

const PlatformLive = Layer.mergeAll(NodeFileSystem.layer, NodePath.layer);

const AppDirsLive = AppDirs.layer({ namespace: "myapp", native: true }).pipe(
  Layer.provide(Layer.mergeAll(Xdg.layer, PlatformLive)),
);

const program = Effect.gen(function* () {
  const appDirs = yield* AppDirs;
  console.log(appDirs.dirs.config);
  // Linux, XDG_CONFIG_HOME set:   $XDG_CONFIG_HOME/myapp
  // Linux, XDG_CONFIG_HOME unset: $HOME/.myapp
  // macOS with `native: true`:    $HOME/Library/Application Support/myapp
  return yield* appDirs.ensureConfig;
  // The same path, now created on disk.
});

Effect.runPromise(program.pipe(Effect.provide(AppDirsLive)));

AppDirs.layer is a layer-returning function, not a layer: calling it twice builds two independent services. Bind its result to a const, as above, and provide that const.

Precedence

Each of config, data, cache and state resolves through five rungs, first match wins:

  1. an explicit dirs.<kind> override — an absolute path, and it wins outright;
  2. the XDG variable, namespaced: $XDG_CONFIG_HOME/<namespace>;
  3. the OS-native directory, when native: true and the platform has one;
  4. $HOME/<fallbackDir> — all four kinds collapse into that one directory;
  5. $HOME/.<namespace>.

Rungs 4 and 5 are deliberately not the spec's per-kind defaults (~/.config, ~/.local/share): an application that wants those passes them as dirs overrides. The runtime directory skips the ladder entirely — an override, or $XDG_RUNTIME_DIR/<namespace>, or nothing.

NativeDirs.resolve is pure — platform, namespace, environment and Path all arrive as parameters — so the whole matrix is testable with no filesystem and no process.platform read. On macOS config, data and state collapse to ~/Library/Application Support/<ns> while cache stays under ~/Library/Caches/<ns>; on Windows they split across %APPDATA% and %LOCALAPPDATA%. Everywhere else it returns Option.none(), because on Linux XDG is the native convention and there is nothing to override.

The platform is a Context.Reference, never a global read. Pin it in a test with Layer.succeed(CurrentPlatform, "win32") and exercise the Windows paths on a Mac.

Config files

XdgConfig is the bridge into @effected/config-file. resolver searches the app's whole config search path, nativeResolver probes the OS-native directory, and savePath names the default write target:

import { ConfigFile, JsonCodec, MergeStrategy } from "@effected/config-file";
import { XdgConfig } from "@effected/xdg";
import { Schema } from "effect";

class AppShape extends Schema.Class<AppShape>("AppShape")({
  port: Schema.Number,
  host: Schema.String,
}) {}

class AppConfig extends ConfigFile.Service<AppConfig, AppShape>()("myapp/Config") {}

export const AppConfigLive = ConfigFile.layer(AppConfig, {
  schema: AppShape,
  codec: JsonCodec,
  resolvers: [
    XdgConfig.resolver({ filename: "config.json" }),
    XdgConfig.nativeResolver({ namespace: "myapp", filename: "config.json" }),
  ],
  strategy: MergeStrategy.firstMatch<AppShape>(),
  defaultPath: XdgConfig.savePath("config.json"),
});

Order matters: put resolver before nativeResolver so an existing ~/.config/<app>/config.json still wins over the OS-native directory. savePath does not create the directory — ConfigFile.save already creates the parent of whatever path it is handed.

Errors

| Tag | Means | Recovery | | --- | --- | --- | | XdgEnvError | $HOME is not set. Carries variable and the structural cause — the underlying ConfigError. | The one environment failure there is. Every other XDG variable is optional by construction, and its absence is a resolved default rather than an error. | | AppDirsError | A directory could not be created. Carries directory (which kind), path and the structural cause. | The only way AppDirs fails, because resolution already happened. Check permissions. |

A namespace that is empty, or contains a path separator, or is exactly . or .., is a defect at layer construction rather than a typed error. It can only come from code, and namespace: "../.." would resolve the application's directories outside $HOME entirely.

Features

  • Xdg / XdgPaths — the resolved XDG environment as a value, including the $XDG_CONFIG_DIRS and $XDG_DATA_DIRS search paths, split and defaulted per the spec. Xdg.layerFrom serves fixed paths for tests.
  • AppDirs / ResolvedAppDirs — the five app-namespaced directories plus the config and data search paths, with ensureConfig, ensureData, ensureCache, ensureState, ensureRuntime and ensure for on-demand creation.
  • NativeDirs — the macOS and Windows conventions, resolved purely from a platform, a namespace and an environment.
  • CurrentPlatform — the platform as a Context.Reference, defaulting to process.platform and overridable in a test.
  • XdgConfigresolver, nativeResolver and savePath, dropping straight into @effected/config-file's ConfigResolver and defaultPath slots.
  • XdgEnvError / AppDirsError — tagged errors carrying their cause structurally.

License

MIT