@qazuor/claude-code-config 0.4.0 → 0.6.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/README.md +395 -50
- package/dist/bin.cjs +3207 -165
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +3207 -165
- package/dist/bin.js.map +1 -1
- package/dist/index.cjs +75 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +284 -1
- package/dist/index.d.ts +284 -1
- package/dist/index.js +75 -58
- package/dist/index.js.map +1 -1
- package/package.json +24 -24
- package/templates/CLAUDE.md.template +60 -5
- package/templates/agents/README.md +58 -39
- package/templates/agents/_registry.json +43 -202
- package/templates/agents/engineering/{hono-engineer.md → api-engineer.md} +61 -70
- package/templates/agents/engineering/database-engineer.md +253 -0
- package/templates/agents/engineering/frontend-engineer.md +302 -0
- package/templates/docs/_registry.json +54 -0
- package/templates/docs/standards/code-standards.md +20 -0
- package/templates/docs/standards/design-standards.md +13 -0
- package/templates/docs/standards/documentation-standards.md +13 -0
- package/templates/docs/standards/performance-standards.md +524 -0
- package/templates/docs/standards/security-standards.md +496 -0
- package/templates/docs/standards/testing-standards.md +15 -0
- package/templates/hooks/on-notification.sh +0 -0
- package/templates/scripts/add-changelogs.sh +0 -0
- package/templates/scripts/generate-code-registry.ts +0 -0
- package/templates/scripts/health-check.sh +0 -0
- package/templates/scripts/sync-registry.sh +0 -0
- package/templates/scripts/telemetry-report.ts +0 -0
- package/templates/scripts/validate-docs.sh +0 -0
- package/templates/scripts/validate-registry.sh +0 -0
- package/templates/scripts/validate-structure.sh +0 -0
- package/templates/scripts/worktree-cleanup.sh +0 -0
- package/templates/scripts/worktree-create.sh +0 -0
- package/templates/skills/README.md +99 -90
- package/templates/skills/_registry.json +323 -16
- package/templates/skills/api-frameworks/express-patterns.md +411 -0
- package/templates/skills/api-frameworks/fastify-patterns.md +419 -0
- package/templates/skills/api-frameworks/hono-patterns.md +388 -0
- package/templates/skills/api-frameworks/nestjs-patterns.md +497 -0
- package/templates/skills/database/drizzle-patterns.md +449 -0
- package/templates/skills/database/mongoose-patterns.md +503 -0
- package/templates/skills/database/prisma-patterns.md +487 -0
- package/templates/skills/frontend-frameworks/astro-patterns.md +415 -0
- package/templates/skills/frontend-frameworks/nextjs-patterns.md +470 -0
- package/templates/skills/frontend-frameworks/react-patterns.md +516 -0
- package/templates/skills/frontend-frameworks/tanstack-start-patterns.md +469 -0
- package/templates/skills/patterns/atdd-methodology.md +364 -0
- package/templates/skills/patterns/bdd-methodology.md +281 -0
- package/templates/skills/patterns/clean-architecture.md +444 -0
- package/templates/skills/patterns/hexagonal-architecture.md +567 -0
- package/templates/skills/patterns/vertical-slice-architecture.md +502 -0
- package/templates/agents/engineering/astro-engineer.md +0 -293
- package/templates/agents/engineering/db-drizzle-engineer.md +0 -360
- package/templates/agents/engineering/express-engineer.md +0 -316
- package/templates/agents/engineering/fastify-engineer.md +0 -399
- package/templates/agents/engineering/mongoose-engineer.md +0 -473
- package/templates/agents/engineering/nestjs-engineer.md +0 -429
- package/templates/agents/engineering/nextjs-engineer.md +0 -451
- package/templates/agents/engineering/prisma-engineer.md +0 -432
- package/templates/agents/engineering/react-senior-dev.md +0 -394
- package/templates/agents/engineering/tanstack-start-engineer.md +0 -447
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
# Drizzle ORM Patterns
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Drizzle is a TypeScript ORM with maximum type safety. This skill provides patterns for database operations with Drizzle.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Schema Definition
|
|
10
|
+
|
|
11
|
+
### Table with Relations
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import {
|
|
15
|
+
pgTable,
|
|
16
|
+
uuid,
|
|
17
|
+
varchar,
|
|
18
|
+
text,
|
|
19
|
+
timestamp,
|
|
20
|
+
integer,
|
|
21
|
+
index,
|
|
22
|
+
check,
|
|
23
|
+
} from 'drizzle-orm/pg-core';
|
|
24
|
+
import { relations, sql } from 'drizzle-orm';
|
|
25
|
+
|
|
26
|
+
// Users table
|
|
27
|
+
export const users = pgTable('users', {
|
|
28
|
+
id: uuid('id').defaultRandom().primaryKey(),
|
|
29
|
+
email: varchar('email', { length: 255 }).notNull().unique(),
|
|
30
|
+
name: varchar('name', { length: 255 }),
|
|
31
|
+
passwordHash: varchar('password_hash', { length: 255 }).notNull(),
|
|
32
|
+
role: varchar('role', { length: 20 }).notNull().default('user'),
|
|
33
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
34
|
+
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
|
35
|
+
deletedAt: timestamp('deleted_at'),
|
|
36
|
+
}, (table) => ({
|
|
37
|
+
emailIdx: index('idx_users_email').on(table.email),
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
// Items table
|
|
41
|
+
export const items = pgTable('items', {
|
|
42
|
+
id: uuid('id').defaultRandom().primaryKey(),
|
|
43
|
+
title: varchar('title', { length: 255 }).notNull(),
|
|
44
|
+
description: text('description'),
|
|
45
|
+
status: varchar('status', { length: 20 }).notNull().default('active'),
|
|
46
|
+
price: integer('price').notNull(),
|
|
47
|
+
authorId: uuid('author_id')
|
|
48
|
+
.notNull()
|
|
49
|
+
.references(() => users.id, { onDelete: 'cascade' }),
|
|
50
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
51
|
+
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
|
52
|
+
deletedAt: timestamp('deleted_at'),
|
|
53
|
+
}, (table) => ({
|
|
54
|
+
authorIdx: index('idx_items_author').on(table.authorId),
|
|
55
|
+
statusIdx: index('idx_items_status').on(table.status),
|
|
56
|
+
priceCheck: check('check_price_positive', sql`${table.price} > 0`),
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
// Tags table (many-to-many)
|
|
60
|
+
export const tags = pgTable('tags', {
|
|
61
|
+
id: uuid('id').defaultRandom().primaryKey(),
|
|
62
|
+
name: varchar('name', { length: 50 }).notNull().unique(),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Junction table
|
|
66
|
+
export const itemTags = pgTable('item_tags', {
|
|
67
|
+
itemId: uuid('item_id')
|
|
68
|
+
.notNull()
|
|
69
|
+
.references(() => items.id, { onDelete: 'cascade' }),
|
|
70
|
+
tagId: uuid('tag_id')
|
|
71
|
+
.notNull()
|
|
72
|
+
.references(() => tags.id, { onDelete: 'cascade' }),
|
|
73
|
+
}, (table) => ({
|
|
74
|
+
pk: primaryKey({ columns: [table.itemId, table.tagId] }),
|
|
75
|
+
}));
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Relations Definition
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
// Relations
|
|
82
|
+
export const usersRelations = relations(users, ({ many }) => ({
|
|
83
|
+
items: many(items),
|
|
84
|
+
}));
|
|
85
|
+
|
|
86
|
+
export const itemsRelations = relations(items, ({ one, many }) => ({
|
|
87
|
+
author: one(users, {
|
|
88
|
+
fields: [items.authorId],
|
|
89
|
+
references: [users.id],
|
|
90
|
+
}),
|
|
91
|
+
tags: many(itemTags),
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
export const itemTagsRelations = relations(itemTags, ({ one }) => ({
|
|
95
|
+
item: one(items, {
|
|
96
|
+
fields: [itemTags.itemId],
|
|
97
|
+
references: [items.id],
|
|
98
|
+
}),
|
|
99
|
+
tag: one(tags, {
|
|
100
|
+
fields: [itemTags.tagId],
|
|
101
|
+
references: [tags.id],
|
|
102
|
+
}),
|
|
103
|
+
}));
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Type Inference
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// Infer types from schema
|
|
110
|
+
export type User = typeof users.$inferSelect;
|
|
111
|
+
export type NewUser = typeof users.$inferInsert;
|
|
112
|
+
export type Item = typeof items.$inferSelect;
|
|
113
|
+
export type NewItem = typeof items.$inferInsert;
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Query Patterns
|
|
119
|
+
|
|
120
|
+
### Basic CRUD
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { eq, and, isNull, desc, sql } from 'drizzle-orm';
|
|
124
|
+
|
|
125
|
+
// Create
|
|
126
|
+
const [newItem] = await db
|
|
127
|
+
.insert(items)
|
|
128
|
+
.values({
|
|
129
|
+
title: 'New Item',
|
|
130
|
+
price: 100,
|
|
131
|
+
authorId: userId,
|
|
132
|
+
})
|
|
133
|
+
.returning();
|
|
134
|
+
|
|
135
|
+
// Read one
|
|
136
|
+
const item = await db.query.items.findFirst({
|
|
137
|
+
where: eq(items.id, itemId),
|
|
138
|
+
with: {
|
|
139
|
+
author: true,
|
|
140
|
+
tags: {
|
|
141
|
+
with: {
|
|
142
|
+
tag: true,
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Read many
|
|
149
|
+
const activeItems = await db.query.items.findMany({
|
|
150
|
+
where: and(
|
|
151
|
+
eq(items.status, 'active'),
|
|
152
|
+
isNull(items.deletedAt)
|
|
153
|
+
),
|
|
154
|
+
orderBy: [desc(items.createdAt)],
|
|
155
|
+
limit: 10,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Update
|
|
159
|
+
const [updated] = await db
|
|
160
|
+
.update(items)
|
|
161
|
+
.set({
|
|
162
|
+
title: 'Updated Title',
|
|
163
|
+
updatedAt: new Date(),
|
|
164
|
+
})
|
|
165
|
+
.where(eq(items.id, itemId))
|
|
166
|
+
.returning();
|
|
167
|
+
|
|
168
|
+
// Delete
|
|
169
|
+
await db
|
|
170
|
+
.delete(items)
|
|
171
|
+
.where(eq(items.id, itemId));
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Pagination
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
async function findPaginated(input: {
|
|
178
|
+
page: number;
|
|
179
|
+
pageSize: number;
|
|
180
|
+
status?: string;
|
|
181
|
+
}) {
|
|
182
|
+
const { page, pageSize, status } = input;
|
|
183
|
+
const offset = (page - 1) * pageSize;
|
|
184
|
+
|
|
185
|
+
const conditions = [isNull(items.deletedAt)];
|
|
186
|
+
if (status) {
|
|
187
|
+
conditions.push(eq(items.status, status));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const [results, [{ count }]] = await Promise.all([
|
|
191
|
+
db.query.items.findMany({
|
|
192
|
+
where: and(...conditions),
|
|
193
|
+
limit: pageSize,
|
|
194
|
+
offset,
|
|
195
|
+
orderBy: [desc(items.createdAt)],
|
|
196
|
+
with: { author: true },
|
|
197
|
+
}),
|
|
198
|
+
db
|
|
199
|
+
.select({ count: sql<number>`count(*)` })
|
|
200
|
+
.from(items)
|
|
201
|
+
.where(and(...conditions)),
|
|
202
|
+
]);
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
data: results,
|
|
206
|
+
pagination: {
|
|
207
|
+
total: Number(count),
|
|
208
|
+
page,
|
|
209
|
+
pageSize,
|
|
210
|
+
totalPages: Math.ceil(Number(count) / pageSize),
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Soft Delete
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
// Soft delete
|
|
220
|
+
await db
|
|
221
|
+
.update(items)
|
|
222
|
+
.set({ deletedAt: new Date() })
|
|
223
|
+
.where(eq(items.id, itemId));
|
|
224
|
+
|
|
225
|
+
// Restore
|
|
226
|
+
await db
|
|
227
|
+
.update(items)
|
|
228
|
+
.set({ deletedAt: null })
|
|
229
|
+
.where(eq(items.id, itemId));
|
|
230
|
+
|
|
231
|
+
// Query non-deleted only
|
|
232
|
+
const activeItems = await db.query.items.findMany({
|
|
233
|
+
where: isNull(items.deletedAt),
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Transactions
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
// Transaction with rollback on error
|
|
241
|
+
const result = await db.transaction(async (tx) => {
|
|
242
|
+
// Create item
|
|
243
|
+
const [item] = await tx
|
|
244
|
+
.insert(items)
|
|
245
|
+
.values({ title: 'New', price: 100, authorId: userId })
|
|
246
|
+
.returning();
|
|
247
|
+
|
|
248
|
+
// Add tags
|
|
249
|
+
await tx.insert(itemTags).values(
|
|
250
|
+
tagIds.map((tagId) => ({
|
|
251
|
+
itemId: item.id,
|
|
252
|
+
tagId,
|
|
253
|
+
}))
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
// Update user stats
|
|
257
|
+
await tx
|
|
258
|
+
.update(users)
|
|
259
|
+
.set({
|
|
260
|
+
itemsCount: sql`${users.itemsCount} + 1`,
|
|
261
|
+
})
|
|
262
|
+
.where(eq(users.id, userId));
|
|
263
|
+
|
|
264
|
+
return item;
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Model Class Pattern
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
import { eq, and, isNull, desc, sql } from 'drizzle-orm';
|
|
274
|
+
import type { Database } from './client';
|
|
275
|
+
|
|
276
|
+
export class ItemModel {
|
|
277
|
+
constructor(private db: Database) {}
|
|
278
|
+
|
|
279
|
+
async findById(id: string) {
|
|
280
|
+
return this.db.query.items.findFirst({
|
|
281
|
+
where: and(eq(items.id, id), isNull(items.deletedAt)),
|
|
282
|
+
with: { author: true },
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async findByAuthor(authorId: string) {
|
|
287
|
+
return this.db.query.items.findMany({
|
|
288
|
+
where: and(
|
|
289
|
+
eq(items.authorId, authorId),
|
|
290
|
+
isNull(items.deletedAt)
|
|
291
|
+
),
|
|
292
|
+
orderBy: [desc(items.createdAt)],
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async create(data: NewItem) {
|
|
297
|
+
const [item] = await this.db
|
|
298
|
+
.insert(items)
|
|
299
|
+
.values(data)
|
|
300
|
+
.returning();
|
|
301
|
+
return item;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async update(id: string, data: Partial<NewItem>) {
|
|
305
|
+
const [item] = await this.db
|
|
306
|
+
.update(items)
|
|
307
|
+
.set({ ...data, updatedAt: new Date() })
|
|
308
|
+
.where(eq(items.id, id))
|
|
309
|
+
.returning();
|
|
310
|
+
return item;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async softDelete(id: string) {
|
|
314
|
+
const [item] = await this.db
|
|
315
|
+
.update(items)
|
|
316
|
+
.set({ deletedAt: new Date() })
|
|
317
|
+
.where(eq(items.id, id))
|
|
318
|
+
.returning();
|
|
319
|
+
return item;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## Migrations
|
|
327
|
+
|
|
328
|
+
### Generate Migration
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
# Generate migration from schema changes
|
|
332
|
+
pnpm drizzle-kit generate
|
|
333
|
+
|
|
334
|
+
# Apply migrations
|
|
335
|
+
pnpm drizzle-kit migrate
|
|
336
|
+
|
|
337
|
+
# Push schema directly (development only)
|
|
338
|
+
pnpm drizzle-kit push
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Migration File Example
|
|
342
|
+
|
|
343
|
+
```sql
|
|
344
|
+
-- 0001_create_items.sql
|
|
345
|
+
CREATE TABLE IF NOT EXISTS "items" (
|
|
346
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
347
|
+
"title" varchar(255) NOT NULL,
|
|
348
|
+
"description" text,
|
|
349
|
+
"status" varchar(20) NOT NULL DEFAULT 'active',
|
|
350
|
+
"price" integer NOT NULL,
|
|
351
|
+
"author_id" uuid NOT NULL REFERENCES "users"("id") ON DELETE CASCADE,
|
|
352
|
+
"created_at" timestamp NOT NULL DEFAULT now(),
|
|
353
|
+
"updated_at" timestamp NOT NULL DEFAULT now(),
|
|
354
|
+
"deleted_at" timestamp
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
CREATE INDEX "idx_items_author" ON "items"("author_id");
|
|
358
|
+
CREATE INDEX "idx_items_status" ON "items"("status");
|
|
359
|
+
|
|
360
|
+
ALTER TABLE "items" ADD CONSTRAINT "check_price_positive"
|
|
361
|
+
CHECK ("price" > 0);
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Database Client Setup
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
// client.ts
|
|
370
|
+
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
371
|
+
import { Pool } from 'pg';
|
|
372
|
+
import * as schema from './schema';
|
|
373
|
+
|
|
374
|
+
const pool = new Pool({
|
|
375
|
+
connectionString: process.env.DATABASE_URL,
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
export const db = drizzle(pool, { schema });
|
|
379
|
+
export type Database = typeof db;
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## Testing
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
import { describe, it, expect, beforeEach, afterAll } from 'vitest';
|
|
388
|
+
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
389
|
+
import { Pool } from 'pg';
|
|
390
|
+
import * as schema from '../schema';
|
|
391
|
+
import { ItemModel } from '../models/item.model';
|
|
392
|
+
|
|
393
|
+
const pool = new Pool({
|
|
394
|
+
connectionString: process.env.TEST_DATABASE_URL,
|
|
395
|
+
});
|
|
396
|
+
const db = drizzle(pool, { schema });
|
|
397
|
+
|
|
398
|
+
describe('ItemModel', () => {
|
|
399
|
+
const model = new ItemModel(db);
|
|
400
|
+
|
|
401
|
+
beforeEach(async () => {
|
|
402
|
+
await db.delete(schema.items);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
afterAll(async () => {
|
|
406
|
+
await pool.end();
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
describe('create', () => {
|
|
410
|
+
it('should create item', async () => {
|
|
411
|
+
const item = await model.create({
|
|
412
|
+
title: 'Test Item',
|
|
413
|
+
price: 100,
|
|
414
|
+
authorId: testUserId,
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
expect(item.id).toBeDefined();
|
|
418
|
+
expect(item.title).toBe('Test Item');
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
describe('findById', () => {
|
|
423
|
+
it('should return null for non-existent', async () => {
|
|
424
|
+
const item = await model.findById('non-existent-id');
|
|
425
|
+
expect(item).toBeNull();
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## Best Practices
|
|
434
|
+
|
|
435
|
+
### Good
|
|
436
|
+
|
|
437
|
+
- Use type inference from schema (`$inferSelect`, `$inferInsert`)
|
|
438
|
+
- Use `relations` for type-safe joins
|
|
439
|
+
- Use transactions for multi-step operations
|
|
440
|
+
- Use `returning()` to get inserted/updated data
|
|
441
|
+
- Use indexes for frequently queried columns
|
|
442
|
+
|
|
443
|
+
### Bad
|
|
444
|
+
|
|
445
|
+
- Manually defining types that duplicate schema
|
|
446
|
+
- Using raw SQL when Drizzle has type-safe alternatives
|
|
447
|
+
- Forgetting to add indexes
|
|
448
|
+
- Not using transactions for related operations
|
|
449
|
+
- Ignoring soft delete patterns
|