Skip to content
GQLoom

Valibot

Valibot 是一个通用的数据验证库,考虑了捆绑包大小、类型安全和开发体验。

@gqloom/valibot 提供了 GQLoom 与 Valibot 的集成,以便将 Valibot Schema 编织成 GraphQL Schema。

安装

sh
npm i @gqloom/core valibot @gqloom/valibot
sh
pnpm add @gqloom/core valibot @gqloom/valibot
sh
yarn add @gqloom/core valibot @gqloom/valibot
sh
bun add @gqloom/core valibot @gqloom/valibot

定义简单标量

在 GQLoom 中,可以直接 Valibot Schema 作为丝线使用:

ts
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
.
number
(),
v
.
integer
()) // GraphQLInt

编织 | Weave

为了让 GQLoom 能正确地将 Valibot Schema 编织到 GraphQL Schema,我们在使用 weave 函数时,需要添加来自 @gqloom/valibotValibotWeaver

ts
import { 
weave
,
resolver
,
query
} from "@gqloom/core"
import {
ValibotWeaver
} from "@gqloom/valibot"
import * as
v
from "valibot"
export const
helloResolver
=
resolver
({
hello
:
query
(
v
.
string
(), () => "Hello, World!"),
}) export const
schema
=
weave
(
ValibotWeaver
,
helloResolver
)

定义对象

我们可以使用 Valibot 定义对象,并将其作为丝线使用:

ts
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
()),
})

名称和更多元数据

为对象定义名称

GQLoom 中,我们有多种方法来为对象定义名称。

使用 __typename 字面量

ts
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"。

ts
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 interfaceunion 时,必填的 __typename 将非常有用。

使用 collectNames

ts
import { 
collectNames
} from "@gqloom/core"
import * as
v
from "valibot"
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 函数接受一个对象,该对象的键是对象的名称,值是对象本身。

ts
import { 
collectNames
} from "@gqloom/core"
import * as
v
from "valibot"
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

ts
import { 
asObjectType
} from "@gqloom/valibot"
import * as
v
from "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 函数,我们可以为对象添加更多元数据,例如 descriptiondeprecationReasonextensions 等。

ts
import { 
asObjectType
} from "@gqloom/valibot"
import * as
v
from "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 中呈现:

graphql
"""A cute cat"""
type Cat {
  name: String!
  age: Int!
  loveFish: Boolean
}

我们还可以使用 asField 函数为字段添加元数据,例如 descriptiontype 等。

ts
import { 
asObjectType
,
asField
} from "@gqloom/valibot"
import {
GraphQLInt
} from "graphql"
import * as
v
from "valibot"
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 字段添加了 typedescription 元数据,最终得到如下 GraphQL Schema:

graphql
"""A cute cat"""
type Cat {
  name: String!

  """How old is the cat"""
  age: Int
  loveFish: Boolean
}

声明接口

我们可以使用 asObjectType 函数来声明接口,例如:

ts
import { 
asObjectType
} from "@gqloom/valibot"
import * as
v
from "valibot"
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 来省略字段,例如:

ts
import { 
asField
} from "@gqloom/valibot"
import * as
v
from "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:

graphql
type Dog {
  name: String
}

定义联合类型

在使用 Valibot 时,我们可以使用 variantunion 定义联合类型。

使用 variant

我们推荐使用 variant 来定义联合类型:

ts
import { 
asUnionType
} from "@gqloom/valibot"
import * as
v
from "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 来定义联合类型:

ts
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.picklistv.enum_ 定义枚举类型。

使用 picklist

通常,我们更推荐使用 v.picklist 来定义枚举类型:

ts
import { 
asEnumType
} from "@gqloom/valibot"
import * as
v
from "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_ 来定义枚举类型:

ts
import { 
asEnumType
} from "@gqloom/valibot"
import * as
v
from "valibot"
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-scalarsGraphQLDateTimeGraphQLJSONGraphQLJSONObject 标量,当遇到 dateanyrecord 类型时,我们将其映射到对应的 GraphQL 标量。

ts
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 函数中:

ts
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