pg-query-sdk 1.0.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 +553 -0
- package/dist/cjs/builders/ConditionBuilder.d.ts +11 -0
- package/dist/cjs/builders/ConditionBuilder.js +43 -0
- package/dist/cjs/builders/QueryBuilder.d.ts +31 -0
- package/dist/cjs/builders/QueryBuilder.js +88 -0
- package/dist/cjs/core/Database.d.ts +20 -0
- package/dist/cjs/core/Database.js +39 -0
- package/dist/cjs/core/ParamContext.d.ts +8 -0
- package/dist/cjs/core/ParamContext.js +16 -0
- package/dist/cjs/core/QueryExecutor.d.ts +14 -0
- package/dist/cjs/core/QueryExecutor.js +39 -0
- package/dist/cjs/core/TransactionManager.d.ts +6 -0
- package/dist/cjs/core/TransactionManager.js +24 -0
- package/dist/cjs/core/UnitOfWork.d.ts +10 -0
- package/dist/cjs/core/UnitOfWork.js +27 -0
- package/dist/cjs/dialects/Dialect.d.ts +4 -0
- package/dist/cjs/dialects/Dialect.js +2 -0
- package/dist/cjs/dialects/MysqlDialect.d.ts +5 -0
- package/dist/cjs/dialects/MysqlDialect.js +11 -0
- package/dist/cjs/dialects/PostgresDialect.d.ts +5 -0
- package/dist/cjs/dialects/PostgresDialect.js +11 -0
- package/dist/cjs/index.d.ts +7 -0
- package/dist/cjs/index.js +20 -0
- package/dist/cjs/orm/EntityManager.d.ts +0 -0
- package/dist/cjs/orm/EntityManager.js +1 -0
- package/dist/cjs/orm/Repository.d.ts +13 -0
- package/dist/cjs/orm/Repository.js +30 -0
- package/dist/cjs/query/ConditionBuilder.d.ts +11 -0
- package/dist/cjs/query/ConditionBuilder.js +67 -0
- package/dist/cjs/query/QueryBuilder.d.ts +43 -0
- package/dist/cjs/query/QueryBuilder.js +152 -0
- package/dist/esm/builders/ConditionBuilder.d.ts +11 -0
- package/dist/esm/builders/ConditionBuilder.js +40 -0
- package/dist/esm/builders/QueryBuilder.d.ts +31 -0
- package/dist/esm/builders/QueryBuilder.js +82 -0
- package/dist/esm/core/Database.d.ts +20 -0
- package/dist/esm/core/Database.js +33 -0
- package/dist/esm/core/ParamContext.d.ts +8 -0
- package/dist/esm/core/ParamContext.js +13 -0
- package/dist/esm/core/QueryExecutor.d.ts +14 -0
- package/dist/esm/core/QueryExecutor.js +36 -0
- package/dist/esm/core/TransactionManager.d.ts +6 -0
- package/dist/esm/core/TransactionManager.js +21 -0
- package/dist/esm/core/UnitOfWork.d.ts +10 -0
- package/dist/esm/core/UnitOfWork.js +24 -0
- package/dist/esm/dialects/Dialect.d.ts +4 -0
- package/dist/esm/dialects/Dialect.js +1 -0
- package/dist/esm/dialects/MysqlDialect.d.ts +5 -0
- package/dist/esm/dialects/MysqlDialect.js +8 -0
- package/dist/esm/dialects/PostgresDialect.d.ts +5 -0
- package/dist/esm/dialects/PostgresDialect.js +8 -0
- package/dist/esm/index.d.ts +7 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/orm/EntityManager.d.ts +0 -0
- package/dist/esm/orm/EntityManager.js +1 -0
- package/dist/esm/orm/Repository.d.ts +13 -0
- package/dist/esm/orm/Repository.js +27 -0
- package/dist/esm/query/ConditionBuilder.d.ts +11 -0
- package/dist/esm/query/ConditionBuilder.js +64 -0
- package/dist/esm/query/QueryBuilder.d.ts +43 -0
- package/dist/esm/query/QueryBuilder.js +146 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
# PG QUERY SDK (TypeScript)
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://www.typescriptlang.org/)
|
|
5
|
+
[](#-arquitetura)
|
|
6
|
+
|
|
7
|
+
Uma biblioteca robusta e tipada para integração com banco de dados **PostgresSQL**. Construída sob os princípios da **Arquitetura Hexagonal**, garantindo que sua integração seja escalável, testável e fácil de manter.
|
|
8
|
+
|
|
9
|
+
**PostgreSQL** SDK com suporte a:
|
|
10
|
+
|
|
11
|
+
- **`Database`**: Ponto de entrada central para todas as operações.
|
|
12
|
+
- **`QueryBuilder`**: Construtor de queries SQL fluente para **SELECT**.
|
|
13
|
+
- **`ConditionBuilder`**: Construtor de cláusulas `WHERE` e `HAVING` complexas.
|
|
14
|
+
- **`QueryExecutor`**: Executor de queries baseado em Pool de conexões.
|
|
15
|
+
- **ORM Básico**: Com `Repository` para abstração de acesso a dados (atualmente apenas `findById` implementado na base).
|
|
16
|
+
- **Transações**: Gerenciamento de transações ACID.
|
|
17
|
+
- Compatível com CommonJS e ESM.
|
|
18
|
+
- Dual build (CJS + ESM).
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# 📦 Instalação
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install pg-query-sdk
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Ou localmente:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install .
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
# 🚀 Primeiros Passos
|
|
37
|
+
|
|
38
|
+
O ponto de entrada principal para interagir com o SDK é a classe `Database`. Ela gerencia a conexão, o dialeto SQL e fornece acesso a todas as funcionalidades.
|
|
39
|
+
|
|
40
|
+
## Inicializando o Database
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import Database from 'pg-query-sdk';
|
|
44
|
+
|
|
45
|
+
const db = new Database({
|
|
46
|
+
connectionString: 'postgres://user:pass@localhost:5432/your_database',
|
|
47
|
+
// Opcional: Você pode especificar um dialeto diferente se necessário
|
|
48
|
+
// dialect: new MyCustomDialect(),
|
|
49
|
+
// Opcional: TTL padrão para cache de queries (em segundos). Use 0 para desativar.
|
|
50
|
+
// defaultCacheTTL: 300
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Agora 'db' está pronto para ser usado!
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
# 🛠 Funcionalidades Principais
|
|
59
|
+
|
|
60
|
+
## 1️⃣ QueryBuilder: Construindo Queries SQL (SELECT)
|
|
61
|
+
|
|
62
|
+
O `QueryBuilder` permite construir queries SQL de forma programática e segura, **focando em operações de seleção de dados**. Ele é acessado através do método `table()` da sua instância `Database`.
|
|
63
|
+
|
|
64
|
+
Ele **não executa nada**, apenas retorna a string SQL e os parâmetros. Para executar a query, você deve encadear `.execute()` no final.
|
|
65
|
+
|
|
66
|
+
### Selecionando Dados
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import Database from 'pg-query-sdk';
|
|
70
|
+
|
|
71
|
+
const db = new Database({
|
|
72
|
+
connectionString: 'postgres://user:pass@localhost:5432/your_database',
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
async function selectExample() {
|
|
76
|
+
// Construindo a query
|
|
77
|
+
const { query, params } = db.table('users')
|
|
78
|
+
.select(['id', 'name', 'email'])
|
|
79
|
+
.where({ active: true })
|
|
80
|
+
.limit(10)
|
|
81
|
+
.offset(0)
|
|
82
|
+
.orderBy('name', 'ASC')
|
|
83
|
+
.build(); // Apenas constrói a query, não executa
|
|
84
|
+
|
|
85
|
+
console.log('SELECT Query:', query);
|
|
86
|
+
// Ex: SELECT id, name, email FROM users WHERE active = $1 ORDER BY name ASC LIMIT 10 OFFSET 0
|
|
87
|
+
console.log('SELECT Params:', params);
|
|
88
|
+
// Ex: [true]
|
|
89
|
+
|
|
90
|
+
// Executando a query
|
|
91
|
+
const users = await db.table('users')
|
|
92
|
+
.select(['id', 'name', 'email'])
|
|
93
|
+
.where({ active: true })
|
|
94
|
+
.limit(10)
|
|
95
|
+
.offset(0)
|
|
96
|
+
.orderBy('name', 'ASC')
|
|
97
|
+
.execute(); // Executa a query e retorna os resultados
|
|
98
|
+
|
|
99
|
+
console.log('Selected Users:', users);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
selectExample();
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Joins
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import Database from 'pg-query-sdk';
|
|
109
|
+
|
|
110
|
+
const db = new Database({
|
|
111
|
+
connectionString: 'postgres://user:pass@localhost:5432/your_database',
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
async function joinExample() {
|
|
115
|
+
const usersWithOrders = await db.table('users')
|
|
116
|
+
.select(['users.name', 'orders.amount', 'orders.status'])
|
|
117
|
+
.join('orders', 'users.id', 'orders.user_id') // INNER JOIN
|
|
118
|
+
.where({ 'orders.status': 'completed' })
|
|
119
|
+
.execute();
|
|
120
|
+
|
|
121
|
+
console.log('Users with completed orders:', usersWithOrders);
|
|
122
|
+
|
|
123
|
+
const usersAndTheirOrders = await db.table('users')
|
|
124
|
+
.select(['users.name', 'orders.amount', 'orders.status'])
|
|
125
|
+
.leftJoin('orders', 'users.id', 'orders.user_id') // LEFT JOIN
|
|
126
|
+
.orderBy('users.name', 'ASC')
|
|
127
|
+
.execute();
|
|
128
|
+
|
|
129
|
+
console.log('Users and all their orders (if any):', usersAndTheirOrders);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
joinExample();
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Group By e Having
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import Database from 'pg-query-sdk';
|
|
139
|
+
|
|
140
|
+
const db = new Database({
|
|
141
|
+
connectionString: 'postgres://user:pass@localhost:5432/your_database',
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
async function groupByHavingExample() {
|
|
145
|
+
const categorySales = await db.table('products')
|
|
146
|
+
.select(['category', 'COUNT(id) as total_products', 'SUM(price) as total_value'])
|
|
147
|
+
.groupBy('category')
|
|
148
|
+
.having({ 'SUM(price)': { op: '>', value: 1000 } }) // HAVING SUM(price) > 1000
|
|
149
|
+
.orderBy('total_value', 'DESC')
|
|
150
|
+
.execute();
|
|
151
|
+
|
|
152
|
+
console.log('Category sales over 1000:', categorySales);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
groupByHavingExample();
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Common Table Expressions (CTEs)
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import Database from 'pg-query-sdk';
|
|
162
|
+
|
|
163
|
+
const db = new Database({
|
|
164
|
+
connectionString: 'postgres://user:pass@localhost:5432/your_database',
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
async function cteExample() {
|
|
168
|
+
// Subquery para usuários ativos
|
|
169
|
+
const activeUsersSubquery = db.table('users')
|
|
170
|
+
.select(['id', 'name'])
|
|
171
|
+
.where({ active: true });
|
|
172
|
+
|
|
173
|
+
// Query principal usando a CTE
|
|
174
|
+
const result = await db.table('active_users') // Referencia a CTE pelo nome
|
|
175
|
+
.with('active_users', activeUsersSubquery) // Define a CTE
|
|
176
|
+
.select(['name'])
|
|
177
|
+
.orderBy('name', 'ASC')
|
|
178
|
+
.execute();
|
|
179
|
+
|
|
180
|
+
console.log('Users from CTE:', result);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
cteExample();
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Subqueries na Cláusula FROM
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import Database from 'pg-query-sdk';
|
|
190
|
+
|
|
191
|
+
const db = new Database({
|
|
192
|
+
connectionString: 'postgres://user:pass@localhost:5432/your_database',
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
async function fromSubqueryExample() {
|
|
196
|
+
// Subquery para obter o total de pedidos por usuário
|
|
197
|
+
const userOrderCounts = db.table('orders')
|
|
198
|
+
.select(['user_id', 'COUNT(id) as order_count'])
|
|
199
|
+
.groupBy('user_id');
|
|
200
|
+
|
|
201
|
+
// Query principal usando a subquery como tabela
|
|
202
|
+
const usersWithOrderCounts = await db.table('users')
|
|
203
|
+
.select(['users.name', 'uoc.order_count'])
|
|
204
|
+
.fromSubquery(userOrderCounts, 'uoc') // Usa a subquery 'userOrderCounts' como 'uoc'
|
|
205
|
+
.join('users', 'users.id', 'uoc.user_id') // JOIN com a tabela original de usuários
|
|
206
|
+
.where({ 'uoc.order_count': { op: '>', value: 5 } })
|
|
207
|
+
.execute();
|
|
208
|
+
|
|
209
|
+
console.log('Users with more than 5 orders:', usersWithOrderCounts);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
fromSubqueryExample();
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## 2️⃣ ConditionBuilder: Cláusulas WHERE e HAVING Avançadas
|
|
216
|
+
|
|
217
|
+
O `ConditionBuilder` é usado dentro dos métodos `where()` e `having()` do `QueryBuilder` para construir condições complexas, incluindo operadores, `NULL` checks, expressões raw e agrupamentos `AND`/`OR`.
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
import Database from 'pg-query-sdk';
|
|
221
|
+
|
|
222
|
+
const db = new Database({
|
|
223
|
+
connectionString: 'postgres://user:pass@localhost:5432/your_database',
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
async function complexWhereExample() {
|
|
227
|
+
const products = await db.table('products')
|
|
228
|
+
.select(['name', 'price', 'stock', 'category'])
|
|
229
|
+
.where(conditions => {
|
|
230
|
+
conditions
|
|
231
|
+
.where({ category: 'electronics' }) // category = $1
|
|
232
|
+
.andGroup(group1 => { // AND (...)
|
|
233
|
+
group1
|
|
234
|
+
.where({ stock: { op: '>', value: 0 } }) // stock > $2
|
|
235
|
+
.orGroup(group2 => { // OR (...)
|
|
236
|
+
group2
|
|
237
|
+
.where({ price: { op: '<', value: 100 } }) // price < $3
|
|
238
|
+
.raw('created_at > NOW() - INTERVAL \'1 year\''); // created_at > ...
|
|
239
|
+
});
|
|
240
|
+
})
|
|
241
|
+
.where({ manufacturer: null }); // manufacturer IS NULL
|
|
242
|
+
})
|
|
243
|
+
.execute();
|
|
244
|
+
|
|
245
|
+
console.log('Complex WHERE Products:', products);
|
|
246
|
+
// A query gerada seria algo como:
|
|
247
|
+
// SELECT name, price, stock, category FROM products
|
|
248
|
+
// WHERE category = $1 AND (stock > $2 OR (price < $3 AND created_at > NOW() - INTERVAL '1 year')) AND manufacturer IS NULL
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
complexWhereExample();
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## 3️⃣ QueryExecutor: Execução Direta de Queries
|
|
255
|
+
|
|
256
|
+
O `QueryExecutor` é a camada responsável por interagir diretamente com o driver `pg` para executar queries. Embora o `QueryBuilder` seja preferível para a maioria dos casos, você pode acessar o `QueryExecutor` diretamente através da instância `Database` para queries SQL customizadas, procedimentos armazenados ou comandos DDL.
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
import Database from 'pg-query-sdk';
|
|
260
|
+
|
|
261
|
+
const db = new Database({
|
|
262
|
+
connectionString: 'postgres://user:pass@localhost:5432/your_database',
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
async function directExecuteExample() {
|
|
266
|
+
// Executando uma query simples
|
|
267
|
+
const result = await db.executor.execute(
|
|
268
|
+
'SELECT version(), NOW() as current_time',
|
|
269
|
+
[]
|
|
270
|
+
);
|
|
271
|
+
console.log('Direct Execution Result:', result.rows);
|
|
272
|
+
|
|
273
|
+
// Executando uma query com parâmetros
|
|
274
|
+
const specificUser = await db.executor.execute(
|
|
275
|
+
'SELECT * FROM users WHERE id = $1',
|
|
276
|
+
[1]
|
|
277
|
+
);
|
|
278
|
+
console.log('Specific User (Direct):', specificUser.rows[0]);
|
|
279
|
+
|
|
280
|
+
// Exemplo de DDL (Data Definition Language) - CUIDADO ao usar em produção!
|
|
281
|
+
// await db.executor.execute('CREATE TABLE IF NOT EXISTS temp_table (id SERIAL PRIMARY KEY, name VARCHAR(255))', []);
|
|
282
|
+
// console.log('temp_table created (if not exists).');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
directExecuteExample();
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## 4️⃣ Transações ACID
|
|
289
|
+
|
|
290
|
+
O SDK oferece um gerenciador de transações robusto para garantir a atomicidade (ACID) das suas operações de banco de dados. Se qualquer operação dentro da transação falhar, todas as alterações serão revertidas (rollback).
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
import Database from 'pg-query-sdk';
|
|
294
|
+
|
|
295
|
+
const db = new Database({
|
|
296
|
+
connectionString: 'postgres://user:pass@localhost:5432/your_database',
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
async function transactionExample() {
|
|
300
|
+
try {
|
|
301
|
+
const result = await db.transaction(async trxDb => {
|
|
302
|
+
// Dentro desta callback, 'trxDb' é uma instância de Database
|
|
303
|
+
// que está vinculada à transação atual.
|
|
304
|
+
// Todas as operações feitas com 'trxDb' farão parte da mesma transação.
|
|
305
|
+
|
|
306
|
+
// Exemplo: Transferir fundos entre contas
|
|
307
|
+
const senderId = 1;
|
|
308
|
+
const receiverId = 2;
|
|
309
|
+
const amount = 100.00;
|
|
310
|
+
|
|
311
|
+
// 1. Decrementar saldo do remetente
|
|
312
|
+
// Nota: Para INSERT/UPDATE/DELETE, você precisará usar db.executor.execute() diretamente
|
|
313
|
+
// ou implementar esses métodos em um repositório customizado.
|
|
314
|
+
await trxDb.executor.execute(
|
|
315
|
+
'UPDATE accounts SET balance = balance - $1 WHERE id = $2 AND balance >= $1 RETURNING id, balance',
|
|
316
|
+
[amount, senderId]
|
|
317
|
+
);
|
|
318
|
+
console.log(`Decremented balance for account ${senderId}`);
|
|
319
|
+
|
|
320
|
+
// Simular uma falha para testar o rollback
|
|
321
|
+
// if (true) throw new Error('Simulated failure');
|
|
322
|
+
|
|
323
|
+
// 2. Incrementar saldo do destinatário
|
|
324
|
+
await trxDb.executor.execute(
|
|
325
|
+
'UPDATE accounts SET balance = balance + $1 WHERE id = $2 RETURNING id, balance',
|
|
326
|
+
[amount, receiverId]
|
|
327
|
+
);
|
|
328
|
+
console.log(`Incremented balance for account ${receiverId}`);
|
|
329
|
+
|
|
330
|
+
// Se tudo ocorrer bem, a transação será commitada automaticamente.
|
|
331
|
+
return `Transaction successful: ${amount} transferred from ${senderId} to ${receiverId}`;
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
console.log(result);
|
|
335
|
+
} catch (error) {
|
|
336
|
+
console.error('Transaction failed:', error.message);
|
|
337
|
+
// Se uma exceção for lançada, a transação será automaticamente revertida (rollback).
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
transactionExample();
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## 5️⃣ ORM Básico com Repositórios
|
|
345
|
+
|
|
346
|
+
O SDK fornece uma base para construir um ORM simples usando a classe `Repository`. Isso ajuda a organizar o código de acesso a dados por entidade.
|
|
347
|
+
|
|
348
|
+
### Definindo um Repositório Customizado
|
|
349
|
+
|
|
350
|
+
A classe base `Repository<T>` oferece um método `findById` e um `qb()` que retorna um `QueryBuilder` pré-configurado para a tabela. Para operações de `INSERT`, `UPDATE` e `DELETE`, você precisará implementá-las em seus repositórios customizados, utilizando o `QueryExecutor` ou o `QueryBuilder` (para `SELECT` após a operação).
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
import { Repository, QueryExecutor, Dialect } from 'pg-query-sdk';
|
|
354
|
+
|
|
355
|
+
// 1. Defina a interface para sua entidade
|
|
356
|
+
interface User {
|
|
357
|
+
id: number;
|
|
358
|
+
name: string;
|
|
359
|
+
email: string;
|
|
360
|
+
age?: number;
|
|
361
|
+
active: boolean;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// 2. Crie sua classe de repositório estendendo Repository<T>
|
|
365
|
+
class UserRepository extends Repository<User> {
|
|
366
|
+
constructor(executor: QueryExecutor, dialect: Dialect) {
|
|
367
|
+
// O construtor base requer o nome da tabela, o executor e o dialeto
|
|
368
|
+
super('users', executor, dialect);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Método implementado na classe base
|
|
372
|
+
// async findById(id: number): Promise<User | null> { ... }
|
|
373
|
+
|
|
374
|
+
// Exemplo de método customizado para o repositório de usuários
|
|
375
|
+
async findActiveUsers(): Promise<User[]> {
|
|
376
|
+
return this.qb() // 'this.qb()' retorna um QueryBuilder pré-configurado para a tabela 'users'
|
|
377
|
+
.where({ active: true })
|
|
378
|
+
.execute();
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async findUsersByAgeRange(minAge: number, maxAge: number): Promise<User[]> {
|
|
382
|
+
return this.qb()
|
|
383
|
+
.where(conditions => {
|
|
384
|
+
conditions
|
|
385
|
+
.where({ age: { op: '>=', value: minAge } })
|
|
386
|
+
.where({ age: { op: '<=', value: maxAge } });
|
|
387
|
+
})
|
|
388
|
+
.execute();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Exemplo de implementação de INSERT em um repositório customizado
|
|
392
|
+
async createUser(data: Omit<User, 'id'>): Promise<User> {
|
|
393
|
+
const { query, params } = this.dialect.createInsertQuery(this.table, data as Record<string, any>, ['id', 'name', 'email', 'age', 'active']);
|
|
394
|
+
const result = await this.executor.execute(query, params);
|
|
395
|
+
return result.rows[0];
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Exemplo de implementação de UPDATE em um repositório customizado
|
|
399
|
+
async updateUser(id: number, data: Partial<User>): Promise<User | null> {
|
|
400
|
+
const { query, params } = this.dialect.createUpdateQuery(this.table, data as Record<string, any>, { id }, ['id', 'name', 'email', 'age', 'active']);
|
|
401
|
+
const result = await this.executor.execute(query, params);
|
|
402
|
+
return result.rows[0] || null;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Exemplo de implementação de DELETE em um repositório customizado
|
|
406
|
+
async deleteUser(id: number): Promise<boolean> {
|
|
407
|
+
const { query, params } = this.dialect.createDeleteQuery(this.table, { id });
|
|
408
|
+
const result = await this.executor.execute(query, params);
|
|
409
|
+
return result.rowCount > 0;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Usando o Repositório Customizado
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
import Database from 'pg-query-sdk';
|
|
418
|
+
// Importe seu UserRepository definido acima
|
|
419
|
+
import { UserRepository } from './path/to/UserRepository'; // Ajuste o caminho conforme necessário
|
|
420
|
+
|
|
421
|
+
const db = new Database({
|
|
422
|
+
connectionString: 'postgres://user:pass@localhost:5432/your_database',
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
async function repositoryExample() {
|
|
426
|
+
// Obtenha uma instância do seu repositório através do método .repository() do Database
|
|
427
|
+
const userRepository = db.repository(UserRepository);
|
|
428
|
+
|
|
429
|
+
// Usando métodos do repositório base
|
|
430
|
+
const userById = await userRepository.findById(1);
|
|
431
|
+
console.log('User by ID:', userById);
|
|
432
|
+
|
|
433
|
+
// Usando métodos customizados
|
|
434
|
+
const activeUsers = await userRepository.findActiveUsers();
|
|
435
|
+
console.log('Active Users:', activeUsers);
|
|
436
|
+
|
|
437
|
+
const usersInAgeRange = await userRepository.findUsersByAgeRange(25, 35);
|
|
438
|
+
console.log('Users in age range 25-35:', usersInAgeRange);
|
|
439
|
+
|
|
440
|
+
// Criando um novo usuário
|
|
441
|
+
const newUser = await userRepository.createUser({
|
|
442
|
+
name: 'Charlie',
|
|
443
|
+
email: 'charlie@example.com',
|
|
444
|
+
age: 29,
|
|
445
|
+
active: true
|
|
446
|
+
});
|
|
447
|
+
console.log('Created new user:', newUser);
|
|
448
|
+
|
|
449
|
+
// Atualizando um usuário
|
|
450
|
+
const updatedUser = await userRepository.updateUser(newUser.id, { age: 30, active: false });
|
|
451
|
+
console.log('Updated user:', updatedUser);
|
|
452
|
+
|
|
453
|
+
// Deletando um usuário
|
|
454
|
+
const deleted = await userRepository.deleteUser(newUser.id);
|
|
455
|
+
console.log(`User ${newUser.id} deleted: ${deleted}`);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
repositoryExample();
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### EntityManager (Planejado)
|
|
462
|
+
|
|
463
|
+
O `EntityManager` é um componente planejado para gerenciar múltiplos repositórios e unidades de trabalho, oferecendo uma interface centralizada para operações de persistência mais complexas. Atualmente, esta classe está vazia e será desenvolvida em futuras iterações.
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
# ⚙️ Dual Module Support
|
|
468
|
+
|
|
469
|
+
O pacote suporta tanto CommonJS quanto ES Modules, permitindo flexibilidade na sua configuração de projeto.
|
|
470
|
+
|
|
471
|
+
## CommonJS
|
|
472
|
+
|
|
473
|
+
```javascript
|
|
474
|
+
const Database = require('pg-query-sdk').default; // Note o .default para a exportação padrão
|
|
475
|
+
|
|
476
|
+
const db = new Database({ /* ... */ });
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
## ESM
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
import Database from 'pg-query-sdk';
|
|
483
|
+
|
|
484
|
+
const db = new Database({ /* ... */ });
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
Isso funciona graças ao campo `exports` no `package.json`:
|
|
488
|
+
|
|
489
|
+
```json
|
|
490
|
+
{
|
|
491
|
+
"exports": {
|
|
492
|
+
".": {
|
|
493
|
+
"require": "./dist/cjs/index.js",
|
|
494
|
+
"import": "./dist/esm/index.js"
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
# 🧱 Estrutura do Projeto
|
|
503
|
+
|
|
504
|
+
```
|
|
505
|
+
pg-query-sdk/
|
|
506
|
+
src/
|
|
507
|
+
core/
|
|
508
|
+
Database.ts # Ponto de entrada principal
|
|
509
|
+
ParamContext.ts # Gerencia parâmetros para queries seguras
|
|
510
|
+
QueryExecutor.ts # Executa queries no PostgreSQL
|
|
511
|
+
TransactionManager.ts # Gerencia transações
|
|
512
|
+
builders/
|
|
513
|
+
ConditionBuilder.ts # Constrói cláusulas WHERE e HAVING
|
|
514
|
+
QueryBuilder.ts # Constrói queries SQL (apenas SELECT)
|
|
515
|
+
orm/
|
|
516
|
+
EntityManager.ts # (Planejado) Gerenciador de entidades
|
|
517
|
+
Repository.ts # Classe base para repositórios ORM
|
|
518
|
+
dialects/
|
|
519
|
+
Dialect.ts # Interface para dialetos SQL
|
|
520
|
+
PostgresDialect.ts # Implementação do dialeto PostgreSQL
|
|
521
|
+
index.ts # Exportações principais do SDK
|
|
522
|
+
test/ # Testes unitários e de integração
|
|
523
|
+
dist/ # Saída da compilação (CJS e ESM)
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
---
|
|
527
|
+
|
|
528
|
+
# 📌 Responsabilidades das Camadas
|
|
529
|
+
|
|
530
|
+
| Camada | Responsabilidade |
|
|
531
|
+
|--------------------|--------------------------------------------------------------------------------------------------------------|
|
|
532
|
+
| `Database` | Ponto de entrada, gerencia conexão, dialeto, transações e acesso a builders/repositórios. |
|
|
533
|
+
| `QueryBuilder` | Construção fluente de queries SQL **apenas para seleção de dados** (SELECT, JOIN, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, OFFSET, CTEs, Subqueries). |
|
|
534
|
+
| `ConditionBuilder` | Construção de cláusulas `WHERE` e `HAVING` complexas e aninhadas. |
|
|
535
|
+
| `QueryExecutor` | Execução de queries no PostgreSQL e gerenciamento do pool de conexões. |
|
|
536
|
+
| `Repository` | Abstração de acesso a dados para uma entidade específica. A classe base implementa `findById`. Métodos como `insert`, `update`, `delete` devem ser implementados nos repositórios customizados. |
|
|
537
|
+
| `TransactionManager`| Gerenciamento de transações ACID. |
|
|
538
|
+
| `EntityManager` | (Planejado) Gerenciamento de múltiplos repositórios e unidade de trabalho. |
|
|
539
|
+
| `pg` (driver) | Comunicação de baixo nível com o banco de dados PostgreSQL. |
|
|
540
|
+
|
|
541
|
+
---
|
|
542
|
+
|
|
543
|
+
# 🔐 Segurança
|
|
544
|
+
|
|
545
|
+
- **Parâmetros Preparados**: Todas as queries construídas pelo `QueryBuilder` e `ConditionBuilder` utilizam parâmetros preparados, prevenindo ataques de SQL Injection. O `QueryExecutor` também suporta parâmetros para queries diretas.
|
|
546
|
+
- **Pool de Conexões**: O `QueryExecutor` gerencia um pool de conexões, otimizando o uso de recursos e garantindo que as conexões sejam reutilizadas de forma eficiente.
|
|
547
|
+
- **Liberação de Conexões**: As conexões são sempre liberadas de volta ao pool no bloco `finally` após a execução da query ou transação, evitando vazamentos de conexão.
|
|
548
|
+
|
|
549
|
+
---
|
|
550
|
+
|
|
551
|
+
## 📄 Licença
|
|
552
|
+
|
|
553
|
+
Distribuído sob a licença MIT. Veja `LICENSE` para mais informações.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import ParamContext from "../core/ParamContext";
|
|
2
|
+
export default class ConditionBuilder {
|
|
3
|
+
private ctx;
|
|
4
|
+
private conditions;
|
|
5
|
+
constructor(ctx: ParamContext);
|
|
6
|
+
where(obj: Record<string, any>): this;
|
|
7
|
+
raw(expression: string): this;
|
|
8
|
+
andGroup(callback: (qb: ConditionBuilder) => void): this;
|
|
9
|
+
orGroup(callback: (qb: ConditionBuilder) => void): this;
|
|
10
|
+
build(prefix?: string): string;
|
|
11
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
class ConditionBuilder {
|
|
4
|
+
constructor(ctx) {
|
|
5
|
+
this.ctx = ctx;
|
|
6
|
+
this.conditions = [];
|
|
7
|
+
}
|
|
8
|
+
where(obj) {
|
|
9
|
+
Object.entries(obj).forEach(([key, value]) => {
|
|
10
|
+
const placeholder = this.ctx.add(value);
|
|
11
|
+
this.conditions.push(`${key} = ${placeholder}`);
|
|
12
|
+
});
|
|
13
|
+
return this;
|
|
14
|
+
}
|
|
15
|
+
raw(expression) {
|
|
16
|
+
this.conditions.push(expression);
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
19
|
+
andGroup(callback) {
|
|
20
|
+
const nested = new ConditionBuilder(this.ctx);
|
|
21
|
+
callback(nested);
|
|
22
|
+
const built = nested.build();
|
|
23
|
+
if (built) {
|
|
24
|
+
this.conditions.push(`(${built.replace(/^WHERE /, '')})`);
|
|
25
|
+
}
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
orGroup(callback) {
|
|
29
|
+
const nested = new ConditionBuilder(this.ctx);
|
|
30
|
+
callback(nested);
|
|
31
|
+
const built = nested.build();
|
|
32
|
+
if (built) {
|
|
33
|
+
this.conditions.push(`OR (${built.replace(/^WHERE /, '')})`);
|
|
34
|
+
}
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
build(prefix = 'WHERE') {
|
|
38
|
+
if (!this.conditions.length)
|
|
39
|
+
return '';
|
|
40
|
+
return `${prefix} ${this.conditions.join(' AND ')}`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.default = ConditionBuilder;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import QueryExecutor from '../core/QueryExecutor';
|
|
2
|
+
import { Dialect } from "../dialects/Dialect";
|
|
3
|
+
export default class QueryBuilder<T = any> {
|
|
4
|
+
private executor;
|
|
5
|
+
private fields;
|
|
6
|
+
private joins;
|
|
7
|
+
private groupByFields;
|
|
8
|
+
private orderByFields;
|
|
9
|
+
private limitCount?;
|
|
10
|
+
private offsetCount?;
|
|
11
|
+
private ctes;
|
|
12
|
+
private fromClause;
|
|
13
|
+
private condition;
|
|
14
|
+
private havingCondition;
|
|
15
|
+
private ctx;
|
|
16
|
+
constructor(table: string, executor: QueryExecutor, dialect: Dialect);
|
|
17
|
+
select(fields: string[]): this;
|
|
18
|
+
private addJoin;
|
|
19
|
+
join(table: string, localKey: string, foreignKey: string): this;
|
|
20
|
+
leftJoin(table: string, localKey: string, foreignKey: string): this;
|
|
21
|
+
rightJoin(table: string, localKey: string, foreignKey: string): this;
|
|
22
|
+
fromSubquery(sub: QueryBuilder, alias: string): this;
|
|
23
|
+
where(obj: Record<string, any>): this;
|
|
24
|
+
whereSub(column: string, operator: 'IN' | 'NOT IN', sub: QueryBuilder): this;
|
|
25
|
+
limit(limit: number): this;
|
|
26
|
+
build(): {
|
|
27
|
+
query: string;
|
|
28
|
+
params: any[];
|
|
29
|
+
};
|
|
30
|
+
execute(): Promise<any[]>;
|
|
31
|
+
}
|