feat(db): mesh data model — meshes, members, invites, audit log

- pgSchema "mesh" with 4 tables isolating the peer mesh domain
- Enums: visibility, transport, tier, role
- audit_log is metadata-only (E2E encryption enforced at broker/client)
- Cascade on mesh delete, soft-delete via archivedAt/revokedAt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-04-04 21:19:32 +01:00
commit d3163a5bff
1384 changed files with 314925 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
---
title: Database client
description: Use database client to interact with the database.
url: /docs/web/database/client
---
# Database client
The database client is an export of the Drizzle client. It is automatically typed by Drizzle based on the schema and is exposed as the db object from the database package (`@turbostarter/db`) in the monorepo.
This guide covers how to initialize the client and also basic operations, such as querying, creating, updating, and deleting records. To learn more about the Drizzle client, check out the [official documentation](https://orm.drizzle.team/kit-docs/overview).
## Initializing the client
Pass the validated `DATABASE_URL` to the client to initialize it.
```ts title="server.ts"
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import { env } from "../env";
const client = postgres(env.DATABASE_URL);
export const db = drizzle(client);
```
Now it's exported from the `@turbostarter/db` package and can be used across the codebase (server-side).
## Querying data
To query data, you can use the `db` object and its methods:
```ts title="query.ts"
import { eq } from "@turbostarter/db";
import { db } from "@turbostarter/db/server";
import { customer } from "@turbostarter/db/schema";
export const getCustomerByUserId = async (userId: string) => {
const [data] = await db
.select()
.from(customer)
.where(eq(customer.userId, userId));
return data ?? null;
};
```
<Cards className="sm:grid-cols-3">
<Card title="Select" description="orm.drizzle.team" href="https://orm.drizzle.team/docs/select" />
<Card title="Filters" description="orm.drizzle.team" href="https://orm.drizzle.team/docs/operators" />
<Card title="Joins" description="orm.drizzle.team" href="https://orm.drizzle.team/docs/joins" />
</Cards>
## Mutating data
You can use the exported utilities to mutate data. Insert, update or delete records in fast and fully type-safe way:
```ts title="mutation.ts"
import { eq } from "@turbostarter/db";
import { db } from "@turbostarter/db/server";
import { customer } from "@turbostarter/db/schema";
export const upsertCustomer = (data: InsertCustomer) => {
return db.insert(customer).values(data).onConflictDoUpdate({
target: customer.userId,
set: data,
});
};
```
<Cards className="sm:grid-cols-3">
<Card title="Insert" description="orm.drizzle.team" href="https://orm.drizzle.team/docs/insert" />
<Card title="Update" description="orm.drizzle.team" href="https://orm.drizzle.team/docs/update" />
<Card title="Delete" description="orm.drizzle.team" href="https://orm.drizzle.team/docs/delete" />
</Cards>

View File

@@ -0,0 +1,49 @@
---
title: Migrations
description: Migrate your changes to the database.
url: /docs/web/database/migrations
---
# Migrations
You have your schema in place, and you want to apply your changes to the database. TurboStarter provides you a convenient way to do so with pre-configured CLI commands.
## Generating migration
To generate a migration, from the schema you need to run the following command:
```bash
pnpm with-env turbo db:generate
```
This will create a new `.sql` file in the `migrations` directory.
<Callout>
Drizzle will also generate a `.json` representation of the migration in the `meta` directory, but it's for its internal purposes and you shouldn't need to touch it.
</Callout>
## Applying migrations
To apply the migrations to the database, you need to run the following command:
```bash
pnpm with-env pnpm --filter @turbostarter/db db:migrate
```
This will apply all the migrations that have not been applied yet. If any conflicts arise, you can resolve them by modifying the generated migration file.
## Pushing changes
To push changes directly to the database, you can use the following command:
```bash
pnpm with-env pnpm --filter @turbostarter/db db:push
```
This lets you push your schema changes directly to the database and omit managing SQL migration files.
<Callout type="warn" title="Use with caution!">
Pushing changes directly to the database (without using migrations) could be risky. Please be careful when using it; we recommend it only for local development and local databases.
[Read more about it in the Drizzle docs](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push).
</Callout>

View File

@@ -0,0 +1,83 @@
---
title: Overview
description: Get started with the database.
url: /docs/web/database/overview
---
# Overview
We're using [Drizzle ORM](https://orm.drizzle.team) to interact with the database. It basically adds a little layer of abstraction between our code and the database.
> If you know SQL, you know Drizzle.
For the database we're leveraging [PostgreSQL](https://www.postgresql.org), but you could use any other database that Drizzle ORM supports (basically any SQL database e.g. [MySQL](https://orm.drizzle.team/docs/get-started-mysql), [SQLite](https://orm.drizzle.team/docs/get-started-sqlite), etc.).
<Callout title="Why Drizzle ORM?">
Drizzle ORM is a powerful tool that allows you to interact with the database in a type-safe manner. It ships with **0** (!) dependencies and is designed to be fast and easy to use.
</Callout>
## Setup
To start interacting with the database you first need to ensure that your database service instance is up and running.
<Tabs items={["Local development", "Cloud instance"]}>
<Tab value="Local development">
For local development we recommend using the [Docker](https://hub.docker.com/_/postgres) container.
You can start the container with the following command:
```bash
pnpm services:setup
```
This will start all the services (including the database container) and initialize the database with the latest schema.
**Where is DATABASE\_URL?**
`DATABASE_URL` is a connection string that is used to connect to the database. When the command will finish it will be displayed in the console and setup to your environment variables.
</Tab>
<Tab value="Cloud instance">
You can also use a cloud instance of database (e.g. [Supabase](/docs/web/recipes/supabase), [Neon](https://neon.tech/), [Turso](https://turso.tech/), etc.), although it's not recommended for local development.
If you choose Supabase as your provider, follow the [Supabase recipe](/docs/web/recipes/supabase#configure-environment-variables) for details on configuring `DATABASE_URL` and running migrations.
**Where is DATABASE\_URL?**
It's available in your provider's project dashboard. You'll need to copy the connection string from there and add it to your `.env.local` file. The format will look something like:
* Neon: `postgresql://user:password@ep-xyz-123.region.aws.neon.tech/dbname`
* Turso: `libsql://your-db-xyz.turso.io`
Make sure to keep this URL secure and never commit it to version control.
</Tab>
</Tabs>
Then, you need to set `DATABASE_URL` environment variable in **root** `.env.local` file.
```dotenv title=".env.local"
# The database URL is used to connect to your database.
DATABASE_URL="postgresql://postgres:postgres@127.0.0.1:54322/postgres"
```
You're ready to go! 🥳
## Studio
TurboStarter provides you also with an interactive UI where you can explore your database and test queries called Studio.
To run the Studio, you can use the following command:
```bash
pnpm with-env pnpm --filter @turbostarter/db db:studio
```
This will start the Studio on [https://local.drizzle.studio](https://local.drizzle.studio).
![Drizzle Studio](/images/docs/db-studio.webp)
## Next steps
* [Update schema](/docs/web/database/schema) - learn about schema and how to update it.
* [Generate & run migrations](/docs/web/database/migrations) - migrate your changes to the database.
* [Initialize client](/docs/web/database/client) - initialize the database client and start interacting with the database.

View File

@@ -0,0 +1,75 @@
---
title: Schema
description: Learn about the database schema.
url: /docs/web/database/schema
---
# Schema
Creating a schema for your data is one of the primary tasks when building a new application.
You can find the schema of each table in `packages/db/src/db/schema` directory. The schema is basically organized by entity and each file is a separate table.
## Defining schema
The schema is defined using SQL-like utilities from [drizzle-orm](https://orm.drizzle.team/docs/sql-schema-declaration).
It supports all the SQL features, such as enums, indexes, foreign keys, extensions and more.
<Callout title="Code-first approach">
We're relying on the [code-first approach](https://orm.drizzle.team/docs/migrations), where we define the schema in code and then generate the SQL from it. That way we can approach full type-safety and the simplest flow for database updates and migrations.
</Callout>
## Example
Let's take a look at the `customer` table, where we store information about our customers.
```typescript title="customer.ts"
export const customer = pgTable("customer", {
id: text().primaryKey().$defaultFn(generateId),
userId: text()
.references(() => user.id, {
onDelete: "cascade",
})
.notNull()
.unique(),
customerId: text().notNull().unique(),
status: billingStatusEnum(),
plan: pricingPlanTypeEnum(),
createdAt: timestamp().notNull().defaultNow(),
updatedAt: timestamp()
.notNull()
.$onUpdate(() => new Date()),
});
```
We're using a few native SQL utilities here, such as:
* `pgTable` - a table definition.
* `primaryKey` - a primary key.
* `defaultFn` - a default function.
* `$onUpdate` - an on update function.
* `notNull` - a not null constraint.
* `defaultNow` - a default now function.
* `timestamp` - a timestamp.
* `text` - a text.
* `unique` - a unique constraint.
* `references` - a reference to another table.
What's more, Drizzle gives us the ability to export the TypeScript types for the table, which we can reuse e.g. for the API calls.
Also, we can use the drizzle extension [drizzle-zod](https://orm.drizzle.team/docs/zod) to generate the Zod schemas for the table.
```typescript title="customer.ts"
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
export const insertCustomerSchema = createInsertSchema(customer);
export const selectCustomerSchema = createSelectSchema(customer);
export const updateCustomerSchema = createUpdateSchema(customer);
export type InsertCustomer = z.infer<typeof insertCustomerSchema>;
export type SelectCustomer = z.infer<typeof selectCustomerSchema>;
export type UpdateCustomer = z.infer<typeof updateCustomerSchema>;
```
Then we can use the generated schemas in our API handlers and frontend forms to validate the data.