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.
- package/README.md +147 -137
- package/README.pt-BR.md +154 -144
- package/dist/DynamicRepository.d.ts +24 -7
- package/dist/VSRepoError.d.ts +21 -1
- package/dist/VSRepository.d.ts +36 -12
- 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 +3 -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 +4 -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/package.json +1 -1
- package/scripts/configure-prisma-import.mjs +2 -1
- 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
|
@@ -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
|
|
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
|
|
664
|
+
## Default Ordering
|
|
665
665
|
|
|
666
|
-
`
|
|
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
|
-
|
|
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
|
-
**`
|
|
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 `
|
|
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 →
|
|
694
|
-
findManyByActive: { map: true }, // no Ordered →
|
|
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
|
-
|
|
697
|
+
injectOrdering: { name: "asc" }, // injectOrdering → defaultOrdering ignored
|
|
698
698
|
},
|
|
699
699
|
}
|
|
700
700
|
```
|
|
701
701
|
|
|
702
|
-
> `
|
|
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
|
|
751
|
-
| ---------------------------- | -------------------------- | ------------------------ |
|
|
752
|
-
| `findOneBy`
|
|
753
|
-
| `findBy`
|
|
754
|
-
| `findUniqueBy` | `findUnique`
|
|
755
|
-
| `findUniqueOrThrowBy` | `findUniqueOrThrow`
|
|
756
|
-
| `findFirstBy` | `findFirst`
|
|
757
|
-
| `findFirstOrThrowBy` | `findFirstOrThrow`
|
|
758
|
-
| `findFirst` | `findFirst`
|
|
759
|
-
| `findFirstOrThrow` | `findFirstOrThrow`
|
|
760
|
-
| `findManyBy` | `findMany`
|
|
761
|
-
| `findMany` | `findMany`
|
|
762
|
-
| `findOneWhere`
|
|
763
|
-
| `findListWhere`
|
|
764
|
-
| `existsBy`
|
|
765
|
-
| `existsWhere`
|
|
766
|
-
| `countBy`
|
|
767
|
-
| `countWhere`
|
|
768
|
-
| `count`
|
|
769
|
-
| `create`
|
|
770
|
-
| `createMany`
|
|
771
|
-
| `createManyAndReturn`
|
|
772
|
-
| `updateBy`
|
|
773
|
-
| `updateManyBy`
|
|
774
|
-
| `updateManyWhere`
|
|
775
|
-
| `updateManyAndReturnBy`
|
|
776
|
-
| `updateManyAndReturnWhere`
|
|
777
|
-
| `upsertBy`
|
|
778
|
-
| `deleteBy`
|
|
779
|
-
| `deleteManyBy`
|
|
780
|
-
| `deleteManyWhere`
|
|
781
|
-
| `aggregate`
|
|
782
|
-
| `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` |
|
|
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
|
|
790
|
+
| Suffix | Prisma operator | Argument required |
|
|
791
791
|
| -------------------- | ---------------------- | ---------------------------|
|
|
792
|
-
| *(no suffix)* | equality (`=`)
|
|
793
|
-
| `Not` | `not`
|
|
794
|
-
| `In` | `in`
|
|
795
|
-
| `NotIn` | `notIn`
|
|
796
|
-
| `Contains` | `contains`
|
|
797
|
-
| `NotContains` | `not.contains`
|
|
798
|
-
| `StartsWith` | `startsWith`
|
|
799
|
-
| `NotStartsWith` | `not.startsWith`
|
|
800
|
-
| `EndsWith` | `endsWith`
|
|
801
|
-
| `NotEndsWith` | `not.endsWith`
|
|
802
|
-
| `GreaterThan` | `gt`
|
|
803
|
-
| `GreaterThanEqual` | `gte`
|
|
804
|
-
| `LessThan` | `lt`
|
|
805
|
-
| `LessThanEqual` | `lte`
|
|
806
|
-
| `Between` | `gte` + `lte`
|
|
807
|
-
| `NotBetween` | `not.gte` + `not.lte`
|
|
808
|
-
| `IsNull` | `null`
|
|
809
|
-
| `IsNotNull` | `not: null`
|
|
810
|
-
| `IsTrue` | `true`
|
|
811
|
-
| `IsFalse` | `false`
|
|
812
|
-
| `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 |
|
|
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
|
|
847
|
-
| --------- | ------------------------------ |
|
|
848
|
-
| `And` | between two fields
|
|
849
|
-
| `Or` | between two fields
|
|
850
|
-
| `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` |
|
|
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
|
|
936
|
-
|
|
|
937
|
-
| `Some`
|
|
938
|
-
| `SomeField`
|
|
939
|
-
| `EveryField`
|
|
940
|
-
| `None`
|
|
941
|
-
| `NoneField`
|
|
942
|
-
| `With`
|
|
943
|
-
| `WithField`
|
|
944
|
-
| `Without`
|
|
945
|
-
| `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 |
|
|
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
|
|
1026
|
+
| Suffix | Additional arguments |
|
|
1026
1027
|
| ------------------------ | -------------------------------|
|
|
1027
|
-
| `Paginated`
|
|
1028
|
-
| `Ordered`
|
|
1029
|
-
| `OrderedAndPaginated`
|
|
1030
|
-
| `PaginatedAndOrdered`
|
|
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`
|
|
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
|
|
1081
|
-
| --------------------- |
|
|
1082
|
-
| `map` | `boolean`
|
|
1083
|
-
| `whereType` | `'extending'` \| `'overwrite'`
|
|
1084
|
-
| `selectModel` | `keyof SelectModels \| false`
|
|
1085
|
-
| `fbMode` | `'one'` \| `'list'`
|
|
1086
|
-
| `proxyTo` | `Valid method pattern`
|
|
1087
|
-
| `pushWhere` | `WhereModel<M>`
|
|
1088
|
-
| `
|
|
1089
|
-
| `injectPagination` | `PaginationModel<M>`
|
|
1090
|
-
| `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). |
|
|
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`, `
|
|
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
|
|
1226
|
-
| ------------ |
|
|
1227
|
-
| `set`
|
|
1228
|
-
| `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 |
|
|
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
|
|
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`)
|
|
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: []`)
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
**`
|
|
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
|
|