@roundtreasury/prisma-extension-soft-delete 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,993 @@
1
+ <div align="center">
2
+ <h1>Prisma Extension Soft Delete</h1>
3
+
4
+ <p>Prisma extension for soft deleting records.</p>
5
+
6
+ <p>
7
+ Soft deleting records is a common pattern in many applications. This library provides an extension for Prisma that
8
+ allows you to soft delete records and exclude them from queries. It handles deleting records through relations and
9
+ excluding soft deleted records when including relations or referencing them in where objects. It does this by using
10
+ the <a href="https://github.com/olivierwilkinson/prisma-nested-middleware">prisma-extension-nested-operations</a>
11
+ library to handle nested relations.
12
+ </p>
13
+
14
+ </div>
15
+
16
+ <hr />
17
+
18
+ [![Build Status][build-badge]][build]
19
+ [![version][version-badge]][package]
20
+ [![MIT License][license-badge]][license]
21
+ [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
22
+ [![PRs Welcome][prs-badge]][prs]
23
+
24
+ ## Table of Contents
25
+
26
+ <!-- START doctoc generated TOC please keep comment here to allow auto update -->
27
+ <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
28
+
29
+ - [Installation](#installation)
30
+ - [Usage](#usage)
31
+ - [Extension Setup](#extension-setup)
32
+ - [Prisma Schema Setup](#prisma-schema-setup)
33
+ - [Behaviour](#behaviour)
34
+ - [Deleting Records](#deleting-records)
35
+ - [Deleting a Single Record](#deleting-a-single-record)
36
+ - [Deleting Multiple Records](#deleting-multiple-records)
37
+ - [Deleting Through a relationship](#deleting-through-a-relationship)
38
+ - [Hard Deletes](#hard-deletes)
39
+ - [Excluding Soft Deleted Records](#excluding-soft-deleted-records)
40
+ - [Excluding Soft Deleted Records in a `findFirst` Operation](#excluding-soft-deleted-records-in-a-findfirst-operation)
41
+ - [Excluding Soft Deleted Records in a `findMany` Operation](#excluding-soft-deleted-records-in-a-findmany-operation)
42
+ - [Excluding Soft Deleted Records in a `findUnique` Operation](#excluding-soft-deleted-records-in-a-findunique-operation)
43
+ - [Updating Records](#updating-records)
44
+ - [Explicitly Updating Many Soft Deleted Records](#explicitly-updating-many-soft-deleted-records)
45
+ - [Where objects](#where-objects)
46
+ - [Explicitly Querying Soft Deleted Records](#explicitly-querying-soft-deleted-records)
47
+ - [Including or Selecting Soft Deleted Records](#including-or-selecting-soft-deleted-records)
48
+ - [Including or Selecting toMany Relations](#including-or-selecting-tomany-relations)
49
+ - [Including or Selecting toOne Relations](#including-or-selecting-toone-relations)
50
+ - [Explicitly Including Soft Deleted Records in toMany Relations](#explicitly-including-soft-deleted-records-in-tomany-relations)
51
+ - [LICENSE](#license)
52
+
53
+ <!-- END doctoc generated TOC please keep comment here to allow auto update -->
54
+
55
+ ## Installation
56
+
57
+ This module is distributed via [npm][npm] and should be installed as one of your
58
+ project's dependencies:
59
+
60
+ ```
61
+ npm install prisma-extension-soft-delete
62
+ ```
63
+
64
+ `@prisma/client` is a peer dependency of this library, so you will need to
65
+ install it if you haven't already:
66
+
67
+ ```
68
+ npm install @prisma/client
69
+ ```
70
+
71
+ ## Usage
72
+
73
+ ### Extension Setup
74
+
75
+ To add soft delete functionality to your Prisma client create the extension using the `createSoftDeleteExtension`
76
+ function and pass it to `client.$extends`.
77
+
78
+ The `createSoftDeleteExtension` function takes a config object where you can define the models you want to use soft
79
+ delete with.
80
+
81
+ ```typescript
82
+ import { PrismaClient } from "@prisma/client";
83
+
84
+ const client = new PrismaClient();
85
+
86
+ const extendedClient = client.$extends(
87
+ createSoftDeleteExtension({
88
+ models: {
89
+ Comment: true,
90
+ },
91
+ })
92
+ );
93
+ ```
94
+
95
+ By default the extension will use a `deleted` field of type `Boolean` on the model. If you want to use a custom field
96
+ name or value you can pass a config object for the model. For example to use a `deletedAt` field where the value is null
97
+ by default and a `DateTime` when the record is deleted you would pass the following:
98
+
99
+ ```typescript
100
+ const extendedClient = client.$extends(
101
+ createSoftDeleteExtension({
102
+ models: {
103
+ Comment: {
104
+ field: "deletedAt",
105
+ createValue: (deleted) => {
106
+ if (deleted) return new Date();
107
+ return null;
108
+ },
109
+ },
110
+ },
111
+ })
112
+ );
113
+ ```
114
+
115
+ The `field` property is the name of the field to use for soft delete, and the `createValue` property is a function that
116
+ takes a deleted argument and returns the value for whether the record is soft deleted or not. The `createValue` method
117
+ must return a falsy value if the record is not deleted and a truthy value if it is deleted.
118
+
119
+ It is possible to setup soft delete for multiple models at once by passing a config for each model in the `models`
120
+ object:
121
+
122
+ ```typescript
123
+ const extendedClient = client.$extends(
124
+ createSoftDeleteExtension({
125
+ models: {
126
+ Comment: true,
127
+ Post: true,
128
+ },
129
+ })
130
+ );
131
+ ```
132
+
133
+ To modify the default field and type for all models you can pass a `defaultConfig`:
134
+
135
+ ```typescript
136
+ const extendedClient = client.$extends(
137
+ createSoftDeleteExtension({
138
+ models: {
139
+ Comment: true,
140
+ Post: true,
141
+ },
142
+ defaultConfig: {
143
+ field: "deletedAt",
144
+ createValue: (deleted) => {
145
+ if (deleted) return new Date();
146
+ return null;
147
+ },
148
+ },
149
+ })
150
+ );
151
+ ```
152
+
153
+ When using the default config you can also override the default config for a specific model by passing a config object
154
+ for that model:
155
+
156
+ ```typescript
157
+ const extendedClient = client.$extends(
158
+ createSoftDeleteExtension({
159
+ models: {
160
+ Comment: true,
161
+ Post: {
162
+ field: "deleted",
163
+ createValue: Boolean,
164
+ },
165
+ },
166
+ defaultConfig: {
167
+ field: "deletedAt",
168
+ createValue: (deleted) => {
169
+ if (deleted) return new Date();
170
+ return null;
171
+ },
172
+ },
173
+ })
174
+ );
175
+ ```
176
+
177
+ The config object also has a `allowToOneUpdates` option that can be used to allow updates to toOne relationships through
178
+ nested updates. By default this is set to `false` and will throw an error if you try to update a toOne relationship
179
+ through a nested update. If you want to allow this you can set `allowToOneUpdates` to `true`:
180
+
181
+ ```typescript
182
+ const extendedClient = client.$extends(
183
+ createSoftDeleteExtension({
184
+ models: {
185
+ Comment: {
186
+ field: "deleted",
187
+ createValue: Boolean,
188
+ allowToOneUpdates: true,
189
+ },
190
+ },
191
+ })
192
+ );
193
+ ```
194
+
195
+ For more information for why updating through toOne relationship is disabled by default see the
196
+ [Updating Records](#updating-records) section.
197
+
198
+ Similarly to `allowToOneUpdates` there is an `allowCompoundUniqueIndexWhere` option that can be used to allow using
199
+ where objects with compound unique index fields when using `findUnique` queries. By default this is set to `false` and
200
+ will throw an error if you try to use a where with compound unique index fields. If you want to allow this you can set
201
+ `allowCompoundUniqueIndexWhere` to `true`:
202
+
203
+ ```typescript
204
+ const extendedClient = client.$extends(
205
+ createSoftDeleteExtension({
206
+ models: {
207
+ Comment: {
208
+ field: "deleted",
209
+ createValue: Boolean,
210
+ allowCompoundUniqueIndexWhere: true,
211
+ },
212
+ },
213
+ })
214
+ );
215
+ ```
216
+
217
+ For more information for why updating through toOne relationship is disabled by default see the
218
+ [Excluding Soft Deleted Records in a `findUnique` Operation](#excluding-soft-deleted-records-in-a-findunique-operation) section.
219
+
220
+
221
+ To allow to one updates or compound unique index fields globally you can use the `defaultConfig` to do so:
222
+
223
+ ```typescript
224
+ const extendedClient = client.$extends(
225
+ createSoftDeleteExtension({
226
+ models: {
227
+ User: true,
228
+ Comment: true,
229
+ },
230
+ defaultConfig: {
231
+ field: "deleted",
232
+ createValue: Boolean,
233
+ allowToOneUpdates: true,
234
+ allowCompoundUniqueIndexWhere: true,
235
+ },
236
+ })
237
+ );
238
+ ```
239
+
240
+ ### Prisma Schema Setup
241
+
242
+ The Prisma schema must be updated to include the soft delete field for each model you want to use soft delete with.
243
+
244
+ For models configured to use the default field and type you must add the `deleted` field to your Prisma schema manually.
245
+ Using the Comment model configured in [Extension Setup](#extension-setup) you would need add the following to the
246
+ Prisma schema:
247
+
248
+ ```prisma
249
+ model Comment {
250
+ deleted Boolean @default(false)
251
+ [other fields]
252
+ }
253
+ ```
254
+
255
+ If the Comment model was configured to use a `deletedAt` field where the value is null by default and a `DateTime` when
256
+ the record is deleted you would need to add the following to your Prisma schema:
257
+
258
+ ```prisma
259
+ model Comment {
260
+ deletedAt DateTime?
261
+ [other fields]
262
+ }
263
+ ```
264
+
265
+ Models configured to use soft delete that are related to other models through a toOne relationship must have this
266
+ relationship defined as optional. This is because the extension will exclude soft deleted records when the relationship
267
+ is included or selected. If the relationship is not optional the types for the relation will be incorrect and you may
268
+ get runtime errors.
269
+
270
+ For example if you have an `author` relationship on the Comment model and the User model is configured to use soft
271
+ delete you would need to change the relationship to be optional:
272
+
273
+ ```prisma
274
+ model Comment {
275
+ authorId Int?
276
+ author User? @relation(fields: [authorId], references: [id])
277
+ [other fields]
278
+ }
279
+ ```
280
+
281
+ `@unique` fields on models that are configured to use soft deletes may cause problems due to the records not actually
282
+ being deleted. If a record is soft deleted and then a new record is created with the same value for the unique field,
283
+ the new record will not be created.
284
+
285
+ ## Behaviour
286
+
287
+ The main behaviour of the extension is to replace delete operations with update operations that set the soft delete
288
+ field to the deleted value.
289
+
290
+ The extension also prevents accidentally fetching or updating soft deleted records by excluding soft deleted records
291
+ from find queries, includes, selects and bulk updates. The extension does allow explicit queries for soft deleted
292
+ records and allows updates through unique fields such is it's id. The reason it allows updates through unique fields is
293
+ because soft deleted records can only be fetched explicitly so updates through a unique fields should be intentional.
294
+
295
+ ### Deleting Records
296
+
297
+ When deleting a record using the `delete` or `deleteMany` operations the extension will change the operation to an
298
+ `update` operation and set the soft delete field to be the deleted value defined in the config for that model.
299
+
300
+ For example if the Comment model was configured to use the default `deleted` field of type `Boolean` the extension
301
+ would change the `delete` operation to an `update` operation and set the `deleted` field to `true`.
302
+
303
+ #### Deleting a Single Record
304
+
305
+ When deleting a single record using the `delete` operation:
306
+
307
+ ```typescript
308
+ await client.comment.delete({
309
+ where: {
310
+ id: 1,
311
+ },
312
+ });
313
+ ```
314
+
315
+ The extension would change the operation to:
316
+
317
+ ```typescript
318
+ await client.comment.update({
319
+ where: {
320
+ id: 1,
321
+ },
322
+ data: {
323
+ deleted: true,
324
+ },
325
+ });
326
+ ```
327
+
328
+ #### Deleting Multiple Records
329
+
330
+ When deleting multiple records using the `deleteMany` operation:
331
+
332
+ ```typescript
333
+ await client.comment.deleteMany({
334
+ where: {
335
+ id: {
336
+ in: [1, 2, 3],
337
+ },
338
+ },
339
+ });
340
+ ```
341
+
342
+ The extension would change the operation to:
343
+
344
+ ```typescript
345
+ await client.comment.updateMany({
346
+ where: {
347
+ id: {
348
+ in: [1, 2, 3],
349
+ },
350
+ },
351
+ data: {
352
+ deleted: true,
353
+ },
354
+ });
355
+ ```
356
+
357
+ #### Deleting Through a relationship
358
+
359
+ When using a nested delete through a relationship the extension will change the nested delete operation to an update
360
+ operation:
361
+
362
+ ```typescript
363
+ await client.post.update({
364
+ where: {
365
+ id: 1,
366
+ },
367
+ data: {
368
+ comments: {
369
+ delete: {
370
+ where: {
371
+ id: 2,
372
+ },
373
+ },
374
+ },
375
+ author: {
376
+ delete: true,
377
+ },
378
+ },
379
+ });
380
+ ```
381
+
382
+ The extension would change the operation to:
383
+
384
+ ```typescript
385
+ await client.post.update({
386
+ where: {
387
+ id: 1,
388
+ },
389
+ data: {
390
+ comments: {
391
+ update: {
392
+ where: {
393
+ id: 2,
394
+ },
395
+ data: {
396
+ deleted: true,
397
+ },
398
+ },
399
+ },
400
+ author: {
401
+ update: {
402
+ deleted: true,
403
+ },
404
+ },
405
+ },
406
+ });
407
+ ```
408
+
409
+ The same behaviour applies when using a nested `deleteMany` with a toMany relationship.
410
+
411
+ #### Hard Deletes
412
+
413
+ Hard deletes are not currently supported by this extension, when the `extendedWhereUnique` feature is supported
414
+ it will be possible to explicitly hard delete a soft deleted record. In the meantime you can use the `executeRaw`
415
+ operation to perform hard deletes.
416
+
417
+ ### Excluding Soft Deleted Records
418
+
419
+ When using the `findUnique`, `findFirst` and `findMany` operations the extension will modify the `where` object passed
420
+ to exclude soft deleted records. It does this by adding an additional condition to the `where` object that excludes
421
+ records where the soft delete field is set to the deleted value defined in the config for that model.
422
+
423
+ #### Excluding Soft Deleted Records in a `findFirst` Operation
424
+
425
+ When using a `findFirst` operation the extension will modify the `where` object to exclude soft deleted records, so for:
426
+
427
+ ```typescript
428
+ await client.comment.findFirst({
429
+ where: {
430
+ id: 1,
431
+ },
432
+ });
433
+ ```
434
+
435
+ The extension would change the operation to:
436
+
437
+ ```typescript
438
+ await client.comment.findFirst({
439
+ where: {
440
+ id: 1,
441
+ deleted: false,
442
+ },
443
+ });
444
+ ```
445
+
446
+ #### Excluding Soft Deleted Records in a `findMany` Operation
447
+
448
+ When using a `findMany` operation the extension will modify the `where` object to exclude soft deleted records, so for:
449
+
450
+ ```typescript
451
+ await client.comment.findMany({
452
+ where: {
453
+ id: 1,
454
+ },
455
+ });
456
+ ```
457
+
458
+ The extension would change the operation to:
459
+
460
+ ```typescript
461
+ await client.comment.findMany({
462
+ where: {
463
+ id: 1,
464
+ deleted: false,
465
+ },
466
+ });
467
+ ```
468
+
469
+ #### Excluding Soft Deleted Records in a `findUnique` Operation
470
+
471
+ When using a `findUnique` operation the extension will change the query to use `findFirst` so that it can modify the
472
+ `where` object to exclude soft deleted records, so for:
473
+
474
+ ```typescript
475
+ await client.comment.findUnique({
476
+ where: {
477
+ id: 1,
478
+ },
479
+ });
480
+ ```
481
+
482
+ The extension would change the operation to:
483
+
484
+ ```typescript
485
+ await client.comment.findFirst({
486
+ where: {
487
+ id: 1,
488
+ deleted: false,
489
+ },
490
+ });
491
+ ```
492
+
493
+ When querying using a compound unique index in the where object the extension will throw an error by default. This
494
+ is because it is not possible to use these types of where object with `findFirst` and it is not possible to exclude
495
+ soft-deleted records when using `findUnique`. For example take the following query:
496
+
497
+ ```typescript
498
+ await client.user.findUnique({
499
+ where: {
500
+ name_email: {
501
+ name: "foo",
502
+ email: "bar",
503
+ },
504
+ },
505
+ });
506
+ ```
507
+
508
+ Since the compound unique index `@@unique([name, email])` is being queried through the `name_email` field of the where
509
+ object the extension will throw to avoid accidentally returning a soft deleted record.
510
+
511
+ It is possible to override this behaviour by setting `allowCompoundUniqueIndexWhere` to `true` in the model config.
512
+
513
+ ### Updating Records
514
+
515
+ Updating records is split into three categories, updating a single record using a root operation, updating a single
516
+ record through a relation and updating multiple records either through a root operation or a relation.
517
+
518
+ When updating a single record using a root operation such as `update` or `upsert` the extension will not modify the
519
+ operation. This is because unless explicitly queried for soft deleted records should not be returned from queries,
520
+ so if these operations are updating a soft deleted record it should be intentional.
521
+
522
+ When updating a single record through a relation the extension will throw an error by default. This is because it is
523
+ not possible to filter out soft deleted records for nested toOne relations. For example take the following query:
524
+
525
+ ```typescript
526
+ await client.post.update({
527
+ where: {
528
+ id: 1,
529
+ },
530
+ data: {
531
+ author: {
532
+ update: {
533
+ name: "foo",
534
+ },
535
+ },
536
+ },
537
+ });
538
+ ```
539
+
540
+ Since the `author` field is a toOne relation it does not support a where object. This means that if the `author` field
541
+ is a soft deleted record it will be updated accidentally.
542
+
543
+ It is possible to override this behaviour by setting `allowToOneUpdates` to `true` in the extension config.
544
+
545
+ When updating multiple records using `updateMany` the extension will modify the `where` object passed to exclude soft
546
+ deleted records. For example take the following query:
547
+
548
+ ```typescript
549
+ await client.comment.updateMany({
550
+ where: {
551
+ id: 1,
552
+ },
553
+ data: {
554
+ content: "foo",
555
+ },
556
+ });
557
+ ```
558
+
559
+ The extension would change the operation to:
560
+
561
+ ```typescript
562
+ await client.comment.updateMany({
563
+ where: {
564
+ id: 1,
565
+ deleted: false,
566
+ },
567
+ data: {
568
+ content: "foo",
569
+ },
570
+ });
571
+ ```
572
+
573
+ This also works when a toMany relation is updated:
574
+
575
+ ```typescript
576
+ await client.post.update({
577
+ where: {
578
+ id: 1,
579
+ },
580
+ data: {
581
+ comments: {
582
+ updateMany: {
583
+ where: {
584
+ id: 1,
585
+ },
586
+ data: {
587
+ content: "foo",
588
+ },
589
+ },
590
+ },
591
+ },
592
+ });
593
+ ```
594
+
595
+ The extension would change the operation to:
596
+
597
+ ```typescript
598
+ await client.post.update({
599
+ where: {
600
+ id: 1,
601
+ },
602
+ data: {
603
+ comments: {
604
+ updateMany: {
605
+ where: {
606
+ id: 1,
607
+ deleted: false,
608
+ },
609
+ data: {
610
+ content: "foo",
611
+ },
612
+ },
613
+ },
614
+ },
615
+ });
616
+ ```
617
+
618
+ #### Explicitly Updating Many Soft Deleted Records
619
+
620
+ When using the `updateMany` operation it is possible to explicitly update many soft deleted records by setting the
621
+ deleted field to the deleted value defined in the config for that model. An example that would update soft deleted
622
+ records would be:
623
+
624
+ ```typescript
625
+ await client.comment.updateMany({
626
+ where: {
627
+ content: "foo",
628
+ deleted: true,
629
+ },
630
+ data: {
631
+ content: "bar",
632
+ },
633
+ });
634
+ ```
635
+
636
+ ### Where objects
637
+
638
+ When using a `where` query it is possible to reference models configured to use soft deletes. In this case the
639
+ extension will modify the `where` object to exclude soft deleted records from the query, so for:
640
+
641
+ ```typescript
642
+ await client.post.findMany({
643
+ where: {
644
+ id: 1,
645
+ comments: {
646
+ some: {
647
+ content: "foo",
648
+ },
649
+ },
650
+ },
651
+ });
652
+ ```
653
+
654
+ The extension would change the operation to:
655
+
656
+ ```typescript
657
+ await client.post.findMany({
658
+ where: {
659
+ id: 1,
660
+ comments: {
661
+ some: {
662
+ content: "foo",
663
+ deleted: false,
664
+ },
665
+ },
666
+ },
667
+ });
668
+ ```
669
+
670
+ This also works when the where object includes logical operators:
671
+
672
+ ```typescript
673
+ await client.post.findMany({
674
+ where: {
675
+ id: 1,
676
+ OR: [
677
+ {
678
+ comments: {
679
+ some: {
680
+ author: {
681
+ name: "Jack",
682
+ },
683
+ },
684
+ },
685
+ },
686
+ {
687
+ comments: {
688
+ none: {
689
+ author: {
690
+ name: "Jill",
691
+ },
692
+ },
693
+ },
694
+ },
695
+ ],
696
+ },
697
+ });
698
+ ```
699
+
700
+ The extension would change the operation to:
701
+
702
+ ```typescript
703
+ await client.post.findMany({
704
+ where: {
705
+ id: 1,
706
+ OR: [
707
+ {
708
+ comments: {
709
+ some: {
710
+ deleted: false,
711
+ author: {
712
+ name: "Jack",
713
+ },
714
+ },
715
+ },
716
+ },
717
+ {
718
+ comments: {
719
+ none: {
720
+ deleted: false,
721
+ author: {
722
+ name: "Jill",
723
+ },
724
+ },
725
+ },
726
+ },
727
+ ],
728
+ },
729
+ });
730
+ ```
731
+
732
+ When using the `every` modifier the extension will modify the `where` object to exclude soft deleted records from the
733
+ query in a different way, so for:
734
+
735
+ ```typescript
736
+ await client.post.findMany({
737
+ where: {
738
+ id: 1,
739
+ comments: {
740
+ every: {
741
+ content: "foo",
742
+ },
743
+ },
744
+ },
745
+ });
746
+ ```
747
+
748
+ The extension would change the operation to:
749
+
750
+ ```typescript
751
+ await client.post.findMany({
752
+ where: {
753
+ id: 1,
754
+ comments: {
755
+ every: {
756
+ OR: [{ deleted: { not: false } }, { content: "foo" }],
757
+ },
758
+ },
759
+ },
760
+ });
761
+ ```
762
+
763
+ This is because if the same logic that is used for `some` and `none` were to be used with `every` then the query would
764
+ fail for cases where there are deleted models.
765
+
766
+ The deleted case uses the `not` operator to ensure that the query works for custom fields and types. For example if the
767
+ field was configured to be `deletedAt` where the type is `DateTime` when deleted and `null` when not deleted then the
768
+ query would be:
769
+
770
+ ```typescript
771
+ await client.post.findMany({
772
+ where: {
773
+ id: 1,
774
+ comments: {
775
+ every: {
776
+ OR: [{ deletedAt: { not: null } }, { content: "foo" }],
777
+ },
778
+ },
779
+ },
780
+ });
781
+ ```
782
+
783
+ #### Explicitly Querying Soft Deleted Records
784
+
785
+ It is possible to explicitly query soft deleted records by setting the configured field in the `where` object. For
786
+ example the following will include deleted records in the results:
787
+
788
+ ```typescript
789
+ await client.comment.findMany({
790
+ where: {
791
+ deleted: true,
792
+ },
793
+ });
794
+ ```
795
+
796
+ It is also possible to explicitly query soft deleted records through relationships in the `where` object. For example
797
+ the following will also not be modified:
798
+
799
+ ```typescript
800
+ await client.post.findMany({
801
+ where: {
802
+ comments: {
803
+ some: {
804
+ deleted: true,
805
+ },
806
+ },
807
+ },
808
+ });
809
+ ```
810
+
811
+ ### Including or Selecting Soft Deleted Records
812
+
813
+ When using `include` or `select` the extension will modify the `include` and `select` objects passed to exclude soft
814
+ deleted records.
815
+
816
+ #### Including or Selecting toMany Relations
817
+
818
+ When using `include` or `select` on a toMany relationship the extension will modify the where object to exclude soft
819
+ deleted records from the query, so for:
820
+
821
+ ```typescript
822
+ await client.post.findMany({
823
+ where: {
824
+ id: 1,
825
+ },
826
+ include: {
827
+ comments: true,
828
+ },
829
+ });
830
+ ```
831
+
832
+ If the Comment model was configured to be soft deleted the extension would modify the `include` action where object to
833
+ exclude soft deleted records, so the query would be:
834
+
835
+ ```typescript
836
+ await client.post.findMany({
837
+ where: {
838
+ id: 1,
839
+ },
840
+ include: {
841
+ comments: {
842
+ where: {
843
+ deleted: false,
844
+ },
845
+ },
846
+ },
847
+ });
848
+ ```
849
+
850
+ The same applies for `select`:
851
+
852
+ ```typescript
853
+ await client.post.findMany({
854
+ where: {
855
+ id: 1,
856
+ },
857
+ select: {
858
+ comments: true,
859
+ },
860
+ });
861
+ ```
862
+
863
+ This also works for nested includes and selects:
864
+
865
+ ```typescript
866
+ await client.user.findMany({
867
+ where: {
868
+ id: 1,
869
+ },
870
+ include: {
871
+ posts: {
872
+ select: {
873
+ comments: {
874
+ where: {
875
+ content: "foo",
876
+ },
877
+ },
878
+ },
879
+ },
880
+ },
881
+ });
882
+ ```
883
+
884
+ The extension would modify the query to:
885
+
886
+ ```typescript
887
+ await client.user.findMany({
888
+ where: {
889
+ id: 1,
890
+ },
891
+ include: {
892
+ posts: {
893
+ select: {
894
+ comments: {
895
+ where: {
896
+ deleted: false,
897
+ content: "foo",
898
+ },
899
+ },
900
+ },
901
+ },
902
+ },
903
+ });
904
+ ```
905
+
906
+ #### Including or Selecting toOne Relations
907
+
908
+ Records included through a toOne relation are also excluded, however there is no way to explicitly include them. For
909
+ example the following query:
910
+
911
+ ```typescript
912
+ await client.post.findFirst({
913
+ where: {
914
+ id: 1,
915
+ },
916
+ include: {
917
+ author: true,
918
+ },
919
+ });
920
+ ```
921
+
922
+ The extension would not modify the query since toOne relations do not support where clauses. Instead the extension
923
+ will manually filter results based on the configured deleted field.
924
+
925
+ So if the author of the Post was soft deleted the extension would filter the results and remove the author from the
926
+ results:
927
+
928
+ ```typescript
929
+ {
930
+ id: 1,
931
+ title: "foo",
932
+ author: null
933
+ }
934
+ ```
935
+
936
+ When selecting specific fields on a toOne relation the extension will manually add the configured deleted field to the
937
+ select object, filter the results and finally strip the deleted field from the results before returning them.
938
+
939
+ For example the following query would behave that way:
940
+
941
+ ```typescript
942
+ await client.post.findMany({
943
+ where: {
944
+ id: 1,
945
+ },
946
+ select: {
947
+ author: {
948
+ select: {
949
+ name: true,
950
+ },
951
+ },
952
+ },
953
+ });
954
+ ```
955
+
956
+ #### Explicitly Including Soft Deleted Records in toMany Relations
957
+
958
+ It is possible to explicitly include soft deleted records in toMany relations by adding the configured deleted field to
959
+ the `where` object. For example the following will include deleted records in the results:
960
+
961
+ ```typescript
962
+ await client.post.findMany({
963
+ where: {
964
+ id: 1,
965
+ },
966
+ include: {
967
+ comments: {
968
+ where: {
969
+ deleted: true,
970
+ },
971
+ },
972
+ },
973
+ });
974
+ ```
975
+
976
+ ## LICENSE
977
+
978
+ Apache 2.0
979
+
980
+ [npm]: https://www.npmjs.com/
981
+ [node]: https://nodejs.org
982
+ [build-badge]: https://github.com/olivierwilkinson/prisma-extension-soft-delete/workflows/prisma-extension-soft-delete/badge.svg
983
+ [build]: https://github.com/olivierwilkinson/prisma-extension-soft-delete/actions?query=branch%main+workflow%3Aprisma-extension-soft-delete
984
+ [version-badge]: https://img.shields.io/npm/v/prisma-extension-soft-delete.svg?style=flat-square
985
+ [package]: https://www.npmjs.com/package/prisma-extension-soft-delete
986
+ [downloads-badge]: https://img.shields.io/npm/dm/prisma-extension-soft-delete.svg?style=flat-square
987
+ [npmtrends]: http://www.npmtrends.com/prisma-extension-soft-delete
988
+ [license-badge]: https://img.shields.io/npm/l/prisma-extension-soft-delete.svg?style=flat-square
989
+ [license]: https://github.com/olivierwilkinson/prisma-extension-soft-delete/blob/main/LICENSE
990
+ [prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square
991
+ [prs]: http://makeapullrequest.com
992
+ [coc-badge]: https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square
993
+ [coc]: https://github.com/olivierwilkinson/prisma-extension-soft-delete/blob/main/other/CODE_OF_CONDUCT.md