Skip to content
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

Add document for using v2 #59

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
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
329 changes: 203 additions & 126 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,10 @@

## What is Outerbase SDK?

Outerbase SDK is a way to interact with your database in a SQL-like manner. This library contains the following primary features:
The Outerbase SDK is like a building block for creating custom database GUIs. It standardizes the connection interface, making it straightforward and easy to use.

- [**Query Builder**](#chaining-query-operations): Execute queries on your database easily.
- [**Saved Queries**](#run-saved-outerbase-queries): Run any saved queries from Outerbase in one line.
- [**Database Model Generator**](#generate-models-from-your-database): Create Typescript models from your database schema.

## Usage

### Install with a package manager
## Install with a package manager

**npm**
```
Expand All @@ -41,171 +36,253 @@ npm i @outerbase/sdk
pnpm add @outerbase/sdk
```

### Initialize a connection to your database

This library currently supports connecting to Outerbase connections, which supports **Postgres**, **MySQL**, **SQLite**, **SQL Server**, **Clickhouse** and more with direct integrations with platforms such as [DigitalOcean](https://digitalocean.com), [Neon](https://neon.tech), and [Turso](https://turso.tech).
## Connection


First we start by creating a connection object which is intended to be extensible where contributors can create a variety of connection types to other databases or additional third party tools to interact with. In this example we use the included `OuterbaseConnection` class.

With a connection object instantiated we can create a new database instance to interact with that connection interface.
The Outerbase SDK supports Postgres, MySQL, SQLite, Motherduck, BigQuery, and MongoDB. It wraps, not manages, your connections, providing a unified interface for easy interaction.

```ts
import { Outerbase, OuterbaseConnection } from '@outerbase/sdk'
#### Postgres

// ...
```typescript
import { PostgreSQLConnection } from "@outerbase/sdk";
import { Client } from 'pg';

const connection: OuterbaseConnection = new OuterbaseConnection('INSERT_API_TOKEN');
const db = Outerbase(connection);
const db = new PostgreSQLConnection(
new PgClient({
host: "localhost",
user: "postgres",
password: "postgres",
database: "postgres",
port: 5432
})
);
```

#### How to create an Outerbase Connection token
#### MySQL

```typescript
import { MySQLConnection } from "@outerbase/sdk";
import { createConnection } from 'mysql2';

const db = new MySQLConnection(
createConnection({
host: "localhost",
user: "root",
password: "123456",
database: "chinook",
port: 3306,
})
);
```
## Connection Interface

When using the `OuterbaseConnection` class, you are required to provide an API token from Outerbase.
### Get Schema

1. Create an account on [Outerbase](https://app.outerbase.com/)
2. Attach the database you want to use
3. Open your Base and click on _Base Settings_ on the left menu
4. Select the _General_ section
5. Underneath the _API Token_ section you will see a button to "Generate API Key". Click that and copy your API token to use it in declaring a new `OuterbaseConnection` object.
```typescript
import { PostgreSQLConnection } from "@outerbase/sdk";
import { Client } from 'pg';

### Chaining query operations
const db = new PostgreSQLConnection(new PgClient({...}));
await db.connect();

Instead of writing SQL directly in your code you can chain commands together that create simple and complex SQL queries for you.
// Get database schema
await db.fetchDatabaseSchema();

After you construct the series of SQL-like operations you intend to execute, you should end it by calling the `.query()` function call which will send the request to the database for exection.
await db.disconnect();
```

#### Select data from database
```ts
let { data, error } = await db
.selectFrom([
{
schema: 'public', // <- Optional
table: 'person',
columns: ['first_name', 'last_name', 'position', 'avatar'],
},
{ table: 'users', columns: ['email'] },
])
.leftJoin('users', equalsColumn('person.user_id', 'users.id'))
.where(isNot('first_name', null))
.where(equals('last_name', 'Doe'))
.where(equalsNumber('avatar', 0))
.limit(10)
.offset(0)
.orderBy(descending('first_name'))
.asClass(Person)
.query()
```

#### Insert data into a table
```ts
let { data } = await db
.insert({ first_name: 'John', last_name: 'Doe', position: 'Developer', avatar: 0 })
.into('person')
.returning(['id'])
.query();
### Create Table

```typescript
await db.createTable("public", "users", [
{ name: "id", definition: { type: "INTEGER", primaryKey: true } },
{ name: "name", definition: { type: "STRING" } },
{
name: "referral_id",
definition: {
type: "INTEGER",
references: { // Foreign Key
column: ["id"],
table: "users"
}
}
}
]);
```

#### Update data in a table
```ts
let { data } = await db
.update({ first_name: 'Johnny' })
.into('person')
.where(equals('last_name', 'Doe'))
.query();
### Drop Table

```typescript
await db.dropTable("public", "users");
```

#### Delete data from a table
```ts
let { data } = await db
.deleteFrom('person')
.where(equals('id', '1234'))
.query();
### Rename Table

```typescript
await db.renameTable("public", "users", "people");
```

> IMPORTANT! To prevent your code from performing actions you do not want to happen, such as deleting data, make sure the database user role you provide in Outerbase has restricted scopes.
### Add Column

### Executing raw SQL queries
### Drop Column

Executing raw SQL queries against your database is possible by passing a valid SQL statement into a database instance created by the library.
### Rename Column

```ts
let { data, error } = await db.queryRaw('SELECT * FROM person');
```
### Select

You can optionally pass in an array of parameters for sanitizing your SQL inputs.
```typescript
const { data, headers } = await db.select("public", "persons");
console.log(data);
/*
[
{ "id": 1, "name": "Brayden" },
{ "id": 2, "name": "Brandon" }
]
*/

```ts
let { data, error } = await db.queryRaw('SELECT * FROM person WHERE id=:id', { id: "123" });
console.log(headers);
/*
[
{ "name": "id", "displayName": "id" },
{ "name": "name", "displayName": "name" },
]
*/
```

### Run saved Outerbase queries

When you save queries to your Outerbase bases you can then directly execute those queries from this library. This enables you to make modifications to your query without having to alter and redeploy your codebase, and instead just make the modifications via Outerbase directly for convenience.
The select comes with several limited options

```ts
let { data, error } = await connection.runSavedQuery(
'ea72da5f-5f7a-4bab-9f72-ffffffffffff'
)
```typescript
const { data } = await db.select("public", "persons", {
where: { name: "id", operator: ">", value: 10 },
orderBy: "id",
limit: 20,
offset: 10
});
```

Note that this is an exported function directly from the `OuterbaseConnection` class.
### Delete

### Map results to class models
```typescript
await db.delete("public", "users", { id: 1 });
```

As you construct a SQL statement to be ran you can also pass in a class type you would like the output to attempt to map to by using `.asClass(ClassName)`. In the below example we pass in `Person` as the class type and the query builder will know to respond either as a single `Person` object or a `Person[]` array based on the contents of the response.
### Insert

```ts
let { data, error } = await db
.asClass(Person)
.queryRaw('SELECT * FROM person');
```typescript
await db.insert("public", "users", { id: 1, name: "Brayden" });
```

If your response cannot map to that class type based on property mismatch, you may not see any data being returned in your model.
### Update

### Generate models from your database
```typescript
await db.insert(
"public", "users",
{ name: "Brayden Junior" },
{ id: 1 }
);
```

> NOTE: This feature is still in early development.
### Raw

```typescript
const { data, headers } = await db.raw("SELECT 1 AS a, 2 AS a;");

console.log(data);
/*
Outerbase SQL detect header name collision and rename to other
[
{ "a": 1, "a1": 2 }
]
*/

console.log(headers);
/*
[
{ "name": "a", displayName: "a" },
{ "name": "a1", displayName: "a" }
]
*/
```

If your database is connected to Outerbase, then you can add a command to your `package.json` file in your project that can be executed to sync and download your database tables as Typescript models. These models are usable in your project and in many cases should map directly to the responses provided by the query builder library.
## Query Builder

To get started first add the following to your `package.json` file:
Our connection interface offers a streamlined, unified API across database drivers. For complex query construction, you can use `raw` SQL or wrap your connection with our query builder.

##### package.json
```ts
"scripts": {
"sync-models": "sync-database-models PATH=./folder/path/to/add/models API_KEY=outerbase_api_key"
}
```
```typescript
import { PostgreSQLConnection, Outerbase } from "@outerbase/sdk";
import { Client } from 'pg';

Based on your `API_KEY` value the command will know how to fetch your database schema from Outerbase. It will convert your schema into various Typescript models and save each file to the path you provide. To run this command and generate the files you can execute the command as it is written above by typing:
const db = new PostgreSQLConnection(new PgClient({...}));
await db.connect();

```
npm run sync-models
```
// Using our query builder
const qb = Outerbase(db);

The output produces a series of files, one per database table, that is a Typescript class for your queries to map their results to and you can access programatically easily. A sample output looks like the following where each property maps to a column in your database.
const builder = qb.createTable('persons')
.column('id', { type: 'SERIAL', primaryKey: true })
.column('first_name', { type: 'VARCHAR(50)' })
.column('last_name', { type: 'VARCHAR(50)' });

```ts
export interface PersonType {
firstName: string;
lastName: string;
position?: string;
avatar?: number;
}
// If you want to preview your SQL query
console.log(builder.toQuery());
// { query: 'CREATE TABLE IF NOT EXISTS "persons" ("id" SERIAL PRIMARY KEY, "first_name" VARCHAR(50), "last_name" VARCHAR(50))' }

export class Person implements PersonType {
firstName: string;
lastName: string;
position?: string;
avatar?: number;

constructor(data: any) {
this.firstName = data.first_name;
this.lastName = data.last_name;
this.position = data.position;
this.avatar = data.avatar;
}
}
// Or you can directly execute it
await builder.query();
```

More Examples:

```typescript
// Insert
await qb.insert({
last_name: 'Visal',
first_name: 'In'
}).into('persons').query();

// Select
const { data } = await qb
.select('id', 'name')
.from('users')
.where({ name: 'Brayden' });

const { data } = await qb
.select('id', 'name')
.from('users')
.where('name', 'LIKE', '%Bray%')
.limit(10)
.offset(5)
.query();

qb.select('id', 'name')
.from('users')
.where(
q.or(
q.where('age', '>', 18),
q.where('gender', '=', 'female'),
q.and(q.where('active', '=', 1), q.where('deleted', '=', 0))
)
)
.toQuery();

/*
{
'query': 'SELECT "id", "name" FROM "users" WHERE "age" > ? OR "gender" = ? OR ("active" = ? AND "deleted" = ?)'
'parameters': [18, 'female', 1, 0]
}
*/

// Update
await qb()
.update({ last_name: 'Visal', first_name: 'In' })
.into('persons').
.where({
id: 123,
active: 1,
}).query();
```

## Contributing
Expand Down