vsrepo 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -41,6 +41,7 @@ VSRepository lets you create strongly-typed repositories with:
41
41
  - [Which fields are eligible](#which-fields-are-eligible)
42
42
  - [Writing an adapter](#writing-an-adapter)
43
43
  - [`select` and `relations`](#select-and-relations)
44
+ - [Strict return typing with `InferMethodReturn`](#strict-return-typing-with-infermethodreturn)
44
45
  - [Dynamic methods](#dynamic-methods)
45
46
  - [Available prefixes](#available-prefixes)
46
47
  - [Field filters](#field-filters)
@@ -48,6 +49,7 @@ VSRepository lets you create strongly-typed repositories with:
48
49
  - [Relation filters](#relation-filters)
49
50
  - [Ordering, pagination and distinct](#ordering-pagination-and-distinct)
50
51
  - [Decorator options](#decorator-options)
52
+ - [Strict return typing with `InferMethodType`](#strict-return-typing-with-infermethodtype)
51
53
  - [Query methods (raw SQL)](#query-methods-raw-sql)
52
54
  - [Spread arguments with `spreadArgs`](#spread-arguments-with-spreadargs)
53
55
  - [Ad-hoc raw queries with `query()`](#ad-hoc-raw-queries-with-query)
@@ -67,22 +69,22 @@ VSRepository lets you create strongly-typed repositories with:
67
69
 
68
70
  If you're coming from the [v1](https://github.com/jaobrabo123/VSRepository/tree/v1) code/docs, here's the short version. See each linked section for details.
69
71
 
70
- | Area | v1 | v2 |
71
- | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
72
- | Database access | Talks to **Prisma** directly, bundled in the core package | Talks to a **`VSRepoAdapter`**; ORM support ships as separate packages (`@vsrepo/prisma7-adapter`, `@vsrepo/drizzle-adapter`, ...) instead of being bundled in the core `vsrepo` package |
73
- | Defining a repository | Functional `setupVSRepo<T, M>()({...}).build(prisma)`, **or** a `DynamicRepository` class | A single **class-based** API: `extends VSRepository<Entity, PKType, OrmTypes>` |
74
- | Dynamic methods | `methods: { findByEmail: { map: true } }` config object | `@DynamicMethod()` decorator on a `declare` field |
75
- | Data projections | Named, reusable `selectModels` + `defaultSelectModel` | Ad-hoc `select`/`relations` passed per call (no named models) |
76
- | Eager loading | `include`/`includeModels` (Prisma-specific) | ORM-agnostic `relations` option |
77
- | Global filters | `requiredWhere` and `pushWhere` | **Removed**; Now it only accepts `softRemoveKey` + `see: "active" \| "removed" \| "all"` |
78
- | Case-insensitive filter suffix | `Insensitive` | `IgnoreCase` |
79
- | Inline ordering in method name | Not supported (`order` had to be passed as an argument via `Ordered`/`Paginated`) | `OrderBy<Field>Asc`/`OrderBy<Field>Desc` chains baked directly into the method name |
80
- | Duplicate handling on `createMany` | `SkipDuplicates` suffix | `IgnoreConflicts` suffix |
81
- | `aggregate` / `groupBy` | Supported (Prisma-native passthrough) | `groupBy` is **not planned** for v2. `aggregate` as a prefix is also unlikely: the most common operations are already covered by dedicated base methods (`sum`, `average`, `min`, `max`, `increment`, `decrement`, `multiply`, `divide`) — see [Atomic and aggregate methods](#atomic-and-aggregate-methods). For anything more complex, use `@QueryMethod`. |
82
- | Error types | `VSRepoError` + subclasses (`VSRepoConfigError`, `VSRepoBuildError`, `VSRepoExtendError`, `VSRepoRuntimeError`) | A base `VSRepoError` class with a `type: VSRepoErrorType` field (`DECORATOR`, `RESOLVER`, `DYNAMIC`, `VALIDATOR`, `BASE`, `ADAPTER`), plus a `VSRepoAdapterError` subclass carrying an `AdapterErrorCode` and the original ORM error |
83
- | Debug logging | `showWorking: true` boolean | `logLevel: VSLogLevel` (`DEBUG`/`INFO`/`WARN`/`ERROR`) + `logSlowThresholdMs` for slow-query warnings |
84
- | `vsrepo generate` CLI (type generation step) | Required before use | Not part of the v2 core — types come directly from your entity/ORM types |
85
- | CRUD extras | `patchList`, raw `options.select`/`options.include` | `select`/`relations` are the default (always "raw"); `patch`/`merge` keep the same semantics. **`patchList` was removed** — for a batch partial update, use a `updateManyBy`/`updateManyWhere` dynamic method instead |
72
+ | Area | v1 | v2 |
73
+ | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
74
+ | Database access | Talks to **Prisma** directly, bundled in the core package | Talks to a **`VSRepoAdapter`**; ORM support ships as separate packages (`@vsrepo/prisma7-adapter`, `@vsrepo/drizzle-adapter`, ...) instead of being bundled in the core `vsrepo` package |
75
+ | Defining a repository | Functional `setupVSRepo<T, M>()({...}).build(prisma)`, **or** a `DynamicRepository` class | A single **class-based** API: `extends VSRepository<Entity, PKType, OrmTypes>` |
76
+ | Dynamic methods | `methods: { findByEmail: { map: true } }` config object | `@DynamicMethod()` decorator on a `declare` field |
77
+ | Data projections | Named, reusable `selectModels` + `defaultSelectModel` | Ad-hoc `select`/`relations` passed per call (no named models) |
78
+ | Eager loading | `include`/`includeModels` (Prisma-specific) | ORM-agnostic `relations` option |
79
+ | Global filters | `requiredWhere` and `pushWhere` | **Removed**; Now it only accepts `softRemoveKey` + `see: "active" \| "removed" \| "all"` |
80
+ | Case-insensitive filter suffix | `Insensitive` | `IgnoreCase` |
81
+ | Inline ordering in method name | Not supported (`order` had to be passed as an argument via `Ordered`/`Paginated`) | `OrderBy<Field>Asc`/`OrderBy<Field>Desc` chains baked directly into the method name |
82
+ | Duplicate handling on `createMany` | `SkipDuplicates` suffix | `IgnoreConflicts` suffix |
83
+ | `aggregate` / `groupBy` | Supported (Prisma-native passthrough) | `groupBy` is **not planned** for v2. `aggregate` as a prefix is also unlikely: the most common operations are already covered by dedicated base methods (`sum`, `average`, `min`, `max`, `increment`, `decrement`, `multiply`, `divide`) — see [Atomic and aggregate methods](#atomic-and-aggregate-methods). For anything more complex, use `@QueryMethod`. |
84
+ | Error types | `VSRepoError` + subclasses (`VSRepoConfigError`, `VSRepoBuildError`, `VSRepoExtendError`, `VSRepoRuntimeError`) | A base `VSRepoError` class with a `type: VSRepoErrorType` field (`DECORATOR`, `RESOLVER`, `DYNAMIC`, `VALIDATOR`, `BASE`, `ADAPTER`), plus a `VSRepoAdapterError` subclass carrying an `AdapterErrorCode` and the original ORM error |
85
+ | Debug logging | `showWorking: true` boolean | `logLevel: VSLogLevel` (`DEBUG`/`INFO`/`WARN`/`ERROR`) + `logSlowThresholdMs` for slow-query warnings |
86
+ | `vsrepo generate` CLI (type generation step) | Required before use | Not part of the v2 core — types come directly from your entity/ORM types |
87
+ | CRUD extras | `patchList`, raw `options.select`/`options.include` | `select`/`relations` are the default (always "raw"); `patch`/`merge` keep the same semantics. **`patchList` was removed** — for a batch partial update, use a `updateManyBy`/`updateManyWhere` dynamic method instead |
86
88
 
87
89
  ---
88
90
 
@@ -99,10 +101,10 @@ The Prisma 7 adapter has now been published to npm as `@vsrepo/prisma7-adapter`.
99
101
 
100
102
  | Adapter | Status |
101
103
  | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
102
- | Prisma 7 (`@vsrepo/prisma7-adapter`) | 🟢 **Released** — published to npm, implements the `VSRepoAdapter` contract (CRUD, relations, transactions, `merge`, logging) with tests; see [`VSRepoPrisma7Adapter`](https://github.com/jaobrabo123/VSRepoPrisma7Adapter) for source and docs. **Note:** the atomic/aggregate methods (`incrementOne`, `decrementOne`, `multiplyOne`, `divideOne`, `sum`, `average`, `min`, `max` — see [Atomic and aggregate methods](#atomic-and-aggregate-methods)) were added to the `VSRepoAdapter` contract after this adapter's last release; confirm its changelog/version implements them before relying on `increment`/`sum`/etc. against Prisma 7. |
103
- | Drizzle (`@vsrepo/drizzle-adapter`) | 🔵 **Alpha** — an early release is available on npm; install it with `npm i @vsrepo/drizzle-adapter@alpha`. The API may still change before the stable release. Check the [`DrizzleAdapter`](https://github.com/jaobrabo123/VSRepoDrizzleAdapter) repository for the current status and known limitations, and feel free to contribute. |
104
+ | Prisma 7 (`@vsrepo/prisma7-adapter`) | 🟢 **Released** — published to npm, implements the `VSRepoAdapter` contract (CRUD, relations, transactions, `merge`, logging, etc.) with tests; see [`VSRepoPrisma7Adapter`](https://github.com/jaobrabo123/VSRepoPrisma7Adapter) for source and docs. |
105
+ | Drizzle (`@vsrepo/drizzle-adapter`) | 🔵 **Alpha** — an early release is available on npm; install it with `npm i @vsrepo/drizzle-adapter@alpha`. The API may still change before the stable release. Check the [`DrizzleAdapter`](https://github.com/jaobrabo123/VSRepoDrizzleAdapter) repository for the current status and known limitations, and feel free to contribute. |
104
106
  | Other ORMs (Prisma 8, TypeORM, etc.) | 🟡 **Planned, not published yet.** No official package exists yet — write your own adapter for now (see [Writing your own adapter](#writing-your-own-adapter)), and consider publishing/contributing it back. |
105
- | Custom adapters | 🟢 Fully supported today — implement the [`VSRepoAdapter`](#writing-your-own-adapter) abstract class yourself for any ORM/database you need, in your own project or package, following the same shape as `@vsrepo/*-adapter` is expected to have. |
107
+ | Custom adapters | 🟢 Fully supported today — implement the [`VSRepoAdapter`](#writing-your-own-adapter) abstract class yourself for any ORM/database you need, in your own project or package. |
106
108
 
107
109
  In short: the repository class, the `@DynamicMethod`/`@QueryMethod` decorators, the name-parsing engine, error handling and logging are all working end-to-end, and Prisma 7 support is now a released, published adapter. The Drizzle adapter is available in alpha. Official adapters for the remaining ORMs are on the roadmap and will ship as separate `@vsrepo/*-adapter` packages rather than as part of the core `vsrepo` package — but you don't have to wait for that: writing (and optionally publishing) your own adapter in the meantime is a fully supported way to use v2 today and to contribute back to the project.
108
110
 
@@ -116,8 +118,6 @@ v2 is installed as the core package plus one adapter package for your ORM, for e
116
118
  npm i vsrepo @vsrepo/prisma7-adapter
117
119
  ```
118
120
 
119
- > `vsrepo` v2.0.0 and `@vsrepo/prisma7-adapter` are both published to npm and ready to use. For any ORM other than Prisma 7, no adapter package exists yet — install the core and write your own adapter (see [Writing your own adapter](#writing-your-own-adapter)).
120
-
121
121
  ---
122
122
 
123
123
  ## Basic usage
@@ -167,14 +167,16 @@ class UserRepository extends VSRepository<User, string> {
167
167
  export default new UserRepository();
168
168
  ```
169
169
 
170
- > The core API (`VSRepository`, `VSRepoAdapter`, `DynamicMethod`, `QueryMethod`, `VSRepoError`, enums and types) is imported from the single `vsrepo` entry point. The concrete adapter comes from a **separate** package (`@vsrepo/*-adapter`). On Prisma 7, install the published [`@vsrepo/prisma7-adapter`](https://github.com/jaobrabo123/VSRepoPrisma7Adapter) (its constructor takes a config object — `tableName`, `pkName`, optional `relations`/`logLevel` — as shown above). Official adapters for other ORMs are planned but not published yet; until they are, you can implement the `VSRepoAdapter` contract yourself (see [Writing your own adapter](#writing-your-own-adapter)) — and publishing it to help the project is very welcome.
170
+ > The core API (`VSRepository`, `VSRepoAdapter`, `DynamicMethod`, `QueryMethod`, `VSRepoError`, enums and types) is imported from the single `vsrepo` entry point. The concrete adapter comes from a **separate** package (`@vsrepo/*-adapter`). On Prisma 7, install the [`@vsrepo/prisma7-adapter`](https://github.com/jaobrabo123/VSRepoPrisma7Adapter).
171
171
 
172
172
  > **The third generic parameter (`OrmTypes`):** `VSRepository<Entity, PKType, OrmTypes>` accepts an optional third type parameter describing your ORM's client/transaction types, via `VSRepoOrmTypes` (`{ dbClient; dbTransaction }`). Supplying it gives you a correctly-typed `getDbClient()`, `transaction()` callback, and `db` option on every method, instead of `any`:
173
173
  >
174
174
  > ```typescript
175
- > type PrismaOrmTypes = { dbClient: PrismaClient; dbTransaction: Prisma.TransactionClient };
175
+ > import { Prisma7OrmTypes } from "@vsrepo/prisma7-adapter";
176
+ >
177
+ > type MyOrmTypes = Prisma7OrmTypes<PrismaClient>;
176
178
  >
177
- > class UserRepository extends VSRepository<User, string, PrismaOrmTypes> {
179
+ > class UserRepository extends VSRepository<User, string, MyOrmTypes> {
178
180
  > // getDbClient() now returns PrismaClient, and transaction(fn) types `tx` as Prisma.TransactionClient
179
181
  > }
180
182
  > ```
@@ -206,14 +208,14 @@ await userRepository.remove(user.id);
206
208
 
207
209
  `VSRepoOptions<T, K>`, passed to `super(...)` inside your repository's constructor:
208
210
 
209
- | Option | Type | Description |
210
- | -------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
211
- | `adapter` | `VSRepoAdapter<T>` | **Required.** The adapter instance that translates repository calls into calls against the underlying ORM/database. |
212
- | `pkName` | `keyof T` | **Required.** Name of the field that represents the entity's primary key. |
213
- | `softRemoveKey` | `keyof T` | Optional. When set, enables `softRemove`, `softRemoveList`, `restore` and `restoreList`. |
214
- | `defaultOrdering` | `Ordering<T>` | Optional. Default ordering applied automatically to queries that accept `order`, unless overridden per call. |
215
- | `logLevel` | `VSLogLevel` | Optional. Minimum severity printed by the internal logger. Defaults to `VSLogLevel.WARN`. |
216
- | `logSlowThresholdMs` | `number \| boolean` | Optional. Duration (ms) above which a finished operation is logged as `WARN`. Defaults to 300ms. Pass `false` to disable slow-operation warnings entirely; pass `true` to use the 300ms default explicitly. |
211
+ | Option | Type | Description |
212
+ | -------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
213
+ | `adapter` | `VSRepoAdapter<T>` | **Required.** The adapter instance that translates repository calls into calls against the underlying ORM/database. |
214
+ | `pkName` | `keyof T` | Optional. Name of the field that represents the entity's primary key. When omitted, the repository falls back to the adapter's `getPkName()`. If the adapter does not implement it either, the constructor throws a `VSRepoError`. |
215
+ | `softRemoveKey` | `keyof T` | Optional. When set, enables `softRemove`, `softRemoveList`, `restore` and `restoreList`. |
216
+ | `defaultOrdering` | `Ordering<T>` | Optional. Default ordering applied automatically to queries that accept `order`, unless overridden per call. |
217
+ | `logLevel` | `VSLogLevel` | Optional. Minimum severity printed by the internal logger. Defaults to `VSLogLevel.WARN`. |
218
+ | `logSlowThresholdMs` | `number \| boolean` | Optional. Duration (ms) above which a finished operation is logged as `WARN`. Defaults to 300ms. Pass `false` to disable slow-operation warnings entirely; pass `true` to use the 300ms default explicitly. |
217
219
 
218
220
  ---
219
221
 
@@ -284,7 +286,7 @@ await userRepository.getAll({ see: "all" }); // everything, ignoring soft-delete
284
286
 
285
287
  ## Atomic and aggregate methods
286
288
 
287
- Every `VSRepository` subclass gets 8 extra methods for working with numeric fields, split into two groups:
289
+ Every `VSRepository` subclass gets 8 methods for working with numeric fields, split into two groups:
288
290
 
289
291
  **Atomic updates** — evaluated server-side against the row's _current_ value (`UPDATE ... SET field = field + value`), not a client-side read-modify-write:
290
292
 
@@ -371,11 +373,51 @@ const userWithAddress = await userRepository.get(id, {
371
373
  >
372
374
  > Custom adapters may map `relations` differently — consult the adapter's documentation for the exact semantics.
373
375
 
376
+ ### Strict return typing with `InferMethodReturn`
377
+
378
+ By default, methods are typed as returning the **whole entity**, ignoring the `select` and `relations` you pass (the same approach TypeORM takes). If you prefer a stricter type, `InferMethodReturn<T, Options>` narrows it to what was actually requested. It is opt-in and purely a type-level utility — nothing changes at runtime.
379
+
380
+ ```typescript
381
+ import type { InferMethodReturn, MethodOptions } from "vsrepo";
382
+
383
+ type Address = { id: string; city: string };
384
+ type Product = { id: string; name: string };
385
+ type User = {
386
+ id: string;
387
+ name: string;
388
+ email: string;
389
+ address: Address | null;
390
+ products: Product[];
391
+ };
392
+
393
+ const options = {
394
+ select: { id: true, name: true, products: { id: true } },
395
+ } satisfies MethodOptions<User>;
396
+
397
+ const users: InferMethodReturn<User[], typeof options> = await userRepository.getAll(options);
398
+ // { id: string; name: string; products: { id: string }[] }[]
399
+ ```
400
+
401
+ - The **first** type argument is what the method returns: `User`, `User | null` or `User[]`. `null` and array-ness are preserved — also on relation fields (`address: Address | null`).
402
+ - The **second** is the options object passed to the method (`typeof options`). It can be omitted, which is the same as passing no options.
403
+
404
+ | Options passed | Inferred result |
405
+ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
406
+ | None (or `{}`) | Only the scalar fields of the entity — no relations. |
407
+ | `relations` | The scalar fields plus the requested relations (nested ones included); each relation brings all of its scalar fields. |
408
+ | `select` | Only the selected fields. A relation set to `true` brings all of its scalar fields; a nested `select` restricts it further. Relations selected this way are loaded even without `relations`. |
409
+ | `select` + `relations` | `select` wins and `relations` is ignored. |
410
+
411
+ - `see` and `db` don't affect the result.
412
+ - Keep the options' literal types, using `satisfies MethodOptions<T>` (as above) or passing them inline. If they are typed as a plain `MethodOptions<T>` (e.g. `const options: MethodOptions<User> = ...`), nothing is known at compile time and `T` is returned unchanged.
413
+ - Optional (`?`) fields and relations of the entity stay optional.
414
+ - To get this inference directly on dynamic methods, see [Strict return typing with `InferMethodType`](#strict-return-typing-with-infermethodtype).
415
+
374
416
  ---
375
417
 
376
418
  ## Dynamic methods
377
419
 
378
- Dynamic methods are declared as a `declare` field annotated with `@DynamicMethod()`. Their behavior — which adapter method to call, which filters to apply, and how arguments map to them — is inferred entirely from the field's **name**, following the same convention-over-configuration philosophy as v1.
420
+ Dynamic methods are declared as a `declare` field annotated with `@DynamicMethod()`. Their behavior — which adapter method to call, which filters to apply, and how arguments map to them — is inferred entirely from the field's **name**.
379
421
 
380
422
  ```typescript
381
423
  class UserRepository extends VSRepository<User, string> {
@@ -407,6 +449,8 @@ class UserRepository extends VSRepository<User, string> {
407
449
  }
408
450
  ```
409
451
 
452
+ > Want the return type to follow the `select`/`relations` you pass, instead of always being the whole entity? Declare the method with [`InferMethodType`](#strict-return-typing-with-infermethodtype).
453
+
410
454
  ### Available prefixes
411
455
 
412
456
  | Prefix | Adapter method | Notes |
@@ -524,7 +568,7 @@ declare findByProductsSome: () => Promise<User[]>;
524
568
  | `Ordered` | Injects an `order: Ordering<T>` argument as the **penultimate** parameter (before the optional `MethodOptions`). |
525
569
  | `OrderedAndPaginated` | Injects `order` as the antepenultimate, then `pagination` as the penultimate — both before `MethodOptions`. |
526
570
  | `PaginatedAndOrdered` | Injects `pagination` as the antepenultimate, then `order` as the penultimate — both before `MethodOptions`. |
527
- | `OrderBy<Field>Asc` / `OrderBy<Field>Desc` | **New in v2.** Bakes a fixed ordering directly into the method name — chain fields with `And` (e.g. `OrderByCreatedAtAscAndNameDesc`). No `order` argument needed. |
571
+ | `OrderBy<Field>Asc` / `OrderBy<Field>Desc` | Bakes a fixed ordering directly into the method name — chain fields with `And` (e.g. `OrderByCreatedAtAscAndNameDesc`). No `order` argument needed. *Note: If you do not specify `Asc` or `Desc`, it defaults to `Asc`.* |
528
572
  | `Distinct<Field>And<Field>...` | Bakes fixed `distinct` fields directly into the method name (only valid on `findBy`/`findWhere`-family methods). |
529
573
  | `IgnoreConflicts` | On `createMany`/`createManyReturning`, skips records that would violate a unique constraint instead of throwing. _(Renamed from v1's `SkipDuplicates`.)_ |
530
574
 
@@ -582,6 +626,42 @@ declare buscarPorEmail: (email: string, options?: MethodOptions<User>) => Promis
582
626
  declare findByStatus: (status: string) => Promise<User[]>;
583
627
  ```
584
628
 
629
+ ### Strict return typing with `InferMethodType`
630
+
631
+ Normally you write a dynamic method's signature by hand, and its return is whatever you declare (usually the whole entity). `InferMethodType<Args, Return, OrmTypes?>` declares the method for you and infers the return **on each call** from the `select`/`relations` you pass — with the same rules as [`InferMethodReturn`](#strict-return-typing-with-infermethodreturn):
632
+
633
+ ```typescript
634
+ class UserRepository extends VSRepository<User, string, MyOrmTypes> {
635
+ @DynamicMethod()
636
+ declare findByName: InferMethodType<[name: string], User[], MyOrmTypes>;
637
+
638
+ // the third generic (OrmTypes) is optional
639
+ @DynamicMethod()
640
+ declare findOneByEmail: InferMethodType<[email: string], User | null>;
641
+ }
642
+
643
+ await userRepository.findByName("John");
644
+ // { id: string; name: string; email: string }[] (only the scalar fields)
645
+
646
+ await userRepository.findByName("John", { select: { id: true, products: { id: true } } });
647
+ // { id: string; products: { id: string }[] }[]
648
+
649
+ await userRepository.findOneByEmail("john@example.com", { relations: { address: true } });
650
+ // { id: string; name: string; email: string; address: Address | null } | null
651
+ ```
652
+
653
+ | Generic | Description |
654
+ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
655
+ | `Args` | Tuple with the method's positional arguments, **without** `options` — e.g. `[name: string]` or `[where: VSRepoWhere<User>, pagination: Pagination]`. |
656
+ | `Return` | What the method resolves to: `Entity`, `Entity \| null` or `Entity[]`. The entity type used for `select`/`relations` is taken from here. |
657
+ | `OrmTypes` | _Optional._ `VSRepoOrmTypes` for your ORM, used to type the `db` option (see [Creating a repository](#creating-a-repository)). Defaults to `VSRepoOrmTypes`. |
658
+
659
+ - `options` (`MethodOptions<Entity, OrmTypes>`) is always the **last**, optional parameter, after every argument in `Args`. If one of those arguments is optional, pass `undefined` explicitly to reach `options`.
660
+ - Without `options` the result has only the scalar fields; with them it follows the [same rules](#strict-return-typing-with-infermethodreturn) as `InferMethodReturn` (including `select` winning over `relations`).
661
+ - Unknown keys in `select`/`relations` (at any depth) are rejected at compile time, and the editor autocompletes them — just like with a plain `MethodOptions<Entity>` parameter.
662
+ - It works together with the [decorator options](#decorator-options) (`proxyTo`, `injectOrdering`).
663
+ - It is meant for dynamic methods that return entities (`findBy…`, `findOneBy…`, `findWhere…`, …). Methods that don't — `countBy…`, `existsBy…` — keep their regular signature.
664
+
585
665
  ---
586
666
 
587
667
  ## Query methods (raw SQL)
@@ -643,10 +723,10 @@ await userRepository.transaction(async tx => {
643
723
 
644
724
  ### Ad-hoc raw queries with `query()`
645
725
 
646
- For one-off raw SQL that doesn't warrant declaring a `@QueryMethod` on the repository class, call `query()` directly — it's available on every `VSRepository` instance and goes through the same adapter's `query()` implementation under the hood:
726
+ For one-off raw SQL that doesn't warrant declaring a `@QueryMethod` on the repository class, call `query()` directly — it's available on every `VSRepository` instance and uses the adapter's `query()` under the hood:
647
727
 
648
728
  ```typescript
649
- query<T = any>(query: string, options?: { args?: any[]; db?: any; modifying?: boolean; singleResult?: boolean }): Promise<T>;
729
+ query<T = any>(query: string, options?: VSRepoQueryOptions<OrmTypes>): Promise<T>;
650
730
  ```
651
731
 
652
732
  ```typescript
@@ -725,6 +805,8 @@ Beyond the entity-shaping types covered above (`VSRepoSelect`, `VSRepoRelations`
725
805
  import type {
726
806
  MethodOptions,
727
807
  RestrictMethodOptions,
808
+ InferMethodReturn,
809
+ InferMethodType,
728
810
  Pagination,
729
811
  Ordering,
730
812
  OrderByField,
@@ -750,8 +832,10 @@ import type {
750
832
  | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
751
833
  | `MethodOptions<T, K>` | Options accepted as the last argument by all dynamic methods and most base methods: `select`, `relations`, `see`, `db`. | [Base methods](#base-methods), [Dynamic methods](#dynamic-methods). |
752
834
  | `RestrictMethodOptions<T, K>` | Narrowed `MethodOptions<T, K>` exposing only `see`/`db` — used by methods that don't shape/return an `Entity` (`total`, `has`, `sum`, `average`, `min`, `max`, `removeList`, `softRemoveList`, `restoreList`). | [Base methods](#base-methods), [Atomic and aggregate methods](#atomic-and-aggregate-methods). |
835
+ | `InferMethodReturn<T, Options>` | Opt-in strict return typing: narrows `T` (`Entity`, `Entity \| null` or `Entity[]`) to the fields and relations actually requested through `select`/`relations`. `select` wins over `relations`. | [Strict return typing with `InferMethodReturn`](#strict-return-typing-with-infermethodreturn). |
836
+ | `InferMethodType<Args, Return, OrmTypes?>` | Declares a dynamic method whose return is inferred on each call from the `select`/`relations` passed as `options`. `OrmTypes` is optional and types the `db` option. | [Strict return typing with `InferMethodType`](#strict-return-typing-with-infermethodtype). |
753
837
  | `Pagination` | `{ limit?, offset? }` accepted by `getAll` and by `Paginated` dynamic methods. | [Base methods](#base-methods), [Ordering, pagination and distinct](#ordering-pagination-and-distinct). |
754
- | `Ordering<T>` / `OrderByField<T>` / `SortDirection` | Ordering shape accepted by `getAll`, `defaultOrdering` and `injectOrdering`, and by `Ordered` dynamic methods. A single object or a chained array; nested objects order to-one relations. | [Constructor options](#constructor-options), [Decorator options](#decorator-options), [Ordering, pagination and distinct](#ordering-pagination-and-distinct). |
838
+ | `Ordering<T>` / `OrderByField<T>` / `SortDirection` | Ordering shape accepted by `getAll`, `defaultOrdering`, `injectOrdering` and by `Ordered` dynamic methods. A single object or a chained array. | [Constructor options](#constructor-options), [Decorator options](#decorator-options), [Ordering, pagination and distinct](#ordering-pagination-and-distinct). |
755
839
  | `SeeMode` | `"active" \| "removed" \| "all"` — controls visibility of soft-deleted records. | [Soft-delete](#soft-delete). |
756
840
  | `DeepPartial<T>` | Recursively makes every property of `T` optional, including nested objects and array elements. | `save`, `saveList`, `patch`, `merge`, and all the dynamic writing methods. |
757
841
  | `CountResult` | `{ count: number }` — the shape returned by batch operations. | `removeList`, `softRemoveList`, `restoreList`, `createManyIgnoreConflicts`. |
@@ -869,7 +953,6 @@ export abstract class VSRepoAdapter<T> {
869
953
  update: DeepPartial<T>,
870
954
  options?: AdapterMethodOptions<T>,
871
955
  ): Promise<T>;
872
-
873
956
  abstract incrementOne<K extends NumericKeys<T>>(
874
957
  field: K,
875
958
  value: NonNullable<T[K]>,
@@ -914,9 +997,12 @@ export abstract class VSRepoAdapter<T> {
914
997
  where?: VSRepoWhere<T>,
915
998
  options?: AdapterMethodOptions<T>,
916
999
  ): Promise<number | null>;
1000
+ getPkName?(): string;
917
1001
  }
918
1002
  ```
919
1003
 
1004
+ The optional `getPkName()` lets the adapter declare the entity's primary-key field to the repository. When instantiating a `VSRepository`, you can omit `pkName` from the constructor options and it will be read from `adapter.getPkName()`. If you omit it and the adapter doesn't implement `getPkName()`, the constructor throws a `VSRepoError`.
1005
+
920
1006
  `VSRepository` never talks to the ORM directly — it only calls these methods with an already-resolved `VSRepoWhere<T>` and `AdapterMethodOptions<T>`. Once an adapter implements this contract, every base method, dynamic method, and query method works against it automatically. For a full, working implementation, see the external [`VSRepoPrisma7Adapter`](https://github.com/jaobrabo123/VSRepoPrisma7Adapter) repo.
921
1007
 
922
1008
  ### Logging from your adapter
@@ -972,14 +1058,14 @@ try {
972
1058
  }
973
1059
  ```
974
1060
 
975
- | `VSRepoErrorType` | Raised when |
976
- | ----------------- | -------------------------------------------------------------------------------------------------------------------------- |
977
- | `DECORATOR` | Invalid arguments were passed to `@DynamicMethod` or `@QueryMethod`. |
978
- | `RESOLVER` | The library failed to resolve a dynamic/query method's configuration into a callable method (e.g. an unknown method name). |
979
- | `DYNAMIC` | A resolved dynamic/query method failed at runtime (e.g. missing arguments). |
980
- | `VALIDATOR` | Invalid method options or arguments were detected during validation. |
981
- | `BASE` | Invalid usage of a base method (`get`, `save`, `remove`, etc). |
982
- | `ADAPTER` | A `VSRepoAdapter` failed while talking to the underlying ORM/database — always thrown as `VSRepoAdapterError`. |
1061
+ | `VSRepoErrorType` | Raised when |
1062
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
1063
+ | `DECORATOR` | Invalid arguments were passed to `@DynamicMethod` or `@QueryMethod`. |
1064
+ | `RESOLVER` | The library failed to resolve a dynamic/query method's configuration into a callable method (e.g. an unknown method name). |
1065
+ | `DYNAMIC` | A resolved dynamic/query method failed at runtime (e.g. missing arguments). |
1066
+ | `VALIDATOR` | Invalid method options or arguments were detected during validation (e.g. a missing `pkName` when the adapter has no `getPkName()`). |
1067
+ | `BASE` | Invalid usage of a base method (`get`, `save`, `remove`, etc). |
1068
+ | `ADAPTER` | A `VSRepoAdapter` failed while talking to the underlying ORM/database — always thrown as `VSRepoAdapterError`. |
983
1069
 
984
1070
  ### `VSRepoAdapterError` and `AdapterErrorCode`
985
1071
 
@@ -1117,14 +1203,13 @@ npm pack --dry-run
1117
1203
  npm pack
1118
1204
 
1119
1205
  # 5. Consume it locally in another project
1120
- npm install ../path/to/vsrepo-1.4.0.tgz
1206
+ npm install ../path/to/vsrepo-*.tgz
1121
1207
  ```
1122
1208
 
1123
1209
  Notes:
1124
1210
 
1125
1211
  - `pnpm build` runs `tsc -p tsconfig.build.json`, which outputs the compiled JS and generated type declarations into `dist/` with `rootDir: src`.
1126
1212
  - The published package contains **only** the `dist/` folder plus the READMEs and `LICENSE` (see `files` in `package.json`). The adapters will live in their own `@vsrepo/*-adapter` packages.
1127
- - The core is ORM-agnostic and has no `@prisma/client` peer dependency.
1128
1213
 
1129
1214
  ---
1130
1215
 
@@ -1151,8 +1236,8 @@ Notes:
1151
1236
  Contributions are welcome, especially for improving the Prisma adapter and finishing the Drizzle one! (**[GitHub repository](https://github.com/jaobrabo123/VSRepository)**):
1152
1237
 
1153
1238
  1. **Fork** the project.
1154
- 2. Create a branch for your change: `git checkout -b v2-my-change`.
1155
- 3. Push your branch: `git push origin v2-my-change`.
1239
+ 2. Create a branch for your change: `git checkout -b my-change`.
1240
+ 3. Push your branch: `git push origin my-change`.
1156
1241
  4. Open a **Pull Request**.
1157
1242
 
1158
1243
  To report issues or suggest features, open an **Issue**.