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/CHANGELOG.md +519 -0
- package/README.md +204 -121
- package/README.pt-BR.md +206 -123
- package/dist/VSRepoAdapter.d.ts +2 -0
- package/dist/VSRepoAdapter.js.map +1 -1
- package/dist/VSRepository.js +9 -1
- package/dist/VSRepository.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/utils/vs-logger.util.d.ts +2 -2
- package/dist/internal/utils/vs-logger.util.js +7 -4
- package/dist/internal/utils/vs-logger.util.js.map +1 -1
- package/dist/internal/validators/vsrepo.validator.js +2 -2
- package/dist/internal/validators/vsrepo.validator.js.map +1 -1
- package/dist/types/utils/infer-method-return.type.d.ts +108 -0
- package/dist/types/utils/infer-method-return.type.js +3 -0
- package/dist/types/utils/infer-method-return.type.js.map +1 -0
- package/dist/types/utils/infer-method-type.type.d.ts +66 -0
- package/dist/types/utils/infer-method-type.type.js +3 -0
- package/dist/types/utils/infer-method-type.type.js.map +1 -0
- package/dist/types/utils/ordering.type.d.ts +1 -5
- package/dist/types/vsrepo/vsrepo-options.type.d.ts +5 -2
- package/package.json +14 -4
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
|
-
|
|
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
|
|
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/
|
|
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`
|
|
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) | **
|
|
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
|
|
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.
|
|
105
|
-
| Drizzle (`@vsrepo/drizzle-adapter`) | π΅ **
|
|
106
|
-
| TypeORM
|
|
107
|
-
|
|
|
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
|
|
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
|
-
>
|
|
175
|
+
> import { Prisma7OrmTypes } from "@vsrepo/prisma7-adapter";
|
|
176
|
+
>
|
|
177
|
+
> type MyOrmTypes = Prisma7OrmTypes<PrismaClient>;
|
|
179
178
|
>
|
|
180
|
-
> class UserRepository extends VSRepository<User, string,
|
|
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
|
|
213
|
-
| -------------------- |
|
|
214
|
-
| `adapter` | `VSRepoAdapter<T>`
|
|
215
|
-
| `pkName` | `keyof T`
|
|
216
|
-
| `softRemoveKey` | `keyof T`
|
|
217
|
-
| `defaultOrdering` | `Ordering<T>`
|
|
218
|
-
| `logLevel` | `VSLogLevel`
|
|
219
|
-
| `logSlowThresholdMs` | `number`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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 `
|
|
440
|
-
| `createMany` | `createMany` | Receives `
|
|
441
|
-
| `createManyReturning` | `createManyReturning` | Receives `
|
|
442
|
-
| `updateBy` | `update` | Field filters + `
|
|
443
|
-
| `updateWhere` | `update` | Receives a `VSRepoWhere<T>` as the first argument, then `
|
|
444
|
-
| `updateManyBy` | `updateMany` | Field filters + `
|
|
445
|
-
| `updateManyWhere` | `updateMany` | Receives a `VSRepoWhere<T>` as the first argument, then `
|
|
446
|
-
| `updateManyReturningBy` | `updateManyReturning` | Field filters + `
|
|
447
|
-
| `updateManyReturningWhere` | `updateManyReturning` | Receives a `VSRepoWhere<T>` as the first argument, then `
|
|
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
|
-
> `
|
|
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` |
|
|
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`,
|
|
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
|
|
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?:
|
|
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`,
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1157
|
-
3. Push your branch: `git push origin
|
|
1158
|
-
4. Open a **Pull Request
|
|
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**.
|