opencastle 0.33.9 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +39 -17
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/stack-config.d.ts.map +1 -1
- package/dist/cli/stack-config.js +5 -0
- package/dist/cli/stack-config.js.map +1 -1
- package/dist/cli/types.d.ts +1 -1
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/orchestrator/plugins/cloudflare/config.d.ts +3 -0
- package/dist/orchestrator/plugins/cloudflare/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/cloudflare/config.js +23 -0
- package/dist/orchestrator/plugins/cloudflare/config.js.map +1 -0
- package/dist/orchestrator/plugins/coolify/config.d.ts +3 -0
- package/dist/orchestrator/plugins/coolify/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/coolify/config.js +28 -0
- package/dist/orchestrator/plugins/coolify/config.js.map +1 -0
- package/dist/orchestrator/plugins/drizzle/config.d.ts +3 -0
- package/dist/orchestrator/plugins/drizzle/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/drizzle/config.js +15 -0
- package/dist/orchestrator/plugins/drizzle/config.js.map +1 -0
- package/dist/orchestrator/plugins/expo/config.d.ts +3 -0
- package/dist/orchestrator/plugins/expo/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/expo/config.js +23 -0
- package/dist/orchestrator/plugins/expo/config.js.map +1 -0
- package/dist/orchestrator/plugins/index.d.ts.map +1 -1
- package/dist/orchestrator/plugins/index.js +12 -0
- package/dist/orchestrator/plugins/index.js.map +1 -1
- package/dist/orchestrator/plugins/sentry/config.d.ts +3 -0
- package/dist/orchestrator/plugins/sentry/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/sentry/config.js +28 -0
- package/dist/orchestrator/plugins/sentry/config.js.map +1 -0
- package/dist/orchestrator/plugins/stripe/config.d.ts +3 -0
- package/dist/orchestrator/plugins/stripe/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/stripe/config.js +42 -0
- package/dist/orchestrator/plugins/stripe/config.js.map +1 -0
- package/dist/orchestrator/plugins/types.d.ts +1 -1
- package/dist/orchestrator/plugins/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli/init.ts +43 -22
- package/src/cli/stack-config.ts +5 -0
- package/src/cli/types.ts +1 -1
- package/src/dashboard/dist/data/convoys/demo-api-v2.json +3 -3
- package/src/dashboard/dist/data/convoys/demo-auth-revamp.json +4 -4
- package/src/dashboard/dist/data/convoys/demo-dashboard-ui.json +12 -12
- package/src/dashboard/dist/data/convoys/demo-data-pipeline.json +3 -3
- package/src/dashboard/dist/data/convoys/demo-deploy-ci.json +1 -1
- package/src/dashboard/dist/data/convoys/demo-docs-update.json +3 -3
- package/src/dashboard/dist/data/convoys/demo-perf-opt.json +4 -4
- package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
- package/src/dashboard/public/data/convoys/demo-api-v2.json +3 -3
- package/src/dashboard/public/data/convoys/demo-auth-revamp.json +4 -4
- package/src/dashboard/public/data/convoys/demo-dashboard-ui.json +12 -12
- package/src/dashboard/public/data/convoys/demo-data-pipeline.json +3 -3
- package/src/dashboard/public/data/convoys/demo-deploy-ci.json +1 -1
- package/src/dashboard/public/data/convoys/demo-docs-update.json +3 -3
- package/src/dashboard/public/data/convoys/demo-perf-opt.json +4 -4
- package/src/orchestrator/customizations/agents/skill-matrix.json +24 -4
- package/src/orchestrator/customizations/agents/skill-matrix.md +5 -0
- package/src/orchestrator/plugins/cloudflare/SKILL.md +111 -0
- package/src/orchestrator/plugins/cloudflare/config.ts +24 -0
- package/src/orchestrator/plugins/cloudflare/references/deployment.md +147 -0
- package/src/orchestrator/plugins/cloudflare/references/storage.md +118 -0
- package/src/orchestrator/plugins/cloudflare/references/workers.md +135 -0
- package/src/orchestrator/plugins/convex/SKILL.md +62 -20
- package/src/orchestrator/plugins/convex/references/auth-auth0.md +116 -0
- package/src/orchestrator/plugins/convex/references/auth-clerk.md +113 -0
- package/src/orchestrator/plugins/convex/references/auth-convex-auth.md +143 -0
- package/src/orchestrator/plugins/convex/references/auth-setup.md +87 -0
- package/src/orchestrator/plugins/convex/references/auth-workos.md +114 -0
- package/src/orchestrator/plugins/convex/references/components-advanced.md +134 -0
- package/src/orchestrator/plugins/convex/references/components.md +171 -0
- package/src/orchestrator/plugins/convex/references/function-budget.md +232 -0
- package/src/orchestrator/plugins/convex/references/hot-path-rules.md +371 -0
- package/src/orchestrator/plugins/convex/references/migrations-component.md +170 -0
- package/src/orchestrator/plugins/convex/references/migrations.md +259 -0
- package/src/orchestrator/plugins/convex/references/occ-conflicts.md +126 -0
- package/src/orchestrator/plugins/convex/references/performance-audit.md +80 -0
- package/src/orchestrator/plugins/convex/references/quickstart.md +176 -0
- package/src/orchestrator/plugins/convex/references/subscription-cost.md +252 -0
- package/src/orchestrator/plugins/coolify/SKILL.md +134 -0
- package/src/orchestrator/plugins/coolify/config.ts +29 -0
- package/src/orchestrator/plugins/coolify/references/applications.md +65 -0
- package/src/orchestrator/plugins/coolify/references/ci-cd-webhooks.md +73 -0
- package/src/orchestrator/plugins/coolify/references/databases-services.md +57 -0
- package/src/orchestrator/plugins/coolify/references/docker-compose.md +121 -0
- package/src/orchestrator/plugins/coolify/references/infrastructure.md +77 -0
- package/src/orchestrator/plugins/drizzle/SKILL.md +123 -0
- package/src/orchestrator/plugins/drizzle/config.ts +16 -0
- package/src/orchestrator/plugins/drizzle/references/migrations.md +112 -0
- package/src/orchestrator/plugins/drizzle/references/query-patterns.md +127 -0
- package/src/orchestrator/plugins/drizzle/references/schema-patterns.md +105 -0
- package/src/orchestrator/plugins/expo/SKILL.md +114 -0
- package/src/orchestrator/plugins/expo/config.ts +24 -0
- package/src/orchestrator/plugins/expo/references/eas-build.md +73 -0
- package/src/orchestrator/plugins/expo/references/native-modules.md +71 -0
- package/src/orchestrator/plugins/expo/references/routing.md +83 -0
- package/src/orchestrator/plugins/index.ts +12 -0
- package/src/orchestrator/plugins/linear/SKILL.md +21 -3
- package/src/orchestrator/plugins/sentry/SKILL.md +94 -0
- package/src/orchestrator/plugins/sentry/config.ts +29 -0
- package/src/orchestrator/plugins/sentry/references/error-patterns.md +112 -0
- package/src/orchestrator/plugins/sentry/references/performance.md +66 -0
- package/src/orchestrator/plugins/sentry/references/sdk-setup.md +108 -0
- package/src/orchestrator/plugins/stripe/SKILL.md +138 -0
- package/src/orchestrator/plugins/stripe/config.ts +43 -0
- package/src/orchestrator/plugins/stripe/references/api-patterns.md +57 -0
- package/src/orchestrator/plugins/stripe/references/projects-setup.md +30 -0
- package/src/orchestrator/plugins/stripe/references/upgrade-guide.md +105 -0
- package/src/orchestrator/plugins/types.ts +1 -1
- package/src/orchestrator/skills/backbone-scaffolding/EXAMPLES.md +1 -1
- package/src/orchestrator/skills/backbone-scaffolding/SKILL.md +32 -16
- package/src/orchestrator/plugins/convex/REFERENCE.md +0 -9
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Docker Compose
|
|
2
|
+
|
|
3
|
+
## Deployment Modes
|
|
4
|
+
|
|
5
|
+
| Mode | How | Constraints |
|
|
6
|
+
|------|-----|-------------|
|
|
7
|
+
| Raw (paste YAML) | Paste compose content directly | No `build:`, no external file mounts; use `image:` and `content:` for inline files |
|
|
8
|
+
| Repository | git URL | Full Docker Compose features: `build:`, external mounts, multi-file |
|
|
9
|
+
|
|
10
|
+
## Magic Variables
|
|
11
|
+
|
|
12
|
+
Coolify auto-generates values for specially named env vars. Declare with port suffix, reference without.
|
|
13
|
+
|
|
14
|
+
| Type | Declaration Example | Generated Value |
|
|
15
|
+
|------|--------------------|-----------------|
|
|
16
|
+
| `PASSWORD` | `SERVICE_PASSWORD_DB` | Random password |
|
|
17
|
+
| `PASSWORD_64` | `SERVICE_PASSWORD_64_KEY` | 64-char password |
|
|
18
|
+
| `USER` | `SERVICE_USER_ADMIN` | Random 16-char string |
|
|
19
|
+
| `URL` | `SERVICE_URL_APP_3000` | `https://app-uuid.example.com` + Traefik routing |
|
|
20
|
+
| `FQDN` | `SERVICE_FQDN_APP` | `app-uuid.example.com` (no scheme) |
|
|
21
|
+
|
|
22
|
+
**Declaration vs. Reference:**
|
|
23
|
+
```yaml
|
|
24
|
+
environment:
|
|
25
|
+
- SERVICE_URL_APP_3000 # Declare with port → activates proxy routing
|
|
26
|
+
- APP_URL=$SERVICE_URL_APP # Reference without port → injects https://… URL
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
⚠️ Use hyphens before port numbers: `SERVICE_URL_MY-SERVICE_3000` ✅ NOT `SERVICE_URL_MY_SERVICE_3000` ❌
|
|
30
|
+
|
|
31
|
+
**Shared credentials** — same `SERVICE_PASSWORD_*` identifier across services = same generated value:
|
|
32
|
+
```yaml
|
|
33
|
+
services:
|
|
34
|
+
db: { environment: [SERVICE_PASSWORD_POSTGRES] }
|
|
35
|
+
app: { environment: [DB_PASS=$SERVICE_PASSWORD_POSTGRES] } # same value
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Env Var Syntax
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
environment:
|
|
42
|
+
- NODE_ENV=production # Hardcoded, hidden from UI
|
|
43
|
+
- API_KEY=${API_KEY} # Editable in UI (empty default)
|
|
44
|
+
- LOG_LEVEL=${LOG_LEVEL:-info} # Editable with default
|
|
45
|
+
- SECRET=${SECRET:?} # Required — blocks deploy if empty
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Health Check Patterns
|
|
49
|
+
|
|
50
|
+
```yaml
|
|
51
|
+
healthcheck:
|
|
52
|
+
# HTTP
|
|
53
|
+
test: ["CMD-SHELL", "wget --spider -q http://localhost:8080"]
|
|
54
|
+
# PostgreSQL
|
|
55
|
+
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
|
56
|
+
# MySQL
|
|
57
|
+
test: ["CMD-SHELL", "healthcheck.sh --connect --innodb_initialized"]
|
|
58
|
+
# Redis
|
|
59
|
+
test: ["CMD-SHELL", "redis-cli ping"]
|
|
60
|
+
interval: 5s
|
|
61
|
+
timeout: 5s
|
|
62
|
+
retries: 10
|
|
63
|
+
start_period: 30s
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Coolify-Specific Extensions
|
|
67
|
+
|
|
68
|
+
```yaml
|
|
69
|
+
volumes:
|
|
70
|
+
- /data/config:/app/config # standard bind mount
|
|
71
|
+
- source: /data/uploads
|
|
72
|
+
target: /app/uploads
|
|
73
|
+
is_directory: true # Coolify creates the directory if missing
|
|
74
|
+
|
|
75
|
+
- source: coolify-config
|
|
76
|
+
target: /app/config/app.conf
|
|
77
|
+
content: | # Inline file content (raw mode only)
|
|
78
|
+
log_level = info
|
|
79
|
+
bind = 0.0.0.0:8080
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```yaml
|
|
83
|
+
# Exclude init/migration containers from health checks
|
|
84
|
+
services:
|
|
85
|
+
migrate:
|
|
86
|
+
image: myapp:latest
|
|
87
|
+
command: ["migrate", "up"]
|
|
88
|
+
labels:
|
|
89
|
+
- "exclude_from_hc=true"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Header Metadata (for community templates)
|
|
93
|
+
|
|
94
|
+
```yaml
|
|
95
|
+
# documentation: https://docs.example.com
|
|
96
|
+
# slogan: "One-click app name"
|
|
97
|
+
# category: self-hosted
|
|
98
|
+
# tags: Notes,Productivity
|
|
99
|
+
# logo: https://cdn.example.com/logo.png
|
|
100
|
+
# port: 3000
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Conversion Checklist
|
|
104
|
+
|
|
105
|
+
When converting a standard `docker-compose.yml` for Coolify:
|
|
106
|
+
|
|
107
|
+
1. **Mode check** — `build:` directives → Raw mode: replace with `image:`; Repo mode: keep
|
|
108
|
+
2. **Add header metadata** — `# documentation:`, `# slogan:`, `# category:`, `# tags:`, `# logo:`, `# port:`
|
|
109
|
+
3. **Replace credentials** — hardcoded passwords/users → `SERVICE_PASSWORD_*` / `SERVICE_USER_*`
|
|
110
|
+
4. **Replace URLs** — hardcoded URLs → `SERVICE_URL_*` variables
|
|
111
|
+
5. **Remove `ports:`** — for proxied services (Traefik handles routing via `SERVICE_URL_*`)
|
|
112
|
+
6. **Add health checks** — use patterns above; add `depends_on` with `condition: service_healthy`
|
|
113
|
+
|
|
114
|
+
## Troubleshooting
|
|
115
|
+
|
|
116
|
+
| Symptom | Fix |
|
|
117
|
+
|---------|-----|
|
|
118
|
+
| "No Available Server" | Check `docker ps` for unhealthy containers |
|
|
119
|
+
| Variables not editable in UI | Use `${VAR}` syntax — not `VAR=value` |
|
|
120
|
+
| Magic variables not generating | Check spelling, `SERVICE_` prefix; requires Coolify ≥ v4.0.0-beta.411 |
|
|
121
|
+
| Port routing broken | Use `SERVICE_URL_NAME_PORT`, hyphen before port, remove `ports:` |
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Infrastructure & Diagnostics
|
|
2
|
+
|
|
3
|
+
## Infrastructure Overview
|
|
4
|
+
|
|
5
|
+
Always start here — returns all servers, projects, apps, databases, services in one call:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
get_infrastructure_overview
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Server Management
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# List servers (summaries)
|
|
15
|
+
list_servers
|
|
16
|
+
|
|
17
|
+
# Full details
|
|
18
|
+
get_server uuid="<uuid>"
|
|
19
|
+
|
|
20
|
+
# Check resources on a server
|
|
21
|
+
server_resources uuid="<uuid>"
|
|
22
|
+
|
|
23
|
+
# List domains
|
|
24
|
+
server_domains uuid="<uuid>"
|
|
25
|
+
|
|
26
|
+
# Validate SSH connection
|
|
27
|
+
validate_server uuid="<uuid>"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Diagnostics
|
|
31
|
+
|
|
32
|
+
Smart lookup — accepts names, domains, or IPs (not just UUIDs):
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# App diagnostics (by name or domain)
|
|
36
|
+
diagnose_app identifier="my-app"
|
|
37
|
+
diagnose_app identifier="example.com"
|
|
38
|
+
|
|
39
|
+
# Server diagnostics (by name or IP)
|
|
40
|
+
diagnose_server identifier="192.168.1.100"
|
|
41
|
+
diagnose_server identifier="coolify-apps"
|
|
42
|
+
|
|
43
|
+
# Scan all infrastructure
|
|
44
|
+
find_issues
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Batch Operations
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Restart all apps in a project
|
|
51
|
+
restart_project_apps project_uuid="<uuid>"
|
|
52
|
+
|
|
53
|
+
# Redeploy all apps in a project
|
|
54
|
+
redeploy_project project_uuid="<uuid>"
|
|
55
|
+
|
|
56
|
+
# Emergency stop all apps (requires confirmation)
|
|
57
|
+
stop_all_apps confirm=true
|
|
58
|
+
|
|
59
|
+
# Bulk update env var across apps
|
|
60
|
+
bulk_env_update key="API_URL" value="https://new-api.example.com" uuids=["<uuid1>","<uuid2>"]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Response Pattern
|
|
64
|
+
|
|
65
|
+
Coolify MCP returns HATEOAS-style `_actions` suggesting next steps:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"data": { "uuid": "abc123", "status": "running" },
|
|
70
|
+
"_actions": [
|
|
71
|
+
{ "tool": "application_logs", "args": { "uuid": "abc123" }, "hint": "View logs" },
|
|
72
|
+
{ "tool": "control", "args": { "resource": "application", "action": "restart", "uuid": "abc123" }, "hint": "Restart" }
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Follow `_actions` suggestions for efficient multi-step workflows.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: drizzle-orm
|
|
3
|
+
description: "Drizzle ORM schema definition, type-safe queries, relational queries, CRUD operations, transactions, migrations with drizzle-kit, and database setup for PostgreSQL, MySQL, and SQLite. Use when defining database schemas, writing queries or joins, managing migrations, setting up a new Drizzle project, or working with drizzle-kit."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Drizzle ORM
|
|
7
|
+
|
|
8
|
+
## Topic Routing
|
|
9
|
+
|
|
10
|
+
Read the matching reference before writing code for any of these topics:
|
|
11
|
+
|
|
12
|
+
| Topic | Reference |
|
|
13
|
+
|-------|-----------|
|
|
14
|
+
| Schema definition & column types | `references/schema-patterns.md` |
|
|
15
|
+
| Queries, joins, & CRUD operations | `references/query-patterns.md` |
|
|
16
|
+
| Migrations & drizzle-kit | `references/migrations.md` |
|
|
17
|
+
|
|
18
|
+
## Critical Rules
|
|
19
|
+
|
|
20
|
+
**Schema**
|
|
21
|
+
- Define schemas in dedicated `schema.ts` files using `pgTable` / `mysqlTable` / `sqliteTable` from the correct dialect package
|
|
22
|
+
- Use `$inferSelect` and `$inferInsert` for TypeScript types — never duplicate type definitions manually
|
|
23
|
+
- Foreign keys require explicit `references(() => table.column)` — omitting this creates an unconstrained column
|
|
24
|
+
- Pass `{ schema }` to `drizzle()` when initializing the client to enable relational queries
|
|
25
|
+
|
|
26
|
+
**Relations**
|
|
27
|
+
- Define with `relations()` from `drizzle-orm` alongside the table definition
|
|
28
|
+
- Relations are required for `db.query` relational API — the SQL-like API does not use them
|
|
29
|
+
- Do not use relations as a substitute for foreign key constraints; define both
|
|
30
|
+
|
|
31
|
+
**Queries**
|
|
32
|
+
- Use SQL-like API (`db.select().from()`) for complex joins and aggregations
|
|
33
|
+
- Use relational API (`db.query.table.findMany({ with: { ... } })`) for nested data fetching
|
|
34
|
+
- Import `eq`, `and`, `or`, `gt`, `like`, `isNull` etc. from `drizzle-orm` for `where` clauses
|
|
35
|
+
- Always use `returning()` to get the inserted, updated, or deleted rows back
|
|
36
|
+
|
|
37
|
+
**Migrations**
|
|
38
|
+
- Use `drizzle-kit` for all migrations: `npx drizzle-kit generate` then `npx drizzle-kit migrate`
|
|
39
|
+
- Configure in `drizzle.config.ts` — connections string must be set before running commands
|
|
40
|
+
- Never manually edit generated migration SQL files — regenerate if changes are needed
|
|
41
|
+
- Use `npx drizzle-kit push` in development only; always use `migrate` for production
|
|
42
|
+
|
|
43
|
+
**Transactions**
|
|
44
|
+
- Wrap multi-step operations in `db.transaction(async (tx) => { ... })`
|
|
45
|
+
- Use `tx` (the transaction argument) instead of `db` for all queries inside the callback
|
|
46
|
+
|
|
47
|
+
**Performance**
|
|
48
|
+
- Use `db.select({ col: table.col })` for partial selects — avoids loading unused columns
|
|
49
|
+
- Add indexes in the schema definition for frequently queried columns
|
|
50
|
+
- Use `.prepare()` for repeated queries (prepared statements)
|
|
51
|
+
|
|
52
|
+
## Schema Definition
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { pgTable, text, integer, timestamp, boolean } from 'drizzle-orm/pg-core';
|
|
56
|
+
import { relations } from 'drizzle-orm';
|
|
57
|
+
|
|
58
|
+
export const users = pgTable('users', {
|
|
59
|
+
id: text('id').primaryKey(),
|
|
60
|
+
email: text('email').notNull().unique(),
|
|
61
|
+
name: text('name').notNull(),
|
|
62
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export const posts = pgTable('posts', {
|
|
66
|
+
id: text('id').primaryKey(),
|
|
67
|
+
title: text('title').notNull(),
|
|
68
|
+
authorId: text('author_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
|
69
|
+
published: boolean('published').default(false).notNull(),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
export const usersRelations = relations(users, ({ many }) => ({
|
|
73
|
+
posts: many(posts),
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
export const postsRelations = relations(posts, ({ one }) => ({
|
|
77
|
+
author: one(users, { fields: [posts.authorId], references: [users.id] }),
|
|
78
|
+
}));
|
|
79
|
+
|
|
80
|
+
// Type inference — no manual duplication
|
|
81
|
+
export type User = typeof users.$inferSelect;
|
|
82
|
+
export type NewUser = typeof users.$inferInsert;
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Query Patterns
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { db } from './db';
|
|
89
|
+
import { eq } from 'drizzle-orm';
|
|
90
|
+
import { users, posts } from './schema';
|
|
91
|
+
|
|
92
|
+
// SQL-like: select with join
|
|
93
|
+
const results = await db
|
|
94
|
+
.select({ user: users, postCount: count(posts.id) })
|
|
95
|
+
.from(users)
|
|
96
|
+
.leftJoin(posts, eq(posts.authorId, users.id))
|
|
97
|
+
.groupBy(users.id);
|
|
98
|
+
|
|
99
|
+
// Relational: nested fetch
|
|
100
|
+
const usersWithPosts = await db.query.users.findMany({
|
|
101
|
+
where: eq(users.id, userId),
|
|
102
|
+
with: { posts: { where: eq(posts.published, true) } },
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Insert with returning
|
|
106
|
+
const [newUser] = await db.insert(users).values({ id, email, name }).returning();
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Reference Files
|
|
110
|
+
|
|
111
|
+
- `references/schema-patterns.md` — Table definitions, column types, constraints, indexes, relations, type inference
|
|
112
|
+
- `references/query-patterns.md` — Select, joins, where clauses, relational API, CRUD, transactions, prepared statements
|
|
113
|
+
- `references/migrations.md` — drizzle.config.ts, generate/migrate/push commands, migration workflow
|
|
114
|
+
|
|
115
|
+
## Quick Workflow: Set up Drizzle in a project
|
|
116
|
+
1. Install: `npm install drizzle-orm` + dialect driver (`postgres` / `@libsql/client` / `better-sqlite3`)
|
|
117
|
+
2. Install drizzle-kit: `npm install -D drizzle-kit`
|
|
118
|
+
3. Define schema in `src/db/schema.ts` using the correct dialect table builder
|
|
119
|
+
4. Create `drizzle.config.ts` with database URL and schema path — verify the config before running commands
|
|
120
|
+
5. Generate migration: `npx drizzle-kit generate` — inspect the SQL output before applying
|
|
121
|
+
6. Apply migration: `npx drizzle-kit migrate`
|
|
122
|
+
- **If migration fails:** check DB connection string → verify schema matches existing tables → use `npx drizzle-kit push` for dev environments
|
|
123
|
+
7. Initialize client: `const db = drizzle(pool, { schema })` and run a test query to confirm connectivity
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PluginConfig } from '../types.js';
|
|
2
|
+
|
|
3
|
+
export const config: PluginConfig = {
|
|
4
|
+
id: 'drizzle',
|
|
5
|
+
name: 'Drizzle ORM',
|
|
6
|
+
category: 'tech',
|
|
7
|
+
subCategory: 'database',
|
|
8
|
+
label: 'Drizzle ORM',
|
|
9
|
+
hint: 'Type-safe SQL ORM with migrations',
|
|
10
|
+
skillName: 'drizzle-orm',
|
|
11
|
+
authType: 'none',
|
|
12
|
+
envVars: [],
|
|
13
|
+
agentToolMap: {},
|
|
14
|
+
docsUrl: null,
|
|
15
|
+
officialDocs: 'https://orm.drizzle.team/',
|
|
16
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Drizzle Migrations
|
|
2
|
+
|
|
3
|
+
## drizzle.config.ts
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { defineConfig } from 'drizzle-kit';
|
|
7
|
+
|
|
8
|
+
export default defineConfig({
|
|
9
|
+
dialect: 'postgresql', // 'postgresql' | 'mysql' | 'sqlite'
|
|
10
|
+
schema: './src/db/schema.ts', // path to your schema file(s)
|
|
11
|
+
out: './drizzle', // output directory for migrations
|
|
12
|
+
dbCredentials: {
|
|
13
|
+
url: process.env.DATABASE_URL!, // never hardcode credentials
|
|
14
|
+
},
|
|
15
|
+
verbose: true,
|
|
16
|
+
strict: true,
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Commands
|
|
21
|
+
|
|
22
|
+
| Command | When to use |
|
|
23
|
+
|---------|------------|
|
|
24
|
+
| `npx drizzle-kit generate` | After schema changes — generates SQL migration files |
|
|
25
|
+
| `npx drizzle-kit migrate` | Apply pending migrations to the database (production-safe) |
|
|
26
|
+
| `npx drizzle-kit push` | Sync schema directly to DB without migrations (dev only) |
|
|
27
|
+
| `npx drizzle-kit studio` | Open local DB browser UI |
|
|
28
|
+
| `npx drizzle-kit check` | Validate migration history consistency |
|
|
29
|
+
|
|
30
|
+
## Migration Workflow
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# 1. Change schema in src/db/schema.ts
|
|
34
|
+
# 2. Generate migration — inspect the SQL before applying
|
|
35
|
+
npx drizzle-kit generate
|
|
36
|
+
|
|
37
|
+
# 3. Review the generated .sql file in ./drizzle/
|
|
38
|
+
# 4. Apply to database
|
|
39
|
+
npx drizzle-kit migrate
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Never edit generated migration files.** If the generated SQL is wrong, adjust the schema and regenerate.
|
|
43
|
+
|
|
44
|
+
## Handling Breaking Changes
|
|
45
|
+
|
|
46
|
+
### Adding a required (non-nullable) column
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// Step 1: Add as nullable first
|
|
50
|
+
newColumn: text('new_column'),
|
|
51
|
+
|
|
52
|
+
// Step 2: Run migration to add the column → backfill data in a separate script/mutation
|
|
53
|
+
// Step 3: Add .notNull() after backfill is complete
|
|
54
|
+
newColumn: text('new_column').notNull(),
|
|
55
|
+
|
|
56
|
+
// Step 4: Run migration again to add the NOT NULL constraint
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Renaming a column
|
|
60
|
+
|
|
61
|
+
Drizzle Kit generates a DROP + ADD by default. To rename without data loss, use a custom migration:
|
|
62
|
+
|
|
63
|
+
```sql
|
|
64
|
+
-- drizzle/0002_rename_column.sql (manually created)
|
|
65
|
+
ALTER TABLE users RENAME COLUMN old_name TO new_name;
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Then run `npx drizzle-kit migrate` — it will apply the custom file.
|
|
69
|
+
|
|
70
|
+
## Programmatic Migrations (CI / startup)
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { drizzle } from 'drizzle-orm/postgres-js';
|
|
74
|
+
import { migrate } from 'drizzle-orm/postgres-js/migrator';
|
|
75
|
+
import postgres from 'postgres';
|
|
76
|
+
|
|
77
|
+
const migrationClient = postgres(process.env.DATABASE_URL!, { max: 1 });
|
|
78
|
+
await migrate(drizzle(migrationClient), { migrationsFolder: './drizzle' });
|
|
79
|
+
await migrationClient.end();
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Database Client Setup
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// src/db/index.ts (PostgreSQL with postgres.js)
|
|
86
|
+
import { drizzle } from 'drizzle-orm/postgres-js';
|
|
87
|
+
import postgres from 'postgres';
|
|
88
|
+
import * as schema from './schema';
|
|
89
|
+
|
|
90
|
+
const pool = postgres(process.env.DATABASE_URL!);
|
|
91
|
+
export const db = drizzle(pool, { schema });
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
// src/db/index.ts (SQLite with better-sqlite3)
|
|
96
|
+
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
|
97
|
+
import Database from 'better-sqlite3';
|
|
98
|
+
import * as schema from './schema';
|
|
99
|
+
|
|
100
|
+
const sqlite = new Database('sqlite.db');
|
|
101
|
+
export const db = drizzle(sqlite, { schema });
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// src/db/index.ts (Turso / libSQL)
|
|
106
|
+
import { drizzle } from 'drizzle-orm/libsql';
|
|
107
|
+
import { createClient } from '@libsql/client';
|
|
108
|
+
import * as schema from './schema';
|
|
109
|
+
|
|
110
|
+
const client = createClient({ url: process.env.TURSO_URL!, authToken: process.env.TURSO_TOKEN! });
|
|
111
|
+
export const db = drizzle(client, { schema });
|
|
112
|
+
```
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Drizzle Query Patterns
|
|
2
|
+
|
|
3
|
+
## SQL-like API (complex joins & aggregations)
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { db } from './db';
|
|
7
|
+
import { eq, and, or, gt, like, isNull, desc, count, sql } from 'drizzle-orm';
|
|
8
|
+
import { users, posts } from './schema';
|
|
9
|
+
|
|
10
|
+
// Basic select with where
|
|
11
|
+
const activeUsers = await db
|
|
12
|
+
.select()
|
|
13
|
+
.from(users)
|
|
14
|
+
.where(and(eq(users.active, true), gt(users.age, 18)));
|
|
15
|
+
|
|
16
|
+
// Partial select (avoids loading unused columns)
|
|
17
|
+
const emails = await db
|
|
18
|
+
.select({ id: users.id, email: users.email })
|
|
19
|
+
.from(users);
|
|
20
|
+
|
|
21
|
+
// Join
|
|
22
|
+
const postsWithAuthors = await db
|
|
23
|
+
.select({ post: posts, author: users })
|
|
24
|
+
.from(posts)
|
|
25
|
+
.innerJoin(users, eq(posts.authorId, users.id))
|
|
26
|
+
.where(eq(posts.published, true))
|
|
27
|
+
.orderBy(desc(posts.createdAt))
|
|
28
|
+
.limit(20);
|
|
29
|
+
|
|
30
|
+
// Aggregate
|
|
31
|
+
const stats = await db
|
|
32
|
+
.select({ total: count(), avgAge: sql<number>`avg(${users.age})` })
|
|
33
|
+
.from(users);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Relational API (nested data fetching)
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// Requires { schema } passed to drizzle() and relations defined in schema
|
|
40
|
+
|
|
41
|
+
// findMany with nested includes
|
|
42
|
+
const usersWithPosts = await db.query.users.findMany({
|
|
43
|
+
where: eq(users.active, true),
|
|
44
|
+
orderBy: desc(users.createdAt),
|
|
45
|
+
limit: 10,
|
|
46
|
+
with: {
|
|
47
|
+
posts: {
|
|
48
|
+
where: eq(posts.published, true),
|
|
49
|
+
columns: { id: true, title: true, createdAt: true }, // partial columns
|
|
50
|
+
orderBy: desc(posts.createdAt),
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// findFirst
|
|
56
|
+
const user = await db.query.users.findFirst({
|
|
57
|
+
where: eq(users.email, email),
|
|
58
|
+
with: { posts: true },
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## CRUD Operations
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// Insert — always use returning() to get back the created row
|
|
66
|
+
const [newUser] = await db
|
|
67
|
+
.insert(users)
|
|
68
|
+
.values({ id: crypto.randomUUID(), email, name })
|
|
69
|
+
.returning();
|
|
70
|
+
|
|
71
|
+
// Upsert (on conflict)
|
|
72
|
+
const [upserted] = await db
|
|
73
|
+
.insert(users)
|
|
74
|
+
.values({ id, email, name })
|
|
75
|
+
.onConflictDoUpdate({
|
|
76
|
+
target: users.email,
|
|
77
|
+
set: { name, updatedAt: new Date() },
|
|
78
|
+
})
|
|
79
|
+
.returning();
|
|
80
|
+
|
|
81
|
+
// Update
|
|
82
|
+
const [updated] = await db
|
|
83
|
+
.update(users)
|
|
84
|
+
.set({ name: 'New Name', updatedAt: new Date() })
|
|
85
|
+
.where(eq(users.id, userId))
|
|
86
|
+
.returning();
|
|
87
|
+
|
|
88
|
+
// Delete
|
|
89
|
+
const [deleted] = await db
|
|
90
|
+
.delete(posts)
|
|
91
|
+
.where(and(eq(posts.authorId, userId), eq(posts.published, false)))
|
|
92
|
+
.returning();
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Transactions
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
const result = await db.transaction(async (tx) => {
|
|
99
|
+
// Use tx instead of db inside the transaction
|
|
100
|
+
const [order] = await tx.insert(orders).values(orderData).returning();
|
|
101
|
+
|
|
102
|
+
await tx.insert(orderItems).values(
|
|
103
|
+
items.map((item) => ({ orderId: order.id, ...item }))
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
await tx
|
|
107
|
+
.update(inventory)
|
|
108
|
+
.set({ quantity: sql`${inventory.quantity} - 1` })
|
|
109
|
+
.where(eq(inventory.productId, productId));
|
|
110
|
+
|
|
111
|
+
return order;
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Prepared Statements
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// Define once, reuse many times
|
|
119
|
+
const getUserById = db
|
|
120
|
+
.select()
|
|
121
|
+
.from(users)
|
|
122
|
+
.where(eq(users.id, sql.placeholder('id')))
|
|
123
|
+
.prepare('get_user_by_id');
|
|
124
|
+
|
|
125
|
+
// Execute with parameters
|
|
126
|
+
const user = await getUserById.execute({ id: userId });
|
|
127
|
+
```
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Drizzle Schema Patterns
|
|
2
|
+
|
|
3
|
+
## PostgreSQL
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import {
|
|
7
|
+
pgTable, text, integer, boolean, timestamp, numeric,
|
|
8
|
+
serial, uuid, jsonb, index, uniqueIndex,
|
|
9
|
+
} from 'drizzle-orm/pg-core';
|
|
10
|
+
import { relations, sql } from 'drizzle-orm';
|
|
11
|
+
|
|
12
|
+
export const users = pgTable('users', {
|
|
13
|
+
id: uuid('id').defaultRandom().primaryKey(),
|
|
14
|
+
email: text('email').notNull().unique(),
|
|
15
|
+
name: text('name').notNull(),
|
|
16
|
+
age: integer('age'),
|
|
17
|
+
active: boolean('active').default(true).notNull(),
|
|
18
|
+
metadata: jsonb('metadata').$type<Record<string, unknown>>(),
|
|
19
|
+
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
|
20
|
+
updatedAt: timestamp('updated_at', { withTimezone: true })
|
|
21
|
+
.defaultNow()
|
|
22
|
+
.$onUpdate(() => new Date())
|
|
23
|
+
.notNull(),
|
|
24
|
+
}, (table) => [
|
|
25
|
+
index('users_email_idx').on(table.email),
|
|
26
|
+
uniqueIndex('users_active_email_idx').on(table.active, table.email),
|
|
27
|
+
]);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## MySQL
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { mysqlTable, varchar, int, boolean, datetime } from 'drizzle-orm/mysql-core';
|
|
34
|
+
|
|
35
|
+
export const products = mysqlTable('products', {
|
|
36
|
+
id: int('id').autoincrement().primaryKey(),
|
|
37
|
+
name: varchar('name', { length: 255 }).notNull(),
|
|
38
|
+
price: int('price').notNull(), // store in cents
|
|
39
|
+
inStock: boolean('in_stock').default(true),
|
|
40
|
+
createdAt: datetime('created_at').default(sql`now()`),
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## SQLite
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { sqliteTable, text, integer, real } from 'drizzle-orm/sqlite-core';
|
|
48
|
+
import { sql } from 'drizzle-orm';
|
|
49
|
+
|
|
50
|
+
export const notes = sqliteTable('notes', {
|
|
51
|
+
id: integer('id', { mode: 'number' }).primaryKey({ autoIncrement: true }),
|
|
52
|
+
title: text('title').notNull(),
|
|
53
|
+
body: text('body'),
|
|
54
|
+
score: real('score').default(0),
|
|
55
|
+
createdAt: integer('created_at', { mode: 'timestamp' })
|
|
56
|
+
.default(sql`(unixepoch())`)
|
|
57
|
+
.notNull(),
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Foreign Keys & Constraints
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
export const posts = pgTable('posts', {
|
|
65
|
+
id: uuid('id').defaultRandom().primaryKey(),
|
|
66
|
+
authorId: uuid('author_id')
|
|
67
|
+
.notNull()
|
|
68
|
+
.references(() => users.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
|
69
|
+
categoryId: uuid('category_id')
|
|
70
|
+
.references(() => categories.id, { onDelete: 'set null' }), // nullable FK
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Relations
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { relations } from 'drizzle-orm';
|
|
78
|
+
|
|
79
|
+
// One-to-many
|
|
80
|
+
export const usersRelations = relations(users, ({ many }) => ({
|
|
81
|
+
posts: many(posts),
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
export const postsRelations = relations(posts, ({ one, many }) => ({
|
|
85
|
+
author: one(users, { fields: [posts.authorId], references: [users.id] }),
|
|
86
|
+
comments: many(comments),
|
|
87
|
+
}));
|
|
88
|
+
|
|
89
|
+
// Many-to-many (via junction table)
|
|
90
|
+
export const postTagsRelations = relations(postTags, ({ one }) => ({
|
|
91
|
+
post: one(posts, { fields: [postTags.postId], references: [posts.id] }),
|
|
92
|
+
tag: one(tags, { fields: [postTags.tagId], references: [tags.id] }),
|
|
93
|
+
}));
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Type Inference
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// Always use $inferSelect and $inferInsert — never write these types manually
|
|
100
|
+
export type User = typeof users.$inferSelect;
|
|
101
|
+
export type NewUser = typeof users.$inferInsert;
|
|
102
|
+
|
|
103
|
+
// Partial insert type (e.g. when ID is generated externally)
|
|
104
|
+
export type CreateUserInput = Omit<NewUser, 'id' | 'createdAt' | 'updatedAt'>;
|
|
105
|
+
```
|