1949007c99
- Add coding convention: no '?:', use explicit 'T | null' - ReflexConfig → discriminated union (SenseReflexConfig | WorkflowReflexConfig) - All optional fields → explicit null (throttle, timeout, interval, on, maxQueue, workflows) - Add exactOptionalPropertyTypes to tsconfig - Add lib: ES2022 to tsconfig - Refactor validateReflexConfig to reduce cognitive complexity 小橘 <xiaoju@shazhou.work>
4.6 KiB
4.6 KiB
Nerve Coding Conventions
语言与范式
函数式优先
用 function + type,不用 class + interface。
// ✅ Good
type Signal = {
senseId: string
value: unknown
ts: number
}
function createSignal(senseId: string, value: unknown): Signal {
return { senseId, value, ts: Date.now() }
}
// ❌ Bad
interface ISignal {
senseId: string
value: unknown
ts: number
}
class Signal implements ISignal {
constructor(public senseId: string, public value: unknown, public ts: number) {}
}
具体规则
| 规则 | 说明 |
|---|---|
type over interface |
所有类型定义用 type,不用 interface |
function over class |
用纯函数 + 闭包,不用 class |
无 this |
函数不依赖 this 上下文 |
| 无继承 | 不用 extends、implements、abstract |
| 组合优先 | 用函数组合代替继承层次 |
| 不可变优先 | 用 Readonly<T>、as const,避免 mutation |
| 禁用 optional properties | 不用 ?:,用 T | null 显式标记可空;多个互斥字段用 discriminated union |
禁用 Optional Properties
不使用 ?:,所有可空字段显式标注 T | null。
// ✅ Good — 明确表达"可以为空"
type SenseConfig = {
group: string;
throttle: string | null;
timeout: string | null;
}
// ❌ Bad — optional 隐藏了"缺失"和"空值"的区别
type SenseConfig = {
group: string;
throttle?: string;
timeout?: string;
}
当多个字段互斥时,用 discriminated union 代替一堆 optional:
// ✅ Good — 编译器保证 sense 和 workflow 不会同时出现
type ReflexConfig =
| { kind: "sense"; sense: string; interval: string | null; on: string[] | null }
| { kind: "workflow"; workflow: string; on: string[] | null }
// ❌ Bad — sense 和 workflow 都 optional,运行时才知道到底填了哪个
type ReflexConfig = {
sense?: string;
workflow?: string;
interval?: string;
on?: string[];
}
例外
以下场景允许 class:
- 第三方库要求(如 Drizzle 的
sqliteTable返回值) - Error 子类(
class NerveError extends Error)
模块与导出
// ✅ Named exports only
export function startEngine(config: EngineConfig): Engine { ... }
export type EngineConfig = { ... }
// ❌ No default exports
export default function startEngine() { ... }
- 一律 named export,不用 default export
- 一个模块做一件事,文件名即职责
命名
| 类型 | 风格 | 示例 |
|---|---|---|
| 文件 | kebab-case | signal-bus.ts |
| 类型 | PascalCase | SignalBus |
| 函数/变量 | camelCase | createSignalBus |
| 常量 | UPPER_SNAKE | MAX_RETRY_COUNT |
| 泛型参数 | 单字母或描述性 | T, TValue |
错误处理
// ✅ 用 Result 类型表达可预期的失败
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E }
function parseSenseConfig(raw: unknown): Result<SenseConfig> {
// ...
}
// ✅ throw 只用于不可恢复的 bug
function unreachable(x: never): never {
throw new Error(`Unreachable: ${x}`)
}
- 可预期的失败用
Result类型返回 throw只用于 bug(程序员错误),不用于业务逻辑- 不用 try-catch 做流程控制
异步
// ✅ async/await,不用 .then() 链
async function runCompute(sense: SenseModule): Promise<Signal | null> {
const value = await sense.compute(db, peers)
if (value == null) return null
return createSignal(sense.id, value)
}
工具链
| 工具 | 用途 |
|---|---|
| pnpm | 包管理 |
| TypeScript | 类型检查(strict mode) |
| Biome | lint + format(替代 ESLint + Prettier) |
| tsup | 打包 |
常用命令
pnpm run check # biome check(lint + format 检查)
pnpm run format # biome format --write(自动修复格式)
pnpm run build # 全量构建
Monorepo 结构
nerve/
packages/
core/ # @nerve/core — 共享类型和工具函数
cli/ # @nerve/cli — CLI 入口
daemon/ # @nerve/daemon — 引擎运行时
docs/ # RFC、convention 等文档
biome.json # 根级 Biome 配置
tsconfig.json # 根级 TypeScript 配置(composite project references)
core是共享层,cli和daemon都依赖它cli和daemon之间不互相依赖- 未来云端版本作为新 package 加入
Commit Convention
<type>(<scope>): <description>
type: feat | fix | refactor | docs | chore | test
scope: core | cli | daemon | rfc-001 | ...
小橘 🍊(NEKO Team)