import type { NotifyConfig, RawSettingValue, SettingsPatch, StoredSettings } from "../model/type.js"; import type { ValidationResult } from "./type.js"; /** bark 紧急度白名单、频道已知键、必填键、**尺寸上界**(`WEBHOOK_TEMPLATE_MAX_CHARS` / * `BARK_LEVELS_LIMIT`)的事实源都在 src/shared/config-schema.ts(两端共享面): * 写入口径与设置页的选项必须是同一份,各写一份就会出现「页面选得到、宿主拒收」。本域只消费。 * * 尺寸上界(#1016 S2)此前在本域就地写字面量,理由是「本轮不扩两端共享 schema」——那份「刻意」 * 正是 S1 要消灭的第二事实源:客户端将来给这两个输入框加同源约束时只能来本域抄一份。 * 两条尺寸判据的语义是**「拒新增、不动存量」**:本次提交的值超限即 400,沿用存量的旧值不重判 * (判据的 `inherited` 面,见 validateChannel);0.2.9 形态清理按同一口径不删它们。 */ /** `"HH:MM"` 二十四小时制:事实源在 src/shared/quiet.ts(`isClockText`),两端同源。 */ /** * 频道实例里**合法的凭据只能走已知字段**(bark 的 `deviceKey`、webhook 的 `token`/`password`/`headerValue`), * 这些凭据别名键在写入口径直接**拒绝**而不是静默剔除——静默剔除会让用户以为设置生效了。 * * 导出是为了让判据按清单表驱动:README 的「保留键写拒」与这份清单必须是同一份事实源。 */ export declare const BARK_RESERVED_KEYS: readonly string[]; /** webhook 侧的凭据别名键;与 `BARK_RESERVED_KEYS` 同语义(见上)。 */ export declare const WEBHOOK_RESERVED_KEYS: readonly string[]; /** * 只接受布尔值的键。 * * 导出而非私有:门禁 `config-matrix` 要按真实取值断言「这份清单是默认设置的子集且值都是布尔」, * 让它读定义处才是唯一事实源——照抄一份给门禁,两边迟早各说各话。 */ export declare const BOOLEAN_KEYS: readonly string[]; /** * 退役键:**曾经**是合法配置键、现已没有值语义的键,写面一律 400 拒收,且每键自带拒收话术。 * * 为什么拒而不是当陌生键放行:陌生键是留给未来版本的空间,而这一批是**已经搬走 / 已经删除**的键—— * 静默放行会让停留在升级前页面上的旧客户端以为保存成功了。话术逐键给出而不是共用一句:0.2.3 那批只是 * 搬了家,`maxConnections` 是机制整体移除,共用一句会把后者引到错误的原因上。 */ export declare const RETIRED_KEYS: Readonly>; /** * 非负整数键及其上界(越界视为非法而不是截断——静默改写用户的输入比拒绝更糟)。 * * 导出理由同 `BOOLEAN_KEYS`:门禁要按真实取值断言「每个键都在默认设置里且上界是非负整数」。 * * 上界的**值**来自 shared 的 `HISTORY_MAX_AGE_DAYS_LIMIT`,不是就地写的字面量:0.2.9 形态清理按 * 「这个值在本版本有没有这种形态」删键,两处不同值就是两份「合法形态」的定义,而清理步那份一旦落后, * 用户一个合法的键会被静默删掉。 */ export declare const COUNT_LIMITS: Record; /** * 原型链上的危险键名:JSON 文本能造出自有键,展开进设置对象就会改写原型。 * * 事实源放在输入闸门(判据要认得它才能把它从「陌生键」里摘出来),写面从这儿取同一份—— * 两处各写一份清单,改了一处就会变成「一个判红一个放行」。 */ export declare const UNSAFE_KEYS: readonly string[]; /** * 文本 → 原始设置。坏 JSON 与非对象(数组、标量)一律得到空设置:配置文件被手改成不合法 * 内容时读面该回落默认值,而不是让整个插件装配失败——设置坏掉不该拖垮通知。 */ export declare function parseJsonObject(text: string): StoredSettings; /** * 归一化:把任意输入收敛成一份完整设置,缺键补默认、非法值回落合法域——**永不失败**, * 是读路径的必经工序。输出是**全量**的(可选字段也写出来,空串 / 空对象表达「没有」): * 让形状随输入变化会迫使下游到处判键在不在,而这里正是唯一能把这件事做掉的地方。 */ export declare function normalizeConfig(input: StoredSettings): NotifyConfig; /** * 校验:给出首个非法键与提示。只对**显式提交**的键负责——缺键不是错误,由归一化补默认; * 一次只报首个非法键,因为设置页的定位光标只能落在一个字段上。**陌生键一律 400**(退役键 * 另有逐键话术,见 RETIRED_KEYS):写面已经不再「透传保留」陌生键,理由见 validateKnownKeys。 */ export declare function validateSettings(raw: SettingsPatch): ValidationResult; /** * 校验**合并结果**(#1016 S2 写面专用入口,本身不收紧任何判据)。 * * 与 `validateSettings` **共用同一个 `verdictOf`**:判定结论逐字相同,一个字都没多判。两者 * 唯一的差别是审谁——本函数审「这次写真正要落盘的那条」,`validateSettings` 审「客户端交上来的 * 草稿」。存量里有越界值时两者给出相反的结论,而后者不是用户要的。 * * 单列一个函数而不是给 `validateSettings` 加可选参数:那是把一个纯函数改成「视参数而定 * 行为」的形状,调用方分不清自己拿到的是哪一种;而两条入口结论一致这件事靠的是它们**共用同一个 * `verdictOf`**,不是靠两处同步维护。 * (`validateSettings` 在导出面基线上——改它的签名要动基线;本条分工取的是形态上的可辩护性, * 不是「动了不用改基线」。) * * @param raw 提交体。 * @param merged 写面**按字段合并后**的结论(service 域 `mergeChannels` 的产物)。它与 * `raw.channels` 是两份东西:前者是这次写要落盘的形态(存量没被提到的键都在里面)加上每条 * 里「哪些字段原样来自存量」的清单,后者是客户端交上来的草稿。 * 缺省 = 按 `raw.channels` 判(草稿测试与直调路径的旧行为,条目里每个键都算本次提交)。 */ export declare function validateSettingsWithMerge(raw: SettingsPatch, merged?: MergedChannelScope): ValidationResult; /** * 写面「按字段合并」结论在判据侧的形状:这次写要落盘的频道数组 + 与它同下标的**两条记账**。 * * 两条记账问的是两个不同的事实,故分列而不是并成一条: * - `inherited`:**值**原样来自存量的字段(#1016 S2)。它让值域判据只审本次真正动过的字段。 * - `preexisting`:**键的缺席**本来就来自存量的必填键(见 `firstMissing`)。存量里就缺、这次也没 * 补上的那个键。 * 「值沿用」与「缺席沿用」合成一条会让两种判据都说不清自己为什么跳过,故各自记各自的。 * * 事实源是 service 域的 `ChannelMerge`(结构同构);缺省 `preexisting` = 没有存量可谈,草稿与直调 * 路径因此维持原样——那里每个键都算本次提交。 */ export type MergedChannelScope = { readonly channels: RawSettingValue; readonly inherited: readonly ReadonlySet[]; readonly preexisting?: readonly ReadonlySet[]; }; /** * 逐值相同判定:标量用 `===`,数组逐项比,对象按键集合比(**键序无关**)。 * * 键序无关有实测依据:客户端把视图原样交回,JSON 往返不保证键序,而「同一份内容、 * 键序不同」在修订号那侧已经是不算改动(见 service 侧 `stableJson` 的注释)—— * 两处对「什么算同一份内容」必须给同一个答案,否则用户什么都没改却被判成本次改动。 * * 一侧缺键即视为不同:存量的键被提交抹掉,是一次真改动(客户端的 `stripChannelEmpties` * 会剥掉空串可选字段,那一类确实会在下次保存时被写掉)。 * * 导出给写面合并(service/impl/merge.ts)判「这次提交有没有真的改这个字段」:同一个问题—— * 「同一份内容」——两处必须给同一个答案,各写一份迟早把「原样带回」和「用户刚改的」读反。 */ export declare function sameValue(left: RawSettingValue | undefined, right: RawSettingValue | undefined): boolean; /** `level` 与 `preset` 是可选键:缺省各有明确语义(前者让「severity → level」映射生效,后者归一到 custom), * 客户端新建频道时本就不带它们——照必填拦下等于让用户的合法提交保存不了。 * * 导出给草稿测试(dry-run)逐项复用:它只审单条、不审「内置必须在场」(见 requireBuiltinsPresent), * 草稿里可以只有目标频道一条。 * * @param inherited 该条目里**沿用存量**的字段名(#1016 S2)。清单里的字段不重判值域:磁盘上 * 一份越界的旧值不该让此后每一次无关保存都被拒——那不是用户造成的错误。缺省 = 每个键都算 * 本次提交(草稿路径没有存量可言)。 * @param preexisting 该条目里**存量本就残缺、这次也没补上**的必填键(#1016 S3 回归修复)。它只作用于 * 「必填键在场」这一条形状判据,且只对清单里的键放行;缺省 = 没有存量可谈,每个键都算本次提交。 */ export declare function validateChannel(raw: RawSettingValue, inherited?: ReadonlySet, preexisting?: ReadonlySet): ValidationResult; /** * 净化:只保留契约认识的键。陌生键一律剔除而不是拒绝——配置文件是共享的,别的东西写进来的 * 键不该让设置读取失败,也不该被原样带进用户层再写回去。**不归一化**:净化只回答「这个键归 * 不归我」,在这里顺手归一化会让写路径把用户的原始提交偷偷改写掉。 * * @returns 净化后的部分设置;输入不是对象时得到空设置——「一个键都不认识」与「没有键」对 * 调用方是同一件事,不必再给一个空值语义让它自己判。 */ export declare function sanitizeSettings(raw: StoredSettings): Partial; /** * 「这个必填键交上来了没有」:**非空串**才算交上来。事实源是 `REQUIRED_KEYS`(S1 的共享 schema), * 「在场」是这一族判据共用的那一句,故单列并导出给写面合并用:两处各写一份「在场」,迟早在 * 「空串算不算在场」上漂。 * * 这一族的另两处:读面 `asChannel` 的空壳判定(`asBarkChannel` 对空串必填键返回 `ok: false`)与 * upgrade 域 0.2.9 形态清理的**必填键判据**(canonical-keys 的 `requiredKeysOf`)。后者的必填键 * **清单**同样只从 `REQUIRED_KEYS` 读,不再在本域另抄一份。 */ export declare function isDeliveredRequired(value: RawSettingValue | undefined): boolean;