@mr.dj2u/knowledge 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.
- package/dist/content/checklists/ship-test-loop.md +16 -0
- package/dist/content/checklists/unified-agent-bundle-validation.md +8 -0
- package/dist/content/examples/ship-test-loop.md +13 -0
- package/dist/content/examples/unified-agent-bundle-bootstrap.md +8 -0
- package/dist/content/guides/animation-performance.md +30 -0
- package/dist/content/guides/post-create-onboarding.md +113 -0
- package/dist/content/patterns/api/api-routes.md +314 -0
- package/dist/content/patterns/api/error-handling.md +311 -0
- package/dist/content/patterns/database/drizzle-schema.md +280 -0
- package/dist/content/patterns/database/migrations.md +365 -0
- package/dist/content/patterns/database/query-organization.md +537 -0
- package/dist/content/patterns/database/relations.md +450 -0
- package/dist/content/patterns/deployment/build-configuration.md +452 -0
- package/dist/content/patterns/deployment/ci-cd-patterns.md +448 -0
- package/dist/content/patterns/deployment/environment-config.md +380 -0
- package/dist/content/patterns/deployment/hosting-setup.md +425 -0
- package/dist/content/patterns/project/configuration-patterns.md +459 -0
- package/dist/content/patterns/project/documentation-org.md +506 -0
- package/dist/content/patterns/project/folder-structure.md +398 -0
- package/dist/content/patterns/project/library-exports.md +465 -0
- package/dist/content/patterns/project/monorepo-structure.md +500 -0
- package/dist/content/patterns/routing/dynamic-routes.md +221 -0
- package/dist/content/patterns/routing/file-based-routing.md +186 -0
- package/dist/content/patterns/routing/route-groups.md +429 -0
- package/dist/content/patterns/state/persistence-middleware.md +521 -0
- package/dist/content/patterns/state/selector-hooks.md +538 -0
- package/dist/content/patterns/state/store-organization.md +539 -0
- package/dist/content/patterns/state/zustand-patterns.md +348 -0
- package/dist/content/patterns/styling/component-styling.md +468 -0
- package/dist/content/patterns/styling/responsive-patterns.md +398 -0
- package/dist/content/patterns/styling/theme-configuration.md +426 -0
- package/dist/content/patterns/styling/uniwind-setup.md +412 -0
- package/dist/content/prompts/continue-development.md +27 -0
- package/dist/content/prompts/create-expo-super-stack.md +29 -0
- package/dist/content/prompts/fix-seo.md +29 -0
- package/dist/content/prompts/onboard-new-expo-app.md +11 -0
- package/dist/content/prompts/prepare-deploy.md +29 -0
- package/dist/content/prompts/project-research-plan.md +29 -0
- package/dist/content/prompts/review-expo-project.md +29 -0
- package/dist/content/prompts/run-doctor.md +30 -0
- package/dist/content/prompts/ship-test-loop.md +24 -0
- package/dist/content/reference/create-expo-stack-uniwind.md +29 -0
- package/dist/content/reference/doctor-dogfood.md +42 -0
- package/dist/content/reference/mcp-sdk-transport.md +30 -0
- package/dist/content/reference/package-ci-patterns.md +24 -0
- package/dist/content/reference/reference-repo-evacuation.md +31 -0
- package/dist/content/resource-index.json +67 -0
- package/dist/content/rules/app-folder-architecture.md +13 -0
- package/dist/content/rules/env-hygiene.md +9 -0
- package/dist/content/rules/seo-metadata.md +7 -0
- package/dist/content/rules/ssr-safety.md +9 -0
- package/dist/content/skills/api-routes.md +33 -0
- package/dist/content/skills/continue-development.md +31 -0
- package/dist/content/skills/debugging.md +31 -0
- package/dist/content/skills/deployment.md +32 -0
- package/dist/content/skills/dev-server-management.md +31 -0
- package/dist/content/skills/env-vars.md +32 -0
- package/dist/content/skills/expo-router-architecture.md +33 -0
- package/dist/content/skills/expo-ssr-safety.md +32 -0
- package/dist/content/skills/plugin-creation.md +41 -0
- package/dist/content/skills/production-server-patterns.md +31 -0
- package/dist/content/skills/project-onboarding.md +31 -0
- package/dist/content/skills/research-plan-intake.md +31 -0
- package/dist/content/skills/seo-metadata.md +31 -0
- package/dist/content/skills/super-stack-startup.md +31 -0
- package/dist/content/skills/uniwind-theming.md +32 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +369 -0
- package/dist/index.js.map +1 -0
- package/dist/patterns/index.d.ts +78 -0
- package/dist/patterns/index.d.ts.map +1 -0
- package/dist/patterns/index.js +264 -0
- package/dist/patterns/index.js.map +1 -0
- package/dist/prompts/index.d.ts +35 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +270 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/skills/index.d.ts +3 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +2 -0
- package/dist/skills/index.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
# Query Organization Pattern
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
Query organization involves structuring database query functions into dedicated files (`*Queries.ts`) for each schema. This pattern provides a clean data access layer where queries are type-safe, reusable, and organized by domain (users, posts, comments, etc.).
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
**Organize queries when:**
|
|
10
|
+
- ✅ Executing SELECT, INSERT, UPDATE, DELETE operations
|
|
11
|
+
- ✅ Need to reuse complex queries across components
|
|
12
|
+
- ✅ Want centralized data access logic
|
|
13
|
+
- ✅ Building filters, pagination, sorting
|
|
14
|
+
- ✅ Implementing complex WHERE clauses
|
|
15
|
+
- ✅ Ensuring type safety across application
|
|
16
|
+
|
|
17
|
+
## Core Concepts
|
|
18
|
+
|
|
19
|
+
**Query Organization Structure:**
|
|
20
|
+
```
|
|
21
|
+
src/db/
|
|
22
|
+
├── *Schema.ts # Schema definitions
|
|
23
|
+
├── relations.ts # Relationship definitions
|
|
24
|
+
├── *Queries.ts # Query functions (one file per schema)
|
|
25
|
+
├── queryHelpers.ts # Shared query utilities
|
|
26
|
+
└── index.ts # Exports all queries
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Principles:**
|
|
30
|
+
1. One `*Queries.ts` file per schema domain
|
|
31
|
+
2. Export type-safe async functions
|
|
32
|
+
3. Use Drizzle ORM for type inference
|
|
33
|
+
4. Centralize WHERE clauses and filters
|
|
34
|
+
5. Enable selective column loading
|
|
35
|
+
6. Support pagination and sorting
|
|
36
|
+
|
|
37
|
+
## Code Examples
|
|
38
|
+
|
|
39
|
+
### Basic Query Organization
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// File: src/db/profilesSchema.ts
|
|
43
|
+
import { pgTable, uuid, text, timestamp } from 'drizzle-orm/pg-core';
|
|
44
|
+
|
|
45
|
+
export const profiles = pgTable('profiles', {
|
|
46
|
+
id: uuid('id').defaultRandom().primaryKey(),
|
|
47
|
+
username: text('username').notNull().unique(),
|
|
48
|
+
email: text('email').notNull().unique(),
|
|
49
|
+
bio: text('bio'),
|
|
50
|
+
profileImage: text('profile_image'),
|
|
51
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
52
|
+
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export type Profile = typeof profiles.$inferSelect;
|
|
56
|
+
export type NewProfile = typeof profiles.$inferInsert;
|
|
57
|
+
|
|
58
|
+
// File: src/db/profilesQueries.ts
|
|
59
|
+
import { db } from './index';
|
|
60
|
+
import { profiles, type Profile, type NewProfile } from './profilesSchema';
|
|
61
|
+
import { eq, ilike, desc, asc } from 'drizzle-orm';
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get profile by ID
|
|
65
|
+
*/
|
|
66
|
+
export async function getProfileById(id: string): Promise<Profile | undefined> {
|
|
67
|
+
const [profile] = await db
|
|
68
|
+
.select()
|
|
69
|
+
.from(profiles)
|
|
70
|
+
.where(eq(profiles.id, id))
|
|
71
|
+
.limit(1);
|
|
72
|
+
|
|
73
|
+
return profile;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get profile by username
|
|
78
|
+
*/
|
|
79
|
+
export async function getProfileByUsername(username: string): Promise<Profile | undefined> {
|
|
80
|
+
const [profile] = await db
|
|
81
|
+
.select()
|
|
82
|
+
.from(profiles)
|
|
83
|
+
.where(eq(profiles.username, username))
|
|
84
|
+
.limit(1);
|
|
85
|
+
|
|
86
|
+
return profile;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get all profiles
|
|
91
|
+
*/
|
|
92
|
+
export async function getAllProfiles(): Promise<Profile[]> {
|
|
93
|
+
return await db
|
|
94
|
+
.select()
|
|
95
|
+
.from(profiles)
|
|
96
|
+
.orderBy(desc(profiles.createdAt));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Create new profile
|
|
101
|
+
*/
|
|
102
|
+
export async function createProfile(data: NewProfile): Promise<Profile> {
|
|
103
|
+
const [profile] = await db
|
|
104
|
+
.insert(profiles)
|
|
105
|
+
.values(data)
|
|
106
|
+
.returning();
|
|
107
|
+
|
|
108
|
+
return profile;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Update profile
|
|
113
|
+
*/
|
|
114
|
+
export async function updateProfile(
|
|
115
|
+
id: string,
|
|
116
|
+
data: Partial<NewProfile>
|
|
117
|
+
): Promise<Profile> {
|
|
118
|
+
const [profile] = await db
|
|
119
|
+
.update(profiles)
|
|
120
|
+
.set({
|
|
121
|
+
...data,
|
|
122
|
+
updatedAt: new Date(),
|
|
123
|
+
})
|
|
124
|
+
.where(eq(profiles.id, id))
|
|
125
|
+
.returning();
|
|
126
|
+
|
|
127
|
+
return profile;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Delete profile
|
|
132
|
+
*/
|
|
133
|
+
export async function deleteProfile(id: string): Promise<void> {
|
|
134
|
+
await db.delete(profiles).where(eq(profiles.id, id));
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Advanced Queries: Filtering & Pagination
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// File: src/db/postsQueries.ts
|
|
142
|
+
import { db } from './index';
|
|
143
|
+
import { posts, profiles } from './schemas';
|
|
144
|
+
import { eq, desc, and, or, ilike, gt, lt } from 'drizzle-orm';
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get posts by user with pagination
|
|
148
|
+
*/
|
|
149
|
+
export async function getUserPosts(
|
|
150
|
+
userId: string,
|
|
151
|
+
limit: number = 20,
|
|
152
|
+
offset: number = 0
|
|
153
|
+
): Promise<Post[]> {
|
|
154
|
+
return await db
|
|
155
|
+
.select()
|
|
156
|
+
.from(posts)
|
|
157
|
+
.where(eq(posts.userId, userId))
|
|
158
|
+
.orderBy(desc(posts.createdAt))
|
|
159
|
+
.limit(limit)
|
|
160
|
+
.offset(offset);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Search posts by title or content
|
|
165
|
+
*/
|
|
166
|
+
export async function searchPosts(query: string): Promise<Post[]> {
|
|
167
|
+
return await db
|
|
168
|
+
.select()
|
|
169
|
+
.from(posts)
|
|
170
|
+
.where(
|
|
171
|
+
or(
|
|
172
|
+
ilike(posts.title, `%${query}%`),
|
|
173
|
+
ilike(posts.content, `%${query}%`)
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
.orderBy(desc(posts.createdAt));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get trending posts (by like count and date)
|
|
181
|
+
*/
|
|
182
|
+
export async function getTrendingPosts(hours: number = 24): Promise<Post[]> {
|
|
183
|
+
const since = new Date(Date.now() - hours * 60 * 60 * 1000);
|
|
184
|
+
|
|
185
|
+
return await db
|
|
186
|
+
.select()
|
|
187
|
+
.from(posts)
|
|
188
|
+
.where(gt(posts.createdAt, since))
|
|
189
|
+
.orderBy((p) => desc(p.likes))
|
|
190
|
+
.limit(10);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get posts with filters
|
|
195
|
+
*/
|
|
196
|
+
interface PostFilters {
|
|
197
|
+
userId?: string;
|
|
198
|
+
published?: boolean;
|
|
199
|
+
createdAfter?: Date;
|
|
200
|
+
createdBefore?: Date;
|
|
201
|
+
searchQuery?: string;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function getPostsWithFilters(
|
|
205
|
+
filters: PostFilters,
|
|
206
|
+
limit: number = 20,
|
|
207
|
+
offset: number = 0
|
|
208
|
+
): Promise<Post[]> {
|
|
209
|
+
const conditions = [];
|
|
210
|
+
|
|
211
|
+
if (filters.userId) {
|
|
212
|
+
conditions.push(eq(posts.userId, filters.userId));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (filters.published !== undefined) {
|
|
216
|
+
conditions.push(eq(posts.published, filters.published));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (filters.createdAfter) {
|
|
220
|
+
conditions.push(gt(posts.createdAt, filters.createdAfter));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (filters.createdBefore) {
|
|
224
|
+
conditions.push(lt(posts.createdAt, filters.createdBefore));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (filters.searchQuery) {
|
|
228
|
+
conditions.push(
|
|
229
|
+
or(
|
|
230
|
+
ilike(posts.title, `%${filters.searchQuery}%`),
|
|
231
|
+
ilike(posts.content, `%${filters.searchQuery}%`)
|
|
232
|
+
)
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return await db
|
|
237
|
+
.select()
|
|
238
|
+
.from(posts)
|
|
239
|
+
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
240
|
+
.orderBy(desc(posts.createdAt))
|
|
241
|
+
.limit(limit)
|
|
242
|
+
.offset(offset);
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Queries with Relations
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// File: src/db/postsQueries.ts
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Get post with author
|
|
253
|
+
*/
|
|
254
|
+
export async function getPostWithAuthor(postId: string) {
|
|
255
|
+
return await db.query.posts.findFirst({
|
|
256
|
+
where: eq(posts.id, postId),
|
|
257
|
+
with: {
|
|
258
|
+
author: true, // Loads profile data
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Get posts with authors and comment count
|
|
265
|
+
*/
|
|
266
|
+
export async function getPostsWithAuthors(limit: number = 20) {
|
|
267
|
+
return await db.query.posts.findMany({
|
|
268
|
+
orderBy: desc(posts.createdAt),
|
|
269
|
+
limit,
|
|
270
|
+
with: {
|
|
271
|
+
author: {
|
|
272
|
+
columns: {
|
|
273
|
+
id: true,
|
|
274
|
+
username: true,
|
|
275
|
+
profileImage: true,
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Get profile with posts
|
|
284
|
+
*/
|
|
285
|
+
export async function getProfileWithPosts(profileId: string) {
|
|
286
|
+
return await db.query.profiles.findFirst({
|
|
287
|
+
where: eq(profiles.id, profileId),
|
|
288
|
+
with: {
|
|
289
|
+
posts: {
|
|
290
|
+
orderBy: desc(posts.createdAt),
|
|
291
|
+
limit: 10,
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Aggregation Queries
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
// File: src/db/aggregateQueries.ts
|
|
302
|
+
import { count, sum, avg, max, min } from 'drizzle-orm';
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Get post statistics
|
|
306
|
+
*/
|
|
307
|
+
export async function getPostStats(userId: string) {
|
|
308
|
+
const [stats] = await db
|
|
309
|
+
.select({
|
|
310
|
+
totalPosts: count(posts.id),
|
|
311
|
+
totalLikes: sum(posts.likes),
|
|
312
|
+
avgLikes: avg(posts.likes),
|
|
313
|
+
maxLikes: max(posts.likes),
|
|
314
|
+
minLikes: min(posts.likes),
|
|
315
|
+
})
|
|
316
|
+
.from(posts)
|
|
317
|
+
.where(eq(posts.userId, userId));
|
|
318
|
+
|
|
319
|
+
return stats;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Get user activity metrics
|
|
324
|
+
*/
|
|
325
|
+
export async function getUserMetrics(userId: string) {
|
|
326
|
+
const [metrics] = await db
|
|
327
|
+
.select({
|
|
328
|
+
postsCount: count(posts.id),
|
|
329
|
+
commentsCount: count(comments.id),
|
|
330
|
+
likesReceived: sum(posts.likes),
|
|
331
|
+
})
|
|
332
|
+
.from(posts)
|
|
333
|
+
.leftJoin(comments, eq(comments.postId, posts.id))
|
|
334
|
+
.where(eq(posts.userId, userId));
|
|
335
|
+
|
|
336
|
+
return metrics;
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Batch Operations
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
// File: src/db/batchQueries.ts
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Create multiple posts
|
|
347
|
+
*/
|
|
348
|
+
export async function createManyPosts(posts: NewPost[]): Promise<Post[]> {
|
|
349
|
+
return await db
|
|
350
|
+
.insert(posts)
|
|
351
|
+
.values(posts)
|
|
352
|
+
.returning();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Update multiple posts (bulk update)
|
|
357
|
+
*/
|
|
358
|
+
export async function updateManyPosts(
|
|
359
|
+
ids: string[],
|
|
360
|
+
data: Partial<NewPost>
|
|
361
|
+
): Promise<void> {
|
|
362
|
+
await db
|
|
363
|
+
.update(posts)
|
|
364
|
+
.set({
|
|
365
|
+
...data,
|
|
366
|
+
updatedAt: new Date(),
|
|
367
|
+
})
|
|
368
|
+
.where(inArray(posts.id, ids));
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Delete multiple posts
|
|
373
|
+
*/
|
|
374
|
+
export async function deleteManyPosts(ids: string[]): Promise<void> {
|
|
375
|
+
await db
|
|
376
|
+
.delete(posts)
|
|
377
|
+
.where(inArray(posts.id, ids));
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Query Helper Functions
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
// File: src/db/queryHelpers.ts
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Pagination helper - returns paginated results + total count
|
|
388
|
+
*/
|
|
389
|
+
export async function paginate<T>(
|
|
390
|
+
query: any,
|
|
391
|
+
page: number = 1,
|
|
392
|
+
limit: number = 20
|
|
393
|
+
) {
|
|
394
|
+
const offset = (page - 1) * limit;
|
|
395
|
+
|
|
396
|
+
const results = await query.limit(limit).offset(offset);
|
|
397
|
+
const [{ total }] = await db
|
|
398
|
+
.select({ total: count() })
|
|
399
|
+
.from(/* table */);
|
|
400
|
+
|
|
401
|
+
return {
|
|
402
|
+
results,
|
|
403
|
+
total,
|
|
404
|
+
page,
|
|
405
|
+
limit,
|
|
406
|
+
totalPages: Math.ceil(total / limit),
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Sort helper - standardize sorting
|
|
412
|
+
*/
|
|
413
|
+
export function buildSort(sortBy?: string, order: 'asc' | 'desc' = 'desc') {
|
|
414
|
+
const sortMap: Record<string, any> = {
|
|
415
|
+
'recent': desc(posts.createdAt),
|
|
416
|
+
'popular': desc(posts.likes),
|
|
417
|
+
'oldest': asc(posts.createdAt),
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
return sortMap[sortBy] || sortMap['recent'];
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
## Query Organization Best Practices
|
|
425
|
+
|
|
426
|
+
### ✅ DO
|
|
427
|
+
|
|
428
|
+
1. **Organize queries by schema**
|
|
429
|
+
```typescript
|
|
430
|
+
// ✅ RIGHT - One file per domain
|
|
431
|
+
src/db/
|
|
432
|
+
├── profilesQueries.ts
|
|
433
|
+
├── postsQueries.ts
|
|
434
|
+
├── commentsQueries.ts
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
2. **Export from central index**
|
|
438
|
+
```typescript
|
|
439
|
+
// src/db/index.ts
|
|
440
|
+
export * from './profilesQueries';
|
|
441
|
+
export * from './postsQueries';
|
|
442
|
+
export * from './commentsQueries';
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
3. **Use type-safe results**
|
|
446
|
+
```typescript
|
|
447
|
+
// ✅ RIGHT - Explicit return types
|
|
448
|
+
export async function getProfile(id: string): Promise<Profile | undefined> {
|
|
449
|
+
return await db.select().from(profiles).where(eq(profiles.id, id)).limit(1);
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
4. **Load only needed columns**
|
|
454
|
+
```typescript
|
|
455
|
+
// ✅ RIGHT - Select specific columns
|
|
456
|
+
export async function getProfilePreview(id: string) {
|
|
457
|
+
return await db
|
|
458
|
+
.select({
|
|
459
|
+
id: profiles.id,
|
|
460
|
+
username: profiles.username,
|
|
461
|
+
profileImage: profiles.profileImage,
|
|
462
|
+
})
|
|
463
|
+
.from(profiles)
|
|
464
|
+
.where(eq(profiles.id, id))
|
|
465
|
+
.limit(1);
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### ❌ DON'T
|
|
470
|
+
|
|
471
|
+
1. **Don't mix query logic in components**
|
|
472
|
+
```typescript
|
|
473
|
+
// ❌ WRONG - Query logic in React component
|
|
474
|
+
export function ProfileCard() {
|
|
475
|
+
const [profile, setProfile] = useState(null);
|
|
476
|
+
|
|
477
|
+
useEffect(() => {
|
|
478
|
+
db.select().from(profiles).where(...).then(setProfile);
|
|
479
|
+
}, []);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// ✅ RIGHT - Query in dedicated file, use hook
|
|
483
|
+
// src/db/profilesQueries.ts
|
|
484
|
+
export async function getProfile(id: string) { ... }
|
|
485
|
+
|
|
486
|
+
// src/hooks/useProfile.ts
|
|
487
|
+
export function useProfile(id: string) {
|
|
488
|
+
return useQuery(() => getProfile(id));
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
2. **Don't load unnecessary relations**
|
|
493
|
+
```typescript
|
|
494
|
+
// ❌ WRONG - Loading everything
|
|
495
|
+
const post = await db.query.posts.findFirst({
|
|
496
|
+
where: eq(posts.id, postId),
|
|
497
|
+
with: {
|
|
498
|
+
author: true,
|
|
499
|
+
comments: { with: { author: true } },
|
|
500
|
+
likes: true,
|
|
501
|
+
tags: true,
|
|
502
|
+
},
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// ✅ RIGHT - Load only needed relations
|
|
506
|
+
const post = await db.query.posts.findFirst({
|
|
507
|
+
where: eq(posts.id, postId),
|
|
508
|
+
with: {
|
|
509
|
+
author: true,
|
|
510
|
+
},
|
|
511
|
+
});
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
3. **Don't repeat queries across files**
|
|
515
|
+
```typescript
|
|
516
|
+
// ❌ WRONG - Query logic duplicated
|
|
517
|
+
// Component A
|
|
518
|
+
const user = await db.select().from(profiles).where(eq(profiles.id, id));
|
|
519
|
+
|
|
520
|
+
// Component B
|
|
521
|
+
const user = await db.select().from(profiles).where(eq(profiles.id, id));
|
|
522
|
+
|
|
523
|
+
// ✅ RIGHT - Centralize in query file
|
|
524
|
+
// src/db/profilesQueries.ts
|
|
525
|
+
export async function getProfileById(id: string) { ... }
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
## Related Patterns
|
|
529
|
+
|
|
530
|
+
- [Drizzle Schema](./drizzle-schema.md) — Schema definitions
|
|
531
|
+
- [Migrations](./migrations.md) — Schema versioning
|
|
532
|
+
- [Relations](./relations.md) — Table relationships
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
*Pattern extracted from production repositories: core-monorepo, PokePages, quantum-api*
|
|
537
|
+
*Files: src/db/*Queries.ts, query builders, aggregation patterns*
|