vsrepo 2.0.0 → 2.2.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.
Files changed (34) hide show
  1. package/README.md +180 -16
  2. package/README.pt-BR.md +180 -16
  3. package/dist/VSRepoAdapter.d.ts +38 -0
  4. package/dist/VSRepository.d.ts +45 -5
  5. package/dist/VSRepository.js +77 -11
  6. package/dist/decorators/query-method.decorator.d.ts +26 -3
  7. package/dist/decorators/query-method.decorator.js +25 -2
  8. package/dist/index.d.ts +8 -1
  9. package/dist/index.js +6 -1
  10. package/dist/internal/enums/vsrepo-error-type.enum.d.ts +1 -1
  11. package/dist/internal/enums/vsrepo-error-type.enum.js +1 -1
  12. package/dist/internal/resolvers/dynamic-methods.resolver.js +32 -6
  13. package/dist/internal/utils/db-arg.util.d.ts +15 -0
  14. package/dist/internal/utils/db-arg.util.js +22 -0
  15. package/dist/internal/utils/with-db.util.d.ts +19 -0
  16. package/dist/internal/utils/with-db.util.js +23 -0
  17. package/dist/internal/validators/decorators.validator.js +2 -0
  18. package/dist/internal/validators/vsrepo.validator.d.ts +8 -1
  19. package/dist/internal/validators/vsrepo.validator.js +23 -0
  20. package/dist/types/decorators/query-method-options.type.d.ts +35 -1
  21. package/dist/types/utils/decimal-like.type.d.ts +23 -0
  22. package/dist/types/utils/decimal-like.type.js +2 -0
  23. package/dist/types/utils/numeric-keys.type.d.ts +24 -0
  24. package/dist/types/utils/numeric-keys.type.js +2 -0
  25. package/dist/types/utils/numeric-like.type.d.ts +10 -0
  26. package/dist/types/utils/numeric-like.type.js +2 -0
  27. package/dist/types/utils/primitive.type.d.ts +2 -1
  28. package/dist/types/utils/query-args.type.d.ts +30 -0
  29. package/dist/types/utils/query-args.type.js +2 -0
  30. package/dist/types/utils/query-method-arg.type.d.ts +3 -2
  31. package/dist/types/utils/restrict-method-options.type.d.ts +14 -0
  32. package/dist/types/utils/restrict-method-options.type.js +2 -0
  33. package/dist/types/vsrepo/vsrepo-query-options.type.d.ts +14 -0
  34. package/package.json +1 -1
package/README.md CHANGED
@@ -15,7 +15,7 @@
15
15
 
16
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
17
 
18
- **ORM-agnostic** repository pattern library, with full **TypeScript** support and automatic **type inference**. VSRepository v2 is a rewrite of the [v1](./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.
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.
19
19
 
20
20
  VSRepository lets you create strongly-typed repositories with:
21
21
 
@@ -39,6 +39,9 @@ VSRepository lets you create strongly-typed repositories with:
39
39
  - [Constructor options](#constructor-options)
40
40
  - [Base methods](#base-methods)
41
41
  - [Soft-delete](#soft-delete)
42
+ - [Atomic and aggregate methods](#atomic-and-aggregate-methods)
43
+ - [Which fields are eligible](#which-fields-are-eligible)
44
+ - [Writing an adapter](#writing-an-adapter)
42
45
  - [`select` and `relations`](#select-and-relations)
43
46
  - [Dynamic methods](#dynamic-methods)
44
47
  - [Available prefixes](#available-prefixes)
@@ -48,6 +51,7 @@ VSRepository lets you create strongly-typed repositories with:
48
51
  - [Ordering, pagination and distinct](#ordering-pagination-and-distinct)
49
52
  - [Decorator options](#decorator-options)
50
53
  - [Query methods (raw SQL)](#query-methods-raw-sql)
54
+ - [Spread arguments with `spreadArgs`](#spread-arguments-with-spreadargs)
51
55
  - [Ad-hoc raw queries with `query()`](#ad-hoc-raw-queries-with-query)
52
56
  - [Transactions](#transactions)
53
57
  - [Utility types](#utility-types)
@@ -63,7 +67,7 @@ VSRepository lets you create strongly-typed repositories with:
63
67
 
64
68
  ## What changed from v1
65
69
 
66
- If you're coming from the [v1](./v1) code/docs, here's the short version. See each linked section for details.
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.
67
71
 
68
72
  | Area | v1 | v2 |
69
73
  | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
@@ -97,7 +101,7 @@ The Prisma 7 adapter has now been published to npm as `@vsrepo/prisma7-adapter`
97
101
 
98
102
  | Adapter | Status |
99
103
  | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
100
- | Prisma 7 (`@vsrepo/prisma7-adapter`) | 🟢 **Released** — published to npm, implements the entire `VSRepoAdapter` contract (CRUD, relations, transactions, `merge`, logging) with tests; see [`VSRepoPrisma7Adapter`](https://github.com/jaobrabo123/VSRepoPrisma7Adapter) for source and docs. |
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. |
101
105
  | 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. |
102
106
  | 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. |
103
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, following the same shape as `@vsrepo/*-adapter` is expected to have. |
@@ -231,11 +235,19 @@ Available automatically on every `VSRepository` subclass:
231
235
  | `removeList(pks, options?)` | Deletes multiple records by primary key, returning `{ count }`. |
232
236
  | `total(options?)` | Returns the total number of records. |
233
237
  | `has(pk, options?)` | Checks whether a record exists, returning `boolean`. |
238
+ | `increment(pk, field, value, options?)` | Atomically adds `value` to a numeric field. See [Atomic and aggregate methods](#atomic-and-aggregate-methods). |
239
+ | `decrement(pk, field, value, options?)` | Atomically subtracts `value` from a numeric field. |
240
+ | `multiply(pk, field, value, options?)` | Atomically multiplies a numeric field by `value`. |
241
+ | `divide(pk, field, value, options?)` | Atomically divides a numeric field by `value`. |
242
+ | `sum(field, where?, options?)` | Sums a numeric field across every matching record; `null` if none match. |
243
+ | `average(field, where?, options?)` | Arithmetic mean of a numeric field across every matching record; `null` if none match. |
244
+ | `min(field, where?, options?)` | Minimum value of a numeric field across every matching record; `null` if none match. |
245
+ | `max(field, where?, options?)` | Maximum value of a numeric field across every matching record; `null` if none match. |
234
246
  | `transaction(fn, options?)` | Runs `fn` inside a native transaction of the underlying ORM. |
235
247
  | `getDbClient()` | Returns the underlying ORM client instance used outside of transactions. |
236
248
  | `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). |
237
249
 
238
- All of the above (except `transaction`, `query`, and `getDbClient`, which accept their own options or none at all) accept a `MethodOptions<Entity, OrmTypes>` object as their last argument (`select`, `relations`, `see`, `db`).
250
+ 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.
239
251
 
240
252
  ---
241
253
 
@@ -270,6 +282,62 @@ await userRepository.getAll({ see: "all" }); // everything, ignoring soft-delete
270
282
 
271
283
  ---
272
284
 
285
+ ## Atomic and aggregate methods
286
+
287
+ Every `VSRepository` subclass gets 8 extra methods for working with numeric fields, split into two groups:
288
+
289
+ **Atomic updates** — evaluated server-side against the row's *current* value (`UPDATE ... SET field = field + value`), not a client-side read-modify-write:
290
+
291
+ ```typescript
292
+ await userRepository.increment("user-1", "balance", 50); // balance = balance + 50
293
+ await userRepository.decrement("user-1", "balance", 50); // balance = balance - 50
294
+ await userRepository.multiply("user-1", "balance", 2); // balance = balance * 2
295
+ await userRepository.divide("user-1", "balance", 4); // balance = balance / 4
296
+ ```
297
+
298
+ All four return the updated `Entity` and accept the full `MethodOptions<Entity, OrmTypes>` (`select`, `relations`, `see`, `db`) as their last argument, same as `get`/`save`/`patch`.
299
+
300
+ **Aggregates** — computed across every record matching an (optional) `where`:
301
+
302
+ ```typescript
303
+ await userRepository.sum("balance"); // total balance across every active record
304
+ await userRepository.sum("balance", { active: true }); // ...restricted by a where
305
+ await userRepository.average("balance");
306
+ await userRepository.min("balance");
307
+ await userRepository.max("balance");
308
+ ```
309
+
310
+ All four return `number | null` — `null` when no record matches, mirroring SQL's `SUM()`/`AVG()`/`MIN()`/`MAX()`, which return `NULL` (not `0`) over an empty set. Unlike the atomic methods, they accept the narrower `RestrictMethodOptions<Entity, OrmTypes>` (`see`, `db` only — no `select`/`relations`, since the result is a plain number, not a shaped `Entity`).
311
+
312
+ Both groups respect `softRemoveKey`/`see` the same way every other base method does — `sum("balance")` only totals non-deleted records by default, pass `{ see: "all" }` or `{ see: "removed" }` to change that.
313
+
314
+ ### Which fields are eligible
315
+
316
+ `field` is constrained to `NumericKeys<Entity>` — keys whose (non-nullable) value type is a `number`, a `bigint`, or a `DecimalLike` object (anything exposing `toNumber()` and `decimalPlaces()`, matching e.g. Prisma's `Prisma.Decimal`):
317
+
318
+ ```typescript
319
+ type Product = { id: string; name: string; price: Decimal; stock: number | null };
320
+
321
+ await productRepository.increment(id, "price", new Decimal(10.5)); // ok — Decimal-like
322
+ await productRepository.increment(id, "stock", 5); // ok — nullable numeric fields are included
323
+ await productRepository.increment(id, "name", 1); // compile error — "name" isn't numeric
324
+ ```
325
+
326
+ `value` is typed as `NonNullable<Entity[Field]>` — it must match the field's own type exactly. A `Decimal` field expects a `Decimal` instance, not a plain `number`/`string`:
327
+
328
+ ```typescript
329
+ await productRepository.increment(id, "price", new Decimal(10.5)); // ok
330
+ await productRepository.increment(id, "price", 10.5); // compile error — wrap it: new Decimal(10.5)
331
+ ```
332
+
333
+ Note that several ORMs (Drizzle, MikroORM, TypeORM) represent `decimal`/`numeric` columns as plain `string` by default, to avoid floating-point precision loss — a `string` field does **not** satisfy `NumericKeys<Entity>` out of the box. Configure the column in a numeric mode (or a transformer) on those ORMs if you want the field to be usable with these 8 methods.
334
+
335
+ ### Writing an adapter
336
+
337
+ `VSRepoAdapter` mirrors the same 8 operations (`incrementOne`, `decrementOne`, `multiplyOne`, `divideOne`, `sum`, `average`, `min`, `max` — see [Writing your own adapter](#writing-your-own-adapter)). Each adapter translates them into whatever its ORM/database considers "native": Prisma has a built-in `{ field: { increment: value } }` update shape and an `aggregate()` call; other ORMs typically need a `QueryBuilder`/raw-`sql` expression (e.g. `SET field = field * :value`, `SELECT SUM(field) ...`) instead. The atomic methods must return the record reflecting the state *after* the write — if the ORM's atomic-update API only returns an affected-row count, issue a follow-up read rather than returning a stale in-memory copy.
338
+
339
+ ---
340
+
273
341
  ## `select` and `relations`
274
342
 
275
343
  v1's named, reusable `selectModels`/`defaultSelectModel` are gone. In v2 you pass `select` and `relations` directly on each call — there's nothing to pre-register:
@@ -531,21 +599,54 @@ class UserRepository extends VSRepository<User, string> {
531
599
 
532
600
  @QueryMethod('UPDATE "user" SET active = true WHERE id = $1', { modifying: true })
533
601
  declare activateUser: (arg: QueryMethodArg<[id: string]>) => Promise<number>;
602
+
603
+ // Only one row is ever expected here, so `singleResult` collapses the
604
+ // array into a single object (or `null` when no row matches).
605
+ @QueryMethod('SELECT * FROM "user" WHERE id = $1 LIMIT 1', { singleResult: true })
606
+ declare findByIdRaw: (arg: QueryMethodArg<[id: string]>) => Promise<User | null>;
534
607
  }
535
608
  ```
536
609
 
537
- | Option | Type | Default | Description |
538
- | ----------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
539
- | `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. |
610
+ | Option | Type | Default | Description |
611
+ | -------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
612
+ | `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. |
613
+ | `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). |
540
614
 
541
615
  Query methods accept `{ args, db? }` at the call site — `db` lets them participate in a `transaction()` block just like base and dynamic methods.
542
616
 
617
+ ### Spread arguments with `spreadArgs`
618
+
619
+ By default, a `@QueryMethod` receives its placeholder values through a single `QueryMethodArg` object (`method({ args: [...] })`). Set `spreadArgs: true` to receive them as separate positional arguments instead, JpaRepository style:
620
+
621
+ ```typescript
622
+ class UserRepository extends VSRepository<User, string> {
623
+ @QueryMethod('SELECT * FROM "user" WHERE email = $1 AND "userType" = $2', {
624
+ spreadArgs: true,
625
+ })
626
+ declare findByEmailAndType: (
627
+ ...args: QueryArgs<[email: string, userType: string]>
628
+ ) => Promise<User[]>;
629
+ }
630
+
631
+ const admins = await userRepository.findByEmailAndType("joao@email.com", "admin");
632
+ ```
633
+
634
+ To run the query against a specific client or transaction instead of the repository's default one, pass `withDb(tx)` as the trailing argument — it wraps `tx` in a `DbArg`, which the resolver recognizes with `instanceof`, so it's never confused with a regular positional argument even if that argument happens to be an object:
635
+
636
+ ```typescript
637
+ await userRepository.transaction(async (tx) => {
638
+ await userRepository.findByEmailAndType("joao@email.com", "admin", withDb(tx));
639
+ });
640
+ ```
641
+
642
+ `spreadArgs` only affects `@QueryMethod`-declared fields — it's `false` by default, and calling a method declared without it using more than one argument throws, since the single-`QueryMethodArg` call style is expected instead. It has no effect on `query()`, which always accepts `{ args, db? }`.
643
+
543
644
  ### Ad-hoc raw queries with `query()`
544
645
 
545
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:
546
647
 
547
648
  ```typescript
548
- query<T = any>(query: string, options?: { args?: any[]; db?: any; modifying?: boolean }): Promise<T>;
649
+ query<T = any>(query: string, options?: { args?: any[]; db?: any; modifying?: boolean; singleResult?: boolean }): Promise<T>;
549
650
  ```
550
651
 
551
652
  ```typescript
@@ -557,13 +658,21 @@ const affectedRows = await userRepository.query<number>(
557
658
  'UPDATE "user" SET active = true WHERE id = $1',
558
659
  { args: ["123"], modifying: true },
559
660
  );
661
+
662
+ // Only one row is ever expected here, so `singleResult` collapses the
663
+ // array into a single object (or `null` when no row matches).
664
+ const user = await userRepository.query<User | null>(
665
+ 'SELECT * FROM "user" WHERE id = $1 LIMIT 1',
666
+ { args: ["123"], singleResult: true },
667
+ );
560
668
  ```
561
669
 
562
- | Option | Type | Default | Description |
563
- | ----------- | --------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
564
- | `args` | `any[]` | `undefined` | Positional parameters injected into `$1`, `$2`, ... placeholders. Never interpolate values directly into the SQL string. |
565
- | `db` | `any` | Repository's default client | Database client or transaction to run this query in. |
566
- | `modifying` | `boolean` | `false` | When `true`, treats the statement as `INSERT`/`UPDATE`/`DELETE`. |
670
+ | Option | Type | Default | Description |
671
+ | -------------- | --------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
672
+ | `args` | `any[]` | `undefined` | Positional parameters injected into `$1`, `$2`, ... placeholders. Never interpolate values directly into the SQL string. |
673
+ | `db` | `any` | Repository's default client | Database client or transaction to run this query in. |
674
+ | `modifying` | `boolean` | `false` | When `true`, treats the statement as `INSERT`/`UPDATE`/`DELETE`. |
675
+ | `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). |
567
676
 
568
677
  Just like base, dynamic and query methods, `query()` accepts `db` in `options` to participate in a `transaction()` block.
569
678
 
@@ -615,6 +724,7 @@ Beyond the entity-shaping types covered above (`VSRepoSelect`, `VSRepoRelations`
615
724
  ```typescript
616
725
  import type {
617
726
  MethodOptions,
727
+ RestrictMethodOptions,
618
728
  Pagination,
619
729
  Ordering,
620
730
  OrderByField,
@@ -623,7 +733,11 @@ import type {
623
733
  DeepPartial,
624
734
  CountResult,
625
735
  QueryMethodArg,
736
+ QueryArgs,
626
737
  KeysOfType,
738
+ NumericKeys,
739
+ NumericLike,
740
+ DecimalLike,
627
741
  Primitive,
628
742
  VSRepoWhere,
629
743
  VSRepoOrmTypes,
@@ -634,14 +748,19 @@ import type {
634
748
 
635
749
  | Type | Description | Used by |
636
750
  | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
637
- | `MethodOptions<T, K>` | Options accepted as the last argument of every base and dynamic method: `select`, `relations`, `see`, `db`. | [Base methods](#base-methods), [Dynamic methods](#dynamic-methods). |
751
+ | `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). |
752
+ | `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). |
638
753
  | `Pagination` | `{ limit?, offset? }` accepted by `getAll` and by `Paginated` dynamic methods. | [Base methods](#base-methods), [Ordering, pagination and distinct](#ordering-pagination-and-distinct). |
639
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). |
640
755
  | `SeeMode` | `"active" \| "removed" \| "all"` — controls visibility of soft-deleted records. | [Soft-delete](#soft-delete). |
641
756
  | `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`. |
642
757
  | `CountResult` | `{ count: number }` — the shape returned by batch operations. | `removeList`, `softRemoveList`, `restoreList`, `createManyIgnoreConflicts`. |
643
758
  | `QueryMethodArg<T>` | `{ args?: T, db? }` — positional SQL parameters (`$1`, `$2`, ...) and transaction client for `@QueryMethod`. | [Query methods (raw SQL)](#query-methods-raw-sql). |
759
+ | `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). |
644
760
  | `KeysOfType<T, K>` | Extracts the keys of `T` whose value type is assignable to `K`. | Constrains `pkName` in [Constructor options](#constructor-options) to fields of the entity matching the configured primary-key type. |
761
+ | `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). |
762
+ | `NumericLike` | `number \| bigint \| DecimalLike`. | [Atomic and aggregate methods](#atomic-and-aggregate-methods). |
763
+ | `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). |
645
764
  | `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. |
646
765
  | `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). |
647
766
  | `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). |
@@ -750,6 +869,51 @@ export abstract class VSRepoAdapter<T> {
750
869
  update: DeepPartial<T>,
751
870
  options?: AdapterMethodOptions<T>,
752
871
  ): Promise<T>;
872
+
873
+ abstract incrementOne<K extends NumericKeys<T>>(
874
+ field: K,
875
+ value: NonNullable<T[K]>,
876
+ where: VSRepoWhere<T>,
877
+ options?: AdapterMethodOptions<T>,
878
+ ): Promise<T>;
879
+ abstract decrementOne<K extends NumericKeys<T>>(
880
+ field: K,
881
+ value: NonNullable<T[K]>,
882
+ where: VSRepoWhere<T>,
883
+ options?: AdapterMethodOptions<T>,
884
+ ): Promise<T>;
885
+ abstract multiplyOne<K extends NumericKeys<T>>(
886
+ field: K,
887
+ value: NonNullable<T[K]>,
888
+ where: VSRepoWhere<T>,
889
+ options?: AdapterMethodOptions<T>,
890
+ ): Promise<T>;
891
+ abstract divideOne<K extends NumericKeys<T>>(
892
+ field: K,
893
+ value: NonNullable<T[K]>,
894
+ where: VSRepoWhere<T>,
895
+ options?: AdapterMethodOptions<T>,
896
+ ): Promise<T>;
897
+ abstract sum(
898
+ field: NumericKeys<T>,
899
+ where?: VSRepoWhere<T>,
900
+ options?: AdapterMethodOptions<T>,
901
+ ): Promise<number | null>;
902
+ abstract average(
903
+ field: NumericKeys<T>,
904
+ where?: VSRepoWhere<T>,
905
+ options?: AdapterMethodOptions<T>,
906
+ ): Promise<number | null>;
907
+ abstract min(
908
+ field: NumericKeys<T>,
909
+ where?: VSRepoWhere<T>,
910
+ options?: AdapterMethodOptions<T>,
911
+ ): Promise<number | null>;
912
+ abstract max(
913
+ field: NumericKeys<T>,
914
+ where?: VSRepoWhere<T>,
915
+ options?: AdapterMethodOptions<T>,
916
+ ): Promise<number | null>;
753
917
  }
754
918
  ```
755
919
 
@@ -812,7 +976,7 @@ try {
812
976
  | ----------------- | -------------------------------------------------------------------------------------------------------------------------- |
813
977
  | `DECORATOR` | Invalid arguments were passed to `@DynamicMethod` or `@QueryMethod`. |
814
978
  | `RESOLVER` | The library failed to resolve a dynamic/query method's configuration into a callable method (e.g. an unknown method name). |
815
- | `DYNAMIC` | A resolved dynamic method failed at runtime (e.g. missing arguments). |
979
+ | `DYNAMIC` | A resolved dynamic/query method failed at runtime (e.g. missing arguments). |
816
980
  | `VALIDATOR` | Invalid method options or arguments were detected during validation. |
817
981
  | `BASE` | Invalid usage of a base method (`get`, `save`, `remove`, etc). |
818
982
  | `ADAPTER` | A `VSRepoAdapter` failed while talking to the underlying ORM/database — always thrown as `VSRepoAdapterError`. |
@@ -957,7 +1121,7 @@ npm install ../path/to/vsrepo-1.4.0.tgz
957
1121
  Notes:
958
1122
 
959
1123
  - `pnpm build` runs `tsc -p tsconfig.build.json`, which outputs the compiled JS and generated type declarations into `dist/` with `rootDir: src`.
960
- - The published package contains **only** the `dist/` folder plus the READMEs and `LICENSE` (see `files` in `package.json`). Source, tests, the `v1/` folder and `generated/` are **not** shipped — the adapters will live in their own `@vsrepo/*-adapter` packages.
1124
+ - 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.
961
1125
  - The core is ORM-agnostic and has no `@prisma/client` peer dependency.
962
1126
 
963
1127
  ---