prax-orm 0.11.0

A next-generation, type-safe ORM for Rust inspired by Prisma
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
---
import DocsLayout from '../../layouts/DocsLayout.astro';
import CodeBlock from '../../components/CodeBlock.astro';

const relationBasics = `// Relations connect models together
model User {
    id    Int    @id @auto
    posts Post[]  // One-to-Many: User has many Posts
}

model Post {
    id       Int  @id @auto
    // Foreign key field
    authorId Int
    // Relation to User model
    author   User @relation(fields: [authorId], references: [id])
}

// Relations are always defined on BOTH sides:
// - One side has the foreign key field(s) + @relation attribute
// - Other side has the array or optional reference`;

const oneToOne = `// One-to-One: User has exactly one Profile
model User {
    id      Int      @id @auto
    email   String   @unique
    profile Profile?  // Optional: User might not have a profile
}

model Profile {
    id     Int    @id @auto
    bio    String?
    avatar String?

    // Foreign key (must be @unique for 1:1)
    userId Int    @unique
    user   User   @relation(fields: [userId], references: [id])
}

// Alternative: Profile ID is also the User ID
model UserWithProfile {
    id   Int @id @auto
    name String
}

model ProfileByUserId {
    // Use same ID as User (shared primary key)
    userId Int   @id
    bio    String?
    user   UserWithProfile @relation(fields: [userId], references: [id])
}`;

const oneToMany = `// One-to-Many: User has many Posts
model User {
    id    Int    @id @auto
    email String @unique
    posts Post[] // Array indicates "many" side
}

model Post {
    id       Int    @id @auto
    title    String
    content  String?

    // "One" side has the foreign key
    authorId Int
    author   User   @relation(fields: [authorId], references: [id])
}

// One-to-Many with optional relationship
model Category {
    id    Int     @id @auto
    name  String
    posts Post[]
}

model PostWithCategory {
    id         Int       @id @auto
    title      String
    categoryId Int?      // Optional foreign key
    category   Category? @relation(fields: [categoryId], references: [id])
}`;

const manyToMany = `// Many-to-Many: Posts have many Tags, Tags have many Posts
// Implicit join table (Prax manages it)
model Post {
    id    Int    @id @auto
    title String
    tags  Tag[]  // Many tags per post
}

model Tag {
    id    Int    @id @auto
    name  String @unique
    posts Post[] // Many posts per tag
}

// Explicit join table (you manage it)
// Use when you need additional fields on the relationship
model PostTagExplicit {
    // Composite primary key
    postId Int
    tagId  Int

    // Additional relationship data
    addedAt   DateTime @default(now())
    addedById Int?

    // Relations
    post Post @relation(fields: [postId], references: [id])
    tag  Tag  @relation(fields: [tagId], references: [id])

    @@id([postId, tagId])
    @@index([tagId])
}`;

const selfRelation = `// Self-relation: Comments can have replies (tree structure)
model Comment {
    id       Int       @id @auto
    content  String
    postId   Int

    // Self-relation for nested comments
    parentId Int?
    parent   Comment?  @relation("CommentReplies", fields: [parentId], references: [id])
    replies  Comment[] @relation("CommentReplies")
}

// Self-relation: Users can follow other users
model User {
    id         Int    @id @auto
    name       String

    // Users I follow
    following  User[] @relation("UserFollows")
    // Users following me
    followers  User[] @relation("UserFollows")
}

// Self-relation: Employee -> Manager hierarchy
model Employee {
    id        Int        @id @auto
    name      String
    managerId Int?
    manager   Employee?  @relation("EmployeeManager", fields: [managerId], references: [id])
    reports   Employee[] @relation("EmployeeManager")
}`;

const multipleRelations = `// Multiple relations between the same models
model User {
    id            Int       @id @auto
    email         String    @unique
    writtenPosts  Post[]    @relation("PostAuthor")    // Posts I wrote
    editedPosts   Post[]    @relation("PostEditor")    // Posts I edited
    likedPosts    Post[]    @relation("PostLikes")     // Posts I liked
}

model Post {
    id         Int      @id @auto
    title      String
    content    String?

    // Different relations to User
    authorId   Int
    author     User     @relation("PostAuthor", fields: [authorId], references: [id])

    editorId   Int?
    editor     User?    @relation("PostEditor", fields: [editorId], references: [id])

    likedBy    User[]   @relation("PostLikes")  // Many-to-many
}`;

const refActions = `// Referential actions control cascading behavior
model User {
    id    Int    @id @auto
    posts Post[]
}

model Post {
    id       Int  @id @auto
    authorId Int

    author User @relation(
        fields: [authorId],
        references: [id],
        onDelete: Cascade,     // Delete posts when user is deleted
        onUpdate: Cascade      // Update FK when user ID changes
    )
}

// All referential actions
model Example {
    parentId Int
    parent   Parent @relation(
        fields: [parentId],
        references: [id],
        onDelete: Cascade,     // Delete this when parent deleted
        // onDelete: Restrict,  // Prevent parent deletion if this exists
        // onDelete: SetNull,   // Set FK to NULL (field must be optional)
        // onDelete: SetDefault,// Set FK to default value
        // onDelete: NoAction,  // Database default (usually error)
        onUpdate: Cascade
    )
}`;

const compositeRelations = `// Relation using composite foreign key
model TenantUser {
    tenantId Int
    id       Int
    email    String
    posts    TenantPost[]

    @@id([tenantId, id])
}

model TenantPost {
    tenantId     Int
    id           Int
    title        String

    // Composite foreign key
    authorTenant Int
    authorId     Int
    author       TenantUser @relation(
        fields: [authorTenant, authorId],
        references: [tenantId, id]
    )

    @@id([tenantId, id])
    @@index([authorTenant, authorId])
}`;

const queryingRelations = `use prax::generated::{user, post, include};

// Include related data (eager loading)
let user_with_posts = client
    .user()
    .find_unique()
    .where(user::id::equals(1))
    .include(user::posts::fetch())
    .exec()
    .await?;

// Nested includes
let user_with_full_posts = client
    .user()
    .find_unique()
    .where(user::id::equals(1))
    .include(user::posts::fetch()
        .include(post::tags::fetch())
        .include(post::comments::fetch()))
    .exec()
    .await?;

// Filter by related records
let users_with_published = client
    .user()
    .find_many()
    .where(user::posts::some(post::published::equals(true)))
    .exec()
    .await?;

// Filter: all, some, none, is, isNot
let authors = client
    .user()
    .find_many()
    .where(user::posts::some(post::likes::gt(100)))  // Has popular post
    .where(user::profile::is(profile::verified::equals(true)))  // Verified
    .exec()
    .await?;`;

const nestedWrites = `use prax_query::data;

// Create with nested relation
let user = client
    .user()
    .create(data! {
        email: "alice@example.com",
        name: "Alice",
        // Create related profile
        profile: {
            create: {
                bio: "Software engineer",
                avatar: "https://example.com/alice.jpg"
            }
        },
        // Create multiple posts
        posts: {
            create: [
                { title: "Hello World", content: "My first post" },
                { title: "Second Post", published: true }
            ]
        }
    })
    .exec()
    .await?;

// Connect to existing records
let post = client
    .post()
    .create(data! {
        title: "New Post",
        author: {
            connect: { id: 1 }
        },
        tags: {
            connect: [{ id: 1 }, { id: 2 }]
        }
    })
    .exec()
    .await?;

// Disconnect relations
let post = client
    .post()
    .update()
    .where(post::id::equals(1))
    .data(data! {
        tags: {
            disconnect: [{ id: 3 }]
        }
    })
    .exec()
    .await?;`;

const bestPractices = `// ✅ Good: Clear naming for relation fields
model User {
    id            Int      @id @auto
    posts         Post[]   @relation("AuthoredPosts")
    favoriteBooks Book[]   @relation("FavoriteBooks")
}

// ✅ Good: Index foreign key columns
model Post {
    id       Int  @id @auto
    authorId Int
    author   User @relation(fields: [authorId], references: [id])

    @@index([authorId])  // Important for query performance!
}

// ✅ Good: Use Cascade carefully
model UserSession {
    id     Int  @id @auto
    userId Int
    user   User @relation(
        fields: [userId],
        references: [id],
        onDelete: Cascade  // Sessions deleted when user deleted
    )
}

// ⚠️ Careful: Restrict for important data
model Order {
    id         Int  @id @auto
    customerId Int
    customer   Customer @relation(
        fields: [customerId],
        references: [id],
        onDelete: Restrict  // Can't delete customer with orders
    )
}`;
---

<DocsLayout title="Relations - Prax ORM">
  <article class="max-w-4xl mx-auto px-6 py-12">
    <header class="mb-12">
      <h1 class="text-4xl font-bold mb-4">Relations</h1>
      <p class="text-xl text-muted">
        Define relationships between your models for powerful data querying and type-safe nested operations.
      </p>
    </header>

    <div class="space-y-12">
      <!-- Introduction -->
      <section>
        <h2 class="text-2xl font-semibold mb-4">Understanding Relations</h2>
        <p class="text-muted mb-4">
          Relations define how models connect to each other. They create foreign key constraints
          in the database and enable type-safe queries with nested data loading in your Rust code.
        </p>
        <CodeBlock code={relationBasics} lang="prax" filename="prax/schema.prax" />
        <div class="mt-6 grid md:grid-cols-3 gap-4">
          <div class="p-4 rounded-xl bg-surface border border-border text-center">
            <div class="text-3xl mb-2">1:1</div>
            <h4 class="font-semibold mb-1">One-to-One</h4>
            <p class="text-muted text-sm">User ↔ Profile</p>
          </div>
          <div class="p-4 rounded-xl bg-surface border border-border text-center">
            <div class="text-3xl mb-2">1:N</div>
            <h4 class="font-semibold mb-1">One-to-Many</h4>
            <p class="text-muted text-sm">User → Posts[]</p>
          </div>
          <div class="p-4 rounded-xl bg-surface border border-border text-center">
            <div class="text-3xl mb-2">M:N</div>
            <h4 class="font-semibold mb-1">Many-to-Many</h4>
            <p class="text-muted text-sm">Posts[] ↔ Tags[]</p>
          </div>
        </div>
      </section>

      <!-- One-to-One -->
      <section>
        <h2 class="text-2xl font-semibold mb-4">One-to-One Relations</h2>
        <p class="text-muted mb-4">
          A one-to-one relation means each record in one model has exactly one related record in another model.
          The foreign key field must have a <code class="px-2 py-1 bg-surface-elevated rounded">&#64;unique</code> constraint.
        </p>
        <CodeBlock code={oneToOne} lang="prax" filename="prax/schema.prax" />
        <div class="mt-4 p-4 rounded-xl bg-info-500/10 border border-info-500/30">
          <p class="text-info-400 text-sm">
            <strong>Tip:</strong> Use one-to-one relations to split large models or when optional extended data
            is only needed in certain contexts.
          </p>
        </div>
      </section>

      <!-- One-to-Many -->
      <section>
        <h2 class="text-2xl font-semibold mb-4">One-to-Many Relations</h2>
        <p class="text-muted mb-4">
          The most common relation type. One record can have many related records, but each related
          record belongs to exactly one parent.
        </p>
        <CodeBlock code={oneToMany} lang="prax" filename="prax/schema.prax" />
      </section>

      <!-- Many-to-Many -->
      <section>
        <h2 class="text-2xl font-semibold mb-4">Many-to-Many Relations</h2>
        <p class="text-muted mb-4">
          Many-to-many relations allow records in both models to have multiple related records.
          Prax can manage the join table automatically, or you can define it explicitly.
        </p>
        <CodeBlock code={manyToMany} lang="prax" filename="prax/schema.prax" />
        <div class="mt-4 p-4 rounded-xl bg-warning-500/10 border border-warning-500/30">
          <p class="text-warning-400 text-sm">
            <strong>When to use explicit join tables:</strong> When you need additional data on the relationship
            (timestamps, ordering, metadata) or need more control over the join table structure.
          </p>
        </div>
      </section>

      <!-- Self-Relations -->
      <section>
        <h2 class="text-2xl font-semibold mb-4">Self-Relations</h2>
        <p class="text-muted mb-4">
          Self-relations allow a model to relate to itself. Common for hierarchical data like
          comments with replies, organizational structures, or social graphs.
        </p>
        <CodeBlock code={selfRelation} lang="prax" filename="prax/schema.prax" />
      </section>

      <!-- Multiple Relations -->
      <section>
        <h2 class="text-2xl font-semibold mb-4">Multiple Relations Between Models</h2>
        <p class="text-muted mb-4">
          When models have multiple relations, use the <code class="px-2 py-1 bg-surface-elevated rounded">name</code>
          argument in <code class="px-2 py-1 bg-surface-elevated rounded">&#64;relation</code> to distinguish them.
        </p>
        <CodeBlock code={multipleRelations} lang="prax" filename="prax/schema.prax" />
      </section>

      <!-- Referential Actions -->
      <section>
        <h2 class="text-2xl font-semibold mb-4">Referential Actions</h2>
        <p class="text-muted mb-4">
          Control what happens to related records when a parent record is deleted or updated.
        </p>
        <CodeBlock code={refActions} lang="prax" filename="prax/schema.prax" />
        <div class="mt-6 overflow-x-auto">
          <table class="w-full text-sm">
            <thead>
              <tr class="border-b border-border">
                <th class="text-left py-3 px-4 font-semibold">Action</th>
                <th class="text-left py-3 px-4 font-semibold">On Delete</th>
                <th class="text-left py-3 px-4 font-semibold">On Update</th>
              </tr>
            </thead>
            <tbody class="text-muted">
              <tr class="border-b border-border">
                <td class="py-3 px-4"><code class="text-primary-400">Cascade</code></td>
                <td class="py-3 px-4">Delete related records</td>
                <td class="py-3 px-4">Update foreign key values</td>
              </tr>
              <tr class="border-b border-border">
                <td class="py-3 px-4"><code class="text-primary-400">Restrict</code></td>
                <td class="py-3 px-4">Prevent deletion if related records exist</td>
                <td class="py-3 px-4">Prevent update if related records exist</td>
              </tr>
              <tr class="border-b border-border">
                <td class="py-3 px-4"><code class="text-primary-400">SetNull</code></td>
                <td class="py-3 px-4">Set foreign key to NULL</td>
                <td class="py-3 px-4">Set foreign key to NULL</td>
              </tr>
              <tr class="border-b border-border">
                <td class="py-3 px-4"><code class="text-primary-400">SetDefault</code></td>
                <td class="py-3 px-4">Set foreign key to default value</td>
                <td class="py-3 px-4">Set foreign key to default value</td>
              </tr>
              <tr class="border-b border-border">
                <td class="py-3 px-4"><code class="text-primary-400">NoAction</code></td>
                <td class="py-3 px-4">Database default (usually error)</td>
                <td class="py-3 px-4">Database default</td>
              </tr>
            </tbody>
          </table>
        </div>
      </section>

      <!-- Composite Relations -->
      <section>
        <h2 class="text-2xl font-semibold mb-4">Composite Foreign Keys</h2>
        <p class="text-muted mb-4">
          Relations can reference multiple fields for composite primary keys:
        </p>
        <CodeBlock code={compositeRelations} lang="prax" filename="prax/schema.prax" />
      </section>

      <!-- Querying Relations -->
      <section>
        <h2 class="text-2xl font-semibold mb-4">Querying Relations</h2>
        <p class="text-muted mb-4">
          Load related data with <code class="px-2 py-1 bg-surface-elevated rounded">include()</code>
          and filter by related records:
        </p>
        <CodeBlock code={queryingRelations} lang="rust" filename="src/main.rs" />
      </section>

      <!-- Nested Writes -->
      <section>
        <h2 class="text-2xl font-semibold mb-4">Nested Writes</h2>
        <p class="text-muted mb-4">
          Create, connect, and disconnect related records in a single operation:
        </p>
        <CodeBlock code={nestedWrites} lang="rust" filename="src/main.rs" />
      </section>

      <!-- Best Practices -->
      <section>
        <h2 class="text-2xl font-semibold mb-4">Best Practices</h2>
        <CodeBlock code={bestPractices} lang="prax" filename="prax/schema.prax" />
        <div class="mt-6 grid gap-4">
          <div class="p-4 rounded-xl bg-surface border border-border">
            <h4 class="font-semibold mb-2 text-primary-400">Always Index Foreign Keys</h4>
            <p class="text-muted text-sm">
              Add <code class="px-1 bg-surface-elevated rounded">&#64;&#64;index([foreignKeyField])</code>
              to improve JOIN and filter query performance.
            </p>
          </div>
          <div class="p-4 rounded-xl bg-surface border border-border">
            <h4 class="font-semibold mb-2 text-primary-400">Choose Referential Actions Carefully</h4>
            <p class="text-muted text-sm">
              Use <code class="px-1 bg-surface-elevated rounded">Cascade</code> for child records that make no sense without the parent.
              Use <code class="px-1 bg-surface-elevated rounded">Restrict</code> for important data that shouldn't be accidentally deleted.
            </p>
          </div>
          <div class="p-4 rounded-xl bg-surface border border-border">
            <h4 class="font-semibold mb-2 text-primary-400">Name Relations for Clarity</h4>
            <p class="text-muted text-sm">
              Always use the <code class="px-1 bg-surface-elevated rounded">name</code> argument when models have multiple relations
              to clearly distinguish their purpose.
            </p>
          </div>
        </div>
      </section>

      <!-- Relation Attributes Reference -->
      <section>
        <h2 class="text-2xl font-semibold mb-4">Relation Attributes Reference</h2>
        <div class="overflow-x-auto">
          <table class="w-full text-sm">
            <thead>
              <tr class="border-b border-border">
                <th class="text-left py-3 px-4 font-semibold">Argument</th>
                <th class="text-left py-3 px-4 font-semibold">Required</th>
                <th class="text-left py-3 px-4 font-semibold">Description</th>
              </tr>
            </thead>
            <tbody class="text-muted">
              <tr class="border-b border-border">
                <td class="py-3 px-4"><code class="text-primary-400">fields</code></td>
                <td class="py-3 px-4">Yes*</td>
                <td class="py-3 px-4">Foreign key field(s) on this model</td>
              </tr>
              <tr class="border-b border-border">
                <td class="py-3 px-4"><code class="text-primary-400">references</code></td>
                <td class="py-3 px-4">Yes*</td>
                <td class="py-3 px-4">Referenced field(s) on the related model</td>
              </tr>
              <tr class="border-b border-border">
                <td class="py-3 px-4"><code class="text-primary-400">name</code></td>
                <td class="py-3 px-4">No</td>
                <td class="py-3 px-4">Relation name (required for multiple relations)</td>
              </tr>
              <tr class="border-b border-border">
                <td class="py-3 px-4"><code class="text-primary-400">onDelete</code></td>
                <td class="py-3 px-4">No</td>
                <td class="py-3 px-4">Action when parent is deleted</td>
              </tr>
              <tr class="border-b border-border">
                <td class="py-3 px-4"><code class="text-primary-400">onUpdate</code></td>
                <td class="py-3 px-4">No</td>
                <td class="py-3 px-4">Action when parent key is updated</td>
              </tr>
            </tbody>
          </table>
        </div>
        <p class="text-muted text-sm mt-4">
          * Required on the side of the relation that holds the foreign key
        </p>
      </section>
    </div>
  </article>
</DocsLayout>