vsrepo 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -10
- package/README.pt-BR.md +54 -10
- package/dist/VSRepository.d.ts +9 -0
- package/dist/VSRepository.js +13 -1
- package/dist/decorators/query-method.decorator.d.ts +26 -3
- package/dist/decorators/query-method.decorator.js +25 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +6 -1
- package/dist/internal/enums/vsrepo-error-type.enum.d.ts +1 -1
- package/dist/internal/enums/vsrepo-error-type.enum.js +1 -1
- package/dist/internal/resolvers/dynamic-methods.resolver.js +32 -6
- package/dist/internal/utils/db-arg.util.d.ts +15 -0
- package/dist/internal/utils/db-arg.util.js +22 -0
- package/dist/internal/utils/with-db.util.d.ts +19 -0
- package/dist/internal/utils/with-db.util.js +23 -0
- package/dist/internal/validators/decorators.validator.js +2 -0
- package/dist/internal/validators/vsrepo.validator.d.ts +1 -1
- package/dist/internal/validators/vsrepo.validator.js +1 -0
- package/dist/types/decorators/query-method-options.type.d.ts +35 -1
- package/dist/types/utils/query-args.type.d.ts +30 -0
- package/dist/types/utils/query-args.type.js +2 -0
- package/dist/types/utils/query-method-arg.type.d.ts +3 -2
- package/dist/types/vsrepo/vsrepo-query-options.type.d.ts +14 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,6 +51,7 @@ VSRepository lets you create strongly-typed repositories with:
|
|
|
51
51
|
- [Ordering, pagination and distinct](#ordering-pagination-and-distinct)
|
|
52
52
|
- [Decorator options](#decorator-options)
|
|
53
53
|
- [Query methods (raw SQL)](#query-methods-raw-sql)
|
|
54
|
+
- [Spread arguments with `spreadArgs`](#spread-arguments-with-spreadargs)
|
|
54
55
|
- [Ad-hoc raw queries with `query()`](#ad-hoc-raw-queries-with-query)
|
|
55
56
|
- [Transactions](#transactions)
|
|
56
57
|
- [Utility types](#utility-types)
|
|
@@ -598,21 +599,54 @@ class UserRepository extends VSRepository<User, string> {
|
|
|
598
599
|
|
|
599
600
|
@QueryMethod('UPDATE "user" SET active = true WHERE id = $1', { modifying: true })
|
|
600
601
|
declare activateUser: (arg: QueryMethodArg<[id: string]>) => Promise<number>;
|
|
602
|
+
|
|
603
|
+
// Only one row is ever expected here, so `singleResult` collapses the
|
|
604
|
+
// array into a single object (or `null` when no row matches).
|
|
605
|
+
@QueryMethod('SELECT * FROM "user" WHERE id = $1 LIMIT 1', { singleResult: true })
|
|
606
|
+
declare findByIdRaw: (arg: QueryMethodArg<[id: string]>) => Promise<User | null>;
|
|
601
607
|
}
|
|
602
608
|
```
|
|
603
609
|
|
|
604
|
-
| Option
|
|
605
|
-
|
|
|
606
|
-
| `modifying`
|
|
610
|
+
| Option | Type | Default | Description |
|
|
611
|
+
| -------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
612
|
+
| `modifying` | `boolean` | `false` | When `true`, runs as `INSERT`/`UPDATE`/`DELETE` and the method resolves to the number of affected rows. When `false`, runs as a read query and resolves to the declared return type. |
|
|
613
|
+
| `singleResult` | `boolean` | `false` | When `true`, collapses an array result into its first element (`null` if empty), so you can declare the return type as a single object instead of an array. Has no effect on non-array results (e.g. a `modifying` query's affected-row count). |
|
|
607
614
|
|
|
608
615
|
Query methods accept `{ args, db? }` at the call site — `db` lets them participate in a `transaction()` block just like base and dynamic methods.
|
|
609
616
|
|
|
617
|
+
### Spread arguments with `spreadArgs`
|
|
618
|
+
|
|
619
|
+
By default, a `@QueryMethod` receives its placeholder values through a single `QueryMethodArg` object (`method({ args: [...] })`). Set `spreadArgs: true` to receive them as separate positional arguments instead, JpaRepository style:
|
|
620
|
+
|
|
621
|
+
```typescript
|
|
622
|
+
class UserRepository extends VSRepository<User, string> {
|
|
623
|
+
@QueryMethod('SELECT * FROM "user" WHERE email = $1 AND "userType" = $2', {
|
|
624
|
+
spreadArgs: true,
|
|
625
|
+
})
|
|
626
|
+
declare findByEmailAndType: (
|
|
627
|
+
...args: QueryArgs<[email: string, userType: string]>
|
|
628
|
+
) => Promise<User[]>;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const admins = await userRepository.findByEmailAndType("joao@email.com", "admin");
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
To run the query against a specific client or transaction instead of the repository's default one, pass `withDb(tx)` as the trailing argument — it wraps `tx` in a `DbArg`, which the resolver recognizes with `instanceof`, so it's never confused with a regular positional argument even if that argument happens to be an object:
|
|
635
|
+
|
|
636
|
+
```typescript
|
|
637
|
+
await userRepository.transaction(async (tx) => {
|
|
638
|
+
await userRepository.findByEmailAndType("joao@email.com", "admin", withDb(tx));
|
|
639
|
+
});
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
`spreadArgs` only affects `@QueryMethod`-declared fields — it's `false` by default, and calling a method declared without it using more than one argument throws, since the single-`QueryMethodArg` call style is expected instead. It has no effect on `query()`, which always accepts `{ args, db? }`.
|
|
643
|
+
|
|
610
644
|
### Ad-hoc raw queries with `query()`
|
|
611
645
|
|
|
612
646
|
For one-off raw SQL that doesn't warrant declaring a `@QueryMethod` on the repository class, call `query()` directly — it's available on every `VSRepository` instance and goes through the same adapter's `query()` implementation under the hood:
|
|
613
647
|
|
|
614
648
|
```typescript
|
|
615
|
-
query<T = any>(query: string, options?: { args?: any[]; db?: any; modifying?: boolean }): Promise<T>;
|
|
649
|
+
query<T = any>(query: string, options?: { args?: any[]; db?: any; modifying?: boolean; singleResult?: boolean }): Promise<T>;
|
|
616
650
|
```
|
|
617
651
|
|
|
618
652
|
```typescript
|
|
@@ -624,13 +658,21 @@ const affectedRows = await userRepository.query<number>(
|
|
|
624
658
|
'UPDATE "user" SET active = true WHERE id = $1',
|
|
625
659
|
{ args: ["123"], modifying: true },
|
|
626
660
|
);
|
|
661
|
+
|
|
662
|
+
// Only one row is ever expected here, so `singleResult` collapses the
|
|
663
|
+
// array into a single object (or `null` when no row matches).
|
|
664
|
+
const user = await userRepository.query<User | null>(
|
|
665
|
+
'SELECT * FROM "user" WHERE id = $1 LIMIT 1',
|
|
666
|
+
{ args: ["123"], singleResult: true },
|
|
667
|
+
);
|
|
627
668
|
```
|
|
628
669
|
|
|
629
|
-
| Option
|
|
630
|
-
|
|
|
631
|
-
| `args`
|
|
632
|
-
| `db`
|
|
633
|
-
| `modifying`
|
|
670
|
+
| Option | Type | Default | Description |
|
|
671
|
+
| -------------- | --------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
672
|
+
| `args` | `any[]` | `undefined` | Positional parameters injected into `$1`, `$2`, ... placeholders. Never interpolate values directly into the SQL string. |
|
|
673
|
+
| `db` | `any` | Repository's default client | Database client or transaction to run this query in. |
|
|
674
|
+
| `modifying` | `boolean` | `false` | When `true`, treats the statement as `INSERT`/`UPDATE`/`DELETE`. |
|
|
675
|
+
| `singleResult` | `boolean` | `false` | When `true`, collapses an array result into its first element (`null` if empty). Has no effect on non-array results (e.g. a `modifying` query's affected-row count). |
|
|
634
676
|
|
|
635
677
|
Just like base, dynamic and query methods, `query()` accepts `db` in `options` to participate in a `transaction()` block.
|
|
636
678
|
|
|
@@ -691,6 +733,7 @@ import type {
|
|
|
691
733
|
DeepPartial,
|
|
692
734
|
CountResult,
|
|
693
735
|
QueryMethodArg,
|
|
736
|
+
QueryArgs,
|
|
694
737
|
KeysOfType,
|
|
695
738
|
NumericKeys,
|
|
696
739
|
NumericLike,
|
|
@@ -713,6 +756,7 @@ import type {
|
|
|
713
756
|
| `DeepPartial<T>` | Recursively makes every property of `T` optional, including nested objects and array elements. | `save`, `saveList`, `patch`, `merge`, and every write method on `VSRepoAdapter`. |
|
|
714
757
|
| `CountResult` | `{ count: number }` — the shape returned by batch operations. | `removeList`, `softRemoveList`, `restoreList`, `createManyIgnoreConflicts`. |
|
|
715
758
|
| `QueryMethodArg<T>` | `{ args?: T, db? }` — positional SQL parameters (`$1`, `$2`, ...) and transaction client for `@QueryMethod`. | [Query methods (raw SQL)](#query-methods-raw-sql). |
|
|
759
|
+
| `QueryArgs<T, O>` | Types the spread parameter list of a `@QueryMethod` declared with `{ spreadArgs: true }`: `T`'s values in order, followed by an optional trailing `DbArg<O>` built via `withDb()`. | [Spread arguments with `spreadArgs`](#spread-arguments-with-spreadargs). |
|
|
716
760
|
| `KeysOfType<T, K>` | Extracts the keys of `T` whose value type is assignable to `K`. | Constrains `pkName` in [Constructor options](#constructor-options) to fields of the entity matching the configured primary-key type. |
|
|
717
761
|
| `NumericKeys<T>` | Extracts the keys of `T` whose (non-nullable) value type is assignable to `NumericLike`. Nullable numeric fields (`number \| null`) are included. | Constrains `field` in [Atomic and aggregate methods](#atomic-and-aggregate-methods) (`increment`, `sum`, etc). |
|
|
718
762
|
| `NumericLike` | `number \| bigint \| DecimalLike`. | [Atomic and aggregate methods](#atomic-and-aggregate-methods). |
|
|
@@ -932,7 +976,7 @@ try {
|
|
|
932
976
|
| ----------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
933
977
|
| `DECORATOR` | Invalid arguments were passed to `@DynamicMethod` or `@QueryMethod`. |
|
|
934
978
|
| `RESOLVER` | The library failed to resolve a dynamic/query method's configuration into a callable method (e.g. an unknown method name). |
|
|
935
|
-
| `DYNAMIC` | A resolved dynamic method failed at runtime (e.g. missing arguments). |
|
|
979
|
+
| `DYNAMIC` | A resolved dynamic/query method failed at runtime (e.g. missing arguments). |
|
|
936
980
|
| `VALIDATOR` | Invalid method options or arguments were detected during validation. |
|
|
937
981
|
| `BASE` | Invalid usage of a base method (`get`, `save`, `remove`, etc). |
|
|
938
982
|
| `ADAPTER` | A `VSRepoAdapter` failed while talking to the underlying ORM/database — always thrown as `VSRepoAdapterError`. |
|
package/README.pt-BR.md
CHANGED
|
@@ -51,6 +51,7 @@ O VSRepository permite criar repositories fortemente tipados com:
|
|
|
51
51
|
- [Ordenação, paginação e distinct](#ordenação-paginação-e-distinct)
|
|
52
52
|
- [Options do decorador](#options-do-decorador)
|
|
53
53
|
- [Query methods (SQL raw)](#query-methods-sql-raw)
|
|
54
|
+
- [Argumentos via spread com `spreadArgs`](#argumentos-via-spread-com-spreadargs)
|
|
54
55
|
- [Queries raw pontuais com `query()`](#queries-raw-pontuais-com-query)
|
|
55
56
|
- [Transações](#transações)
|
|
56
57
|
- [Tipos utilitários](#tipos-utilitários)
|
|
@@ -598,21 +599,54 @@ class UserRepository extends VSRepository<User, string> {
|
|
|
598
599
|
|
|
599
600
|
@QueryMethod('UPDATE "user" SET active = true WHERE id = $1', { modifying: true })
|
|
600
601
|
declare activateUser: (arg: QueryMethodArg<[id: string]>) => Promise<number>;
|
|
602
|
+
|
|
603
|
+
// Aqui só se espera uma linha, então `singleResult` transforma o array
|
|
604
|
+
// em um único objeto (ou `null` quando nenhuma linha corresponde).
|
|
605
|
+
@QueryMethod('SELECT * FROM "user" WHERE id = $1 LIMIT 1', { singleResult: true })
|
|
606
|
+
declare findByIdRaw: (arg: QueryMethodArg<[id: string]>) => Promise<User | null>;
|
|
601
607
|
}
|
|
602
608
|
```
|
|
603
609
|
|
|
604
|
-
| Option
|
|
605
|
-
|
|
|
606
|
-
| `modifying`
|
|
610
|
+
| Option | Tipo | Padrão | Descrição |
|
|
611
|
+
| -------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
612
|
+
| `modifying` | `boolean` | `false` | Quando `true`, executa como `INSERT`/`UPDATE`/`DELETE` e o método resolve para o número de linhas afetadas. Quando `false`, executa como query de leitura e resolve para o tipo de retorno declarado. |
|
|
613
|
+
| `singleResult` | `boolean` | `false` | Quando `true`, transforma um resultado em array no seu primeiro elemento (`null` se vazio), permitindo declarar o tipo de retorno como um objeto único em vez de array. Não tem efeito em resultados que não são array (ex.: o número de linhas afetadas de uma query `modifying`). |
|
|
607
614
|
|
|
608
615
|
Query methods aceitam `{ args, db? }` na chamada — `db` permite que participem de um bloco `transaction()`, assim como os métodos base e dinâmicos.
|
|
609
616
|
|
|
617
|
+
### Argumentos via spread com `spreadArgs`
|
|
618
|
+
|
|
619
|
+
Por padrão, um `@QueryMethod` recebe seus valores de placeholder através de um único objeto `QueryMethodArg` (`method({ args: [...] })`). Defina `spreadArgs: true` para recebê-los como argumentos posicionais separados, no estilo do JpaRepository:
|
|
620
|
+
|
|
621
|
+
```typescript
|
|
622
|
+
class UserRepository extends VSRepository<User, string> {
|
|
623
|
+
@QueryMethod('SELECT * FROM "user" WHERE email = $1 AND "userType" = $2', {
|
|
624
|
+
spreadArgs: true,
|
|
625
|
+
})
|
|
626
|
+
declare findByEmailAndType: (
|
|
627
|
+
...args: QueryArgs<[email: string, userType: string]>
|
|
628
|
+
) => Promise<User[]>;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const admins = await userRepository.findByEmailAndType("joao@email.com", "admin");
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
Para rodar a query com um client ou transação específico em vez do client padrão do repository, passe `withDb(tx)` como argumento final — ele embrulha `tx` em um `DbArg`, que o resolver reconhece via `instanceof`, então nunca é confundido com um argumento posicional comum, mesmo que esse argumento seja um objeto:
|
|
635
|
+
|
|
636
|
+
```typescript
|
|
637
|
+
await userRepository.transaction(async (tx) => {
|
|
638
|
+
await userRepository.findByEmailAndType("joao@email.com", "admin", withDb(tx));
|
|
639
|
+
});
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
`spreadArgs` afeta apenas campos declarados com `@QueryMethod` — o padrão é `false`, e chamar um método declarado sem essa opção usando mais de um argumento lança erro, já que se espera o estilo de chamada com um único `QueryMethodArg`. Não tem efeito sobre `query()`, que sempre aceita `{ args, db? }`.
|
|
643
|
+
|
|
610
644
|
### Queries raw pontuais com `query()`
|
|
611
645
|
|
|
612
646
|
Para SQL raw pontual que não justifica declarar um `@QueryMethod` na classe do repository, chame `query()` diretamente — ele está disponível em toda instância de `VSRepository` e passa pela mesma implementação de `query()` do adapter por baixo dos panos:
|
|
613
647
|
|
|
614
648
|
```typescript
|
|
615
|
-
query<T = any>(query: string, options?: { args?: any[]; db?: any; modifying?: boolean }): Promise<T>;
|
|
649
|
+
query<T = any>(query: string, options?: { args?: any[]; db?: any; modifying?: boolean; singleResult?: boolean }): Promise<T>;
|
|
616
650
|
```
|
|
617
651
|
|
|
618
652
|
```typescript
|
|
@@ -624,13 +658,21 @@ const linhasAfetadas = await userRepository.query<number>(
|
|
|
624
658
|
'UPDATE "user" SET active = true WHERE id = $1',
|
|
625
659
|
{ args: ["123"], modifying: true },
|
|
626
660
|
);
|
|
661
|
+
|
|
662
|
+
// Aqui só se espera uma linha, então `singleResult` transforma o array
|
|
663
|
+
// em um único objeto (ou `null` quando nenhuma linha corresponde).
|
|
664
|
+
const user = await userRepository.query<User | null>(
|
|
665
|
+
'SELECT * FROM "user" WHERE id = $1 LIMIT 1',
|
|
666
|
+
{ args: ["123"], singleResult: true },
|
|
667
|
+
);
|
|
627
668
|
```
|
|
628
669
|
|
|
629
|
-
| Option
|
|
630
|
-
|
|
|
631
|
-
| `args`
|
|
632
|
-
| `db`
|
|
633
|
-
| `modifying`
|
|
670
|
+
| Option | Tipo | Padrão | Descrição |
|
|
671
|
+
| -------------- | --------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
672
|
+
| `args` | `any[]` | `undefined` | Parâmetros posicionais injetados nos placeholders `$1`, `$2`, ... Nunca interpole valores diretamente na string SQL. |
|
|
673
|
+
| `db` | `any` | Client padrão do repository | Client ou transação do banco em que essa query deve rodar. |
|
|
674
|
+
| `modifying` | `boolean` | `false` | Quando `true`, trata a instrução como `INSERT`/`UPDATE`/`DELETE`. |
|
|
675
|
+
| `singleResult` | `boolean` | `false` | Quando `true`, transforma um resultado em array no seu primeiro elemento (`null` se vazio). Não tem efeito em resultados que não são array (ex.: o número de linhas afetadas de uma query `modifying`). |
|
|
634
676
|
|
|
635
677
|
Assim como os métodos base, dinâmicos e query, `query()` aceita `db` em `options` para participar de um bloco `transaction()`.
|
|
636
678
|
|
|
@@ -694,6 +736,7 @@ import type {
|
|
|
694
736
|
DeepPartial,
|
|
695
737
|
CountResult,
|
|
696
738
|
QueryMethodArg,
|
|
739
|
+
QueryArgs,
|
|
697
740
|
KeysOfType,
|
|
698
741
|
NumericKeys,
|
|
699
742
|
NumericLike,
|
|
@@ -716,6 +759,7 @@ import type {
|
|
|
716
759
|
| `DeepPartial<T>` | Torna todas as propriedades de `T` opcionais recursivamente, incluindo objetos aninhados e elementos de array. | `save`, `saveList`, `patch`, `merge`, e todo método de escrita do `VSRepoAdapter`. |
|
|
717
760
|
| `CountResult` | `{ count: number }` — o formato retornado por operações em lote. | `removeList`, `softRemoveList`, `restoreList`, `createManyIgnoreConflicts`. |
|
|
718
761
|
| `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). |
|
|
762
|
+
| `QueryArgs<T, O>` | Tipa a lista de parâmetros via spread de um `@QueryMethod` declarado com `{ spreadArgs: true }`: os valores de `T`, em ordem, seguidos de um `DbArg<O>` opcional construído via `withDb()`. | [Argumentos via spread com `spreadArgs`](#argumentos-via-spread-com-spreadargs). |
|
|
719
763
|
| `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
764
|
| `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
765
|
| `NumericLike` | `number \| bigint \| DecimalLike`. | [Métodos atômicos e de agregação](#métodos-atômicos-e-de-agregação). |
|
|
@@ -935,7 +979,7 @@ try {
|
|
|
935
979
|
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
936
980
|
| `DECORATOR` | Argumentos inválidos foram passados para `@DynamicMethod` ou `@QueryMethod`. |
|
|
937
981
|
| `RESOLVER` | A biblioteca falhou ao resolver a configuração de um método dinâmico/de query em um método chamável (ex.: um nome de método desconhecido). |
|
|
938
|
-
| `DYNAMIC` | Um
|
|
982
|
+
| `DYNAMIC` | Um dynamic/query method já resolvido falhou em tempo de execução (ex.: argumentos faltando). |
|
|
939
983
|
| `VALIDATOR` | Options ou argumentos de método inválidos foram detectados durante a validação. |
|
|
940
984
|
| `BASE` | Uso inválido de um método base (`get`, `save`, `remove`, etc). |
|
|
941
985
|
| `ADAPTER` | Um `VSRepoAdapter` falhou ao falar com o ORM/banco subjacente — sempre é lançado como `VSRepoAdapterError`. |
|
package/dist/VSRepository.d.ts
CHANGED
|
@@ -79,6 +79,8 @@ export declare abstract class VSRepository<Entity, PKType, OrmTypes extends VSRe
|
|
|
79
79
|
* Use `$1`, `$2`, ... placeholders for values passed via `options.args` —
|
|
80
80
|
* never interpolate values directly into `query`, to avoid SQL injection.
|
|
81
81
|
* Set `options.modifying: true` for `INSERT`/`UPDATE`/`DELETE` statements.
|
|
82
|
+
* Set `options.singleResult: true` to collapse an array result into its
|
|
83
|
+
* first element (`null` if empty) — see {@link VSRepoQueryOptions.singleResult}.
|
|
82
84
|
*
|
|
83
85
|
* @example
|
|
84
86
|
* ```typescript
|
|
@@ -91,6 +93,13 @@ export declare abstract class VSRepository<Entity, PKType, OrmTypes extends VSRe
|
|
|
91
93
|
* 'UPDATE "user" SET active = true WHERE id = $1',
|
|
92
94
|
* { args: ["123"], modifying: true },
|
|
93
95
|
* );
|
|
96
|
+
*
|
|
97
|
+
* // Only one row is ever expected here, so `singleResult` collapses the
|
|
98
|
+
* // array into a single object (or `null` when no row matches).
|
|
99
|
+
* const user = await userRepository.query<User | null>(
|
|
100
|
+
* 'SELECT * FROM "user" WHERE id = $1 LIMIT 1',
|
|
101
|
+
* { args: ["123"], singleResult: true },
|
|
102
|
+
* );
|
|
94
103
|
* ```
|
|
95
104
|
*/
|
|
96
105
|
query<T = any>(query: string, options?: VSRepoQueryOptions<OrmTypes>): Promise<T>;
|
package/dist/VSRepository.js
CHANGED
|
@@ -141,6 +141,8 @@ class VSRepository {
|
|
|
141
141
|
* Use `$1`, `$2`, ... placeholders for values passed via `options.args` —
|
|
142
142
|
* never interpolate values directly into `query`, to avoid SQL injection.
|
|
143
143
|
* Set `options.modifying: true` for `INSERT`/`UPDATE`/`DELETE` statements.
|
|
144
|
+
* Set `options.singleResult: true` to collapse an array result into its
|
|
145
|
+
* first element (`null` if empty) — see {@link VSRepoQueryOptions.singleResult}.
|
|
144
146
|
*
|
|
145
147
|
* @example
|
|
146
148
|
* ```typescript
|
|
@@ -153,6 +155,13 @@ class VSRepository {
|
|
|
153
155
|
* 'UPDATE "user" SET active = true WHERE id = $1',
|
|
154
156
|
* { args: ["123"], modifying: true },
|
|
155
157
|
* );
|
|
158
|
+
*
|
|
159
|
+
* // Only one row is ever expected here, so `singleResult` collapses the
|
|
160
|
+
* // array into a single object (or `null` when no row matches).
|
|
161
|
+
* const user = await userRepository.query<User | null>(
|
|
162
|
+
* 'SELECT * FROM "user" WHERE id = $1 LIMIT 1',
|
|
163
|
+
* { args: ["123"], singleResult: true },
|
|
164
|
+
* );
|
|
156
165
|
* ```
|
|
157
166
|
*/
|
|
158
167
|
async query(query, options) {
|
|
@@ -168,8 +177,11 @@ class VSRepository {
|
|
|
168
177
|
db: optionsValidated.db,
|
|
169
178
|
modifying: optionsValidated.modifying ?? false,
|
|
170
179
|
});
|
|
180
|
+
const resolved = optionsValidated.singleResult && Array.isArray(result)
|
|
181
|
+
? (result[0] ?? null)
|
|
182
|
+
: result;
|
|
171
183
|
this.logger.endPerformLog(start);
|
|
172
|
-
return
|
|
184
|
+
return resolved;
|
|
173
185
|
}
|
|
174
186
|
catch (err) {
|
|
175
187
|
this.logger.endPerformLog(start);
|
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
import { QueryMethodOptions } from "../types/decorators/query-method-options.type";
|
|
1
|
+
import type { QueryMethodOptions } from "../types/decorators/query-method-options.type";
|
|
2
2
|
/**
|
|
3
3
|
* Property decorator used to declare a raw SQL query method on a `VSRepository`
|
|
4
4
|
* subclass, bypassing name-based method parsing entirely.
|
|
5
5
|
*
|
|
6
6
|
* Applied to a `declare` class field, it executes `value` directly through the
|
|
7
7
|
* adapter's `query()` method, with parameters injected positionally via the
|
|
8
|
-
* `args` array passed at the call site (`$1`, `$2`, ... placeholders)
|
|
8
|
+
* `args` array passed at the call site (`$1`, `$2`, ... placeholders) — or,
|
|
9
|
+
* with `spreadArgs: true`, via separate positional arguments instead.
|
|
9
10
|
*
|
|
10
11
|
* @param value Raw SQL statement to execute. Use `$1`, `$2`, ... placeholders for
|
|
11
12
|
* the values that will be passed via `args` — never interpolate values directly into `value`.
|
|
12
|
-
* @param options Optional configuration; set `modifying: true` for `INSERT`/`UPDATE`/`DELETE` statements
|
|
13
|
+
* @param options Optional configuration; set `modifying: true` for `INSERT`/`UPDATE`/`DELETE` statements,
|
|
14
|
+
* `singleResult: true` to collapse an array result into its first element, and
|
|
15
|
+
* `spreadArgs: true` to receive placeholder values as separate arguments instead of a
|
|
16
|
+
* single `QueryMethodArg` object — see {@link QueryMethodOptions.singleResult} and
|
|
17
|
+
* {@link QueryMethodOptions.spreadArgs}.
|
|
13
18
|
*
|
|
14
19
|
* @example
|
|
15
20
|
* ```typescript
|
|
@@ -19,7 +24,25 @@ import { QueryMethodOptions } from "../types/decorators/query-method-options.typ
|
|
|
19
24
|
*
|
|
20
25
|
* @QueryMethod('UPDATE "user" SET active = true WHERE id = $1', { modifying: true })
|
|
21
26
|
* declare activateUser: (arg: QueryMethodArg<[id: string]>) => Promise<number>;
|
|
27
|
+
*
|
|
28
|
+
* // Only one row is ever expected here, so `singleResult` collapses the
|
|
29
|
+
* // array into a single object (or `null` when no row matches).
|
|
30
|
+
* @QueryMethod('SELECT * FROM "user" WHERE id = $1 LIMIT 1', { singleResult: true })
|
|
31
|
+
* declare findByIdRaw: (arg: QueryMethodArg<[id: string]>) => Promise<User | null>;
|
|
32
|
+
*
|
|
33
|
+
* // `spreadArgs: true` takes placeholder values as separate arguments,
|
|
34
|
+
* // JpaRepository style, instead of a single `{ args: [...] }` object.
|
|
35
|
+
* // An optional trailing `withDb(tx)` runs the query in a transaction.
|
|
36
|
+
* @QueryMethod('SELECT * FROM "user" WHERE email = $1 AND "userType" = $2', {
|
|
37
|
+
* spreadArgs: true,
|
|
38
|
+
* })
|
|
39
|
+
* declare findByEmailAndType: (
|
|
40
|
+
* ...args: QueryArgs<[email: string, userType: string]>
|
|
41
|
+
* ) => Promise<User[]>;
|
|
22
42
|
* }
|
|
43
|
+
*
|
|
44
|
+
* await userRepository.findByEmailAndType("joao@email.com", "admin");
|
|
45
|
+
* await userRepository.findByEmailAndType("joao@email.com", "admin", withDb(tx));
|
|
23
46
|
* ```
|
|
24
47
|
*
|
|
25
48
|
* @publicApi
|
|
@@ -11,11 +11,16 @@ const decorators_validator_1 = require("../internal/validators/decorators.valida
|
|
|
11
11
|
*
|
|
12
12
|
* Applied to a `declare` class field, it executes `value` directly through the
|
|
13
13
|
* adapter's `query()` method, with parameters injected positionally via the
|
|
14
|
-
* `args` array passed at the call site (`$1`, `$2`, ... placeholders)
|
|
14
|
+
* `args` array passed at the call site (`$1`, `$2`, ... placeholders) — or,
|
|
15
|
+
* with `spreadArgs: true`, via separate positional arguments instead.
|
|
15
16
|
*
|
|
16
17
|
* @param value Raw SQL statement to execute. Use `$1`, `$2`, ... placeholders for
|
|
17
18
|
* the values that will be passed via `args` — never interpolate values directly into `value`.
|
|
18
|
-
* @param options Optional configuration; set `modifying: true` for `INSERT`/`UPDATE`/`DELETE` statements
|
|
19
|
+
* @param options Optional configuration; set `modifying: true` for `INSERT`/`UPDATE`/`DELETE` statements,
|
|
20
|
+
* `singleResult: true` to collapse an array result into its first element, and
|
|
21
|
+
* `spreadArgs: true` to receive placeholder values as separate arguments instead of a
|
|
22
|
+
* single `QueryMethodArg` object — see {@link QueryMethodOptions.singleResult} and
|
|
23
|
+
* {@link QueryMethodOptions.spreadArgs}.
|
|
19
24
|
*
|
|
20
25
|
* @example
|
|
21
26
|
* ```typescript
|
|
@@ -25,7 +30,25 @@ const decorators_validator_1 = require("../internal/validators/decorators.valida
|
|
|
25
30
|
*
|
|
26
31
|
* @QueryMethod('UPDATE "user" SET active = true WHERE id = $1', { modifying: true })
|
|
27
32
|
* declare activateUser: (arg: QueryMethodArg<[id: string]>) => Promise<number>;
|
|
33
|
+
*
|
|
34
|
+
* // Only one row is ever expected here, so `singleResult` collapses the
|
|
35
|
+
* // array into a single object (or `null` when no row matches).
|
|
36
|
+
* @QueryMethod('SELECT * FROM "user" WHERE id = $1 LIMIT 1', { singleResult: true })
|
|
37
|
+
* declare findByIdRaw: (arg: QueryMethodArg<[id: string]>) => Promise<User | null>;
|
|
38
|
+
*
|
|
39
|
+
* // `spreadArgs: true` takes placeholder values as separate arguments,
|
|
40
|
+
* // JpaRepository style, instead of a single `{ args: [...] }` object.
|
|
41
|
+
* // An optional trailing `withDb(tx)` runs the query in a transaction.
|
|
42
|
+
* @QueryMethod('SELECT * FROM "user" WHERE email = $1 AND "userType" = $2', {
|
|
43
|
+
* spreadArgs: true,
|
|
44
|
+
* })
|
|
45
|
+
* declare findByEmailAndType: (
|
|
46
|
+
* ...args: QueryArgs<[email: string, userType: string]>
|
|
47
|
+
* ) => Promise<User[]>;
|
|
28
48
|
* }
|
|
49
|
+
*
|
|
50
|
+
* await userRepository.findByEmailAndType("joao@email.com", "admin");
|
|
51
|
+
* await userRepository.findByEmailAndType("joao@email.com", "admin", withDb(tx));
|
|
29
52
|
* ```
|
|
30
53
|
*
|
|
31
54
|
* @publicApi
|
package/dist/index.d.ts
CHANGED
|
@@ -3,12 +3,14 @@ export { VSRepository } from "./VSRepository.js";
|
|
|
3
3
|
export { VSRepoAdapter } from "./VSRepoAdapter.js";
|
|
4
4
|
export { VSRepoError } from "./errors/VSRepoError.js";
|
|
5
5
|
export { VSRepoAdapterError } from "./errors/VSRepoAdapterError.js";
|
|
6
|
+
export { DbArg } from "./internal/utils/db-arg.util.js";
|
|
6
7
|
export { DynamicMethod } from "./decorators/dynamic-method.decorator.js";
|
|
7
8
|
export { QueryMethod } from "./decorators/query-method.decorator.js";
|
|
8
9
|
export { VSRepoErrorType } from "./internal/enums/vsrepo-error-type.enum.js";
|
|
9
10
|
export { VSLogLevel } from "./internal/enums/vs-log-level.enum.js";
|
|
10
11
|
export { TransactionIsolationLevel } from "./internal/enums/transaction-isolation-level.enum.js";
|
|
11
12
|
export { AdapterErrorCode } from "./internal/enums/adapter-error-code.enum.js";
|
|
13
|
+
export { withDb } from "./internal/utils/with-db.util.js";
|
|
12
14
|
export type { VSRepoOptions } from "./types/vsrepo/vsrepo-options.type.js";
|
|
13
15
|
export type { VSRepoOrmTypes } from "./types/vsrepo/vsrepo-orm-types.type.js";
|
|
14
16
|
export type { VSRepoArgs } from "./types/vsrepo/vsrepo-args.type.js";
|
|
@@ -35,4 +37,5 @@ export type { DecimalLike } from "./types/utils/decimal-like.type.js";
|
|
|
35
37
|
export type { NumericKeys } from "./types/utils/numeric-keys.type.js";
|
|
36
38
|
export type { NumericLike } from "./types/utils/numeric-like.type.js";
|
|
37
39
|
export type { RestrictMethodOptions } from "./types/utils/restrict-method-options.type.js";
|
|
40
|
+
export type { QueryArgs } from "./types/utils/query-args.type.js";
|
|
38
41
|
export { VSLogger } from "./internal/utils/vs-logger.util.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.VSLogger = exports.AdapterErrorCode = exports.TransactionIsolationLevel = exports.VSLogLevel = exports.VSRepoErrorType = exports.QueryMethod = exports.DynamicMethod = exports.VSRepoAdapterError = exports.VSRepoError = exports.VSRepoAdapter = exports.VSRepository = void 0;
|
|
3
|
+
exports.VSLogger = exports.withDb = exports.AdapterErrorCode = exports.TransactionIsolationLevel = exports.VSLogLevel = exports.VSRepoErrorType = exports.QueryMethod = exports.DynamicMethod = exports.DbArg = exports.VSRepoAdapterError = exports.VSRepoError = exports.VSRepoAdapter = exports.VSRepository = void 0;
|
|
4
4
|
require("reflect-metadata");
|
|
5
5
|
// Public classes / constructors
|
|
6
6
|
var VSRepository_js_1 = require("./VSRepository.js");
|
|
@@ -11,6 +11,8 @@ var VSRepoError_js_1 = require("./errors/VSRepoError.js");
|
|
|
11
11
|
Object.defineProperty(exports, "VSRepoError", { enumerable: true, get: function () { return VSRepoError_js_1.VSRepoError; } });
|
|
12
12
|
var VSRepoAdapterError_js_1 = require("./errors/VSRepoAdapterError.js");
|
|
13
13
|
Object.defineProperty(exports, "VSRepoAdapterError", { enumerable: true, get: function () { return VSRepoAdapterError_js_1.VSRepoAdapterError; } });
|
|
14
|
+
var db_arg_util_js_1 = require("./internal/utils/db-arg.util.js");
|
|
15
|
+
Object.defineProperty(exports, "DbArg", { enumerable: true, get: function () { return db_arg_util_js_1.DbArg; } });
|
|
14
16
|
// Decorators
|
|
15
17
|
var dynamic_method_decorator_js_1 = require("./decorators/dynamic-method.decorator.js");
|
|
16
18
|
Object.defineProperty(exports, "DynamicMethod", { enumerable: true, get: function () { return dynamic_method_decorator_js_1.DynamicMethod; } });
|
|
@@ -25,6 +27,9 @@ var transaction_isolation_level_enum_js_1 = require("./internal/enums/transactio
|
|
|
25
27
|
Object.defineProperty(exports, "TransactionIsolationLevel", { enumerable: true, get: function () { return transaction_isolation_level_enum_js_1.TransactionIsolationLevel; } });
|
|
26
28
|
var adapter_error_code_enum_js_1 = require("./internal/enums/adapter-error-code.enum.js");
|
|
27
29
|
Object.defineProperty(exports, "AdapterErrorCode", { enumerable: true, get: function () { return adapter_error_code_enum_js_1.AdapterErrorCode; } });
|
|
30
|
+
// Public functions
|
|
31
|
+
var with_db_util_js_1 = require("./internal/utils/with-db.util.js");
|
|
32
|
+
Object.defineProperty(exports, "withDb", { enumerable: true, get: function () { return with_db_util_js_1.withDb; } });
|
|
28
33
|
// Internal features
|
|
29
34
|
var vs_logger_util_js_1 = require("./internal/utils/vs-logger.util.js");
|
|
30
35
|
Object.defineProperty(exports, "VSLogger", { enumerable: true, get: function () { return vs_logger_util_js_1.VSLogger; } });
|
|
@@ -8,7 +8,7 @@ export declare enum VSRepoErrorType {
|
|
|
8
8
|
DECORATOR = "DECORATOR",
|
|
9
9
|
/** Failure while resolving a dynamic or query method's configuration into a callable method. */
|
|
10
10
|
RESOLVER = "RESOLVER",
|
|
11
|
-
/** Failure while executing a resolved dynamic method at runtime. */
|
|
11
|
+
/** Failure while executing a resolved dynamic/query method at runtime. */
|
|
12
12
|
DYNAMIC = "DYNAMIC",
|
|
13
13
|
/** Invalid method options or arguments detected during validation. */
|
|
14
14
|
VALIDATOR = "VALIDATOR",
|
|
@@ -12,7 +12,7 @@ var VSRepoErrorType;
|
|
|
12
12
|
VSRepoErrorType["DECORATOR"] = "DECORATOR";
|
|
13
13
|
/** Failure while resolving a dynamic or query method's configuration into a callable method. */
|
|
14
14
|
VSRepoErrorType["RESOLVER"] = "RESOLVER";
|
|
15
|
-
/** Failure while executing a resolved dynamic method at runtime. */
|
|
15
|
+
/** Failure while executing a resolved dynamic/query method at runtime. */
|
|
16
16
|
VSRepoErrorType["DYNAMIC"] = "DYNAMIC";
|
|
17
17
|
/** Invalid method options or arguments detected during validation. */
|
|
18
18
|
VSRepoErrorType["VALIDATOR"] = "VALIDATOR";
|
|
@@ -12,6 +12,7 @@ const uncapitalize_util_1 = require("../utils/uncapitalize.util");
|
|
|
12
12
|
const deepmerge_1 = __importDefault(require("deepmerge"));
|
|
13
13
|
const vsrepo_error_type_enum_1 = require("../enums/vsrepo-error-type.enum");
|
|
14
14
|
const debug_arg_symbol_constant_1 = require("../constants/debug-arg-symbol.constant");
|
|
15
|
+
const db_arg_util_1 = require("../utils/db-arg.util");
|
|
15
16
|
class DynamicMethodsResolver {
|
|
16
17
|
logger;
|
|
17
18
|
adapter;
|
|
@@ -880,19 +881,44 @@ class DynamicMethodsResolver {
|
|
|
880
881
|
this.logger.logDebug(`Resolving ${queryMethods.length} query method(s):`, queryMethods);
|
|
881
882
|
for (const method of queryMethods) {
|
|
882
883
|
const originalKey = method.propertyKey;
|
|
883
|
-
const modifyingQueryMethod = method.modifying;
|
|
884
|
+
const modifyingQueryMethod = method.modifying ?? false;
|
|
884
885
|
const valueQueryMethod = method.value;
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
886
|
+
const singleResult = method.singleResult;
|
|
887
|
+
const spreadArgsMode = method.spreadArgs;
|
|
888
|
+
instance[originalKey] = async (...args) => {
|
|
889
|
+
let db;
|
|
890
|
+
let queryArgs;
|
|
891
|
+
if (spreadArgsMode) {
|
|
892
|
+
const dbPos = args.at(-1);
|
|
893
|
+
if (dbPos instanceof db_arg_util_1.DbArg) {
|
|
894
|
+
db = dbPos.getDb();
|
|
895
|
+
queryArgs = args.slice(0, -1);
|
|
896
|
+
}
|
|
897
|
+
else {
|
|
898
|
+
queryArgs = args;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
else {
|
|
902
|
+
if (args.length > 1) {
|
|
903
|
+
const errorMessage = `This query method was declared without spreadArgs = true, use a single QueryMethodArg instead`;
|
|
904
|
+
this.logger.logError(`Cannot run '${String(originalKey)}': ${errorMessage}`);
|
|
905
|
+
throw new VSRepoError_1.VSRepoError(errorMessage, vsrepo_error_type_enum_1.VSRepoErrorType.DYNAMIC);
|
|
906
|
+
}
|
|
907
|
+
const queryArgValidated = this.validator.validateQueryMethodArg(args[0]);
|
|
908
|
+
db = queryArgValidated.db;
|
|
909
|
+
queryArgs = queryArgValidated.args;
|
|
910
|
+
}
|
|
911
|
+
db ??= this.adapter.getDbClient();
|
|
888
912
|
const start = this.logger.startPerformLog(`run ${String(originalKey)} (Modifying: ${modifyingQueryMethod})`);
|
|
889
913
|
try {
|
|
890
914
|
const result = await this.adapter.query(valueQueryMethod, {
|
|
891
|
-
|
|
915
|
+
db,
|
|
916
|
+
args: queryArgs,
|
|
892
917
|
modifying: modifyingQueryMethod,
|
|
893
918
|
});
|
|
919
|
+
const resolved = singleResult && Array.isArray(result) ? (result[0] ?? null) : result;
|
|
894
920
|
this.logger.endPerformLog(start);
|
|
895
|
-
return
|
|
921
|
+
return resolved;
|
|
896
922
|
}
|
|
897
923
|
catch (err) {
|
|
898
924
|
this.logger.endPerformLog(start);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { VSRepoOrmTypes } from "../../types/vsrepo/vsrepo-orm-types.type";
|
|
2
|
+
/**
|
|
3
|
+
* Wraps a database client or transaction so it can be recognized, at
|
|
4
|
+
* runtime, as the trailing `db` override in a {@link QueryArgs} spread call —
|
|
5
|
+
* as opposed to a regular positional query argument. Build one with
|
|
6
|
+
* {@link withDb} rather than constructing it directly.
|
|
7
|
+
*
|
|
8
|
+
* @publicApi
|
|
9
|
+
*/
|
|
10
|
+
export declare class DbArg<T extends VSRepoOrmTypes = VSRepoOrmTypes> {
|
|
11
|
+
private readonly db;
|
|
12
|
+
constructor(db: T["dbClient"] | T["dbTransaction"]);
|
|
13
|
+
/** Returns the wrapped database client or transaction. */
|
|
14
|
+
getDb(): T["dbClient"] | T["dbTransaction"];
|
|
15
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DbArg = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Wraps a database client or transaction so it can be recognized, at
|
|
6
|
+
* runtime, as the trailing `db` override in a {@link QueryArgs} spread call —
|
|
7
|
+
* as opposed to a regular positional query argument. Build one with
|
|
8
|
+
* {@link withDb} rather than constructing it directly.
|
|
9
|
+
*
|
|
10
|
+
* @publicApi
|
|
11
|
+
*/
|
|
12
|
+
class DbArg {
|
|
13
|
+
db;
|
|
14
|
+
constructor(db) {
|
|
15
|
+
this.db = db;
|
|
16
|
+
}
|
|
17
|
+
/** Returns the wrapped database client or transaction. */
|
|
18
|
+
getDb() {
|
|
19
|
+
return this.db;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.DbArg = DbArg;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { VSRepoOrmTypes } from "../../types/vsrepo/vsrepo-orm-types.type";
|
|
2
|
+
import { DbArg } from "./db-arg.util";
|
|
3
|
+
/**
|
|
4
|
+
* Wraps a database client or transaction so it can be passed as the
|
|
5
|
+
* trailing argument of a `@QueryMethod` declared with `{ spreadArgs: true }`,
|
|
6
|
+
* running that call against `db` instead of the repository's default
|
|
7
|
+
* client — the same role `{ db }` plays in the single-object
|
|
8
|
+
* `QueryMethodArg` call style.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* await userRepository.transaction(async (tx) => {
|
|
13
|
+
* await userRepository.findByEmailAndType("joao@email.com", "admin", withDb(tx));
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @publicApi
|
|
18
|
+
*/
|
|
19
|
+
export declare function withDb<T extends VSRepoOrmTypes = VSRepoOrmTypes>(db: T["dbClient"] | T["dbTransaction"]): DbArg<T>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withDb = withDb;
|
|
4
|
+
const db_arg_util_1 = require("./db-arg.util");
|
|
5
|
+
/**
|
|
6
|
+
* Wraps a database client or transaction so it can be passed as the
|
|
7
|
+
* trailing argument of a `@QueryMethod` declared with `{ spreadArgs: true }`,
|
|
8
|
+
* running that call against `db` instead of the repository's default
|
|
9
|
+
* client — the same role `{ db }` plays in the single-object
|
|
10
|
+
* `QueryMethodArg` call style.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* await userRepository.transaction(async (tx) => {
|
|
15
|
+
* await userRepository.findByEmailAndType("joao@email.com", "admin", withDb(tx));
|
|
16
|
+
* });
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @publicApi
|
|
20
|
+
*/
|
|
21
|
+
function withDb(db) {
|
|
22
|
+
return new db_arg_util_1.DbArg(db);
|
|
23
|
+
}
|
|
@@ -59,6 +59,8 @@ class DecoratorsValidator {
|
|
|
59
59
|
}
|
|
60
60
|
static queryMethodOptionsSchema = v.object({
|
|
61
61
|
modifying: v.optional(v.boolean(), false),
|
|
62
|
+
singleResult: v.optional(v.boolean()),
|
|
63
|
+
spreadArgs: v.optional(v.boolean()),
|
|
62
64
|
});
|
|
63
65
|
static validateQueryMethodOptions(options) {
|
|
64
66
|
const parsed = v.safeParse(this.queryMethodOptionsSchema, options);
|
|
@@ -26,7 +26,7 @@ export declare class VSRepoValidator<T, K, O extends VSRepoOrmTypes = VSRepoOrmT
|
|
|
26
26
|
order?: Ordering<T>;
|
|
27
27
|
};
|
|
28
28
|
private queryArgSchema;
|
|
29
|
-
validateQueryMethodArg(arg?: unknown): QueryMethodArg<any>;
|
|
29
|
+
validateQueryMethodArg(arg?: unknown): QueryMethodArg<any[]>;
|
|
30
30
|
private queryOptionsSchema;
|
|
31
31
|
validateQueryOptions(options?: unknown): VSRepoQueryOptions;
|
|
32
32
|
private transactionOptionsSchema;
|
|
@@ -127,6 +127,7 @@ class VSRepoValidator {
|
|
|
127
127
|
args: v.optional(v.array(v.any())),
|
|
128
128
|
db: v.optional(v.any()),
|
|
129
129
|
modifying: v.optional(v.boolean()),
|
|
130
|
+
singleResult: v.optional(v.boolean()),
|
|
130
131
|
});
|
|
131
132
|
validateQueryOptions(options) {
|
|
132
133
|
const parsed = v.safeParse(this.queryOptionsSchema, options ?? {});
|
|
@@ -11,5 +11,39 @@ export type QueryMethodOptions = {
|
|
|
11
11
|
* to whatever return type is declared on the field.
|
|
12
12
|
* @default false
|
|
13
13
|
*/
|
|
14
|
-
modifying
|
|
14
|
+
modifying?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* When `true`, the array returned by the underlying query is collapsed
|
|
17
|
+
* into its first element (`null` if the array is empty) before
|
|
18
|
+
* being resolved to the caller. Has no effect when the query resolves
|
|
19
|
+
* to something other than an array (e.g. a `modifying` query's
|
|
20
|
+
* affected-row count).
|
|
21
|
+
*
|
|
22
|
+
* Useful for queries you already know return at most one row (e.g. a
|
|
23
|
+
* `SELECT ... LIMIT 1` or a lookup by a unique column), where declaring
|
|
24
|
+
* the return type as an array would be misleading.
|
|
25
|
+
*
|
|
26
|
+
* @default false
|
|
27
|
+
*/
|
|
28
|
+
singleResult?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* When `true`, the decorated method receives its SQL placeholder values
|
|
31
|
+
* as separate positional arguments (`method(a, b, c)`) instead of a
|
|
32
|
+
* single {@link QueryMethodArg} object (`method({ args: [a, b, c] })`).
|
|
33
|
+
* Type the declared field's parameters with {@link QueryArgs} to get
|
|
34
|
+
* autocompletion and arity checking for this call style.
|
|
35
|
+
*
|
|
36
|
+
* To run the query against a specific client or transaction — instead
|
|
37
|
+
* of the repository's default one — pass {@link DbArg} (built via
|
|
38
|
+
* {@link withDb}) as the trailing argument: `method(a, b, withDb(tx))`.
|
|
39
|
+
* It's recognized by `instanceof`, so it never collides with a regular
|
|
40
|
+
* positional argument, even one that happens to be an object.
|
|
41
|
+
*
|
|
42
|
+
* When `false` (the default), calling the method with more than one
|
|
43
|
+
* argument throws, since it expects the single-object `QueryMethodArg`
|
|
44
|
+
* call style instead.
|
|
45
|
+
*
|
|
46
|
+
* @default false
|
|
47
|
+
*/
|
|
48
|
+
spreadArgs?: boolean;
|
|
15
49
|
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { DbArg } from "../../internal/utils/db-arg.util";
|
|
2
|
+
import { VSRepoOrmTypes } from "../vsrepo/vsrepo-orm-types.type";
|
|
3
|
+
/**
|
|
4
|
+
* Types the parameter list of a `@QueryMethod` declared with
|
|
5
|
+
* `{ spreadArgs: true }`: the SQL placeholder values (`T`), in order,
|
|
6
|
+
* followed by an optional trailing {@link DbArg} — built via {@link withDb} —
|
|
7
|
+
* to run the query against a specific client or transaction instead of the
|
|
8
|
+
* repository's default one.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* class UserRepository extends VSRepository<User, string> {
|
|
13
|
+
* @QueryMethod('SELECT * FROM "user" WHERE email = $1 AND "userType" = $2', {
|
|
14
|
+
* spreadArgs: true,
|
|
15
|
+
* })
|
|
16
|
+
* declare findByEmailAndType: (
|
|
17
|
+
* ...args: QueryArgs<[email: string, userType: string]>
|
|
18
|
+
* ) => Promise<User[]>;
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* await userRepository.findByEmailAndType("joao@email.com", "admin");
|
|
22
|
+
* await userRepository.findByEmailAndType("joao@email.com", "admin", withDb(tx));
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @publicApi
|
|
26
|
+
*/
|
|
27
|
+
export type QueryArgs<T extends Array<any> = [], O extends VSRepoOrmTypes = VSRepoOrmTypes> = [
|
|
28
|
+
...T,
|
|
29
|
+
db?: DbArg<O>
|
|
30
|
+
];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { VSRepoOrmTypes } from "../vsrepo/vsrepo-orm-types.type";
|
|
1
2
|
/**
|
|
2
3
|
* Single argument accepted by a method declared with `@QueryMethod`.
|
|
3
4
|
*
|
|
@@ -19,9 +20,9 @@
|
|
|
19
20
|
*
|
|
20
21
|
* @publicApi
|
|
21
22
|
*/
|
|
22
|
-
export type QueryMethodArg<T extends Array<any
|
|
23
|
+
export type QueryMethodArg<T extends Array<any> = [], O extends VSRepoOrmTypes = VSRepoOrmTypes> = {
|
|
23
24
|
/** Positional parameters injected into the SQL placeholders (`$1`, `$2`, ...). */
|
|
24
25
|
args?: T;
|
|
25
26
|
/** Database client or transaction to run this query in, instead of the repository's default client. */
|
|
26
|
-
db?:
|
|
27
|
+
db?: O["dbClient"] | O["dbTransaction"];
|
|
27
28
|
};
|
|
@@ -14,4 +14,18 @@ export type VSRepoQueryOptions<T extends VSRepoOrmTypes = VSRepoOrmTypes> = {
|
|
|
14
14
|
* @default false
|
|
15
15
|
*/
|
|
16
16
|
modifying?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* When `true`, the array returned by the underlying query is collapsed
|
|
19
|
+
* into its first element (`null` if the array is empty) before
|
|
20
|
+
* being resolved to the caller. Has no effect when the query resolves
|
|
21
|
+
* to something other than an array (e.g. a `modifying` query's
|
|
22
|
+
* affected-row count).
|
|
23
|
+
*
|
|
24
|
+
* Useful for queries you already know return at most one row (e.g. a
|
|
25
|
+
* `SELECT ... LIMIT 1` or a lookup by a unique column), where declaring
|
|
26
|
+
* the return type as an array would be misleading.
|
|
27
|
+
*
|
|
28
|
+
* @default false
|
|
29
|
+
*/
|
|
30
|
+
singleResult?: boolean;
|
|
17
31
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vsrepo",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.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": {
|