vsrepo 1.3.5 → 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 (28) hide show
  1. package/README.md +147 -137
  2. package/README.pt-BR.md +154 -144
  3. package/dist/DynamicRepository.d.ts +24 -7
  4. package/dist/VSRepoError.d.ts +21 -1
  5. package/dist/VSRepository.d.ts +36 -12
  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 +3 -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 +4 -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/package.json +1 -1
  21. package/scripts/configure-prisma-import.mjs +2 -1
  22. package/scripts/copy-types.mjs +16 -16
  23. package/dist/internal/constants/dinamic-methods-key.constant.js +0 -4
  24. package/dist/internal/resolvers/dinamic-method-customization.resolve.js +0 -57
  25. package/dist/internal/resolvers/dinamic-method-info.resolve.js +0 -279
  26. /package/dist/internal/{resolvers/types/dinamic-method-customization.type.js → errors/types/vs-repo-runtime-error-code.type.js} +0 -0
  27. /package/dist/internal/resolvers/types/{dinamic-method-info.type.js → dynamic-method-customization.type.js} +0 -0
  28. /package/dist/internal/resolvers/types/{dinamic-method-where-ops.type.js → dynamic-method-info.type.js} +0 -0
package/README.md CHANGED
@@ -39,7 +39,7 @@ VSRepository lets you create strongly-typed repositories with:
39
39
  - [Include Models](#include-models)
40
40
  - [Raw include (options.include)](#raw-include-optionsinclude)
41
41
  - [Required Where](#required-where)
42
- - [Default Ordenation](#default-ordenation)
42
+ - [Default Ordering](#default-ordering)
43
43
  - [`see` option](#see-option)
44
44
  - [Dynamic methods](#dynamic-methods)
45
45
  - [Available prefixes](#available-prefixes)
@@ -96,14 +96,14 @@ npx vsrepo generate \
96
96
 
97
97
  **Available flags:**
98
98
 
99
- | Flag | Alias | Default |
99
+ | Flag | Alias | Default |
100
100
  | ---------- | ----- | -------------------- |
101
101
  | `--output` | `-o` | `generated/vsrepo` |
102
102
  | `--prisma` | `-p` | `generated/prisma` |
103
103
 
104
104
  **Generated files:**
105
105
 
106
- ```
106
+ ```text
107
107
  generated/vsrepo/
108
108
  ├── DynamicRepository.ts
109
109
  ├── DynamicRepository.types.d.ts
@@ -337,18 +337,18 @@ When calling `.build(prisma)`, the base methods below are automatically made ava
337
337
  | Method | Description |
338
338
  | ------------------------ | -------------------------------------------------------------------------------------------------------------|
339
339
  | `get(pk)` | Fetches a record by its primary key |
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 |
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 |
342
342
  | `save(obj)` | Creates or updates — if the object has a `pk` it performs an `upsert`, otherwise a `create` |
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
+ | `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 |
346
346
  | `merge(pk, obj)` | Fetches a record and deep merges it in memory — **does not persist**, returns the merged object |
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` |
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` |
352
352
 
353
353
  All of them accept `options` as the last argument.
354
354
 
@@ -357,11 +357,11 @@ All of them accept `options` as the last argument.
357
357
  When `softRemovekName` is configured on the repository, the following additional methods become available:
358
358
 
359
359
  | Method | Description |
360
- | -------------------------- | ------------------------------------------------------------------------------------|
360
+ | -------------------------- | ---------------------------------------------------------------------------------- |
361
361
  | `softRemove(pk)` | Marks a record as removed by filling `softRemovekName` with the current date |
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
+ | `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 }` |
365
365
 
366
366
  ```ts
367
367
  const userRepository = setupVSRepo<User, "user">()(({
@@ -661,15 +661,15 @@ Useful for manual soft-deletes, multi-tenancy, and global filters of any kind.
661
661
 
662
662
  ---
663
663
 
664
- ## Default Ordenation
664
+ ## Default Ordering
665
665
 
666
- `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.
667
667
 
668
668
  ```ts
669
669
  const userRepository = setupVSRepo<User, "user">()(({
670
670
  tableName: "user",
671
671
  pkName: "id",
672
- defaultOrdenation: { createdAt: "desc" },
672
+ defaultOrdering: { createdAt: "desc" },
673
673
  }).build(prisma);
674
674
  ```
675
675
 
@@ -683,23 +683,23 @@ const users = await userRepository.getAll();
683
683
  const paginated = await userRepository.getAll({ pagination: { take: 10 } });
684
684
  ```
685
685
 
686
- **`defaultOrdenation` is ignored when:**
686
+ **`defaultOrdering` is ignored when:**
687
687
 
688
688
  - The method uses the `Ordered`, `OrderedAndPaginated`, or `PaginatedAndOrdered` suffix — in these cases the `order` argument passed in the call takes priority.
689
- - 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.
690
690
 
691
691
  ```ts
692
692
  methods: {
693
- findManyPaginatedAndOrdered: { map: true }, // order comes from the argument → defaultOrdenation ignored
694
- 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
695
695
  findManyByStatus: {
696
696
  map: true,
697
- injectOrdenation: { name: "asc" }, // injectOrdenation → defaultOrdenation ignored
697
+ injectOrdering: { name: "asc" }, // injectOrdering → defaultOrdering ignored
698
698
  },
699
699
  }
700
700
  ```
701
701
 
702
- > `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.
703
703
 
704
704
  ---
705
705
 
@@ -707,9 +707,9 @@ methods: {
707
707
 
708
708
  When `softRemovekName` is configured, every method accepts the `see` option to control the visibility of soft-deleted records:
709
709
 
710
- | Value | Behavior |
710
+ | Value | Behavior |
711
711
  | ----------- | --------------------------------------------------------------|
712
- | `"active"` | Returns only records that are **not** removed (default) |
712
+ | `"active"` | Returns only records that are **not** removed (default) |
713
713
  | `"removed"` | Returns only removed records |
714
714
  | `"all"` | Returns all records, regardless of status |
715
715
 
@@ -747,39 +747,39 @@ methods: {
747
747
 
748
748
  The method name's prefix determines which Prisma operation will be called and which arguments are expected.
749
749
 
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` |
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` |
783
783
 
784
784
  ---
785
785
 
@@ -787,29 +787,29 @@ The method name's prefix determines which Prisma operation will be called and wh
787
787
 
788
788
  Filters are suffixes applied to the field name inside the method. The field itself comes capitalized right after the prefix (or after `By`).
789
789
 
790
- | Suffix | Prisma operator | Argument required |
790
+ | Suffix | Prisma operator | Argument required |
791
791
  | -------------------- | ---------------------- | ---------------------------|
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 |
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 |
813
813
 
814
814
  `Insensitive` is a combinator and can be used together with another text filter:
815
815
 
@@ -843,11 +843,11 @@ findByNameOptionalAndEmail // name is optional, email is required
843
843
 
844
844
  ### Logical operators
845
845
 
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` |
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` |
851
851
 
852
852
  `AND` (in caps) has a specific rule:
853
853
 
@@ -927,22 +927,23 @@ Generates (`findByEmailOrNameANDActiveStatusAndAgeGreaterThan`):
927
927
  Allow filtering by fields of related models.
928
928
 
929
929
  > [!IMPORTANT]
930
+ >
930
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 } }>`).
931
932
  > - **Suffix compatibility**:
932
933
  > - The `Some`, `Every`, and `None` suffixes only work for **to-many** relations (`many-to-many` and `one-to-many`).
933
934
  > - The `With` and `Without` suffixes only work for **to-one** relations (`one-to-one` and `many-to-one`).
934
935
 
935
- | Relation suffix | Prisma operator | Note |
936
- | ------------------------ | ----------------- | -------------------------------------------------------|
937
- | `Some` | `some: {}` | Relation has *some* record |
938
- | `SomeField` | `some.field` | Filters within the relation's records |
939
- | `EveryField` | `every.field` | Filters within the relation's records |
940
- | `None` | `none: {}` | Relation has *no* records |
941
- | `NoneField` | `none.field` | Filters within the relation's records |
942
- | `With` | `is: {}` | Relation exists (not null) |
943
- | `WithField` | `is.field` | Filters a field within the relation |
944
- | `Without` | `isNot: {}` | Relation doesn't exist (is null) |
945
- | `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 |
946
947
 
947
948
  Considering `user` with a to-one relation `profile` and a to-many relation `posts`:
948
949
 
@@ -1022,18 +1023,18 @@ Generates (`findByProfileWithout`):
1022
1023
 
1023
1024
  Applied at the **end** of the method name, they automatically inject the pagination and ordering arguments.
1024
1025
 
1025
- | Suffix | Additional arguments |
1026
+ | Suffix | Additional arguments |
1026
1027
  | ------------------------ | -------------------------------|
1027
- | `Paginated` | `(pagination)` |
1028
- | `Ordered` | `(order)` |
1029
- | `OrderedAndPaginated` | `(order, pagination)` |
1030
- | `PaginatedAndOrdered` | `(pagination, order)` |
1028
+ | `Paginated` | `(pagination)` |
1029
+ | `Ordered` | `(order)` |
1030
+ | `OrderedAndPaginated` | `(order, pagination)` |
1031
+ | `PaginatedAndOrdered` | `(pagination, order)` |
1031
1032
 
1032
1033
  For `createMany` and `createManyAndReturn`, the `SkipDuplicates` suffix is available:
1033
1034
 
1034
1035
  | Suffix | Effect |
1035
- | --------------------- | ---------------------------------------------|
1036
- | `SkipDuplicates` | Skips duplicate records during insertion |
1036
+ | ------------------- | ------------------------------------------ |
1037
+ | `SkipDuplicates` | Skips duplicate records during insertion |
1037
1038
 
1038
1039
  ---
1039
1040
 
@@ -1077,17 +1078,17 @@ await userRepository.findManyByNameDistinctRole("John");
1077
1078
 
1078
1079
  Each entry in `methods` accepts the following options:
1079
1080
 
1080
- | Option | Type | Default | Description |
1081
- | --------------------- | --------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------|
1082
- | `map` | `boolean` | — | **Required.** Defines whether the method will be exposed on the repository. |
1083
- | `whereType` | `'extending'` \| `'overwrite'` | `extending` | `extending` combines with `requiredWhere`. `overwrite` ignores `requiredWhere`. |
1084
- | `selectModel` | `keyof SelectModels \| false` | — | Overrides `defaultSelectModel` for this method. |
1085
- | `fbMode` | `'one'` \| `'list'` | `'list'` | (**Deprecated. Use `findOneBy`**) Only for `findBy`. `'one'` returns `T \| null`; `'list'` returns `T[]`. |
1086
- | `proxyTo` | `Valid method pattern` | — | Delegates the logic to another valid method pattern. |
1087
- | `pushWhere` | `WhereModel<M>` | — | Extra `where` added to the query in addition to `requiredWhere`. |
1088
- | `injectOrdenation` | `OrdenationModel<M>` | — | Fixed ordering automatically injected into the query. |
1089
- | `injectPagination` | `PaginationModel<M>` | — | Fixed pagination automatically injected into the query. |
1090
- | `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). |
1091
1092
 
1092
1093
  ---
1093
1094
 
@@ -1169,13 +1170,13 @@ await userRepository.prisma.$transaction(async (tx) => {
1169
1170
  });
1170
1171
  ```
1171
1172
 
1172
- | Option | Type | Default | Description |
1173
- | ------------- | --------- | ------- | -------------------------------------------------------------------------------------------------------------------------------|
1174
- | `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`. |
1175
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). |
1176
1177
 
1177
1178
  > [!NOTE]
1178
- > 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`.
1179
1180
 
1180
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).
1181
1182
 
@@ -1213,7 +1214,7 @@ const userRepository = setupVSRepo<User, "user">()(({
1213
1214
 
1214
1215
  **Relation modes:**
1215
1216
 
1216
- | Mode | Relation |
1217
+ | Mode | Relation |
1217
1218
  | ----- | -------------- |
1218
1219
  | `oto` | one-to-one |
1219
1220
  | `otm` | one-to-many |
@@ -1222,22 +1223,22 @@ const userRepository = setupVSRepo<User, "user">()(({
1222
1223
 
1223
1224
  **Restrictions:**
1224
1225
 
1225
- | Restriction | Behavior on update |
1226
- | ------------ | ----------------------------------------------------------------|
1227
- | `set` | Fully replaces (removes the ones that weren't sent) |
1228
- | `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 |
1229
1230
 
1230
1231
  > [!WARNING]
1231
1232
  > **`set` means different things depending on the relation's `mode` — and this can cause data loss if you're not careful.**
1232
1233
  >
1233
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).
1234
1235
  >
1235
- > | Mode | `restriction: "set"` when an item is omitted | Does the item continue to exist in the database? |
1236
- > | ----- | ------------------------------------------------ | -----------------------------------------------------|
1236
+ > | Mode | `restriction: "set"` when an item is omitted | Does the item continue to exist in the database? |
1237
+ > | ----- | ------------------------------------------------ | ----------------------------------------------------- |
1237
1238
  > | `oto` | Passing `null` in the field → **deletes** the related record (`delete: true`) | No |
1238
- > | `otm` | Items outside the sent list → **deleted** (`deleteMany` with `notIn`) | No |
1239
+ > | `otm` | Items outside the sent list → **deleted** (`deleteMany` with `notIn`) | No |
1239
1240
  > | `mto` | Passing `null` in the field (with `nullable: true`) → **unlinks** (`disconnect: true`) | Yes |
1240
- > | `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 |
1241
1242
  >
1242
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.
1243
1244
 
@@ -1338,14 +1339,23 @@ try {
1338
1339
 
1339
1340
  **Available subclasses:**
1340
1341
 
1341
- | Class | When it's thrown |
1342
- | ---------------------- | ------------------------------------------------------------------------|
1342
+ | Class | When it's thrown |
1343
+ | ----------------------- | ------------------------------------------------------------------------ |
1343
1344
  | `VSRepoConfigError` | Invalid configuration in `setupVSRepo` |
1344
1345
  | `VSRepoBuildError` | Invalid method name, field type, or configuration in `build` |
1345
1346
  | `VSRepoExtendError` | Invalid argument in `extend` |
1347
+ | `VSRepoDecoratorError` | Invalid argument passed to `@DynamicMethod` or `@QueryMethod` |
1346
1348
  | `VSRepoRuntimeError` | Runtime error during an operation |
1347
1349
 
1348
- `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. |
1349
1359
 
1350
1360
  ---
1351
1361
 
@@ -1378,7 +1388,7 @@ import type {
1378
1388
  IncludeModel,
1379
1389
  IncludeModels,
1380
1390
  WhereModel,
1381
- OrdenationModel,
1391
+ OrderingModel,
1382
1392
  PaginationModel,
1383
1393
  ModelUpsertInput,
1384
1394
  PrismaModelInputs,
@@ -1461,7 +1471,7 @@ setupVSRepo<TPayload, TTableName>()({
1461
1471
  defaultSelectModel?: keyof SM; // Select applied by default
1462
1472
  includeModels?: IncludeModels<M>; // Named data projections (include) — no default, only in the call
1463
1473
  requiredWhere?: WhereModel<M>; // Always-applied filters
1464
- defaultOrdenation?: OrdenationModel<M>; // Default ordering for queries without Ordered/injectOrdenation
1474
+ defaultOrdering?: OrderingModel<M>; // Default ordering for queries without Ordered/injectOrdering
1465
1475
  relations?: RepositoryRelations<T>; // Relation configuration
1466
1476
  methods?: Record<string, MethodConfig<M, SM>>; // Dynamic methods
1467
1477
  });
@@ -1512,7 +1522,7 @@ repo.extend((repo) => ({
1512
1522
 
1513
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.
1514
1524
 
1515
- ```
1525
+ ```text
1516
1526
  examples/
1517
1527
  ├── prisma.ts # PrismaClient instance used by the examples
1518
1528
  ├── repositories.ts # Repository configuration (User, Address, Product) with setupVSRepo
@@ -1583,7 +1593,7 @@ Recommended `tsconfig.json`:
1583
1593
 
1584
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.
1585
1595
 
1586
- **`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.
1587
1597
 
1588
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.
1589
1599