README ¶
Native Data Connector code generator
The NDC code generator provides a set of tools to develop data connectors quickly. It's suitable for developers who create connectors for business logic functions (or action in GraphQL Engine v2).
The generator is inspired by ndc-typescript-deno and ndc-nodejs-lambda that automatically infer TypeScript functions as NDC functions/procedures for use at runtime. It's possible to do this with Go via reflection. However, code generation is better for performance, type-safe, and no magic.
Installation
Download
Get a release here.
Build from source
To install with Go 1.21+:
go install github.com/hasura/ndc-sdk-go/cmd/hasura-ndc-go@latest
How to Use
❯ hasura-ndc-go -h
Usage: hasura-ndc-go <command>
Flags:
-h, --help Show context-sensitive help.
Commands:
new --name=STRING --module=STRING
Initialize an NDC connector boilerplate. For example:
hasura-ndc-go new -n example -m github.com/foo/example
update
Generate schema and implementation for the connector from functions.
test snapshots
Generate test snapshots.
Initialize connector project
The new
command generates a boilerplate project for connector development from template with the following folder structure:
functions
: the folder contains query and mutation functions. Theupdate
command will parse.go
files in this folder.types
: the folder contains reusable types such asRawConfiguration
,Configuration
andState
.connector.go
: parts of Connector methods, exceptGetSchema
,Query
andMutation
methods that will be generated by theupdate
command.main.go
: the main function that runs the connector CLI.go.mod
: the module file with required dependencies.README.md
: the index README file.Dockerfile
: the build template for Docker image.
The command requires names of the connector and module. By default, the tool creates a new folder with the connector name. If you want to customize the path or generate files in the current folder. use --output
(-o
) argument.
hasura-ndc-go new -n example -m github.com/foo/example -o .
Generate queries and mutations
The update
command parses code in the functions
folder, finds functions and types that are allowed to be exposed and generates the following files:
schema.generated.json
: the generated connector schema in JSON format.connector.generated.go
: implementGetSchema
,Query
andMutation
methods with exposed functions.
hasura-ndc-go update
How it works
Functions
Functions that are allowed to be exposed as queries or mutations need to have a Function
or Procedure
prefix in the name. For example:
// FunctionHello sends a hello message
func FunctionHello(ctx context.Context, state *types.State, arguments *HelloArguments) (*HelloResult, error)
// ProcedureCreateAuthor creates an author
func ProcedureCreateAuthor(ctx context.Context, state *types.State, arguments *CreateAuthorArguments) (*CreateAuthorResult, error)
Or use @function
or @procedure
comment tag:
[!IMPORTANT] The first word of the comment must be the function name. Without it the parser can not find the exact comment of the function:
// Hello sends a hello message
// @function
func Hello(ctx context.Context, state *types.State, arguments *HelloArguments) (*HelloResult, error)
// CreateAuthor creates an author
// @procedure
func CreateAuthor(ctx context.Context, state *types.State, arguments *CreateAuthorArguments) (*CreateAuthorResult, error)
// you also can set the alias after the tag:
// Foo a bar
// @function bar
func Foo(ctx context.Context, state *types.State) (*FooResult, error)
Function and Procedure names will be formatted to camelCase
by default.
The generator detects comments by the nearby code position. It isn't perfectly accurate in some use cases. Prefixing name in the function is highly recommended.
A function must have 2 (no argument) or 3 parameters. Context
and State
are always present as 2 first parameters. The result is a tuple with an expected output and error
.
Function is a type of Query and Procedure is a type of mutation. Collection is usually used for database queries so it isn't used for business logic.
Types
The tool only infers arguments and result types of exposed functions to generate object type schemas:
- Argument type must be a struct with serializable properties.
- Result type can be a scalar, slice, or struct.
Object Types
The tool can infer properties of the struct and generate Object Type schema. The json
tags will be read as properties name to be consistent with JSON Marshaller
and Unmarshaller
. For example, with the following type:
type CreateAuthorResult struct {
ID int `json:"id"`
Name string `json:"name"`
}
// auto-generated
// func (j CreateAuthorResult) ToMap() map[string]any {
// return map[string]any{
// "id": j.ID,
// "name": j.Name,
// }
// }
the schema will be:
{
"CreateAuthorResult": {
"fields": {
"id": {
"type": {
"name": "Int",
"type": "named"
}
},
"name": {
"type": {
"name": "String",
"type": "named"
}
}
}
}
}
Arguments
Arguments must be defined as struct types. The generator automatically infers argument types in functions to generate schemas and Encoder and Decoder methods. Value fields are treated as required and pointer fields are optional.
type HelloArguments struct {
Greeting string `json:"greeting"` // value argument will be required
Count *int `json:"count"` // pointer arguments are optional
}
Scalar Types
Supported types
The basic scalar types supported are:
Name | Native type | Description | JSON representation |
---|---|---|---|
Boolean | bool | A 8-bit signed integer with a minimum value of -2^7 and a maximum value of 2^7 - 1 | boolean |
String | string | String | string |
Int8 | int8, uin8 | A 8-bit signed integer with a minimum value of -2^7 and a maximum value of 2^7 - 1 | int8 |
Int16 | int16, uint16 | A 16-bit signed integer with a minimum value of -2^15 and a maximum value of 2^15 - 1 | int16 |
Int32 | int32, uint32 | A 32-bit signed integer with a minimum value of -2^31 and a maximum value of 2^31 - 1 | int32 |
Int64 | int64, uint64 | A 64-bit signed integer with a minimum value of -2^63 and a maximum value of 2^63 - 1 | int64 |
Bigint | scalar.BigInt | A 64-bit signed integer with a minimum value of -2^63 and a maximum value of 2^63 - 1 | string |
Float32 | float32 | An IEEE-754 single-precision floating-point number | float32 |
Float64 | float64 | An IEEE-754 double-precision floating-point number | float64 |
UUID | github.com/google/uuid.UUID | UUID string (8-4-4-4-12) | uuid |
Date | scalar.Date | ISO 8601 date | string |
TimestampTZ | time.Time | ISO 8601 timestamp-with-timezone | string |
Enum | Enumeration values | string | |
Bytes | scalar.Bytes | Base64-encoded bytes | string |
JSON | any, map[K]V | Arbitrary JSON | json |
RawJSON | json.RawMessage | Raw arbitrary JSON | json |
[!NOTE] We don't recommend to use the
RawJSON
scalar forfunction
arguments because the decoder will re-encode the value to[]byte
that isn't performance-wise.
Alias scalar types will be inferred to the origin type in the schema.
// the scalar type in schema is still a `String`.
type Text string
If you want to define a custom scalar type, the type name must have a Scalar
prefix or @scalar
tag in the comment. The generator doesn't care about the underlying type even if it is a struct.
[!IMPORTANT] Require the type name at the head of the comment if using comment tags.
type ScalarFoo struct {
bar string
}
// output: Foo
// auto-generated
// func (j ScalarFoo) ScalarName() string {
// return "Foo"
// }
// Tag a tag scalar
// @scalar
type Tag struct {
tag string
}
// output: Tag
// Foo a foo scalar
// @scalar Bar
type Foo struct {}
// output: Bar
For custom scalar, you must implement a method to decode any
value so its data can be set when resolving request query arguments. UnmarshalJSON
is also used when encoding results.
func (c *ScalarFoo) FromValue(value any) (err error) {
c.Bar, err = utils.DecodeString(value)
return
}
func (c *ScalarFoo) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
c.bar = s
return nil
}
Enum
You can define the enum type with @enum
comment tag. The data type must be an alias of string.
// @enum <values separated by commas>
//
// example:
//
// SomeEnum some enum
// @enum foo, bar
type SomeEnum string
The tool will help generate schema, constants and helper methods for it.
{
"scalar_types": {
"SomeEnum": {
"aggregate_functions": {},
"comparison_operators": {},
"representation": {
"one_of": ["foo", "bar"],
"type": "enum"
}
}
}
}
Limitation
Comments of types from third-party packages can't be parsed. If you want to use scalars from 3rd dependencies, you must wrap them with type alias.
Documentation
The tool parses comments of functions and types by the nearby code position to describe properties in the schema. For example:
// Creates an author
func ProcedureCreateAuthor(ctx context.Context, state *types.State, arguments *CreateAuthorArguments) (*CreateAuthorResult, error)
// {
// "name": "createAuthor",
// "description": "Creates an author",
// ...
// }
Example
See example/codegen.
Test Snapshots
The tool supports test snapshots generation for query and mutation requests and responses that are compatible with ndc-test replay command. See generated snapshots at the codegen example.
Usage: hasura-ndc-go test snapshots
Generate test snapshots.
Flags:
-h, --help Show context-sensitive help.
--log-level="info" Log level.
--schema=STRING NDC schema file path. Use either endpoint or schema path
--endpoint=STRING The endpoint of the connector. Use either endpoint or schema path
--dir=STRING The directory of test snapshots.
--depth=10 The selection depth of nested fields in result types.
--seed=SEED Using a fixed seed will produce the same output on every run.
--query=QUERY,... Specify individual queries to be generated. Separated by commas, or 'all' for all queries
--mutation=MUTATION,... Specify individual mutations to be generated. Separated by commas, or 'all' for all mutations
--strategy="none" Decide the strategy to do when the snapshot file exists.
The command accepts either a connector --endpoint
or a JSON --schema
file.
Endpoint
hasura-ndc-go test snapshots --endpoint http://localhost:8080 --dir testdata
NDC Schema
hasura-ndc-go test snapshots --schema schema.generated.json --dir testdata
Documentation ¶
There is no documentation for this package.