vsrepo 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -6
- package/README.pt-BR.md +126 -6
- package/dist/VSRepoAdapter.d.ts +38 -0
- package/dist/VSRepository.d.ts +36 -5
- package/dist/VSRepository.js +64 -10
- package/dist/index.d.ts +5 -1
- package/dist/internal/validators/vsrepo.validator.d.ts +7 -0
- package/dist/internal/validators/vsrepo.validator.js +22 -0
- package/dist/types/utils/decimal-like.type.d.ts +23 -0
- package/dist/types/utils/decimal-like.type.js +2 -0
- package/dist/types/utils/numeric-keys.type.d.ts +24 -0
- package/dist/types/utils/numeric-keys.type.js +2 -0
- package/dist/types/utils/numeric-like.type.d.ts +10 -0
- package/dist/types/utils/numeric-like.type.js +2 -0
- package/dist/types/utils/primitive.type.d.ts +2 -1
- package/dist/types/utils/restrict-method-options.type.d.ts +14 -0
- package/dist/types/utils/restrict-method-options.type.js +2 -0
- 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](
|
|
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)
|
|
@@ -63,7 +66,7 @@ VSRepository lets you create strongly-typed repositories with:
|
|
|
63
66
|
|
|
64
67
|
## What changed from v1
|
|
65
68
|
|
|
66
|
-
If you're coming from the [v1](
|
|
69
|
+
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
70
|
|
|
68
71
|
| Area | v1 | v2 |
|
|
69
72
|
| -------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
@@ -97,7 +100,7 @@ The Prisma 7 adapter has now been published to npm as `@vsrepo/prisma7-adapter`
|
|
|
97
100
|
|
|
98
101
|
| Adapter | Status |
|
|
99
102
|
| ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
100
|
-
| Prisma 7 (`@vsrepo/prisma7-adapter`) | 🟢 **Released** — published to npm, implements the
|
|
103
|
+
| 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
104
|
| 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
105
|
| 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
106
|
| 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 +234,19 @@ Available automatically on every `VSRepository` subclass:
|
|
|
231
234
|
| `removeList(pks, options?)` | Deletes multiple records by primary key, returning `{ count }`. |
|
|
232
235
|
| `total(options?)` | Returns the total number of records. |
|
|
233
236
|
| `has(pk, options?)` | Checks whether a record exists, returning `boolean`. |
|
|
237
|
+
| `increment(pk, field, value, options?)` | Atomically adds `value` to a numeric field. See [Atomic and aggregate methods](#atomic-and-aggregate-methods). |
|
|
238
|
+
| `decrement(pk, field, value, options?)` | Atomically subtracts `value` from a numeric field. |
|
|
239
|
+
| `multiply(pk, field, value, options?)` | Atomically multiplies a numeric field by `value`. |
|
|
240
|
+
| `divide(pk, field, value, options?)` | Atomically divides a numeric field by `value`. |
|
|
241
|
+
| `sum(field, where?, options?)` | Sums a numeric field across every matching record; `null` if none match. |
|
|
242
|
+
| `average(field, where?, options?)` | Arithmetic mean of a numeric field across every matching record; `null` if none match. |
|
|
243
|
+
| `min(field, where?, options?)` | Minimum value of a numeric field across every matching record; `null` if none match. |
|
|
244
|
+
| `max(field, where?, options?)` | Maximum value of a numeric field across every matching record; `null` if none match. |
|
|
234
245
|
| `transaction(fn, options?)` | Runs `fn` inside a native transaction of the underlying ORM. |
|
|
235
246
|
| `getDbClient()` | Returns the underlying ORM client instance used outside of transactions. |
|
|
236
247
|
| `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
248
|
|
|
238
|
-
|
|
249
|
+
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
250
|
|
|
240
251
|
---
|
|
241
252
|
|
|
@@ -270,6 +281,62 @@ await userRepository.getAll({ see: "all" }); // everything, ignoring soft-delete
|
|
|
270
281
|
|
|
271
282
|
---
|
|
272
283
|
|
|
284
|
+
## Atomic and aggregate methods
|
|
285
|
+
|
|
286
|
+
Every `VSRepository` subclass gets 8 extra methods for working with numeric fields, split into two groups:
|
|
287
|
+
|
|
288
|
+
**Atomic updates** — evaluated server-side against the row's *current* value (`UPDATE ... SET field = field + value`), not a client-side read-modify-write:
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
await userRepository.increment("user-1", "balance", 50); // balance = balance + 50
|
|
292
|
+
await userRepository.decrement("user-1", "balance", 50); // balance = balance - 50
|
|
293
|
+
await userRepository.multiply("user-1", "balance", 2); // balance = balance * 2
|
|
294
|
+
await userRepository.divide("user-1", "balance", 4); // balance = balance / 4
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
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`.
|
|
298
|
+
|
|
299
|
+
**Aggregates** — computed across every record matching an (optional) `where`:
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
await userRepository.sum("balance"); // total balance across every active record
|
|
303
|
+
await userRepository.sum("balance", { active: true }); // ...restricted by a where
|
|
304
|
+
await userRepository.average("balance");
|
|
305
|
+
await userRepository.min("balance");
|
|
306
|
+
await userRepository.max("balance");
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
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`).
|
|
310
|
+
|
|
311
|
+
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.
|
|
312
|
+
|
|
313
|
+
### Which fields are eligible
|
|
314
|
+
|
|
315
|
+
`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`):
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
type Product = { id: string; name: string; price: Decimal; stock: number | null };
|
|
319
|
+
|
|
320
|
+
await productRepository.increment(id, "price", new Decimal(10.5)); // ok — Decimal-like
|
|
321
|
+
await productRepository.increment(id, "stock", 5); // ok — nullable numeric fields are included
|
|
322
|
+
await productRepository.increment(id, "name", 1); // compile error — "name" isn't numeric
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
`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`:
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
await productRepository.increment(id, "price", new Decimal(10.5)); // ok
|
|
329
|
+
await productRepository.increment(id, "price", 10.5); // compile error — wrap it: new Decimal(10.5)
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
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.
|
|
333
|
+
|
|
334
|
+
### Writing an adapter
|
|
335
|
+
|
|
336
|
+
`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.
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
273
340
|
## `select` and `relations`
|
|
274
341
|
|
|
275
342
|
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:
|
|
@@ -615,6 +682,7 @@ Beyond the entity-shaping types covered above (`VSRepoSelect`, `VSRepoRelations`
|
|
|
615
682
|
```typescript
|
|
616
683
|
import type {
|
|
617
684
|
MethodOptions,
|
|
685
|
+
RestrictMethodOptions,
|
|
618
686
|
Pagination,
|
|
619
687
|
Ordering,
|
|
620
688
|
OrderByField,
|
|
@@ -624,6 +692,9 @@ import type {
|
|
|
624
692
|
CountResult,
|
|
625
693
|
QueryMethodArg,
|
|
626
694
|
KeysOfType,
|
|
695
|
+
NumericKeys,
|
|
696
|
+
NumericLike,
|
|
697
|
+
DecimalLike,
|
|
627
698
|
Primitive,
|
|
628
699
|
VSRepoWhere,
|
|
629
700
|
VSRepoOrmTypes,
|
|
@@ -634,7 +705,8 @@ import type {
|
|
|
634
705
|
|
|
635
706
|
| Type | Description | Used by |
|
|
636
707
|
| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
637
|
-
| `MethodOptions<T, K>` | Options accepted as the last argument of
|
|
708
|
+
| `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). |
|
|
709
|
+
| `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
710
|
| `Pagination` | `{ limit?, offset? }` accepted by `getAll` and by `Paginated` dynamic methods. | [Base methods](#base-methods), [Ordering, pagination and distinct](#ordering-pagination-and-distinct). |
|
|
639
711
|
| `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
712
|
| `SeeMode` | `"active" \| "removed" \| "all"` — controls visibility of soft-deleted records. | [Soft-delete](#soft-delete). |
|
|
@@ -642,6 +714,9 @@ import type {
|
|
|
642
714
|
| `CountResult` | `{ count: number }` — the shape returned by batch operations. | `removeList`, `softRemoveList`, `restoreList`, `createManyIgnoreConflicts`. |
|
|
643
715
|
| `QueryMethodArg<T>` | `{ args?: T, db? }` — positional SQL parameters (`$1`, `$2`, ...) and transaction client for `@QueryMethod`. | [Query methods (raw SQL)](#query-methods-raw-sql). |
|
|
644
716
|
| `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. |
|
|
717
|
+
| `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). |
|
|
718
|
+
| `NumericLike` | `number \| bigint \| DecimalLike`. | [Atomic and aggregate methods](#atomic-and-aggregate-methods). |
|
|
719
|
+
| `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
720
|
| `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
721
|
| `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
722
|
| `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 +825,51 @@ export abstract class VSRepoAdapter<T> {
|
|
|
750
825
|
update: DeepPartial<T>,
|
|
751
826
|
options?: AdapterMethodOptions<T>,
|
|
752
827
|
): Promise<T>;
|
|
828
|
+
|
|
829
|
+
abstract incrementOne<K extends NumericKeys<T>>(
|
|
830
|
+
field: K,
|
|
831
|
+
value: NonNullable<T[K]>,
|
|
832
|
+
where: VSRepoWhere<T>,
|
|
833
|
+
options?: AdapterMethodOptions<T>,
|
|
834
|
+
): Promise<T>;
|
|
835
|
+
abstract decrementOne<K extends NumericKeys<T>>(
|
|
836
|
+
field: K,
|
|
837
|
+
value: NonNullable<T[K]>,
|
|
838
|
+
where: VSRepoWhere<T>,
|
|
839
|
+
options?: AdapterMethodOptions<T>,
|
|
840
|
+
): Promise<T>;
|
|
841
|
+
abstract multiplyOne<K extends NumericKeys<T>>(
|
|
842
|
+
field: K,
|
|
843
|
+
value: NonNullable<T[K]>,
|
|
844
|
+
where: VSRepoWhere<T>,
|
|
845
|
+
options?: AdapterMethodOptions<T>,
|
|
846
|
+
): Promise<T>;
|
|
847
|
+
abstract divideOne<K extends NumericKeys<T>>(
|
|
848
|
+
field: K,
|
|
849
|
+
value: NonNullable<T[K]>,
|
|
850
|
+
where: VSRepoWhere<T>,
|
|
851
|
+
options?: AdapterMethodOptions<T>,
|
|
852
|
+
): Promise<T>;
|
|
853
|
+
abstract sum(
|
|
854
|
+
field: NumericKeys<T>,
|
|
855
|
+
where?: VSRepoWhere<T>,
|
|
856
|
+
options?: AdapterMethodOptions<T>,
|
|
857
|
+
): Promise<number | null>;
|
|
858
|
+
abstract average(
|
|
859
|
+
field: NumericKeys<T>,
|
|
860
|
+
where?: VSRepoWhere<T>,
|
|
861
|
+
options?: AdapterMethodOptions<T>,
|
|
862
|
+
): Promise<number | null>;
|
|
863
|
+
abstract min(
|
|
864
|
+
field: NumericKeys<T>,
|
|
865
|
+
where?: VSRepoWhere<T>,
|
|
866
|
+
options?: AdapterMethodOptions<T>,
|
|
867
|
+
): Promise<number | null>;
|
|
868
|
+
abstract max(
|
|
869
|
+
field: NumericKeys<T>,
|
|
870
|
+
where?: VSRepoWhere<T>,
|
|
871
|
+
options?: AdapterMethodOptions<T>,
|
|
872
|
+
): Promise<number | null>;
|
|
753
873
|
}
|
|
754
874
|
```
|
|
755
875
|
|
|
@@ -957,7 +1077,7 @@ npm install ../path/to/vsrepo-1.4.0.tgz
|
|
|
957
1077
|
Notes:
|
|
958
1078
|
|
|
959
1079
|
- `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`).
|
|
1080
|
+
- 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
1081
|
- The core is ORM-agnostic and has no `@prisma/client` peer dependency.
|
|
962
1082
|
|
|
963
1083
|
---
|
package/README.pt-BR.md
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
> ✅ **Lançado.** O VSRepository v2.0.0 (o core agnóstico de ORM) e o [`@vsrepo/prisma7-adapter`](https://github.com/jaobrabo123/VSRepoPrisma7Adapter) já foram publicados e estão prontos para uso. O Prisma 7 é o primeiro adapter totalmente suportado; outros ORMs (TypeORM, Drizzle, etc.) ainda estão em desenvolvimento — veja [Status dos adapters](#status-dos-adapters). Se você precisa da versão anterior, somente Prisma, use o código/docs da [`v1`](https://github.com/jaobrabo123/VSRepository/tree/v1).
|
|
17
17
|
|
|
18
|
-
Biblioteca de repository pattern **agnóstica de ORM**, com suporte completo a **TypeScript** e **type inference** automático. O VSRepository v2 é uma reescrita da biblioteca [v1](
|
|
18
|
+
Biblioteca de repository pattern **agnóstica de ORM**, com suporte completo a **TypeScript** e **type inference** automático. O VSRepository v2 é uma reescrita da biblioteca [v1](https://github.com/jaobrabo123/VSRepository/tree/v1): em vez de falar diretamente com o Prisma, o núcleo agora delega toda operação a um **adapter** plugável, permitindo que a mesma API de repository funcione com Prisma, TypeORM ou qualquer outro ORM/banco que implemente o contrato de adapter.
|
|
19
19
|
|
|
20
20
|
O VSRepository permite criar repositories fortemente tipados com:
|
|
21
21
|
|
|
@@ -39,6 +39,9 @@ O VSRepository permite criar repositories fortemente tipados com:
|
|
|
39
39
|
- [Options do construtor](#options-do-construtor)
|
|
40
40
|
- [Métodos base](#métodos-base)
|
|
41
41
|
- [Soft-delete](#soft-delete)
|
|
42
|
+
- [Métodos atômicos e de agregação](#métodos-atômicos-e-de-agregação)
|
|
43
|
+
- [Quais campos são elegíveis](#quais-campos-são-elegíveis)
|
|
44
|
+
- [Escrevendo um adapter](#escrevendo-um-adapter)
|
|
42
45
|
- [`select` e `relations`](#select-e-relations)
|
|
43
46
|
- [Métodos dinâmicos](#métodos-dinâmicos)
|
|
44
47
|
- [Prefixos disponíveis](#prefixos-disponíveis)
|
|
@@ -63,7 +66,7 @@ O VSRepository permite criar repositories fortemente tipados com:
|
|
|
63
66
|
|
|
64
67
|
## O que mudou da v1
|
|
65
68
|
|
|
66
|
-
Se você vem do código/docs da [v1](
|
|
69
|
+
Se você vem do código/docs da [v1](https://github.com/jaobrabo123/VSRepository/tree/v1), aqui está o resumo. Veja cada seção linkada para detalhes.
|
|
67
70
|
|
|
68
71
|
| Área | v1 | v2 |
|
|
69
72
|
| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -97,7 +100,7 @@ O adapter do Prisma 7 já foi publicado no npm como `@vsrepo/prisma7-adapter`
|
|
|
97
100
|
|
|
98
101
|
| Adapter | Status |
|
|
99
102
|
| ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
100
|
-
| Prisma 7 (`@vsrepo/prisma7-adapter`) | 🟢 **Lançado** — publicado no npm, implementa
|
|
103
|
+
| Prisma 7 (`@vsrepo/prisma7-adapter`) | 🟢 **Lançado** — publicado no npm, implementa o contrato de `VSRepoAdapter` (CRUD, relations, transactions, `merge`, logging) com testes; veja o [`VSRepoPrisma7Adapter`](https://github.com/jaobrabo123/VSRepoPrisma7Adapter) para o código-fonte e docs. **Nota:** os métodos atômicos/de agregação (`incrementOne`, `decrementOne`, `multiplyOne`, `divideOne`, `sum`, `average`, `min`, `max` — veja [Métodos atômicos e de agregação](#métodos-atômicos-e-de-agregação)) foram adicionados ao contrato do `VSRepoAdapter` depois do último release desse adapter; confirme no changelog/versão dele se já implementam esses métodos antes de depender de `increment`/`sum`/etc. contra o Prisma 7. |
|
|
101
104
|
| TypeORM (`@vsrepo/typeorm-adapter`) | 🟡 **Planejado, ainda não publicado.** Só foi escrito um parser de referência da cláusula `where` (`parseVSRepoWhere`) para validar o design; é o ponto de partida planejado do futuro pacote `@vsrepo/typeorm-adapter`. Contribuições da comunidade nessa frente são bem-vindas. |
|
|
102
105
|
| Outros ORMs (Prisma 8, Drizzle, etc.) | 🟡 **Planejados, ainda não publicados.** Nenhum pacote oficial existe ainda — por enquanto, escreva o seu próprio adapter (veja [Escrevendo seu próprio adapter](#escrevendo-seu-próprio-adapter)) e considere publicá-lo/contribuir de volta com o projeto. |
|
|
103
106
|
| Adapters customizados | 🟢 Totalmente suportados hoje — implemente você mesmo a classe abstrata [`VSRepoAdapter`](#escrevendo-seu-próprio-adapter) para qualquer ORM/banco que precisar, no seu próprio projeto ou pacote, seguindo o mesmo formato esperado dos `@vsrepo/*-adapter`. |
|
|
@@ -231,11 +234,19 @@ Disponíveis automaticamente em toda subclasse de `VSRepository`:
|
|
|
231
234
|
| `removeList(pks, options?)` | Remove vários registros pela primary key, retornando `{ count }`. |
|
|
232
235
|
| `total(options?)` | Retorna o total de registros. |
|
|
233
236
|
| `has(pk, options?)` | Verifica se um registro existe, retornando `boolean`. |
|
|
237
|
+
| `increment(pk, field, value, options?)` | Adiciona `value` a um campo numérico de forma atômica. Veja [Métodos atômicos e de agregação](#métodos-atômicos-e-de-agregação). |
|
|
238
|
+
| `decrement(pk, field, value, options?)` | Subtrai `value` de um campo numérico de forma atômica. |
|
|
239
|
+
| `multiply(pk, field, value, options?)` | Multiplica um campo numérico por `value` de forma atômica. |
|
|
240
|
+
| `divide(pk, field, value, options?)` | Divide um campo numérico por `value` de forma atômica. |
|
|
241
|
+
| `sum(field, where?, options?)` | Soma um campo numérico em todos os registros que baterem no filtro; `null` se nenhum bater. |
|
|
242
|
+
| `average(field, where?, options?)` | Média aritmética de um campo numérico em todos os registros que baterem no filtro; `null` se nenhum bater. |
|
|
243
|
+
| `min(field, where?, options?)` | Valor mínimo de um campo numérico em todos os registros que baterem no filtro; `null` se nenhum bater. |
|
|
244
|
+
| `max(field, where?, options?)` | Valor máximo de um campo numérico em todos os registros que baterem no filtro; `null` se nenhum bater. |
|
|
234
245
|
| `transaction(fn, options?)` | Executa `fn` dentro de uma transação nativa do ORM. |
|
|
235
246
|
| `getDbClient()` | Retorna a instância do client do ORM usada fora de transações. |
|
|
236
247
|
| `query<T>(query, options?)` | Executa uma instrução SQL raw diretamente contra o banco. Veja [Queries raw pontuais com `query()`](#queries-raw-pontuais-com-query). |
|
|
237
248
|
|
|
238
|
-
|
|
249
|
+
A maioria dos métodos acima aceita um objeto `MethodOptions<Entity, OrmTypes>` como último argumento (`select`, `relations`, `see`, `db`). Alguns — `total`, `has`, `removeList`, `sum`, `average`, `min`, `max`, e os métodos em lote de soft-delete (`softRemoveList`/`restoreList`) — não retornam/moldam uma `Entity`, então aceitam o tipo mais restrito `RestrictMethodOptions<Entity, OrmTypes>` (só `see`, `db`; sem `select`/`relations`). `transaction`, `query` e `getDbClient` recebem options próprias ou nenhuma.
|
|
239
250
|
|
|
240
251
|
---
|
|
241
252
|
|
|
@@ -270,6 +281,62 @@ await userRepository.getAll({ see: "all" }); // todos, ignorando o soft-delete
|
|
|
270
281
|
|
|
271
282
|
---
|
|
272
283
|
|
|
284
|
+
## Métodos atômicos e de agregação
|
|
285
|
+
|
|
286
|
+
Toda subclasse de `VSRepository` ganha 8 métodos extras para trabalhar com campos numéricos, divididos em dois grupos:
|
|
287
|
+
|
|
288
|
+
**Updates atômicos** — avaliados no servidor contra o valor *atual* da linha (`UPDATE ... SET field = field + value`), não um read-modify-write feito no client:
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
await userRepository.increment("user-1", "balance", 50); // balance = balance + 50
|
|
292
|
+
await userRepository.decrement("user-1", "balance", 50); // balance = balance - 50
|
|
293
|
+
await userRepository.multiply("user-1", "balance", 2); // balance = balance * 2
|
|
294
|
+
await userRepository.divide("user-1", "balance", 4); // balance = balance / 4
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Os quatro retornam a `Entity` atualizada e aceitam o `MethodOptions<Entity, OrmTypes>` completo (`select`, `relations`, `see`, `db`) como último argumento, igual `get`/`save`/`patch`.
|
|
298
|
+
|
|
299
|
+
**Agregações** — calculadas sobre todos os registros que baterem num `where` (opcional):
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
await userRepository.sum("balance"); // soma do saldo de todos os registros ativos
|
|
303
|
+
await userRepository.sum("balance", { active: true }); // ...restrito por um where
|
|
304
|
+
await userRepository.average("balance");
|
|
305
|
+
await userRepository.min("balance");
|
|
306
|
+
await userRepository.max("balance");
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Os quatro retornam `number | null` — `null` quando nenhum registro bate no filtro, espelhando o comportamento de `SUM()`/`AVG()`/`MIN()`/`MAX()` do SQL, que retornam `NULL` (não `0`) sobre um conjunto vazio. Diferente dos métodos atômicos, eles aceitam o tipo mais restrito `RestrictMethodOptions<Entity, OrmTypes>` (só `see`, `db` — sem `select`/`relations`, já que o resultado é um número simples, não uma `Entity` moldada).
|
|
310
|
+
|
|
311
|
+
Os dois grupos respeitam `softRemoveKey`/`see` do mesmo jeito que todo outro método base — `sum("balance")` só soma registros não removidos por padrão; passe `{ see: "all" }` ou `{ see: "removed" }` para mudar isso.
|
|
312
|
+
|
|
313
|
+
### Quais campos são elegíveis
|
|
314
|
+
|
|
315
|
+
`field` é restrito a `NumericKeys<Entity>` — chaves cujo valor (ignorando `null`/`undefined`) é um `number`, um `bigint`, ou um objeto `DecimalLike` (qualquer coisa que exponha `toNumber()` e `decimalPlaces()`, como o `Prisma.Decimal` do Prisma):
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
type Product = { id: string; name: string; price: Decimal; stock: number | null };
|
|
319
|
+
|
|
320
|
+
await productRepository.increment(id, "price", new Decimal(10.5)); // ok — Decimal-like
|
|
321
|
+
await productRepository.increment(id, "stock", 5); // ok — campos numéricos nullable são incluídos
|
|
322
|
+
await productRepository.increment(id, "name", 1); // erro de compilação — "name" não é numérico
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
`value` é tipado como `NonNullable<Entity[Field]>` — precisa bater exatamente com o tipo do próprio campo. Um campo `Decimal` espera uma instância de `Decimal`, não um `number`/`string` puro:
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
await productRepository.increment(id, "price", new Decimal(10.5)); // ok
|
|
329
|
+
await productRepository.increment(id, "price", 10.5); // erro de compilação — envolva: new Decimal(10.5)
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Vale notar que vários ORMs (Drizzle, MikroORM, TypeORM) representam colunas `decimal`/`numeric` como `string` pura por padrão, para evitar perda de precisão de ponto flutuante — um campo `string` **não** satisfaz `NumericKeys<Entity>` por padrão. Configure a coluna em modo numérico (ou um transformer) nesses ORMs se quiser que o campo fique disponível para esses 8 métodos.
|
|
333
|
+
|
|
334
|
+
### Escrevendo um adapter
|
|
335
|
+
|
|
336
|
+
O `VSRepoAdapter` espelha as mesmas 8 operações (`incrementOne`, `decrementOne`, `multiplyOne`, `divideOne`, `sum`, `average`, `min`, `max` — veja [Escrevendo seu próprio adapter](#escrevendo-seu-próprio-adapter)). Cada adapter traduz isso para o que o ORM/banco considera "nativo": o Prisma tem um formato de update embutido (`{ field: { increment: value } }`) e uma chamada `aggregate()`; outros ORMs em geral precisam de um `QueryBuilder`/expressão `sql` raw (ex.: `SET field = field * :value`, `SELECT SUM(field) ...`). Os métodos atômicos precisam retornar o registro refletindo o estado *depois* do write — se a API de update atômico do ORM só retorna a quantidade de linhas afetadas, faça uma leitura extra em vez de devolver uma cópia desatualizada que já estava em memória.
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
273
340
|
## `select` e `relations`
|
|
274
341
|
|
|
275
342
|
Os `selectModels`/`defaultSelectModel` nomeados e reutilizáveis da v1 não existem mais. Na v2 você passa `select` e `relations` diretamente em cada chamada — não há nada para pré-registrar:
|
|
@@ -618,6 +685,7 @@ Além dos tipos que descrevem o formato da entidade já vistos acima (`VSRepoSel
|
|
|
618
685
|
```typescript
|
|
619
686
|
import type {
|
|
620
687
|
MethodOptions,
|
|
688
|
+
RestrictMethodOptions,
|
|
621
689
|
Pagination,
|
|
622
690
|
Ordering,
|
|
623
691
|
OrderByField,
|
|
@@ -627,6 +695,9 @@ import type {
|
|
|
627
695
|
CountResult,
|
|
628
696
|
QueryMethodArg,
|
|
629
697
|
KeysOfType,
|
|
698
|
+
NumericKeys,
|
|
699
|
+
NumericLike,
|
|
700
|
+
DecimalLike,
|
|
630
701
|
Primitive,
|
|
631
702
|
VSRepoWhere,
|
|
632
703
|
VSRepoOrmTypes,
|
|
@@ -637,7 +708,8 @@ import type {
|
|
|
637
708
|
|
|
638
709
|
| Tipo | Descrição | Usado por |
|
|
639
710
|
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
640
|
-
| `MethodOptions<T, K>` | Options aceitas como último argumento
|
|
711
|
+
| `MethodOptions<T, K>` | Options aceitas como último argumento pela maioria dos métodos base e dinâmicos: `select`, `relations`, `see`, `db`. | [Métodos base](#métodos-base), [Métodos Dinâmicos](#métodos-dinâmicos). |
|
|
712
|
+
| `RestrictMethodOptions<T, K>` | `MethodOptions<T, K>` restrito, expondo só `see`/`db` — usado pelos métodos que não retornam/moldam uma `Entity` (`total`, `has`, `sum`, `average`, `min`, `max`, `removeList`, `softRemoveList`, `restoreList`). | [Métodos base](#métodos-base), [Métodos atômicos e de agregação](#métodos-atômicos-e-de-agregação). |
|
|
641
713
|
| `Pagination` | `{ limit?, offset? }` aceito por `getAll` e pelos métodos dinâmicos com `Paginated`. | [Métodos base](#métodos-base), [Ordenação, paginação e distinct](#ordenação-paginação-e-distinct). |
|
|
642
714
|
| `Ordering<T>` / `OrderByField<T>` / `SortDirection` | Formato de ordenação aceito por `getAll`, `defaultOrdering` e `injectOrdering`, e pelos métodos dinâmicos com `Ordered`. Pode ser um único objeto ou um array encadeado; objetos aninhados ordenam relações to-one. | [Options do construtor](#options-do-construtor), [Options do decorador](#options-do-decorador), [Ordenação, paginação e distinct](#ordenação-paginação-e-distinct). |
|
|
643
715
|
| `SeeMode` | `"active" \| "removed" \| "all"` — controla a visibilidade de registros com soft-delete. | [Soft-delete](#soft-delete). |
|
|
@@ -645,6 +717,9 @@ import type {
|
|
|
645
717
|
| `CountResult` | `{ count: number }` — o formato retornado por operações em lote. | `removeList`, `softRemoveList`, `restoreList`, `createManyIgnoreConflicts`. |
|
|
646
718
|
| `QueryMethodArg<T>` | `{ args?: T, db? }` — parâmetros posicionais do SQL (`$1`, `$2`, ...) e cliente de transação para o `@QueryMethod`. | [Query methods (SQL raw)](#query-methods-sql-raw). |
|
|
647
719
|
| `KeysOfType<T, K>` | Extrai as chaves de `T` cujo tipo de valor é atribuível a `K`. | Restringe `pkName`, em [Options do construtor](#options-do-construtor), aos campos da entidade compatíveis com o tipo de chave primária configurado. |
|
|
720
|
+
| `NumericKeys<T>` | Extrai as chaves de `T` cujo tipo de valor (ignorando `null`/`undefined`) é atribuível a `NumericLike`. Campos numéricos nullable (`number \| null`) são incluídos. | Restringe `field` em [Métodos atômicos e de agregação](#métodos-atômicos-e-de-agregação) (`increment`, `sum`, etc). |
|
|
721
|
+
| `NumericLike` | `number \| bigint \| DecimalLike`. | [Métodos atômicos e de agregação](#métodos-atômicos-e-de-agregação). |
|
|
722
|
+
| `DecimalLike` | Formato estrutural de um valor decimal de precisão arbitrária (`{ toNumber(): number; decimalPlaces(): number }`), compatível com o `Prisma.Decimal` do Prisma sem precisar importá-lo diretamente. | [Quais campos são elegíveis](#quais-campos-são-elegíveis). |
|
|
648
723
|
| `Primitive` | União de tipos escalares (`string \| number \| boolean \| bigint \| symbol \| undefined \| null \| Date`) tratados como valores-folha — e não relações — ao percorrer o formato de uma entidade. | Usado por `Ordering<T>` para distinguir campos escalares de campos de relação. |
|
|
649
724
|
| `VSRepoWhere<T>` | Tipo de filtro agnóstico de ORM aceito pelos métodos dinâmicos `*Where` (ex.: `findWhere`, `findOneWhere`, `updateWhere`). Suporta filtros de campo, operadores lógicos (`AND`/`OR`/`NOT`) e filtros de relação. | [Prefixos `findWhere`, `findOneWhere` e demais `*Where`](#prefixos-disponíveis). |
|
|
650
725
|
| `VSRepoOrmTypes` | `{ dbClient; dbTransaction }` — descreve os tipos de client/transaction do seu ORM. Passado como terceiro generic de `VSRepository<Entity, PKType, OrmTypes>` para tipar `getDbClient()`, `transaction()` e a option `db` em vez de `any`. | [Criando um repository](#criando-um-repository). |
|
|
@@ -753,6 +828,51 @@ export abstract class VSRepoAdapter<T> {
|
|
|
753
828
|
update: DeepPartial<T>,
|
|
754
829
|
options?: AdapterMethodOptions<T>,
|
|
755
830
|
): Promise<T>;
|
|
831
|
+
|
|
832
|
+
abstract incrementOne<K extends NumericKeys<T>>(
|
|
833
|
+
field: K,
|
|
834
|
+
value: NonNullable<T[K]>,
|
|
835
|
+
where: VSRepoWhere<T>,
|
|
836
|
+
options?: AdapterMethodOptions<T>,
|
|
837
|
+
): Promise<T>;
|
|
838
|
+
abstract decrementOne<K extends NumericKeys<T>>(
|
|
839
|
+
field: K,
|
|
840
|
+
value: NonNullable<T[K]>,
|
|
841
|
+
where: VSRepoWhere<T>,
|
|
842
|
+
options?: AdapterMethodOptions<T>,
|
|
843
|
+
): Promise<T>;
|
|
844
|
+
abstract multiplyOne<K extends NumericKeys<T>>(
|
|
845
|
+
field: K,
|
|
846
|
+
value: NonNullable<T[K]>,
|
|
847
|
+
where: VSRepoWhere<T>,
|
|
848
|
+
options?: AdapterMethodOptions<T>,
|
|
849
|
+
): Promise<T>;
|
|
850
|
+
abstract divideOne<K extends NumericKeys<T>>(
|
|
851
|
+
field: K,
|
|
852
|
+
value: NonNullable<T[K]>,
|
|
853
|
+
where: VSRepoWhere<T>,
|
|
854
|
+
options?: AdapterMethodOptions<T>,
|
|
855
|
+
): Promise<T>;
|
|
856
|
+
abstract sum(
|
|
857
|
+
field: NumericKeys<T>,
|
|
858
|
+
where?: VSRepoWhere<T>,
|
|
859
|
+
options?: AdapterMethodOptions<T>,
|
|
860
|
+
): Promise<number | null>;
|
|
861
|
+
abstract average(
|
|
862
|
+
field: NumericKeys<T>,
|
|
863
|
+
where?: VSRepoWhere<T>,
|
|
864
|
+
options?: AdapterMethodOptions<T>,
|
|
865
|
+
): Promise<number | null>;
|
|
866
|
+
abstract min(
|
|
867
|
+
field: NumericKeys<T>,
|
|
868
|
+
where?: VSRepoWhere<T>,
|
|
869
|
+
options?: AdapterMethodOptions<T>,
|
|
870
|
+
): Promise<number | null>;
|
|
871
|
+
abstract max(
|
|
872
|
+
field: NumericKeys<T>,
|
|
873
|
+
where?: VSRepoWhere<T>,
|
|
874
|
+
options?: AdapterMethodOptions<T>,
|
|
875
|
+
): Promise<number | null>;
|
|
756
876
|
}
|
|
757
877
|
```
|
|
758
878
|
|
|
@@ -960,7 +1080,7 @@ npm install ../caminho/vsrepo-1.4.0.tgz
|
|
|
960
1080
|
Observações:
|
|
961
1081
|
|
|
962
1082
|
- `pnpm build` executa `tsc -p tsconfig.build.json`, que gera o JS compilado e as declarações de tipo em `dist/` com `rootDir: src`.
|
|
963
|
-
- O pacote publicado contém **apenas** a pasta `dist/` além dos READMEs e da `LICENSE` (veja `files` no `package.json`).
|
|
1083
|
+
- O pacote publicado contém **apenas** a pasta `dist/` além dos READMEs e da `LICENSE` (veja `files` no `package.json`). Os adapters viverão em seus próprios pacotes `@vsrepo/*-adapter`.
|
|
964
1084
|
- O core é ORM-agnóstico e não tem dependência peer de `@prisma/client`.
|
|
965
1085
|
|
|
966
1086
|
---
|
package/dist/VSRepoAdapter.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { AdapterMethodOptions } from "./types/adapter/adapter-method-options.typ
|
|
|
2
2
|
import { AdapterQueryOptions } from "./types/adapter/adapter-query-options.type";
|
|
3
3
|
import { CountResult } from "./types/utils/count-result.type";
|
|
4
4
|
import { DeepPartial } from "./types/utils/deep-partial.type";
|
|
5
|
+
import { NumericKeys } from "./types/utils/numeric-keys.type";
|
|
5
6
|
import { VSRepoTransactionOptions } from "./types/vsrepo/vsrepo-transaction-options.type";
|
|
6
7
|
import { VSRepoWhere } from "./types/vsrepo/vsrepo-where.type";
|
|
7
8
|
/**
|
|
@@ -68,4 +69,41 @@ export declare abstract class VSRepoAdapter<T> {
|
|
|
68
69
|
abstract merge<K>(where: VSRepoWhere<T>, obj: DeepPartial<T>, options?: AdapterMethodOptions<T>): Promise<K & T>;
|
|
69
70
|
/** Creates a record if none matches `where`, otherwise updates it. */
|
|
70
71
|
abstract upsert(where: VSRepoWhere<T>, create: DeepPartial<T>, update: DeepPartial<T>, options?: AdapterMethodOptions<T>): Promise<T>;
|
|
72
|
+
/**
|
|
73
|
+
* Atomically adds `value` to `field` on the single record matching
|
|
74
|
+
* `where` — equivalent to `UPDATE ... SET field = field + value WHERE
|
|
75
|
+
* ...`, evaluated server-side against the row's current value (not a
|
|
76
|
+
* client-side read-modify-write).
|
|
77
|
+
*
|
|
78
|
+
* Implementations must return the record reflecting the state *after*
|
|
79
|
+
* the write. If the underlying ORM's atomic-update API doesn't already
|
|
80
|
+
* return the updated row (e.g. it only returns an affected-row count),
|
|
81
|
+
* issue a follow-up read instead of returning a stale in-memory copy.
|
|
82
|
+
*/
|
|
83
|
+
abstract incrementOne<K extends NumericKeys<T>>(field: K, value: NonNullable<T[K]>, where: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<T>;
|
|
84
|
+
/** Same as {@link VSRepoAdapter.incrementOne}, subtracting `value` instead of adding it. */
|
|
85
|
+
abstract decrementOne<K extends NumericKeys<T>>(field: K, value: NonNullable<T[K]>, where: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<T>;
|
|
86
|
+
/** Same as {@link VSRepoAdapter.incrementOne}, multiplying the field's current value by `value`. */
|
|
87
|
+
abstract multiplyOne<K extends NumericKeys<T>>(field: K, value: NonNullable<T[K]>, where: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<T>;
|
|
88
|
+
/**
|
|
89
|
+
* Same as {@link VSRepoAdapter.incrementOne}, dividing the field's
|
|
90
|
+
* current value by `value`. Division-by-zero behavior is not
|
|
91
|
+
* standardized by this contract — it is whatever the underlying
|
|
92
|
+
* database/driver does natively (e.g. Postgres raises a
|
|
93
|
+
* `division_by_zero` error); document your adapter's actual behavior.
|
|
94
|
+
*/
|
|
95
|
+
abstract divideOne<K extends NumericKeys<T>>(field: K, value: NonNullable<T[K]>, where: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<T>;
|
|
96
|
+
/**
|
|
97
|
+
* Returns the sum of `field` across every record matching `where` (all
|
|
98
|
+
* records if `where` is omitted/empty), or `null` if no record
|
|
99
|
+
* matches — mirroring SQL's `SUM()`, which returns `NULL` (not `0`)
|
|
100
|
+
* over an empty set.
|
|
101
|
+
*/
|
|
102
|
+
abstract sum(field: NumericKeys<T>, where?: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<number | null>;
|
|
103
|
+
/** Same as {@link VSRepoAdapter.sum}, but the arithmetic mean (`AVG()`) instead of the total. */
|
|
104
|
+
abstract average(field: NumericKeys<T>, where?: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<number | null>;
|
|
105
|
+
/** Same as {@link VSRepoAdapter.sum}, but the minimum value (`MIN()`) instead of the total. */
|
|
106
|
+
abstract min(field: NumericKeys<T>, where?: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<number | null>;
|
|
107
|
+
/** Same as {@link VSRepoAdapter.sum}, but the maximum value (`MAX()`) instead of the total. */
|
|
108
|
+
abstract max(field: NumericKeys<T>, where?: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<number | null>;
|
|
71
109
|
}
|
package/dist/VSRepository.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { VSRepoOptions } from "./types/vsrepo/vsrepo-options.type";
|
|
|
3
3
|
import { CountResult } from "./types/utils/count-result.type";
|
|
4
4
|
import { DeepPartial } from "./types/utils/deep-partial.type";
|
|
5
5
|
import { KeysOfType } from "./types/utils/keys-of-type.type";
|
|
6
|
+
import { VSRepoWhere } from "./types/vsrepo/vsrepo-where.type";
|
|
6
7
|
import { VSRepoOrmTypes } from "./types/vsrepo/vsrepo-orm-types.type";
|
|
7
8
|
import { VSRepoTransactionOptions } from "./types/vsrepo/vsrepo-transaction-options.type";
|
|
8
9
|
import { MethodOptions } from "./types/utils/methods-options.type";
|
|
@@ -10,6 +11,8 @@ import { Ordering } from "./types/utils/ordering.type";
|
|
|
10
11
|
import { Pagination } from "./types/utils/pagination.type";
|
|
11
12
|
import { VSRepoArgs } from "./types/vsrepo/vsrepo-args.type";
|
|
12
13
|
import { VSRepoQueryOptions } from "./types/vsrepo/vsrepo-query-options.type";
|
|
14
|
+
import { NumericKeys } from "./types/utils/numeric-keys.type";
|
|
15
|
+
import { RestrictMethodOptions } from "./types/utils/restrict-method-options.type";
|
|
13
16
|
/**
|
|
14
17
|
* ORM-agnostic base repository, exposing a complete set of ready-to-use CRUD
|
|
15
18
|
* and soft-delete methods around an entity, plus any `@DynamicMethod`/`@QueryMethod`
|
|
@@ -111,7 +114,7 @@ export declare abstract class VSRepository<Entity, PKType, OrmTypes extends VSRe
|
|
|
111
114
|
/** Deletes a record identified by its primary key (PK). */
|
|
112
115
|
remove(pk: PKType, options?: MethodOptions<Entity, OrmTypes>): Promise<Entity>;
|
|
113
116
|
/** Deletes multiple records by their primary keys, returning the count of affected rows. */
|
|
114
|
-
removeList(pks: PKType[], options?:
|
|
117
|
+
removeList(pks: PKType[], options?: RestrictMethodOptions<Entity, OrmTypes>): Promise<CountResult>;
|
|
115
118
|
/** Partially updates an existing record by its primary key (PK). */
|
|
116
119
|
patch(pk: PKType, obj: DeepPartial<Entity>, options?: MethodOptions<Entity, OrmTypes>): Promise<Entity>;
|
|
117
120
|
/**
|
|
@@ -121,15 +124,43 @@ export declare abstract class VSRepository<Entity, PKType, OrmTypes extends VSRe
|
|
|
121
124
|
*/
|
|
122
125
|
merge<U extends DeepPartial<Entity>>(pk: PKType, obj: U, options?: MethodOptions<Entity, OrmTypes>): Promise<(U & Entity) | null>;
|
|
123
126
|
/** Returns the total number of records. */
|
|
124
|
-
total(options?:
|
|
127
|
+
total(options?: RestrictMethodOptions<Entity, OrmTypes>): Promise<number>;
|
|
125
128
|
/** Checks whether a record exists by its primary key (PK). */
|
|
126
|
-
has(pk: PKType, options?:
|
|
129
|
+
has(pk: PKType, options?: RestrictMethodOptions<Entity, OrmTypes>): Promise<boolean>;
|
|
127
130
|
/** Marks a record as deleted (soft-delete). Requires `softRemoveKey` to be configured on the repository. */
|
|
128
131
|
softRemove(pk: PKType, options?: MethodOptions<Entity, OrmTypes>): Promise<Entity>;
|
|
129
132
|
/** Marks multiple records as deleted (soft-delete) in batch. Requires `softRemoveKey` to be configured on the repository. */
|
|
130
|
-
softRemoveList(pks: PKType[], options?:
|
|
133
|
+
softRemoveList(pks: PKType[], options?: RestrictMethodOptions<Entity, OrmTypes>): Promise<CountResult>;
|
|
131
134
|
/** Restores a record previously marked as deleted (soft-delete). Requires `softRemoveKey` to be configured on the repository. */
|
|
132
135
|
restore(pk: PKType, options?: MethodOptions<Entity, OrmTypes>): Promise<Entity>;
|
|
133
136
|
/** Restores multiple records previously marked as deleted (soft-delete) in batch. Requires `softRemoveKey` to be configured on the repository. */
|
|
134
|
-
restoreList(pks: PKType[], options?:
|
|
137
|
+
restoreList(pks: PKType[], options?: RestrictMethodOptions<Entity, OrmTypes>): Promise<CountResult>;
|
|
138
|
+
/**
|
|
139
|
+
* Atomically adds `value` to a numeric field of the record identified
|
|
140
|
+
* by `pk`, evaluated server-side against the row's current value (e.g.
|
|
141
|
+
* `saldo = saldo + value`) — not a fetch-then-save round trip.
|
|
142
|
+
*/
|
|
143
|
+
increment<Field extends NumericKeys<Entity>>(pk: PKType, field: Field, value: NonNullable<Entity[Field]>, options?: MethodOptions<Entity, OrmTypes>): Promise<Entity>;
|
|
144
|
+
/** Same as {@link VSRepository.increment}, subtracting `value` instead of adding it. */
|
|
145
|
+
decrement<Field extends NumericKeys<Entity>>(pk: PKType, field: Field, value: NonNullable<Entity[Field]>, options?: MethodOptions<Entity, OrmTypes>): Promise<Entity>;
|
|
146
|
+
/** Same as {@link VSRepository.increment}, multiplying the field's current value by `value`. */
|
|
147
|
+
multiply<Field extends NumericKeys<Entity>>(pk: PKType, field: Field, value: NonNullable<Entity[Field]>, options?: MethodOptions<Entity, OrmTypes>): Promise<Entity>;
|
|
148
|
+
/**
|
|
149
|
+
* Same as {@link VSRepository.increment}, dividing the field's current
|
|
150
|
+
* value by `value`. Division-by-zero behavior depends on the adapter/
|
|
151
|
+
* underlying database (see {@link VSRepoAdapter.divideOne}).
|
|
152
|
+
*/
|
|
153
|
+
divide<Field extends NumericKeys<Entity>>(pk: PKType, field: Field, value: NonNullable<Entity[Field]>, options?: MethodOptions<Entity, OrmTypes>): Promise<Entity>;
|
|
154
|
+
/**
|
|
155
|
+
* Returns the sum of a numeric field across every record matching
|
|
156
|
+
* `where` (all records if omitted), or `null` if none match — mirrors
|
|
157
|
+
* SQL's `SUM()`, which returns `NULL` (not `0`) over an empty set.
|
|
158
|
+
*/
|
|
159
|
+
sum(field: NumericKeys<Entity>, where?: VSRepoWhere<Entity>, options?: RestrictMethodOptions<Entity, OrmTypes>): Promise<number | null>;
|
|
160
|
+
/** Same as {@link VSRepository.sum}, but the arithmetic mean instead of the total. */
|
|
161
|
+
average(field: NumericKeys<Entity>, where?: VSRepoWhere<Entity>, options?: RestrictMethodOptions<Entity, OrmTypes>): Promise<number | null>;
|
|
162
|
+
/** Same as {@link VSRepository.sum}, but the minimum value instead of the total. */
|
|
163
|
+
min(field: NumericKeys<Entity>, where?: VSRepoWhere<Entity>, options?: RestrictMethodOptions<Entity, OrmTypes>): Promise<number | null>;
|
|
164
|
+
/** Same as {@link VSRepository.sum}, but the maximum value instead of the total. */
|
|
165
|
+
max(field: NumericKeys<Entity>, where?: VSRepoWhere<Entity>, options?: RestrictMethodOptions<Entity, OrmTypes>): Promise<number | null>;
|
|
135
166
|
}
|
package/dist/VSRepository.js
CHANGED
|
@@ -102,10 +102,12 @@ class VSRepository {
|
|
|
102
102
|
wherePkIn(pks) {
|
|
103
103
|
return { [this.pkName]: { in: pks } };
|
|
104
104
|
}
|
|
105
|
-
async execBaseMethod(fn, methodName, optionsUnchecked) {
|
|
106
|
-
const optionsChecked =
|
|
107
|
-
? this.validator.
|
|
108
|
-
:
|
|
105
|
+
async execBaseMethod(fn, methodName, optionsUnchecked, opsType = "common") {
|
|
106
|
+
const optionsChecked = opsType === "common"
|
|
107
|
+
? this.validator.validateMethodOptions(optionsUnchecked)
|
|
108
|
+
: opsType === "restrict"
|
|
109
|
+
? this.validator.validateRestrictMethodOptions(optionsUnchecked)
|
|
110
|
+
: this.validator.validateGetAllMethodOptions(optionsUnchecked);
|
|
109
111
|
optionsChecked.db ??= this.getDbClient();
|
|
110
112
|
const start = this.logger.startPerformLog("run " + methodName);
|
|
111
113
|
try {
|
|
@@ -195,7 +197,7 @@ class VSRepository {
|
|
|
195
197
|
return this.execBaseMethod((opt) => this.adapter.findMany(this.mergeWheresResolver.resolve(opt.see, {}), {
|
|
196
198
|
...opt,
|
|
197
199
|
order: opt.order ?? this.defaultOrdering,
|
|
198
|
-
}), "getAll", options);
|
|
200
|
+
}), "getAll", options, "getAll");
|
|
199
201
|
}
|
|
200
202
|
/** Creates or updates (upsert) a record. */
|
|
201
203
|
async save(obj, options) {
|
|
@@ -217,7 +219,7 @@ class VSRepository {
|
|
|
217
219
|
if (!Array.isArray(pks)) {
|
|
218
220
|
this.fail("'pks' must be a valid array", vsrepo_error_type_enum_1.VSRepoErrorType.BASE);
|
|
219
221
|
}
|
|
220
|
-
return this.execBaseMethod(opt => this.adapter.deleteMany(this.mergeWheresResolver.resolve(opt.see, this.wherePkIn(pks)), opt), "removeList", options);
|
|
222
|
+
return this.execBaseMethod(opt => this.adapter.deleteMany(this.mergeWheresResolver.resolve(opt.see, this.wherePkIn(pks)), opt), "removeList", options, "restrict");
|
|
221
223
|
}
|
|
222
224
|
/** Partially updates an existing record by its primary key (PK). */
|
|
223
225
|
async patch(pk, obj, options) {
|
|
@@ -233,11 +235,11 @@ class VSRepository {
|
|
|
233
235
|
}
|
|
234
236
|
/** Returns the total number of records. */
|
|
235
237
|
async total(options) {
|
|
236
|
-
return this.execBaseMethod(opt => this.adapter.count(this.mergeWheresResolver.resolve(opt.see, {}), opt), "total", options);
|
|
238
|
+
return this.execBaseMethod(opt => this.adapter.count(this.mergeWheresResolver.resolve(opt.see, {}), opt), "total", options, "restrict");
|
|
237
239
|
}
|
|
238
240
|
/** Checks whether a record exists by its primary key (PK). */
|
|
239
241
|
async has(pk, options) {
|
|
240
|
-
return this.execBaseMethod(opt => this.adapter.exists(this.mergeWheresResolver.resolve(opt.see, this.wherePk(pk)), opt), "has", options);
|
|
242
|
+
return this.execBaseMethod(opt => this.adapter.exists(this.mergeWheresResolver.resolve(opt.see, this.wherePk(pk)), opt), "has", options, "restrict");
|
|
241
243
|
}
|
|
242
244
|
/** Marks a record as deleted (soft-delete). Requires `softRemoveKey` to be configured on the repository. */
|
|
243
245
|
async softRemove(pk, options) {
|
|
@@ -256,7 +258,7 @@ class VSRepository {
|
|
|
256
258
|
this.fail("'pks' must be a valid array", vsrepo_error_type_enum_1.VSRepoErrorType.BASE);
|
|
257
259
|
}
|
|
258
260
|
const key = this.softRemoveKey;
|
|
259
|
-
return this.execBaseMethod(opt => this.adapter.updateMany(this.mergeWheresResolver.resolve(opt.see ?? "all", this.wherePkIn(pks)), { [key]: new Date() }, opt), "softRemoveList", options);
|
|
261
|
+
return this.execBaseMethod(opt => this.adapter.updateMany(this.mergeWheresResolver.resolve(opt.see ?? "all", this.wherePkIn(pks)), { [key]: new Date() }, opt), "softRemoveList", options, "restrict");
|
|
260
262
|
}
|
|
261
263
|
/** Restores a record previously marked as deleted (soft-delete). Requires `softRemoveKey` to be configured on the repository. */
|
|
262
264
|
async restore(pk, options) {
|
|
@@ -275,7 +277,59 @@ class VSRepository {
|
|
|
275
277
|
this.fail("'pks' must be a valid array", vsrepo_error_type_enum_1.VSRepoErrorType.BASE);
|
|
276
278
|
}
|
|
277
279
|
const key = this.softRemoveKey;
|
|
278
|
-
return this.execBaseMethod(opt => this.adapter.updateMany(this.mergeWheresResolver.resolve(opt.see ?? "all", this.wherePkIn(pks)), { [key]: null }, opt), "restoreList", options);
|
|
280
|
+
return this.execBaseMethod(opt => this.adapter.updateMany(this.mergeWheresResolver.resolve(opt.see ?? "all", this.wherePkIn(pks)), { [key]: null }, opt), "restoreList", options, "restrict");
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Atomically adds `value` to a numeric field of the record identified
|
|
284
|
+
* by `pk`, evaluated server-side against the row's current value (e.g.
|
|
285
|
+
* `saldo = saldo + value`) — not a fetch-then-save round trip.
|
|
286
|
+
*/
|
|
287
|
+
async increment(pk, field, value, options) {
|
|
288
|
+
this.validator.assertIsNumericLike(value);
|
|
289
|
+
return this.execBaseMethod(opt => this.adapter.incrementOne(field, value, this.mergeWheresResolver.resolve(opt.see, this.wherePk(pk)), opt), "increment", options);
|
|
290
|
+
}
|
|
291
|
+
/** Same as {@link VSRepository.increment}, subtracting `value` instead of adding it. */
|
|
292
|
+
async decrement(pk, field, value, options) {
|
|
293
|
+
this.validator.assertIsNumericLike(value);
|
|
294
|
+
return this.execBaseMethod(opt => this.adapter.decrementOne(field, value, this.mergeWheresResolver.resolve(opt.see, this.wherePk(pk)), opt), "decrement", options);
|
|
295
|
+
}
|
|
296
|
+
/** Same as {@link VSRepository.increment}, multiplying the field's current value by `value`. */
|
|
297
|
+
async multiply(pk, field, value, options) {
|
|
298
|
+
this.validator.assertIsNumericLike(value);
|
|
299
|
+
return this.execBaseMethod(opt => this.adapter.multiplyOne(field, value, this.mergeWheresResolver.resolve(opt.see, this.wherePk(pk)), opt), "multiply", options);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Same as {@link VSRepository.increment}, dividing the field's current
|
|
303
|
+
* value by `value`. Division-by-zero behavior depends on the adapter/
|
|
304
|
+
* underlying database (see {@link VSRepoAdapter.divideOne}).
|
|
305
|
+
*/
|
|
306
|
+
async divide(pk, field, value, options) {
|
|
307
|
+
this.validator.assertIsNumericLike(value);
|
|
308
|
+
return this.execBaseMethod(opt => this.adapter.divideOne(field, value, this.mergeWheresResolver.resolve(opt.see, this.wherePk(pk)), opt), "divide", options);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Returns the sum of a numeric field across every record matching
|
|
312
|
+
* `where` (all records if omitted), or `null` if none match — mirrors
|
|
313
|
+
* SQL's `SUM()`, which returns `NULL` (not `0`) over an empty set.
|
|
314
|
+
*/
|
|
315
|
+
async sum(field, where, options) {
|
|
316
|
+
const validatedWhere = this.validator.validateWhere(where ?? {});
|
|
317
|
+
return this.execBaseMethod(opt => this.adapter.sum(field, this.mergeWheresResolver.resolve(opt.see, validatedWhere), opt), "sum", options, "restrict");
|
|
318
|
+
}
|
|
319
|
+
/** Same as {@link VSRepository.sum}, but the arithmetic mean instead of the total. */
|
|
320
|
+
async average(field, where, options) {
|
|
321
|
+
const validatedWhere = this.validator.validateWhere(where ?? {});
|
|
322
|
+
return this.execBaseMethod(opt => this.adapter.average(field, this.mergeWheresResolver.resolve(opt.see, validatedWhere), opt), "average", options, "restrict");
|
|
323
|
+
}
|
|
324
|
+
/** Same as {@link VSRepository.sum}, but the minimum value instead of the total. */
|
|
325
|
+
async min(field, where, options) {
|
|
326
|
+
const validatedWhere = this.validator.validateWhere(where ?? {});
|
|
327
|
+
return this.execBaseMethod(opt => this.adapter.min(field, this.mergeWheresResolver.resolve(opt.see, validatedWhere), opt), "min", options, "restrict");
|
|
328
|
+
}
|
|
329
|
+
/** Same as {@link VSRepository.sum}, but the maximum value instead of the total. */
|
|
330
|
+
async max(field, where, options) {
|
|
331
|
+
const validatedWhere = this.validator.validateWhere(where ?? {});
|
|
332
|
+
return this.execBaseMethod(opt => this.adapter.max(field, this.mergeWheresResolver.resolve(opt.see, validatedWhere), opt), "max", options, "restrict");
|
|
279
333
|
}
|
|
280
334
|
}
|
|
281
335
|
exports.VSRepository = VSRepository;
|
package/dist/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export type { VSRepoOptions } from "./types/vsrepo/vsrepo-options.type.js";
|
|
|
13
13
|
export type { VSRepoOrmTypes } from "./types/vsrepo/vsrepo-orm-types.type.js";
|
|
14
14
|
export type { VSRepoArgs } from "./types/vsrepo/vsrepo-args.type.js";
|
|
15
15
|
export type { VSRepoMethod } from "./types/vsrepo/vsrepo-method.type.js";
|
|
16
|
-
export type { VSRepoWhere, VSRepoWherePlain, VSRepoFieldWhere, VSRepoFieldOperators } from "./types/vsrepo/vsrepo-where.type.js";
|
|
16
|
+
export type { VSRepoWhere, VSRepoWherePlain, VSRepoFieldWhere, VSRepoFieldOperators, } from "./types/vsrepo/vsrepo-where.type.js";
|
|
17
17
|
export type { VSRepoRelations, RelationKeys } from "./types/vsrepo/vsrepo-relations.type.js";
|
|
18
18
|
export type { VSRepoSelect } from "./types/vsrepo/vsrepo-select.type.js";
|
|
19
19
|
export type { VSRepoTransactionOptions } from "./types/vsrepo/vsrepo-transaction-options.type.js";
|
|
@@ -31,4 +31,8 @@ export type { Pagination } from "./types/utils/pagination.type.js";
|
|
|
31
31
|
export type { Primitive } from "./types/utils/primitive.type.js";
|
|
32
32
|
export type { SeeMode } from "./types/utils/see-mode.type.js";
|
|
33
33
|
export type { VSRepoQueryOptions } from "./types/vsrepo/vsrepo-query-options.type";
|
|
34
|
+
export type { DecimalLike } from "./types/utils/decimal-like.type.js";
|
|
35
|
+
export type { NumericKeys } from "./types/utils/numeric-keys.type.js";
|
|
36
|
+
export type { NumericLike } from "./types/utils/numeric-like.type.js";
|
|
37
|
+
export type { RestrictMethodOptions } from "./types/utils/restrict-method-options.type.js";
|
|
34
38
|
export { VSLogger } from "./internal/utils/vs-logger.util.js";
|
|
@@ -8,6 +8,8 @@ import { Ordering } from "../../types/utils/ordering.type";
|
|
|
8
8
|
import { VSLogger } from "../utils/vs-logger.util";
|
|
9
9
|
import { VSRepoWhere } from "../../types/vsrepo/vsrepo-where.type";
|
|
10
10
|
import { VSRepoQueryOptions } from "../../types/vsrepo/vsrepo-query-options.type";
|
|
11
|
+
import { NumericLike } from "../../types/utils/numeric-like.type";
|
|
12
|
+
import { RestrictMethodOptions } from "../../types/utils/restrict-method-options.type";
|
|
11
13
|
export declare class VSRepoValidator<T, K, O extends VSRepoOrmTypes = VSRepoOrmTypes> {
|
|
12
14
|
private logger?;
|
|
13
15
|
setLogger(logger: VSLogger): void;
|
|
@@ -16,6 +18,8 @@ export declare class VSRepoValidator<T, K, O extends VSRepoOrmTypes = VSRepoOrmT
|
|
|
16
18
|
validateConstructorOptions(options: unknown): VSRepoOptions<T, K>;
|
|
17
19
|
private readonly methodOptionsSchema;
|
|
18
20
|
validateMethodOptions(options?: unknown): MethodOptions<T, O>;
|
|
21
|
+
private readonly restrictMethodOptionsSchema;
|
|
22
|
+
validateRestrictMethodOptions(options?: unknown): RestrictMethodOptions<T, O>;
|
|
19
23
|
private readonly getAllMethodOptionsSchema;
|
|
20
24
|
validateGetAllMethodOptions(options?: unknown): MethodOptions<T, O> & {
|
|
21
25
|
pagination?: Pagination;
|
|
@@ -30,4 +34,7 @@ export declare class VSRepoValidator<T, K, O extends VSRepoOrmTypes = VSRepoOrmT
|
|
|
30
34
|
validateOrdering(value: unknown): Ordering<T>;
|
|
31
35
|
validatePagination(value: unknown): Pagination;
|
|
32
36
|
validateWhere(value: unknown): VSRepoWhere<T>;
|
|
37
|
+
private decimalLikeSchema;
|
|
38
|
+
private numericLikeSchema;
|
|
39
|
+
assertIsNumericLike(value: unknown): asserts value is NumericLike;
|
|
33
40
|
}
|
|
@@ -89,6 +89,17 @@ class VSRepoValidator {
|
|
|
89
89
|
}
|
|
90
90
|
return parsed.output;
|
|
91
91
|
}
|
|
92
|
+
restrictMethodOptionsSchema = v.object({
|
|
93
|
+
db: v.optional(v.any()),
|
|
94
|
+
see: v.optional(v.picklist(["active", "removed", "all"])),
|
|
95
|
+
});
|
|
96
|
+
validateRestrictMethodOptions(options) {
|
|
97
|
+
const parsed = v.safeParse(this.restrictMethodOptionsSchema, options ?? {});
|
|
98
|
+
if (!parsed.success) {
|
|
99
|
+
this.failValidation(parsed.issues[0], vsrepo_error_type_enum_1.VSRepoErrorType.VALIDATOR);
|
|
100
|
+
}
|
|
101
|
+
return parsed.output;
|
|
102
|
+
}
|
|
92
103
|
getAllMethodOptionsSchema = v.object({
|
|
93
104
|
...this.methodOptionsSchema.entries,
|
|
94
105
|
order: v.optional(ordering_schema_1.default),
|
|
@@ -156,5 +167,16 @@ class VSRepoValidator {
|
|
|
156
167
|
}
|
|
157
168
|
return parsed.output;
|
|
158
169
|
}
|
|
170
|
+
decimalLikeSchema = v.looseObject({
|
|
171
|
+
toNumber: v.function(),
|
|
172
|
+
decimalPlaces: v.function(),
|
|
173
|
+
});
|
|
174
|
+
numericLikeSchema = v.union([v.number(), v.bigint(), this.decimalLikeSchema]);
|
|
175
|
+
assertIsNumericLike(value) {
|
|
176
|
+
const parsed = v.safeParse(this.numericLikeSchema, value);
|
|
177
|
+
if (!parsed.success) {
|
|
178
|
+
this.failValidation(parsed.issues[0], vsrepo_error_type_enum_1.VSRepoErrorType.VALIDATOR);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
159
181
|
}
|
|
160
182
|
exports.VSRepoValidator = VSRepoValidator;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural shape of an arbitrary-precision "Decimal" value, as commonly
|
|
3
|
+
* returned by ORMs for `decimal`/`numeric` columns (e.g. Prisma's
|
|
4
|
+
* `Prisma.Decimal`, built on top of `decimal.js`).
|
|
5
|
+
*
|
|
6
|
+
* Matched structurally (duck-typed) instead of importing a concrete class,
|
|
7
|
+
* so the core stays ORM-agnostic — any object exposing both `toNumber()`
|
|
8
|
+
* and `decimalPlaces()` is treated as Decimal-like by {@link NumericLike}
|
|
9
|
+
* and, transitively, by {@link NumericKeys}.
|
|
10
|
+
*
|
|
11
|
+
* Note that several ORMs (e.g. Drizzle, MikroORM, TypeORM) represent
|
|
12
|
+
* `decimal`/`numeric` columns as plain `string` by default, to avoid
|
|
13
|
+
* floating-point precision loss — a `string` value does **not** satisfy
|
|
14
|
+
* `DecimalLike`. Configure the column in a numeric mode (or provide a
|
|
15
|
+
* transformer) on those ORMs if you want the field to be eligible for
|
|
16
|
+
* `increment`/`decrement`/`multiply`/`divide`/`sum`/`average`/`min`/`max`.
|
|
17
|
+
*
|
|
18
|
+
* @publicApi
|
|
19
|
+
*/
|
|
20
|
+
export type DecimalLike = {
|
|
21
|
+
toNumber(): number;
|
|
22
|
+
decimalPlaces(): number;
|
|
23
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { NumericLike } from "./numeric-like.type";
|
|
2
|
+
/**
|
|
3
|
+
* Extracts the keys of `T` whose (non-nullable) value type is assignable to
|
|
4
|
+
* {@link NumericLike} — i.e. the fields eligible as the `field` argument of
|
|
5
|
+
* `increment`/`decrement`/`multiply`/`divide`/`sum`/`average`/`min`/`max`.
|
|
6
|
+
*
|
|
7
|
+
* Nullable/optional numeric fields (e.g. `number | null`) ARE included —
|
|
8
|
+
* the `null`/`undefined` part is stripped before the check, it isn't a
|
|
9
|
+
* reason to exclude the field. This means a field that is currently `NULL`
|
|
10
|
+
* in the database can be targeted; be aware that in standard SQL, arithmetic
|
|
11
|
+
* against a `NULL` value (`NULL + 5`) itself stays `NULL` — this type only
|
|
12
|
+
* governs what compiles, not the row's runtime value.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* type Product = { id: string; price: Decimal; stock: number | null; name: string };
|
|
17
|
+
* type Numeric = NumericKeys<Product>; // "price" | "stock"
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @publicApi
|
|
21
|
+
*/
|
|
22
|
+
export type NumericKeys<T> = {
|
|
23
|
+
[P in keyof T]: NonNullable<T[P]> extends NumericLike ? P : never;
|
|
24
|
+
}[keyof T];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { DecimalLike } from "./decimal-like.type";
|
|
2
|
+
/**
|
|
3
|
+
* Union of value types accepted as "numeric" by the atomic
|
|
4
|
+
* (`increment`/`decrement`/`multiply`/`divide`) and aggregate
|
|
5
|
+
* (`sum`/`average`/`min`/`max`) operations: a native `number`, a native
|
|
6
|
+
* `bigint`, or a {@link DecimalLike} object.
|
|
7
|
+
*
|
|
8
|
+
* @publicApi
|
|
9
|
+
*/
|
|
10
|
+
export type NumericLike = number | bigint | DecimalLike;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { DecimalLike } from "./decimal-like.type";
|
|
1
2
|
/**
|
|
2
3
|
* Types treated as scalar (non-relation) values when walking an entity's shape.
|
|
3
4
|
*
|
|
4
5
|
* @publicApi
|
|
5
6
|
*/
|
|
6
|
-
export type Primitive = string | number | boolean | bigint | symbol | undefined | null | Date;
|
|
7
|
+
export type Primitive = string | number | boolean | bigint | symbol | undefined | null | Date | DecimalLike;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { VSRepoOrmTypes } from "../vsrepo/vsrepo-orm-types.type";
|
|
2
|
+
import { MethodOptions } from "./methods-options.type";
|
|
3
|
+
/**
|
|
4
|
+
* Narrowed variant of {@link MethodOptions} exposing only `db` and `see`.
|
|
5
|
+
*
|
|
6
|
+
* Used by base methods that don't shape/return an `Entity` — count-like
|
|
7
|
+
* operations (`total`, `has`, `sum`, `average`, `min`, `max`) and
|
|
8
|
+
* batch-delete-like operations (`removeList`, `softRemoveList`,
|
|
9
|
+
* `restoreList`) — where `select`/`relations` (which only make sense when
|
|
10
|
+
* an `Entity` is being returned) don't apply.
|
|
11
|
+
*
|
|
12
|
+
* @publicApi
|
|
13
|
+
*/
|
|
14
|
+
export type RestrictMethodOptions<T, O extends VSRepoOrmTypes = VSRepoOrmTypes> = Pick<MethodOptions<T, O>, "db" | "see">;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vsrepo",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "ORM-agnostic repository pattern library with full TypeScript support and automatic type inference.",
|
|
5
5
|
"homepage": "https://github.com/jaobrabo123/VSRepository#readme",
|
|
6
6
|
"repository": {
|