Skip to content

Draft: Add auto-generated plugins to import data to Splitgraph from all supported data sources (Airbyte, Singer Taps, etc), and add export plugin to export from Splitgraph to Seafowl via Splitgraph GraphQL API #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b3f0c64
Copy relevant parts of SplitgraphImportCSVPlugin to base-import-plugi…
milesrichardson May 10, 2023
5ab75f8
Migrate `SplitgraphImportCSVPlugin` to extend base `SplitgraphImportP…
milesrichardson May 11, 2023
2111f28
Return instance of `Object.getPrototypeOf(this)` instead of storing `…
milesrichardson May 11, 2023
5b4ea36
Reduce plugin API surface by marking functions as `protected`
milesrichardson May 11, 2023
368f599
Upgrade `json-schema-to-typescript` to latest
milesrichardson May 12, 2023
3bb19ae
Name generated interfaces of plugin types PluginName + SchemaName
milesrichardson May 12, 2023
74dfe2a
Upgrade graphql-codegen packages to latest
milesrichardson May 15, 2023
57b0720
Make `PluginName` a generic type parameter
milesrichardson May 15, 2023
c525096
Add factory that returns class implementing ImportPlugin interface fo…
milesrichardson May 15, 2023
f52749b
Create `AirbyteGitHubImportPlugin` as proof-of-concept of auto-genera…
milesrichardson May 15, 2023
78af9cd
Add notes to CONTRIBUTING.md about how to use VSCode debugger with tests
milesrichardson May 15, 2023
3258aa1
Add integration test that ingests Seafowl GitHub repo with airbyte-gi…
milesrichardson May 16, 2023
0b99458
Auto-generate a PluginClass for every plugin in a `plugin.ts` file fo…
milesrichardson May 16, 2023
c06ea72
Add generic parameter `PluginName` to `ExportPlugin<PluginName>`
milesrichardson May 17, 2023
7909ebd
Refactor (readability): move public methods to top of `SplitgraphImpo…
milesrichardson May 17, 2023
fcdc15b
Refactor `ExportQueryPlugin` to inherit from new `SplitgraphExportPlu…
milesrichardson May 17, 2023
d1bd3b6
Add `SplitgraphExportToSeafowlPlugin` for exporting from Splitgraph t…
milesrichardson May 17, 2023
c2e2970
Add basic integration test for exporting query from Splitgraph to sel…
milesrichardson May 17, 2023
ae998c6
Refactor plugin names for consistency and accuracy
milesrichardson May 17, 2023
83a089d
Implement pollDeferredTask (WIP)
milesrichardson May 22, 2023
709d546
Make entire return type of pollDeferredTask generic, not just respons…
milesrichardson May 22, 2023
8ed0030
Cleanup/simplify types
milesrichardson May 22, 2023
8674498
Add test for completed deferred export task
milesrichardson May 22, 2023
ef315b2
Implement deferrable import tasks (covers all import plugins)
milesrichardson May 22, 2023
e9dc906
Implement deferrable `export-to-seafowl` plugin
milesrichardson May 22, 2023
4df66e0
Temporarily hardcode the two plugins used for the import/export demo,…
milesrichardson Jun 9, 2023
d16cb38
Fix test that was failing because Splitgraph export job completed too…
milesrichardson Jun 9, 2023
7ce363a
Update `export-to-seafowl` to conform to new API where exported table…
milesrichardson Jun 9, 2023
b19c0cc
Fix bug in `fingerprintQuery` when `window.crypto` is undefined
milesrichardson Jun 29, 2023
2212612
Remove stray debugger statement and fix return value of deferred expo…
milesrichardson Jun 29, 2023
3ec594a
Update `useSql` hook to accept query building function and abort signal
milesrichardson Jun 29, 2023
2ea2ae8
Fix tests (sort of: this accounts for hardcoded plugin initialization…
milesrichardson Jun 29, 2023
9496035
Bump version to `0.0.12` and prep for publish to `canary` tag
milesrichardson Jun 29, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ VITE_TEST_INTEGRATION=1
VITE_TEST_DDN_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
VITE_TEST_DDN_API_SECRET=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
VITE_TEST_SEAFOWL_SECRET=zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
VITE_TEST_GITHUB_PAT=uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
VITE_TEST_SEAFOWL_EXPORT_DEST_URL=https://demo.seafowl.cloud
# should match the username associated with the API_KEY for Splitgraph
VITE_TEST_SEAFOWL_EXPORT_DEST_DBNAME=miles
VITE_TEST_SEAFOWL_EXPORT_DEST_SECRET=tttttttttttttttttttttttttttttttt
```

Then simply append `--mode integration` flag to any variant of `yarn test` that
Expand All @@ -68,6 +73,24 @@ where a separate process like `mitmproxy` can intercept outbound requests:
yarn test-mitm --mode integration
```

### Run tests in VSCode "JavaScript Debug Terminal"

If using VSCode, the easiest debugging method is to open a "JavaScript Debug
Terminal" (which you can do via the command palette
"`Debug: JavaScript Debug Terminal`"). Put a `debugger;` statement where you
want to break, and then run vitest in single-threaded mode:

```bash
yarn test --single-thread
```

Or, if you also want to use mitmproxy (assumed to be listening on port `7979`),
then:

```bash
yarn test-mitm --single-thread
```

### Typecheck

We use `tsc` for typechecking, with the default solution file `tsconfig.json`
Expand Down Expand Up @@ -160,6 +183,9 @@ defined, in topological order. Therefore, to indicate a workspace is
publishable, make sure the `package.json` includes `scripts.version` and
`scripts.publish`.

If you want to publish a prerelease ("tag"), then add `--tag` to the
`publish-all` command, e.g. `yarn publish-all --tag canary --otp <your otp>`

Create deferred patch (0.0.x) changes (if necessary) in topological order

(NOTE: To update all versions immediately, change `-d` to `-i`, and then there
Expand Down
2 changes: 1 addition & 1 deletion packages/base-client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@madatdata/base-client",
"version": "0.0.11",
"version": "0.0.12",
"packageManager": "yarn@3.2.0",
"main": "index.ts",
"types": "./build/es2020/index.d.ts",
Expand Down
108 changes: 95 additions & 13 deletions packages/base-db/base-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,69 @@ import {

import type { HTTPStrategies } from "@madatdata/client-http";

export interface ImportPlugin extends Plugin {
export interface ImportPlugin<
PluginName extends string,
ConcreteSourceOptions extends object = any,
ConcreteDestOptions extends object = any
> extends Plugin {
__name: PluginName;
importData: (
sourceOptions: any,
destOptions: any
) => Promise<{ response: any | null; error: any | null; info?: any | null }>;
sourceOptions: ConcreteSourceOptions,
destOptions: ConcreteDestOptions,
importOptions?: { defer: boolean }
) => Promise<{
taskId?: string | null;
response: any | null;
error: any | null;
info?: any | null;
}>;
}

// interface ImportPluginWithOptions extends ImportPlugin {
// withOptions: WithOptions<ImportPlugin>;
// }

export interface ExportPlugin extends Plugin {
export interface ExportPlugin<
PluginName extends string,
ConcreteSourceOptions extends object = any,
ConcreteDestOptions extends object = any
> extends Plugin {
__name: PluginName;
exportData: (
sourceOptions: any,
destOptions: any
) => Promise<{ response: any | null; error: any | null; info?: any | null }>;
sourceOptions: ConcreteSourceOptions,
destOptions: ConcreteDestOptions,
exportOptions?: { defer: boolean }
) => Promise<{
response: any | null;
error: any | null;
info?: any | null;
taskId?: string | null;
taskIds?: any;
}>;
}

export interface DeferredTaskPlugin<
PluginName extends string,
// TODO: maybe should extend { __name: PluginName } to ensure finding
// correct task plugin by serialized deferred task

DeferredTaskResponse extends {
completed: boolean;
response: any | null;
error: any | null;
info: any | null;
} = {
completed: boolean;
response: Record<string, unknown>;
error: any;
info: any;
},
MemoizedDeferredTask extends object = any
> extends Plugin {
__name: PluginName;
pollDeferredTask: (
memoizedDeferredTask: MemoizedDeferredTask
) => Promise<DeferredTaskResponse>;
}

// interface ExportPluginWithOptions extends ExportPlugin {
Expand All @@ -51,6 +98,13 @@ export interface DbPluggableInterface<ConcretePluginList extends PluginList>
exportData: <MatchingPlugin extends ExportPluginFromList<ConcretePluginList>>(
...exportDataArgsForPlugin: Parameters<MatchingPlugin["exportData"]>
) => Promise<unknown>;
pollDeferredTask: <
MatchingPlugin extends DeferredTaskPluginFromList<ConcretePluginList>
>(
...pollDeferredTaskArgsForPlugin: Parameters<
MatchingPlugin["pollDeferredTask"]
>
) => Promise<unknown>;
}

export interface Db<ConcretePluginList extends PluginList> {
Expand All @@ -62,6 +116,12 @@ export interface Db<ConcretePluginList extends PluginList> {
pluginName: MatchingPlugin["__name"],
...rest: Parameters<MatchingPlugin["exportData"]>
) => Promise<unknown>;
pollDeferredTask: <
MatchingPlugin extends DeferredTaskPluginFromList<ConcretePluginList>
>(
pluginName: MatchingPlugin["__name"],
...rest: Parameters<MatchingPlugin["pollDeferredTask"]>
) => Promise<unknown>;
makeClient: <ImplementationSpecificClientOptions extends ClientOptions>(
makeClientForProtocol: (
wrappedOptions: ImplementationSpecificClientOptions
Expand All @@ -74,17 +134,25 @@ export type ImportPluginFromList<
ConcretePluginList extends PluginList,
PluginName extends ExtractPlugin<
ConcretePluginList,
ImportPlugin
ImportPlugin<string>
>["__name"] = string
> = ExtractPlugin<ConcretePluginList, ImportPlugin & { __name: PluginName }>;
> = ExtractPlugin<ConcretePluginList, ImportPlugin<PluginName>>;

export type ExportPluginFromList<
ConcretePluginList extends PluginList,
PluginName extends ExtractPlugin<
ConcretePluginList,
ExportPlugin
ExportPlugin<string>
>["__name"] = string
> = ExtractPlugin<ConcretePluginList, ExportPlugin & { __name: PluginName }>;
> = ExtractPlugin<ConcretePluginList, ExportPlugin<PluginName>>;

export type DeferredTaskPluginFromList<
ConcretePluginList extends PluginList,
PluginName extends ExtractPlugin<
ConcretePluginList,
DeferredTaskPlugin<string>
>["__name"] = string
> = ExtractPlugin<ConcretePluginList, DeferredTaskPlugin<PluginName>>;

export interface DbOptions<ConcretePluginList extends PluginList> {
plugins: ConcretePluginList;
Expand Down Expand Up @@ -173,6 +241,20 @@ export abstract class BaseDb<
...rest: Parameters<MatchingPlugin["exportData"]>
): Promise<unknown>;

abstract pollDeferredTask<
PluginName extends DeferredTaskPluginFromList<
ConcretePluginList,
string
>["__name"],
MatchingPlugin extends DeferredTaskPluginFromList<
ConcretePluginList,
PluginName
>
>(
pluginName: PluginName,
...rest: Parameters<MatchingPlugin["pollDeferredTask"]>
): Promise<unknown>;

/**
* Return a fingerprint and normalized query (used as input to the fingerprint)
* for a given SQL string. Default to SHA-256 and normalizing for HTTP headers.
Expand All @@ -187,7 +269,7 @@ export abstract class BaseDb<
// In vitest, really JSDOM, it's a bit of a mix between the two (window is available?)
// NOTE: Need to test how this will work in a browser bundle which we don't even have yet
const subtle = await (async () => {
if (!window?.crypto?.subtle) {
if (typeof window === "undefined" || !window?.crypto?.subtle) {
const { webcrypto } = await import("crypto");

if (webcrypto.subtle) {
Expand Down
2 changes: 1 addition & 1 deletion packages/base-db/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@madatdata/base-db",
"version": "0.0.11",
"version": "0.0.12",
"packageManager": "yarn@3.2.0",
"main": "index.ts",
"types": "./build/es2020/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/client-http/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@madatdata/client-http",
"version": "0.0.11",
"version": "0.0.12",
"packageManager": "yarn@3.2.0",
"main": "index.ts",
"types": "./build/es2020/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/client-postgres/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@madatdata/client-postgres",
"version": "0.0.11",
"version": "0.0.12",
"packageManager": "yarn@3.2.0",
"main": "index.ts",
"types": "./build/es2020/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@madatdata/core",
"version": "0.0.11",
"version": "0.0.12",
"packageManager": "yarn@3.2.0",
"main": "index.ts",
"types": "./build/es2020/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/splitgraph-seafowl-sync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe.skipIf(shouldSkipSeafowlTests() || shouldSkipIntegrationTests())(
const seafowl = createRealSeafowlDataContext();

const { response } = await splitgraph.db.exportData(
"exportQuery",
"export-query-to-file",
{
query: `SELECT a as int_val, string_agg(random()::text, '') as text_val
FROM generate_series(1, 5) a, generate_series(1, 50) b
Expand Down
88 changes: 82 additions & 6 deletions packages/core/splitgraph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ describe("makeSplitgraphHTTPContext", () => {
},
},
"plugins": [
_SplitgraphImportCSVPlugin {
SplitgraphImportCSVPlugin {
"__name": "csv",
"graphqlClient": SplitgraphGraphQLClient {
"graphqlClient": GraphQLClient {
Expand All @@ -310,8 +310,46 @@ describe("makeSplitgraphHTTPContext", () => {
},
"transformRequestHeaders": [Function],
},
_ExportQueryPlugin {
"__name": "exportQuery",
SplitgraphGeneratedImportPlugin {
"__name": "airbyte-github",
"graphqlClient": SplitgraphGraphQLClient {
"graphqlClient": GraphQLClient {
"options": {
"headers": [Function],
},
"url": "https://api.splitgraph.com/gql/cloud/unified/graphql",
},
"graphqlEndpoint": "https://api.splitgraph.com/gql/cloud/unified/graphql",
"transformRequestHeaders": [Function],
},
"graphqlEndpoint": "https://api.splitgraph.com/gql/cloud/unified/graphql",
"opts": {
"graphqlEndpoint": "https://api.splitgraph.com/gql/cloud/unified/graphql",
"transformRequestHeaders": [Function],
},
"transformRequestHeaders": [Function],
},
SplitgraphExportQueryToFilePlugin {
"__name": "export-query-to-file",
"graphqlClient": SplitgraphGraphQLClient {
"graphqlClient": GraphQLClient {
"options": {
"headers": [Function],
},
"url": "https://api.splitgraph.com/gql/cloud/unified/graphql",
},
"graphqlEndpoint": "https://api.splitgraph.com/gql/cloud/unified/graphql",
"transformRequestHeaders": [Function],
},
"graphqlEndpoint": "https://api.splitgraph.com/gql/cloud/unified/graphql",
"opts": {
"graphqlEndpoint": "https://api.splitgraph.com/gql/cloud/unified/graphql",
"transformRequestHeaders": [Function],
},
"transformRequestHeaders": [Function],
},
SplitgraphExportToSeafowlPlugin {
"__name": "export-to-seafowl",
"graphqlClient": SplitgraphGraphQLClient {
"graphqlClient": GraphQLClient {
"options": {
Expand All @@ -334,7 +372,7 @@ describe("makeSplitgraphHTTPContext", () => {
"plugins": PluginRegistry {
"hostContext": {},
"plugins": [
_SplitgraphImportCSVPlugin {
SplitgraphImportCSVPlugin {
"__name": "csv",
"graphqlClient": SplitgraphGraphQLClient {
"graphqlClient": GraphQLClient {
Expand All @@ -353,8 +391,46 @@ describe("makeSplitgraphHTTPContext", () => {
},
"transformRequestHeaders": [Function],
},
_ExportQueryPlugin {
"__name": "exportQuery",
SplitgraphGeneratedImportPlugin {
"__name": "airbyte-github",
"graphqlClient": SplitgraphGraphQLClient {
"graphqlClient": GraphQLClient {
"options": {
"headers": [Function],
},
"url": "https://api.splitgraph.com/gql/cloud/unified/graphql",
},
"graphqlEndpoint": "https://api.splitgraph.com/gql/cloud/unified/graphql",
"transformRequestHeaders": [Function],
},
"graphqlEndpoint": "https://api.splitgraph.com/gql/cloud/unified/graphql",
"opts": {
"graphqlEndpoint": "https://api.splitgraph.com/gql/cloud/unified/graphql",
"transformRequestHeaders": [Function],
},
"transformRequestHeaders": [Function],
},
SplitgraphExportQueryToFilePlugin {
"__name": "export-query-to-file",
"graphqlClient": SplitgraphGraphQLClient {
"graphqlClient": GraphQLClient {
"options": {
"headers": [Function],
},
"url": "https://api.splitgraph.com/gql/cloud/unified/graphql",
},
"graphqlEndpoint": "https://api.splitgraph.com/gql/cloud/unified/graphql",
"transformRequestHeaders": [Function],
},
"graphqlEndpoint": "https://api.splitgraph.com/gql/cloud/unified/graphql",
"opts": {
"graphqlEndpoint": "https://api.splitgraph.com/gql/cloud/unified/graphql",
"transformRequestHeaders": [Function],
},
"transformRequestHeaders": [Function],
},
SplitgraphExportToSeafowlPlugin {
"__name": "export-to-seafowl",
"graphqlClient": SplitgraphGraphQLClient {
"graphqlClient": GraphQLClient {
"options": {
Expand Down
Loading