Valibot
Valibot is the open source schema library for TypeScript with bundle size, type safety and developer experience in mind.
@gqloom/valibot provides integration of GQLoom with Valibot to weave Valibot Schema into GraphQL Schema.
Installation
npm i graphql @gqloom/core valibot @gqloom/valibotpnpm add graphql @gqloom/core valibot @gqloom/valibotyarn add graphql @gqloom/core valibot @gqloom/valibotbun add graphql @gqloom/core valibot @gqloom/valibotdeno add npm:graphql npm:@gqloom/core npm:valibot npm:@gqloom/valibotDefining simple scalars
In GQLoom, you can directly use Valibot schemas as silk:
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()) // GraphQLIntWeave
To ensure that GQLoom correctly weaves Valibot schemas into the GraphQL schema, we need to add the ValibotWeaver from @gqloom/valibot when using the weave function.
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)Defining objects
We can use Valibot to define objects and use them as silk:
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()),
})Names and more metadata
Defining names for objects
Note
Naming is optional in GQLoom, and GQLoom will automatically name objects based on operation names.
However, explicit naming is the recommended practice in most scenarios.
The recommended practice is to use the __typename literal to define a name for the object, for example:
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()),
})We can also use the required __typename literal to set a specific value, which is very useful when using GraphQL interface and union, for example:
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()),
})Using collectNames
We can use the collectNames function to define names for objects. The collectNames function accepts an object whose key is the name of the object and whose value is the object itself.
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 }) // Collect names for Cat, which will be presented in the GraphQL Schema after weaving.We can also use the collectNames function to define names for objects and deconstruct the returned objects into Cat and export them.
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()),
}),
})Using asObjectType
We can use the asObjectType function to create metadata and pass it into the Valibot pipeline to define a name for the object. The asObjectType function takes the complete GraphQL object type definition and returns metadata.
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" })
)Adding more metadata
With the asObjectType function, we can add more metadata to the object, such as description, deprecationReason, extensions and so on.
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",
})
)In the above code, we have added a description metadata to the Cat object which will be presented in the GraphQL Schema:
"""A cute cat"""
type Cat {
name: String!
age: Int!
loveFish: Boolean
}We can also use the asField function to add metadata to a field, such as description, type, and so on.
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",
extensions: {
complexity: 2,
},
})
),
loveFish: v.nullish(v.boolean()),
}),
asObjectType({
name: "Cat",
description: "A cute cat",
})
)In the above code, we added type and description metadata to the age field and ended up with the following GraphQL Schema:
"""A cute cat"""
type Cat {
name: String!
"""How old is the cat"""
age: Int
loveFish: Boolean
}Declaring Interfaces
We can use the asObjectType function to define interfaces, for example:
import * as v from "valibot"
import { asObjectType } from "@gqloom/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] })
)In the above code, we have created an interface Fruit using the asObjectType function and declared the Orange object as an implementation of the Fruit interface using the interfaces option.
Omitting Fields
We can also omit fields by setting type to null using the asField function, for example:
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 })),
})The following GraphQL Schema will be obtained:
type Dog {
name: String
}Defining Union Types
When using Valibot, we can define union types using variant or union.
Using variant
We recommend using variant to define union types:
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" })
)In the above code, we have created a union type using the variant function. In the case of Animal, it distinguishes the specific type by the __typename field.
Using union
We can also use union to define union types:
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 })In the above code, we have created a union type using the union function. In the case of Animal, it uses the resolveType function to differentiate between specific types. Here, if an animal likes fish, then it is a cat, otherwise it is a dog.
Defining Enumeration Types
We can define enumeration types using v.picklist or v.enum_.
Using picklist
In general, we prefer to use v.picklist to define enumerated types:
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>Using enum_
We can also use v.enum_ to define enumeration types:
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" },
},
})
)Custom Type Mappings
To accommodate more Valibot types, we can extend GQLoom to add more type mappings.
First we use ValibotWeaver.config to define the type mapping configuration.
Here we import the GraphQLDateTime, GraphQLJSON and GraphQLJSONObject scalars from [graphql-scalars] and map them to the matching GraphQL scalars when encountering the date, any and record types.
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
}
},
})Configurations are passed into the weave function when weaving the GraphQL Schema:
import { weave } from "@gqloom/core"
export const schema = weave(valibotWeaverConfig, helloResolver)Default Type Mappings
The following table lists the default mappings between Valibot types and GraphQL types in GQLoom:
| Valibot types | GraphQL types |
|---|---|
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.nanoid()) | GraphQLID |
v.pipe(v.string(), v.ulid()) | GraphQLID |
v.pipe(v.string(), v.uuid()) | GraphQLID |
v.union() | GraphQLUnionType |
v.variant() | GraphQLUnionType |