// Package core provides the primary API to include and use Super Graph with your own code. // For detailed documentation visit https://supergraph.dev // // Example usage: /* package main import ( "database/sql" "fmt" "time" "github.com/dosco/super-graph/config" "github.com/dosco/super-graph/core" _ "github.com/jackc/pgx/v4/stdlib" ) func main() { db, err := sql.Open("pgx", "postgres://postgrs:@localhost:5432/example_db") if err != nil { log.Fatalf(err) } conf, err := config.NewConfig("./config") if err != nil { log.Fatalf(err) } sg, err = core.NewSuperGraph(conf, db) if err != nil { log.Fatalf(err) } query := ` query { posts { id title } }` res, err := sg.GraphQL(context.Background(), query, nil) if err != nil { log.Fatalf(err) } fmt.Println(string(res.Data)) } */ package core import ( "context" "crypto/sha256" "database/sql" "encoding/json" "fmt" "log" "github.com/dosco/super-graph/config" "github.com/dosco/super-graph/core/internal/allow" "github.com/dosco/super-graph/core/internal/crypto" "github.com/dosco/super-graph/core/internal/psql" "github.com/dosco/super-graph/core/internal/qcode" ) type contextkey int // Constants to set values on the context passed to the NewSuperGraph function const ( // Name of the authentication provider. Eg. google, github, etc UserIDProviderKey contextkey = iota // User ID value for authenticated users UserIDKey // User role if pre-defined UserRoleKey ) // SuperGraph struct is an instance of the Super Graph engine it holds all the required information like // datase schemas, relationships, etc that the GraphQL to SQL compiler would need to do it's job. type SuperGraph struct { conf *config.Config db *sql.DB schema *psql.DBSchema allowList *allow.List encKey [32]byte prepared map[string]*preparedItem getRole *sql.Stmt qc *qcode.Compiler pc *psql.Compiler } // NewConfig functions initializes config using a config.Core struct func NewConfig(core config.Core, configPath string, logger *log.Logger) (*config.Config, error) { c, err := config.NewConfigFrom(&config.Config{Core: core}, configPath, logger) if err != nil { return nil, err } return c, nil } // NewSuperGraph creates the SuperGraph struct, this involves querying the database to learn its // schemas and relationships func NewSuperGraph(conf *config.Config, db *sql.DB) (*SuperGraph, error) { if !conf.IsValid() { return nil, fmt.Errorf("invalid config") } sg := &SuperGraph{ conf: conf, db: db, } if err := sg.initCompilers(); err != nil { return nil, err } if err := sg.initAllowList(); err != nil { return nil, err } if err := sg.initPrepared(); err != nil { return nil, err } if len(conf.SecretKey) != 0 { sk := sha256.Sum256([]byte(conf.SecretKey)) conf.SecretKey = "" sg.encKey = sk } else { sg.encKey = crypto.NewEncryptionKey() } return sg, nil } // Result struct contains the output of the GraphQL function this includes resulting json from the // database query and any error information type Result struct { op qcode.QType name string sql string role string Error string `json:"message,omitempty"` Data json.RawMessage `json:"data,omitempty"` Extensions *extensions `json:"extensions,omitempty"` } // GraphQL function is called on the SuperGraph struct to convert the provided GraphQL query into an // SQL query and execute it on the database. In production mode prepared statements are directly used // and no query compiling takes places. // // In developer mode all names queries are saved into a file `allow.list` and in production mode only // queries from this file can be run. func (sg *SuperGraph) GraphQL(c context.Context, query string, vars json.RawMessage) (*Result, error) { ct := scontext{Context: c, sg: sg, query: query, vars: vars} if len(vars) <= 2 { ct.vars = nil } if keyExists(c, UserIDKey) { ct.role = "user" } else { ct.role = "anon" } ct.res.op = qcode.GetQType(query) ct.res.name = allow.QueryName(query) data, err := ct.execQuery() if err != nil { return &ct.res, err } ct.res.Data = json.RawMessage(data) return &ct.res, nil }