@ action/ config- 文件
可编译配置文件加载效果 。 宣告一个解锁链——一条明确的路径,从cwd向上行走,工作空间或基特根, /etc ——通过效果解码每个已发现的文件 Schema,并将结果与合并策略相结合。 JSON解码出盒;JSONC,YAML和TOML各取一个可选的引擎包. 编解码器,解析器和合并策略都是可插接的接合器,而故障的来由是带有结构化有效载荷而不是活口的被标记的错误,因此"在任何地方没有配置"与"我发现的配置被打破"是分道扬镳.
预
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/ config- file
配置加载是一类应用程序通常放弃的地方:库中找到文件,解析文件,验证文件,并报告其中每一个明显的失败,如一个不透明错误 reason 绳子。 这个包裹拒绝。 发现、阅读、解析、验证和持久性都因自己的有标记的错误而失败,而且每个错误都带有结构性原因——a ConfigValidationError 将计程树交给你,不是 String(ParseError)。解析器要求流入层的类别,而不是被丢弃,合并步骤报告每个贡献源,而不仅仅是第一个源,并且可以将装入的文件交给 Config 作为 v4 的存取器 ConfigProvider 在环境之下层出不穷.
安装
npm install @effected/config-file effect @effect/platform-node
pnpm add @effected/config-file effect @effect/platform-node
需要节点.js~~24.11.0. 那个安装覆盖了JSON。 要读取 JSONC, YAML 或 TOML, 添加匹配的引擎 :
pnpm add @effected/jsonc # for @effected/config-file/jsonc
pnpm add @effected/yaml # for @effected/config-file/yaml
pnpm add @effected/toml # for @effected/config-file/toml
全体 @effected/* 软件包仅为无害环境管理:只公布出口地图 import 条件,所以 require() 包括解析 CJS 模式的工具 —— 与节点失败 ERR_PACKAGE_PATH_NOT_EXPORTED 而不是装入不存在的 CJS 建筑。 从ES模块导入.
effect v4 是一个对等依赖, 也一样 @effected/walker 财务报告和审定财务报表 @effected/glob - 转回原始的 upwardWalk, (中文(简体) ), workspaceRoot 财务报告和审定财务报表 gitRoot 解析器是建立在, 匹配它同行。 自动安装对等器的软件包管理器会将其拉入; 如果不安装, 将明确添加到您的清单中 . @effected/jsonc, (中文(简体) ), @effected/yaml 财务报告和审定财务报表 @effected/toml 为: 选项 对等者: ./jsonc, (中文(简体) ), ./yaml 财务报告和审定财务报表 ./toml 子路径加载它们,而包根从不这样做,所以一个JSON唯一的程序安装并加载其中任何一个. 它的一次性依赖是 @effected/files,它背面的原子写, 所以没有它拖入你的树 从外部 effect 财务报告和审定财务报表 @effected/*。 。 。 .
读取和写入文件需要 FileSystem (单位:千美元) Path 执行,一旦处于边缘即提供 @effect/platform-node 在节点上.
快开始
宣布一个计划,薄荷 服务类与之 ConfigFile.Service,并用 ConfigFile.layer。平台层一次提供,在边缘:
import { ConfigFile, ConfigResolver, JsonCodec, MergeStrategy } from "@effected/config-file";
import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem";
import * as NodePath from "@effect/platform-node/NodePath";
import { Effect, Layer, Schema } from "effect";
class AppShape extends Schema.Class<AppShape>("AppShape")({
port: Schema.Number,
host: Schema.String,
}) {}
class AppConfig extends ConfigFile.Service<AppConfig, AppShape>()("app/Config") {}
const AppConfigLive = ConfigFile.layer(AppConfig, {
schema: AppShape,
codec: JsonCodec,
resolvers: [ConfigResolver.upwardWalk({ filename: ".apprc" })],
strategy: MergeStrategy.firstMatch<AppShape>(),
});
const program = Effect.gen(function* () {
const config = yield* AppConfig;
return yield* config.load;
});
const PlatformLive = Layer.mergeAll(NodeFileSystem.layer, NodePath.layer);
Effect.runPromise(program.pipe(Effect.provide(AppConfigLive), Effect.provide(PlatformLive))).then(console.log);
// AppShape { port: 3000, host: "localhost" }
ConfigFile.layer 是图层返回 函数,而非一层:称其为"两次"会构建出两个独立的服务实例. 如上所述,将其结果与康斯特结合,并提供康斯特.
解决者按优先顺序协商,最高者优先. MergeStrategy.firstMatch 夺得冠军; MergeStrategy.layeredMerge 深相匹配的每个源, 更高优先级的密钥覆盖低等密钥 :
import { ConfigFile, ConfigResolver, MergeStrategy } from "@effected/config-file";
import { YamlCodec } from "@effected/config-file/yaml";
import { Schema } from "effect";
class Settings extends Schema.Class<Settings>("Settings")({ port: Schema.Number }) {}
class SettingsConfig extends ConfigFile.Service<SettingsConfig, Settings>()("app/Settings") {}
export const SettingsLive = ConfigFile.layer(SettingsConfig, {
schema: Settings,
codec: YamlCodec,
resolvers: [
ConfigResolver.upwardWalk({ filename: ".apprc.yaml" }),
ConfigResolver.workspaceRoot({ filename: ".apprc.yaml" }),
ConfigResolver.systemEtc({ app: "myapp", filename: "config.yaml" }),
],
strategy: MergeStrategy.layeredMerge<Settings>(),
});
读取已知路径
并不是每个呼叫者都有一个配置文件——有些人只有一条已经保证过的呼叫者路径,比如CLI's --config 旗帜. ConfigFile.read 是一个从服务、层和解析器链中取出的一发子弹:读取、解码和验证一个单一路径,每个调用时命名的 schema 和 codec 而不是绑定在一个服务类中:
import { ConfigFile, JsonCodec } from "@effected/config-file";
import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem";
import { Effect, Schema } from "effect";
class MyConfig extends Schema.Class<MyConfig>("MyConfig")({ port: Schema.Number }) {}
const program = ConfigFile.read("./app.config.json", { schema: MyConfig, codec: JsonCodec });
Effect.runPromise(program.pipe(Effect.provide(NodeFileSystem.layer))).then(console.log);
// MyConfig { port: 3000 }
它刻意只读和无发现——没有解析链,没有 save编号 :update联系 ConfigFile.layer 任何时刻都是被通缉的.
拒绝密钥, 计划不知道
Effect的解码器默认忽略了未知的密钥,对于一个配置加载器来说,这意味着一个打字"d"区段被静默地放下. 用户不会出错,他们写的设定没有效果,运行中没有任何内容说明原因. parseOptions 线程解码选项进入装入器执行的每个解码器 ConfigFile.layer 财务报告和审定财务报表 ConfigFile.read 类似:
import { ConfigFile, ConfigResolver, JsonCodec, MergeStrategy } from "@effected/config-file";
import { Schema } from "effect";
class Settings extends Schema.Class<Settings>("Settings")({ port: Schema.Number }) {}
class SettingsConfig extends ConfigFile.Service<SettingsConfig, Settings>()("app/Settings") {}
export const SettingsLive = ConfigFile.layer(SettingsConfig, {
schema: Settings,
codec: JsonCodec,
resolvers: [ConfigResolver.upwardWalk({ filename: ".apprc" })],
strategy: MergeStrategy.firstMatch<Settings>(),
parseOptions: { onExcessProperty: "error", errors: "all" },
});
// A file carrying `{ "port": 3000, "prot": 3001 }` now fails with a
// ConfigValidationError whose issue tree names the offending path.
这个 validate 选项无法表示:它运行在已解码的值上,此时多余的密钥已经消失。 对 onExcessProperty: "error" 与 errors: "all" ——解码器报告只有第一个问题,否则,所以一个有3个打字符的文件会花费用户三个固定和重跑周期的费用. 额外工作只发生在已经失败的文档上 .
一个故意承认通过段的图谋 一直在工作 "error" - 但知道为什么,因为形状暗示相反:a Schema.StructWithRest 休养 切换过量的检查 完全的不只是钥匙 其余的封面。 没有休息的 Structs 严格地独立地停留,所以严格性是由每个关卡而不是每个密钥决定的. 选择 parseOptions 改变不了任何变化,这就使这个问题成为每个负载者的决定,而不是迁移.
错误
每一次失败都是被标记的错误 Effect.catchTag。标签存在,以便回收可以不同:
| 标记 | 手段 | 恢复 |
|---|---|---|
ConfigFileNotFoundError | 解锁链没有匹配。 卡莉丝 searched,解析器名称探测,和 candidates在磁盘上检查的路径 . | 倒退到默认——一个常常不是错误的失败. loadOrDefault 帮你处理. |
ConfigFileReadError | 找到文件但无法读取 。 卡莉丝 path 结构 cause。 。 。 . | 通常致命:文件存在,进程无法读取. 检查权限 . |
ConfigFileWriteError | 无法写入文件 。 卡莉丝 path 结构 cause。 。 。 . | 在别处重试, 或者表面给用户 . |
ConfigDefaultPathMissingError | save 或者说 update 被调用到一个没有 defaultPath。 。 。 . | 接线错误, 不是数据条件 。 修补层层,或调用 write 以一条明确的道路. |
ConfigValidationError | 文件没有满足计划 或呼叫者提供 validate 拒绝。 承载结构化 issue 树和可选 path。 。 。 . | 报告问题; 不要运行无法验证的配置 . |
ConfigCodecError | 解码器无法解析或字符串 。 卡莉丝 codec, (中文(简体) ), operation 结构 cause。 。 。 . | 文件腐败了 报道与因. |
ConfigMigrationError | 版本迁移失败 。 卡莉丝 version, (中文(简体) ), name, (中文(简体) ), phase 结构 cause。 。 。 . | 报告该步骤失败; 磁盘上的配置未被更改 . |
ConfigEncryptionError | 加密、解密、密钥开发或基数64步骤失败 。 卡莉丝 phase 结构 cause。 。 。 . | 错误的密码句和腐败的信封都在这里;检查 phase。 。 。 . |
ConfigLoadError, (中文(简体) ), ConfigReadError, (中文(简体) ), ConfigEncodeError, (中文(简体) ), ConfigWriteError, (中文(简体) ), ConfigSaveError 财务报告和审定财务报表 ConfigUpdateError 是出口工会,确切地点出每种方法可能产生的失败 ConfigEncodeError 实值 ConfigWriteError 减 ConfigFileWriteError,因为 encode 永远不要碰磁盘。 抓住一个标签 缩小工会, 留下其余的传播:
import type { ConfigFileShape } from "@effected/config-file";
import { Effect, Schema } from "effect";
class AppShape extends Schema.Class<AppShape>("AppShape")({ port: Schema.Number }) {}
const fallback = new AppShape({ port: 3000 });
// `load` fails with ConfigLoadError. Handling the not-found tag leaves
// ConfigReadError — the file-is-broken failures, which we let propagate.
export const loadOrFallback = (config: ConfigFileShape<AppShape>) =>
config.load.pipe(Effect.catchTag("ConfigFileNotFoundError", () => Effect.succeed(fallback)));
编解码器
包裹中有4艘解码器飞船,每艘都有独立的命名导出. JsonCodec 来源于软件包根;另外三个各有各自的子路径和一个可选的引擎对等端,可以安装在旁边:
| 代码c | 格式 | 从 | 发动机 |
|---|---|---|---|
JsonCodec | 贾森 | @effected/config-file | 主机 JSON 全球, 完全没有解析器 |
JsoncCodec | 约克 | @effected/config-file/jsonc | @effected/jsonc |
YamlCodec | 也门 | @effected/config-file/yaml | @effected/yaml |
TomlCodec | 移动 | @effected/config-file/toml | @effected/toml |
您只安装并装入您命名的解析器 。 根从未到达子路径,所以一个JSON唯一的应用程序没有安装解析器,也没有船. 每个子路径只到达自己的引擎, 解码器从来没有被收集到一个命名空间对象, 所以导入 TomlCodec 从未提及 YAML 或 JSONC 绑定 .
编译. EncryptedCodec 用 AES-GCM 包入任何编码器,以及 ConfigMigration.make 将任何解码器包裹起来,这样解码的内容会被提升到最新的版本。 每个 宽度 错误通道,而不是将其故障平整到内置编码器的错误,以及装饰的编码器插件进入 ConfigFile.layer, (中文(简体) ), ConfigFile.testLayer 财务报告和审定财务报表 ConfigFile.read 象其他的一样,他们的配主将因它而扩大。 一个服务密钥将拓宽错误命名为它的第三种参数 :
import type { ConfigCodec } from "@effected/config-file";
import {
ConfigFile,
ConfigMigration,
ConfigResolver,
EncryptedCodec,
EncryptedCodecKey,
JsonCodec,
MergeStrategy,
} from "@effected/config-file";
import { Effect, Schema } from "effect";
const migrating = ConfigMigration.make({
codec: JsonCodec,
migrations: [
{
version: 2,
name: "add-port",
up: (raw) => Effect.succeed({ ...(raw as Record<string, unknown>), port: 8080 }),
},
],
});
// `parse` now fails with ConfigCodecError | ConfigMigrationError | ConfigEncryptionError.
export const secret = EncryptedCodec(migrating, EncryptedCodecKey.fromPassphrase("hunter2", new Uint8Array(16)));
const Settings = Schema.Struct({ port: Schema.Number });
// Read back from the codec value: ConfigCodecError | ConfigMigrationError | ConfigEncryptionError.
type SecretError = ConfigCodec.ErrorOf<typeof secret>;
class SecretSettings extends ConfigFile.Service<SecretSettings, typeof Settings.Type, SecretError>()(
"app/SecretSettings",
) {}
export const SecretSettingsLive = ConfigFile.layer(SecretSettings, {
schema: Settings,
codec: secret,
resolvers: [ConfigResolver.explicitPath("./settings.enc")],
strategy: MergeStrategy.firstMatch(),
});
离开第三种参数和 ConfigFile.layer 调用是一个编译错误: 该服务的方法会随着一个 ConfigEncryptionError 其类型从不命名。 信封的基数64由核心解码 effect/encoding/Base64,所以周围的白空间是被容忍的,而里面的白空间,或者缺失的粘贴则失败了 ConfigEncryptionError 阶段 "encoding"。 。 。 .
特征
ConfigFile.Service编号 :ConfigFile.layer编号 :ConfigFile.testLayer——每层服务类及其分层.testLayer种子文件输入临时目录,并连接 真实 因此,试验要锻炼实际的管道,而不是能够从管道中漂出的一个支点.parseOptions——解码选项被串入到每个解码层和层上read。 。 。 .onExcessProperty: "error"只有这样才能报告 故意删除的打字段或字段.ConfigFile.read——单发从服务中逃出:读取,解码并验证一个明确路径,每个调用命名的 schema and codec,没有解析器链和写出路径.encode编号 :write编号 :save编号 :update——写道.encode(value, options?)返回已序列化文本 awrite将不写入、不发布事件或不指定错误路径:--dry-run旗帜.write采取一个明确的路径 呼叫者保证;save财务报告和审定财务报表update解析层defaultPath并创建其父目录.encode财务报告和审定财务报表write接受{ header },在文件上方正本加一行#:schema https://…命令 atop a TOML 文件, 例如 。 调用者以目标格式拥有其有效性; JSON 没有注释语法, 所以在JsonCodec生成无法解析的文件.ConfigResolver—— 说explicitPath, (中文(简体) ),staticDir, (中文(简体) ),upwardWalk, (中文(简体) ),workspaceRoot, (中文(简体) ),gitRoot财务报告和审定财务报表systemEtc。解析器的错误通道是never通过合同: 每一个文件系统失效都变为Option.none(),因此一个无法读取的层次永远不会中止链条.MergeStrategy—— 说firstMatch财务报告和审定财务报表layeredMerge,以优先排序合并已发现的来源.JsonCodec(基层),JsoncCodec(单位:千美元)./jsonc, (中文(简体) ),YamlCodec(单位:千美元)./yaml, (中文(简体) ),TomlCodec(单位:千美元)./toml)——JSON,JSONC,YAML和TOML,导出独立并被子路径分割,因此一个未使用格式的引擎既不被安装也不被装入.ConfigCodec编号 :EncryptedCodec编号 :ConfigMigration——可插接的可编码接合器,在其出错类型中通用,因此装饰器会拓宽而不是平整.ConfigCodec是界面:通过满足它来带来自己的格式.ConfigEvents——选入PubSub联 合 国ConfigEvent,在省略时为零成本:否events选项表示完全没有上下文。 失败事件带有结构化的打出错误, 绝不是reason绳子.asConfigProvider编号 :layerConfigProvider- 以 v4 形式曝光已装入、已验证的文件ConfigProvider,在环境下分层,所以环境变量会覆盖它部署的文件.