@ 剪贴板- 健康/ 合同核心
为剪贴板的合同共享Zod计划.
目录
安装
npm install @clipboard-health/contract-core
使用量
Zod 计划
逗号分隔阵列计划
commaSeparatedArray(itemSchema) 验证以逗号分隔的字符串或数组输入,并将其规范化为已键入数组。 为共享合同所设计,服务器接收逗号分隔的查询字符串,客户端通过打入的数组.
// z.input → string | string[]
// z.output → string[]
commaSeparatedArray(nonEmptyString).optional();
// z.input → string | ("CNA" | "RN" | "LVN")[]
// z.output → ("CNA" | "RN" | "LVN")[]
commaSeparatedArray(requiredEnum(["CNA", "RN", "LVN"]));
// z.input → string | string[]
// z.output → string[] (each validated as ObjectId)
commaSeparatedArray(objectId);
// z.input → string | (string | Date)[]
// z.output → Date[]
commaSeparatedArray(dateTimeSchema());
由所有合同核心计划 和enum助手组成 替换 z.preprocess(splitString, z.array(...)) 与适当的 z.input 输入 splitString 模式去除输入类型到 unknown) (中文(简体) ).
日期时间计划
dateTimeSchema() 验证严格的 ISO-8601 日期时间字符串并将其转换为 Date 对象。 与 z.coerce.date(),它拒绝像时代数字和只使用日期字符串这样的松散输入。 可用 .optional(), (中文(简体) ), .nullable()等在呼叫现场.
歧视性结合与倒退
discriminatedUnionWithFallback(discriminator, variants) 使用与 z.discriminatedUnion 并返回一个响应计划 对于一个联盟,它的变体 设定随着时间的推移增长, 所以读容忍变体 这个消费者尚不知道.
它只用于答复。 要求宣布自己 z.discriminatedUnion 在同一个变体上,一个未知变体是一个客户端错误而不是可以容忍的东西.
z.discriminatedUnion (请求) | discriminatedUnionWithFallback (答复) | |
|---|---|---|
| 未知歧视者 | 拒绝 | 崩溃后退 |
| 已知变体上未知的顶级密钥 | 脱去 | 脱去 |
| 缺少字段, 无效值 | 拒绝 | 拒绝 |
z.union 不能表达这一点。 它的倒置分枝匹配任何未知值, 所以一个已知的变体如果字段不好, 每个分枝都会失败, 并报告不透明 invalid_union 每个分支都有一个嵌入错误。 重写未知的歧义者首先意味着反应通常带有歧视性,并且只报告违法领域的问题,因此拒绝的反应可以从日志中诊断出来.
Enum 验证助手
这个软件包提供四个enum验证帮助,以覆盖不同的使用大小写:
退后验证( 有连接) :
requiredEnumWithFallback(values)- 无效的价值观被强迫ENUM_FALLBACK(单位:千美元)"UNRECOGNIZED_"),一种商业文本中性的哨兵,自动附在enum类型上.undefined无法验证 .optionalEnumWithFallback(values)- 无效的价值观被强迫ENUM_FALLBACK(单位:千美元)"UNRECOGNIZED_") (中文(简体) ).undefined通过时为undefined。 。 。 .
严格验证( 无倒计时 ) :
requiredEnum(values)- 包装z.enum()需要严格验证。 无效的值失败验证 .optionalEnum(values)- 包装z.enum()可选的严格验证。 无效的值失败验证, 但是undefined允许.
缩小类型 : 所有帮助者拒绝加宽 string[] 编译时的数组。 当通过一个预先宣布的变量时,使用 as const 以保留文字类型:
// Inline arrays work as-is
requiredEnum(["a", "b"]);
// Pre-declared variables require `as const`
const VALUES = ["a", "b"] as const;
requiredEnum(VALUES);
// Without `as const`, the type widens to string[] and is rejected
const widened = ["a", "b"];
requiredEnum(widened); // TS error
import {
apiErrors,
booleanString,
commaSeparatedArray,
dateTimeSchema,
discriminatedUnionWithFallback,
ENUM_FALLBACK,
nonEmptyString,
objectId,
optionalEnum,
optionalEnumWithFallback,
requiredEnum,
requiredEnumWithFallback,
uuid,
} from "@clipboard-health/contract-core";
import { z, type ZodError } from "zod";
function logError(error: unknown) {
console.error((error as ZodError).issues[0]!.message);
}
apiErrors.parse({
errors: [
{
code: "NotFound",
detail: "Resource 'b146a790-9ed1-499f-966d-6c4905dc667f' not found",
id: "6191a8a0-96ff-4d4b-8e0f-746a5ab215f9",
status: "404",
title: "Not Found",
},
],
});
// Comma-separated array examples
// Designed for shared contracts: server receives "CNA,RN" (string), client passes ["CNA", "RN"] (array).
// Both normalize to the same typed array output.
const workerTypes = commaSeparatedArray(requiredEnum(["CNA", "RN", "LVN"]));
// Server-side: comma-separated string from query params
const fromString = workerTypes.parse("CNA,RN");
// => ["CNA", "RN"]
console.log(fromString);
// Client-side: typed array
const fromArray = workerTypes.parse(["CNA", "LVN"]);
// => ["CNA", "LVN"]
console.log(fromArray);
// Composes with other schemas
const workerIds = commaSeparatedArray(objectId).optional();
const dates = commaSeparatedArray(dateTimeSchema());
// Works with .optional()
const noIds = workerIds.parse(undefined);
// => undefined
console.log(noIds);
const someDates = dates.parse("2026-01-01T00:00:00.000Z,2026-01-02T00:00:00.000Z");
// => [Date, Date]
console.log(someDates[0] instanceof Date); // true
booleanString.parse("true");
try {
booleanString.parse("invalid");
} catch (error) {
logError(error);
// => Invalid enum value. Expected 'true' | 'false', received 'invalid'
}
nonEmptyString.parse("hello");
try {
nonEmptyString.parse("");
} catch (error) {
logError(error);
// => String must contain at least 1 character(s)
}
// UUID validation examples
uuid.parse("b8d617bb-edef-4262-a6e3-6cc807fa1b26");
try {
uuid.parse("invalid");
} catch (error) {
logError(error);
// => Invalid UUID format
}
// DateTime schema examples
// Accepts ISO-8601 datetime strings and Date objects, normalizes to Date.
// Composable with .optional(), .nullable(), etc.
const createdAt = dateTimeSchema().parse("2026-03-15T10:30:00.000Z");
// => Date object
console.log(createdAt instanceof Date); // true
console.log(createdAt.toISOString()); // "2026-03-15T10:30:00.000Z"
// Date objects pass through as-is
const fromDate = dateTimeSchema().parse(new Date("2026-03-15T10:30:00.000Z"));
// => Date object
console.log(fromDate instanceof Date); // true
try {
dateTimeSchema().parse("2026-03-15"); // date-only string
} catch (error) {
logError(error);
// => Invalid datetime
}
try {
dateTimeSchema().parse(1_773_340_050_000); // epoch number
} catch (error) {
logError(error);
// => Invalid union
}
// Optional usage — compose at the call site
const schema = dateTimeSchema().optional();
const noDate = schema.parse(undefined);
// => undefined
console.log(noDate);
const someDate = schema.parse("2026-03-15T10:30:00.000Z");
// => Date object
console.log(someDate);
// Enum with fallback examples
// ENUM_FALLBACK is a neutral sentinel ("UNRECOGNIZED_") automatically appended
// to the enum type. Consumers cannot choose their own fallback value, preventing
// misuse where a business-meaningful value is treated as a default.
/* -- required -- */
const requiredStatusEnumSchema = requiredEnumWithFallback(["pending", "completed", "failed"]);
// type RequiredStatusEnum = "pending" | "completed" | "failed" | "UNRECOGNIZED_"
type RequiredStatusEnum = z.infer<typeof requiredStatusEnumSchema>;
const completedStatus: RequiredStatusEnum = requiredStatusEnumSchema.parse("completed");
// => "completed"
console.log(completedStatus);
const additionalStatus = requiredStatusEnumSchema.parse("additional");
// => "UNRECOGNIZED_" (ENUM_FALLBACK)
console.log(additionalStatus);
console.log(additionalStatus === ENUM_FALLBACK); // true
try {
requiredStatusEnumSchema.parse(undefined);
} catch (error) {
logError(error);
// => Validation error
}
/* -- optional -- */
const optionalStatusEnumSchema = optionalEnumWithFallback(["pending", "completed", "failed"]);
// type OptionalStatusEnum = "pending" | "completed" | "failed" | "UNRECOGNIZED_" | undefined
type OptionalStatusEnum = z.infer<typeof optionalStatusEnumSchema>;
const failedStatus: OptionalStatusEnum = optionalStatusEnumSchema.parse("failed");
// => "failed"
console.log(failedStatus);
const extraStatus = optionalStatusEnumSchema.parse("extra");
// => "UNRECOGNIZED_" (ENUM_FALLBACK)
console.log(extraStatus);
const undefinedStatus = optionalStatusEnumSchema.parse(undefined);
// => undefined
console.log(undefinedStatus);
// Strict enum examples (no fallback behavior)
// Use these when invalid values should fail validation rather than being coerced.
// Ideal for user types, authentication, and critical business logic.
/* -- required strict -- */
const requiredUserTypeSchema = requiredEnum(["admin", "worker", "workplace"]);
// type RequiredUserType = "admin" | "worker" | "workplace"
type RequiredUserType = z.infer<typeof requiredUserTypeSchema>;
const adminUser: RequiredUserType = requiredUserTypeSchema.parse("admin");
// => "admin"
console.log(adminUser);
try {
requiredUserTypeSchema.parse("invalid");
} catch (error) {
logError(error);
// => Invalid enum value. Expected 'admin' | 'worker' | 'workplace', received 'invalid'
}
try {
requiredUserTypeSchema.parse(undefined);
} catch (error) {
logError(error);
// => Required
}
/* -- optional strict -- */
const optionalUserTypeSchema = optionalEnum(["admin", "worker", "workplace"]);
// type OptionalUserType = "admin" | "worker" | "workplace" | undefined
type OptionalUserType = z.infer<typeof optionalUserTypeSchema>;
const workerUser: OptionalUserType = optionalUserTypeSchema.parse("worker");
// => "worker"
console.log(workerUser);
const noUserType = optionalUserTypeSchema.parse(undefined);
// => undefined
console.log(noUserType);
try {
optionalUserTypeSchema.parse("invalid");
} catch (error) {
logError(error);
// => Invalid enum value. Expected 'admin' | 'worker' | 'workplace', received 'invalid'
}
// Discriminated union with fallback examples
// Takes the same arguments as z.discriminatedUnion and returns a response schema
// that tolerates variants this consumer does not know yet.
const notificationVariants = [
z.object({
channel: z.literal("EMAIL"),
emailAddress: z.string().email(),
}),
z.object({
channel: z.literal("SMS"),
phoneNumber: nonEmptyString,
}),
] as const;
const notificationResponseSchema = discriminatedUnionWithFallback("channel", notificationVariants);
const emailNotification = notificationResponseSchema.parse({
channel: "EMAIL",
emailAddress: "[email protected]",
});
// => { channel: "EMAIL", emailAddress: "[email protected]" }
console.log(emailNotification);
// A variant this consumer does not recognize yet collapses to ENUM_FALLBACK.
const unrecognizedNotification = notificationResponseSchema.parse({
channel: "PUSH",
deviceToken: "abc123",
});
// => { channel: "UNRECOGNIZED_" }
console.log(unrecognizedNotification);
// A known variant with a bad field still fails, naming the field.
try {
notificationResponseSchema.parse({ channel: "EMAIL", emailAddress: 3 });
} catch (error) {
logError(error);
// => Expected string, received number
}
// Requests use z.discriminatedUnion directly: an unknown variant is a client
// error, not something to tolerate.
const notificationRequestSchema = z.discriminatedUnion("channel", [...notificationVariants]);
try {
notificationRequestSchema.parse({ channel: "PUSH", deviceToken: "abc123" });
} catch (error) {
logError(error);
// => Invalid discriminator value. Expected 'EMAIL' | 'SMS'
}
地方发展指挥部
见 package.json scripts 用于命令列表.