Valibot
Valibot 是一个通用的数据验证库,考虑了捆绑包大小、类型安全和开发体验。
@gqloom/valibot
提供了 GQLoom 与 Valibot 的集成,以便将 Valibot Schema 编织成 GraphQL Schema。
安装
npm install @gqloom/core valibot @gqloom/valibot
定义简单标量
在 GQLoom 中,可以直接Valibot Schema 作为丝线使用:
import * as v from "valibot"
const StringScalar = v.string() // GraphQLString
const BooleanScalar = v.boolean() // GraphQLBoolean
const FloatScalar = v.number() // GraphQLFloat
const IntScalar = v.pipe(v.nullable(v.number()), v.integer()) // GraphQLInt
编织 | Weave
为了让 GQLoom
能正确地将 Valibot Schema 编织到 GraphQL Schema,我们在使用 weave
函数时,需要添加来自 @gqloom/valibot
的 ValibotWeaver
。
import { resolver, query ,weave } from "@gqloom/core"
import { ValibotWeaver } from "@gqloom/valibot"
export const helloResolver = resolver({
hello: query(v.string(), () => "Hello, World!"),
})
export const schema = weave(ValibotWeaver, helloResolver)
定义对象
我们可以使用 Valibot 定义对象,并将其作为丝线使用:
import * as v from "valibot"
import { collectNames } from "@gqloom/core"
export const Cat = v.object({
name: v.string(),
age: v.pipe(v.number(), v.integer()),
loveFish: v.nullish(v.boolean()),
})
collectNames({ Cat })
名称和更多元数据
为对象定义名称
在 GQLoom
中,我们有多种方法来为对象定义名称。
使用__typename
字面量
import * as v from "valibot"
export const Cat = v.object({
__typename: v.nullish(v.literal("Cat")),
name: v.string(),
age: v.pipe(v.number(), v.integer()),
loveFish: v.nullish(v.boolean()),
})
在上面的代码中,我们使用 __typename
字面量来为对象定义名称。我们还将 __typename
字面量设置为 nullish
,这意味着 __typename
字段是可选的,如果存在,则必须为 "Cat"。
import * as v from "valibot"
export const Cat = v.object({
__typename: v.literal("Cat"),
name: v.string(),
age: v.pipe(v.number(), v.integer()),
loveFish: v.nullish(v.boolean()),
})
在上面的代码中,我们仍旧使用 __typename
字面量来为对象定义名称,但这次我们将 __typename
字面量设置为 "Cat",这意味着 __typename
字段是必须的,且必须为 "Cat"。当使用 GraphQL interface
和 union
时,必填的 __typename
将非常有用。
使用collectNames
import * as v from "valibot"
import { collectNames } from "@gqloom/core"
export const Cat = v.object({
name: v.string(),
age: v.pipe(v.number(), v.integer()),
loveFish: v.nullish(v.boolean()),
})
collectNames({ Cat }) // 为 Cat 收集名称,在编织后将呈现在 GraphQL Schema 中
在上面的代码中,我们使用 collectNames
函数来为对象定义名称。collectNames
函数接受一个对象,该对象的键是对象的名称,值是对象本身。
import * as v from "valibot"
import { collectNames } from "@gqloom/core"
export const { Cat } = collectNames({
Cat: v.object({
name: v.string(),
age: v.pipe(v.number(), v.integer()),
loveFish: v.nullish(v.boolean()),
}),
})
在上面的代码中,我们使用 collectNames
函数来为对象定义名称,并将返回的对象解构为 Cat
并导出。
使用asObjectType
import * as v from "valibot"
import { asObjectType } from "@gqloom/valibot"
export const Cat = v.pipe(
v.object({
name: v.string(),
age: v.pipe(v.number(), v.integer()),
loveFish: v.nullish(v.boolean()),
}),
asObjectType({ name: "Cat" })
)
在上面的代码中,我们使用 asObjectType
函数创建一个元数据并将其传入 Valibot
管道中来为对象定义名称。asObjectType
函数接受完整的 GraphQL 对象类型定义,并返回一个元数据。
添加更多元数据
通过 asObjectType
函数,我们可以为对象添加更多元数据,例如 description
、deprecationReason
、extensions
等。
import * as v from "valibot"
import { asObjectType } from "@gqloom/valibot"
export const Cat = v.pipe(
v.object({
name: v.string(),
age: v.pipe(v.number(), v.integer()),
loveFish: v.nullish(v.boolean()),
}),
asObjectType({
name: "Cat",
description: "A cute cat",
})
)
在上面的代码中,我们为 Cat
对象添加了一个 description
元数据,该元数据将在 GraphQL Schema 中呈现:
"""A cute cat"""
type Cat {
name: String!
age: Int!
loveFish: Boolean
}
我们还可以使用 asField
函数为字段添加元数据,例如 description
、type
等。
import * as v from "valibot"
import { asObjectType, asField } from "@gqloom/valibot"
import { GraphQLInt } from "graphql"
export const Cat = v.pipe(
v.object({
name: v.string(),
age: v.pipe(
v.number(),
asField({ type: GraphQLInt, description: "How old is the cat" })
),
loveFish: v.nullish(v.boolean()),
}),
asObjectType({
name: "Cat",
description: "A cute cat",
})
)
在上面的代码中,我们为 age
字段添加了 type
和 description
元数据,最终得到如下 GraphQL Schema:
"""A cute cat"""
type Cat {
name: String!
"""How old is the cat"""
age: Int
loveFish: Boolean
}
声明接口
我们还可以使用 asObjectType
函数来声明接口,例如:
import * as v from "valibot"
import { asObjectType } from "../src"
const Fruit = v.object({
__typename: v.nullish(v.literal("Fruit")),
name: v.string(),
color: v.string(),
prize: v.number(),
})
const Orange = v.pipe(
v.object({
__typename: v.nullish(v.literal("Orange")),
name: v.string(),
color: v.string(),
prize: v.number(),
}),
asObjectType({ interfaces: [Fruit] })
)
在上面的代码中,我们使用 asObjectType
函数创建了一个接口 Fruit
,并使用 interfaces
选项将 Orange
对象声明为 Fruit
接口的实现。
省略字段
我们还可以使用 asField
函数将 type
设置为 null
来省略字段,例如:
import * as v from "valibot"
import { asField } from "@gqloom/valibot"
const Dog = v.object({
__typename: v.nullish(v.literal("Dog")),
name: v.nullish(v.string()),
birthday: v.pipe(v.nullish(v.date()), asField({ type: null })),
})
将得到如下 GraphQL Schema:
type Dog {
name: String
}
定义联合类型
在使用 Valibot
时,我们可以使用 variant
或 union
定义联合类型。
使用 variant
我们推荐使用 variant
来定义联合类型:
import * as v from "valibot"
import { asUnionType } from "@gqloom/valibot"
const Cat = v.object({
__typename: v.literal("Cat"),
name: v.string(),
age: v.pipe(v.number(), v.integer()),
loveFish: v.nullish(v.boolean()),
})
const Dog = v.object({
__typename: v.literal("Dog"),
name: v.string(),
age: v.pipe(v.number(), v.integer()),
loveBone: v.nullish(v.boolean()),
})
const Animal = v.pipe(
v.variant("__typename", [Cat, Dog]),
asUnionType({ name: "Animal" })
)
在上面的代码中,我们使用 variant
函数创建了一个联合类型。对于 Animal
来说,它通过 __typename
字段来区分具体的类型。
使用 union
我们还可以使用 union
来定义联合类型:
import { collectNames } from "@gqloom/core"
import { asUnionType } from "@gqloom/valibot"
import * as v from "valibot"
const Cat = v.object({
name: v.string(),
age: v.pipe(v.number(), v.integer()),
loveFish: v.nullish(v.boolean()),
})
const Dog = v.object({
name: v.string(),
age: v.pipe(v.number(), v.integer()),
loveBone: v.nullish(v.boolean()),
})
const Animal = v.pipe(
v.union([Cat, Dog]),
asUnionType({
resolveType: (it) => (it.loveFish ? "Cat" : "Dog"),
})
)
collectNames({ Cat, Dog, Animal })
在上面的代码中,我们使用 union
函数创建了一个联合类型。对于 Animal
来说,它通过 resolveType
函数来区分具体的类型。
在这里,如果一个动物它喜欢鱼,那么它就是一只猫,否则就是一只狗。
定义枚举类型
我们可以使用 v.picklist
或 v.enum_
定义枚举类型。
使用 picklist
通常,我们更推荐使用 v.picklist
来定义枚举类型:
import * as v from "valibot"
import { asEnumType } from "@gqloom/valibot"
export const Fruit = v.pipe(
v.picklist(["apple", "banana", "orange"]),
asEnumType({
name: "Fruit",
valuesConfig: {
apple: { description: "red" },
banana: { description: "yellow" },
orange: { description: "orange" },
},
})
)
export type IFruit = v.InferOutput<typeof Fruit>
使用 enum_
我们也可以使用 v.enum_
来定义枚举类型:
export enum Fruit {
apple = "apple",
banana = "banana",
orange = "orange",
}
export const FruitE = v.pipe(
v.enum_(Fruit),
asEnumType({
name: "Fruit",
valuesConfig: {
apple: { description: "red" },
[Fruit.banana]: { description: "yellow" },
[Fruit.orange]: { description: "orange" },
},
})
)
自定义类型映射
为了适应更多的 Valibot 类型,我们可以拓展 GQLoom 为其添加更多的类型映射。
首先我们使用 ValibotWeaver.config
来定义类型映射的配置。这里我们导入来自 graphql-scalars 的 GraphQLDateTime
、GraphQLJSON
和 GraphQLJSONObject
标量,当遇到 date
、any
和 record
类型时,我们将其映射到对应的 GraphQL 标量。
import {
GraphQLDateTime,
GraphQLJSON,
GraphQLJSONObject,
} from "graphql-scalars"
import { ValibotWeaver } from "@gqloom/valibot"
export const valibotWeaverConfig = ValibotWeaver.config({
presetGraphQLType: (schema) => {
switch (schema.type) {
case "date":
return GraphQLDateTime
case "any":
return GraphQLJSON
case "record":
return GraphQLJSONObject
}
},
})
在编织 GraphQL Schema 时传入配置到 weave
函数中:
import { weave } from "@gqloom/core"
export const schema = weave(valibotWeaverConfig, HelloResolver)
默认类型映射
下表列出了 GQLoom 中 Valibot 类型与 GraphQL 类型之间的默认映射关系:
Valibot 类型 | GraphQL 类型 |
---|
v.array() | GraphQLList |
v.bigint() | GraphQLInt |
v.date() | GraphQLString |
v.enum_() | GraphQLEnumType |
v.picklist() | GraphQLEnumType |
v.literal(false) | GraphQLBoolean |
v.literal(0) | GraphQLFloat |
v.literal("") | GraphQLString |
v.looseObject() | GraphQLObjectType |
v.object() | GraphQLObjectType |
v.objectWithRest() | GraphQLObjectType |
v.strict_object() | GraphQLObjectType |
v.nonNullable() | GraphQLNonNull |
v.nonNullish() | GraphQLNonNull |
v.nonOptional() | GraphQLNonNull |
v.number() | GraphQLFloat |
v.pipe(v.number(), v.integer()) | GraphQLInt |
v.string() | GraphQLString |
v.pipe(v.string(), v.cuid2()) | GraphQLID |
v.pipe(v.string(), v.ulid()) | GraphQLID |
v.pipe(v.string(), v.uuid()) | GraphQLID |
v.union() | GraphQLUnionType |
v.variant() | GraphQLUnionType |