-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Build your own connector. Temp home in docs while Academy is paused. (#…
…411) Co-authored-by: Rob Dominguez <[email protected]>
- Loading branch information
1 parent
6bb3ae2
commit 65401fe
Showing
31 changed files
with
2,339 additions
and
14 deletions.
There are no files selected for viewing
38 changes: 38 additions & 0 deletions
38
docs/connectors/build-your-own/00-get-started/01-clone.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
--- | ||
title: "Clone the Repo" | ||
metaTitle: 'Clone the Repo | Hasura DDN Data Connector Tutorial' | ||
metaDescription: 'Learn how to build a data connector in Typescript for Hasura DDN' | ||
--- | ||
|
||
Clone the [repo](https://github.com/hasura/ndc-typescript-learn-course) for this project to get started. | ||
|
||
```shell | ||
# If using GitHub with SSH | ||
git clone [email protected]:hasura/ndc-typescript-learn-course.git | ||
# OR if using GitHub with HTTPS | ||
git clone https://github.com/hasura/ndc-typescript-learn-course.git | ||
``` | ||
|
||
You'll see in the main branch two files: `finishedIndex.ts` and `index.ts`. The `finishedIndex.ts` file contains the | ||
completed implementation of the connector as reference, while the `index.ts` file is empty allowing you to follow along. | ||
|
||
Then install the dependencies: | ||
```shell | ||
npm install | ||
``` | ||
|
||
You can build and run the connector, when you need to, with: | ||
```shell | ||
npm run build && node dist/index.js serve --configuration . | ||
``` | ||
|
||
However, you can run nodemon to watch for changes and rebuild automatically: | ||
|
||
```shell | ||
npm run dev | ||
``` | ||
|
||
[//]: # (TODO: Cannot find more information about the configuration file creation and usage.) | ||
|
||
_Note: the configuration.json file is a pre-configured file which gives the connector information about the data | ||
source._ |
75 changes: 75 additions & 0 deletions
75
docs/connectors/build-your-own/00-get-started/02-basics.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
--- | ||
title: "Basic Setup" | ||
metaTitle: 'Basic Setup | Hasura DDN Data Connector Tutorial' | ||
metaDescription: 'Learn how to build a data connector in Typescript for Hasura DDN' | ||
--- | ||
|
||
Let's set up the scaffolding for our connector, and we'll see the first queries start to work. We'll also start to | ||
develop a test suite, and see our connector running in Hasura. | ||
|
||
For now, we'll just handle the most basic queries, but later, we'll start to fill in some of the gaps in our | ||
implementation, and see more queries return results correctly. | ||
|
||
[//]: # (TODO: This: "We'll also cover topics such as metrics, connector configuration, error reporting, and tracing. | ||
" is not implemented - possibly Phil Freeman will do this in the future?) | ||
|
||
The data source you'll be targeting is a SQLite database running on your local machine, and we'll be using the Hasura | ||
[TypeScript connector SDK](https://github.com/hasura/ndc-sdk-typescript). | ||
|
||
If you've cloned the repo in the previous step, you can follow along with the code in this tutorial. | ||
|
||
Let's start by following the [SDK guidelines](https://github.com/hasura/ndc-sdk-typescript) and use the `start` | ||
function which take a `connector` of type `Connector`. | ||
|
||
## Start | ||
|
||
In your `src/index.ts` file, add the following: | ||
|
||
```typescript | ||
const connector: Connector<Configuration, State> = {}; | ||
|
||
start(connector); | ||
``` | ||
|
||
We will also need some imports over the course of the tutorial. Paste these at the top of your index.ts file: | ||
|
||
```typescript | ||
import opentelemetry from "@opentelemetry/api"; | ||
import sqlite3 from "sqlite3"; | ||
import { readFile } from "fs/promises"; | ||
import { resolve } from "path"; | ||
import { Database, open } from "sqlite"; | ||
import { | ||
BadGateway, | ||
BadRequest, | ||
CapabilitiesResponse, | ||
CollectionInfo, | ||
ComparisonTarget, | ||
ComparisonValue, | ||
Connector, | ||
ConnectorError, | ||
ExplainResponse, | ||
Expression, | ||
ForeignKeyConstraint, | ||
InternalServerError, | ||
MutationRequest, | ||
MutationResponse, | ||
NotSupported, | ||
ObjectField, | ||
ObjectType, | ||
OrderByElement, | ||
Query, | ||
QueryRequest, | ||
QueryResponse, | ||
Relationship, | ||
RowFieldValue, | ||
ScalarType, | ||
SchemaResponse, | ||
start, | ||
} from "@hasura/ndc-sdk-typescript"; | ||
import { withActiveSpan } from "@hasura/ndc-sdk-typescript/instrumentation"; | ||
import { Counter, Registry } from "prom-client"; | ||
``` | ||
|
||
You'll notice that your IDE will complain about the `connector` object not having the correct type, and | ||
`Configuration, State` all being undefined. Let's fix that in the next section... |
46 changes: 46 additions & 0 deletions
46
docs/connectors/build-your-own/00-get-started/03-configuration-and-state.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
--- | ||
title: "Configuration and State" | ||
metaTitle: 'Configuration and State | Hasura DDN Data Connector Tutorial' | ||
metaDescription: 'Learn how to build a data connector in Typescript for Hasura DDN' | ||
--- | ||
|
||
We need to fill in implementations for each of the required functions, but we won't need all of these to work just yet. | ||
|
||
First, you'll see that we define two types: `Configuration`, and `State`. | ||
|
||
Let's define those now above the `connector` and `start` function: | ||
|
||
```typescript | ||
type Configuration = { | ||
tables: TableConfiguration[]; | ||
filename: string; | ||
}; | ||
|
||
type TableConfiguration = { | ||
tableName: string; | ||
columns: { [k: string]: Column }; | ||
}; | ||
|
||
type Column = {}; | ||
|
||
type State = { | ||
db: Database; | ||
}; | ||
``` | ||
|
||
`Configuration` is the type of the connector's configuration, which will be read from a directory on disk. By | ||
convention, this configuration should be enough to reproducibly determine the NDC schema, so for our SQLite connector, | ||
we configure the connector with a list of tables that we want to expose. Each table is defined by its name and a list of | ||
columns. Columns don't have any specific configuration yet, but we leave an empty object type here because we might want | ||
to capture things like column types later on. | ||
|
||
[//]: # (TODO: What does it mean to validate the configuration? What does it mean to have a validated configuration?) | ||
|
||
## State | ||
|
||
The `State` type is for things like connection pools, handles, or any non-serializable state that gets allocated on | ||
startup, and which lives for the lifetime of the connector. For our connector, we need to keep a handle to our sqlite | ||
database. | ||
|
||
Cool, so now that we've got our types defined, we can fill in the function definitions which the connector requires | ||
in order to interact with our SQLite database and Hasura DDN. Let's do that in the next step. |
80 changes: 80 additions & 0 deletions
80
docs/connectors/build-your-own/00-get-started/04-function-definitions.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
--- | ||
title: "Function Definitions" | ||
metaTitle: 'Function Definitions | Hasura DDN Data Connector Tutorial' | ||
metaDescription: 'Learn how to build a data connector in Typescript for Hasura DDN' | ||
--- | ||
|
||
Now let's fill in some function definitions, these are the functions required to provide to the connector to satisfy | ||
the Hasura connector specification, and we'll be implementing them as we go through the course. | ||
|
||
Copy and paste the following required functions into the `src/index.ts` file. Note that the amended `connector` is | ||
also included at the bottom, overwriting the previous connector definition with this which takes these functions as | ||
arguments. | ||
|
||
```typescript | ||
async function parseConfiguration(configurationDir: string): Promise<Configuration> { | ||
throw new Error("Function not implemented."); | ||
} | ||
|
||
async function tryInitState(configuration: Configuration, registry: Registry): Promise<State> { | ||
throw new Error("Function not implemented."); | ||
} | ||
|
||
function getCapabilities(configuration: Configuration): CapabilitiesResponse { | ||
throw new Error("Function not implemented."); | ||
} | ||
|
||
async function getSchema(configuration: Configuration): Promise<SchemaResponse> { | ||
throw new Error("Function not implemented."); | ||
} | ||
|
||
async function query(configuration: Configuration, state: State, request: QueryRequest): Promise<QueryResponse> { | ||
throw new Error("Function not implemented."); | ||
} | ||
|
||
async function fetchMetrics(configuration: Configuration, state: State): Promise<undefined> { | ||
throw new Error("Function not implemented."); | ||
} | ||
|
||
async function healthCheck(configuration: Configuration, state: State): Promise<undefined> { | ||
throw new Error("Function not implemented."); | ||
} | ||
|
||
async function queryExplain(configuration: Configuration, state: State, request: QueryRequest): Promise<ExplainResponse> { | ||
throw new Error("Function not implemented."); | ||
} | ||
|
||
async function mutationExplain(configuration: Configuration, state: State, request: MutationRequest): Promise<ExplainResponse> { | ||
throw new Error("Function not implemented."); | ||
} | ||
|
||
async function mutation(configuration: Configuration, state: State, request: MutationRequest): Promise<MutationResponse> { | ||
throw new Error("Function not implemented."); | ||
} | ||
``` | ||
|
||
Now we need to update the `connector` definition to include these functions. | ||
|
||
```typescript | ||
const connector: Connector<Configuration, State> = { | ||
parseConfiguration, | ||
tryInitState, | ||
getCapabilities, | ||
getSchema, | ||
query, | ||
fetchMetrics, | ||
healthCheck, | ||
queryExplain, | ||
mutationExplain, | ||
mutation | ||
}; | ||
``` | ||
|
||
Ok, moving on swiftly, for this course we will only need to implement the first five functions: | ||
- `parseConfiguration`: which reads the configuration from files on disk. | ||
- `tryInitState`: which initializes our database connection. | ||
- `getCapabilities`: which returns the NDC capabilities of our connector. | ||
- `getSchema`: which returns an NDC schema containing our tables and columns. | ||
- `query`: which actually responds to query requests. | ||
|
||
Let's do that now. |
60 changes: 60 additions & 0 deletions
60
docs/connectors/build-your-own/00-get-started/05-implementation.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
--- | ||
title: "Implementation" | ||
metaTitle: 'Implementation | Hasura DDN Data Connector Tutorial' | ||
metaDescription: 'Learn how to build a data connector in Typescript for Hasura DDN' | ||
--- | ||
|
||
Right now, we only need to implement five required functions: | ||
- `parseConfiguration`: which reads the configuration from files on disk. | ||
- `tryInitState`: which initializes our database connection. | ||
- `getCapabilities`: which returns the NDC capabilities of our connector. | ||
- `getSchema`: which returns an NDC schema containing our tables and columns. | ||
- `query`: which actually responds to query requests. | ||
|
||
We'll skip configuration validation entirely for now, and just read the raw configuration from a `configuration.json` | ||
file in the configuration directory: | ||
|
||
```typescript | ||
async function parseConfiguration(configurationDir: string): Promise<Configuration> { | ||
const configuration_file = resolve(configurationDir, 'configuration.json'); | ||
const configuration_data = await readFile(configuration_file); | ||
const configuration = JSON.parse(configuration_data.toString()); | ||
return { | ||
filename: resolve(configurationDir, 'database.db'), | ||
...configuration | ||
}; | ||
} | ||
``` | ||
|
||
To initialize our state, which in our case contains a connection to the database, we'll use the `open` function to | ||
open a connection to it, and store the resulting connection object in our state by returning it: | ||
|
||
```typescript | ||
async function tryInitState( | ||
configuration: Configuration, | ||
registry: Registry | ||
): Promise<State> { | ||
const db = await open({ | ||
filename: configuration.filename, | ||
driver: sqlite3.Database | ||
}); | ||
|
||
return { db }; | ||
} | ||
``` | ||
|
||
[//]: # (TODO: Link to the relevant part of the spec) | ||
Our capabilities response will be very simple, because we won't support many capabilities yet. We just return the | ||
version range of the specification that we are compatible with, and the basic `query` and `mutation` capabilities. | ||
|
||
```typescript | ||
function getCapabilities(configuration: Configuration): CapabilitiesResponse { | ||
return { | ||
version: "0.1.2", | ||
capabilities: { | ||
query: {}, | ||
mutation: {} | ||
} | ||
} | ||
} | ||
``` |
Oops, something went wrong.