vsrepo 2.3.0 β†’ 2.5.0-beta

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
@@ -13,16 +13,14 @@
13
13
 
14
14
  πŸ‡ΊπŸ‡Έ You're reading the English version. [πŸ‡§πŸ‡· Ler em portuguΓͺs](./README.pt-BR.md)
15
15
 
16
- > βœ… **Released.** VSRepository v2.0.0 (the ORM-agnostic core) and the [`@vsrepo/prisma7-adapter`](https://github.com/jaobrabo123/VSRepoPrisma7Adapter) are both published and ready to use. Prisma 7 is the first fully supported adapter; other ORMs (TypeORM, Drizzle, etc.) are still in progress β€” see [Adapter status](#adapter-status). If you need the previous Prisma-only release, use the [`v1`](https://github.com/jaobrabo123/VSRepository/tree/v1) code/docs instead.
17
-
18
- **ORM-agnostic** repository pattern library, with full **TypeScript** support and automatic **type inference**. VSRepository v2 is a rewrite of the [v1](https://github.com/jaobrabo123/VSRepository/tree/v1) library: instead of talking to Prisma directly, the core now delegates every operation to a pluggable **adapter**, so the same repository API can work against Prisma, TypeORM, or any other ORM/database that implements the adapter contract.
16
+ **ORM-agnostic** repository pattern library, with full **TypeScript** support and automatic **type inference**. VSRepository v2 is a rewrite of the [v1](https://github.com/jaobrabo123/VSRepository/tree/v1) library: instead of talking to Prisma directly, the core now delegates every operation to a pluggable **adapter**, so the same repository API can work against Prisma, Drizzle, or any other ORM/database that implements the adapter contract.
19
17
 
20
18
  VSRepository lets you create strongly-typed repositories with:
21
19
 
22
20
  - Automatic **base methods**: `get`, `getOrThrow`, `getList`, `save`, `saveList`, `remove`, `removeList`, `patch`, `merge`, `getAll`, `total`, `has`
23
21
  - **Native soft-delete**: `softRemove`, `softRemoveList`, `restore`, `restoreList`
24
22
  - **Dynamic methods** inferred from a `declare` field name via the `@DynamicMethod` decorator: `findByEmail`, `findManyByStatusPaginated`, `updateById`
25
- - **Raw SQL query methods** via the new `@QueryMethod` decorator, bypassing the name-parsing engine entirely
23
+ - **Raw SQL query methods** via the `@QueryMethod` decorator, bypassing the name-parsing engine entirely
26
24
  - Ad-hoc **`select`/`relations`** per call β€” no more pre-declared named projections
27
25
  - **Type safety** across 100% of operations
28
26
  - Native ORM **transactions**, shared across repositories
@@ -43,6 +41,7 @@ VSRepository lets you create strongly-typed repositories with:
43
41
  - [Which fields are eligible](#which-fields-are-eligible)
44
42
  - [Writing an adapter](#writing-an-adapter)
45
43
  - [`select` and `relations`](#select-and-relations)
44
+ - [Strict return typing with `InferMethodReturn` (BETA)](#strict-return-typing-with-infermethodreturn-beta)
46
45
  - [Dynamic methods](#dynamic-methods)
47
46
  - [Available prefixes](#available-prefixes)
48
47
  - [Field filters](#field-filters)
@@ -50,6 +49,7 @@ VSRepository lets you create strongly-typed repositories with:
50
49
  - [Relation filters](#relation-filters)
51
50
  - [Ordering, pagination and distinct](#ordering-pagination-and-distinct)
52
51
  - [Decorator options](#decorator-options)
52
+ - [Strict return typing with `InferMethodType`](#strict-return-typing-with-infermethodtype-beta)
53
53
  - [Query methods (raw SQL)](#query-methods-raw-sql)
54
54
  - [Spread arguments with `spreadArgs`](#spread-arguments-with-spreadargs)
55
55
  - [Ad-hoc raw queries with `query()`](#ad-hoc-raw-queries-with-query)
@@ -69,22 +69,22 @@ VSRepository lets you create strongly-typed repositories with:
69
69
 
70
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.
71
71
 
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/typeorm-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` (any arbitrary filter, always applied) | **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) | **Not implemented yet** |
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 |
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 |
88
88
 
89
89
  ---
90
90
 
@@ -97,17 +97,16 @@ VSRepository v2 is **ORM-agnostic by design**. The core package (`vsrepo`) only
97
97
  - `@vsrepo/typeorm-adapter`
98
98
  - `@vsrepo/drizzle-adapter`
99
99
 
100
- The Prisma 7 adapter has now been published to npm as `@vsrepo/prisma7-adapter` β€” it's currently the **only** published adapter. Adapters for the other ORMs listed above (Prisma 8, TypeORM, Drizzle) are **planned**; they just haven't been published yet. Until an official `@vsrepo/*-adapter` package exists for your ORM, you're welcome to write your own for your project, and if you'd like, publish it and open a PR to help grow the ecosystem β€” contributions here are very welcome.
100
+ The Prisma 7 adapter has now been published to npm as `@vsrepo/prisma7-adapter`. The Drizzle adapter is available as an **alpha** release β€” install it with `@vsrepo/drizzle-adapter@alpha`. Adapters for other ORMs are **planned** but not published yet. Until an official `@vsrepo/*-adapter` package exists for your ORM, you're welcome to write your own for your project, and if you'd like, publish it and open a PR to help grow the ecosystem β€” contributions here are very welcome.
101
101
 
102
102
  | Adapter | Status |
103
103
  | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
104
- | 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. |
105
- | Drizzle (`@vsrepo/drizzle-adapter`) | πŸ”΅ **In development** β€” The adapter for Drizzle ORM is currently under development and accepts community contributions; check the current status of the [`DrizzleAdapter`](https://github.com/jaobrabo123/VSRepoDrizzleAdapter) |
106
- | TypeORM (`@vsrepo/typeorm-adapter`) | 🟑 **Planned, not published yet.** Only a reference `where`-clause parser (`parseVSRepoWhere`) was written to validate the design; it's the planned starting point for the future `@vsrepo/typeorm-adapter` package. Community contributions toward this are welcome. |
107
- | Other ORMs (Prisma 8, Drizzle, 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. |
108
- | 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. |
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. |
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. |
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. |
109
108
 
110
- 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. 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.
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.
111
110
 
112
111
  ---
113
112
 
@@ -119,8 +118,6 @@ v2 is installed as the core package plus one adapter package for your ORM, for e
119
118
  npm i vsrepo @vsrepo/prisma7-adapter
120
119
  ```
121
120
 
122
- > `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)).
123
-
124
121
  ---
125
122
 
126
123
  ## Basic usage
@@ -170,14 +167,16 @@ class UserRepository extends VSRepository<User, string> {
170
167
  export default new UserRepository();
171
168
  ```
172
169
 
173
- > 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).
174
171
 
175
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`:
176
173
  >
177
174
  > ```typescript
178
- > type PrismaOrmTypes = { dbClient: PrismaClient; dbTransaction: Prisma.TransactionClient };
175
+ > import { Prisma7OrmTypes } from "@vsrepo/prisma7-adapter";
176
+ >
177
+ > type MyOrmTypes = Prisma7OrmTypes<PrismaClient>;
179
178
  >
180
- > class UserRepository extends VSRepository<User, string, PrismaOrmTypes> {
179
+ > class UserRepository extends VSRepository<User, string, MyOrmTypes> {
181
180
  > // getDbClient() now returns PrismaClient, and transaction(fn) types `tx` as Prisma.TransactionClient
182
181
  > }
183
182
  > ```
@@ -209,14 +208,14 @@ await userRepository.remove(user.id);
209
208
 
210
209
  `VSRepoOptions<T, K>`, passed to `super(...)` inside your repository's constructor:
211
210
 
212
- | Option | Type | Description |
213
- | -------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------- |
214
- | `adapter` | `VSRepoAdapter<T>` | **Required.** The adapter instance that translates repository calls into calls against the underlying ORM/database. |
215
- | `pkName` | `keyof T` | **Required.** Name of the field that represents the entity's primary key. |
216
- | `softRemoveKey` | `keyof T` | Optional. When set, enables `softRemove`, `softRemoveList`, `restore` and `restoreList`. |
217
- | `defaultOrdering` | `Ordering<T>` | Optional. Default ordering applied automatically to queries that accept `order`, unless overridden per call. |
218
- | `logLevel` | `VSLogLevel` | Optional. Minimum severity printed by the internal logger. Defaults to `VSLogLevel.WARN`. |
219
- | `logSlowThresholdMs` | `number` | Optional. Duration (ms) above which a finished operation is logged as `WARN` instead of `DEBUG`. Defaults to 300ms. |
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. |
220
219
 
221
220
  ---
222
221
 
@@ -247,7 +246,7 @@ Available automatically on every `VSRepository` subclass:
247
246
  | `min(field, where?, options?)` | Minimum value of a numeric field across every matching record; `null` if none match. |
248
247
  | `max(field, where?, options?)` | Maximum value of a numeric field across every matching record; `null` if none match. |
249
248
  | `transaction(fn, options?)` | Runs `fn` inside a native transaction of the underlying ORM. |
250
- | `getDbClient()` | Returns the underlying ORM client instance used outside of transactions. |
249
+ | `getDbClient()` | Returns the ORM client instance. |
251
250
  | `query<T>(query, options?)` | Executes a raw SQL statement directly against the database. See [Ad-hoc raw queries with `query()`](#ad-hoc-raw-queries-with-query). |
252
251
 
253
252
  Most of the above accept a `MethodOptions<Entity, OrmTypes>` object as their last argument (`select`, `relations`, `see`, `db`). A few β€” `total`, `has`, `removeList`, `sum`, `average`, `min`, `max`, and the soft-delete batch methods (`softRemoveList`/`restoreList`) β€” don't return/shape an `Entity`, so they accept the narrower `RestrictMethodOptions<Entity, OrmTypes>` instead (`see`, `db` only; no `select`/`relations`). `transaction`, `query`, and `getDbClient` accept their own options or none at all.
@@ -256,7 +255,7 @@ Most of the above accept a `MethodOptions<Entity, OrmTypes>` object as their las
256
255
 
257
256
  ## Soft-delete
258
257
 
259
- Soft-delete is now a **first-class, built-in concept**. Configure `softRemoveKey` once on the repository:
258
+ Soft-delete is a **first-class, built-in concept**. Configure `softRemoveKey` once on the repository:
260
259
 
261
260
  ```typescript
262
261
  super({
@@ -287,7 +286,7 @@ await userRepository.getAll({ see: "all" }); // everything, ignoring soft-delete
287
286
 
288
287
  ## Atomic and aggregate methods
289
288
 
290
- 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:
291
290
 
292
291
  **Atomic updates** β€” evaluated server-side against the row's _current_ value (`UPDATE ... SET field = field + value`), not a client-side read-modify-write:
293
292
 
@@ -363,14 +362,6 @@ const userWithAddress = await userRepository.get(id, {
363
362
  >
364
363
  > The core only forwards `MethodOptions.select` and `MethodOptions.relations` to the adapter β€” each adapter decides how to translate them to the underlying ORM:
365
364
  >
366
- > - **TypeORM (`@vsrepo/typeorm-adapter`)** β€” `relations` is **required** to load any relation, even when you only want a nested projection via `select`. TypeORM will not JOIN/emit the relation unless it is listed in `relations`:
367
- > ```typescript
368
- > // TypeORM: select alone is NOT enough
369
- > await userRepository.get(id, {
370
- > select: { id: true, address: { city: true } },
371
- > relations: { address: true }, // ← required in TypeORM
372
- > });
373
- > ```
374
365
  > - **Prisma 7 (`@vsrepo/prisma7-adapter` / `VSRepoPrisma7Adapter`)** β€” `relations` is converted to Prisma `include` (`parsePrismaInclude`). **If `select` is present, `relations` is ignored** because Prisma does not allow `select` + `include` in the same query:
375
366
  > ```typescript
376
367
  > // Prisma7: relations is ignored when select exists
@@ -382,16 +373,56 @@ const userWithAddress = await userRepository.get(id, {
382
373
  >
383
374
  > Custom adapters may map `relations` differently β€” consult the adapter's documentation for the exact semantics.
384
375
 
376
+ ### Strict return typing with `InferMethodReturn` [BETA]
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-beta).
415
+
385
416
  ---
386
417
 
387
418
  ## Dynamic methods
388
419
 
389
- 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**.
390
421
 
391
422
  ```typescript
392
423
  class UserRepository extends VSRepository<User, string> {
393
424
  @DynamicMethod()
394
- declare findByEmail: (email: string) => Promise<User[]>;
425
+ declare findByEmail: (email: string, options?: MethodOptions<User>) => Promise<User[]>;
395
426
 
396
427
  @DynamicMethod()
397
428
  declare findOneByEmail: (email: string) => Promise<User | null>;
@@ -407,54 +438,55 @@ class UserRepository extends VSRepository<User, string> {
407
438
  options?: MethodOptions<User>,
408
439
  ) => Promise<User[]>;
409
440
 
410
- // OrderedAndPaginated: field filters, then order, then pagination, then MethodOptions
441
+ // field filters, then pagination, then MethodOptions
411
442
  @DynamicMethod()
412
443
  declare findByNameIgnoreCaseOrAgeBetweenOrderByCreatedAtAscPaginated: (
413
444
  name: string,
414
445
  age: [number, number],
415
- order: Ordering<User>,
416
446
  pagination: Pagination,
417
447
  options?: MethodOptions<User>,
418
448
  ) => Promise<User[]>;
419
449
  }
420
450
  ```
421
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-beta).
453
+
422
454
  ### Available prefixes
423
455
 
424
- | Prefix | Adapter method | Notes |
425
- | -------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------ |
426
- | `findBy` | `findMany` | Field filters follow the prefix. |
427
- | `findOneBy` | `findOne` | Field filters follow the prefix; single result. |
428
- | `findOneOrThrowBy` | `findOneOrThrow` | Throws if no record is found. |
429
- | `findOneOrThrow` | `findOneOrThrow` | No field filters; applies only soft-delete/`see`. |
430
- | `findOneOrThrowWhere` | `findOneOrThrow` | Receives a `VSRepoWhere<T>` as the first argument. |
431
- | `findWhere` | `findMany` | Receives a `VSRepoWhere<T>` as the first argument. |
432
- | `findOneWhere` | `findOne` | Receives a `VSRepoWhere<T>` as the first argument. |
433
- | `findOne` | `findOne` | No field filters; applies only soft-delete/`see`. |
434
- | `countBy` | `count` | Field filters follow the prefix. |
435
- | `countWhere` | `count` | Receives a `VSRepoWhere<T>` as the first argument. |
436
- | `count` | `count` | No field filters. |
437
- | `existsBy` | `exists` | Returns `boolean`. |
438
- | `existsWhere` | `exists` | Receives a `VSRepoWhere<T>` as the first argument. |
439
- | `create` | `create` | Receives `data` as argument. |
440
- | `createMany` | `createMany` | Receives `data[]` as argument; supports `IgnoreConflicts`. |
441
- | `createManyReturning` | `createManyReturning` | Receives `data[]` as argument; supports `IgnoreConflicts`; returns the created records (`T[]`) instead of `CountResult`. |
442
- | `updateBy` | `update` | Field filters + `data` as argument. |
443
- | `updateWhere` | `update` | Receives a `VSRepoWhere<T>` as the first argument, then `data`. |
444
- | `updateManyBy` | `updateMany` | Field filters + `data`. |
445
- | `updateManyWhere` | `updateMany` | Receives a `VSRepoWhere<T>` as the first argument, then `data`. |
446
- | `updateManyReturningBy` | `updateManyReturning` | Field filters + `data`; returns updated records. |
447
- | `updateManyReturningWhere` | `updateManyReturning` | Receives a `VSRepoWhere<T>` as the first argument, then `data`; returns updated records. |
448
- | `upsertBy` | `upsert` | Field filters + `create`/`update` payloads. |
449
- | `upsertWhere` | `upsert` | Receives a `VSRepoWhere<T>` as the first argument, then `create`/`update` payloads. |
450
- | `deleteBy` | `delete` | Field filters follow the prefix. |
451
- | `deleteWhere` | `delete` | Receives a `VSRepoWhere<T>` as the first argument. |
452
- | `deleteManyBy` | `deleteMany` | Field filters follow the prefix. |
453
- | `deleteManyWhere` | `deleteMany` | Receives a `VSRepoWhere<T>` as the first argument. |
454
- | `deleteManyReturningBy` | `deleteManyReturning` | Field filters follow the prefix; returns deleted records. |
455
- | `deleteManyReturningWhere` | `deleteManyReturning` | Receives a `VSRepoWhere<T>` as the first argument; returns deleted records. |
456
-
457
- > `aggregate` and `groupBy` are **not implemented yet** in v2 (they existed in v1). This is planned but not currently available.
456
+ | Prefix | Adapter method | Notes |
457
+ | -------------------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
458
+ | `findBy` | `findMany` | Field filters follow the prefix. |
459
+ | `findOneBy` | `findOne` | Field filters follow the prefix; single result. |
460
+ | `findOneOrThrowBy` | `findOneOrThrow` | Throws if no record is found. |
461
+ | `findOneOrThrow` | `findOneOrThrow` | No field filters; applies only soft-delete/`see`. |
462
+ | `findOneOrThrowWhere` | `findOneOrThrow` | Receives a `VSRepoWhere<T>` as the first argument. |
463
+ | `findWhere` | `findMany` | Receives a `VSRepoWhere<T>` as the first argument. |
464
+ | `findOneWhere` | `findOne` | Receives a `VSRepoWhere<T>` as the first argument. |
465
+ | `findOne` | `findOne` | No field filters; applies only soft-delete/`see`. |
466
+ | `countBy` | `count` | Field filters follow the prefix. |
467
+ | `countWhere` | `count` | Receives a `VSRepoWhere<T>` as the first argument. |
468
+ | `count` | `count` | No field filters. |
469
+ | `existsBy` | `exists` | Returns `boolean`. |
470
+ | `existsWhere` | `exists` | Receives a `VSRepoWhere<T>` as the first argument. |
471
+ | `create` | `create` | Receives `DeepPartial<Entity>` as argument. |
472
+ | `createMany` | `createMany` | Receives `DeepPartial<Entity>[]` as argument; supports `IgnoreConflicts`. |
473
+ | `createManyReturning` | `createManyReturning` | Receives `DeepPartial<Entity>[]` as argument; supports `IgnoreConflicts`; returns the created records (`T[]`) instead of `CountResult`. |
474
+ | `updateBy` | `update` | Field filters + `DeepPartial<Entity>` as argument. |
475
+ | `updateWhere` | `update` | Receives a `VSRepoWhere<T>` as the first argument, then `DeepPartial<Entity>`. |
476
+ | `updateManyBy` | `updateMany` | Field filters + `DeepPartial<Entity>`. |
477
+ | `updateManyWhere` | `updateMany` | Receives a `VSRepoWhere<T>` as the first argument, then `DeepPartial<Entity>`. |
478
+ | `updateManyReturningBy` | `updateManyReturning` | Field filters + `DeepPartial<Entity>`; returns updated records. |
479
+ | `updateManyReturningWhere` | `updateManyReturning` | Receives a `VSRepoWhere<T>` as the first argument, then `DeepPartial<Entity>`; returns updated records. |
480
+ | `upsertBy` | `upsert` | Field filters + `create`/`update` payloads. |
481
+ | `upsertWhere` | `upsert` | Receives a `VSRepoWhere<T>` as the first argument, then `create`/`update` payloads. |
482
+ | `deleteBy` | `delete` | Field filters follow the prefix. |
483
+ | `deleteWhere` | `delete` | Receives a `VSRepoWhere<T>` as the first argument. |
484
+ | `deleteManyBy` | `deleteMany` | Field filters follow the prefix. |
485
+ | `deleteManyWhere` | `deleteMany` | Receives a `VSRepoWhere<T>` as the first argument. |
486
+ | `deleteManyReturningBy` | `deleteManyReturning` | Field filters follow the prefix; returns deleted records. |
487
+ | `deleteManyReturningWhere` | `deleteManyReturning` | Receives a `VSRepoWhere<T>` as the first argument; returns deleted records. |
488
+
489
+ > `groupBy` is **not planned** for v2 β€” it doesn't map cleanly onto the ORM-agnostic contract. `aggregate` as a separate prefix is also unlikely to be implemented: the most common aggregate operations (`sum`, `average`, `min`, `max`, `increment`, `decrement`, `multiply`, `divide`) are already available as dedicated base methods β€” see [Atomic and aggregate methods](#atomic-and-aggregate-methods). For anything more complex, use a `@QueryMethod` with raw SQL.
458
490
 
459
491
  ### Field filters
460
492
 
@@ -536,7 +568,7 @@ declare findByProductsSome: () => Promise<User[]>;
536
568
  | `Ordered` | Injects an `order: Ordering<T>` argument as the **penultimate** parameter (before the optional `MethodOptions`). |
537
569
  | `OrderedAndPaginated` | Injects `order` as the antepenultimate, then `pagination` as the penultimate β€” both before `MethodOptions`. |
538
570
  | `PaginatedAndOrdered` | Injects `pagination` as the antepenultimate, then `order` as the penultimate β€” both before `MethodOptions`. |
539
- | `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`.* |
540
572
  | `Distinct<Field>And<Field>...` | Bakes fixed `distinct` fields directly into the method name (only valid on `findBy`/`findWhere`-family methods). |
541
573
  | `IgnoreConflicts` | On `createMany`/`createManyReturning`, skips records that would violate a unique constraint instead of throwing. _(Renamed from v1's `SkipDuplicates`.)_ |
542
574
 
@@ -585,10 +617,51 @@ declare findOne: (options?: MethodOptions<User>) => Promise<User | null>;
585
617
  | `injectOrdering` | `Ordering<T>` | Fixed ordering automatically injected, overriding the repository's `defaultOrdering`. |
586
618
 
587
619
  ```typescript
620
+ // proxyTo: gives the method a custom name while reusing an existing pattern
621
+ @DynamicMethod<User>({ proxyTo: "findByEmail" })
622
+ declare buscarPorEmail: (email: string, options?: MethodOptions<User>) => Promise<User[]>;
623
+
624
+ // injectOrdering: always sorts by createdAt desc, overriding defaultOrdering
588
625
  @DynamicMethod<User>({ injectOrdering: { createdAt: "desc" } })
589
626
  declare findByStatus: (status: string) => Promise<User[]>;
590
627
  ```
591
628
 
629
+ ### Strict return typing with `InferMethodType` [BETA]
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-beta):
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-beta) 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
+
592
665
  ---
593
666
 
594
667
  ## Query methods (raw SQL)
@@ -612,7 +685,7 @@ class UserRepository extends VSRepository<User, string> {
612
685
 
613
686
  | Option | Type | Default | Description |
614
687
  | -------------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
615
- | `modifying` | `boolean` | `false` | When `true`, runs as `INSERT`/`UPDATE`/`DELETE` and the method resolves to the number of affected rows. When `false`, runs as a read query and resolves to the declared return type. |
688
+ | `modifying` | `boolean` | `false` | When `true`, the method resolves to the number of affected rows. When `false`, runs as a read query and resolves to the declared return type. |
616
689
  | `singleResult` | `boolean` | `false` | When `true`, collapses an array result into its first element (`null` if empty), so you can declare the return type as a single object instead of an array. Has no effect on non-array results (e.g. a `modifying` query's affected-row count). |
617
690
 
618
691
  Query methods accept `{ args, db? }` at the call site β€” `db` lets them participate in a `transaction()` block just like base and dynamic methods.
@@ -629,6 +702,10 @@ class UserRepository extends VSRepository<User, string> {
629
702
  declare findByEmailAndType: (
630
703
  ...args: QueryArgs<[email: string, userType: string]>
631
704
  ) => Promise<User[]>;
705
+
706
+ // Instead of using `QueryArgs`, you can also simply set `DbArg` as the last parameter
707
+ @QueryMethod('SELECT * FROM "user" WHERE id = $1', { spreadArgs: true })
708
+ declare findById: (id: string, db?: DbArg) => Promise<User[]>;
632
709
  }
633
710
 
634
711
  const admins = await userRepository.findByEmailAndType("joao@email.com", "admin");
@@ -646,10 +723,10 @@ await userRepository.transaction(async tx => {
646
723
 
647
724
  ### Ad-hoc raw queries with `query()`
648
725
 
649
- 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:
650
727
 
651
728
  ```typescript
652
- 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>;
653
730
  ```
654
731
 
655
732
  ```typescript
@@ -674,7 +751,7 @@ const user = await userRepository.query<User | null>('SELECT * FROM "user" WHERE
674
751
  | -------------- | --------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
675
752
  | `args` | `any[]` | `undefined` | Positional parameters injected into the SQL placeholders β€” the placeholder syntax depends on the database/driver behind your adapter. Never interpolate values directly into the SQL string. |
676
753
  | `db` | `any` | Repository's default client | Database client or transaction to run this query in. |
677
- | `modifying` | `boolean` | `false` | When `true`, treats the statement as `INSERT`/`UPDATE`/`DELETE`. |
754
+ | `modifying` | `boolean` | `false` | When `true`, returns the number of affected rows. |
678
755
  | `singleResult` | `boolean` | `false` | When `true`, collapses an array result into its first element (`null` if empty). Has no effect on non-array results (e.g. a `modifying` query's affected-row count). |
679
756
 
680
757
  Just like base, dynamic and query methods, `query()` accepts `db` in `options` to participate in a `transaction()` block.
@@ -728,6 +805,8 @@ Beyond the entity-shaping types covered above (`VSRepoSelect`, `VSRepoRelations`
728
805
  import type {
729
806
  MethodOptions,
730
807
  RestrictMethodOptions,
808
+ InferMethodReturn,
809
+ InferMethodType,
731
810
  Pagination,
732
811
  Ordering,
733
812
  OrderByField,
@@ -751,12 +830,14 @@ import type {
751
830
 
752
831
  | Type | Description | Used by |
753
832
  | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
754
- | `MethodOptions<T, K>` | Options accepted as the last argument of most base and dynamic methods: `select`, `relations`, `see`, `db`. | [Base methods](#base-methods), [Dynamic methods](#dynamic-methods). |
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). |
755
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-beta). |
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-beta). |
756
837
  | `Pagination` | `{ limit?, offset? }` accepted by `getAll` and by `Paginated` dynamic methods. | [Base methods](#base-methods), [Ordering, pagination and distinct](#ordering-pagination-and-distinct). |
757
- | `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). |
758
839
  | `SeeMode` | `"active" \| "removed" \| "all"` β€” controls visibility of soft-deleted records. | [Soft-delete](#soft-delete). |
759
- | `DeepPartial<T>` | Recursively makes every property of `T` optional, including nested objects and array elements. | `save`, `saveList`, `patch`, `merge`, and every write method on `VSRepoAdapter`. |
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. |
760
841
  | `CountResult` | `{ count: number }` β€” the shape returned by batch operations. | `removeList`, `softRemoveList`, `restoreList`, `createManyIgnoreConflicts`. |
761
842
  | `QueryMethodArg<T>` | `{ args?: T, db? }` β€” positional SQL parameters (the placeholder syntax depends on the database/driver behind your adapter: `$1`, `$2`, ... for PostgreSQL, `?` for MySQL) and transaction client for `@QueryMethod`. | [Query methods (raw SQL)](#query-methods-raw-sql). |
762
843
  | `QueryArgs<T, O>` | Types the spread parameter list of a `@QueryMethod` declared with `{ spreadArgs: true }`: `T`'s values in order, followed by an optional trailing `DbArg<O>` built via `withDb()`. | [Spread arguments with `spreadArgs`](#spread-arguments-with-spreadargs). |
@@ -764,7 +845,7 @@ import type {
764
845
  | `NumericKeys<T>` | Extracts the keys of `T` whose (non-nullable) value type is assignable to `NumericLike`. Nullable numeric fields (`number \| null`) are included. | Constrains `field` in [Atomic and aggregate methods](#atomic-and-aggregate-methods) (`increment`, `sum`, etc). |
765
846
  | `NumericLike` | `number \| bigint \| DecimalLike`. | [Atomic and aggregate methods](#atomic-and-aggregate-methods). |
766
847
  | `DecimalLike` | Structural shape of an arbitrary-precision decimal value (`{ toNumber(): number; decimalPlaces(): number }`), matching e.g. Prisma's `Prisma.Decimal` without importing it directly. | [Which fields are eligible](#which-fields-are-eligible). |
767
- | `Primitive` | Union of scalar types (`string \| number \| boolean \| bigint \| symbol \| undefined \| null \| Date`) treated as leaves β€” not relations β€” when walking an entity's shape. | Used by `Ordering<T>` to tell scalar fields apart from relation fields. |
848
+ | `Primitive` | Union of scalar types (`string \| number \| boolean \| bigint \| symbol \| undefined \| null \| Date \| DecimalLike`) treated as leaves β€” not relations β€” when walking an entity's shape. | Used by `Ordering<T>` to tell scalar fields apart from relation fields. |
768
849
  | `VSRepoWhere<T>` | ORM-agnostic filter type accepted by `*Where` dynamic methods (e.g. `findWhere`, `findOneWhere`, `updateWhere`). Supports field filters, logical operators (`AND`/`OR`/`NOT`), and relation filters. | [`findWhere`, `findOneWhere` and other `*Where` prefixes](#available-prefixes). |
769
850
  | `VSRepoOrmTypes` | `{ dbClient; dbTransaction }` β€” describes your ORM's client/transaction types. Passed as the third generic to `VSRepository<Entity, PKType, OrmTypes>` to type `getDbClient()`, `transaction()` and the `db` option instead of `any`. | [Creating a repository](#creating-a-repository). |
770
851
  | `VSRepoTransactionOptions` | `{ isolationLevel?, timeoutMs? }` β€” options accepted as the second argument of `transaction()`. | [Transactions](#transactions). |
@@ -872,7 +953,6 @@ export abstract class VSRepoAdapter<T> {
872
953
  update: DeepPartial<T>,
873
954
  options?: AdapterMethodOptions<T>,
874
955
  ): Promise<T>;
875
-
876
956
  abstract incrementOne<K extends NumericKeys<T>>(
877
957
  field: K,
878
958
  value: NonNullable<T[K]>,
@@ -917,9 +997,12 @@ export abstract class VSRepoAdapter<T> {
917
997
  where?: VSRepoWhere<T>,
918
998
  options?: AdapterMethodOptions<T>,
919
999
  ): Promise<number | null>;
1000
+ getPkName?(): string;
920
1001
  }
921
1002
  ```
922
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
+
923
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.
924
1007
 
925
1008
  ### Logging from your adapter
@@ -947,13 +1030,13 @@ export class MyOrmAdapter<T> extends VSRepoAdapter<T> {
947
1030
  }
948
1031
  ```
949
1032
 
950
- | Method | Description |
951
- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------ |
952
- | `new VSLogger(logLevel, name, slowThresholdMs?)` | Creates a logger; `name` prefixes every line, `slowThresholdMs` defaults to 300. |
953
- | `logDebug/logInfo/logWarn(text, obj?)` | Logs at the given level if `logLevel` allows it; `obj` is appended as pretty-printed JSON. |
954
- | `logError(text, err?)` | Logs at `ERROR`; if `err` is an `Error`, only `name`/`message`/`stack`/`cause` are logged. |
955
- | `startPerformLog(operation)` / `endPerformLog(data)` | Bracket a block to log its duration, escalating to `WARN` if it exceeds `slowThresholdMs`. |
956
- | `getLogLevel()` | Returns the logger's configured `VSLogLevel`. |
1033
+ | Method | Description |
1034
+ | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1035
+ | `new VSLogger(logLevel, name, slowThresholdMs?)` | Creates a logger; `name` prefixes every line. `slowThresholdMs` controls the slow-operation threshold: a `number` sets it in ms (default 300), `false` disables slow-operation warnings entirely, `true` or omitted uses the 300ms default. |
1036
+ | `logDebug/logInfo/logWarn(text, obj?)` | Logs at the given level if `logLevel` allows it; `obj` is appended as pretty-printed JSON. |
1037
+ | `logError(text, err?)` | Logs at `ERROR`; if `err` is an `Error`, only `name`/`message`/`stack`/`cause` are logged. |
1038
+ | `startPerformLog(operation)` / `endPerformLog(data)` | Bracket a block to log its duration, escalating to `WARN` if it exceeds `slowThresholdMs`. |
1039
+ | `getLogLevel()` | Returns the logger's configured `VSLogLevel`. |
957
1040
 
958
1041
  This is purely a convenience for adapter authors β€” nothing in the core requires your adapter to use it.
959
1042
 
@@ -975,14 +1058,14 @@ try {
975
1058
  }
976
1059
  ```
977
1060
 
978
- | `VSRepoErrorType` | Raised when |
979
- | ----------------- | -------------------------------------------------------------------------------------------------------------------------- |
980
- | `DECORATOR` | Invalid arguments were passed to `@DynamicMethod` or `@QueryMethod`. |
981
- | `RESOLVER` | The library failed to resolve a dynamic/query method's configuration into a callable method (e.g. an unknown method name). |
982
- | `DYNAMIC` | A resolved dynamic/query method failed at runtime (e.g. missing arguments). |
983
- | `VALIDATOR` | Invalid method options or arguments were detected during validation. |
984
- | `BASE` | Invalid usage of a base method (`get`, `save`, `remove`, etc). |
985
- | `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`. |
986
1069
 
987
1070
  ### `VSRepoAdapterError` and `AdapterErrorCode`
988
1071
 
@@ -1088,7 +1171,8 @@ super({
1088
1171
  pkName: "id",
1089
1172
  adapter,
1090
1173
  logLevel: VSLogLevel.DEBUG,
1091
- logSlowThresholdMs: 200,
1174
+ logSlowThresholdMs: 200, // warn if any operation takes > 200ms
1175
+ // logSlowThresholdMs: false, // disable slow-operation warnings entirely
1092
1176
  });
1093
1177
  ```
1094
1178
 
@@ -1119,14 +1203,13 @@ npm pack --dry-run
1119
1203
  npm pack
1120
1204
 
1121
1205
  # 5. Consume it locally in another project
1122
- npm install ../path/to/vsrepo-1.4.0.tgz
1206
+ npm install ../path/to/vsrepo-*.tgz
1123
1207
  ```
1124
1208
 
1125
1209
  Notes:
1126
1210
 
1127
1211
  - `pnpm build` runs `tsc -p tsconfig.build.json`, which outputs the compiled JS and generated type declarations into `dist/` with `rootDir: src`.
1128
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.
1129
- - The core is ORM-agnostic and has no `@prisma/client` peer dependency.
1130
1213
 
1131
1214
  ---
1132
1215
 
@@ -1150,11 +1233,11 @@ Notes:
1150
1233
 
1151
1234
  ## Contributing
1152
1235
 
1153
- Contributions are welcome, especially towards finishing the Prisma and TypeORM adapters! (**[GitHub repository](https://github.com/jaobrabo123/VSRepository)**):
1236
+ Contributions are welcome, especially for improving the Prisma adapter and finishing the Drizzle one! (**[GitHub repository](https://github.com/jaobrabo123/VSRepository)**):
1154
1237
 
1155
1238
  1. **Fork** the project.
1156
- 2. Create a branch off `v2` for your change: `git checkout -b v2-my-change`.
1157
- 3. Push your branch: `git push origin v2-my-change`.
1158
- 4. Open a **Pull Request** against `v2`.
1239
+ 2. Create a branch for your change: `git checkout -b my-change`.
1240
+ 3. Push your branch: `git push origin my-change`.
1241
+ 4. Open a **Pull Request**.
1159
1242
 
1160
1243
  To report issues or suggest features, open an **Issue**.