From 11f058b47bd03374be463dd5a45a670d3d8f8be9 Mon Sep 17 00:00:00 2001 From: Fernando Hurtado Date: Sat, 12 Oct 2024 09:49:35 +0200 Subject: [PATCH] docs: improved doc organization --- package.json | 1 + .../000-introduction.md} | 4 +- .../100-examples.md} | 0 .../composition.md} | 4 +- .../docs/docs/200-query-language/index.md | 12 +++++ .../000-Introduction.md} | 2 +- packages/docs/docs/300-security/index.md | 13 ++++++ .../query-middleware.md | 4 +- .../query-permissions.md | 0 .../registered-queries.md | 0 packages/docs/docs/375-pagination.md | 3 -- packages/docs/docs/400-complex-queries.md | 31 ------------- ...azy-queries.md => 500-deferred-queries.md} | 22 ++++----- .../docs/docs/600-handling-schema-changes.md | 3 -- packages/docs/docs/700-generating-types.md | 46 +++++++++++++------ ...y-executors.md => 800-custom-providers.md} | 10 ++-- scripts/compile-executable-examples.cjs | 4 +- 17 files changed, 84 insertions(+), 75 deletions(-) rename packages/docs/docs/{300-query-language.md => 200-query-language/000-introduction.md} (91%) rename packages/docs/docs/{350-examples.md => 200-query-language/100-examples.md} (100%) rename packages/docs/docs/{350-query-composition.md => 200-query-language/composition.md} (92%) create mode 100644 packages/docs/docs/200-query-language/index.md rename packages/docs/docs/{200-security/index.md => 300-security/000-Introduction.md} (99%) create mode 100644 packages/docs/docs/300-security/index.md rename packages/docs/docs/{200-security => 300-security}/query-middleware.md (88%) rename packages/docs/docs/{200-security => 300-security}/query-permissions.md (100%) rename packages/docs/docs/{200-security => 300-security}/registered-queries.md (100%) delete mode 100644 packages/docs/docs/375-pagination.md delete mode 100644 packages/docs/docs/400-complex-queries.md rename packages/docs/docs/{500-lazy-queries.md => 500-deferred-queries.md} (53%) delete mode 100644 packages/docs/docs/600-handling-schema-changes.md rename packages/docs/docs/{800-custom-query-executors.md => 800-custom-providers.md} (61%) diff --git a/package.json b/package.json index 520c8b66..c7f0db88 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/docs/docs/300-query-language.md b/packages/docs/docs/200-query-language/000-introduction.md similarity index 91% rename from packages/docs/docs/300-query-language.md rename to packages/docs/docs/200-query-language/000-introduction.md index a6098af1..2777992f 100644 --- a/packages/docs/docs/300-query-language.md +++ b/packages/docs/docs/200-query-language/000-introduction.md @@ -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 diff --git a/packages/docs/docs/350-examples.md b/packages/docs/docs/200-query-language/100-examples.md similarity index 100% rename from packages/docs/docs/350-examples.md rename to packages/docs/docs/200-query-language/100-examples.md diff --git a/packages/docs/docs/350-query-composition.md b/packages/docs/docs/200-query-language/composition.md similarity index 92% rename from packages/docs/docs/350-query-composition.md rename to packages/docs/docs/200-query-language/composition.md index c23c2076..1054faa7 100644 --- a/packages/docs/docs/350-query-composition.md +++ b/packages/docs/docs/200-query-language/composition.md @@ -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 diff --git a/packages/docs/docs/200-query-language/index.md b/packages/docs/docs/200-query-language/index.md new file mode 100644 index 00000000..dbf439bd --- /dev/null +++ b/packages/docs/docs/200-query-language/index.md @@ -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). + + diff --git a/packages/docs/docs/200-security/index.md b/packages/docs/docs/300-security/000-Introduction.md similarity index 99% rename from packages/docs/docs/200-security/index.md rename to packages/docs/docs/300-security/000-Introduction.md index 72f834ec..f35acc01 100644 --- a/packages/docs/docs/200-security/index.md +++ b/packages/docs/docs/300-security/000-Introduction.md @@ -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. diff --git a/packages/docs/docs/300-security/index.md b/packages/docs/docs/300-security/index.md new file mode 100644 index 00000000..9067f687 --- /dev/null +++ b/packages/docs/docs/300-security/index.md @@ -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). diff --git a/packages/docs/docs/200-security/query-middleware.md b/packages/docs/docs/300-security/query-middleware.md similarity index 88% rename from packages/docs/docs/200-security/query-middleware.md rename to packages/docs/docs/300-security/query-middleware.md index 155ed974..bf485ce3 100644 --- a/packages/docs/docs/200-security/query-middleware.md +++ b/packages/docs/docs/300-security/query-middleware.md @@ -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'; diff --git a/packages/docs/docs/200-security/query-permissions.md b/packages/docs/docs/300-security/query-permissions.md similarity index 100% rename from packages/docs/docs/200-security/query-permissions.md rename to packages/docs/docs/300-security/query-permissions.md diff --git a/packages/docs/docs/200-security/registered-queries.md b/packages/docs/docs/300-security/registered-queries.md similarity index 100% rename from packages/docs/docs/200-security/registered-queries.md rename to packages/docs/docs/300-security/registered-queries.md diff --git a/packages/docs/docs/375-pagination.md b/packages/docs/docs/375-pagination.md deleted file mode 100644 index b62f40cc..00000000 --- a/packages/docs/docs/375-pagination.md +++ /dev/null @@ -1,3 +0,0 @@ -# Pagination - -TODO diff --git a/packages/docs/docs/400-complex-queries.md b/packages/docs/docs/400-complex-queries.md deleted file mode 100644 index a1d5e465..00000000 --- a/packages/docs/docs/400-complex-queries.md +++ /dev/null @@ -1,31 +0,0 @@ -# Complex queries - -## Having - -Find all users with more than 5 pets - -```ts -import { from } from '@/db'; - -const pet = from('pet').select('name', 'age'); - -const petsFromOwner = pet - .where({ - owner_id: col('users.id'), - }) - .many(); - -const users = from('users') - .select('id', 'email') - .include({ pets: petsFromOwner }) - .having(gt(count(pets), 5)); -``` - -```ts -import { from, Exp } from '@/db'; -import { sub, currentDate, interval, gte } from '@synthql/queries'; - -function inThePast3Months(exp: Exp) { - return gte(exp, sub(currentDate, interval('3 months'))); -} -``` diff --git a/packages/docs/docs/500-lazy-queries.md b/packages/docs/docs/500-deferred-queries.md similarity index 53% rename from packages/docs/docs/500-lazy-queries.md rename to packages/docs/docs/500-deferred-queries.md index a1bab693..c20418d4 100644 --- a/packages/docs/docs/500-lazy-queries.md +++ b/packages/docs/docs/500-deferred-queries.md @@ -1,4 +1,4 @@ -# Lazy queries +# Deferred queries ## The bigger the query, the longer the latency @@ -6,39 +6,39 @@ One of the disadvantages of large query trees is that they result in proportiona 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": [...]}} ``` diff --git a/packages/docs/docs/600-handling-schema-changes.md b/packages/docs/docs/600-handling-schema-changes.md deleted file mode 100644 index 9400b39a..00000000 --- a/packages/docs/docs/600-handling-schema-changes.md +++ /dev/null @@ -1,3 +0,0 @@ -# Schema changes - -## jsonb / json columns diff --git a/packages/docs/docs/700-generating-types.md b/packages/docs/docs/700-generating-types.md index 7370a6c1..56934378 100644 --- a/packages/docs/docs/700-generating-types.md +++ b/packages/docs/docs/700-generating-types.md @@ -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" } ``` diff --git a/packages/docs/docs/800-custom-query-executors.md b/packages/docs/docs/800-custom-providers.md similarity index 61% rename from packages/docs/docs/800-custom-query-executors.md rename to packages/docs/docs/800-custom-providers.md index 6f7e9b64..55277fd4 100644 --- a/packages/docs/docs/800-custom-query-executors.md +++ b/packages/docs/docs/800-custom-providers.md @@ -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"; diff --git a/scripts/compile-executable-examples.cjs b/scripts/compile-executable-examples.cjs index 4b71aca8..3230c7b6 100644 --- a/scripts/compile-executable-examples.cjs +++ b/scripts/compile-executable-examples.cjs @@ -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}`);