Skip to content
Merged
Show file tree
Hide file tree
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
81 changes: 43 additions & 38 deletions apps/web/content/docs/modules/database.mdx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
---
title: Database
description: Schemas, CRUD, custom endpoints, indexes, and query trees.
agent_summary: "Client: /database/{Schema}, /database/function/{name}; provision schemas via MCP; no client-side filtering — custom endpoints for filtered queries."
description: Schemas, CRUD, custom endpoints, indexes, query trees, and GitOps export.
agent_summary: "Client: /database/{Schema}, /database/function/{name}; provision schemas and custom endpoints via MCP; never filter collections client-side; introspection is Admin API only (not MCP)."
---

The **database** module is the data layer. Apps never connect to MongoDB or PostgreSQL directly — schemas define document shapes, CMS options expose CRUD routes, and custom endpoints serve filtered queries.
import DatabaseDeepDive from "@/mdx/deep-dives/database-deep-dive.mdx";

Your app needs structured data, but connecting to MongoDB or PostgreSQL directly bypasses Conduit auth, authorization, and routing. The **database** module is the data layer: you define **schemas**, enable **CMS CRUD** routes for simple access, and provision **custom endpoints** for every filtered query.

The most common mistake is calling `GET /database/{Schema}` and filtering in application code. That route runs `findMany({}, …)` — **there is no filter parameter**. Any WHERE clause, ownership scope, date range, or text search belongs in a provisioned custom endpoint at `/database/function/{name}`.

## Use cases

Expand All @@ -16,15 +20,19 @@ The **database** module is the data layer. Apps never connect to MongoDB or Post
},
{
title: "Filtered lists",
outcome: "Custom endpoints for WHERE clauses, search, and pagination",
outcome: "Custom endpoints with query trees for WHERE, search, and pagination",
},
{
title: "Multi-tenant records",
outcome: "Authorization-enabled schemas with scope on create",
},
{
title: "Extend auth User",
outcome: "Schema extensions add fields to module-owned schemas",
title: "Brownfield databases",
outcome: "Introspect existing collections via Admin API, finalize as CMS schemas",
},
{
title: "Config-as-code",
outcome: "Export schemas, extensions, and custom endpoints in GitOps state snapshots",
},
]}
/>
Expand All @@ -33,14 +41,16 @@ The **database** module is the data layer. Apps never connect to MongoDB or Post

<ModuleCapabilities
items={[
"Schema definitions",
"MongoDB & PostgreSQL",
"Schema definitions & extensions",
"CMS CRUD routes",
"Custom endpoints",
"Query trees",
"Custom endpoints & query trees",
"Comparison operators (eq, in, contains, …)",
"Indexes",
"Populate relations",
"Text search (like: true)",
"Schema extensions",
"Database introspection (Admin API)",
"GitOps export (schemas, extensions, endpoints)",
"GraphQL (via router)",
]}
/>
Expand Down Expand Up @@ -72,43 +82,29 @@ The **database** module is the data layer. Apps never connect to MongoDB or Post
## How it works

<ModuleDeepDive>
### CMS CRUD

When `conduitOptions.cms.enabled` and per-operation flags are set, the Client API exposes:

| Intent | HTTP | Path |
|--------|------|------|
| List (unfiltered) | GET | `/database/{Schema}?skip&limit&sort&populate&scope` |
| Get by id | GET | `/database/{Schema}/{id}` |
| Create | POST | `/database/{Schema}` |
| Update | PATCH | `/database/{Schema}/{id}` |
| Delete | DELETE | `/database/{Schema}/{id}` |

`GET /database/{Schema}` runs `findMany({}, …)` — **no filter parameter exists**.

### The iron rule

Any query needing WHERE clauses, `$in`, date ranges, text search, or ownership scoping must use a **provisioned custom endpoint** at `/database/function/{name}`. Never fetch a collection and `.filter()` in application code.

### Custom endpoints

Provision via MCP `post_database_customendpoints`. Operations map to HTTP methods (GET, POST, PUT, PATCH, DELETE). Query trees declare inputs, filters, populate, pagination (`paginated: true` requires `skip` + `limit`), and optional `like: true` text search.

On authorization-enabled schemas, custom endpoints **must** have `authentication: true` and accept optional `scope`.
<DatabaseDeepDive />
</ModuleDeepDive>

## Configure

Provision at dev/deploy time via MCP with `?modules=database`:
Enable the database module in deployment, then provision via MCP with `?modules=database`:

```text
post_database_schemas
patch_database_schemas_id
post_database_customendpoints
patch_database_customendpoints_id
post_database_schemas_id_indexes
patch_config_database
```

Key `conduitOptions` keys: `cms.enabled`, `cms.crudOperations.*.enabled`, `cms.crudOperations.*.authenticated`, `authorization.enabled`.
| Config key | Meaning |
|------------|---------|
| `readPreference` | MongoDB replica read preference (`primary`, `secondaryPreferred`, …) |
| `writeConcern` | MongoDB write concern (`1`, `majority`) |
| `readConcern` | MongoDB read concern (`local`, `majority`, …) |

Sequelize/PostgreSQL caveats: index creation via Admin API is limited, case-sensitive `$like` behavior differs by dialect, and partial-row updates only change provided columns.

## Client API

Expand All @@ -120,16 +116,25 @@ Key `conduitOptions` keys: `cms.enabled`, `cms.crudOperations.*.enabled`, `cms.c

## MCP

Enable with `?modules=database` in your MCP server URL.

| Tool | Purpose |
|------|---------|
| `post_database_schemas` | Create schema |
| `get_database_schemas` | List schemas |
| `post_database_customendpoints` | Create filtered query endpoint |
| `post_database_schemas_id_indexes` | Add DB index |
| `post_database_schemas` | Create schema |
| `patch_database_schemas_id` | Update schema |
| `get_database_customendpoints` | List custom endpoints |
| `post_database_customendpoints` | Create custom endpoint |
| `patch_database_customendpoints_id` | Update custom endpoint |
| `post_database_schemas_id_indexes` | Add index |
| `patch_config_database` | Replica set / DB engine settings |

**Not exposed via MCP:** introspection routes (`/database/introspection/*`), `POST /database/schemas/import`. Use Admin API or Admin Panel for those operator workflows.

<NextSteps steps={[
{ title: "Your first app", href: "/docs/getting-started/first-app" },
{ title: "Authorization & scope", href: "/docs/modules/authorization" },
{ title: "GitOps state export", href: "/docs/guides/gitops-state-export" },
{ title: "Client API reference", href: "/docs/reference/client-api" },
{ title: "MCP tools", href: "/docs/reference/mcp-tools" },
]} />
17 changes: 15 additions & 2 deletions apps/web/src/components/docs/module-deep-dive.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,21 @@ type ModuleDeepDiveProps = {
/** Visual container for expert content — use a markdown ## heading above for TOC. */
export function ModuleDeepDive({ children }: ModuleDeepDiveProps) {
return (
<div className="not-prose my-6 rounded-xl border border-dashed border-border bg-muted/20 p-5 [&_.prose]:my-0">
<div className="prose prose-sm dark:prose-invert max-w-none">{children}</div>
<div className="not-prose my-6 rounded-xl border border-dashed border-border bg-muted/20 p-5">
<div
className={[
"prose dark:prose-invert max-w-none min-w-0 w-full",
"[&_h3:first-of-type]:mt-0",
"[&_h3:not(:first-of-type)]:mt-8 [&_h3]:mb-3",
"[&_h4]:mt-5 [&_h4]:mb-2",
"[&_div.relative]:my-4 [&_div.relative]:max-w-full",
"[&_td]:align-top [&_td]:break-words [&_td_code]:break-all",
"[&_figure]:my-4",
"[&_blockquote]:not-italic [&_blockquote]:rounded-lg [&_blockquote]:border-l-4 [&_blockquote]:border-primary/30 [&_blockquote]:bg-muted/40 [&_blockquote]:px-4 [&_blockquote]:py-3 [&_blockquote]:text-muted-foreground",
].join(" ")}
>
{children}
</div>
</div>
);
}
114 changes: 114 additions & 0 deletions apps/web/src/mdx/deep-dives/database-deep-dive.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
### Schemas

A schema declares field types, indexes, and `conduitOptions` that control runtime behavior:

| `conduitOptions` key | Effect |
|---------------------|--------|
| `cms.enabled` | Register CMS routes for this schema |
| `cms.crudOperations.*.enabled` | Per-operation route exposure (create, read, update, delete) |
| `cms.crudOperations.*.authenticated` | Require bearer token on that operation |
| `authorization.enabled` | ReBAC ownership tuples on documents — pass `scope` on creates |
| `permissions.*` | Admin-panel schema permissions (separate from document ReBAC) |

**Schema extensions** add fields to module-owned schemas (e.g. extend the auth `User` schema). Extensions are database-owned and included in GitOps export alongside base schemas.

Provision schemas at dev/deploy time via MCP `post_database_schemas` or `patch_database_schemas_id`. Module-owned schemas cannot be overwritten by import.

### CMS CRUD

When `conduitOptions.cms` flags are set, the Client API exposes:

| Intent | HTTP | Path |
|--------|------|------|
| List (unfiltered) | GET | `/database/{Schema}?skip&limit&sort&populate&scope` |
| Get by id | GET | `/database/{Schema}/{id}` |
| Create | POST | `/database/{Schema}?scope` |
| Update | PATCH | `/database/{Schema}/{id}` |
| Delete | DELETE | `/database/{Schema}/{id}` |

List queries accept `skip`, `limit`, `sort`, and `populate` (GraphQL-style relation joins on REST). On authorization-enabled schemas, pass `scope` (e.g. `Team:abc`) on **creates** so ownership tuples attach to the right resource.

`GET /database/{Schema}` always queries with an empty filter `{}`. Use it only when a full collection scan (with pagination) is intentional.

### Custom endpoints

Custom endpoints map to Client API routes at `/database/function/{name}`. Provision via MCP `post_database_customendpoints`.

| Field | Purpose |
|-------|---------|
| `operation` | HTTP verb: `0` GET, `1` POST, `2` PUT, `3` DELETE, `4` PATCH |
| `selectedSchema` / `selectedSchemaName` | Target schema |
| `inputs` | Parameters (`location`: `0` body, `1` query, `2` URL) |
| `query` | Query tree for read/update/delete filters |
| `assignments` | Field writes for POST/PUT/PATCH |
| `authentication` | Require bearer token (mandatory on authorization-enabled schemas) |
| `paginated` | Require `skip` + `limit` inputs |
| `sorted` | Enable sort parameter |

**Query trees** nest `AND` / `OR` arrays of leaf nodes. Each leaf has `schemaField`, `operation`, and `comparisonField`:

```json
{
"AND": [
{
"schemaField": "authorId",
"operation": 0,
"comparisonField": { "type": "Input", "value": "authorId" }
}
]
}
```

`comparisonField.type` is `Input` (from endpoint params), `Schema` (field value from the matched document), `Custom` (fixed value), or `Context` (request context path).

Set `comparisonField.like: true` for case-insensitive text search (`$ilike`); `caseSensitiveLike: true` uses `$like`. On **PostgreSQL**, `$ilike` maps to `ILIKE` via the Sequelize adapter. On **MongoDB**, both operators are supported natively. Avoid `like: true` on non-text field types.

#### Comparison operations

| Op | Name | BSON / behavior |
|----|------|-----------------|
| `0` | Equal | `{ field: value }` |
| `1` | Not equal | `{ field: { $ne: value } }` |
| `2` | Greater than | `{ field: { $gt: value } }` |
| `3` | Greater or equal | `{ field: { $gte: value } }` |
| `4` | Less than | `{ field: { $lt: value } }` |
| `5` | Less or equal | `{ field: { $lte: value } }` |
| `6` | In set | `{ field: { $in: value } }` — value must be an array |
| `7` | Not in set | `{ field: { $nin: value } }` |
| `8` | Contains (reserved) | Same as **Equal** (`0`) — `{ field: value }` |

Op `8` does not implement array-contains yet. Use MongoDB `$in` / element-match patterns via custom logic until this operator is completed.

#### Assignment actions (write endpoints)

For POST/PUT/PATCH endpoints, `assignments` map inputs to schema fields:

| Action | Name | Effect |
|--------|------|--------|
| `0` | Set | Assign field value |
| `1` | Increment | `$inc` positive |
| `2` | Decrement | `$inc` negative |
| `3` | Append | `$push` (array fields only; PUT only) |
| `4` | Remove | `$pull` (array fields only; PUT only) |

### Database introspection

When you already have collections or tables outside Conduit, **introspection** discovers them and registers **pending schemas** for review. Finalize pending schemas to convert them into CMS schemas (CRUD disabled by default on imported schemas).

Introspection is an **operator workflow** on the Admin API (`GET`/`POST /database/introspection`, pending schema routes). These routes are marked `mcp: false` — **no MCP tools exist for introspection**. Use the Admin Panel or direct Admin API calls with admin credentials.

Large MongoDB collections can slow introspection; PostgreSQL introspection improved in recent releases.

### GitOps export

The database module participates in platform state export/import. Exportable resource types:

| Type | Priority | Contents |
|------|----------|----------|
| `schemas` | 10 | CMS schema definitions |
| `extensions` | 11 | Database-owned schema extensions |
| `customEndpoints` | 20 | Custom endpoint definitions |

Use `GET /state/export` and `POST /state/import` on the Admin API, or module-scoped `GET /database/schemas/export` and `GET /database/customEndpoints/export`. See [GitOps state export](/docs/guides/gitops-state-export).

`POST /database/schemas/import` is also excluded from MCP (`mcp: false`) — prefer `/state/import` or Admin API directly in CI pipelines.
Loading