@mr.dj2u/knowledge 0.1.7 → 0.1.9

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 (68) hide show
  1. package/dist/content/checklists/push-merge-loop.md +17 -17
  2. package/dist/content/checklists/unified-agent-bundle-validation.md +9 -9
  3. package/dist/content/examples/push-merge-loop.md +14 -14
  4. package/dist/content/examples/unified-agent-bundle-bootstrap.md +8 -8
  5. package/dist/content/guides/post-create-onboarding.md +150 -140
  6. package/dist/content/patterns/api/api-routes.md +313 -313
  7. package/dist/content/patterns/api/error-handling.md +310 -310
  8. package/dist/content/patterns/database/drizzle-schema.md +279 -279
  9. package/dist/content/patterns/database/migrations.md +364 -364
  10. package/dist/content/patterns/database/query-organization.md +536 -536
  11. package/dist/content/patterns/database/relations.md +449 -449
  12. package/dist/content/patterns/deployment/build-configuration.md +440 -440
  13. package/dist/content/patterns/deployment/ci-cd-patterns.md +447 -447
  14. package/dist/content/patterns/deployment/environment-config.md +379 -379
  15. package/dist/content/patterns/deployment/hosting-setup.md +424 -424
  16. package/dist/content/patterns/project/configuration-patterns.md +459 -459
  17. package/dist/content/patterns/project/documentation-org.md +506 -506
  18. package/dist/content/patterns/project/folder-structure.md +397 -397
  19. package/dist/content/patterns/project/library-exports.md +464 -464
  20. package/dist/content/patterns/project/monorepo-structure.md +500 -500
  21. package/dist/content/patterns/routing/dynamic-routes.md +220 -220
  22. package/dist/content/patterns/routing/file-based-routing.md +185 -185
  23. package/dist/content/patterns/routing/route-groups.md +428 -428
  24. package/dist/content/patterns/state/persistence-middleware.md +520 -520
  25. package/dist/content/patterns/state/selector-hooks.md +537 -537
  26. package/dist/content/patterns/state/store-organization.md +538 -538
  27. package/dist/content/patterns/state/zustand-patterns.md +347 -347
  28. package/dist/content/patterns/styling/component-styling.md +467 -467
  29. package/dist/content/patterns/styling/responsive-patterns.md +397 -397
  30. package/dist/content/patterns/styling/theme-configuration.md +425 -425
  31. package/dist/content/patterns/styling/uniwind-setup.md +411 -411
  32. package/dist/content/prompts/continue-development.md +41 -35
  33. package/dist/content/prompts/create-expo-super-stack.md +4 -1
  34. package/dist/content/prompts/fix-seo.md +29 -29
  35. package/dist/content/prompts/onboard-new-expo-app.md +11 -11
  36. package/dist/content/prompts/prepare-deploy.md +29 -29
  37. package/dist/content/prompts/project-research-plan.md +29 -29
  38. package/dist/content/prompts/push-merge-loop.md +25 -25
  39. package/dist/content/prompts/retrospective-project-onboarding.md +23 -0
  40. package/dist/content/prompts/review-expo-project.md +29 -29
  41. package/dist/content/prompts/run-doctor.md +38 -38
  42. package/dist/content/prompts/wrap-up.md +70 -67
  43. package/dist/content/reference/create-expo-stack-uniwind.md +29 -29
  44. package/dist/content/reference/doctor-dogfood.md +1 -1
  45. package/dist/content/reference/mcp-sdk-transport.md +30 -30
  46. package/dist/content/reference/reference-repo-evacuation.md +31 -31
  47. package/dist/content/resource-index.json +1 -0
  48. package/dist/content/rules/env-hygiene.md +10 -0
  49. package/dist/content/rules/ssr-safety.md +7 -0
  50. package/dist/content/skills/api-routes.md +34 -33
  51. package/dist/content/skills/continue-development.md +49 -32
  52. package/dist/content/skills/debugging.md +32 -32
  53. package/dist/content/skills/deployment.md +32 -32
  54. package/dist/content/skills/dev-server-management.md +32 -32
  55. package/dist/content/skills/env-vars.md +32 -32
  56. package/dist/content/skills/expo-router-architecture.md +34 -33
  57. package/dist/content/skills/expo-ssr-safety.md +32 -32
  58. package/dist/content/skills/plugin-creation.md +41 -41
  59. package/dist/content/skills/production-server-patterns.md +31 -31
  60. package/dist/content/skills/project-onboarding.md +35 -31
  61. package/dist/content/skills/research-plan-intake.md +32 -32
  62. package/dist/content/skills/seo-metadata.md +31 -31
  63. package/dist/content/skills/super-stack-startup.md +38 -34
  64. package/dist/content/skills/uniwind-theming.md +32 -32
  65. package/dist/prompts/index.d.ts.map +1 -1
  66. package/dist/prompts/index.js +11 -0
  67. package/dist/prompts/index.js.map +1 -1
  68. package/package.json +7 -1
@@ -1,280 +1,280 @@
1
- # Drizzle ORM Schema Design
2
-
3
- ## Description
4
-
5
- Drizzle ORM schemas define type-safe database tables with full TypeScript inference from database types to application types. Schemas use domain-specific files with shared relations.ts for foreign keys and relationships, enabling compile-time type safety and automatic type generation.
6
-
7
- ## When to Use
8
-
9
- **Use Drizzle schemas** for:
10
- - ✅ Any PostgreSQL table definition in React Native/Node.js applications
11
- - ✅ Projects requiring full TypeScript type inference (no separate type definitions)
12
- - ✅ Applications with complex relationships and joins
13
- - ✅ Teams valuing compile-time safety over runtime convenience
14
-
15
- ## Code Example
16
-
17
- ### Basic Schema with Type Inference
18
-
19
- ```typescript
20
- // File: packages/db/src/schema/quantum-api/quantum-execution-jobs/schema.ts
21
- import { pgTable, uuid, text, jsonb, integer, timestamp, unique, check } from 'drizzle-orm/pg-core';
22
-
23
- export const quantumExecutionJobs = pgTable('quantum_execution_jobs', {
24
- id: uuid('id').defaultRandom().primaryKey(),
25
- provider: text('provider').notNull(), // 'ibm' | 'aws' | etc.
26
- remoteJobId: text('remote_job_id').notNull(),
27
- status: text('status').notNull().default('queued'), // queued | running | succeeded | failed | cancelled
28
- requestPayload: jsonb('request_payload').notNull(), // Full request object
29
- resultPayload: jsonb('result_payload'), // Result when status === succeeded
30
- errorPayload: jsonb('error_payload'), // Error object when status === failed
31
- createdAt: timestamp('created_at').defaultNow().notNull(),
32
- updatedAt: timestamp('updated_at').defaultNow().notNull(),
33
- completedAt: timestamp('completed_at'),
34
- }, (table) => ({
35
- // Composite unique constraint
36
- uniqueRemoteJob: unique().on(table.provider, table.remoteJobId),
37
-
38
- // Check constraints for valid status values
39
- statusCheck: check(
40
- 'status_check',
41
- sql`status IN ('queued', 'running', 'succeeded', 'failed', 'cancelled')`
42
- ),
43
-
44
- // Ensure completedAt is set when status is terminal
45
- completedAtCheck: check(
46
- 'completed_at_check',
47
- sql`(status IN ('succeeded', 'failed', 'cancelled') AND completed_at IS NOT NULL)
48
- OR (status NOT IN ('succeeded', 'failed', 'cancelled') AND completed_at IS NULL)`
49
- ),
50
- }));
51
-
52
- // Infer types from schema
53
- export type QuantumExecutionJob = typeof quantumExecutionJobs.$inferSelect;
54
- export type NewQuantumExecutionJob = typeof quantumExecutionJobs.$inferInsert;
55
- ```
56
-
57
- **From:** DJsPortfolio/packages/db/src/schema/quantum-api/quantum-execution-jobs/schema.ts (lines 1-69)
58
-
59
- ### Schema with Foreign Keys
60
-
61
- ```typescript
62
- // File: src/db/eventClaimsSchema.ts (PokePages pattern)
63
- import { pgTable, uuid, text, timestamp, integer, foreignKey } from 'drizzle-orm/pg-core';
64
- import { profiles } from './profilesSchema';
65
- import { events } from './eventsSchema';
66
-
67
- export const eventClaims = pgTable(
68
- 'event_claims',
69
- {
70
- id: uuid('id').defaultRandom().primaryKey(),
71
- userId: uuid('user_id').notNull(),
72
- eventId: uuid('event_id').notNull(),
73
- claimedAt: timestamp('claimed_at').defaultNow().notNull(),
74
- proofData: text('proof_data'), // Optional proof/screenshot
75
- },
76
- (table) => ({
77
- userFk: foreignKey({
78
- columns: [table.userId],
79
- foreignColumns: [profiles.id],
80
- name: 'event_claims_user_fk',
81
- }).onDelete('cascade'),
82
-
83
- eventFk: foreignKey({
84
- columns: [table.eventId],
85
- foreignColumns: [events.id],
86
- name: 'event_claims_event_fk',
87
- }).onDelete('cascade'),
88
-
89
- // Unique constraint: user can only claim event once
90
- uniqueUserEvent: unique().on(table.userId, table.eventId),
91
- })
92
- );
93
-
94
- export type EventClaim = typeof eventClaims.$inferSelect;
95
- export type NewEventClaim = typeof eventClaims.$inferInsert;
96
- ```
97
-
98
- **From:** PokePages/src/db/eventClaimsSchema.ts pattern
99
-
100
- ### Indexed Schema for Performance
101
-
102
- ```typescript
103
- // File: src/db/postsSchema.ts
104
- import { pgTable, uuid, text, timestamp, integer, index } from 'drizzle-orm/pg-core';
105
-
106
- export const posts = pgTable(
107
- 'posts',
108
- {
109
- id: uuid('id').defaultRandom().primaryKey(),
110
- userId: uuid('user_id').notNull(),
111
- title: text('title').notNull(),
112
- content: text('content').notNull(),
113
- likes: integer('likes').default(0),
114
- createdAt: timestamp('created_at').defaultNow().notNull(),
115
- updatedAt: timestamp('updated_at').defaultNow().notNull(),
116
- deletedAt: timestamp('deleted_at'), // Soft delete
117
- },
118
- (table) => ({
119
- // Single column indexes for common queries
120
- userIdIdx: index('posts_user_id_idx').on(table.userId),
121
- createdAtIdx: index('posts_created_at_idx').on(table.createdAt),
122
-
123
- // Composite index for common query pattern
124
- userCreatedIdx: index('posts_user_created_idx')
125
- .on(table.userId, table.createdAt),
126
-
127
- // Index for soft deletes
128
- activePostsIdx: index('posts_active_idx')
129
- .on(table.userId, table.deletedAt),
130
- })
131
- );
132
- ```
133
-
134
- ## Configuration
135
-
136
- ### Database Connection Setup
137
-
138
- ```typescript
139
- // File: src/db/index.ts
140
- import { drizzle } from 'drizzle-orm/postgres-js';
141
- import postgres from 'postgres';
142
- import * as schemas from './schemas';
143
-
144
- const connectionString = process.env.DATABASE_URL!;
145
-
146
- // Optimized connection pool for Supabase PgBouncer
147
- export const client = postgres(connectionString, {
148
- prepare: false, // Required for PgBouncer
149
- ssl: 'require',
150
- max: 3, // Small pool size
151
- idle_timeout: 20, // Close idle connections
152
- connect_timeout: 30, // Connection timeout
153
- max_lifetime: 1800, // Recycle after 30 minutes
154
- });
155
-
156
- export const db = drizzle(client, {
157
- schema: schemas,
158
- });
159
- ```
160
-
161
- ### TypeScript Configuration
162
-
163
- ```typescript
164
- // File: tsconfig.json
165
- {
166
- "compilerOptions": {
167
- "plugins": [
168
- {
169
- "name": "drizzle-orm/compiler",
170
- "options": {
171
- "casing": "snake_case"
172
- }
173
- }
174
- ]
175
- }
176
- }
177
- ```
178
-
179
- ## Best Practices
180
-
181
- ### ✅ DO
182
-
183
- 1. **Infer types from schema** — don't maintain separate type definitions
184
- ```typescript
185
- // ✅ GOOD - single source of truth
186
- export type Post = typeof posts.$inferSelect;
187
- export type NewPost = typeof posts.$inferInsert;
188
-
189
- // ❌ BAD - duplicate type definitions
190
- interface Post {
191
- id: string;
192
- title: string;
193
- // ... manually maintaining type sync
194
- }
195
- ```
196
-
197
- 2. **Use domain-specific schema files**
198
- ```
199
- src/db/
200
- ├── profilesSchema.ts
201
- ├── postsSchema.ts
202
- ├── commentsSchema.ts
203
- ├── relations.ts ← all relationships defined here
204
- └── index.ts
205
- ```
206
-
207
- 3. **Define relationships in shared relations.ts**
208
- ```typescript
209
- // File: src/db/relations.ts
210
- import { relations } from 'drizzle-orm';
211
- import { profiles, posts, comments } from './schemas';
212
-
213
- export const profilesRelations = relations(profiles, ({ many }) => ({
214
- posts: many(posts),
215
- }));
216
- ```
217
-
218
- 4. **Use JSONB for flexible data** instead of many normalized tables
219
- ```typescript
220
- requestPayload: jsonb('request_payload').notNull(),
221
- resultPayload: jsonb('result_payload'),
222
- ```
223
-
224
- 5. **Add check constraints** for data integrity
225
- ```typescript
226
- check(
227
- 'status_check',
228
- sql`status IN ('pending', 'completed', 'failed')`
229
- )
230
- ```
231
-
232
- ### ❌ DON'T
233
-
234
- 1. **Don't create overly normalized schemas** when embedding is more performant
235
- ```typescript
236
- // ❌ AVOID - creates N+1 queries
237
- users table → user_profiles table → user_settings table
238
-
239
- // ✅ BETTER - use JSONB for related data
240
- users: { profile: jsonb, settings: jsonb }
241
- ```
242
-
243
- 2. **Don't forget indexes** on frequently queried columns
244
- ```typescript
245
- // ❌ BAD - no index on userId
246
- posts table with userId but no index
247
-
248
- // ✅ GOOD - indexed for fast lookups
249
- userIdIdx: index().on(table.userId)
250
- ```
251
-
252
- 3. **Don't use mutable defaults** (like functions) for timestamps
253
- ```typescript
254
- // ❌ BAD - function called once at migration time
255
- createdAt: timestamp('created_at').default(fn)
256
-
257
- // ✅ GOOD - database handles default for each row
258
- createdAt: timestamp('created_at').defaultNow()
259
- ```
260
-
261
- 4. **Don't create tables without soft delete** if you might need historical data
262
- ```typescript
263
- // ✅ GOOD - reversible deletion
264
- deletedAt: timestamp('deleted_at')
265
-
266
- // Query active records
267
- where(isNull(posts.deletedAt))
268
- ```
269
-
270
- ## Related Patterns
271
-
272
- - [Database Migrations](./migrations.md) — Managing schema changes
273
- - [Relationships](./relations.md) — Foreign keys and joins
274
- - [Query Organization](./query-organization.md) — Data access layer
275
-
276
- ---
277
-
278
- *Pattern extracted from production repositories: DJsPortfolio, PokePages, core-monorepo*
279
- *Files: DJsPortfolio/packages\db\src\schema\quantum-api\quantum-execution-jobs\schema.ts*
1
+ # Drizzle ORM Schema Design
2
+
3
+ ## Description
4
+
5
+ Drizzle ORM schemas define type-safe database tables with full TypeScript inference from database types to application types. Schemas use domain-specific files with shared relations.ts for foreign keys and relationships, enabling compile-time type safety and automatic type generation.
6
+
7
+ ## When to Use
8
+
9
+ **Use Drizzle schemas** for:
10
+ - ✅ Any PostgreSQL table definition in React Native/Node.js applications
11
+ - ✅ Projects requiring full TypeScript type inference (no separate type definitions)
12
+ - ✅ Applications with complex relationships and joins
13
+ - ✅ Teams valuing compile-time safety over runtime convenience
14
+
15
+ ## Code Example
16
+
17
+ ### Basic Schema with Type Inference
18
+
19
+ ```typescript
20
+ // File: packages/db/src/schema/quantum-api/quantum-execution-jobs/schema.ts
21
+ import { pgTable, uuid, text, jsonb, integer, timestamp, unique, check } from 'drizzle-orm/pg-core';
22
+
23
+ export const quantumExecutionJobs = pgTable('quantum_execution_jobs', {
24
+ id: uuid('id').defaultRandom().primaryKey(),
25
+ provider: text('provider').notNull(), // 'ibm' | 'aws' | etc.
26
+ remoteJobId: text('remote_job_id').notNull(),
27
+ status: text('status').notNull().default('queued'), // queued | running | succeeded | failed | cancelled
28
+ requestPayload: jsonb('request_payload').notNull(), // Full request object
29
+ resultPayload: jsonb('result_payload'), // Result when status === succeeded
30
+ errorPayload: jsonb('error_payload'), // Error object when status === failed
31
+ createdAt: timestamp('created_at').defaultNow().notNull(),
32
+ updatedAt: timestamp('updated_at').defaultNow().notNull(),
33
+ completedAt: timestamp('completed_at'),
34
+ }, (table) => ({
35
+ // Composite unique constraint
36
+ uniqueRemoteJob: unique().on(table.provider, table.remoteJobId),
37
+
38
+ // Check constraints for valid status values
39
+ statusCheck: check(
40
+ 'status_check',
41
+ sql`status IN ('queued', 'running', 'succeeded', 'failed', 'cancelled')`
42
+ ),
43
+
44
+ // Ensure completedAt is set when status is terminal
45
+ completedAtCheck: check(
46
+ 'completed_at_check',
47
+ sql`(status IN ('succeeded', 'failed', 'cancelled') AND completed_at IS NOT NULL)
48
+ OR (status NOT IN ('succeeded', 'failed', 'cancelled') AND completed_at IS NULL)`
49
+ ),
50
+ }));
51
+
52
+ // Infer types from schema
53
+ export type QuantumExecutionJob = typeof quantumExecutionJobs.$inferSelect;
54
+ export type NewQuantumExecutionJob = typeof quantumExecutionJobs.$inferInsert;
55
+ ```
56
+
57
+ **From:** DJsPortfolio/packages/db/src/schema/quantum-api/quantum-execution-jobs/schema.ts (lines 1-69)
58
+
59
+ ### Schema with Foreign Keys
60
+
61
+ ```typescript
62
+ // File: src/db/eventClaimsSchema.ts (PokePages pattern)
63
+ import { pgTable, uuid, text, timestamp, integer, foreignKey } from 'drizzle-orm/pg-core';
64
+ import { profiles } from './profilesSchema';
65
+ import { events } from './eventsSchema';
66
+
67
+ export const eventClaims = pgTable(
68
+ 'event_claims',
69
+ {
70
+ id: uuid('id').defaultRandom().primaryKey(),
71
+ userId: uuid('user_id').notNull(),
72
+ eventId: uuid('event_id').notNull(),
73
+ claimedAt: timestamp('claimed_at').defaultNow().notNull(),
74
+ proofData: text('proof_data'), // Optional proof/screenshot
75
+ },
76
+ (table) => ({
77
+ userFk: foreignKey({
78
+ columns: [table.userId],
79
+ foreignColumns: [profiles.id],
80
+ name: 'event_claims_user_fk',
81
+ }).onDelete('cascade'),
82
+
83
+ eventFk: foreignKey({
84
+ columns: [table.eventId],
85
+ foreignColumns: [events.id],
86
+ name: 'event_claims_event_fk',
87
+ }).onDelete('cascade'),
88
+
89
+ // Unique constraint: user can only claim event once
90
+ uniqueUserEvent: unique().on(table.userId, table.eventId),
91
+ })
92
+ );
93
+
94
+ export type EventClaim = typeof eventClaims.$inferSelect;
95
+ export type NewEventClaim = typeof eventClaims.$inferInsert;
96
+ ```
97
+
98
+ **From:** PokePages/src/db/eventClaimsSchema.ts pattern
99
+
100
+ ### Indexed Schema for Performance
101
+
102
+ ```typescript
103
+ // File: src/db/postsSchema.ts
104
+ import { pgTable, uuid, text, timestamp, integer, index } from 'drizzle-orm/pg-core';
105
+
106
+ export const posts = pgTable(
107
+ 'posts',
108
+ {
109
+ id: uuid('id').defaultRandom().primaryKey(),
110
+ userId: uuid('user_id').notNull(),
111
+ title: text('title').notNull(),
112
+ content: text('content').notNull(),
113
+ likes: integer('likes').default(0),
114
+ createdAt: timestamp('created_at').defaultNow().notNull(),
115
+ updatedAt: timestamp('updated_at').defaultNow().notNull(),
116
+ deletedAt: timestamp('deleted_at'), // Soft delete
117
+ },
118
+ (table) => ({
119
+ // Single column indexes for common queries
120
+ userIdIdx: index('posts_user_id_idx').on(table.userId),
121
+ createdAtIdx: index('posts_created_at_idx').on(table.createdAt),
122
+
123
+ // Composite index for common query pattern
124
+ userCreatedIdx: index('posts_user_created_idx')
125
+ .on(table.userId, table.createdAt),
126
+
127
+ // Index for soft deletes
128
+ activePostsIdx: index('posts_active_idx')
129
+ .on(table.userId, table.deletedAt),
130
+ })
131
+ );
132
+ ```
133
+
134
+ ## Configuration
135
+
136
+ ### Database Connection Setup
137
+
138
+ ```typescript
139
+ // File: src/db/index.ts
140
+ import { drizzle } from 'drizzle-orm/postgres-js';
141
+ import postgres from 'postgres';
142
+ import * as schemas from './schemas';
143
+
144
+ const connectionString = process.env.DATABASE_URL!;
145
+
146
+ // Optimized connection pool for Supabase PgBouncer
147
+ export const client = postgres(connectionString, {
148
+ prepare: false, // Required for PgBouncer
149
+ ssl: 'require',
150
+ max: 3, // Small pool size
151
+ idle_timeout: 20, // Close idle connections
152
+ connect_timeout: 30, // Connection timeout
153
+ max_lifetime: 1800, // Recycle after 30 minutes
154
+ });
155
+
156
+ export const db = drizzle(client, {
157
+ schema: schemas,
158
+ });
159
+ ```
160
+
161
+ ### TypeScript Configuration
162
+
163
+ ```typescript
164
+ // File: tsconfig.json
165
+ {
166
+ "compilerOptions": {
167
+ "plugins": [
168
+ {
169
+ "name": "drizzle-orm/compiler",
170
+ "options": {
171
+ "casing": "snake_case"
172
+ }
173
+ }
174
+ ]
175
+ }
176
+ }
177
+ ```
178
+
179
+ ## Best Practices
180
+
181
+ ### ✅ DO
182
+
183
+ 1. **Infer types from schema** — don't maintain separate type definitions
184
+ ```typescript
185
+ // ✅ GOOD - single source of truth
186
+ export type Post = typeof posts.$inferSelect;
187
+ export type NewPost = typeof posts.$inferInsert;
188
+
189
+ // ❌ BAD - duplicate type definitions
190
+ interface Post {
191
+ id: string;
192
+ title: string;
193
+ // ... manually maintaining type sync
194
+ }
195
+ ```
196
+
197
+ 2. **Use domain-specific schema files**
198
+ ```
199
+ src/db/
200
+ ├── profilesSchema.ts
201
+ ├── postsSchema.ts
202
+ ├── commentsSchema.ts
203
+ ├── relations.ts ← all relationships defined here
204
+ └── index.ts
205
+ ```
206
+
207
+ 3. **Define relationships in shared relations.ts**
208
+ ```typescript
209
+ // File: src/db/relations.ts
210
+ import { relations } from 'drizzle-orm';
211
+ import { profiles, posts, comments } from './schemas';
212
+
213
+ export const profilesRelations = relations(profiles, ({ many }) => ({
214
+ posts: many(posts),
215
+ }));
216
+ ```
217
+
218
+ 4. **Use JSONB for flexible data** instead of many normalized tables
219
+ ```typescript
220
+ requestPayload: jsonb('request_payload').notNull(),
221
+ resultPayload: jsonb('result_payload'),
222
+ ```
223
+
224
+ 5. **Add check constraints** for data integrity
225
+ ```typescript
226
+ check(
227
+ 'status_check',
228
+ sql`status IN ('pending', 'completed', 'failed')`
229
+ )
230
+ ```
231
+
232
+ ### ❌ DON'T
233
+
234
+ 1. **Don't create overly normalized schemas** when embedding is more performant
235
+ ```typescript
236
+ // ❌ AVOID - creates N+1 queries
237
+ users table → user_profiles table → user_settings table
238
+
239
+ // ✅ BETTER - use JSONB for related data
240
+ users: { profile: jsonb, settings: jsonb }
241
+ ```
242
+
243
+ 2. **Don't forget indexes** on frequently queried columns
244
+ ```typescript
245
+ // ❌ BAD - no index on userId
246
+ posts table with userId but no index
247
+
248
+ // ✅ GOOD - indexed for fast lookups
249
+ userIdIdx: index().on(table.userId)
250
+ ```
251
+
252
+ 3. **Don't use mutable defaults** (like functions) for timestamps
253
+ ```typescript
254
+ // ❌ BAD - function called once at migration time
255
+ createdAt: timestamp('created_at').default(fn)
256
+
257
+ // ✅ GOOD - database handles default for each row
258
+ createdAt: timestamp('created_at').defaultNow()
259
+ ```
260
+
261
+ 4. **Don't create tables without soft delete** if you might need historical data
262
+ ```typescript
263
+ // ✅ GOOD - reversible deletion
264
+ deletedAt: timestamp('deleted_at')
265
+
266
+ // Query active records
267
+ where(isNull(posts.deletedAt))
268
+ ```
269
+
270
+ ## Related Patterns
271
+
272
+ - [Database Migrations](./migrations.md) — Managing schema changes
273
+ - [Relationships](./relations.md) — Foreign keys and joins
274
+ - [Query Organization](./query-organization.md) — Data access layer
275
+
276
+ ---
277
+
278
+ *Pattern extracted from production repositories: DJsPortfolio, PokePages, core-monorepo*
279
+ *Files: DJsPortfolio/packages\db\src\schema\quantum-api\quantum-execution-jobs\schema.ts*
280
280
  *Lines 1-69 with full JSONB payload handling and check constraints*