@levironexe/architect 0.1.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.
Files changed (210) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/CONTRIBUTING.md +55 -0
  3. package/README.md +341 -0
  4. package/dist/analyzers/ast-parser.d.ts +3 -0
  5. package/dist/analyzers/ast-parser.js +305 -0
  6. package/dist/analyzers/ast-parser.js.map +1 -0
  7. package/dist/analyzers/dependency-graph.d.ts +2 -0
  8. package/dist/analyzers/dependency-graph.js +67 -0
  9. package/dist/analyzers/dependency-graph.js.map +1 -0
  10. package/dist/analyzers/duplication.d.ts +2 -0
  11. package/dist/analyzers/duplication.js +56 -0
  12. package/dist/analyzers/duplication.js.map +1 -0
  13. package/dist/analyzers/file-walker.d.ts +3 -0
  14. package/dist/analyzers/file-walker.js +80 -0
  15. package/dist/analyzers/file-walker.js.map +1 -0
  16. package/dist/cli/context-runner.d.ts +1 -0
  17. package/dist/cli/context-runner.js +16 -0
  18. package/dist/cli/context-runner.js.map +1 -0
  19. package/dist/cli/index.d.ts +24 -0
  20. package/dist/cli/index.js +217 -0
  21. package/dist/cli/index.js.map +1 -0
  22. package/dist/cli/init-runner.d.ts +25 -0
  23. package/dist/cli/init-runner.js +152 -0
  24. package/dist/cli/init-runner.js.map +1 -0
  25. package/dist/cli/scan-runner.d.ts +8 -0
  26. package/dist/cli/scan-runner.js +133 -0
  27. package/dist/cli/scan-runner.js.map +1 -0
  28. package/dist/formatters/plan-json.d.ts +2 -0
  29. package/dist/formatters/plan-json.js +4 -0
  30. package/dist/formatters/plan-json.js.map +1 -0
  31. package/dist/formatters/plan-markdown.d.ts +2 -0
  32. package/dist/formatters/plan-markdown.js +42 -0
  33. package/dist/formatters/plan-markdown.js.map +1 -0
  34. package/dist/formatters/plan-prompt.d.ts +4 -0
  35. package/dist/formatters/plan-prompt.js +5 -0
  36. package/dist/formatters/plan-prompt.js.map +1 -0
  37. package/dist/formatters/plan-terminal.d.ts +5 -0
  38. package/dist/formatters/plan-terminal.js +62 -0
  39. package/dist/formatters/plan-terminal.js.map +1 -0
  40. package/dist/generators/blueprint-renderer.d.ts +3 -0
  41. package/dist/generators/blueprint-renderer.js +27 -0
  42. package/dist/generators/blueprint-renderer.js.map +1 -0
  43. package/dist/generators/claudeWriter.d.ts +3 -0
  44. package/dist/generators/claudeWriter.js +9 -0
  45. package/dist/generators/claudeWriter.js.map +1 -0
  46. package/dist/generators/copilotWriter.d.ts +3 -0
  47. package/dist/generators/copilotWriter.js +11 -0
  48. package/dist/generators/copilotWriter.js.map +1 -0
  49. package/dist/generators/cursorWriter.d.ts +3 -0
  50. package/dist/generators/cursorWriter.js +14 -0
  51. package/dist/generators/cursorWriter.js.map +1 -0
  52. package/dist/generators/genericWriter.d.ts +3 -0
  53. package/dist/generators/genericWriter.js +9 -0
  54. package/dist/generators/genericWriter.js.map +1 -0
  55. package/dist/generators/template-context.d.ts +18 -0
  56. package/dist/generators/template-context.js +126 -0
  57. package/dist/generators/template-context.js.map +1 -0
  58. package/dist/generators/templateRenderer.d.ts +2 -0
  59. package/dist/generators/templateRenderer.js +19 -0
  60. package/dist/generators/templateRenderer.js.map +1 -0
  61. package/dist/generators/windsurfWriter.d.ts +3 -0
  62. package/dist/generators/windsurfWriter.js +14 -0
  63. package/dist/generators/windsurfWriter.js.map +1 -0
  64. package/dist/generators/writer-types.d.ts +11 -0
  65. package/dist/generators/writer-types.js +40 -0
  66. package/dist/generators/writer-types.js.map +1 -0
  67. package/dist/llm/claude-provider.d.ts +8 -0
  68. package/dist/llm/claude-provider.js +22 -0
  69. package/dist/llm/claude-provider.js.map +1 -0
  70. package/dist/llm/concern-classifier.d.ts +15 -0
  71. package/dist/llm/concern-classifier.js +61 -0
  72. package/dist/llm/concern-classifier.js.map +1 -0
  73. package/dist/llm/config.d.ts +11 -0
  74. package/dist/llm/config.js +120 -0
  75. package/dist/llm/config.js.map +1 -0
  76. package/dist/llm/ollama-provider.d.ts +8 -0
  77. package/dist/llm/ollama-provider.js +27 -0
  78. package/dist/llm/ollama-provider.js.map +1 -0
  79. package/dist/llm/openai-provider.d.ts +8 -0
  80. package/dist/llm/openai-provider.js +19 -0
  81. package/dist/llm/openai-provider.js.map +1 -0
  82. package/dist/llm/prompt-builder.d.ts +12 -0
  83. package/dist/llm/prompt-builder.js +132 -0
  84. package/dist/llm/prompt-builder.js.map +1 -0
  85. package/dist/llm/provider.d.ts +17 -0
  86. package/dist/llm/provider.js +2 -0
  87. package/dist/llm/provider.js.map +1 -0
  88. package/dist/llm/response-parser.d.ts +6 -0
  89. package/dist/llm/response-parser.js +128 -0
  90. package/dist/llm/response-parser.js.map +1 -0
  91. package/dist/planner/plan-generator.d.ts +7 -0
  92. package/dist/planner/plan-generator.js +275 -0
  93. package/dist/planner/plan-generator.js.map +1 -0
  94. package/dist/planner/plan-prompt-builder.d.ts +9 -0
  95. package/dist/planner/plan-prompt-builder.js +92 -0
  96. package/dist/planner/plan-prompt-builder.js.map +1 -0
  97. package/dist/planner/plan-response-parser.d.ts +7 -0
  98. package/dist/planner/plan-response-parser.js +21 -0
  99. package/dist/planner/plan-response-parser.js.map +1 -0
  100. package/dist/planner/plan-validator.d.ts +3 -0
  101. package/dist/planner/plan-validator.js +49 -0
  102. package/dist/planner/plan-validator.js.map +1 -0
  103. package/dist/reporters/scan-json.d.ts +13 -0
  104. package/dist/reporters/scan-json.js +26 -0
  105. package/dist/reporters/scan-json.js.map +1 -0
  106. package/dist/reporters/terminal.d.ts +6 -0
  107. package/dist/reporters/terminal.js +224 -0
  108. package/dist/reporters/terminal.js.map +1 -0
  109. package/dist/scoring/consistency-score.d.ts +3 -0
  110. package/dist/scoring/consistency-score.js +23 -0
  111. package/dist/scoring/consistency-score.js.map +1 -0
  112. package/dist/scoring/duplication-score.d.ts +3 -0
  113. package/dist/scoring/duplication-score.js +16 -0
  114. package/dist/scoring/duplication-score.js.map +1 -0
  115. package/dist/scoring/health-score.d.ts +4 -0
  116. package/dist/scoring/health-score.js +20 -0
  117. package/dist/scoring/health-score.js.map +1 -0
  118. package/dist/scoring/issue-builder.d.ts +4 -0
  119. package/dist/scoring/issue-builder.js +62 -0
  120. package/dist/scoring/issue-builder.js.map +1 -0
  121. package/dist/scoring/modularity-score.d.ts +3 -0
  122. package/dist/scoring/modularity-score.js +56 -0
  123. package/dist/scoring/modularity-score.js.map +1 -0
  124. package/dist/scoring/pattern-analysis.d.ts +3 -0
  125. package/dist/scoring/pattern-analysis.js +74 -0
  126. package/dist/scoring/pattern-analysis.js.map +1 -0
  127. package/dist/scoring/separation-score.d.ts +3 -0
  128. package/dist/scoring/separation-score.js +35 -0
  129. package/dist/scoring/separation-score.js.map +1 -0
  130. package/dist/skills/detector.d.ts +4 -0
  131. package/dist/skills/detector.js +104 -0
  132. package/dist/skills/detector.js.map +1 -0
  133. package/dist/skills/lister.d.ts +9 -0
  134. package/dist/skills/lister.js +35 -0
  135. package/dist/skills/lister.js.map +1 -0
  136. package/dist/skills/loader.d.ts +6 -0
  137. package/dist/skills/loader.js +76 -0
  138. package/dist/skills/loader.js.map +1 -0
  139. package/dist/skills/structure-check.d.ts +2 -0
  140. package/dist/skills/structure-check.js +37 -0
  141. package/dist/skills/structure-check.js.map +1 -0
  142. package/dist/skills/validator.d.ts +6 -0
  143. package/dist/skills/validator.js +229 -0
  144. package/dist/skills/validator.js.map +1 -0
  145. package/dist/types/analysis.d.ts +130 -0
  146. package/dist/types/analysis.js +41 -0
  147. package/dist/types/analysis.js.map +1 -0
  148. package/dist/types/concern.d.ts +48 -0
  149. package/dist/types/concern.js +16 -0
  150. package/dist/types/concern.js.map +1 -0
  151. package/dist/types/generation.d.ts +32 -0
  152. package/dist/types/generation.js +2 -0
  153. package/dist/types/generation.js.map +1 -0
  154. package/dist/types/issue.d.ts +12 -0
  155. package/dist/types/issue.js +2 -0
  156. package/dist/types/issue.js.map +1 -0
  157. package/dist/types/pattern.d.ts +15 -0
  158. package/dist/types/pattern.js +2 -0
  159. package/dist/types/pattern.js.map +1 -0
  160. package/dist/types/plan.d.ts +56 -0
  161. package/dist/types/plan.js +2 -0
  162. package/dist/types/plan.js.map +1 -0
  163. package/dist/types/scan-output.d.ts +84 -0
  164. package/dist/types/scan-output.js +2 -0
  165. package/dist/types/scan-output.js.map +1 -0
  166. package/dist/types/scoring.d.ts +15 -0
  167. package/dist/types/scoring.js +2 -0
  168. package/dist/types/scoring.js.map +1 -0
  169. package/dist/types/skill.d.ts +97 -0
  170. package/dist/types/skill.js +2 -0
  171. package/dist/types/skill.js.map +1 -0
  172. package/dist/utils/agent-detector.d.ts +2 -0
  173. package/dist/utils/agent-detector.js +22 -0
  174. package/dist/utils/agent-detector.js.map +1 -0
  175. package/dist/utils/interactive.d.ts +6 -0
  176. package/dist/utils/interactive.js +15 -0
  177. package/dist/utils/interactive.js.map +1 -0
  178. package/dist/utils/path.d.ts +5 -0
  179. package/dist/utils/path.js +31 -0
  180. package/dist/utils/path.js.map +1 -0
  181. package/dist/utils/progress.d.ts +17 -0
  182. package/dist/utils/progress.js +48 -0
  183. package/dist/utils/progress.js.map +1 -0
  184. package/dist/utils/thresholds.d.ts +6 -0
  185. package/dist/utils/thresholds.js +48 -0
  186. package/dist/utils/thresholds.js.map +1 -0
  187. package/package.json +63 -0
  188. package/skills/meta/general-js.skill.yaml +131 -0
  189. package/skills/patterns/clerk-auth.skill.yaml +349 -0
  190. package/skills/patterns/docker-deploy.skill.yaml +214 -0
  191. package/skills/patterns/drizzle.skill.yaml +277 -0
  192. package/skills/patterns/mongoose.skill.yaml +290 -0
  193. package/skills/patterns/nextauth.skill.yaml +308 -0
  194. package/skills/patterns/playwright-e2e.skill.yaml +265 -0
  195. package/skills/patterns/prisma.skill.yaml +255 -0
  196. package/skills/patterns/s3-storage.skill.yaml +235 -0
  197. package/skills/patterns/selenium-e2e.skill.yaml +276 -0
  198. package/skills/patterns/supabase-auth.skill.yaml +298 -0
  199. package/skills/patterns/supabase.skill.yaml +304 -0
  200. package/skills/patterns/vercel-deploy.skill.yaml +219 -0
  201. package/skills/patterns/vitest-testing.skill.yaml +262 -0
  202. package/skills/stacks/express-api.skill.yaml +155 -0
  203. package/skills/stacks/fastify-api.skill.yaml +119 -0
  204. package/skills/stacks/hono-api.skill.yaml +130 -0
  205. package/skills/stacks/nestjs.skill.yaml +135 -0
  206. package/skills/stacks/nextjs-app-router.skill.yaml +176 -0
  207. package/skills/stacks/react-spa.skill.yaml +153 -0
  208. package/skills/stacks/vue-nuxt.skill.yaml +115 -0
  209. package/templates/architect-plan.md +139 -0
  210. package/templates/architect-refactor.md +119 -0
@@ -0,0 +1,277 @@
1
+ schema_version: "2.0.0"
2
+ id: drizzle
3
+ name: "Drizzle ORM"
4
+ version: "2.0.0"
5
+ description: "SQL-first Drizzle ORM with TypeScript schema as source of truth, drizzle-kit migrations, singleton database connection, inferred types, and explicit query building."
6
+ category: pattern
7
+ language: javascript
8
+ frameworks:
9
+ - drizzle-orm
10
+ dependencies:
11
+ none:
12
+ - prisma
13
+ - "@prisma/client"
14
+ - mongoose
15
+ detection:
16
+ dependencies:
17
+ any:
18
+ - drizzle-orm
19
+ source_indicators:
20
+ - "drizzle("
21
+ - "pgTable("
22
+ - "mysqlTable("
23
+ - "sqliteTable("
24
+ - "from 'drizzle-orm'"
25
+ structure:
26
+ required_dirs:
27
+ - path: src/db
28
+ purpose: "Drizzle schema definitions in schema.ts — the single source of truth for all table structures and TypeScript types. Every table, column, index, and relation is defined here as TypeScript. The generated types (via `typeof users.$inferSelect`) come from these definitions — never from separate type files."
29
+ - path: drizzle
30
+ purpose: "Generated migration SQL files created by `npx drizzle-kit generate`. These files represent the canonical migration history. Never edit them manually — drizzle-kit detects checksum changes and may regenerate or fail on the next run."
31
+ recommended_dirs:
32
+ - path: src/lib
33
+ purpose: "Drizzle database connection singleton in db.ts — the only place that creates the database connection and passes it to drizzle(). Imported by all repository files. Prevents multiple connections in serverless environments with the same global caching pattern as Prisma."
34
+ - path: src/db/queries
35
+ purpose: "Reusable query functions organized by resource — e.g. queries/users.ts, queries/posts.ts. Functions use Drizzle's query builder API and return typed results. Services and API routes call these functions rather than building queries inline."
36
+ separation:
37
+ rules:
38
+ - concern: schema_definition
39
+ belongs_in: src/db
40
+ rule_text: "Define all table schemas in src/db/schema.ts using Drizzle's TypeScript API. Export the table objects so that drizzle-kit can discover them for migration generation. Use `typeof table.$inferSelect` and `typeof table.$inferInsert` for type inference — never manually write type interfaces for database rows."
41
+ example: |
42
+ // src/db/schema.ts — single source of truth
43
+ import { pgTable, text, timestamp, uuid, boolean } from 'drizzle-orm/pg-core';
44
+
45
+ export const users = pgTable('users', {
46
+ id: uuid('id').primaryKey().defaultRandom(),
47
+ email: text('email').notNull().unique(),
48
+ name: text('name'),
49
+ emailVerified: boolean('email_verified').notNull().default(false),
50
+ createdAt: timestamp('created_at').defaultNow().notNull(),
51
+ updatedAt: timestamp('updated_at').defaultNow().notNull(),
52
+ });
53
+
54
+ export const posts = pgTable('posts', {
55
+ id: uuid('id').primaryKey().defaultRandom(),
56
+ title: text('title').notNull(),
57
+ content: text('content'),
58
+ userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
59
+ publishedAt: timestamp('published_at'),
60
+ createdAt: timestamp('created_at').defaultNow().notNull(),
61
+ });
62
+
63
+ // Inferred types — never write these manually
64
+ export type User = typeof users.$inferSelect;
65
+ export type NewUser = typeof users.$inferInsert;
66
+ export type Post = typeof posts.$inferSelect;
67
+ export type NewPost = typeof posts.$inferInsert;
68
+ indicators:
69
+ - "pgTable("
70
+ - "mysqlTable("
71
+ - "sqliteTable("
72
+ - "$inferSelect"
73
+ - "$inferInsert"
74
+ - concern: database_connection
75
+ belongs_in: src/lib
76
+ rule_text: "Create the Drizzle database connection in src/lib/db.ts using a module-level or global singleton. Pass the underlying driver instance (e.g., postgres from 'postgres' or Pool from 'pg') to drizzle(). Never create a new connection per query or per request."
77
+ example: |
78
+ // src/lib/db.ts — singleton pattern for serverless compatibility
79
+ import { drizzle } from 'drizzle-orm/postgres-js';
80
+ import postgres from 'postgres';
81
+ import * as schema from '@/db/schema';
82
+
83
+ // Singleton: reuse connection across hot reloads in development
84
+ const globalForDb = globalThis as unknown as {
85
+ connection: ReturnType<typeof postgres> | undefined;
86
+ };
87
+
88
+ const connection =
89
+ globalForDb.connection ??
90
+ postgres(process.env.DATABASE_URL!, {
91
+ max: process.env.NODE_ENV === 'production' ? 10 : 1,
92
+ });
93
+
94
+ if (process.env.NODE_ENV !== 'production') globalForDb.connection = connection;
95
+
96
+ export const db = drizzle(connection, { schema });
97
+ indicators:
98
+ - "drizzle("
99
+ - "DATABASE_URL"
100
+ - "from 'drizzle-orm"
101
+ - concern: migrations
102
+ belongs_in: drizzle
103
+ rule_text: "Use drizzle-kit to generate and apply migrations. Configure drizzle.config.ts with the schema path and migration output directory. Run `npx drizzle-kit generate` to create migration SQL from schema changes, and `npx drizzle-kit migrate` (or `push` for prototyping) to apply them."
104
+ example: |
105
+ // drizzle.config.ts — tells drizzle-kit where to find schema and write migrations
106
+ import { defineConfig } from 'drizzle-kit';
107
+ export default defineConfig({
108
+ schema: './src/db/schema.ts',
109
+ out: './drizzle',
110
+ dialect: 'postgresql',
111
+ dbCredentials: { url: process.env.DATABASE_URL! },
112
+ });
113
+
114
+ # Development workflow:
115
+ # 1. Edit src/db/schema.ts
116
+ # 2. Generate migration:
117
+ npx drizzle-kit generate
118
+ # 3. Apply migration:
119
+ npx drizzle-kit migrate
120
+ # 4. The db object's types update automatically (schema is the source)
121
+ indicators:
122
+ - "drizzle-kit generate"
123
+ - "drizzle-kit migrate"
124
+ - "drizzle.config.ts"
125
+ - "defineConfig("
126
+ - concern: query_building
127
+ belongs_in: src/db/queries
128
+ rule_text: "Use Drizzle's query builder API for all queries. For complex SQL, use the `sql` template tag with parameterized values — never string concatenation. Queries live in src/db/queries/ functions; services call these functions rather than building queries inline."
129
+ example: |
130
+ // src/db/queries/users.ts
131
+ import { db } from '@/lib/db';
132
+ import { users, posts } from '@/db/schema';
133
+ import { eq, and, desc, ilike, sql } from 'drizzle-orm';
134
+
135
+ export async function findUserByEmail(email: string) {
136
+ return db.query.users.findFirst({
137
+ where: eq(users.email, email),
138
+ });
139
+ }
140
+
141
+ export async function listUsersWithPostCount(limit = 20) {
142
+ return db
143
+ .select({
144
+ id: users.id,
145
+ email: users.email,
146
+ name: users.name,
147
+ postCount: sql<number>`count(${posts.id})`.mapWith(Number),
148
+ })
149
+ .from(users)
150
+ .leftJoin(posts, eq(posts.userId, users.id))
151
+ .groupBy(users.id)
152
+ .orderBy(desc(users.createdAt))
153
+ .limit(limit);
154
+ }
155
+ anti_indicators:
156
+ - "db.execute(`SELECT"
157
+ - "db.execute(\"SELECT"
158
+ - "+ email +"
159
+ - concern: transactions
160
+ belongs_in: src/db/queries
161
+ rule_text: "Use db.transaction() for operations that must succeed or fail atomically. The transaction callback receives a `tx` context — use `tx` instead of `db` for all queries inside the transaction."
162
+ example: |
163
+ // src/db/queries/orders.ts
164
+ import { db } from '@/lib/db';
165
+ import { orders, inventory } from '@/db/schema';
166
+ import { eq, sql } from 'drizzle-orm';
167
+
168
+ export async function createOrderWithInventoryDecrement(
169
+ userId: string,
170
+ productId: string,
171
+ quantity: number,
172
+ price: number
173
+ ) {
174
+ return db.transaction(async (tx) => {
175
+ // Atomically decrement inventory
176
+ const [product] = await tx
177
+ .update(inventory)
178
+ .set({ stock: sql`${inventory.stock} - ${quantity}` })
179
+ .where(and(eq(inventory.productId, productId), sql`${inventory.stock} >= ${quantity}`))
180
+ .returning();
181
+
182
+ if (!product) throw new Error('Insufficient stock');
183
+
184
+ return tx.insert(orders).values({ userId, productId, quantity, total: price * quantity }).returning();
185
+ });
186
+ }
187
+ indicators:
188
+ - "db.transaction("
189
+ - "tx.insert("
190
+ - "tx.update("
191
+ patterns:
192
+ data_flow:
193
+ direction: "API Route/Server Action → Service → Query Function (src/db/queries/) → Drizzle → Database"
194
+ rules:
195
+ - "src/db/schema.ts is the source of truth — TypeScript types are inferred from it, never written manually."
196
+ - "db in src/lib/db.ts is the only Drizzle instance — imported by all query files."
197
+ - "Query functions in src/db/queries/ own all SQL — services call these functions, never db.select() directly."
198
+ - "Migrations are generated by drizzle-kit from schema changes — never write migration SQL manually."
199
+ - "Use $inferSelect and $inferInsert for parameter and return types — keeps types in sync with schema automatically."
200
+ error_handling:
201
+ recommended: "Drizzle does not wrap errors — database errors bubble as driver-level errors. Check error.code: '23505' = PostgreSQL unique violation, '23503' = foreign key violation. Wrap in try/catch in repositories."
202
+ naming:
203
+ schema: "src/db/schema.ts — all pgTable/mysqlTable definitions with $inferSelect/$inferInsert exports"
204
+ connection: "src/lib/db.ts — drizzle(connection, { schema }) singleton"
205
+ queries: "src/db/queries/[resource].ts — e.g. users.ts, posts.ts"
206
+ config: "drizzle.config.ts — drizzle-kit configuration at project root"
207
+ anti_patterns:
208
+ - id: raw_sql_strings
209
+ severity: warning
210
+ description: "Building SQL queries with string concatenation or unparameterized template literals. Even with Drizzle available, some developers fall back to raw strings when queries get complex. This loses type safety and creates SQL injection risk."
211
+ bad_example: |
212
+ // ❌ String concatenation — SQL injection risk, no type safety
213
+ const email = req.body.email;
214
+ await db.execute(`SELECT * FROM users WHERE email = '${email}'`);
215
+ // If email = "' OR '1'='1" → returns all users
216
+ good_example: |
217
+ // ✓ Drizzle query builder — parameterized and type-safe
218
+ import { eq } from 'drizzle-orm';
219
+ const user = await db.select().from(users).where(eq(users.email, email));
220
+
221
+ // ✓ For complex SQL: use sql tag with parameters (not string concat)
222
+ import { sql } from 'drizzle-orm';
223
+ const result = await db.execute(sql`SELECT * FROM users WHERE email = ${email}`);
224
+ - id: multiple_db_connections
225
+ severity: critical
226
+ description: "Creating a new Drizzle instance (and underlying driver connection/pool) in multiple files instead of importing from src/lib/db.ts. In serverless environments, each function invocation may create a new connection pool, exhausting database connections under load."
227
+ bad_example: |
228
+ // ❌ New drizzle instance in every repository file
229
+ // src/db/queries/users.ts
230
+ import postgres from 'postgres';
231
+ import { drizzle } from 'drizzle-orm/postgres-js';
232
+ const db = drizzle(postgres(process.env.DATABASE_URL!)); // new connection pool
233
+
234
+ // src/db/queries/posts.ts
235
+ const db = drizzle(postgres(process.env.DATABASE_URL!)); // another pool
236
+ good_example: |
237
+ // ✓ Import the singleton from src/lib/db.ts everywhere
238
+ import { db } from '@/lib/db';
239
+ const users = await db.select().from(usersTable).where(eq(usersTable.id, id));
240
+ - id: schema_outside_db_dir
241
+ severity: warning
242
+ description: "Defining table schemas outside src/db/schema.ts. drizzle-kit looks for schemas in the path configured in drizzle.config.ts — schemas defined elsewhere are invisible to the migration tool and won't generate migrations."
243
+ bad_example: |
244
+ // ❌ Schema scattered in model files — drizzle-kit can't find it
245
+ // src/models/user.model.ts
246
+ export const users = pgTable('users', { id: text('id') });
247
+ // drizzle-kit generate → no migration generated for this table
248
+ good_example: |
249
+ // ✓ All schemas in src/db/schema.ts — drizzle-kit finds them all
250
+ // drizzle.config.ts: schema: './src/db/schema.ts'
251
+ export const users = pgTable('users', { id: uuid('id').primaryKey() });
252
+ - id: manual_type_interfaces
253
+ severity: warning
254
+ description: "Writing TypeScript interfaces for database row types by hand instead of using `typeof table.$inferSelect`. When the schema changes, the manual interface goes stale — the database returns columns the interface doesn't know about, or the interface references columns that no longer exist."
255
+ bad_example: |
256
+ // ❌ Manual type — gets out of sync when schema changes
257
+ interface User {
258
+ id: string;
259
+ email: string;
260
+ name: string; // What if 'name' is renamed to 'fullName' in the schema?
261
+ }
262
+ good_example: |
263
+ // ✓ Inferred type — always in sync with schema.ts
264
+ import { users } from '@/db/schema';
265
+ export type User = typeof users.$inferSelect;
266
+ export type NewUser = typeof users.$inferInsert;
267
+ - id: push_in_production
268
+ severity: critical
269
+ description: "Using `npx drizzle-kit push` in production environments. `push` modifies the database directly without creating a migration file — there is no migration history, no rollback path, and no CI/CD audit trail. Use `push` only for local prototyping; use `migrate` in all deployed environments."
270
+ bad_example: |
271
+ # ❌ In production CI/CD pipeline — no migration history
272
+ npx drizzle-kit push
273
+ # Database changed but no migration file created — next deploy may push again
274
+ good_example: |
275
+ # ✓ Production: apply versioned migrations from the drizzle/ directory
276
+ npx drizzle-kit migrate
277
+ # Each migration is a timestamped SQL file with a checksum — safe to replay
@@ -0,0 +1,290 @@
1
+ schema_version: "2.0.0"
2
+ id: mongoose
3
+ name: "Mongoose"
4
+ version: "2.0.0"
5
+ description: "Mongoose ODM for MongoDB with typed schemas, single connection, model-layer queries with .lean(), and index-backed query performance."
6
+ category: pattern
7
+ language: javascript
8
+ frameworks:
9
+ - mongoose
10
+ dependencies:
11
+ none:
12
+ - prisma
13
+ - drizzle-orm
14
+ - "@prisma/client"
15
+ detection:
16
+ dependencies:
17
+ any:
18
+ - mongoose
19
+ source_indicators:
20
+ - "mongoose.connect("
21
+ - "new Schema("
22
+ - "model("
23
+ - "mongoose.model("
24
+ - "from 'mongoose'"
25
+ structure:
26
+ required_dirs:
27
+ - path: src/models
28
+ purpose: "Mongoose schema and model definitions — one file per collection (e.g., user.model.ts, post.model.ts). Each file exports the Mongoose model and the TypeScript interface that describes the document shape. Models are the only layer that calls mongoose.model() and defines Schema instances."
29
+ - path: src/lib
30
+ purpose: "Database connection singleton in db.ts — the only place that calls mongoose.connect(). This module is imported once (in the app entry point or Next.js instrumentation) and handles reconnect logic. Never call mongoose.connect() in route handlers or models."
31
+ recommended_dirs:
32
+ - path: src/repositories
33
+ purpose: "Data access layer — one file per model (e.g., user.repository.ts) containing all queries for that collection. Repository functions call Model.find(), Model.findById(), etc. and return typed lean documents. Services call repositories — never the Mongoose Model directly."
34
+ - path: src/types
35
+ purpose: "Shared TypeScript interfaces for document types (HydratedDocument<User>, lean User). Keeps model files focused on schema definition and separates type definitions for reuse across layers."
36
+ separation:
37
+ rules:
38
+ - concern: schema_definition
39
+ belongs_in: src/models
40
+ rule_text: "Define all Mongoose schemas in src/models/ with explicit types matching the TypeScript interface. Always include { timestamps: true } in schema options — Mongoose then manages createdAt and updatedAt automatically. Add index: true or unique: true to fields you query frequently."
41
+ example: |
42
+ // src/models/user.model.ts
43
+ import { Schema, model, type Document } from 'mongoose';
44
+
45
+ export interface IUser {
46
+ email: string;
47
+ name: string;
48
+ role: 'user' | 'admin';
49
+ profileId?: string;
50
+ createdAt: Date; // added by { timestamps: true }
51
+ updatedAt: Date;
52
+ }
53
+
54
+ const userSchema = new Schema<IUser>(
55
+ {
56
+ email: { type: String, required: true, unique: true, lowercase: true, trim: true },
57
+ name: { type: String, required: true, trim: true },
58
+ role: { type: String, enum: ['user', 'admin'], default: 'user' },
59
+ profileId: { type: String, index: true }, // indexed for fast lookup
60
+ },
61
+ {
62
+ timestamps: true, // auto-manages createdAt and updatedAt
63
+ toJSON: { virtuals: true }, // include virtuals in JSON output
64
+ }
65
+ );
66
+
67
+ export const User = model<IUser>('User', userSchema);
68
+ indicators:
69
+ - "new Schema("
70
+ - "mongoose.model("
71
+ - "model<I"
72
+ - "timestamps: true"
73
+ - concern: single_connection
74
+ belongs_in: src/lib
75
+ rule_text: "Call mongoose.connect() exactly once in src/lib/db.ts using the cached connection pattern. In Next.js, the connection is shared across hot reloads — without caching the connection promise, each module reload creates a new connection to MongoDB until the pool limit is hit."
76
+ example: |
77
+ // src/lib/db.ts
78
+ import mongoose from 'mongoose';
79
+
80
+ declare global {
81
+ // eslint-disable-next-line no-var
82
+ var mongoose: { conn: typeof import('mongoose') | null; promise: Promise<typeof import('mongoose')> | null };
83
+ }
84
+
85
+ let cached = global.mongoose ?? { conn: null, promise: null };
86
+ if (!global.mongoose) global.mongoose = cached;
87
+
88
+ export async function connectDB() {
89
+ if (cached.conn) return cached.conn;
90
+ if (!cached.promise) {
91
+ cached.promise = mongoose.connect(process.env.MONGODB_URI!, {
92
+ bufferCommands: false,
93
+ maxPoolSize: process.env.NODE_ENV === 'production' ? 10 : 2,
94
+ });
95
+ }
96
+ cached.conn = await cached.promise;
97
+ return cached.conn;
98
+ }
99
+ anti_indicators:
100
+ - "mongoose.connect("
101
+ - concern: query_performance
102
+ belongs_in: src/repositories
103
+ rule_text: "Use .lean() on read queries that don't need Mongoose document methods (.save(), virtuals, middleware). lean() returns plain JavaScript objects instead of full Mongoose documents — 2-10x faster for large result sets. Use .select() to limit returned fields and .limit() to cap result size."
104
+ example: |
105
+ // src/repositories/user.repository.ts
106
+ import { connectDB } from '@/lib/db';
107
+ import { User, type IUser } from '@/models/user.model';
108
+
109
+ // lean() + select(): fast, minimal-payload read
110
+ export async function listUsers(limit = 50): Promise<Partial<IUser>[]> {
111
+ await connectDB();
112
+ return User.find({ role: 'user' })
113
+ .select('email name createdAt') // only the fields callers need
114
+ .sort({ createdAt: -1 })
115
+ .limit(limit)
116
+ .lean(); // plain objects — no Mongoose overhead
117
+ }
118
+
119
+ // No .lean() when mutation is needed (need .save())
120
+ export async function findUserById(id: string) {
121
+ await connectDB();
122
+ return User.findById(id); // returns HydratedDocument<IUser>
123
+ }
124
+ indicators:
125
+ - ".lean()"
126
+ - ".find({"
127
+ - ".sort("
128
+ - ".select("
129
+ - ".limit("
130
+ - concern: population
131
+ belongs_in: src/repositories
132
+ rule_text: "Use .populate() to fetch referenced documents in a single aggregated query instead of manually fetching references in a loop. Pre-populate only the fields the caller needs using the second argument or select option."
133
+ example: |
134
+ // src/repositories/post.repository.ts
135
+ export async function listPostsWithAuthors(limit = 20) {
136
+ await connectDB();
137
+ return Post.find({ publishedAt: { $ne: null } })
138
+ .populate<{ author: IUser }>('author', 'name email') // only name + email from User
139
+ .sort({ publishedAt: -1 })
140
+ .limit(limit)
141
+ .lean();
142
+ }
143
+ indicators:
144
+ - ".populate("
145
+ - concern: transactions
146
+ belongs_in: src/repositories
147
+ rule_text: "Use Mongoose sessions and transactions for operations that span multiple collections and must be atomic. Requires a MongoDB replica set (available on Atlas M10+ or local replica set). Pass the session to every operation that participates in the transaction."
148
+ example: |
149
+ // src/repositories/transfer.repository.ts
150
+ import mongoose from 'mongoose';
151
+ import { Account } from '@/models/account.model';
152
+ import { Transaction } from '@/models/transaction.model';
153
+
154
+ export async function transferFunds(fromId: string, toId: string, amount: number) {
155
+ const session = await mongoose.startSession();
156
+ session.startTransaction();
157
+ try {
158
+ await Account.findByIdAndUpdate(fromId, { $inc: { balance: -amount } }, { session });
159
+ await Account.findByIdAndUpdate(toId, { $inc: { balance: amount } }, { session });
160
+ await Transaction.create([{ fromId, toId, amount }], { session });
161
+ await session.commitTransaction();
162
+ } catch (err) {
163
+ await session.abortTransaction();
164
+ throw err;
165
+ } finally {
166
+ session.endSession();
167
+ }
168
+ }
169
+ indicators:
170
+ - "mongoose.startSession"
171
+ - "startTransaction"
172
+ - "commitTransaction"
173
+ - "abortTransaction"
174
+ patterns:
175
+ data_flow:
176
+ direction: "API Route/Server Action → Service → Repository → Mongoose Model → MongoDB"
177
+ rules:
178
+ - "src/lib/db.ts connectDB() is called before every repository operation — safely no-ops if already connected."
179
+ - "Repositories return plain JavaScript objects (via .lean()) or HydratedDocument only when mutation is needed."
180
+ - "Services call repository functions — never import Mongoose Models directly."
181
+ - "Use .populate() for relations — never loop with findById() to fetch referenced documents."
182
+ - "All queries on frequently-filtered fields have matching index: true in the schema."
183
+ error_handling:
184
+ recommended: "Mongoose validation errors are instanceof mongoose.Error.ValidationError — catch separately from MongoDB driver errors. For duplicate key errors, check err.code === 11000 (MongoDB duplicate key)."
185
+ naming:
186
+ models: "src/models/[resource].model.ts — e.g. user.model.ts exports User model + IUser interface"
187
+ connection: "src/lib/db.ts — exports connectDB() using cached connection pattern"
188
+ repositories: "src/repositories/[resource].repository.ts — e.g. user.repository.ts"
189
+ anti_patterns:
190
+ - id: multiple_connections
191
+ severity: critical
192
+ description: "Calling mongoose.connect() in individual model files, route handlers, or repository functions. In serverless environments, each function invocation recreates the connection — MongoDB Atlas free-tier clusters hit connection limits instantly."
193
+ bad_example: |
194
+ // ❌ mongoose.connect() in a route handler — new connection per request
195
+ export async function GET() {
196
+ await mongoose.connect(process.env.MONGODB_URI!); // new connection every request!
197
+ const users = await User.find().lean();
198
+ return Response.json(users);
199
+ }
200
+ good_example: |
201
+ // ✓ Call connectDB() which uses the cached singleton
202
+ import { connectDB } from '@/lib/db';
203
+ export async function GET() {
204
+ await connectDB(); // no-op if already connected
205
+ const users = await User.find().lean();
206
+ return Response.json(users);
207
+ }
208
+ - id: schema_in_route
209
+ severity: critical
210
+ description: "Defining Mongoose schemas inside route handlers or service functions — the schema and model are recreated on every call. In Next.js, this also triggers a `Cannot overwrite model once compiled` error after HMR because the model name is already registered."
211
+ bad_example: |
212
+ // ❌ Schema defined inside a route handler — recreated every call
213
+ export async function GET() {
214
+ const UserSchema = new Schema({ email: String }); // recreated every request
215
+ const User = model('User', UserSchema); // throws on second call: model already registered
216
+ return Response.json(await User.find());
217
+ }
218
+ good_example: |
219
+ // ✓ Schema defined once in src/models/user.model.ts
220
+ // import the model from there in all route handlers and repositories
221
+ import { User } from '@/models/user.model';
222
+ - id: populate_n_plus_one
223
+ severity: warning
224
+ description: "Fetching documents with references and then manually looping to fetch each referenced document. This creates N+1 database round trips — one query for the list, then one per document for its reference."
225
+ bad_example: |
226
+ // ❌ N+1: 1 query for posts + 1 query per post for its author
227
+ const posts = await Post.find({ publishedAt: { $ne: null } }).lean();
228
+ for (const post of posts) {
229
+ post.author = await User.findById(post.authorId).lean(); // N separate queries
230
+ }
231
+ good_example: |
232
+ // ✓ Single aggregated query via populate
233
+ const posts = await Post.find({ publishedAt: { $ne: null } })
234
+ .populate('author', 'name email') // single JOIN-like query
235
+ .lean();
236
+ - id: missing_schema_index
237
+ severity: warning
238
+ description: "Querying a field frequently without adding index: true (or a compound index) to the schema. Without an index, MongoDB scans every document in the collection on each query — O(n) queries that degrade linearly as data grows."
239
+ bad_example: |
240
+ // ❌ userId queried often but no index — full collection scan on every query
241
+ const userSchema = new Schema({
242
+ email: String,
243
+ userId: String, // frequently queried but not indexed
244
+ }, { timestamps: true });
245
+
246
+ // In repository:
247
+ User.find({ userId }) // COLLSCAN on 1M documents = slow
248
+ good_example: |
249
+ // ✓ Add index to frequently-queried fields
250
+ const userSchema = new Schema({
251
+ email: { type: String, unique: true }, // unique implies index
252
+ userId: { type: String, index: true }, // explicit index for fast lookup
253
+ }, { timestamps: true });
254
+ - id: schema_without_timestamps
255
+ severity: warning
256
+ description: "Defining schemas without `{ timestamps: true }` and manually managing createdAt/updatedAt fields. Manual timestamp management is error-prone — it's easy to forget to update updatedAt on every modification."
257
+ bad_example: |
258
+ // ❌ Manual timestamps — easy to forget updatedAt on updates
259
+ const postSchema = new Schema({
260
+ title: String,
261
+ content: String,
262
+ createdAt: { type: Date, default: Date.now }, // missing from updates
263
+ updatedAt: Date, // must manually set on every save
264
+ });
265
+ good_example: |
266
+ // ✓ Mongoose manages both createdAt and updatedAt automatically
267
+ const postSchema = new Schema(
268
+ {
269
+ title: { type: String, required: true },
270
+ content: String,
271
+ },
272
+ { timestamps: true } // auto-sets createdAt and updatedAt
273
+ );
274
+ - id: excessive_lean
275
+ severity: warning
276
+ description: "Using .lean() on queries where you then call Mongoose document methods (.save(), .validate(), middleware hooks). .lean() returns plain JavaScript objects — calling .save() on them throws at runtime."
277
+ bad_example: |
278
+ // ❌ .lean() then .save() — TypeError at runtime
279
+ const user = await User.findById(id).lean(); // plain object, not a Mongoose doc
280
+ user.name = 'New Name';
281
+ await user.save(); // TypeError: user.save is not a function
282
+ good_example: |
283
+ // ✓ Skip .lean() when you need to mutate and save
284
+ const user = await User.findById(id); // HydratedDocument<IUser>
285
+ if (!user) throw new Error('User not found');
286
+ user.name = 'New Name';
287
+ await user.save(); // works — Mongoose document method
288
+
289
+ // ✓ Or use findByIdAndUpdate for simpler mutations
290
+ await User.findByIdAndUpdate(id, { name: 'New Name' }, { new: true });