mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 19:21:10 +02:00
docs(database): document the pgSchema isolation rule
pgSchema was enforced in practice across every service (auth, credits, usr, events, mail, research, subscriptions, analytics) but nowhere documented as a rule. New services had to reverse-engineer the pattern from existing code, and the example in the guideline itself still used raw pgTable() — actively steering readers in the wrong direction. - New "Schema Isolation" section: the rule, the why, the naming table (service → schema), the mana_sync exception, a grep-based verification command. - Updated the "Table Definition Pattern" example to use pgSchema so readers copy the right thing. The root CLAUDE.md already links here from the "Database (PostgreSQL)" section — no change needed there. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a7fe828d32
commit
dd756c4664
1 changed files with 60 additions and 2 deletions
|
|
@ -85,6 +85,60 @@ export class DatabaseModule implements OnModuleDestroy {
|
|||
}
|
||||
```
|
||||
|
||||
## Schema Isolation (`pgSchema`)
|
||||
|
||||
**CRITICAL**: Every service and app that shares the `mana_platform` database MUST namespace its tables under its own PostgreSQL schema using `pgSchema('<name>').table(...)`. Never use raw `pgTable()` in this monorepo.
|
||||
|
||||
### Why
|
||||
|
||||
`mana_platform` is the one shared database for all services (see root `CLAUDE.md`). Without a schema prefix, a new `users` table in one service would collide with `users` in another. The Postgres schema acts as a namespace and as an RLS boundary — it's the mechanism by which `mana-auth`'s `auth.users` and e.g. `mana-credits`' `credits.balances` coexist cleanly.
|
||||
|
||||
### Pattern
|
||||
|
||||
```typescript
|
||||
// src/db/schema/auth.ts
|
||||
import { pgSchema, text, timestamp, boolean } from 'drizzle-orm/pg-core';
|
||||
|
||||
// One schema instance per file (or per service — export from index.ts).
|
||||
export const authSchema = pgSchema('auth');
|
||||
|
||||
// Tables hang off the schema, NOT off `pgTable`.
|
||||
export const users = authSchema.table('users', {
|
||||
id: text('id').primaryKey(),
|
||||
email: text('email').unique().notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
```
|
||||
|
||||
### Schema naming
|
||||
|
||||
| Service | Schema |
|
||||
| -------------------- | --------------------------- |
|
||||
| `mana-auth` | `auth` |
|
||||
| `mana-credits` | `credits` |
|
||||
| `mana-user` | `usr` |
|
||||
| `mana-events` | `events`, `event_discovery` |
|
||||
| `mana-mail` | `mail` |
|
||||
| `mana-research` | `research` |
|
||||
| `mana-subscriptions` | `subscriptions` |
|
||||
| `mana-analytics` | `feedback` |
|
||||
|
||||
New services: pick a short, unambiguous name (`auth`, not `mana_auth_schema`), add it here, and keep it stable — renaming a schema is a breaking migration.
|
||||
|
||||
### The `mana_sync` exception
|
||||
|
||||
`mana_sync` is its own database (not part of `mana_platform`), and its tables are append-only write-heavy. It uses raw `pgTable()` — no multi-schema tenancy. Service-owned projections on top of `mana_sync` (e.g. `mana-ai` → `mana_ai.mission_snapshots`) DO use `pgSchema()` to stay out of the core sync engine's namespace.
|
||||
|
||||
### Verification
|
||||
|
||||
Before merging a change that adds a new Drizzle schema file, confirm with:
|
||||
|
||||
```bash
|
||||
rg "pgTable\(" services/ apps/api/ packages/ --type ts
|
||||
```
|
||||
|
||||
Any hit that's not inside `mana-sync` is a violation. There's no automated lint rule yet — adding one is tracked in the architecture audit.
|
||||
|
||||
## Schema Design
|
||||
|
||||
### File Organization
|
||||
|
|
@ -126,7 +180,7 @@ See [Authentication Guidelines](./authentication.md#user-id-format) for details.
|
|||
```typescript
|
||||
// src/db/schema/files.schema.ts
|
||||
import {
|
||||
pgTable,
|
||||
pgSchema,
|
||||
uuid,
|
||||
varchar,
|
||||
text,
|
||||
|
|
@ -134,10 +188,14 @@ import {
|
|||
timestamp,
|
||||
bigint,
|
||||
integer,
|
||||
index,
|
||||
} from 'drizzle-orm/pg-core';
|
||||
import { relations } from 'drizzle-orm';
|
||||
|
||||
export const files = pgTable(
|
||||
// Namespace this service's tables — see "Schema Isolation" above.
|
||||
export const storageSchema = pgSchema('storage');
|
||||
|
||||
export const files = storageSchema.table(
|
||||
'files',
|
||||
{
|
||||
// Primary key - always UUID with auto-generation
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue