vsrepo 1.3.4 → 1.3.6
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 +181 -137
- package/README.pt-BR.md +188 -144
- package/dist/DynamicRepository.d.ts +36 -15
- package/dist/VSRepoError.d.ts +21 -1
- package/dist/VSRepository.d.ts +126 -87
- package/dist/VSRepository.js +49 -49
- package/dist/internal/constants/dynamic-methods-key.constant.js +4 -0
- package/dist/internal/decorators/dynamic-method.decorator.js +4 -4
- package/dist/internal/decorators/query-method.decorator.js +7 -3
- package/dist/internal/entities/dynamic-method-metadata.entity.js +3 -3
- package/dist/internal/resolvers/base-methods.resolve.js +2 -2
- package/dist/internal/resolvers/dbAndPrismaArgs.resolve.js +7 -4
- package/dist/internal/resolvers/dynamic-method-customization.resolve.js +57 -0
- package/dist/internal/resolvers/dynamic-method-info.resolve.js +279 -0
- package/dist/internal/resolvers/dynamic-methods-metadata.resolve.js +2 -2
- package/dist/internal/resolvers/pretty-wheres.resolve.js +8 -8
- package/dist/internal/resolvers/types/dynamic-method-where-ops.type.js +2 -0
- package/dist/internal/utils/schemas.util.js +8 -2
- package/dist/internal/validation/constructor-config.validate.js +7 -4
- package/dist/internal/validation/method-options.validate.js +10 -3
- package/package.json +11 -2
- package/scripts/configure-prisma-import.mjs +12 -8
- package/scripts/copy-types.mjs +16 -16
- package/dist/internal/constants/dinamic-methods-key.constant.js +0 -4
- package/dist/internal/resolvers/dinamic-method-customization.resolve.js +0 -57
- package/dist/internal/resolvers/dinamic-method-info.resolve.js +0 -279
- /package/dist/internal/{resolvers/types/dinamic-method-customization.type.js → errors/types/vs-repo-runtime-error-code.type.js} +0 -0
- /package/dist/internal/resolvers/types/{dinamic-method-info.type.js → dynamic-method-customization.type.js} +0 -0
- /package/dist/internal/resolvers/types/{dinamic-method-where-ops.type.js → dynamic-method-info.type.js} +0 -0
package/README.md
CHANGED
|
@@ -35,10 +35,11 @@ VSRepository lets you create strongly-typed repositories with:
|
|
|
35
35
|
- [Merge](#merge)
|
|
36
36
|
- [Configuring the base methods](#configuring-the-base-methods)
|
|
37
37
|
- [Select Models](#select-models)
|
|
38
|
+
- [Raw select (options.select)](#raw-select-optionsselect)
|
|
38
39
|
- [Include Models](#include-models)
|
|
39
40
|
- [Raw include (options.include)](#raw-include-optionsinclude)
|
|
40
41
|
- [Required Where](#required-where)
|
|
41
|
-
- [Default
|
|
42
|
+
- [Default Ordering](#default-ordering)
|
|
42
43
|
- [`see` option](#see-option)
|
|
43
44
|
- [Dynamic methods](#dynamic-methods)
|
|
44
45
|
- [Available prefixes](#available-prefixes)
|
|
@@ -95,15 +96,17 @@ npx vsrepo generate \
|
|
|
95
96
|
|
|
96
97
|
**Available flags:**
|
|
97
98
|
|
|
98
|
-
| Flag | Alias | Default
|
|
99
|
+
| Flag | Alias | Default |
|
|
99
100
|
| ---------- | ----- | -------------------- |
|
|
100
101
|
| `--output` | `-o` | `generated/vsrepo` |
|
|
101
102
|
| `--prisma` | `-p` | `generated/prisma` |
|
|
102
103
|
|
|
103
104
|
**Generated files:**
|
|
104
105
|
|
|
105
|
-
```
|
|
106
|
+
```text
|
|
106
107
|
generated/vsrepo/
|
|
108
|
+
├── DynamicRepository.ts
|
|
109
|
+
├── DynamicRepository.types.d.ts
|
|
107
110
|
├── VSRepoError.ts
|
|
108
111
|
├── VSRepoError.types.d.ts
|
|
109
112
|
├── VSRepository.ts
|
|
@@ -334,18 +337,18 @@ When calling `.build(prisma)`, the base methods below are automatically made ava
|
|
|
334
337
|
| Method | Description |
|
|
335
338
|
| ------------------------ | -------------------------------------------------------------------------------------------------------------|
|
|
336
339
|
| `get(pk)` | Fetches a record by its primary key |
|
|
337
|
-
| `getOrThrow(pk)` | Fetches a record by its primary key; throws `VSRepoRuntimeError` (code `"20727"`) if not found
|
|
338
|
-
| `getList(pks)` | Fetches multiple records from a list of primary keys
|
|
340
|
+
| `getOrThrow(pk)` | Fetches a record by its primary key; throws `VSRepoRuntimeError` (code `"20727"`) if not found |
|
|
341
|
+
| `getList(pks)` | Fetches multiple records from a list of primary keys |
|
|
339
342
|
| `save(obj)` | Creates or updates — if the object has a `pk` it performs an `upsert`, otherwise a `create` |
|
|
340
|
-
| `saveList(objs)` | Saves an array of objects in a single automatic transaction
|
|
341
|
-
| `patch(pk, obj)` | Partially updates a record by its primary key
|
|
342
|
-
| `patchList(tuples)` | Partially updates multiple records via an array of `[pk, obj]` tuples in an automatic transaction
|
|
343
|
+
| `saveList(objs)` | Saves an array of objects in a single automatic transaction |
|
|
344
|
+
| `patch(pk, obj)` | Partially updates a record by its primary key |
|
|
345
|
+
| `patchList(tuples)` | Partially updates multiple records via an array of `[pk, obj]` tuples in an automatic transaction |
|
|
343
346
|
| `merge(pk, obj)` | Fetches a record and deep merges it in memory — **does not persist**, returns the merged object |
|
|
344
|
-
| `remove(pk)` | Removes a record by its primary key
|
|
345
|
-
| `removeList(pks)` | Removes several records by a list of primary keys — returns `{ count }`
|
|
346
|
-
| `getAll()` | Returns all records (accepts `pagination` and `order` in `options`)
|
|
347
|
-
| `total()` | Returns the total number of records
|
|
348
|
-
| `has(pk)` | Checks whether a record exists by its primary key — returns `boolean`
|
|
347
|
+
| `remove(pk)` | Removes a record by its primary key |
|
|
348
|
+
| `removeList(pks)` | Removes several records by a list of primary keys — returns `{ count }` |
|
|
349
|
+
| `getAll()` | Returns all records (accepts `pagination` and `order` in `options`) |
|
|
350
|
+
| `total()` | Returns the total number of records |
|
|
351
|
+
| `has(pk)` | Checks whether a record exists by its primary key — returns `boolean` |
|
|
349
352
|
|
|
350
353
|
All of them accept `options` as the last argument.
|
|
351
354
|
|
|
@@ -354,11 +357,11 @@ All of them accept `options` as the last argument.
|
|
|
354
357
|
When `softRemovekName` is configured on the repository, the following additional methods become available:
|
|
355
358
|
|
|
356
359
|
| Method | Description |
|
|
357
|
-
| -------------------------- |
|
|
360
|
+
| -------------------------- | ---------------------------------------------------------------------------------- |
|
|
358
361
|
| `softRemove(pk)` | Marks a record as removed by filling `softRemovekName` with the current date |
|
|
359
|
-
| `softRemoveList(pks)` | Marks multiple records as removed in batch — returns `{ count }`
|
|
360
|
-
| `restore(pk)` | Restores a soft-deleted record, clearing the `softRemovekName` field
|
|
361
|
-
| `restoreList(pks)` | Restores multiple soft-deleted records in batch — returns `{ count }`
|
|
362
|
+
| `softRemoveList(pks)` | Marks multiple records as removed in batch — returns `{ count }` |
|
|
363
|
+
| `restore(pk)` | Restores a soft-deleted record, clearing the `softRemovekName` field |
|
|
364
|
+
| `restoreList(pks)` | Restores multiple soft-deleted records in batch — returns `{ count }` |
|
|
362
365
|
|
|
363
366
|
```ts
|
|
364
367
|
const userRepository = setupVSRepo<User, "user">()(({
|
|
@@ -531,6 +534,35 @@ const user = await userRepository.get(id, { selectModel: "minimal" });
|
|
|
531
534
|
const fullUser = await userRepository.get(id, { selectModel: false });
|
|
532
535
|
```
|
|
533
536
|
|
|
537
|
+
### Raw `select` (`options.select`)
|
|
538
|
+
|
|
539
|
+
Besides `selectModel` (named, pre-configured in `selectModels`), you can pass a raw Prisma `select` directly in the call, without registering it beforehand on the repository.
|
|
540
|
+
|
|
541
|
+
```ts
|
|
542
|
+
const user = await userRepository.get(id, {
|
|
543
|
+
select: { id: true, name: true },
|
|
544
|
+
});
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
`options.select` accepts any valid Prisma `select` for the repository's model — it's fully typed and offers the same autocomplete/validation as calling `prisma.user.findMany({ select: ... })` directly, and the method's return type is narrowed to exactly the fields selected.
|
|
548
|
+
|
|
549
|
+
**Rules and behavior:**
|
|
550
|
+
|
|
551
|
+
- **Mutually exclusive with `selectModel`, `includeModel` and `include`.** Only one of the four can be provided per call; the types enforce this — passing more than one is a compile-time error.
|
|
552
|
+
- **Ad hoc, not reusable.** Unlike `selectModel`, it doesn't need to be declared in `selectModels`. Use it for one-off projections that don't justify a named select model.
|
|
553
|
+
- **No `defaultSelectModel` is applied.** When `select` is provided, the default select (`defaultSelectModel`) is ignored and only the raw `select` is sent to Prisma.
|
|
554
|
+
|
|
555
|
+
```ts
|
|
556
|
+
// CORRECT ✅ — raw select only
|
|
557
|
+
await userRepository.get(id, { select: { id: true, name: true } });
|
|
558
|
+
|
|
559
|
+
// WRONG ❌ — combining select with selectModel/includeModel/include is not allowed
|
|
560
|
+
await userRepository.get(id, { selectModel: "public", select: { id: true } });
|
|
561
|
+
await userRepository.get(id, { include: { posts: true }, select: { id: true } });
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
> **When to use `selectModel` vs. `select`:** prefer `selectModel` for projections reused across multiple calls (defined once in `selectModels`); use `select` for specific, occasional projections that don't need a name.
|
|
565
|
+
|
|
534
566
|
---
|
|
535
567
|
|
|
536
568
|
## Include Models
|
|
@@ -629,15 +661,15 @@ Useful for manual soft-deletes, multi-tenancy, and global filters of any kind.
|
|
|
629
661
|
|
|
630
662
|
---
|
|
631
663
|
|
|
632
|
-
## Default
|
|
664
|
+
## Default Ordering
|
|
633
665
|
|
|
634
|
-
`
|
|
666
|
+
`defaultOrdering` defines a default ordering that's automatically applied to every query that accepts `orderBy`, without needing to repeat the `order` argument on every call.
|
|
635
667
|
|
|
636
668
|
```ts
|
|
637
669
|
const userRepository = setupVSRepo<User, "user">()(({
|
|
638
670
|
tableName: "user",
|
|
639
671
|
pkName: "id",
|
|
640
|
-
|
|
672
|
+
defaultOrdering: { createdAt: "desc" },
|
|
641
673
|
}).build(prisma);
|
|
642
674
|
```
|
|
643
675
|
|
|
@@ -651,23 +683,23 @@ const users = await userRepository.getAll();
|
|
|
651
683
|
const paginated = await userRepository.getAll({ pagination: { take: 10 } });
|
|
652
684
|
```
|
|
653
685
|
|
|
654
|
-
**`
|
|
686
|
+
**`defaultOrdering` is ignored when:**
|
|
655
687
|
|
|
656
688
|
- The method uses the `Ordered`, `OrderedAndPaginated`, or `PaginatedAndOrdered` suffix — in these cases the `order` argument passed in the call takes priority.
|
|
657
|
-
- The dynamic method has `
|
|
689
|
+
- The dynamic method has `injectOrdering` configured — the method's fixed ordering takes precedence.
|
|
658
690
|
|
|
659
691
|
```ts
|
|
660
692
|
methods: {
|
|
661
|
-
findManyPaginatedAndOrdered: { map: true }, // order comes from the argument →
|
|
662
|
-
findManyByActive: { map: true }, // no Ordered →
|
|
693
|
+
findManyPaginatedAndOrdered: { map: true }, // order comes from the argument → defaultOrdering ignored
|
|
694
|
+
findManyByActive: { map: true }, // no Ordered → defaultOrdering applied
|
|
663
695
|
findManyByStatus: {
|
|
664
696
|
map: true,
|
|
665
|
-
|
|
697
|
+
injectOrdering: { name: "asc" }, // injectOrdering → defaultOrdering ignored
|
|
666
698
|
},
|
|
667
699
|
}
|
|
668
700
|
```
|
|
669
701
|
|
|
670
|
-
> `
|
|
702
|
+
> `defaultOrdering` accepts the same type as Prisma's native `orderBy` for the model — including arrays of chained orderings.
|
|
671
703
|
|
|
672
704
|
---
|
|
673
705
|
|
|
@@ -675,9 +707,9 @@ methods: {
|
|
|
675
707
|
|
|
676
708
|
When `softRemovekName` is configured, every method accepts the `see` option to control the visibility of soft-deleted records:
|
|
677
709
|
|
|
678
|
-
| Value | Behavior
|
|
710
|
+
| Value | Behavior |
|
|
679
711
|
| ----------- | --------------------------------------------------------------|
|
|
680
|
-
| `"active"` | Returns only records that are **not** removed (default)
|
|
712
|
+
| `"active"` | Returns only records that are **not** removed (default) |
|
|
681
713
|
| `"removed"` | Returns only removed records |
|
|
682
714
|
| `"all"` | Returns all records, regardless of status |
|
|
683
715
|
|
|
@@ -715,39 +747,39 @@ methods: {
|
|
|
715
747
|
|
|
716
748
|
The method name's prefix determines which Prisma operation will be called and which arguments are expected.
|
|
717
749
|
|
|
718
|
-
| Prefix
|
|
719
|
-
| ---------------------------- | -------------------------- | ------------------------ |
|
|
720
|
-
| `findOneBy`
|
|
721
|
-
| `findBy`
|
|
722
|
-
| `findUniqueBy` | `findUnique`
|
|
723
|
-
| `findUniqueOrThrowBy` | `findUniqueOrThrow`
|
|
724
|
-
| `findFirstBy` | `findFirst`
|
|
725
|
-
| `findFirstOrThrowBy` | `findFirstOrThrow`
|
|
726
|
-
| `findFirst` | `findFirst`
|
|
727
|
-
| `findFirstOrThrow` | `findFirstOrThrow`
|
|
728
|
-
| `findManyBy` | `findMany`
|
|
729
|
-
| `findMany` | `findMany`
|
|
730
|
-
| `findOneWhere`
|
|
731
|
-
| `findListWhere`
|
|
732
|
-
| `existsBy`
|
|
733
|
-
| `existsWhere`
|
|
734
|
-
| `countBy`
|
|
735
|
-
| `countWhere`
|
|
736
|
-
| `count`
|
|
737
|
-
| `create`
|
|
738
|
-
| `createMany`
|
|
739
|
-
| `createManyAndReturn`
|
|
740
|
-
| `updateBy`
|
|
741
|
-
| `updateManyBy`
|
|
742
|
-
| `updateManyWhere`
|
|
743
|
-
| `updateManyAndReturnBy`
|
|
744
|
-
| `updateManyAndReturnWhere`
|
|
745
|
-
| `upsertBy`
|
|
746
|
-
| `deleteBy`
|
|
747
|
-
| `deleteManyBy`
|
|
748
|
-
| `deleteManyWhere`
|
|
749
|
-
| `aggregate`
|
|
750
|
-
| `groupBy`
|
|
750
|
+
| Prefix | Prisma operation | Return | Notes |
|
|
751
|
+
| ---------------------------- | -------------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------- |
|
|
752
|
+
| `findOneBy` | `findFirst` | `T \| null` | Single return. |
|
|
753
|
+
| `findBy` | `findMany` / `findFirst` | `T[]` or `T \| null` | Default is list; use `fbMode: "one"` for a single return (**deprecated**, use `findOneBy`) |
|
|
754
|
+
| `findUniqueBy` | `findUnique` | `T \| null` | |
|
|
755
|
+
| `findUniqueOrThrowBy` | `findUniqueOrThrow` | `T` | Throws an error if not found |
|
|
756
|
+
| `findFirstBy` | `findFirst` | `T \| null` | Accepts fields as filter |
|
|
757
|
+
| `findFirstOrThrowBy` | `findFirstOrThrow` | `T` | Accepts fields as filter; throws an error if not found |
|
|
758
|
+
| `findFirst` | `findFirst` | `T \| null` | No field filters; applies only `requiredWhere` and `pushWhere` |
|
|
759
|
+
| `findFirstOrThrow` | `findFirstOrThrow` | `T` | No field filters; applies only `requiredWhere` and `pushWhere`; throws an error if not found |
|
|
760
|
+
| `findManyBy` | `findMany` | `T[]` | Accepts fields as filter |
|
|
761
|
+
| `findMany` | `findMany` | `T[]` | No field filters; applies only `requiredWhere` and `pushWhere` |
|
|
762
|
+
| `findOneWhere` | `findFirst` | `T \| null` | Receives an explicit `where` object as argument |
|
|
763
|
+
| `findListWhere` | `findMany` | `T[]` | Receives an explicit `where` object as argument |
|
|
764
|
+
| `existsBy` | `findFirst` | `boolean` | Returns `true` if found, `false` otherwise |
|
|
765
|
+
| `existsWhere` | `findFirst` | `boolean` | Receives an explicit `where` object and returns whether it exists |
|
|
766
|
+
| `countBy` | `count` | `number` | Accepts fields as filter |
|
|
767
|
+
| `countWhere` | `count` | `number` | Receives an explicit `where` object as argument |
|
|
768
|
+
| `count` | `count` | `number` | No field filters; applies only `requiredWhere` and `pushWhere` |
|
|
769
|
+
| `create` | `create` | `T` | Receives `data` as argument |
|
|
770
|
+
| `createMany` | `createMany` | `{ count: number }` | Receives `data` as argument; supports `SkipDuplicates` |
|
|
771
|
+
| `createManyAndReturn` | `createManyAndReturn` | `T[]` | Receives `data` as argument; supports `SkipDuplicates` |
|
|
772
|
+
| `updateBy` | `update` | `T` | Receives `data` as argument |
|
|
773
|
+
| `updateManyBy` | `updateMany` | `{ count: number }` | Receives `data` as argument |
|
|
774
|
+
| `updateManyWhere` | `updateMany` | `{ count: number }` | Receives a `where` object and a `data` object as arguments |
|
|
775
|
+
| `updateManyAndReturnBy` | `updateManyAndReturn` | `T[]` | Receives `data` as argument |
|
|
776
|
+
| `updateManyAndReturnWhere` | `updateManyAndReturn` | `T[]` | Receives a `where` object and a `data` object as arguments |
|
|
777
|
+
| `upsertBy` | `upsert` | `T` | Receives `update` and `create` as arguments |
|
|
778
|
+
| `deleteBy` | `delete` | `T` | |
|
|
779
|
+
| `deleteManyBy` | `deleteMany` | `{ count: number }` | |
|
|
780
|
+
| `deleteManyWhere` | `deleteMany` | `{ count: number }` | Receives an explicit `where` object as argument |
|
|
781
|
+
| `aggregate` | `aggregate` | `Dynamic` | Name must be exact; receives native Prisma args; ignores `selectModels`, `pushWhere`, and `requiredWhere` |
|
|
782
|
+
| `groupBy` | `groupBy` | `Dynamic[]` | Name must be exact; receives native Prisma args; ignores `selectModels`, `pushWhere`, and `requiredWhere` |
|
|
751
783
|
|
|
752
784
|
---
|
|
753
785
|
|
|
@@ -755,29 +787,29 @@ The method name's prefix determines which Prisma operation will be called and wh
|
|
|
755
787
|
|
|
756
788
|
Filters are suffixes applied to the field name inside the method. The field itself comes capitalized right after the prefix (or after `By`).
|
|
757
789
|
|
|
758
|
-
| Suffix
|
|
790
|
+
| Suffix | Prisma operator | Argument required |
|
|
759
791
|
| -------------------- | ---------------------- | ---------------------------|
|
|
760
|
-
| *(no suffix)* | equality (`=`)
|
|
761
|
-
| `Not` | `not`
|
|
762
|
-
| `In` | `in`
|
|
763
|
-
| `NotIn` | `notIn`
|
|
764
|
-
| `Contains` | `contains`
|
|
765
|
-
| `NotContains` | `not.contains`
|
|
766
|
-
| `StartsWith` | `startsWith`
|
|
767
|
-
| `NotStartsWith` | `not.startsWith`
|
|
768
|
-
| `EndsWith` | `endsWith`
|
|
769
|
-
| `NotEndsWith` | `not.endsWith`
|
|
770
|
-
| `GreaterThan` | `gt`
|
|
771
|
-
| `GreaterThanEqual` | `gte`
|
|
772
|
-
| `LessThan` | `lt`
|
|
773
|
-
| `LessThanEqual` | `lte`
|
|
774
|
-
| `Between` | `gte` + `lte`
|
|
775
|
-
| `NotBetween` | `not.gte` + `not.lte`
|
|
776
|
-
| `IsNull` | `null`
|
|
777
|
-
| `IsNotNull` | `not: null`
|
|
778
|
-
| `IsTrue` | `true`
|
|
779
|
-
| `IsFalse` | `false`
|
|
780
|
-
| `Insensitive` | `mode: 'insensitive'`
|
|
792
|
+
| *(no suffix)* | equality (`=`) | yes |
|
|
793
|
+
| `Not` | `not` | yes |
|
|
794
|
+
| `In` | `in` | yes (array) |
|
|
795
|
+
| `NotIn` | `notIn` | yes (array) |
|
|
796
|
+
| `Contains` | `contains` | yes |
|
|
797
|
+
| `NotContains` | `not.contains` | yes |
|
|
798
|
+
| `StartsWith` | `startsWith` | yes |
|
|
799
|
+
| `NotStartsWith` | `not.startsWith` | yes |
|
|
800
|
+
| `EndsWith` | `endsWith` | yes |
|
|
801
|
+
| `NotEndsWith` | `not.endsWith` | yes |
|
|
802
|
+
| `GreaterThan` | `gt` | yes |
|
|
803
|
+
| `GreaterThanEqual` | `gte` | yes |
|
|
804
|
+
| `LessThan` | `lt` | yes |
|
|
805
|
+
| `LessThanEqual` | `lte` | yes |
|
|
806
|
+
| `Between` | `gte` + `lte` | yes (tuple `[min, max]`) |
|
|
807
|
+
| `NotBetween` | `not.gte` + `not.lte` | yes (tuple `[min, max]`) |
|
|
808
|
+
| `IsNull` | `null` | no |
|
|
809
|
+
| `IsNotNull` | `not: null` | no |
|
|
810
|
+
| `IsTrue` | `true` | no |
|
|
811
|
+
| `IsFalse` | `false` | no |
|
|
812
|
+
| `Insensitive` | `mode: 'insensitive'` | combinator |
|
|
781
813
|
|
|
782
814
|
`Insensitive` is a combinator and can be used together with another text filter:
|
|
783
815
|
|
|
@@ -811,11 +843,11 @@ findByNameOptionalAndEmail // name is optional, email is required
|
|
|
811
843
|
|
|
812
844
|
### Logical operators
|
|
813
845
|
|
|
814
|
-
| Operator | Usage in the name
|
|
815
|
-
| --------- | ------------------------------ |
|
|
816
|
-
| `And` | between two fields
|
|
817
|
-
| `Or` | between two fields
|
|
818
|
-
| `AND` | separates a final `AND` block
|
|
846
|
+
| Operator | Usage in the name | Example |
|
|
847
|
+
| --------- | ------------------------------ | ---------------------------------- |
|
|
848
|
+
| `And` | between two fields | `findOneByIdAndEmail` |
|
|
849
|
+
| `Or` | between two fields | `findByNameOrEmail` |
|
|
850
|
+
| `AND` | separates a final `AND` block | `findByEmailOrNameANDActiveStatus` |
|
|
819
851
|
|
|
820
852
|
`AND` (in caps) has a specific rule:
|
|
821
853
|
|
|
@@ -895,22 +927,23 @@ Generates (`findByEmailOrNameANDActiveStatusAndAgeGreaterThan`):
|
|
|
895
927
|
Allow filtering by fields of related models.
|
|
896
928
|
|
|
897
929
|
> [!IMPORTANT]
|
|
930
|
+
>
|
|
898
931
|
> - **Relation typing**: For TypeScript to recognize the types of relation fields in dynamic methods, the generic entity type passed to `setupVSRepo` must include the structured relations (e.g. using Prisma's `UserGetPayload<{ include: { profile: true, posts: true } }>`).
|
|
899
932
|
> - **Suffix compatibility**:
|
|
900
933
|
> - The `Some`, `Every`, and `None` suffixes only work for **to-many** relations (`many-to-many` and `one-to-many`).
|
|
901
934
|
> - The `With` and `Without` suffixes only work for **to-one** relations (`one-to-one` and `many-to-one`).
|
|
902
935
|
|
|
903
|
-
| Relation suffix
|
|
904
|
-
|
|
|
905
|
-
| `Some`
|
|
906
|
-
| `SomeField`
|
|
907
|
-
| `EveryField`
|
|
908
|
-
| `None`
|
|
909
|
-
| `NoneField`
|
|
910
|
-
| `With`
|
|
911
|
-
| `WithField`
|
|
912
|
-
| `Without`
|
|
913
|
-
| `WithoutField`
|
|
936
|
+
| Relation suffix | Prisma operator | Note |
|
|
937
|
+
| --------------------- | ----------------- | ----------------------------------------------------- |
|
|
938
|
+
| `Some` | `some: {}` | Relation has *some* record |
|
|
939
|
+
| `SomeField` | `some.field` | Filters within the relation's records |
|
|
940
|
+
| `EveryField` | `every.field` | Filters within the relation's records |
|
|
941
|
+
| `None` | `none: {}` | Relation has *no* records |
|
|
942
|
+
| `NoneField` | `none.field` | Filters within the relation's records |
|
|
943
|
+
| `With` | `is: {}` | Relation exists (not null) |
|
|
944
|
+
| `WithField` | `is.field` | Filters a field within the relation |
|
|
945
|
+
| `Without` | `isNot: {}` | Relation doesn't exist (is null) |
|
|
946
|
+
| `WithoutField` | `isNot.field` | Filters a field within the relation with negation |
|
|
914
947
|
|
|
915
948
|
Considering `user` with a to-one relation `profile` and a to-many relation `posts`:
|
|
916
949
|
|
|
@@ -990,18 +1023,18 @@ Generates (`findByProfileWithout`):
|
|
|
990
1023
|
|
|
991
1024
|
Applied at the **end** of the method name, they automatically inject the pagination and ordering arguments.
|
|
992
1025
|
|
|
993
|
-
| Suffix
|
|
1026
|
+
| Suffix | Additional arguments |
|
|
994
1027
|
| ------------------------ | -------------------------------|
|
|
995
|
-
| `Paginated`
|
|
996
|
-
| `Ordered`
|
|
997
|
-
| `OrderedAndPaginated`
|
|
998
|
-
| `PaginatedAndOrdered`
|
|
1028
|
+
| `Paginated` | `(pagination)` |
|
|
1029
|
+
| `Ordered` | `(order)` |
|
|
1030
|
+
| `OrderedAndPaginated` | `(order, pagination)` |
|
|
1031
|
+
| `PaginatedAndOrdered` | `(pagination, order)` |
|
|
999
1032
|
|
|
1000
1033
|
For `createMany` and `createManyAndReturn`, the `SkipDuplicates` suffix is available:
|
|
1001
1034
|
|
|
1002
1035
|
| Suffix | Effect |
|
|
1003
|
-
|
|
|
1004
|
-
| `SkipDuplicates`
|
|
1036
|
+
| ------------------- | ------------------------------------------ |
|
|
1037
|
+
| `SkipDuplicates` | Skips duplicate records during insertion |
|
|
1005
1038
|
|
|
1006
1039
|
---
|
|
1007
1040
|
|
|
@@ -1045,17 +1078,17 @@ await userRepository.findManyByNameDistinctRole("John");
|
|
|
1045
1078
|
|
|
1046
1079
|
Each entry in `methods` accepts the following options:
|
|
1047
1080
|
|
|
1048
|
-
| Option
|
|
1049
|
-
| --------------------- |
|
|
1050
|
-
| `map` | `boolean`
|
|
1051
|
-
| `whereType` | `'extending'` \| `'overwrite'`
|
|
1052
|
-
| `selectModel` | `keyof SelectModels \| false`
|
|
1053
|
-
| `fbMode` | `'one'` \| `'list'`
|
|
1054
|
-
| `proxyTo` | `Valid method pattern`
|
|
1055
|
-
| `pushWhere` | `WhereModel<M>`
|
|
1056
|
-
| `
|
|
1057
|
-
| `injectPagination` | `PaginationModel<M>`
|
|
1058
|
-
| `query` | `{ value: string; modifying?: boolean }` | —
|
|
1081
|
+
| Option | Type | Default | Description |
|
|
1082
|
+
| --------------------- | ---------------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
1083
|
+
| `map` | `boolean` | — | **Required.** Defines whether the method will be exposed on the repository. |
|
|
1084
|
+
| `whereType` | `'extending'` \| `'overwrite'` | `extending` | `extending` combines with `requiredWhere`. `overwrite` ignores `requiredWhere`. |
|
|
1085
|
+
| `selectModel` | `keyof SelectModels \| false` | — | Overrides `defaultSelectModel` for this method. |
|
|
1086
|
+
| `fbMode` | `'one'` \| `'list'` | `'list'` | (**Deprecated. Use `findOneBy`**) Only for `findBy`. `'one'` returns `T \| null`; `'list'` returns `T[]`. |
|
|
1087
|
+
| `proxyTo` | `Valid method pattern` | — | Delegates the logic to another valid method pattern. |
|
|
1088
|
+
| `pushWhere` | `WhereModel<M>` | — | Extra `where` added to the query in addition to `requiredWhere`. |
|
|
1089
|
+
| `injectOrdering` | `OrderingModel<M>` | — | Fixed ordering automatically injected into the query. |
|
|
1090
|
+
| `injectPagination` | `PaginationModel<M>` | — | Fixed pagination automatically injected into the query. |
|
|
1091
|
+
| `query` | `{ value: string; modifying?: boolean }` | — | Turns the method into a **Query Method** (raw SQL). Ignores every other option above — see [Query Methods](#query-methods). |
|
|
1059
1092
|
|
|
1060
1093
|
---
|
|
1061
1094
|
|
|
@@ -1137,13 +1170,13 @@ await userRepository.prisma.$transaction(async (tx) => {
|
|
|
1137
1170
|
});
|
|
1138
1171
|
```
|
|
1139
1172
|
|
|
1140
|
-
| Option | Type | Default | Description
|
|
1141
|
-
| ------------- | --------- | ------- |
|
|
1142
|
-
| `value` | `string` | — | **Required.** Raw SQL to execute. Use `$1`, `$2`, ... for the placeholders of the values in `args`.
|
|
1173
|
+
| Option | Type | Default | Description |
|
|
1174
|
+
| ------------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1175
|
+
| `value` | `string` | — | **Required.** Raw SQL to execute. Use `$1`, `$2`, ... for the placeholders of the values in `args`. |
|
|
1143
1176
|
| `modifying` | `boolean` | `false` | When `true`, executes via `$executeRawUnsafe` and the method always resolves to `number`. When `false`, executes via `$queryRawUnsafe` and the method resolves to `TReturn` (`any` by default, inferable via a generic at the call site). |
|
|
1144
1177
|
|
|
1145
1178
|
> [!NOTE]
|
|
1146
|
-
> Unlike the other dynamic methods, Query Methods **completely ignore** `selectModels`, `requiredWhere`, `pushWhere`, `whereType`, `
|
|
1179
|
+
> Unlike the other dynamic methods, Query Methods **completely ignore** `selectModels`, `requiredWhere`, `pushWhere`, `whereType`, `injectOrdering`, `injectPagination`, and `proxyTo` — none of that applies, since there's no name parsing or `where`/`select` assembly by VSRepository. Free-form method names (outside the `findBy`, `updateBy`, etc. patterns) also **don't** require `proxyTo`.
|
|
1147
1180
|
|
|
1148
1181
|
The same functionality is available in the class-based approach via the `@QueryMethod` decorator — see [README-DynamicRepo.md](./README-DynamicRepo.md#the-querymethod-decorator).
|
|
1149
1182
|
|
|
@@ -1181,7 +1214,7 @@ const userRepository = setupVSRepo<User, "user">()(({
|
|
|
1181
1214
|
|
|
1182
1215
|
**Relation modes:**
|
|
1183
1216
|
|
|
1184
|
-
| Mode | Relation
|
|
1217
|
+
| Mode | Relation |
|
|
1185
1218
|
| ----- | -------------- |
|
|
1186
1219
|
| `oto` | one-to-one |
|
|
1187
1220
|
| `otm` | one-to-many |
|
|
@@ -1190,22 +1223,22 @@ const userRepository = setupVSRepo<User, "user">()(({
|
|
|
1190
1223
|
|
|
1191
1224
|
**Restrictions:**
|
|
1192
1225
|
|
|
1193
|
-
| Restriction
|
|
1194
|
-
| ------------ |
|
|
1195
|
-
| `set`
|
|
1196
|
-
| `add`
|
|
1226
|
+
| Restriction | Behavior on update |
|
|
1227
|
+
| ------------ | ------------------------------------------------------ |
|
|
1228
|
+
| `set` | Fully replaces (removes the ones that weren't sent) |
|
|
1229
|
+
| `add` | Adds/updates without removing existing ones |
|
|
1197
1230
|
|
|
1198
1231
|
> [!WARNING]
|
|
1199
1232
|
> **`set` means different things depending on the relation's `mode` — and this can cause data loss if you're not careful.**
|
|
1200
1233
|
>
|
|
1201
1234
|
> In relations where the related record **belongs** to the parent record (`oto` and `otm`), "removing the ones that weren't sent" means **deleting the record from the database** (`delete`/`deleteMany`). In relations where the related record is **independent** (`mto` and `mtm`), "removing" just means **unlinking** (`disconnect`/`set: []`) — the related record continues to exist in the database, it just stops pointing to the parent (or being in the join table).
|
|
1202
1235
|
>
|
|
1203
|
-
> | Mode
|
|
1204
|
-
> | ----- | ------------------------------------------------ |
|
|
1236
|
+
> | Mode | `restriction: "set"` when an item is omitted | Does the item continue to exist in the database? |
|
|
1237
|
+
> | ----- | ------------------------------------------------ | ----------------------------------------------------- |
|
|
1205
1238
|
> | `oto` | Passing `null` in the field → **deletes** the related record (`delete: true`) | No |
|
|
1206
|
-
> | `otm` | Items outside the sent list → **deleted** (`deleteMany` with `notIn`)
|
|
1239
|
+
> | `otm` | Items outside the sent list → **deleted** (`deleteMany` with `notIn`) | No |
|
|
1207
1240
|
> | `mto` | Passing `null` in the field (with `nullable: true`) → **unlinks** (`disconnect: true`) | Yes |
|
|
1208
|
-
> | `mtm` | Items outside the sent list → **unlinked** from the join table (`set: []`)
|
|
1241
|
+
> | `mtm` | Items outside the sent list → **unlinked** from the join table (`set: []`) | Yes |
|
|
1209
1242
|
>
|
|
1210
1243
|
> Practical example: if `posts` is `otm` with `restriction: "set"`, a `save`/`patch` that sends the user with only 2 of the 5 existing posts will **delete the other 3 posts from the database**, not just unlink them from the user. If the expected behavior is just to unlink without deleting, use `restriction: "add"` (which never removes anything) and handle removal manually.
|
|
1211
1244
|
|
|
@@ -1306,14 +1339,23 @@ try {
|
|
|
1306
1339
|
|
|
1307
1340
|
**Available subclasses:**
|
|
1308
1341
|
|
|
1309
|
-
| Class
|
|
1310
|
-
|
|
|
1342
|
+
| Class | When it's thrown |
|
|
1343
|
+
| ----------------------- | ------------------------------------------------------------------------ |
|
|
1311
1344
|
| `VSRepoConfigError` | Invalid configuration in `setupVSRepo` |
|
|
1312
1345
|
| `VSRepoBuildError` | Invalid method name, field type, or configuration in `build` |
|
|
1313
1346
|
| `VSRepoExtendError` | Invalid argument in `extend` |
|
|
1347
|
+
| `VSRepoDecoratorError` | Invalid argument passed to `@DynamicMethod` or `@QueryMethod` |
|
|
1314
1348
|
| `VSRepoRuntimeError` | Runtime error during an operation |
|
|
1315
1349
|
|
|
1316
|
-
`VSRepoRuntimeError` has a `code` property for programmatic identification
|
|
1350
|
+
`VSRepoRuntimeError` has a `code: VSRepoRuntimeErrorCode` property for programmatic identification, instead of having to parse the (human-readable, and possibly localized) message:
|
|
1351
|
+
|
|
1352
|
+
| Code | Meaning |
|
|
1353
|
+
| ---- | ------- |
|
|
1354
|
+
| `"65706"` | A required argument is missing or has an invalid shape — e.g. a missing `pk`, a `pks`/`objs`/`tuples` that isn't an array, or an `options`/`obj` that isn't a valid object. |
|
|
1355
|
+
| `"20727"` | No record was found for the provided primary key (`getOrThrow` when fetching the base record). |
|
|
1356
|
+
| `"67542"` | Validation (zod) of a method's `options`, or of a `@QueryMethod` argument, failed. |
|
|
1357
|
+
| `"91868"` | A relation passed to `save`/`patch`/`merge` has an invalid shape for the configured `mode`/`restriction` (e.g. `null` on a `mtm`/`otm` relation, or an array on a `oto`/`mto` relation). |
|
|
1358
|
+
| `"48670"` | A dynamic method (`config.methods`) was called with fewer positional arguments than its `where` fields require. |
|
|
1317
1359
|
|
|
1318
1360
|
---
|
|
1319
1361
|
|
|
@@ -1346,7 +1388,7 @@ import type {
|
|
|
1346
1388
|
IncludeModel,
|
|
1347
1389
|
IncludeModels,
|
|
1348
1390
|
WhereModel,
|
|
1349
|
-
|
|
1391
|
+
OrderingModel,
|
|
1350
1392
|
PaginationModel,
|
|
1351
1393
|
ModelUpsertInput,
|
|
1352
1394
|
PrismaModelInputs,
|
|
@@ -1367,6 +1409,8 @@ type OptsModel = MethodOptionsModel<typeof userVSRepo>;
|
|
|
1367
1409
|
```
|
|
1368
1410
|
|
|
1369
1411
|
> The second parameter of `MethodOptions` (`IM`) represents the valid keys of `includeModels`. When provided, `selectModel` and `includeModel` become mutually exclusive in the type — it's not possible to pass both in the same call.
|
|
1412
|
+
>
|
|
1413
|
+
> `MethodOptions` also accepts two further generic parameters, `RI` and `RS`, for typing raw `include` and raw `select` respectively (both default to `never`, meaning they're not accepted unless explicitly typed): `MethodOptions<"public", "withPosts", "user", IncludeModel<"user">, SelectModel<"user">>`. `MethodOptionsModel`, derived directly from a configured repository, does not expose `RI`/`RS` — use `MethodOptions` directly if you need to type raw `include`/`select` options.
|
|
1370
1414
|
|
|
1371
1415
|
### Configuration types
|
|
1372
1416
|
|
|
@@ -1427,7 +1471,7 @@ setupVSRepo<TPayload, TTableName>()({
|
|
|
1427
1471
|
defaultSelectModel?: keyof SM; // Select applied by default
|
|
1428
1472
|
includeModels?: IncludeModels<M>; // Named data projections (include) — no default, only in the call
|
|
1429
1473
|
requiredWhere?: WhereModel<M>; // Always-applied filters
|
|
1430
|
-
|
|
1474
|
+
defaultOrdering?: OrderingModel<M>; // Default ordering for queries without Ordered/injectOrdering
|
|
1431
1475
|
relations?: RepositoryRelations<T>; // Relation configuration
|
|
1432
1476
|
methods?: Record<string, MethodConfig<M, SM>>; // Dynamic methods
|
|
1433
1477
|
});
|
|
@@ -1478,7 +1522,7 @@ repo.extend((repo) => ({
|
|
|
1478
1522
|
|
|
1479
1523
|
Besides this README, the repository has an **[`examples/`](https://github.com/jaobrabo123/VSRepository/tree/main/examples)** folder with practical, commented, ready-to-run examples — it's the best place to see VSRepository being used in real scenarios.
|
|
1480
1524
|
|
|
1481
|
-
```
|
|
1525
|
+
```text
|
|
1482
1526
|
examples/
|
|
1483
1527
|
├── prisma.ts # PrismaClient instance used by the examples
|
|
1484
1528
|
├── repositories.ts # Repository configuration (User, Address, Product) with setupVSRepo
|
|
@@ -1549,7 +1593,7 @@ Recommended `tsconfig.json`:
|
|
|
1549
1593
|
|
|
1550
1594
|
**`softRemovekName` throws an error at build** — The provided field must be of type `DateTime` in the Prisma schema. Types like `Boolean` or `String` are not accepted.
|
|
1551
1595
|
|
|
1552
|
-
**`
|
|
1596
|
+
**`defaultOrdering` isn't being applied** — Check whether the method uses the `Ordered`, `OrderedAndPaginated`, or `PaginatedAndOrdered` suffix, and whether it has `injectOrdering` configured. Both take priority over the default ordering.
|
|
1553
1597
|
|
|
1554
1598
|
**`Distinct` suffix not recognized** — `Distinct` is only resolved on read prefixes (`findMany`, `findFirst`, `findBy`, `existsBy`, etc). In methods like `count`, `createMany`, `updateMany`, or `deleteMany` the suffix is ignored.
|
|
1555
1599
|
|