Skip to content

Commit b40cb8d

Browse files
authored
feat: NDC generation tool (#7)
1 parent f033e66 commit b40cb8d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+3791
-195
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ go.work
2626
go.work.sum
2727

2828
.idea/
29+
_output/
2930
coverage.out

Diff for: Makefile

+23-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
VERSION ?= $(shell ./scripts/get-CalVer.sh)
2+
PLUGINS_BRANCH ?= master
3+
OUTPUT_DIR := _output
4+
15
.PHONY: typegen
26
typegen:
37
cd typegen && ./regenerate-schema.sh
@@ -14,4 +18,22 @@ test:
1418
# https://golangci-lint.run/usage/install
1519
.PHONY: lint
1620
lint:
17-
golangci-lint run
21+
golangci-lint run
22+
23+
# clean the output directory
24+
.PHONY: clean
25+
clean:
26+
rm -rf "$(OUTPUT_DIR)"
27+
28+
.PHONY: build-codegen
29+
build-codegen:
30+
go build -o _output/ndc-go-sdk ./cmd/ndc-go-sdk
31+
32+
# build the build-codegen cli for all given platform/arch
33+
.PHONY: build-codegen
34+
ci-build-codegen: export CGO_ENABLED=0
35+
ci-build-codegen: clean
36+
go run github.com/mitchellh/gox -ldflags '-X github.com/hasura/ndc-sdk-go/cmd/ndc-go-sdk/version.BuildVersion=$(VERSION) -s -w -extldflags "-static"' \
37+
-osarch="linux/amd64 darwin/amd64 windows/amd64 darwin/arm64" \
38+
-output="$(OUTPUT_DIR)/$(VERSION)/ndc-go-sdk-{{.OS}}-{{.Arch}}" \
39+
./cmd/ndc-go-sdk

Diff for: README.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@ This SDK is mostly analogous to the Rust SDK, except where necessary.
44

55
All functions of the Connector interface are analogous to their Rust counterparts.
66

7-
## Components
7+
## Features
88

99
- Connector HTTP server
1010
- Authentication
1111
- Observability with OpenTelemetry and Prometheus
1212

13+
## Quick start
14+
15+
Checkout the [generation tool](cmd/ndc-go-sdk) to quickly setup and develop data connectors.
16+
1317
## Using this SDK
1418

1519
The SDK exports a `Start` function, which takes a `connector` object, that is an object that implements the `Connector` interface defined in [connector/types.go](connector/types.go)

Diff for: cmd/ndc-go-sdk/README.md

+213
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
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

Comments
 (0)