|
| 1 | +# Native Data Connector code generator |
| 2 | + |
| 3 | +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). |
| 4 | + |
| 5 | +The generator is inspired by [ndc-typescript-deno](https://github.com/hasura/ndc-typescript-deno) and [ndc-nodejs-lambda](https://github.com/hasura/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. |
| 6 | + |
| 7 | +## Installation |
| 8 | + |
| 9 | +- **Install from source**: To install with Go 1.19+: |
| 10 | + |
| 11 | +```bash |
| 12 | +go install github.com/hasura/ndc-sdk-go/cmd/ndc-go-sdk@latest |
| 13 | +``` |
| 14 | + |
| 15 | +## How to Use |
| 16 | + |
| 17 | +```bash |
| 18 | +❯ ndc-go-sdk -h |
| 19 | +Usage: ndc-go-sdk <command> |
| 20 | + |
| 21 | +Flags: |
| 22 | + -h, --help Show context-sensitive help. |
| 23 | + |
| 24 | +Commands: |
| 25 | + init --name=STRING --module=STRING |
| 26 | + Initialize an NDC connector boilerplate. For example: |
| 27 | + |
| 28 | + ndc-go-sdk init -n example -m github.com/foo/example |
| 29 | + |
| 30 | + generate |
| 31 | + Generate schema and implementation for the connector from functions. |
| 32 | +``` |
| 33 | + |
| 34 | +### Initialize connector project |
| 35 | + |
| 36 | +The `init` command generates a boilerplate project for connector development from [template](templates/new) with the following folder structure: |
| 37 | + |
| 38 | +- `functions`: the folder contains query and mutation functions. The `generate` command will parse `.go` files in this folder. |
| 39 | +- `types`: the folder contains reusable types such as `RawConfiguration`, `Configuration` and `State`. |
| 40 | +- `connector.go`: parts of Connector methods, except `GetSchema`, `Query` and `Mutation` methods that will be generated by the `generate` command. |
| 41 | +- `main.go`: the main function that runs the connector CLI. |
| 42 | +- `go.mod`: the module file with required dependencies. |
| 43 | +- `README.md`: the index README file. |
| 44 | +- `Dockerfile`: the build template for Docker image. |
| 45 | + |
| 46 | +The command requires names of 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. |
| 47 | + |
| 48 | +```bash |
| 49 | +ndc-go-sdk init -n example -m github.com/foo/example -o . |
| 50 | +``` |
| 51 | + |
| 52 | +### Generate queries and mutations |
| 53 | + |
| 54 | +The `generate` command parses code in the `functions` folder, finds functions and types that are allowed to expose and generates following files: |
| 55 | + |
| 56 | +- `schema.generated.json`: the generated connector schema in JSON format. |
| 57 | +- `connector.generated.go`: implement `GetSchema`, `Query` and `Mutation` methods with exposed functions. |
| 58 | + |
| 59 | +```bash |
| 60 | +ndc-go-sdk generate |
| 61 | +``` |
| 62 | + |
| 63 | +## How it works |
| 64 | + |
| 65 | +### Functions |
| 66 | + |
| 67 | +Functions which are allowed to expose as queries or mutations need to have `Function` or `Procedure` prefix in name. For example: |
| 68 | + |
| 69 | +```go |
| 70 | +// FunctionHello sends a hello message |
| 71 | +func FunctionHello(ctx context.Context, state *types.State, arguments *HelloArguments) (*HelloResult, error) |
| 72 | + |
| 73 | +// ProcedureCreateAuthor creates an author |
| 74 | +func ProcedureCreateAuthor(ctx context.Context, state *types.State, arguments *CreateAuthorArguments) (*CreateAuthorResult, error) |
| 75 | +``` |
| 76 | + |
| 77 | +Or use `@function` or `@procedure` comment tag: |
| 78 | + |
| 79 | +```go |
| 80 | +// Hello sends a hello message |
| 81 | +// @function |
| 82 | +func Hello(ctx context.Context, state *types.State, arguments *HelloArguments) (*HelloResult, error) |
| 83 | + |
| 84 | +// CreateAuthor creates an author |
| 85 | +// @procedure |
| 86 | +func CreateAuthor(ctx context.Context, state *types.State, arguments *CreateAuthorArguments) (*CreateAuthorResult, error) |
| 87 | + |
| 88 | +// you also can set the alias after the tag: |
| 89 | + |
| 90 | +// Foo a bar |
| 91 | +// @function bar |
| 92 | +func Foo(ctx context.Context, state *types.State) (*FooResult, error) |
| 93 | +``` |
| 94 | + |
| 95 | +Function and Procedure names will be formatted to `camelCase` by default. |
| 96 | + |
| 97 | +> The generator detects comments by the nearby code position. It isn't perfectly accurate in some use cases. Prefix name in function is highly recommended. |
| 98 | + |
| 99 | +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 a expected output and `error`. |
| 100 | + |
| 101 | +> [Function](https://hasura.github.io/ndc-spec/specification/schema/functions.html) is a type of Query and [Procedure](https://hasura.github.io/ndc-spec/specification/schema/procedures.html) is a type of mutation. [Collection](https://hasura.github.io/ndc-spec/specification/schema/collections.html) is usually used for database queries so it isn't used for business logic. |
| 102 | + |
| 103 | +### Types |
| 104 | + |
| 105 | +The tool only infers arguments and result types of exposed functions to generate object type schemas: |
| 106 | + |
| 107 | +- Argument type must be a struct with serializable properties. |
| 108 | +- Result type can be a scalar, slice or struct. |
| 109 | + |
| 110 | +#### Object Types |
| 111 | + |
| 112 | +The tool can infer properties of the struct and generate [Object Type](https://hasura.github.io/ndc-spec/specification/schema/object-types.html) schema. The `json` tags will be read as properties name to be consistent with `JSON Marshaller` and `Unmarshaller`. For example, with the following type: |
| 113 | + |
| 114 | +```go |
| 115 | +type CreateAuthorResult struct { |
| 116 | + ID int `json:"id"` |
| 117 | + Name string `json:"name"` |
| 118 | +} |
| 119 | + |
| 120 | +// auto generated |
| 121 | +// func (j CreateAuthorResult) ToMap() map[string]any { |
| 122 | +// return map[string]any{ |
| 123 | +// "id": j.ID, |
| 124 | +// "name": j.Name, |
| 125 | +// } |
| 126 | +// } |
| 127 | +``` |
| 128 | + |
| 129 | +the schema will be: |
| 130 | + |
| 131 | +```json |
| 132 | +{ |
| 133 | + "CreateAuthorResult": { |
| 134 | + "fields": { |
| 135 | + "id": { |
| 136 | + "type": { |
| 137 | + "name": "Int", |
| 138 | + "type": "named" |
| 139 | + } |
| 140 | + }, |
| 141 | + "name": { |
| 142 | + "type": { |
| 143 | + "name": "String", |
| 144 | + "type": "named" |
| 145 | + } |
| 146 | + } |
| 147 | + } |
| 148 | + } |
| 149 | +} |
| 150 | +``` |
| 151 | + |
| 152 | +#### Scalar Types |
| 153 | + |
| 154 | +**Supported types** |
| 155 | + |
| 156 | +The basic scalar types supported are: |
| 157 | + |
| 158 | +- `string` (NDC scalar type: `String`) |
| 159 | +- `int`, `int8`, `int16`, `int32`, `int64`, `uint`, `uint8`, `uint16`, `uint32`, `uint64` (NDC scalar type: `Int`) |
| 160 | +- `float32`, `float64` (NDC scalar type: `Float`) |
| 161 | +- `complex64`, `complex128` (NDC scalar type: `Complex`) |
| 162 | +- `bool` (NDC scalar type: `Boolean`) |
| 163 | +- `time.Time` (NDC scalar type: `DateTime`, represented as an [ISO formatted](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) string in JSON) |
| 164 | +- `time.Duration` (NDC scalar type: `Duration`, represented as a duration string in JSON) |
| 165 | +- `github.com/google/uuid.UUID` (NDC scalar type: `UUID`, represented as an UUID string in JSON) |
| 166 | + |
| 167 | +Alias scalar types will be inferred to the origin type in schema. |
| 168 | + |
| 169 | +```go |
| 170 | +// the scalar type in schema is still a `String`. |
| 171 | +type Text string |
| 172 | +``` |
| 173 | + |
| 174 | +If you want to define a custom scalar type, the type name must have a `Scalar` prefix or `@scalar` tag the comment. The generator doesn't care about the underlying type even it is a struct. |
| 175 | + |
| 176 | +```go |
| 177 | +type ScalarFoo struct { |
| 178 | + Bar string |
| 179 | +} |
| 180 | +// output: Foo |
| 181 | +// auto generated |
| 182 | +// func (j ScalarFoo) ScalarName() string { |
| 183 | +// return "Foo" |
| 184 | +// } |
| 185 | + |
| 186 | +// @scalar |
| 187 | +type Tag struct { |
| 188 | + tag string |
| 189 | +} |
| 190 | +// output: Tag |
| 191 | + |
| 192 | + |
| 193 | +// @scalar Bar |
| 194 | +type Foo struct {} |
| 195 | +// output: Bar |
| 196 | +``` |
| 197 | + |
| 198 | +> The generator detects comments by the nearby position. It isn't perfectly accurate in some use cases. Prefix name in function is highly recommended. |
| 199 | +
|
| 200 | +### Documentation |
| 201 | + |
| 202 | +The tool parses comments of functions and types by the nearby code position to description properties in the schema. For example: |
| 203 | + |
| 204 | +```go |
| 205 | +// Creates an author |
| 206 | +func ProcedureCreateAuthor(ctx context.Context, state *types.State, arguments *CreateAuthorArguments) (*CreateAuthorResult, error) |
| 207 | + |
| 208 | +// { |
| 209 | +// "name": "createAuthor", |
| 210 | +// "description": "Creates an author", |
| 211 | +// ... |
| 212 | +// } |
| 213 | +``` |
0 commit comments