# @aalis/schema-config 中文文档

规范地址：https://npmcn.com/package/%40aalis/schema-config/

版本：0\.16\.1
npm 元数据更新时间：2026-11-01T02:30:07.672Z
中文译文更新时间：2026-11-06T17:46:48.277Z
软件包许可：MIT

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

原始 npm 页面：https://www.npmjs.com/package/%40aalis/schema-config
原文对照：https://npmcn.com/package/%40aalis/schema-config/?lang=original
项目主页：https://github.com/AalisLabs/Aalis#readme
源码仓库：https://github.com/AalisLabs/Aalis

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

- 包体积（解压后）：140 kB，15 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- 运行依赖：1 个
- peer 依赖：@aalis/core \>=0\.21\.0 \<1\.0\.0
- 首次发布：2026-08-04T12:08:20.590Z
- 最新版本发布：2026-11-01T02:30:07.537Z
- 近 30 天下载量：2547（2026-10-05 至 2026-11-03，npm 官方下载统计）
- 已发布版本数：10（近 12 个月 10 个）

## 中文简介

配置表单 Schema 词汇契约包：`ConfigSchema` / `SchemaField` / `SchemaGroup` / `SchemaArray` 及 `SchemaFieldTypes` 扩展点；声明与读取插件配置的 `defineConfig` / `ConfigOf` / `parseConfig`；可按会话覆盖的字段 `SessionFieldSpec` / `SessionFieldsOf` / `isSessionField`，经 JSON 下发的 \`WireSchemaFiel

## 安装

```sh
npm install @aalis/schema-config
```

也可以使用：`pnpm add @aalis/schema-config`、`yarn add @aalis/schema-config`、`bun add @aalis/schema-config`

## README

# @aalis/schema-config

配置表单 Schema 词汇契约包：`ConfigSchema` / `SchemaField` / `SchemaGroup` / `SchemaArray` 及 `SchemaFieldTypes` 扩展点；声明与读取插件配置的 `defineConfig` / `ConfigOf` / `parseConfig`；可按会话覆盖的字段 `SessionFieldSpec` / `SessionFieldsOf` / `isSessionField`，经 JSON 下发的 `WireSchemaField` / `WireConfigSchema`；对这套词汇的中立解释函数 `defaultsFrom` / `deepMergeDefaults` / `validateConfig` / `readFieldValue` / `removeExtraFields`，密钥引用的类型与工具 `SecretRef` / `isSecretRef` / `mapSecretFields` / `SecretPosition`，配置危险键闸与拷贝 `isUnsafeConfigKey` / `cloneConfigObject`，以及插件因配置问题无法激活时在 `apply` 里抛出的 `configError` / `missingConfigError`（后者用于缺必填项；name 为 `ConfigError`、不带 stack，激活失败日志只有一行，不像程序崩溃）。

`@aalis/core` 把插件的 `configSchema` 当作 opaque 数据透传、不解释任何字段；表单词汇（label / options / textarea …）属呈现层，统一住在本包。同一份 schema 既是表单描述，又是插件配置值类型的来源（`ConfigOf`）和运行时解析规则（`parseConfig`）；渲染宿主（WebUI）与宿主政策（runtime 的配置同步）也用它消费 schema。

运行时只依赖同样零依赖的 `@aalis/schema-i18n`（给人看的文字是它的文字对象）；对 `@aalis/core` 只有 type-only 锚点（以 peer 依赖声明）。字段类型可由其他 api 包经 declaration merging 扩展（如 `api-llm` 注入 `'llm-ref'`）。

## 声明与读取

```ts
import { config, definePlugin, logger } from '@aalis/core';
import { defineConfig, parseConfig } from '@aalis/schema-config';

const configSchema = defineConfig({
  apiKey: { type: 'string', label: 'API Key', required: true, secret: true },
  port: { type: 'number', label: '端口', default: 8080, min: 1, max: 65535, integer: true },
  mode: {
    type: 'select',
    label: '模式',
    default: 'fast',
    options: [
      { label: '快', value: 'fast' },
      { label: '稳', value: 'safe' },
    ],
  },
});

export default definePlugin({
  name: '@your-scope/plugin-x',
  configSchema,
  uses: { config, logger },
  apply({ config, logger }) {
    const cfg = parseConfig(configSchema, config.use, logger.use);
    // cfg: { apiKey: string; port: number; mode: 'fast' | 'safe' }
  },
});
```

-   **`defineConfig(schema)`**：运行时原样返回传入的对象（不冻结、不加任何属性，WebUI 读的就是这个活对象），类型层保留字面量，返回值可直接放进 `definePlugin({ configSchema })`。不要给变量标 `ConfigSchema` 类型注解，注解会把字面量拓宽掉，`ConfigOf` 只能推出宽类型。推出的类型里各属性只读、`options` 是字面量元组；需要在运行时改写 schema（如按已注册的服务补全选项）时，经 `ConfigSchema` 或 `SchemaField` 类型的引用改写。
-   **`ConfigOf<S>`**：从 schema 推导配置值类型。字段取 `SchemaFieldTypes` 里登记的取值类型（见下表）；select / multiselect 有静态 `options`、没有 `dynamicOptions` 且 `allowCustom` 不为 `true` 时收窄为选项值的字面量联合，数字选项对应数字字面量。有 `default` 或 `required: true` 的字段必定存在，其余为可选；分组推为嵌套对象，总是存在；数组推为元素类型的数组，元素按同一规则推导。带 `session` 的顶层字段不在其中（见下文「可按会话覆盖的字段」）。
-   **`parseConfig(schema, raw, logger?)`**：按 schema 解析插件拿到的配置，返回 `ConfigOf` 类型的新对象，不改入参。之后的夹紧、跨字段约束等核对作用于返回值。

## 给人看的文字

-   字段、分组与数组的 `label`、`description` 和选项的 `label` 可以写字符串，也可以写 `@aalis/schema-i18n` 的文字对象（插件自己原文字典里的 `t('…')`），渲染宿主按读者语言渲染。`default` 是取值，不是文字。
-   `validateConfig` 的每条问题带 `code`（`missing`、`type`、`not-option`、`pattern`、`format`、`integer`、`min`、`max`、`reserved-key`、`validate`）与原因的文字对象 `text`；`message` 是它的原文，日志用。
-   `configError(message)` 收字符串或文字对象，收文字对象时挂上它（name 仍是 `ConfigError`），WebUI 按读者语言显示失败原因；`missingConfigError` 的说法本身是文字对象，`note` 同样可以是文字对象。
-   本包自己的字典（ns `@aalis/schema-config`）收核心配置表单（`CORE_CONFIG_SCHEMA`）、校验原因与配置错误的说法。

## 字段类型

本包内置的中立类型。「判定」一列是 `validateConfig` 与 `parseConfig` 共用的逐字段规则：

| type | 取值类型（`ConfigOf`） | 判定 |
| --- | --- | --- |
| `string` / `textarea` | `string` | 字符串；有限数字按字符串收；声明了 `pattern` 时按正则匹配；声明了 `format` 时按路径写法判定 |
| `number` | `number` | 有限数值；去掉首尾空白后非空、能完整解析为有限数的字符串按数字收；`min` / `max` / `integer` |
| `boolean` | `boolean` | 类型，不做转换 |
| `select` | 选项值的字面量联合，否则 `string` | 字符串或有限数字；有静态 `options`、没有 `dynamicOptions` 且 `allowCustom` 不为 `true` 时须是某个选项值 |
| `multiselect` | 选项值字面量联合的数组，否则 `string[]` | 数组；元素同 `select`；按输入顺序保留。`ordered: true` 时先后有意义（如语言的先后）：去掉重复项（按选项归位后的值比，不算问题），WebUI 给排序控件 |
| `list` | `string[]` | 数组，元素按字符串判定；有序，允许重复（如命令行参数） |
| `map` | `Record<string, string>` | 对象，每个值按字符串判定（如环境变量表） |

-   **标量宽容**：YAML 里不加引号的纯数字（QQ 号、口令、端口、环境变量值）在期望字符串的位置照常可用，加了引号的数字在期望数字的位置照常可用；`parseConfig` 返回换算后的值。
-   **`format`**：写宿主文件路径的 `string` / `textarea` 字段声明。`'absolute-path'` 只收绝对路径（`/…`、`C:\…` 或 `\\…`）；`'executable'` 另收不含路径分隔符的命令名（按 PATH 查找），`C:foo` 这类带盘符的相对写法不算。判定忽略首尾空白，交给插件的值也去掉首尾空白；空串与只有空白都表示未填、不查（`required` 字段算缺失）；只按写法判定、与宿主平台无关，Windows 形状的绝对路径在 POSIX 宿主上同样通过。配置里的相对路径没有可靠的基准（宿主进程的 cwd 不带语义），所以不收。
-   **选项即取值范围**：选项按字符串比较，`parseConfig` 取选项声明的原值（WebUI 把数字选项存成字符串，读出时归位成数字）。
-   **`dynamicOptions` / `allowCustom`**：两者影响取值判定，由本包声明。`dynamicOptions` 是动态选项来源的服务名（WebUI 经 webui-server 调该服务的 `listModels()` 或等价方法获取选项），声明后静态 `options` 不再是取值范围，只查取值是字符串或数字；`allowCustom` 表示接受选项之外的值，不查是否在选项内：WebUI 给 multiselect 提供手动输入；select 用它表示静态选项不全（例如由插件在运行时补全选项的字段）。
-   **扩展字段类型**：api 包经 declaration merging 往 `SchemaFieldTypes` 登记类型名与取值类型（如 `'llm-ref': ModelRef`），`ConfigOf` 据此推导。本包不按类型校验外来类型的取值，只调字段自己的 `validate`（见下文）：`validateConfig` 放行，`parseConfig` 除把 `''` 当作未配置外原样透传。

## 字段自己的校验与文字读写（`validate` / `fromText` / `toText`）

-   **`validate(value)`**：合法返回 `undefined`，不合法返回原因（非空字符串或文字对象）；抛错或返回别的值都按不合法，原因写「校验时出错」，不转述抛出的错。在类型与约束都过了之后调，拿换算后的取值（外来类型是原值）。`validateConfig` 报 `invalid`（`code` 为 `validate`），`parseConfig` 按无效处置（见下文），`readFieldValue` 给出原因。list、map、multiselect 有坏元素时，`validateConfig` 与 `readFieldValue` 只报元素、不调它，`parseConfig` 先丢坏元素、再拿剩下的值调它。外来类型的字段要核取值就写它。原因会原样进 `parseConfig` 的告警与拒绝激活的消息，不要在里面写取值（字段可能是密钥）。
-   **`fromText(text)` / `toText(value)`**：文字与取值互换；内置类型不用写。
-   三者是进程内函数，schema 经 JSON 下发时没有；线上类型是 `WireSchemaField` / `WireConfigSchema`。

## 可按会话覆盖的字段（`session`）

顶层字段写了 `session`（声明 `SessionFieldSpec`，成员由 `@aalis/api-session-settings` 并入）即可按会话覆盖：插件经会话设置的读取器按会话读它，改了不重载插件。`isSessionField(entry)` 判一个顶层条目是不是这种字段（分组、数组上写了不算，分组与数组元素里的字段不按它判）。

-   **`parseConfig`** 不交出这类字段、不补 `default`，配置里有这个键也不告警；`ConfigOf` 同样不含。
-   **`SessionFieldsOf<S>`**：这类字段经读取器读到的值。取值类型同 `ConfigOf`，`onInvalid: 'error'` 的为 `unknown`（坏值原样交出，由插件核）；有 `default` 的必定存在，没有的为可选。
-   **`validateConfig`、`defaultsFrom`、`removeExtraFields`** 照常把它当 schema 字段：它的全局层就是配置文档里的值，作者默认照常写进文档。
-   **`readFieldValue(field, value)`**：单个值的判定，与 `validateConfig` 同一套：合法给 `{ value }`（换算后的取值；没配置的给 `undefined`），不合法给 `{ invalid }`（集合里有坏元素也算）。不回落 `default`、不抛错、不拷贝。判的是取值，不是文档写法：密钥字段上的引用 `validateConfig` 放行，这里判为不合法。

## 密钥字段（`secret`）

`secret: true` 表示字段的值是凭据（API key、token、可能带账号密码的连接串），由本包声明，各宿主都按它处理：值不放在配置文档里，存进宿主的密钥存储（Node 宿主 `@aalis/runtime` 用数据目录的 `secrets.yaml`），文档里只写引用 `{ $secret: '<键>' }`（类型 `SecretRef`），宿主把配置交给插件之前解析回原值，展示与回传都不给原值。标在 `map`、`list` 等非标量字段上时整个值算一个密钥。本包只声明词汇，不读写密钥存储。

-   **`isSecretRef(value)`**：是不是引用，即只有 `$secret` 一个自有键、值为非空字符串的纯对象。只在 `secret: true` 的字段上有这层含义。
-   **`mapSecretFields(schema, config, fn)`**：逐个处理配置里密钥字段上的值（顶层、分组内、数组元素内；值为 `undefined` 的跳过），`fn` 收到值与位置 `SecretPosition`（`path` 是点号路径，数组元素里的不带下标；`inArray` 表示在数组元素里），`fn` 返回 `undefined` 时删掉这个键。不改入参：有改动时返回浅拷贝（没动到的部分与入参共享），没有改动时返回入参本身，调用方要改写结果请先复制。宿主拆出、解析密钥与给呈现方去掉密钥都用它。
-   **`validateConfig`** 校验的是文档：密钥字段上的引用算合法，不查键在不在。
-   **`parseConfig`** 给插件用：密钥字段上仍是引用，说明宿主没能解析它（键不在密钥存储里），一律不可恢复，消息点名键名；不看 `default` 与 `onInvalid`，在数组元素里也让整次解析失败，不只丢这一条元素。回落默认值会悄悄连上别的东西（例如默认的本机数据库），所以不回落。
-   **`defaultsFrom`** 不产出密钥字段：派生结果会被宿主写进配置文档，密钥的默认值不进文档；`parseConfig` 照常补上默认值。

## parseConfig 的处置

-   **缺失**：`undefined` 与 `null`（YAML 裸键）；`required` 字段另把 `''` 当作缺失（WebUI 与脚手架用 `''` 表示未填；声明了 `format` 的只有空白也算），外来类型的 `''` 一律当作缺失（WebUI 给未选的 `llm-ref` 存 `''`）。有 `default` 用 `default` 的拷贝；无 `default` 且 `required` 不可恢复；否则省略该键。
-   **无效**（不符合上表的判定，或字段的 `validate` 判为不合法）：未声明 `onInvalid` 时保持宽容处置：有 `default` 回落并告警；无 `default` 且 `required` 不可恢复；否则省略并告警。字段声明 `onInvalid: 'error'` 时，显式无效值即使有 `default` 也抛 `ConfigError`；它不改变 `required` 的缺省含义，`undefined` / `null` 仍走上述缺省规则。
-   **list / map / multiselect**：默认坏元素、坏值逐个丢弃并告警，其余保留，剩下的值再过字段的 `validate`；稀疏数组的空位和 map 的保留键也属于坏成员。严格字段只要含一个坏成员就拒绝整个值，不返回部分结果。
-   **分组**：值不是对象时默认告警，整组按空对象解析，子字段各取默认值。显式给非对象且分组含严格字段时拒绝；`undefined` / `null` 仍视为缺省。
-   **数组**：值不是数组按无效处理，含严格元素字段时显式无效的数组值会被拒绝；元素不是对象、或元素内有不可恢复的字段时只丢这一条元素并告警，邻居元素照常解析；元素字段的默认值逐元素补齐（runtime 的配置同步不补数组元素的默认值）。
-   **整体配置**：显式非对象且 schema 含严格字段时拒绝；`undefined` / `null` 仍视为缺省。严格错误说明字段路径与安全的类型或约束原因；集合的下标可定位，map 的键可能含密钥，因此错误只定位到 map 字段，不写用户值或键。
-   **schema 外的键**：丢弃并告警。runtime 已先裁掉顶层与分组内的，这里主要覆盖数组元素，以及不经 runtime 的宿主。
-   **不可恢复**（顶层或分组内）：缺失抛 `missingConfigError`，无效抛 `configError`，插件随之进 error 态，在 WebUI 保存或配置文件热重载后重试激活。密钥字段上没被解析的引用见上节，在任何位置都不可恢复。
-   **告警文案**：`配置项 <路径> <原因>，改用默认值 <JSON>`、`配置项 <路径> <原因>，已忽略`、`配置项 <数组路径>[i] 已忽略：<字段> <原因>`。只写路径、类型名与约束，不写原值（可能是密钥）；默认值会写出。

数组也可声明 `onInvalid: 'error'`：非对象元素或元素内不可恢复的字段会拒绝整个数组，避免丢弃坏项后触发空数组的业务默认；元素内普通字段仍按自身的回落策略解析。未声明时继续逐项跳过，适合相互独立的 server 列表。

`validateConfig(schema, config)` 用同一份判定做只读检查，返回问题清单（`kind` 为 `missing` 或 `invalid`），不转换、不回填、不裁剪；它把 `undefined` / `null` 当作未配置，`required` 字段另把 `''`（声明了 `format` 的字符串只有空白也算）当作缺失，与 `parseConfig` 同一判定；只在 `required` 且无 `default` 时报缺，声明了 `default` 的永远不算缺。`onInvalid` 改变 `parseConfig` 的处置，不改变校验器的判定或报告。宿主用它告警或拦截保存：runtime 的配置同步对启用中的插件打告警，WebUI 保存插件配置时拒绝本次新引入的 `invalid`。

`list` 与 `map` 的补充约定：

-   **默认值**：`default` 分别写数组与对象，`defaultsFrom` 按值拷贝（`secret: true` 的除外）。`deepMergeDefaults` 合并默认值时递归合并对象（runtime 的配置同步与 WebUI 保存插件配置都用它），因此顶层或分组里的 `map` 会按键补齐：默认映射中的键总会回到用户配置里，删掉也无效。需要用户可删除的预置条目时，默认值写 `{}`，由插件代码兜底。`list` 不参与合并，用户配置了就整体采用。
-   **未知字段裁剪**：`removeExtraFields` 把 `map` 当作叶子整体保留，不按键裁剪。
-   **缺省 UI**（WebUI 的 SchemaForm）：两者都渲染为多行文本框。`list` 每行一项，空白行忽略；`map` 每行一条 `KEY=VALUE`，按第一个 `=` 切分，键去掉首尾空白，值原样保留，缺少 `=` 或键为空的行不保存。现有的数字、布尔项或值按字符串显示，编辑后写成字符串；其余无法按这套规则逐行表达的值（项或值本身含换行、`list` 含空白项、`null`、嵌套的数组或对象等）会让该字段只读显示，需在配置文件中编辑。旧版存下的空串按空列表 / 空映射显示，保存时写成 `[]` / `{}`。

## 消费方式：用宽区间，别用 caret

仓内五十余个包这样依赖本包（与 `@aalis/core` 的 peerDep 同一风格）：

```jsonc
"dependencies": { "@aalis/schema-config": "workspace:>=0.9.0 <1.0.0" }
```

发布时该区间**原样保留**（实测 `pnpm pack` 产物即 `>=0.9.0 <1.0.0`），本地仍照常 link 到 workspace。

**不要用 caret。** 0.x 的 caret（`^0.9.0` = `>=0.9.0 <0.10.0`）锁死 minor：本包加一个字段类型 就是一次 minor，届时所有已发布消费者的范围会拒收新版，node\_modules 里出现两份副本——而 declaration merging 是按模块副本生效的，`SchemaFieldTypes` / `SchemaField` 的合并面会就此裂开 （一份合并了 `'llm-ref'`，另一份没有）。宽区间让整个 0.x 段自由流动，加词汇零级联。

解析以调用方传入的值为准。宿主与 Core 的配置拷贝会移除 `__proto__` / `constructor` / `prototype` 保留键，`parseConfig` 无法诊断在上游已被删除的条目；配置字典（含 env）不能使用这些键。

## 常见问题

### @aalis/schema-config 是什么？

配置表单 Schema 词汇契约包：ConfigSchema / SchemaField / SchemaGroup / SchemaArray 及 SchemaFieldTypes 扩展点；声明与读取插件配置的 defineConfig / ConfigOf / parseConfig；可按会话覆盖的字段 SessionFieldSpec / SessionFieldsOf / isSessionField，经 JSON 下发的 \`WireSchemaFiel

### 如何安装 @aalis/schema-config？

在项目目录运行 npm install @aalis/schema-config，或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖：@aalis/core（\>=0\.21\.0 \<1\.0\.0）。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖，Yarn 需要手动安装。

### @aalis/schema-config 支持 TypeScript 吗？

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

### @aalis/schema-config 支持 ESM 还是 CommonJS？

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

### @aalis/schema-config 可以商用吗？

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

### @aalis/schema-config 的下载量有多少？

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

### @aalis/schema-config 还在维护吗？

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

