# @effected/app 中文文档

规范地址：https://npmcn.com/package/%40effected/app/

版本：0\.33\.0
npm 元数据更新时间：2026-11-12T19:05:40.384Z
中文译文更新时间：2026-11-13T09:05:28.984Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40effected/app
原文对照：https://npmcn.com/package/%40effected/app/?lang=original
项目主页：https://github.com/spencerbeggs/effected/tree/main/packages/app#readme
源码仓库：https://github.com/spencerbeggs/effected

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：109 kB，15 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=24\.11\.0
- 运行依赖：0 个
- peer 依赖：effect ~4\.1\.0，@effected/xdg ^0\.21\.0，@effected/glob ^0\.22\.0，@effected/toml ^0\.20\.0，@effected/yaml ^0\.30\.0，@effected/jsonc ^0\.25\.0，@effected/memfs ^0\.26\.2，@effected/store ^0\.25\.0，@effected/walker ^0\.29\.0，@effected/config-file ^0\.28\.0
- 首次发布：2026-07-16T17:32:32.521Z
- 最新版本发布：2026-11-12T19:05:40.038Z
- 近 30 天下载量：8343（2026-10-13 至 2026-11-11，npm 官方下载统计）
- 已发布版本数：69（近 12 个月 69 个）

## 中文简介

效果应用控制平面:一层接通了 XDG-namespaced 目录,一个迁移了的 SQLite 商店,一个 TTL 缓存和一个配置文件到同一个地方.

## 安装

```sh
npm install @effected/app
```

也可以使用：`pnpm add @effected/app`、`yarn add @effected/app`、`bun add @effected/app`

## README

# @ 受影响/ 应用

[![npm](https://img.shields.io/npm/v/@effected%2Fapp?label=npm&color=cb3837)](https://www.npmjs.com/package/@effected/app) [![License: MIT](https://img.shields.io/badge/License-MIT-4caf50.svg)](https://opensource.org/licenses/MIT) [![Node.js %3E%3D24.11.0](https://img.shields.io/badge/Node.js-%3E%3D24.11.0-5fa04e.svg)](https://nodejs.org/) [![TypeScript 7.0](https://img.shields.io/badge/TypeScript-7.0-3178c6.svg)](https://www.typescriptlang.org/)

效果应用控制平面. `App.layer` 提供应用程序 XDG 名称空格目录、 已迁移的 SQLite 状态数据库、 TTL 缓存以及 –通过 `AppConfig.layer` ——一个配置文件,都指向了同一个地方,名称空间键入了完全一次. 是一个组成 [`@effected/xdg`](https://npmcn.com/package/%40effected/xdg), (中文(简体) ), [`@effected/store`](https://npmcn.com/package/%40effected/store) 财务报告和审定财务报表 [`@effected/config-file`](https://npmcn.com/package/%40effected/config-file)没有别的.

> **预`1.0.0`。 。 。 .** 此软件包是 `@effected/*` 在稳定基础上建造的工具包 效应v4(`effect` `~4.1.0`) 并仍在 `0.x` 发展。 稳定效果 制作一个工具箱 `1.0.0` 可能,不是自动的。 为了保持你的 `effect` 财务报告和审定财务报表 `@effect/*` 该工具包在线上构建和测试、安装 [`@effected/pnpm-plugin-effected`](https://www.npmjs.com/package/@effected/pnpm-plugin-effected)。 。 。 .
> 
> **稳定:不稳定.** 此软件包的 API 表面尚未考虑 完成并可能改变 `0.x` 释放 定一个确切的版本——甚至 标记软件包 _稳定_ 在此之前 `1.0.0` 可以引入突破性更改 和精确的针 变成一个类型检查错误 而不是一个 跑步时间惊喜。 完全政策: [释放策略](https://github.com/spencerbeggs/effected#release-strategy)。 。 。 .

## 为什么 @ effect/ app

每一个基于这个工具箱的应用程序都写着相同的40行接线,而这种接线是看对看错的. `@effected/xdg` 解决命名空间目录的位置. `@effected/store` 在路径上打开数据库。 在它们之间坐着一个没人考虑的命令,直到它被咬:存储层不能创建缺失的父目录,所以必须确保目录 _在此之前_ 或每次第一次运行都无法打开数据库.

另一半是命名空间本身. 一个应用程序为它的目录取名,然后为它的配置文件取名,两个字符串漂移—— `"myapp"` 在这里, `"my-app"` 现在它从目录中读取了配置.

这个套件是两者兼而有之的,这就是它的全部。 它拥有 **无域逻辑**:没有服务,没有计划,没有错误分类,它再出口什么也没有. 整个地表是地层工厂,一个配置预设和一个类型的别名. 如果这里有变化需要 `Context.Service`,这是信号 变化属于它下的三个包之一.

**无法依赖库或软件包 `@effected/app`** ——但该图上方的应用正是其预定的消费者. 使用应用程序控制平面作为依赖的图书馆会将SQLite驱动程序拖入自己的消费者树上. 这是一个应用程序在它的边缘构成的软件包,也是工具包中唯一一个其文件显示OpenTeleometry去向的软件包.

## 安装

```bash
npm install @effected/app effect @effected/xdg @effected/store @effected/config-file
```

```bash
pnpm add @effected/app effect @effected/xdg @effected/store @effected/config-file
```

对于测试层 `@effected/app/testing`,添加 `@effected/memfs` 作为非依赖性,只有输入点才输入:

```bash
pnpm add -D @effected/memfs
```

需要节点.js~~24.11.0.

全体 `@effected/*` 软件包仅为无害环境管理:只公布出口地图 `import` 条件,所以 `require()` 包括解析 CJS 模式的工具 —— 与节点失败 `ERR_PACKAGE_PATH_NOT_EXPORTED` 而不是装入不存在的 CJS 建筑。 从ES模块导入.

还有 **无运行时间依赖关系**。 。 。 . `effect` v4 是一个对等依赖, 也一样 `@effected/xdg`, (中文(简体) ), `@effected/store` 财务报告和审定财务报表 `@effected/config-file` ——是负载而不是偶然的. 3个都出现在这个包的公开签名类型中,所以在您图表中任何一个的第二个拷贝都会为一个概念铸出两个不同的服务标记而层会默默地无法满足要求. 单一副本才是重点,而这正是同龄人宣布的内容。 自动安装对等器的软件包管理器会将其拉入; 如果不安装, 将明确添加到您的清单中 .

软件包是集成层 **继承,而不是做任何事情**数字 : `@effected/store` 达到 `@effect/sql-sqlite-node`并传播。 它没有执行IO,它下面的三个包还没有执行.

创建目录需要 `FileSystem` (单位:千美元) `Path` 执行,一旦处于边缘即提供 `@effect/platform-node` 在节点上.

## 快开始

一个配置文件、一个缓存和一个状态数据库,在一个平台导入上 :

```ts
import { App, AppConfig } from "@effected/app";
import { ConfigFile, JsonCodec } from "@effected/config-file";
import { Cache, Store } from "@effected/store";
import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
import * as NodeServices from "@effect/platform-node/NodeServices";
import { Effect, Layer, Schema } from "effect";

class Settings extends Schema.Class<Settings>("Settings")({
  registry: Schema.String,
  concurrency: Schema.Number,
}) {}
class SettingsFile extends ConfigFile.Service<SettingsFile, Settings>()("myapp/Settings") {}

const migrations = [
  { id: 1, name: "runs", up: (sql) => sql`CREATE TABLE runs (id TEXT PRIMARY KEY, at TEXT)` },
];

// Bound once, to a const — see Memoization below. This is the whole control plane:
// XDG dirs for "myapp", store.db in the state dir, cache.db in the cache dir.
const AppLive = App.layer({ namespace: "myapp", store: { migrations }, cache: { maxEntries: 500 } });

const ConfigLive = AppConfig.layer(SettingsFile, {
  filename: "config.json", // no namespace: it comes from AppLive's AppDirs
  schema: Settings,
  codec: JsonCodec,
});

const MainLive = ConfigLive.pipe(
  Layer.provideMerge(AppLive),
  Layer.provide(NodeServices.layer), // the one place a platform is named
);

const main = Effect.gen(function* () {
  const settings = yield* (yield* SettingsFile).load;
  const store = yield* Store;
  const cache = yield* Cache;

  yield* store.client`INSERT INTO runs (id, at) VALUES (${crypto.randomUUID()}, datetime())`;
  yield* cache.set({ key: "last-registry", value: new TextEncoder().encode(settings.registry) });
});

NodeRuntime.runMain(main.pipe(Effect.provide(MainLive)));
```

4个服务,1个平台导入,1个名称空间一次打入,以及每一次打入的频道失败. 这就是整个包裹.

## 文件落地

`App.layer` 确保每个目录在打开其中的任何内容之前。 与 `namespace: "myapp"` 默认 `native: false`,XDG规则适用于每个平台:

| 什麽 | 目录 | 默认文件 |
| --- | --- | --- |
| 国家数据库 | `$XDG_STATE_HOME/myapp` —— 说 `~/.local/state/myapp` | `store.db` |
| 缓存数据库 | `$XDG_CACHE_HOME/myapp` —— 说 `~/.cache/myapp` | `cache.db` |
| 配置文件 | `$XDG_CONFIG_HOME/myapp` —— 说 `~/.config/myapp` |  `filename` |

`AppOptions` 实值 [`@effected/xdg`](https://npmcn.com/package/%40effected/xdg)是因为 `AppDirsOptions` 直接穿过.. `namespace`, (中文(简体) ), `native`, (中文(简体) ), `fallbackDir` 财务报告和审定财务报表 `dirs` 意思是说,他们在这里的意思是, 包括了五重优先阶梯, 而这个软件包 重新记录了所有的东西.

`AppStore` 财务报告和审定财务报表 `AppCache` 带一个 `directory` (单位:千美元)`"state"`, (中文(简体) ), `"data"` 或者说 `"cache"`; 默认如上)和一个可选的相对关系 `subdir` 在它下面,创造与 `mkdir -p` 在数据库打开之前 `{ directory: "data", subdir: "projects/abc123", filename: "data.db" }` 陆地在 `~/.local/share/myapp/projects/abc123/data.db`A级 `subdir` 无法在同一类型上创建失败 `AppDirsError` 作为目录本身 `mkdir`,数据库层需要 `FileSystem` 以及 `AppDirs` 财务报告和审定财务报表 `Path`。 。 。 .

每个 `filename` 使用一个 **单一路径组件**和每个 `subdir` 组件遵守相同的规则(没有空组件,没有) `.` 或者说 `..`无线索 `/`,没有 `\`) (中文(简体) ). 两者都是线条,所以不好 **死亡** 而不是输入失败。 A 类 `subdir` 因此,从运行时间数据——路径上的每个项目密钥——得出的数据必须来自只能产生有效成分的衍生数据,例如散列:

````ts
import { createHash } from "node:crypto";

const subdir = createHash("sha256").update(projectRoot).digest("hex").slice(0, 16);
``` An empty name, one containing a separator, or `.` / `..` would escape the namespace directory, so it dies at layer construction: it can only come from code, never from user input.

## The namespace is typed once

`AppConfig.layer` takes no namespace. It reads one from the ambient `AppDirs` service at layer build time, so the namespace is named exactly once — in `App.layer` — and the two-strings drift cannot happen. Anything that can be derived is not asked for.

What it does take is a **codec, required**, never inferred from the filename's extension. Inferring one would hard-code a *format* choice into a composition layer, which is not this package's decision to make; and the named import is also what keeps the other three parsing engines out of your bundle.

`AppConfig` lives in its own module and reaches `@effected/xdg` and `@effected/config-file` **only** — never `@effected/store`. An application that wants XDG-placed config files and no database imports `AppConfig` alone, and no SQLite driver enters its graph. `App`, `AppStore` and `AppCache` are the exports that reach a database, and keeping the two graphs apart is why there is no `App = { … }` namespace object here.

## A `--config` flag, without leaving the preset

Most CLIs have one, and it has to outrank the app's own search path. Pass `resolvers` and it does:

```ts
import { AppConfig } from "@effected/app";
import { ConfigResolver } from "@effected/config-file";
import { TomlCodec } from "@effected/config-file/toml";

const ConfigLive = AppConfig.layer(SettingsFile, {
 filename: "settings.toml",
 schema: Settings,
 codec: TomlCodec,
 // Composed AHEAD of the XDG chain, in the order given.
 resolvers: flag === undefined ? [] : [ConfigResolver.explicitPath(flag)],
});
````

XDG搜索路径和原生探测器 留在任何你预花的后方, 所以没有旗帜,没有什么变化. `ConfigResolver.staticDir` 涵盖a `--config` 命名目录,以及 `ConfigResolver.upwardWalk` 一个从cwd上方找到的工程本地文件.

两个属性要考虑. **一个无所事事的解决者**: 每一个 `ConfigResolver`'错误频道是 `never` 契约,所以 `--config` 指向一个不存在的文件会悄悄地加载 XDG 配置。 如果这一定是个错误,那么在建立地层之前请检查路径——发现不能为您作出这种区分. 还有 **保存路径不受影响**数字 : `save` 仍然写到应用程序自己的配置目录, 所以写回文件的旗子是 `write(value, path)`,它明确地走路径.

### 将旗子接入地层

在 CLI 分析任何内容之前, 一层被构建, 所以解析 `--config` 必须达到 `AppConfig.layer` 不知为何. `effect/cli` 有两个方法, 也没有需要 `Effect.provide` 在操作器内部.

对于一个命令, `Command.provide` 带 **解析输入的函数**,不仅仅是一个完成的层:

```ts
Command.make("validate", { config: Flag.string("config").pipe(Flag.optional) }, () =>
 Effect.gen(function* () {
  const settings = yield* (yield* SettingsFile).load; // just requires the service
 }),
).pipe(Command.provide(({ config }) => makeConfigLive(Option.getOrUndefined(config))));
```

对于共享一个子命令的多个子命令 `--config`,使旗帜成为 **全球环境** —— 说 `GlobalFlag.setting` 返回 a `Context.Service`,所以解析值可以坐落在层的 `R` 和层相接一次在根上:

```ts
const ConfigFlag = GlobalFlag.setting("config")({ flag: Flag.string("config").pipe(Flag.optional) });

const ConfigLive = Layer.unwrap(Effect.map(ConfigFlag, (f) => makeConfigLive(Option.getOrUndefined(f))));

const root = Command.make("myapp", {}, () => Effect.void).pipe(
 Command.withSubcommands([validate, sync]),
 Command.provide(ConfigLive), // one call site, every subcommand
 Command.withGlobalFlags([ConfigFlag]),
);
```

然后在子命令名的两侧接受旗帜.

一个需要XDG解析器的链条,除了最后某个地方——或者根本不是——已经超越了预设. 编曲 `ConfigFile.layer` 从 [`@effected/config-file`](https://npmcn.com/package/%40effected/config-file) 直接下令整条链条,只花费你 `defaultPath` 和环境命名空间线,此预设是免费的.

内容 `native` 选项默认为 **`true`** - 恰恰相反 `AppDirsOptions.native`不对称是故意的. _创建_ 本地目录将应用程序输入到一个位置,因此是选入的; _探测_ 对于一个配置文件,用户已经放在那里的成本为 `stat` 没有发现,所以是选择退出。 阅读 `~/Library/Application Support` 这是礼节,不请自来.

## 打开前确保

包里唯一要说的. `AppStore.layer` 产量 `AppDirs`运行 `ensureState`,加入路径,然后把它交给 `Store.layerSqlite`· ; `AppCache.layer` 做同样的 `ensureCache`命令使目录在打开前存在 -- `AppDirs.ensure*` 是一个 `mkdir -p` 编号 **类型** `AppDirsError` 通道,因此"状态目录无法被创建"的到来是可收回的边界故障而不是死亡.

没什么 `orDie`d) 制作签名打字机。 回归测试将无法写入的祖先钉入打字失败并监视死亡.

`App.layer` 总是提供 **两者** 数据库。 一个只想要一个编曲的应用程序 `AppStore.layer` 或者说 `AppCache.layer` 直接并永远不打开另一个文件。 诚实的后果:通过否 `cache` 选项 **仍然打开 `cache.db`**,因为每个 `CacheOptions` 字段是可选的,缺席意味着默认,而不是缺席.

## 纪念: 将地层绑入凸起

这里的每个导出都是 **参数化层厂**,而效果会记忆层 **参考文献**。每次呼叫 `App.layer(…)` 建立一个新的.

```ts
const AppLive = App.layer({ namespace: "myapp", store: { migrations } }); // once
```

打给工厂内线 两个提供地点,你打开 **两个数据库**:将两个连接到一个文件、两个迁移分类账和两个独立的 `CacheEvent` 用户的PubSub分别看到一半的活动. 这就是一个应用程序最有可能两次构成同一层的软件包,这就是为什么规则在这里,而不是在脚注中.

## 每个命令的数据库:所有目录

大楼 `App.layer` **打开和迁移两个数据库**渴望着.. `store.db` 财务报告和审定财务报表 `cache.db` 无论程序是否触碰它们,层的构建时刻都存在。 长期服务就是你想要的 国家综合指数不是:提供 `App.layer` 在入口处,每个命令都会创建和迁移两个文件, `--help`\-相邻路径包含,删除应用程序文件的命令最终会删除它自己的进程所持有的数据库.

`App.layerDirs` 是目录中的一半 `Xdg` 财务报告和审定财务报表 `AppDirs` 一个名字空间, `XdgEnvError` 在错误频道上, `FileSystem` 财务报告和审定财务报表 `Path` 内 `R`,则没有打开数据库。 在边缘提供一次,在模块范围将每个数据库层绑定一次,并附在 `Command.provide` 仅在使用它的命令上:

```ts
import { App, AppStore } from "@effected/app";
import { CliRuntime } from "@effected/cli-kit";
import { Store } from "@effected/store";
import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
import * as NodeServices from "@effect/platform-node/NodeServices";
import { Effect, Layer } from "effect";
import { Command } from "effect/cli";

// Module scope, bound once: built once per provided layer graph.
const StoreLive = AppStore.layer({ migrations });

// Opens store.db — only when `history` runs.
const history = Command.make("history", {}, () =>
  Effect.gen(function* () {
    const store = yield* Store;
    yield* store.client`SELECT id FROM runs`;
  }),
).pipe(Command.provide(StoreLive));

// Opens no database at all.
const where = Command.make("where", {}, () => Effect.void);

const cli = Command.make("myapp").pipe(Command.withSubcommands([history, where]));

// Directories at the edge, once; AppDirs satisfies StoreLive's requirement.
const PlatformLive = App.layerDirs({ namespace: "myapp" }).pipe(Layer.provideMerge(NodeServices.layer));

NodeRuntime.runMain(CliRuntime.main(Command.run(cli, { version: "1.0.0" }), { platform: PlatformLive }));
```

模块范围绑定, 如 `StoreLive` 留下来 **一个** 然而,许多复合材料在其中重新使用,提供了图层 `Layer.mergeAll(StoreLive, …)` 旁边 `Repo.pipe(Layer.provide(StoreLive))` - 因为一个图层 以参考方式回忆,和 `Effect.provide` 巢穴在另一个再利用中 包含一个已经构建。 提供非巢-相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相相接相相接相相相接相接相相相相相相相相接相接相接相相接相相相接相相相相相相相相相相相相相相相相相相相相相.

`Command.provide` 也接受命令解析输入的函数, `(input) => Layer`,用于一个取决于国旗选项的数据库。 捆绑任何不依赖于该函数外部输入的东西——它内部所建的一层是每个呼叫上的新参考.

## 多个商店

`AppStore.layer` 财务报告和审定财务报表 `AppCache.layer` 提供工具包的单曲 `Store` 财务报告和审定财务报表 `Cache` 服务。 一个保留了第二个数据库的应用程序——一个位于运行历史旁的登记册,一个单独的大对象缓存——在相同的形状上声明自己的服务密钥,并用它构建 `layerAs`数字 :

```ts
import { AppCache, AppStore } from "@effected/app";
import type { CacheShape, StoreShape } from "@effected/store";
import { Context, Layer } from "effect";

class RegistryStore extends Context.Service<RegistryStore, StoreShape>()("myapp/RegistryStore") {}
class TarballCache extends Context.Service<TarballCache, CacheShape>()("myapp/TarballCache") {}

// Every one bound once, to a const.
const StoreLive = AppStore.layer({ migrations });
const RegistryStoreLive = AppStore.layerAs(RegistryStore, { filename: "registry.db", migrations: registryMigrations });
const TarballCacheLive = AppCache.layerAs(TarballCache, { filename: "tarballs.db", maxEntries: 200 });

const DatabasesLive = Layer.mergeAll(StoreLive, RegistryStoreLive, TarballCacheLive);
```

每个按键层运行的保证- 打开前胶水, 与其主目录相同—— 存储状态, 缓存状态, 除非 `directory` 编号 : `subdir` 和产出 **只有它自己的钥匙**页:1 `Store` 或者说 `Cache` 它在内部建立 永不漏出, 所以它作曲 在初级,没有阴影。 每个文件都有自己的迁移和自己的分类账.

-   **`filename` 需要时间.** 默认 `store.db` 或者说 `cache.db` 将一个密钥层悄悄地放入原始文件——两个连接和一个数据库的两个迁移分类账.
-   **密钥的服务类型必须是 `StoreShape` 或者说 `CacheShape`。 。 。 .** 一个不相容的形状是一个编译错误,一个添加成员的形状也一样(例如:`StoreShape & { … }`),作为不能转让给 `never` 因为这个层无法提供它们。 检查无法通过方法语法参数bivariance来查看:一个成员重新声明是一种方法,其参数更宽仍会编译.
-   **将每个密钥层绑入一个 `const`。 。 。 .** 每个 `layerAs(…)` 调用是一个新层,上面的记忆陷阱分别适用于每个层:两个内线调用相同的密钥打开文件两次.

密钥层需要 `AppDirs`, (中文(简体) ), `Path` 财务报告和审定财务报表 `FileSystem`,这正是什么 `App.layerDirs` 加上平台提供的——它们插入上方的每个命令形状不变.

每个 `AppStore` 编号 : `AppCache` 选项超出路径 `@effected/store`'',直接通过: `client` (诸如: `busyTimeout` 财务报告和审定财务报表 `disableWAL` ——每个连接设置的地方,从不迁移),, `checkpointOnClose`, (中文(简体) ), `onConnect` (每条连接钩在分类账前运行,在任何交易之外),在商店上运行 `adoptMigratorLedger` 财务报告和审定财务报表 `mirrorMigratorLedger`,它移动一个以前通过效果/ sql 迁移的数据库 `Migrator` 打开 `Store` 不重运行其历史,并保持旧版本的分类账流(见 `@effected/store`读取规则。 绝对或主机选择路径的数据库不是一个应用程序关切: 使用 `Store.layerSqliteAs` 编号 : `Cache.layerSqliteAs` 从 `@effected/store` 直接说.

### 档案在哪里?

报告或坚持数据库路径的程序(a) `doctor` 命令,“你的数据存在”一行——从中获取 `AppStore.location(options)` 编号 : `AppCache.location(options)`,通过同样的选项,它通过层。 它解决了环境的绝对路径 `AppDirs` 不创建任何东西( e)`R` 实值 `AppDirs | Path`因此,报告路径和打开的文件不能不同意:

```ts
const options = { filename: "registry.db", directory: "data", migrations } as const;

const RegistryStoreLive = AppStore.layerAs(RegistryStore, options);
const where = AppStore.location(options); // Effect<string, never, AppDirs | Path>
```

## 测试:一行,没有平台包

`AppTest.layer`从 `@effected/app/testing` 入口,是密封控制平面——固定的XDG路径, `:memory:` 数据库,以及来自 `@effected/memfs`,所有由层本身提供。 它住在自己的入口上,所以一个应用程序的运行时间导入图从不加载测试代码; `@effected/memfs` 是一个只需要此切入点的可选的对等点, 因此将其安装为 dev 依赖 。 消费者的首次测试不需要平台导入:

```ts
import { AppTest } from "@effected/app/testing";
import { layer } from "@effect/vitest";
import { Effect } from "effect";

layer(AppTest.layer({ namespace: "myapp" }))("app", (it) => {
  it.effect("stores state", () =>
    Effect.gen(function* () {
      // Store and Cache are here, in memory, hermetic.
    }));
});
```

文件系统是真实的,只是内存: `AppDirs.ensure*` 在音量上创建目录, `files` 种子它(一个配置体在下面) `/home/test/.config/<namespace>/` 是什么样的 `AppConfig.layer` 由试验层的负载和 `MemoryFileSystem.Volume` 服务读取程序所写的. `FileSystem` 财务报告和审定财务报表 `Path` 从四道服务旁边的地层出来,所以 `AppConfig.layer` 以单曲作曲 `Layer.provide` 在同一个音量。 数据库是例外——它们是 `:memory:` 在此打开,通过本地 SQLite 绑定在 `App.layer`,所以不要作曲 `AppStore.layer` 或者说 `AppCache.layer` 在试验层之上——在合成默认情况下,绑定物未能打开一个在主机上不存在的路径(a) `StoreError`),与真实的 `paths` 它会写入主机磁盘; 端到端测试 `App.layer` 运行在临时指令 `HOME`,这就是这个包 自己的整合套件.

## 错误

`AppError` 是一个 **只类型** 其他名称——可复制的联盟 `catchTags` 选项。 它没有定义: 里面的每一个标记都是由拥有操作的软件包下面的软件包提起和记录的,每个软件都通过未被包装的软件流出. A 类 `StoreMigrationError` 到达您的操作员 仍然携带迁移的 `id`, (中文(简体) ), `name` 财务报告和审定财务报表 `direction`。 。 。 .

| 标记 | 升为 | 手段 |
| --- | --- | --- |
| `XdgEnvError` | [`@effected/xdg`](https://npmcn.com/package/%40effected/xdg) | 主目录没有设置( H)`$HOME`窗口 `%USERPROFILE%` 接下来 `$HOME`)——那里唯一的环境失败. |
| `AppDirsError` | [`@effected/xdg`](https://npmcn.com/package/%40effected/xdg) | 无法创建目录 。 检查权限 . |
| `StoreError` | [`@effected/store`](https://npmcn.com/package/%40effected/store) | 州数据库自己的SQL失败了——分类账簿记账,或围绕迁移的查询. |
| `StoreMigrationError` | [`@effected/store`](https://npmcn.com/package/%40effected/store) | 你的一个移民失败了 卡莉丝 `direction`, (中文(简体) ), `id` 财务报告和审定财务报表 `name`。 。 。 . |
| `CacheError` | [`@effected/store`](https://npmcn.com/package/%40effected/store) | 缓存操作的 SQL 失败 。 缓存是缓存——倒回源头通常是正确的. |

线路错误是 **缺陷**,而不是错误: a `filename` 不是一个单一的路径组件在地层构造中死亡 `namespace` 会逃脱的 `$HOME`它们只能来自代码.

## 遥测在应用边上

此软件包发送 **没有自己的跨度**故意的 故意的 胶水中的每一个易落的操作 都已经被拥有它的包所覆盖—— `AppDirs.ensure*` 按xdg、迁移和每个 `Cache` 按存储方式排列,每个 `ConfigFile` 通过配置文件的方法。 这里的跨度是另一个包裹的跨度.

这个工具箱的每一个库都是遥测不可知的,没有导入 `@effect/opentelemetry`。 。 。 . **申请在顶端有一次:**

```ts
import { NodeSdk } from "@effect/opentelemetry";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";

const TelemetryLive = NodeSdk.layer(() => ({
  resource: { serviceName: "myapp" },
  spanProcessor: new BatchSpanProcessor(new OTLPTraceExporter()),
}));

const MainLive = ConfigLive.pipe(
  Layer.provideMerge(AppLive),
  Layer.provide(NodeServices.layer),
  Layer.provide(TelemetryLive), // composed once, beneath everything
);
```

在堆栈下方提供它,每个包的包都已经发出——目录创建,迁移,缓存读取,配置负载——到达您的收藏器. 库中没有任何变化,因为为您选择输出者的库会做出一个应用程序的决定.

## 特征

-   `App.layer` 控制飞机: `Xdg`, (中文(简体) ), `AppDirs`, (中文(简体) ), `Store` 财务报告和审定财务报表 `Cache` 从一个电话,只有 `FileSystem` 财务报告和审定财务报表 `Path` 留给平台层提供.
-   `App.layerDirs` —— 说 `Xdg` 财务报告和审定财务报表 `AppDirs` 单独打开,不打开数据库:目录随处可见,数据库只在使用时附加.
-   `AppTest.layer` (从) `@effected/app/testing`)——同四种服务外加内幕 `FileSystem`, (中文(简体) ), `Path` 财务报告和审定财务报表 `MemoryFileSystem.Volume` 合成XDG路径, `:memory:` 数据库,一个可播种的 memfs 音量,不需要平台软件包.
-   `AppStore.layer` 编号 : `AppCache.layer` ——状态-目录和缓存-目录数据库自行建立,每个数据库在打开文件前保证其目录.
-   `AppStore.layerAs` 编号 : `AppCache.layerAs` ——在您定义的服务密钥下的额外数据库,有它自己的文件,迁移和分类账.
-   `AppConfig.layer` —— 说 `@effected/config-file` 连接到 xdg 的解析器链并保存路径, 从环境中读取命名空间 `AppDirs` 和你命名的密码.
-   `AppError` 控制平面可能失败的每样东西 都只有类型结合 `catchTags` 在边缘.

## 许可证

[麻省理工学院](https://npmcn.com/package/%40effected/app/LICENSE)

## 常见问题

### @effected/app 是什么？

效果应用控制平面:一层接通了 XDG-namespaced 目录,一个迁移了的 SQLite 商店,一个 TTL 缓存和一个配置文件到同一个地方\.

### 如何安装 @effected/app？

在项目目录运行 npm install @effected/app，或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖：effect（~4\.1\.0）、@effected/xdg（^0\.21\.0）、@effected/glob（^0\.22\.0）、@effected/store（^0\.25\.0）、@effected/walker（^0\.29\.0）、@effected/config-file（^0\.28\.0）。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖，Yarn 需要手动安装。

### @effected/app 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @effected/app 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @effected/app 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=24\.11\.0。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @effected/app 可以商用吗？

@effected/app 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @effected/app 的下载量有多少？

根据 npm 官方下载统计，@effected/app 在 2026/10/13 – 2026/11/11 这 30 天内被下载 8,343 次，日均约 278 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @effected/app 还在维护吗？

最新版本 0\.33\.0 发布于 2026/11/13（今天）；至今共发布 69 个版本，其中过去 12 个月发布了 69 个；首次发布于 2026/07/17。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

