npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@grafbase/sdk

v0.24.0

Published

The Grafbase SDK

Downloads

10,922

Readme

Get Started

Adding to an existing project:

npm install @grafbase/sdk --save-dev

Initializing a new project

grafbase init --config-format typescript

Example

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:

  • clientId
  • groupsClaim
// 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:

  • issuer
  • secret

Optional fields:

  • clientId
  • groupsClaim
const derp = auth.JWT({
  issuer: g.env('ISSUER_URL'),
  secret: g.env('JWT_SECRET')
})

JWKS

Required fields:

  • issuer

Optional fields:

  • clientId
  • groupsClaim
  • jwksEndpoint

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.