factory-db-schema-architect
Skill9 starsChanged 4 months agoMITView on GitHub
Use when designing or modifying database schemas, migrations, multi-tenant data models, or polymorphic table structures. Carries the factory's data-layer conventions — Drizzle with domain-partitioned schema modules, `_shared.ts` with `timestamps` helper and `pgTableCreator`, org-keyed FKs with cascade delete, JSONB envelope for non-query-driving data, polymorphic table patterns (shared base + variant tables), schema-derived type exports, ESLint Drizzle WHERE-enforcement, soft-delete mixin (Python). Produces schema files that fit the house style — not generic Postgres tables.
In this repository42 other files it gives its agents.
- guidance/claude.mdCLAUDE.md
- factory-animationSkill
- factory-api-route-engineerSkill
- factory-apiSkill
- factory-authSkill
- factory-auth-wiring-specialistSkill
- factory-ciSkill
- factory-closeSkill
- factory-code-reviewerSkill
- factory-commitsSkill
- factory-data-layerSkill
- factory-data-pipeline-engineerSkill
- factory-data-pipelinesSkill
- factory-db-migration-engineerSkill
- factory-db-migrationSkill
- factory-deploymentSkill
- factory-designSkill
- factory-entrySkill
- factory-feature-architectSkill
- factory-forms-builderSkill
- factory-formsSkill
- factory-frontend-engineerSkill
- factory-frontendSkill
- factory-kit-auditSkill
- factory-llm-workflow-engineerSkill
- factory-llm-workflowsSkill
- factory-observabilitySkill
- factory-pitfallsSkill
- factory-promptingSkill
- factory-promptSkill
- factory-releaseSkill
- factory-security-engineerSkill
- factory-securitySkill
- factory-setup-linearSkill
- factory-stackSkill
- factory-standupSkill
- factory-state-modelingSkill
- factory-submitSkill
- factory-testingSkill
- factory-verification-engineerSkill
- factory-verificationSkill
- factory-voiceSkill
The file
We split this skill into the rules it gives an agent
11 rules. About 0 are common, 4 are distinctive, and 7 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
7
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 14
Polymorphic?: If entities share a base type but diverge significantly (ICE vs BEV vehicles, individual vs business accounts), use the …
- Very specific: line 21
Partition by domain?: Check src/server/db/schemas/:
- Very specific: line 22
If _shared.ts exists with timestamps and pgTableCreator, reuse them
- Distinctive: line 23
If not, create them as part of this work
- Distinctive: line 26
Soft-delete or hard-delete?: Default to hard-delete (with cascade).
- Very specific: line 36
ESLint Drizzle rules?: If eslint-plugin-drizzle isn't installed, recommend adding it for the WHERE enforcement on UPDATE/DELETE.
- Very specific: line 108
Don't skip the org FK on domain tables: Every tenant-scoped table has it with onDelete: 'cascade'.
- Very specific: line 110
Don't define entity types separately from the schema: Use $inferSelect / $inferInsert.
- Distinctive: line 114
Don't put queryable data in JSONB: If something filters / sorts on it, it's a column.
- Very specific: line 116
Don't add
updatedAtmanually: Use the timestamps spread from _shared.ts. - Distinctive: line 120
If the user asks to add a single column to an existing table, do it directly with a migration.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Apply the db-schema-architect specialist workflow. Design schemas grounded in the factory's data-layer conventions, not generic SQL. Load factory-data-layer and factory-stack through the host's skill capability when needed.
How to think (in order)
- What entities are in scope? Restate the request — name every entity, every relationship. If the user asks for "a customer model," check whether they mean (a) just
customers, or (b)customers + addresses + contacts + .... Commit to the interpretation; flag the assumption. - Multi-tenant key? Almost always yes. Every domain table gets
orgId(orworkspaceId/projectId) FK withonDelete: 'cascade'. If this is a shared-reference table (countries, states, vehicle types), call out that it's tenant-agnostic. - Polymorphic? If entities share a base type but diverge significantly (ICE vs BEV vehicles, individual vs business accounts), use the shared-base + variant-tables pattern from
factory-data-layer.md. Don't reach for nullable columns ordiscriminator: 'type'. - JSONB or columns? For each field, ask: "Does anything query / sort / filter on this?"
- Yes → real column
- No, but it's structured → JSONB envelope (
customAttributes,metadata,config) - No, and it's blob-shaped → external storage (S3) with a pointer column
- Partition by domain? Check
src/server/db/schemas/:- If
_shared.tsexists withtimestampsandpgTableCreator, reuse them - If not, create them as part of this work
- Pick / create the domain file (e.g.
fleet.ts,payments.ts)
- If
- Soft-delete or hard-delete? Default to hard-delete (with cascade). Use soft-delete when:
- Regulatory requirement (audit history must persist)
- User-facing "trash bin" UX
- References across tenants where hard delete would break referential integrity
- Migration shape?
drizzle-kit generatefrom the schema diff. Name the migration file:- Timestamps:
<unix>_<verb_subject>.sql(preferred) - Or sequential:
000N_<verb_subject>.sql - Pick one convention per project and stick. Mixed naming is a
factory-pitfalls.mdentry.
- Timestamps:
- ESLint Drizzle rules? If
eslint-plugin-drizzleisn't installed, recommend adding it for the WHERE enforcement on UPDATE/DELETE.
Reference: canonical schema file shape
// src/server/db/schemas/customers.ts
import { uuid, text, jsonb, pgEnum } from 'drizzle-orm/pg-core';
import { pgTable, timestamps } from './_shared';
import { organizations } from './auth';
export const customerStatus = pgEnum('customer_status', ['active', 'inactive', 'pending']);
export const customers = pgTable('customers', {
id: uuid('id').defaultRandom().primaryKey(),
orgId: uuid('org_id').references(() => organizations.id, { onDelete: 'cascade' }).notNull(),
name: text('name').notNull(),
email: text('email'),
status: customerStatus('status').notNull().default('pending'),
customAttributes: jsonb('custom_attributes').$type<Record<string, unknown>>().default({}),
...timestamps,
});
export type Customer = typeof customers.$inferSelect;
export type NewCustomer = typeof customers.$inferInsert;// src/server/db/schemas/_shared.ts (create if missing)
import { pgTableCreator, timestamp } from 'drizzle-orm/pg-core';
export const pgTable = pgTableCreator((name) => `myapp_${name}`);
export const timestamps = {
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull().$onUpdate(() => new Date()),
};Output format
## Restated request
<one sentence — entities, relationships>
## Entities
- <entity>: <fields, relationships, tenancy key>
## Schema decisions
- Multi-tenant key: <orgId / workspaceId / N/A — why>
- Polymorphic: <yes/no — and shape>
- JSONB fields: <which fields, why>
- Soft-delete: <yes/no — why>
- Migration naming: <timestamps / sequential — flag if pre-existing convention differs>
## Files to create or modify
<bulleted with paths>
## Schema code
<actual Drizzle code, organized by file>
## Migration plan
<drizzle-kit generate command + resulting migration filename>
## ESLint check
- WHERE enforcement on UPDATE/DELETE: <enabled / recommend enabling>
## Open questions
<things the user should confirm>What you do NOT do
- Don't skip the org FK on domain tables. Every tenant-scoped table has it with
onDelete: 'cascade'. - Don't put the whole schema in one file. Domain-partitioned modules.
- Don't define entity types separately from the schema. Use
$inferSelect/$inferInsert. - Don't allow nullable-column proliferation. Polymorphic? Use shared-base + variants.
- Don't mix migration-file naming conventions within a project.
- Don't run migrations at runtime. CI's job — see
factory-deployment.md. - Don't put queryable data in JSONB. If something filters / sorts on it, it's a column.
- Don't reach for raw SQL. Drizzle handles everything Postgres can do.
- Don't add
updatedAtmanually. Use thetimestampsspread from_shared.ts.
When the request is too small for this framework
If the user asks to add a single column to an existing table, do it directly with a migration. The framework is for new tables, new entities, or non-trivial schema evolution.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

