关于 typed-service-contracts
一种面向 CLI 和库开发的架构技能,以契约和垂直切片组织应用逻辑。它通过 Zod 解析输入,并要求处理器将错误映射为 Result,而非抛出异常。
主要功能
- 01以 Spec 定义输入、输出和错误契约
- 02用 Zod 解析并转换输入
- 03用 Handler 实现副作用逻辑
- 04通过 Result 表示成功或失败
- 05分离契约测试与逻辑测试
适用场景
- 01为 CLI 定义严格的输入边界
- 02解析并验证文件路径等输入
- 03测试错误映射和业务逻辑结果
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ google-labs-code/ stitch-sdk --skill typed-service-contracts安装后在 Claude Code 中输入/typed-service-contracts确认已加载。
详细介绍
类型化服务契约(Spec 与 Handler 模式)
此技能定义了一种基于契约式设计(DbC)原则的垂直切片架构。它将应用逻辑视为经过严格定义的工作单元:输入会被解析(而不只是验证),错误则作为值处理(结果模式),而非异常。
何时使用此技能
- **构建 CLI 或库:**需要在用户输入和系统逻辑之间设立严格边界时。
- **复杂验证:**输入在可用之前需要转换(解析)时(例如,确保字符串是有效的文件路径)。
- **高可靠性要求:**无法承受未处理的运行时异常,且需要穷尽式错误处理时。
- **测试重点:**希望将数据验证测试与业务逻辑测试分开时。
架构组件
1. Spec(spec.ts)
“契约”或“端口”。它定义_做什么_,必须包含:
- **输入 Schema:**一个 Zod Schema,用于将原始输入解析为有效的 DTO。
- **输出 Schema:**一个定义成功数据结构的 Zod Schema。
- **错误 Schema:**由特定失败模式组成的判别联合(而非通用错误)。
- 结果类型:
Success | Failure的DiscriminatedUnion。 - **接口:**能力定义(例如,
interface ConfigureSpec)。
2. Handler(handler.ts)
“实现”或“适配器”。它定义_如何做_,必须:
- 实现 Spec 中定义的接口。
- 是一个处理副作用(文件系统、API 调用)的“非纯”类。
- 绝不抛出异常。它必须捕获内部错误,并将其映射到
Result类型。
如何使用
步骤 1:定义契约(spec.ts)
遵循此模板定义边界。
import { z } from "zod";
// 1. VALIDATION HELPERS (Reusable Refinements)
export const SafePathSchema = z
.string()
.min(1)
.refine((p) => !p.includes(".."), "No traversal allowed");
// 2. INPUT (The Command) - "Parse, don't validate"
export const MyTaskInputSchema = z.object({
path: SafePathSchema,
force: z.boolean().default(false),
});
export type MyTaskInput = z.infer<typeof MyTaskInputSchema>;
// 3. ERROR CODES (Exhaustive)
export const MyTaskErrorCode = z.enum([
"FILE_NOT_FOUND",
"PERMISSION_DENIED",
"UNKNOWN_ERROR",
]);
// 4. RESULT (The Monad)
export const MyTaskSuccess = z.object({
success: z.literal(true),
data: z.string(), // The output payload
});
export const MyTaskFailure = z.object({
success: z.literal(false),
error: z.object({
code: MyTaskErrorCode,
message: z.string(),
suggestion: z.string().optional(),
recoverable: z.boolean(),
}),
});
export type MyTaskResult =
| z.infer<typeof MyTaskSuccess>
| z.infer<typeof MyTaskFailure>;
// 5. INTERFACE (The Capability)
export interface MyTaskSpec {
execute(input: MyTaskInput): Promise<MyTaskResult>;
}
步骤 2:实现 Handler(handler.ts)
遵循此模板实现逻辑。
import { MyTaskSpec, MyTaskInput, MyTaskResult } from "./spec.js";
import * as fs from "fs";
export class MyTaskHandler implements MyTaskSpec {
async execute(input: MyTaskInput): Promise<MyTaskResult> {
try {
// 1. Business Logic
if (!fs.existsSync(input.path)) {
// 2. Explicit Error Return (No Throwing)
return {
success: false,
error: {
code: "FILE_NOT_FOUND",
message: `Path does not exist: ${input.path}`,
recoverable: true,
},
};
}
// 3. Success Return
return {
success: true,
data: "Operation complete",
};
} catch (error) {
// 4. Safety Net: Catch unknown runtime errors
return {
success: false,
error: {
code: "UNKNOWN_ERROR",
message: error instanceof Error ? error.message : String(error),
recoverable: false,
},
};
}
}
}
步骤 3:测试策略
不要编写单体测试。将测试拆分为契约测试和逻辑测试。
A. 契约测试(Schema)
测试_把关者_。确保无效数据在到达 Handler 之前就被拒绝。
- **重点:**边缘情况、验证规则、Zod refinements。
- **风格:**数据驱动(表格测试)。
// spec.test.ts
import { MyTaskInputSchema } from "./spec";
const invalidCases = [
{ val: "../etc/passwd", err: "No traversal allowed" },
{ val: "", err: "min(1)" },
];
test.each(invalidCases)("validates paths", ({ val, err }) => {
const result = MyTaskInputSchema.safeParse({ path: val });
expect(result.success).toBe(false);
});
B. 逻辑测试(Handler)
测试_厨师_。模拟外部依赖(fs、网络),并断言 Result Object。
- **重点:**业务逻辑流程、错误映射、成功状态。
- **风格:**模拟单元测试或场景运行器。
// handler.test.ts
import { MyTaskHandler } from "./handler";
import { vi } from "vitest"; // or jest
test("returns FILE_NOT_FOUND if path missing", async () => {
// MOCK
vi.mocked(fs.existsSync).mockReturnValue(false);
// EXECUTE
const handler = new MyTaskHandler();
const result = await handler.execute({ path: "/fake" });
// ASSERT (Check the Result Object)
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error.code).toBe("FILE_NOT_FOUND");
}
});
评论
支持 Markdown