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.
Files changed (29) hide show
  1. package/README.md +181 -137
  2. package/README.pt-BR.md +188 -144
  3. package/dist/DynamicRepository.d.ts +36 -15
  4. package/dist/VSRepoError.d.ts +21 -1
  5. package/dist/VSRepository.d.ts +126 -87
  6. package/dist/VSRepository.js +49 -49
  7. package/dist/internal/constants/dynamic-methods-key.constant.js +4 -0
  8. package/dist/internal/decorators/dynamic-method.decorator.js +4 -4
  9. package/dist/internal/decorators/query-method.decorator.js +7 -3
  10. package/dist/internal/entities/dynamic-method-metadata.entity.js +3 -3
  11. package/dist/internal/resolvers/base-methods.resolve.js +2 -2
  12. package/dist/internal/resolvers/dbAndPrismaArgs.resolve.js +7 -4
  13. package/dist/internal/resolvers/dynamic-method-customization.resolve.js +57 -0
  14. package/dist/internal/resolvers/dynamic-method-info.resolve.js +279 -0
  15. package/dist/internal/resolvers/dynamic-methods-metadata.resolve.js +2 -2
  16. package/dist/internal/resolvers/pretty-wheres.resolve.js +8 -8
  17. package/dist/internal/resolvers/types/dynamic-method-where-ops.type.js +2 -0
  18. package/dist/internal/utils/schemas.util.js +8 -2
  19. package/dist/internal/validation/constructor-config.validate.js +7 -4
  20. package/dist/internal/validation/method-options.validate.js +10 -3
  21. package/package.json +11 -2
  22. package/scripts/configure-prisma-import.mjs +12 -8
  23. package/scripts/copy-types.mjs +16 -16
  24. package/dist/internal/constants/dinamic-methods-key.constant.js +0 -4
  25. package/dist/internal/resolvers/dinamic-method-customization.resolve.js +0 -57
  26. package/dist/internal/resolvers/dinamic-method-info.resolve.js +0 -279
  27. /package/dist/internal/{resolvers/types/dinamic-method-customization.type.js → errors/types/vs-repo-runtime-error-code.type.js} +0 -0
  28. /package/dist/internal/resolvers/types/{dinamic-method-info.type.js → dynamic-method-customization.type.js} +0 -0
  29. /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 Ordenation](#default-ordenation)
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 Ordenation
664
+ ## Default Ordering
633
665
 
634
- `defaultOrdenation` defines a default ordering that's automatically applied to every query that accepts `orderBy`, without needing to repeat the `order` argument on every call.
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
- defaultOrdenation: { createdAt: "desc" },
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
- **`defaultOrdenation` is ignored when:**
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 `injectOrdenation` configured — the method's fixed ordering takes precedence.
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 → defaultOrdenation ignored
662
- findManyByActive: { map: true }, // no Ordered → defaultOrdenation applied
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
- injectOrdenation: { name: "asc" }, // injectOrdenation → defaultOrdenation ignored
697
+ injectOrdering: { name: "asc" }, // injectOrdering → defaultOrdering ignored
666
698
  },
667
699
  }
668
700
  ```
669
701
 
670
- > `defaultOrdenation` accepts the same type as Prisma's native `orderBy` for the model — including arrays of chained orderings.
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 | Prisma operation | Return | Notes |
719
- | ---------------------------- | -------------------------- | ------------------------ | ---------------------------------------------------------------------------|
720
- | `findOneBy` | `findFirst` | `T \| null` | Single return. |
721
- | `findBy` | `findMany` / `findFirst` | `T[]` or `T \| null` | Default is list; use `fbMode: "one"` for a single return (**deprecated**, use `findOneBy`) |
722
- | `findUniqueBy` | `findUnique` | `T \| null` | |
723
- | `findUniqueOrThrowBy` | `findUniqueOrThrow` | `T` | Throws an error if not found |
724
- | `findFirstBy` | `findFirst` | `T \| null` | Accepts fields as filter |
725
- | `findFirstOrThrowBy` | `findFirstOrThrow` | `T` | Accepts fields as filter; throws an error if not found |
726
- | `findFirst` | `findFirst` | `T \| null` | No field filters; applies only `requiredWhere` and `pushWhere` |
727
- | `findFirstOrThrow` | `findFirstOrThrow` | `T` | No field filters; applies only `requiredWhere` and `pushWhere`; throws an error if not found |
728
- | `findManyBy` | `findMany` | `T[]` | Accepts fields as filter |
729
- | `findMany` | `findMany` | `T[]` | No field filters; applies only `requiredWhere` and `pushWhere` |
730
- | `findOneWhere` | `findFirst` | `T \| null` | Receives an explicit `where` object as argument |
731
- | `findListWhere` | `findMany` | `T[]` | Receives an explicit `where` object as argument |
732
- | `existsBy` | `findFirst` | `boolean` | Returns `true` if found, `false` otherwise |
733
- | `existsWhere` | `findFirst` | `boolean` | Receives an explicit `where` object and returns whether it exists |
734
- | `countBy` | `count` | `number` | Accepts fields as filter |
735
- | `countWhere` | `count` | `number` | Receives an explicit `where` object as argument |
736
- | `count` | `count` | `number` | No field filters; applies only `requiredWhere` and `pushWhere` |
737
- | `create` | `create` | `T` | Receives `data` as argument |
738
- | `createMany` | `createMany` | `{ count: number }` | Receives `data` as argument; supports `SkipDuplicates` |
739
- | `createManyAndReturn` | `createManyAndReturn` | `T[]` | Receives `data` as argument; supports `SkipDuplicates` |
740
- | `updateBy` | `update` | `T` | Receives `data` as argument |
741
- | `updateManyBy` | `updateMany` | `{ count: number }` | Receives `data` as argument |
742
- | `updateManyWhere` | `updateMany` | `{ count: number }` | Receives a `where` object and a `data` object as arguments |
743
- | `updateManyAndReturnBy` | `updateManyAndReturn` | `T[]` | Receives `data` as argument |
744
- | `updateManyAndReturnWhere` | `updateManyAndReturn` | `T[]` | Receives a `where` object and a `data` object as arguments |
745
- | `upsertBy` | `upsert` | `T` | Receives `update` and `create` as arguments |
746
- | `deleteBy` | `delete` | `T` | |
747
- | `deleteManyBy` | `deleteMany` | `{ count: number }` | |
748
- | `deleteManyWhere` | `deleteMany` | `{ count: number }` | Receives an explicit `where` object as argument |
749
- | `aggregate` | `aggregate` | `Dynamic` | Name must be exact; receives native Prisma args; ignores `selectModels`, `pushWhere`, and `requiredWhere` |
750
- | `groupBy` | `groupBy` | `Dynamic[]` | Name must be exact; receives native Prisma args; ignores `selectModels`, `pushWhere`, and `requiredWhere` |
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 | Prisma operator | Argument required |
790
+ | Suffix | Prisma operator | Argument required |
759
791
  | -------------------- | ---------------------- | ---------------------------|
760
- | *(no suffix)* | equality (`=`) | yes |
761
- | `Not` | `not` | yes |
762
- | `In` | `in` | yes (array) |
763
- | `NotIn` | `notIn` | yes (array) |
764
- | `Contains` | `contains` | yes |
765
- | `NotContains` | `not.contains` | yes |
766
- | `StartsWith` | `startsWith` | yes |
767
- | `NotStartsWith` | `not.startsWith` | yes |
768
- | `EndsWith` | `endsWith` | yes |
769
- | `NotEndsWith` | `not.endsWith` | yes |
770
- | `GreaterThan` | `gt` | yes |
771
- | `GreaterThanEqual` | `gte` | yes |
772
- | `LessThan` | `lt` | yes |
773
- | `LessThanEqual` | `lte` | yes |
774
- | `Between` | `gte` + `lte` | yes (tuple `[min, max]`) |
775
- | `NotBetween` | `not.gte` + `not.lte` | yes (tuple `[min, max]`) |
776
- | `IsNull` | `null` | no |
777
- | `IsNotNull` | `not: null` | no |
778
- | `IsTrue` | `true` | no |
779
- | `IsFalse` | `false` | no |
780
- | `Insensitive` | `mode: 'insensitive'` | combinator |
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 | Example |
815
- | --------- | ------------------------------ | -----------------------------------|
816
- | `And` | between two fields | `findOneByIdAndEmail` |
817
- | `Or` | between two fields | `findByNameOrEmail` |
818
- | `AND` | separates a final `AND` block | `findByEmailOrNameANDActiveStatus` |
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 | Prisma operator | Note |
904
- | ------------------------ | ----------------- | -------------------------------------------------------|
905
- | `Some` | `some: {}` | Relation has *some* record |
906
- | `SomeField` | `some.field` | Filters within the relation's records |
907
- | `EveryField` | `every.field` | Filters within the relation's records |
908
- | `None` | `none: {}` | Relation has *no* records |
909
- | `NoneField` | `none.field` | Filters within the relation's records |
910
- | `With` | `is: {}` | Relation exists (not null) |
911
- | `WithField` | `is.field` | Filters a field within the relation |
912
- | `Without` | `isNot: {}` | Relation doesn't exist (is null) |
913
- | `WithoutField` | `isNot.field` | Filters a field within the relation with negation |
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 | Additional arguments |
1026
+ | Suffix | Additional arguments |
994
1027
  | ------------------------ | -------------------------------|
995
- | `Paginated` | `(pagination)` |
996
- | `Ordered` | `(order)` |
997
- | `OrderedAndPaginated` | `(order, pagination)` |
998
- | `PaginatedAndOrdered` | `(pagination, order)` |
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` | Skips duplicate records during insertion |
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 | Type | Default | Description |
1049
- | --------------------- | --------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------|
1050
- | `map` | `boolean` | — | **Required.** Defines whether the method will be exposed on the repository. |
1051
- | `whereType` | `'extending'` \| `'overwrite'` | `extending` | `extending` combines with `requiredWhere`. `overwrite` ignores `requiredWhere`. |
1052
- | `selectModel` | `keyof SelectModels \| false` | — | Overrides `defaultSelectModel` for this method. |
1053
- | `fbMode` | `'one'` \| `'list'` | `'list'` | (**Deprecated. Use `findOneBy`**) Only for `findBy`. `'one'` returns `T \| null`; `'list'` returns `T[]`. |
1054
- | `proxyTo` | `Valid method pattern` | — | Delegates the logic to another valid method pattern. |
1055
- | `pushWhere` | `WhereModel<M>` | — | Extra `where` added to the query in addition to `requiredWhere`. |
1056
- | `injectOrdenation` | `OrdenationModel<M>` | — | Fixed ordering automatically injected into the query. |
1057
- | `injectPagination` | `PaginationModel<M>` | — | Fixed pagination automatically injected into the query. |
1058
- | `query` | `{ value: string; modifying?: boolean }` | — | Turns the method into a **Query Method** (raw SQL). Ignores every other option above — see [Query Methods](#query-methods). |
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`, `injectOrdenation`, `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`.
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 | Behavior on update |
1194
- | ------------ | ----------------------------------------------------------------|
1195
- | `set` | Fully replaces (removes the ones that weren't sent) |
1196
- | `add` | Adds/updates without removing existing ones |
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 | `restriction: "set"` when an item is omitted | Does the item continue to exist in the database? |
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`) | No |
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: []`) | Yes |
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 | When it's thrown |
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. Code `"20727"` is thrown by `getOrThrow` when the record is not found, for example.
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
- OrdenationModel,
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
- defaultOrdenation?: OrdenationModel<M>; // Default ordering for queries without Ordered/injectOrdenation
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
- **`defaultOrdenation` isn't being applied** — Check whether the method uses the `Ordered`, `OrderedAndPaginated`, or `PaginatedAndOrdered` suffix, and whether it has `injectOrdenation` configured. Both take priority over the default ordering.
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