Skip to content

Commit

Permalink
docs: improved doc organization
Browse files Browse the repository at this point in the history
  • Loading branch information
fhur committed Oct 12, 2024
1 parent 4209cf9 commit 11f058b
Show file tree
Hide file tree
Showing 17 changed files with 84 additions and 75 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"format": "yarn prettier --write .",
"typecheck": "yarn nx run-many -t typecheck",
"test:ci": "yarn nx run-many -t test:ci",
"docs:start": "yarn --cwd ./packages/docs start",
"build": "yarn nx run-many -t build",
"ci": "yarn nx run-many -t typecheck test:ci build format",
"ci:force": "yarn nx run-many --skip-nx-cache -t typecheck test:ci build",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Query language
# Introduction

SynthQL comes with a very basic query language. Let's see a few examples:
SynthQL comes with a simple, but powerful query language. Let's see a few examples:

## Find user by ID

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ Effectively this means that it is impossible to share SQL fragments between quer

SynthQL is designed for composition and lets you achieve this in several ways. Let's see a few examples:

## Defining views
## Defining fragments

The first step towards reusable queries is to be able to give a name to a table + columns. I call these `views` and they can be defined as follows
The first step towards reusable queries is to be able to give a name to a table + columns. I call these `fragments` and they can be defined as follows

```ts
// A view over the pets table
Expand Down
12 changes: 12 additions & 0 deletions packages/docs/docs/200-query-language/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Query language

## Introduction
Learn the basics of the SynthQL query language. [Read more](./query-language/introduction).

## Examples
Looking for examples? Checkout the [examples section](./query-language/examples).

## Query composition
SynthQL let's you build bigger queries from smaller ones. Learn about [query composition](./query-language/composition).


Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
---

# Security
# Introduction

Letting clients make arbitrary queries, even if read-only comes with a set of security challenges. SynthQL comes with built in mechanisms to implement robust authorization logic so you can limit what queries clients can make.

Expand Down
13 changes: 13 additions & 0 deletions packages/docs/docs/300-security/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Security

## Introduction

Learn the basics of security in SynthQL. [Read more](./security/introduction).

## Query middleware

Learn how to use query middlewares to add additional security checks to your queries. [Read more](./security/query-middleware).

## Query permissions

Learn how to add permissions to your queries, to implement ACL-like functionality. [Read more](./security/query-permissions).
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

Query middlewares are functions that are executed before a query is executed. They can be used to add additional functionality to the query, such as logging, caching, or authentication.

In the context of security, query middlewares can be used to add additional checks on every query, or add additional filters to the query to limit the result set.
In the context of security, query middlewares can be used to add additional checks on every query, or limit the result set.

## Adding a middleware

You can add a middleware to the query engine using the `.use()` method.
You can add a middleware to the query engine as follows:

```ts
import { DB } from './db';
Expand Down
3 changes: 0 additions & 3 deletions packages/docs/docs/375-pagination.md

This file was deleted.

31 changes: 0 additions & 31 deletions packages/docs/docs/400-complex-queries.md

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,44 +1,44 @@
# Lazy queries
# Deferred queries

## The bigger the query, the longer the latency

One of the disadvantages of large query trees is that they result in proportionally longer latencies. The reason is simple: you have to wait for the entire query tree to load before you can send the response back to the client.

So the bigger the query, the longer the wait time.

To mitigate this issue, SynthQL lets you mark parts of your query tree as `lazy`. A lazy boundary will split your query into two and will tell the QueryEngine to flush results to the client in sequences.
To mitigate this issue, SynthQL lets you mark parts of your query tree with `.defer()`. A deferred boundary will split your query into two and will tell the QueryEngine to flush results to the client in sequences.

This feature is similar to [GraphQL's @defer directive](https://graphql.org/blog/2020-12-08-improving-latency-with-defer-and-stream-directives/).

## Example: Store with many products

Let's imagine that you have a store that can sell hundreds of different products. You need to implement a Store page in which you display the store's properties and after ccrolling a bit the user can see a list of all the products sold by the store.
Let's imagine that you have a store that can sell hundreds of different products. You need to implement a Store page in which you display the store's properties and after scrolling a bit the user can see a list of all the products sold by the store.

To improve the latency of this page, you can mark the `products` query as `.lazy()` as follows:
To improve the latency of this page, you can mark the `products` query as `.defer()` as follows:

```tsx
const products = from('products')
.column('id', 'price', 'name')
.lazy() // <======= this marks the products query as lazy
.defer() // <======= this marks the products query as deferred
.many();

const query = from('store').column('id', 'store_name', 'store_owner').include({
const query = from('store').column('store_name', 'store_owner').include({
products,
});

useSynthql(query);
```

Marking the `products` subquery as `lazy` will result in the query client first fetching the `store`, and then re-rendering the component when eventually the data from the `products` comes in.
Marking the `products` subquery as `defer` will result in the query client first fetching the `store`, and then re-rendering the component when eventually the data from the `products` comes in.

## What happens over the wire

When the `QueryEngine` executes a query, it will flush results 'early' to the client, whenever it sees a `lazy()` boundary. In this example this will result in two lines of JSON being sent to the client over the same HTTP connection, as seen below:
When the `QueryEngine` executes a query, it will flush results 'early' to the client, whenever it sees a `.defer()` boundary. In this example this will result in two lines of JSON being sent to the client over the same HTTP connection, as seen below:

```ts
```json
// First line of JSON
{id: '123', 'store_name': 'Fun Inc.', store_owner: 'Bob', products: {status:'pending'}}
{"store_name": "Fun Inc.", "store_owner": "Bob", "products": {"status":"pending"}}

// Once the products have loaded
{id: '123', 'store_name': 'Toys Inc.', store_owner: 'Bill', products: {status:'done', data: [...]}}
{"store_name": "Toys Inc.", "store_owner": "Bill", "products": {"status":"done", "data": [...]}}
```
3 changes: 0 additions & 3 deletions packages/docs/docs/600-handling-schema-changes.md

This file was deleted.

46 changes: 33 additions & 13 deletions packages/docs/docs/700-generating-types.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,44 @@
# Generating types

To generate a schema, create an instance of the `QueryEngine` and call the `generateSchema` function.
SynthQL comes with a CLI tool for generating types from your database. These types are used to guarantee end to end type safety when building queries.

```ts
import { QueryEngine } from "@synthql/backend";
To generate types from your database, run the following command:

```bash
npx @synthql/cli generate \

# A list of schemas to include, separated by spaces
--schemas public types \

const queryEngine = new QueryEngine({ ... })
# A list of tables to include, separated by spaces
--tables table1 table2 \

await queryEngine.generateSchema({
schemas: ["public","another_schema"],
out: "./src/generated/synthql/db.ts"
})
# The connection string to your database
# e.g. postgres://username:password@host:port/database
--url DATABASE_URL \

# The default schema to use when no schema is specified in a query
--defaultSchema luminovo
```

Once the schema has been generated, you can import the `from` function to start building type-safe queries.
You can also get help by running `npx @synthql/cli generate --help`.

```ts
import { from } from './src/generated/synthql/db.ts';
## synthql.config.json

function findDog(id: string) {
return from('dogs').where({ id }).one();
You can also generate types from a configuration file by running

```bash
npx @synthql/cli generate --configFile ./synthql.config.json --url DATABASE_URL
```

Here's an example configuration file:

```ts
// at ./synthql.config.json
{
"$schema": "https://synthql.dev/schemas/synthql.config.json",
"schemas": ["public"],
"tables": ["table1", "table2"],
"defaultSchema": "luminovo"
}
```
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Custom Query Executors
# Custom query providers

Although SynthQL provides great support for fetching data from your database, not all data comes from databases. Custom query executors let you execute parts of a query tree using a custom executor function.
While SynthQL is designed for database queries, it can also work with other data sources. Custom query providers allow you to use specific functions to fetch data from non-database sources as part of your query.

This can be used to fetch data from a source other than your database, such as a REST endpoint, a file or any other data source you can imagine.
This can be used to fetch data from a REST endpoint, a file or any other data source you can imagine.

## How can I configure a custom executor
## How can I configure a custom provider

When constructing a `QueryEngine` you may pass a list of `executors`. In this example we're configuring a custom executor for the `rotten_tomatoes_rating` table.
When constructing a `QueryEngine` you may pass a list of `providers`. In this example we're configuring a custom provider for the `rotten_tomatoes_rating` table.

```ts
import { QueryProvider } from "@synthql/backend";
Expand Down
4 changes: 2 additions & 2 deletions scripts/compile-executable-examples.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ async function main() {
);
const outputFilePath = path.join(
__dirname,
'../packages/docs/docs',
'350-examples.md',
'../packages/docs/docs/200-query-language',
'100-examples.md',
);
fs.writeFileSync(outputFilePath, markdown);
console.log(`Markdown generated at ${outputFilePath}`);
Expand Down

0 comments on commit 11f058b

Please sign in to comment.