@grafbase/sdk v0.27.4
Get Started
Adding to an existing project:
npm install @grafbase/sdk --save-devInitializing a new project
grafbase init --config-format typescriptExample
The configuration should define the schema, exporting the config as default:
// g is a schema generator, config the final object to return
import { graph, config } from '@grafbase/sdk'
const g = graph.Standalone()
// types are generated with the `type` method,
// followed by the name and fields.
const profile = g.type('Profile', {
address: g.string()
})
// finally we export the default config
export default config({
graph: g
})When grafbase dev finds the above config from $PROJECT/grafbase/grafbase.config.ts, it genereates the SDL to $PROJECT/.grafbase/generated/schema/schema.graphql:
type Profile {
address: String!
}
type User @model {
name: String!
age: Int
profile: Profile
parent: User
}The above SDL is now used when starting the dev.
Federated Graphs
A federated graph can be defined as follows:
import { graph, config } from '@grafbase/sdk'
export default config({
graph: g.Federated()
})Types
Types are generated with the type method:
g.type('Profile', {
address: g.string()
})Enums
Enums can be generated with the enum method:
g.enum('Fruits', ['Apples', 'Oranges'])An enum can be used as a field type with the enumRef method:
const e = g.enum('Fruits', ['Apples', 'Oranges'])
g.type('User', {
favoriteFruit: g.enumRef(e)
})Queries and Mutations
Queries are generated with the query method, mutations with the mutation method:
g.query('greet', {
args: { name: g.string().optional() },
returns: g.string(),
resolver: 'hello'
})
const input = g.input('CheckoutSessionInput', { name: g.string() })
const output = g.type('CheckoutSessionOutput', { successful: g.boolean() })
g.mutation('checkout', {
args: { input: g.inputRef(input) },
returns: g.ref(output),
resolver: 'checkout'
})Unions
Unions can be done using the union method:
const user = g.type('User', {
name: g.string(),
age: g.int().optional()
})
const address = g.type('Address', {
street: g.string().optional()
})
g.union('UserOrAddress', { user, address })Interfaces
Interfaces can be generated using the interface method, and a type can be extended with an interface:
const produce = g.interface('Produce', {
name: g.string(),
quantity: g.int(),
price: g.float(),
nutrients: g.string().optional().list().optional()
})
g.type('Fruit', {
isSeedless: g.boolean().optional(),
ripenessIndicators: g.string().optional().list().optional()
}).implements(produce)Notice how one doesn't need to type the fields to the type: they are inferred to the final SDL from the interface definition.
Field generation
Fields are generated from the g object:
- ID:
g.id() - String:
g.string() - Int:
g.int() - Float:
g.float() - Boolean:
g.boolean() - Date:
g.date() - DateTime:
g.datetime() - Email:
g.email() - IPAddress:
g.ipAddress() - Timestamp:
g.timestamp() - URL:
g.url() - JSON:
g.json() - PhoneNumber:
g.phoneNumber()
Enum fields
// first greate an enum
const fruits = g.enumType('Fruits', ['Apples', 'Oranges'])
// then use it e.g. in a model
g.model('User', {
favoriteFruit: g.enum(fruits)
})Reference fields
Referencing a type is with the ref method:
const profile = g.type('Profile', {
address: g.string()
})
g.model('User', {
profile: g.ref(profile)
})Optional fields
By default the generated fields are required. To make them optional is with the optional method:
const user = g.type('User', {
posts: g.string().optional()
})List fields
List fields can be done with the list method:
const user = g.type('User', {
names: g.string().list()
})By default, the list or list items are required. To make the items nullable, call the optional method to the base type:
const user = g.type('User', {
names: g.string().optional().list()
})To make the list itself optional, call the optional method to the list type:
const user = g.type('User', {
names: g.string().list().optional()
})Unique
Unique field can be defined to certain types of fields with the unique method:
const user = g.type('User', {
name: g.string().unique()
})Additional unique scope can be given as a parameter:
const user = g.type('User', {
name: g.string().unique(['email']),
email: g.string()
})Length limit
Certain field types can have a limited length:
const user = g.type('User', {
name: g.string().length({ min: 1, max: 255 })
})Connectors
Connectors are created through the connector interface:
import { connector } from '../../src/index'OpenAPI
The OpenAPI connector can be created with the OpenAPI method:
const openai = connector.OpenAPI("OpenAI",
schema:
'https://raw.githubusercontent.com/openai/openai-openapi/master/openapi.yaml'
})
const stripe = connector.OpenAPI("Stripe", {
schema:
'https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json',
headers: (headers) => {
// used in client and introspection requests
headers.set('Authorization', `Bearer ${g.env('STRIPE_API_KEY')}`)
// used only in introspection requests
headers.introspection('foo', 'bar')
// forward headers from requests to datasource
headers.set('x-api-key', { forward: 'x-api-key' })
}
})Connectors can be added to the schema using g.datasource(), including an optional namespace:
g.datasource(stripe)
g.datasource(openai)GraphQL
The GraphQL connector can be created with the GraphQL method:
const contentful = connector.GraphQL('Contentful', {
url: g.env('CONTENTFUL_API_URL'),
headers: (headers) => {
headers.set('Authorization', `Bearer ${g.env('CONTENTFUL_API_KEY')}`)
headers.set('Method', 'POST')
headers.set('x-api-key', { forward: 'x-api-key' })
}
})
const github = connector.GraphQL('GitHub', {
url: 'https://api.github.com/graphql'
})Connectors can be added to the schema using g.datasource(), including an optional namespace:
g.datasource(contentful)
g.datasource(github)MongoDB
The MongoDB connector can be created with the MongoDB method:
const mongodb = connector.MongoDB('MongoDB', {
url: 'https://data.mongodb-api.com/app/data-test/endpoint/data/v1',
apiKey: 'SOME_KEY',
dataSource: 'data',
database: 'tables'
})
// Models must be added manually for this connector.
mongodb.model('User', { field: g.string() }).collection('users')
g.datasource(mongodb)Authentication
Auth providers can be created through the auth object.
import { auth } from '@grafbase/sdk'OpenID
Required fields:
issuer
Optional fields:
clientIdgroupsClaim
// first create the provider
const clerk = auth.OpenIDConnect({
issuer: g.env('ISSUER_URL')
})
// add it to the config with the rules
const cfg = config({
schema: g,
auth: {
providers: [clerk],
rules: (rules) => {
rules.private()
}
}
})JWT
Required fields:
issuersecret
Optional fields:
clientIdgroupsClaim
const derp = auth.JWT({
issuer: g.env('ISSUER_URL'),
secret: g.env('JWT_SECRET')
})JWKS
Required fields:
issuer
Optional fields:
clientIdgroupsClaimjwksEndpoint
A JWKS provider has to define either issuer or jwksEndpoint
const derp = auth.JWKS({
issuer: g.env('ISSUER_URL')
})Authorizer
Required fields:
name
const authorizer = auth.Authorizer({
name: 'custom-auth'
})The name maps the name of the file including a custom authentication function. For this example, there has to be a file implementing the authentication function in grafbase/auth/custom-auth.js.
Rule Definitions
Everywhere where one can define authentication rules, it happens through a lambda with a rules builder.
{
rules: (rules) => {
rules.private().read()
rules.groups(['admin', 'root']).delete()
}
}Global Rules
Global rules are defined through the auth definition in the configuration.
const clerk = auth.OpenIDConnect({
issuer: g.env('ISSUER_URL')
})
const cfg = config({
schema: g,
auth: {
providers: [clerk],
rules: (rules) => {
rules.private()
}
}
})Caching
Caching can be defined globally, per type or per field.
config({
schema: g,
cache: {
rules: [
{
types: 'Query',
maxAge: 60
},
{
types: ['GitHub', 'Strava'],
maxAge: 60,
staleWhileRevalidate: 60
},
{
types: [{ name: 'Query' }, { name: 'GitHub', fields: ['name'] }],
maxAge: 60
}
]
}
})
g.model('User', {
name: g.string().optional()
}).cache({
maxAge: 60,
staleWhileRevalidate: 60,
mutationInvalidation: 'entity'
})
g.type('User', {
name: g.string().optional()
}).cache({
maxAge: 60,
staleWhileRevalidate: 60,
mutationInvalidation: 'type'
})
g.model('User', {
name: g.string().cache({ maxAge: 60, staleWhileRevalidate: 60 })
})Extending Types
Types can be extended with extra queries, handled with resolvers.
To extend a type that is defined in the Grafbase schema, define the type first and extend it by using the type as the parameter:
const user = g.type('User', {
name: g.string()
})
g.extend(user, {
myField: {
args: { myArg: g.string() },
returns: g.string(),
resolver: 'file'
}
})Sometimes a type is not defined directly in the schema, but instead introspected from an external connector. In these cases passing a string as the first argument allows extending the type with custom queries. Keep in mind that in these cases it is not validated in compile-time if the type exist.
g.extend('StripeCustomer', {
myField: {
args: { myArg: g.string() },
returns: g.string(),
resolver: 'file'
}
})Environment variables
Node's process.env return nullable strings, which are a bit annoying to use in fields requiring non-nullable values. The schema has a helper g.env() that throws if the variable is not set, and returns a guaranteed string.
const github = connector.GraphQL('GitHub', {
url: 'https://api.github.com/graphql',
headers: (headers) => {
headers.set('Authorization', `Bearer ${g.env('GITHUB_TOKEN')}`)
}
})When working locally with Grafbase CLI you must set the environment variables inside grafbase/.env.
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago