EdgeAIG's picture
download
raw
26.5 kB
/**
* Data sources used by `Config` to load raw configuration values. A
* `ConfigProvider` reads paths from places such as environment variables,
* JavaScript objects, `.env` contents, or directories, and returns a uniform
* `Node` shape that config schemas can decode. The module also includes helpers
* for composing providers, changing paths, and installing providers through
* layers.
*
* @since 4.0.0
*/
import * as Context from "./Context.js";
import * as Data from "./Data.js";
import * as Effect from "./Effect.js";
import * as FileSystem from "./FileSystem.js";
import { format } from "./Formatter.js";
import { dual, flow } from "./Function.js";
import { PipeInspectableProto } from "./internal/core.js";
import * as Layer from "./Layer.js";
import * as Path_ from "./Path.js";
import * as Predicate from "./Predicate.js";
import * as Str from "./String.js";
/**
* Creates a `Value` node representing a terminal string leaf.
*
* **When to use**
*
* Use when building nodes inside a custom `ConfigProvider`'s `get`
* callback.
*
* **Details**
*
* The function returns a new plain object.
*
* **Example** (Creating a value node)
*
* ```ts
* import { ConfigProvider } from "effect"
*
* const node = ConfigProvider.makeValue("3000")
* // { _tag: "Value", value: "3000" }
* ```
*
* @see {@link makeRecord} – for object-like containers
* @see {@link makeArray} – for array-like containers
*
* @category constructors
* @since 4.0.0
*/
export function makeValue(value) {
return {
_tag: "Value",
value
};
}
/**
* Creates a `Record` node representing an object-like container with known
* child keys.
*
* **When to use**
*
* Use when you need to describe a directory or JSON object inside a custom
* provider.
*
* **Details**
*
* The optional `value` allows a node to be both a container and a leaf at the
* same time (for example, an env var `A=x` that also has children `A_FOO` and
* `A_BAR`).
*
* **Example** (Creating a record node)
*
* ```ts
* import { ConfigProvider } from "effect"
*
* const node = ConfigProvider.makeRecord(new Set(["host", "port"]))
* // { _tag: "Record", keys: Set(["host", "port"]), value: undefined }
* ```
*
* @see {@link makeValue} – for terminal leaves
* @see {@link makeArray} – for array-like containers
*
* @category constructors
* @since 4.0.0
*/
export function makeRecord(keys, value) {
return {
_tag: "Record",
keys,
value
};
}
/**
* Creates an `Array` node representing an indexed container with a known
* length.
*
* **When to use**
*
* Use when you need to describe a JSON array or numerically indexed env vars
* inside a custom provider.
*
* **Details**
*
* The optional `value` allows a node to be both a container and a leaf at the
* same time.
*
* **Example** (Creating an array node)
*
* ```ts
* import { ConfigProvider } from "effect"
*
* const node = ConfigProvider.makeArray(3)
* // { _tag: "Array", length: 3, value: undefined }
* ```
*
* @see {@link makeValue} – for terminal leaves
* @see {@link makeRecord} – for object-like containers
*
* @category constructors
* @since 4.0.0
*/
export function makeArray(length, value) {
return {
_tag: "Array",
length,
value
};
}
/**
* Typed error indicating that a configuration source could not be read.
*
* **When to use**
*
* Use when you need to report that a custom provider's underlying store is
* unreachable or produced an I/O error while reading configuration data.
*
* **Gotchas**
*
* Do not use `SourceError` for "key not found". That case is represented by
* returning `undefined` from `load` or `get`.
*
* **Example** (Failing with a SourceError)
*
* ```ts
* import { ConfigProvider, Effect } from "effect"
*
* const provider = ConfigProvider.make((_path) =>
* Effect.fail(
* new ConfigProvider.SourceError({ message: "connection refused" })
* )
* )
* ```
*
* @see {@link ConfigProvider} – the interface whose `load`/`get` may fail
* with this error
*
* @category models
* @since 4.0.0
*/
export class SourceError extends /*#__PURE__*/Data.TaggedError("SourceError") {}
/**
* Context reference for the active raw configuration provider, registered in the context with a
* default value of `fromEnv()`. Because it is a `Context.Reference`, it is
* available without explicit provision; `Config` schemas automatically resolve
* it.
*
* **When to use**
*
* Use to override the active raw configuration provider for an entire program,
* or retrieve the current provider inside an Effect.
*
* **Example** (Providing a custom provider)
*
* ```ts
* import { ConfigProvider, Effect } from "effect"
*
* const provider = ConfigProvider.fromUnknown({ port: 8080 })
*
* const program = Effect.gen(function*() {
* const current = yield* ConfigProvider.ConfigProvider
* return current
* }).pipe(
* Effect.provideService(ConfigProvider.ConfigProvider, provider)
* )
* ```
*
* @see {@link layer} – install a provider as a Layer
* @see {@link layerAdd} – add a fallback provider as a Layer
*
* @category services
* @since 2.0.0
*/
export const ConfigProvider = /*#__PURE__*/Context.Reference("effect/ConfigProvider", {
defaultValue: () => fromEnv()
});
const Proto = {
...PipeInspectableProto,
toJSON() {
return {
_id: "ConfigProvider"
};
}
};
/**
* Creates a `ConfigProvider` from a raw lookup function.
*
* **When to use**
*
* Use when implementing a provider backed by a custom store, such as a
* database, remote API, or in-memory map.
*
* **Details**
*
* The `get` callback receives a `Path` and must return
* `Effect<Node | undefined, SourceError>`. Return `undefined` when the path
* does not exist; fail with `SourceError` only for actual I/O errors.
*
* The optional `mapInput` and `prefix` parameters are wired into the
* resulting `load` method so that combinators like {@link mapInput} and
* {@link nested} can compose without wrapping `get`.
*
* **Example** (A simple in-memory provider)
*
* ```ts
* import { ConfigProvider, Effect } from "effect"
*
* const data: Record<string, string> = {
* host: "localhost",
* port: "5432"
* }
*
* const provider = ConfigProvider.make((path) => {
* const key = path.join(".")
* const value = data[key]
* return Effect.succeed(
* value !== undefined ? ConfigProvider.makeValue(value) : undefined
* )
* })
* ```
*
* @see {@link fromEnv} – pre-built provider for environment variables
* @see {@link fromUnknown} – pre-built provider for JSON objects
*
* @category constructors
* @since 2.0.0
*/
export function make(get, mapInput, prefix) {
const self = Object.create(Proto);
self.get = get;
self.mapInput = mapInput;
self.prefix = prefix;
self.load = path => {
if (mapInput) path = mapInput(path);
if (prefix) path = [...prefix, ...path];
return get(path);
};
return self;
}
/**
* Returns a provider that falls back to `that` when `self` returns `undefined`
* for a path.
*
* **When to use**
*
* Use to layer multiple config sources, such as env vars plus a defaults file,
* or provide partial overrides on top of a base config.
*
* **Details**
*
* Supports both data-last and data-first calling conventions.
*
* **Gotchas**
*
* The fallback only runs when the path is not found (`undefined`). A
* `SourceError` from `self` is not caught; it propagates immediately.
*
* **Example** (Falling back to a default provider)
*
* ```ts
* import { ConfigProvider } from "effect"
*
* const envProvider = ConfigProvider.fromEnv({
* env: { HOST: "prod.example.com" }
* })
* const defaults = ConfigProvider.fromUnknown({ HOST: "localhost", PORT: "3000" })
*
* const combined = ConfigProvider.orElse(envProvider, defaults)
* ```
*
* @see {@link layerAdd} – install a fallback provider via a Layer
*
* @category combinators
* @since 2.0.0
*/
export const orElse = /*#__PURE__*/dual(2, (self, that) => make(path => Effect.flatMap(self.get(path), node => node ? Effect.succeed(node) : that.get(path))));
/**
* Transforms the path segments before they reach the underlying store.
*
* **When to use**
*
* Use when you need to rename, re-case, or otherwise transform config path
* segments before lookup.
*
* **Details**
*
* The function `f` receives the full path and must return a new path. If the
* provider already has a `mapInput`, the functions compose: the existing
* mapping runs first, then `f`. Supports both data-last and data-first calling
* conventions.
*
* **Example** (Uppercasing path segments)
*
* ```ts
* import { ConfigProvider } from "effect"
*
* const provider = ConfigProvider.fromEnv({
* env: { APP_HOST: "localhost" }
* })
*
* const upper = ConfigProvider.mapInput(provider, (path) =>
* path.map((seg) =>
* typeof seg === "string" ? seg.toUpperCase() : seg
* )
* )
* ```
*
* @see {@link constantCase} – a preset that converts to `CONSTANT_CASE`
* @see {@link nested} – for prepending a prefix instead of transforming
*
* @category combinators
* @since 4.0.0
*/
export const mapInput = /*#__PURE__*/dual(2, (self, f) => {
return make(self.get, self.mapInput ? flow(self.mapInput, f) : f, self.prefix ? f(self.prefix) : undefined);
});
/**
* Converts all string path segments to `CONSTANT_CASE` before lookup.
*
* **When to use**
*
* Use to bridge camelCase schema keys to `SCREAMING_SNAKE_CASE`
* environment variables.
*
* **Details**
*
* Numeric segments are left unchanged. This is a specialization of
* {@link mapInput}.
*
* **Example** (Resolving camelCase keys to env vars)
*
* ```ts
* import { ConfigProvider } from "effect"
*
* const provider = ConfigProvider.fromEnv({
* env: { DATABASE_HOST: "localhost" }
* }).pipe(ConfigProvider.constantCase)
*
* // path ["databaseHost"] now resolves to env var DATABASE_HOST
* ```
*
* @see {@link mapInput} – for arbitrary path transformations
*
* @category combinators
* @since 2.0.0
*/
export const constantCase = /*#__PURE__*/mapInput(path => path.map(seg => typeof seg === "number" ? seg : Str.constantCase(seg)));
/**
* Scopes a provider so that all lookups are prefixed with the given path
* segments.
*
* **When to use**
*
* Use to namespace config under a prefix like `"app"` or `"database"`, or
* reuse the same provider shape for multiple sub-configs.
*
* **Details**
*
* Accepts a single string or a full `Path` array. Supports both data-last and
* data-first calling conventions.
*
* **Gotchas**
*
* The prefix is prepended after any `mapInput` transformation runs, so
* ordering matters when composing with {@link mapInput} or
* {@link constantCase}.
*
* **Example** (Nesting under a prefix)
*
* ```ts
* import { ConfigProvider } from "effect"
*
* const provider = ConfigProvider.fromEnv({
* env: { APP_HOST: "localhost", APP_PORT: "3000" }
* })
*
* // Lookups for ["HOST"] now resolve to ["APP", "HOST"]
* const scoped = ConfigProvider.nested(provider, "APP")
* ```
*
* @see {@link mapInput} – for arbitrary path transformations
*
* @category combinators
* @since 2.0.0
*/
export const nested = /*#__PURE__*/dual(2, (self, prefix) => {
const path = typeof prefix === "string" ? [prefix] : prefix;
return make(self.get, self.mapInput, self.prefix ? [...self.prefix, ...path] : path);
});
/**
* Provides a layer that installs a `ConfigProvider` as the active provider for
* all downstream effects, replacing any previously installed provider.
*
* **When to use**
*
* Use to set the config source for an entire application or test suite.
*
* **Details**
*
* Accepts either a plain `ConfigProvider` or an `Effect` that produces one.
* When given an Effect, it is evaluated once when the layer is built.
*
* **Example** (Using a JSON object as the config source)
*
* ```ts
* import { Config, ConfigProvider, Effect, Layer } from "effect"
*
* const TestLayer = ConfigProvider.layer(
* ConfigProvider.fromUnknown({ port: 8080 })
* )
*
* const program = Effect.gen(function*() {
* const port = yield* Config.number("port")
* return port
* })
*
* // Effect.runSync(Effect.provide(program, TestLayer)) // 8080
* ```
*
* @see {@link layerAdd} – add a provider without replacing the existing one
*
* @category layers
* @since 4.0.0
*/
export const layer = self => Effect.isEffect(self) ? Layer.effect(ConfigProvider)(self) : Layer.succeed(ConfigProvider)(self);
/**
* Creates a Layer that composes a new `ConfigProvider` with the currently
* active one, rather than replacing it.
*
* **When to use**
*
* Use to add defaults that should only apply when the primary provider has no
* value for a path, or override specific keys while keeping the rest from the
* existing provider by setting `asPrimary: true`.
*
* **Details**
*
* By default, the new provider acts as a fallback and is consulted only when
* the current provider returns `undefined`. Set `asPrimary: true` to make the
* new provider the primary source, with the existing one as fallback.
*
* **Example** (Adding default values)
*
* ```ts
* import { ConfigProvider } from "effect"
*
* const defaults = ConfigProvider.fromUnknown({
* HOST: "localhost",
* PORT: "3000"
* })
*
* // The current env provider is tried first; `defaults` is the fallback
* const DefaultsLayer = ConfigProvider.layerAdd(defaults)
* ```
*
* @see {@link layer} – replace the provider entirely
* @see {@link orElse} – compose providers without layers
*
* @category layers
* @since 4.0.0
*/
export const layerAdd = (self, options) => Layer.effect(ConfigProvider)(Effect.gen(function* () {
const current = yield* ConfigProvider;
const configProvider = Effect.isEffect(self) ? yield* self : self;
return options?.asPrimary ? orElse(configProvider, current) : orElse(current, configProvider);
}));
/**
* Creates a `ConfigProvider` backed by an in-memory JavaScript value
* (typically a parsed JSON object).
*
* **When to use**
*
* Use when you need deterministic config from an in-memory JavaScript value,
* such as in tests, embedded config, or parsed JSON.
*
* **Details**
*
* Path traversal follows standard JS rules: string segments index into object
* keys, numeric segments index into arrays. Returns `undefined` for any path
* that cannot be resolved. Never fails with `SourceError`.
*
* Primitive values (`number`, `boolean`, `bigint`) are stringified via
* `String(...)`.
*
* **Example** (Providing config from a plain object)
*
* ```ts
* import { Config, ConfigProvider, Effect } from "effect"
*
* const provider = ConfigProvider.fromUnknown({
* database: {
* host: "localhost",
* port: 5432
* }
* })
*
* const host = Config.string("host").parse(
* provider.pipe(ConfigProvider.nested("database"))
* )
*
* // Effect.runSync(host) // "localhost"
* ```
*
* @see {@link fromEnv} – for environment variables
* @see {@link make} – for custom backing stores
*
* @category ConfigProviders
* @since 4.0.0
*/
export function fromUnknown(root) {
return make(path => Effect.succeed(nodeAtJson(root, path)));
}
function nodeAtJson(root, path) {
let cur = root;
for (const seg of path) {
if (cur === null || cur === undefined) return undefined;
if (Array.isArray(cur)) {
if (typeof seg !== "number" || !Number.isInteger(seg) || seg < 0 || seg >= cur.length) return undefined;
cur = cur[seg];
continue;
}
if (Predicate.isObject(cur)) {
if (typeof seg !== "string") return undefined;
if (!Object.hasOwn(cur, seg)) return undefined;
cur = cur[seg];
continue;
}
// cannot descend
return undefined;
}
return describeUnknown(cur);
}
function describeUnknown(u) {
if (u === undefined || u === null) return undefined;
if (typeof u === "string") return makeValue(u);
if (typeof u === "number" || typeof u === "boolean" || typeof u === "bigint") {
return makeValue(String(u));
}
if (Array.isArray(u)) return makeArray(u.length);
if (Predicate.isObject(u)) {
return makeRecord(new Set(Object.keys(u)));
}
// unknown values
return makeValue(format(u));
}
/**
* Creates a `ConfigProvider` backed by environment variables.
*
* **When to use**
*
* Use to read configuration from `process.env`, which is the default when no
* provider is explicitly set, or pass a custom env record for testing or
* non-Node runtimes.
*
* **Details**
*
* Path segments are joined with `_` for direct lookup, and env var names are
* also split on `_` to build a trie for child key discovery. This means
* `DATABASE_HOST=localhost` is accessible at both path `["DATABASE_HOST"]`
* and `["DATABASE", "HOST"]`. If all immediate children of a trie node have
* purely numeric names, the node is reported as an `Array`; otherwise as a
* `Record`.
*
* The default environment merges `process.env` and `import.meta.env` (when
* available). Override by passing `{ env: { ... } }`.
*
* Never fails with `SourceError` — all lookups are synchronous.
*
* **Example** (Reading from a custom env record)
*
* ```ts
* import { Config, ConfigProvider, Effect } from "effect"
*
* const provider = ConfigProvider.fromEnv({
* env: {
* DATABASE_HOST: "localhost",
* DATABASE_PORT: "5432"
* }
* })
*
* const host = Config.string("HOST").parse(
* provider.pipe(ConfigProvider.nested("DATABASE"))
* )
*
* // Effect.runSync(host) // "localhost"
* ```
*
* @see {@link fromUnknown} – for JSON objects
* @see {@link constantCase} – bridge camelCase keys to SCREAMING_SNAKE_CASE
*
* @category ConfigProviders
* @since 2.0.0
*/
export function fromEnv(options) {
const env = options?.env ?? {
...globalThis?.process?.env,
...import.meta?.env
};
const trie = buildEnvTrie(env);
return make(path => Effect.succeed(nodeAtEnv(trie, env, path)));
}
function buildEnvTrie(env) {
const root = {};
for (const [name, value] of Object.entries(env)) {
if (value === undefined) continue;
// Split on "_" and keep empty segments (no special handling for "__")
const segments = name.split("_");
let node = root;
for (const seg of segments) {
node.children ??= {};
node = node.children[seg] ??= {};
}
// co-located value at this node
node.value = value;
}
return root;
}
const NUMERIC_INDEX = /^(0|[1-9][0-9]*)$/;
function nodeAtEnv(trie, env, path) {
const key = path.map(String).join("_");
const leafValue = env[key];
const trieNode = trieNodeAt(trie, path);
const children = trieNode?.children ? Object.keys(trieNode.children) : [];
if (children.length === 0) {
return leafValue === undefined ? undefined : makeValue(leafValue);
}
const allNumeric = children.every(k => NUMERIC_INDEX.test(k));
if (allNumeric) {
const length = Math.max(...children.map(k => parseInt(k, 10))) + 1;
return makeArray(length, leafValue);
}
return makeRecord(new Set(children), leafValue);
}
function trieNodeAt(root, path) {
if (path.length === 0) return root;
// Convert path segments to strings and navigate through the trie
let node = root;
for (const seg of path) {
node = node?.children?.[String(seg)];
if (!node) return undefined;
}
return node;
}
/**
* Creates a `ConfigProvider` by parsing the string contents of a `.env` file.
*
* **When to use**
*
* Use when you already have the `.env` contents as a string, such as contents
* fetched from a remote store or embedded in a test.
*
* **Details**
*
* Supports `export` prefixes, single/double/backtick quoting, inline comments,
* and escaped newlines. Variable expansion (for example, `${VAR}`) is disabled
* by default; enable with `{ expandVariables: true }`.
*
* Parsing is based on the `dotenv` / `dotenv-expand` algorithm.
*
* Internally delegates to {@link fromEnv} with the parsed key-value pairs.
*
* **Example** (Parsing .env contents)
*
* ```ts
* import { ConfigProvider } from "effect"
*
* const contents = `
* HOST=localhost
* PORT=3000
* # this is a comment
* `
*
* const provider = ConfigProvider.fromDotEnvContents(contents)
* ```
*
* @see {@link fromDotEnv} – loads a `.env` file from disk
* @see {@link fromEnv} – for raw environment variable access
*
* @category ConfigProviders
* @since 4.0.0
*/
export function fromDotEnvContents(lines, options) {
let env = parseDotEnvContents(lines);
if (options?.expandVariables) {
env = dotEnvExpand(env);
}
return fromEnv({
env
});
}
const DOT_ENV_LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
function parseDotEnvContents(lines) {
const obj = {};
// Convert line breaks to same format
lines = lines.replace(/\r\n?/gm, "\n");
let match;
while ((match = DOT_ENV_LINE.exec(lines)) != null) {
const key = match[1];
// Default undefined or null to empty string
let value = match[2] || "";
// Remove whitespace
value = value.trim();
// Check if double quoted
const maybeQuote = value[0];
// Remove surrounding quotes
value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2");
// Expand newlines if double quoted
if (maybeQuote === "\"") {
value = value.replace(/\\n/g, "\n");
value = value.replace(/\\r/g, "\r");
}
// Add to object
obj[key] = value;
}
return obj;
}
function dotEnvExpand(parsed) {
const newParsed = {};
for (const configKey in parsed) {
// resolve escape sequences
newParsed[configKey] = interpolate(parsed[configKey], parsed).replace(/\\\$/g, "$");
}
return newParsed;
}
function interpolate(envValue, parsed) {
// find the last unescaped dollar sign in the
// value so that we can evaluate it
const lastUnescapedDollarSignIndex = searchLast(envValue, /(?!(?<=\\))\$/g);
// If we couldn't match any unescaped dollar sign
// let's return the string as is
if (lastUnescapedDollarSignIndex === -1) return envValue;
// This is the right-most group of variables in the string
const rightMostGroup = envValue.slice(lastUnescapedDollarSignIndex);
/**
* This finds the inner most variable/group divided
* by variable name and default value (if present)
* (
* (?!(?<=\\))\$ // only match dollar signs that are not escaped
* {? // optional opening curly brace
* ([\w]+) // match the variable name
* (?::-([^}\\]*))? // match an optional default value
* }? // optional closing curly brace
* )
*/
const matchGroup = /((?!(?<=\\))\${?([\w]+)(?::-([^}\\]*))?}?)/;
const match = rightMostGroup.match(matchGroup);
if (match !== null) {
const [_, group, variableName, defaultValue] = match;
return interpolate(envValue.replace(group, defaultValue || parsed[variableName] || ""), parsed);
}
return envValue;
}
function searchLast(str, rgx) {
const matches = Array.from(str.matchAll(rgx));
return matches.length > 0 ? matches.slice(-1)[0].index : -1;
}
/**
* Creates a `ConfigProvider` by reading and parsing a `.env` file from the
* file system.
*
* **When to use**
*
* Use to load environment config from a `.env` file at application startup.
*
* **Details**
*
* Requires `FileSystem` in the Effect context. Defaults to reading `".env"` in
* the current directory; override with `{ path: "/custom/.env" }`.
*
* Returns an `Effect` that resolves to a `ConfigProvider`. Fails with a
* `PlatformError` if the file cannot be read.
*
* **Example** (Loading a .env file)
*
* ```ts
* import { ConfigProvider, Effect } from "effect"
*
* const program = Effect.gen(function*() {
* const provider = yield* ConfigProvider.fromDotEnv()
* return provider
* })
* ```
*
* @see {@link fromDotEnvContents} – parse a `.env` string directly
* @see {@link fromEnv} – read from the runtime environment
*
* @category constructors
* @since 4.0.0
*/
export const fromDotEnv = /*#__PURE__*/Effect.fnUntraced(function* (options) {
const fs = yield* FileSystem.FileSystem;
const content = yield* fs.readFileString(options?.path ?? ".env");
return fromEnv({
env: parseDotEnvContents(content)
});
});
/**
* Creates a `ConfigProvider` that reads configuration from a directory tree
* on disk, where each file is a leaf value and each directory is a container.
*
* **When to use**
*
* Use when you expose each config key as a file under a directory, such as
* Kubernetes ConfigMap or Secret volume mounts.
*
* **Details**
*
* Resolution tries a regular file first and returns a `Value` node with
* trimmed file contents. If the file read fails, it tries a directory and
* returns a `Record` node with immediate child names as keys. If both fail, it
* returns `SourceError`.
*
* Requires `Path` and `FileSystem` in the Effect context. Defaults to root
* path `/`; override with `{ rootPath: "/etc/config" }`.
*
* **Example** (Reading config from a directory)
*
* ```ts
* import { ConfigProvider, Effect } from "effect"
*
* const program = Effect.gen(function*() {
* const provider = yield* ConfigProvider.fromDir({
* rootPath: "/etc/myapp"
* })
* return provider
* })
* ```
*
* @see {@link fromEnv} – for environment variables
* @see {@link fromDotEnv} – for `.env` files
*
* @category ConfigProviders
* @since 4.0.0
*/
export const fromDir = /*#__PURE__*/Effect.fnUntraced(function* (options) {
const platformPath = yield* Path_.Path;
const fs = yield* FileSystem.FileSystem;
const rootPath = options?.rootPath ?? "/";
return make(path => {
const fullPath = platformPath.join(rootPath, ...path.map(String));
// Try reading as a *file*
const asFile = fs.readFileString(fullPath).pipe(Effect.map(content => makeValue(content.trim())));
// If not a file, try reading as a *directory*
const asDirectory = fs.readDirectory(fullPath).pipe(Effect.map(entries => {
// Support both string paths and DirEntry-like objects
const keys = entries.map(e => typeof e === "string" ? platformPath.basename(e) : format(e?.name ?? ""));
return makeRecord(new Set(keys));
}));
return asFile.pipe(Effect.catch(() => asDirectory), Effect.mapError(cause => new SourceError({
message: `Failed to read file at ${platformPath.join(rootPath, ...path.map(String))}`,
cause
})));
});
});
//# sourceMappingURL=ConfigProvider.js.map

Xet Storage Details

Size:
26.5 kB
·
Xet hash:
becaba194054b0130f0a3af835a10baf12de4b1f1a8ef082b6d86a95ba657273

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.