@ 受影响/ 应用
效果应用控制平面. App.layer 提供应用程序 XDG 名称空格目录、 已迁移的 SQLite 状态数据库、 TTL 缓存以及 –通过 AppConfig.layer ——一个配置文件,都指向了同一个地方,名称空间键入了完全一次. 是一个组成 @effected/xdg, (中文(简体) ), @effected/store 财务报告和审定财务报表 @effected/config-file没有别的.
预
1.0.0。 。 。 . 此软件包是@effected/*在稳定基础上建造的工具包 效应v4(effect~4.1.0) 并仍在0.x发展。 稳定效果 制作一个工具箱1.0.0可能,不是自动的。 为了保持你的effect财务报告和审定财务报表@effect/*该工具包在线上构建和测试、安装@effected/pnpm-plugin-effected。 。 。 .稳定:不稳定. 此软件包的 API 表面尚未考虑 完成并可能改变
0.x释放 定一个确切的版本——甚至 标记软件包 稳定 在此之前1.0.0可以引入突破性更改 和精确的针 变成一个类型检查错误 而不是一个 跑步时间惊喜。 完全政策: 释放策略。 。 。 .
为什么 @ effect/ app
每一个基于这个工具箱的应用程序都写着相同的40行接线,而这种接线是看对看错的. @effected/xdg 解决命名空间目录的位置. @effected/store 在路径上打开数据库。 在它们之间坐着一个没人考虑的命令,直到它被咬:存储层不能创建缺失的父目录,所以必须确保目录 在此之前 或每次第一次运行都无法打开数据库.
另一半是命名空间本身. 一个应用程序为它的目录取名,然后为它的配置文件取名,两个字符串漂移—— "myapp" 在这里, "my-app" 现在它从目录中读取了配置.
这个套件是两者兼而有之的,这就是它的全部。 它拥有 无域逻辑:没有服务,没有计划,没有错误分类,它再出口什么也没有. 整个地表是地层工厂,一个配置预设和一个类型的别名. 如果这里有变化需要 Context.Service,这是信号 变化属于它下的三个包之一.
无法依赖库或软件包 @effected/app ——但该图上方的应用正是其预定的消费者. 使用应用程序控制平面作为依赖的图书馆会将SQLite驱动程序拖入自己的消费者树上. 这是一个应用程序在它的边缘构成的软件包,也是工具包中唯一一个其文件显示OpenTeleometry去向的软件包.
安装
npm install @effected/app effect @effected/xdg @effected/store @effected/config-file
pnpm add @effected/app effect @effected/xdg @effected/store @effected/config-file
对于测试层 @effected/app/testing,添加 @effected/memfs 作为非依赖性,只有输入点才输入:
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 在节点上.
快开始
一个配置文件、一个缓存和一个状态数据库,在一个平台导入上 :
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是因为 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.dbA级 subdir 无法在同一类型上创建失败 AppDirsError 作为目录本身 mkdir,数据库层需要 FileSystem 以及 AppDirs 财务报告和审定财务报表 Path。 。 。 .
每个 filename 使用一个 单一路径组件和每个 subdir 组件遵守相同的规则(没有空组件,没有) . 或者说 ..无线索 /,没有 \) (中文(简体) ). 两者都是线条,所以不好 死亡 而不是输入失败。 A 类 subdir 因此,从运行时间数据——路径上的每个项目密钥——得出的数据必须来自只能产生有效成分的衍生数据,例如散列:
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 带 解析输入的函数,不仅仅是一个完成的层:
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 和层相接一次在根上:
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 直接下令整条链条,只花费你 defaultPath 和环境命名空间线,此预设是免费的.
内容 native 选项默认为 true - 恰恰相反 AppDirsOptions.native不对称是故意的. 创建 本地目录将应用程序输入到一个位置,因此是选入的; 探测 对于一个配置文件,用户已经放在那里的成本为 stat 没有发现,所以是选择退出。 阅读 ~/Library/Application Support 这是礼节,不请自来.
打开前确保
包里唯一要说的. AppStore.layer 产量 AppDirs运行 ensureState,加入路径,然后把它交给 Store.layerSqlite· ; AppCache.layer 做同样的 ensureCache命令使目录在打开前存在 -- AppDirs.ensure* 是一个 mkdir -p 编号 类型 AppDirsError 通道,因此"状态目录无法被创建"的到来是可收回的边界故障而不是死亡.
没什么 orDied) 制作签名打字机。 回归测试将无法写入的祖先钉入打字失败并监视死亡.
App.layer 总是提供 两者 数据库。 一个只想要一个编曲的应用程序 AppStore.layer 或者说 AppCache.layer 直接并永远不打开另一个文件。 诚实的后果:通过否 cache 选项 仍然打开 cache.db,因为每个 CacheOptions 字段是可选的,缺席意味着默认,而不是缺席.
纪念: 将地层绑入凸起
这里的每个导出都是 参数化层厂,而效果会记忆层 参考文献。每次呼叫 App.layer(…) 建立一个新的.
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 仅在使用它的命令上:
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数字 :
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因此,报告路径和打开的文件不能不同意:
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 依赖 。 消费者的首次测试不需要平台导入:
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 | 主目录没有设置( H)$HOME窗口 %USERPROFILE% 接下来 $HOME)——那里唯一的环境失败. |
AppDirsError | @effected/xdg | 无法创建目录 。 检查权限 . |
StoreError | @effected/store | 州数据库自己的SQL失败了——分类账簿记账,或围绕迁移的查询. |
StoreMigrationError | @effected/store | 你的一个移民失败了 卡莉丝 direction, (中文(简体) ), id 财务报告和审定财务报表 name。 。 。 . |
CacheError | @effected/store | 缓存操作的 SQL 失败 。 缓存是缓存——倒回源头通常是正确的. |
线路错误是 缺陷,而不是错误: a filename 不是一个单一的路径组件在地层构造中死亡 namespace 会逃脱的 $HOME它们只能来自代码.
遥测在应用边上
此软件包发送 没有自己的跨度故意的 故意的 胶水中的每一个易落的操作 都已经被拥有它的包所覆盖—— AppDirs.ensure* 按xdg、迁移和每个 Cache 按存储方式排列,每个 ConfigFile 通过配置文件的方法。 这里的跨度是另一个包裹的跨度.
这个工具箱的每一个库都是遥测不可知的,没有导入 @effect/opentelemetry。 。 。 . 申请在顶端有一次:
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在边缘.