@groundbrick/repository-base 0.0.1
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 +546 -0
- package/dist/core/BaseRepository.d.ts +70 -0
- package/dist/core/BaseRepository.d.ts.map +1 -0
- package/dist/core/BaseRepository.js +452 -0
- package/dist/core/BaseRepository.js.map +1 -0
- package/dist/core/SqlAdapter.d.ts +41 -0
- package/dist/core/SqlAdapter.d.ts.map +1 -0
- package/dist/core/SqlAdapter.js +84 -0
- package/dist/core/SqlAdapter.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/types/Entity.d.ts +32 -0
- package/dist/types/Entity.d.ts.map +1 -0
- package/dist/types/Entity.js +3 -0
- package/dist/types/Entity.js.map +1 -0
- package/dist/types/QueryOptions.d.ts +40 -0
- package/dist/types/QueryOptions.d.ts.map +1 -0
- package/dist/types/QueryOptions.js +2 -0
- package/dist/types/QueryOptions.js.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
# @groundbrick/repository-base
|
|
2
|
+
|
|
3
|
+
๐พ SQL Repository Foundation for PostgreSQL and MySQL
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
Generic repository base classes with CRUD operations for the microframework. Provides a simple, type-safe data access layer that works consistently across PostgreSQL and MySQL databases.
|
|
7
|
+
|
|
8
|
+
## ๐ Features
|
|
9
|
+
|
|
10
|
+
- โ
**Generic CRUD Operations**: Type-safe create, read, update, delete operations
|
|
11
|
+
- ๐ **Database Agnostic**: Works with both PostgreSQL and MySQL through unified interface
|
|
12
|
+
- ๐ง **Simple Query Methods**: Easy-to-use methods for common database operations
|
|
13
|
+
- ๐งฉ **Pagination Support**: Built-in pagination with metadata
|
|
14
|
+
- ๐ก **Automatic SQL Adaptation**: Handles PostgreSQL `$1` vs MySQL `?` parameter differences
|
|
15
|
+
- ๐ **Logging Integration**: Comprehensive logging with performance tracking
|
|
16
|
+
- ๐งช **Type Safety**: Full TypeScript support with generic entity types
|
|
17
|
+
- ๐ฆ **No ORM Complexity**: Simple, lightweight approach focused on SQL
|
|
18
|
+
|
|
19
|
+
## ๐ฆ Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @groundbrick/repository-base
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## โก Quick Start
|
|
26
|
+
|
|
27
|
+
### 1. Define Your Entity
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { BaseEntity } from '@groundbrick/repository-base';
|
|
31
|
+
|
|
32
|
+
// Simple entity interface
|
|
33
|
+
interface User extends BaseEntity {
|
|
34
|
+
name: string;
|
|
35
|
+
email: string;
|
|
36
|
+
active: boolean;
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2. Create Repository
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { BaseRepository, DatabaseClient } from '@groundbrick/repository-base';
|
|
44
|
+
|
|
45
|
+
class UserRepository extends BaseRepository<User> {
|
|
46
|
+
constructor(db: DatabaseClient) {
|
|
47
|
+
super(db, 'users'); // Pass database client and table name
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Add custom methods as needed
|
|
51
|
+
async findByEmail(email: string): Promise<User | null> {
|
|
52
|
+
return this.findOne('email = ?', [email]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async findActiveUsers(): Promise<User[]> {
|
|
56
|
+
return this.findMany('active = ?', [true]);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 3. Use Repository
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { DatabaseFactory } from '@groundbrick/db-postgres';
|
|
65
|
+
|
|
66
|
+
// Initialize database
|
|
67
|
+
const db = DatabaseFactory.getInstance({
|
|
68
|
+
host: 'localhost',
|
|
69
|
+
database: 'myapp',
|
|
70
|
+
user: 'user',
|
|
71
|
+
password: 'password'
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Create repository
|
|
75
|
+
const userRepository = new UserRepository(db);
|
|
76
|
+
|
|
77
|
+
// Use CRUD operations
|
|
78
|
+
const user = await userRepository.create({
|
|
79
|
+
name: 'John Doe',
|
|
80
|
+
email: 'john@example.com',
|
|
81
|
+
active: true
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const foundUser = await userRepository.findById(user.id!);
|
|
85
|
+
const allUsers = await userRepository.findAll();
|
|
86
|
+
const activeUsers = await userRepository.findActiveUsers();
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## ๐ API Reference
|
|
90
|
+
|
|
91
|
+
### BaseRepository<T>
|
|
92
|
+
|
|
93
|
+
Generic base repository class providing CRUD operations for any entity type.
|
|
94
|
+
|
|
95
|
+
#### Constructor
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
constructor(
|
|
99
|
+
db: DatabaseClient,
|
|
100
|
+
tableName: string,
|
|
101
|
+
logger?: Logger
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### Basic CRUD Methods
|
|
106
|
+
|
|
107
|
+
##### `findById(id: number): Promise<T | null>`
|
|
108
|
+
Find a single record by ID.
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
const user = await userRepository.findById(123);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
##### `findOne(condition: string, params?: any[]): Promise<T | null>`
|
|
115
|
+
Find a single record by condition.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
const user = await userRepository.findOne('email = ?', ['john@example.com']);
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
##### `findMany(condition?: string, params?: any[]): Promise<T[]>`
|
|
122
|
+
Find multiple records by condition.
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const activeUsers = await userRepository.findMany('active = ?', [true]);
|
|
126
|
+
const allUsers = await userRepository.findMany(); // No condition = all records
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
##### `findAll(options?: QueryOptions): Promise<T[]>`
|
|
130
|
+
Find all records with optional sorting and pagination.
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
// Simple find all
|
|
134
|
+
const users = await userRepository.findAll();
|
|
135
|
+
|
|
136
|
+
// With pagination
|
|
137
|
+
const users = await userRepository.findAll({
|
|
138
|
+
pagination: { limit: 10, offset: 20 }
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// With sorting
|
|
142
|
+
const users = await userRepository.findAll({
|
|
143
|
+
sort: { column: 'name', direction: 'ASC' }
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Multiple sorts
|
|
147
|
+
const users = await userRepository.findAll({
|
|
148
|
+
sort: [
|
|
149
|
+
{ column: 'active', direction: 'DESC' },
|
|
150
|
+
{ column: 'name', direction: 'ASC' }
|
|
151
|
+
]
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
##### `create(data: CreateEntityData<T>): Promise<T>`
|
|
156
|
+
Create a new record. Automatically excludes `id`, `created_at`, and `updated_at`.
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
const user = await userRepository.create({
|
|
160
|
+
name: 'John Doe',
|
|
161
|
+
email: 'john@example.com',
|
|
162
|
+
active: true
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
##### `update(id: number, data: UpdateEntityData<T>): Promise<T | null>`
|
|
167
|
+
Update a record by ID. Automatically excludes `id` and `created_at`.
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
const updated = await userRepository.update(123, {
|
|
171
|
+
name: 'Jane Doe',
|
|
172
|
+
active: false
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
##### `delete(id: number): Promise<boolean>`
|
|
177
|
+
Delete a record by ID. Returns `true` if record was deleted, `false` if not found.
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
const deleted = await userRepository.delete(123);
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
#### Utility Methods
|
|
184
|
+
|
|
185
|
+
##### `count(condition?: string, params?: any[]): Promise<number>`
|
|
186
|
+
Count records with optional condition.
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
const totalUsers = await userRepository.count();
|
|
190
|
+
const activeCount = await userRepository.count('active = ?', [true]);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
##### `exists(id: number): Promise<boolean>`
|
|
194
|
+
Check if a record exists by ID.
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
const userExists = await userRepository.exists(123);
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
##### `findPaginated(options): Promise<PaginatedResult<T>>`
|
|
201
|
+
Find records with pagination and metadata.
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
const result = await userRepository.findPaginated({
|
|
205
|
+
condition: 'active = ?',
|
|
206
|
+
params: [true],
|
|
207
|
+
pagination: { limit: 10, offset: 0 }
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
console.log(result.data); // Array of records
|
|
211
|
+
console.log(result.total); // Total count
|
|
212
|
+
console.log(result.hasNext); // Boolean
|
|
213
|
+
console.log(result.hasPrevious); // Boolean
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
##### `rawQuery<R>(sql: string, params?: any[]): Promise<QueryResult<R>>`
|
|
217
|
+
Execute raw SQL query (use with caution).
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
const result = await userRepository.rawQuery<User>(
|
|
221
|
+
'SELECT * FROM users WHERE created_at > ?',
|
|
222
|
+
[new Date('2024-01-01')]
|
|
223
|
+
);
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## ๐งพ Types
|
|
227
|
+
|
|
228
|
+
### Entity Types
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
// Base entity with common fields
|
|
232
|
+
interface BaseEntity {
|
|
233
|
+
id?: number;
|
|
234
|
+
created_at?: Date;
|
|
235
|
+
updated_at?: Date;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Simple entity with just ID
|
|
239
|
+
interface SimpleEntity {
|
|
240
|
+
id?: number;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Helper types for CRUD operations
|
|
244
|
+
type CreateEntityData<T> = Omit<T, 'id' | 'created_at' | 'updated_at'>;
|
|
245
|
+
type UpdateEntityData<T> = Partial<Omit<T, 'id' | 'created_at'>>;
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Query Types
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
interface QueryOptions {
|
|
252
|
+
pagination?: {
|
|
253
|
+
limit?: number;
|
|
254
|
+
offset?: number;
|
|
255
|
+
};
|
|
256
|
+
sort?: {
|
|
257
|
+
column: string;
|
|
258
|
+
direction?: 'ASC' | 'DESC';
|
|
259
|
+
} | Array<{
|
|
260
|
+
column: string;
|
|
261
|
+
direction?: 'ASC' | 'DESC';
|
|
262
|
+
}>;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
interface PaginatedResult<T> {
|
|
266
|
+
data: T[];
|
|
267
|
+
total: number;
|
|
268
|
+
limit: number;
|
|
269
|
+
offset: number;
|
|
270
|
+
hasNext: boolean;
|
|
271
|
+
hasPrevious: boolean;
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## ๐ Database Compatibility
|
|
276
|
+
|
|
277
|
+
This package automatically handles differences between PostgreSQL and MySQL:
|
|
278
|
+
|
|
279
|
+
| Feature | PostgreSQL | MySQL | Repository Behavior |
|
|
280
|
+
|---------|------------|-------|-------------------|
|
|
281
|
+
| Parameters | `$1, $2, $3` | `?, ?, ?` | Automatically converted |
|
|
282
|
+
| RETURNING | Supported | Not supported | Uses RETURNING when available, falls back to SELECT |
|
|
283
|
+
| INSERT ID | RETURNING * | LAST_INSERT_ID() | Handles both approaches |
|
|
284
|
+
| Timestamps | NOW() | NOW() | Consistent across both |
|
|
285
|
+
|
|
286
|
+
## ๐ง Custom Repository Examples
|
|
287
|
+
|
|
288
|
+
### Basic Custom Repository
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
class ProductRepository extends BaseRepository<Product> {
|
|
292
|
+
constructor(db: DatabaseClient) {
|
|
293
|
+
super(db, 'products');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async findByCategory(categoryId: number): Promise<Product[]> {
|
|
297
|
+
return this.findMany('category_id = ?', [categoryId]);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async findFeatured(): Promise<Product[]> {
|
|
301
|
+
return this.findMany('featured = ?', [true]);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async searchByName(name: string): Promise<Product[]> {
|
|
305
|
+
return this.findMany('name ILIKE ?', [`%${name}%`]);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Repository with Complex Queries
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
class OrderRepository extends BaseRepository<Order> {
|
|
314
|
+
constructor(db: DatabaseClient) {
|
|
315
|
+
super(db, 'orders');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async findByDateRange(startDate: Date, endDate: Date): Promise<Order[]> {
|
|
319
|
+
return this.findMany(
|
|
320
|
+
'created_at >= ? AND created_at <= ?',
|
|
321
|
+
[startDate, endDate]
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async getMonthlyStats(year: number, month: number) {
|
|
326
|
+
const sql = `
|
|
327
|
+
SELECT
|
|
328
|
+
COUNT(*) as total_orders,
|
|
329
|
+
SUM(total_amount) as total_revenue,
|
|
330
|
+
AVG(total_amount) as avg_order_value
|
|
331
|
+
FROM orders
|
|
332
|
+
WHERE EXTRACT(YEAR FROM created_at) = ?
|
|
333
|
+
AND EXTRACT(MONTH FROM created_at) = ?
|
|
334
|
+
`;
|
|
335
|
+
|
|
336
|
+
const result = await this.rawQuery<{
|
|
337
|
+
total_orders: number;
|
|
338
|
+
total_revenue: number;
|
|
339
|
+
avg_order_value: number;
|
|
340
|
+
}>(sql, [year, month]);
|
|
341
|
+
|
|
342
|
+
return result.rows[0];
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Repository with Soft Deletes
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
interface SoftDeletableEntity extends BaseEntity {
|
|
351
|
+
deleted_at?: Date;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
class UserRepository extends BaseRepository<User & SoftDeletableEntity> {
|
|
355
|
+
constructor(db: DatabaseClient) {
|
|
356
|
+
super(db, 'users');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Override findMany to exclude soft-deleted records by default
|
|
360
|
+
async findMany(condition?: string, params: any[] = []): Promise<User[]> {
|
|
361
|
+
const softDeleteCondition = 'deleted_at IS NULL';
|
|
362
|
+
|
|
363
|
+
if (condition) {
|
|
364
|
+
condition = `${condition} AND ${softDeleteCondition}`;
|
|
365
|
+
} else {
|
|
366
|
+
condition = softDeleteCondition;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return super.findMany(condition, params);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Soft delete method
|
|
373
|
+
async softDelete(id: number): Promise<boolean> {
|
|
374
|
+
const updated = await this.update(id, {
|
|
375
|
+
deleted_at: new Date()
|
|
376
|
+
} as Partial<User>);
|
|
377
|
+
return updated !== null;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Find including soft-deleted records
|
|
381
|
+
async findAllIncludingDeleted(): Promise<User[]> {
|
|
382
|
+
return super.findMany(); // Bypass the override
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## ๐งฉ Integration with Service Layer
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
import { UserRepository } from './repositories/UserRepository';
|
|
391
|
+
import { createLogger } from '@groundbrick/logger';
|
|
392
|
+
|
|
393
|
+
class UserService {
|
|
394
|
+
private logger = createLogger().child('user-service');
|
|
395
|
+
|
|
396
|
+
constructor(private userRepository: UserRepository) {}
|
|
397
|
+
|
|
398
|
+
async createUser(userData: CreateUserRequest): Promise<User> {
|
|
399
|
+
// Validation happens here in service layer
|
|
400
|
+
if (!userData.email || !userData.name) {
|
|
401
|
+
throw new Error('Email and name are required');
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Check if user already exists
|
|
405
|
+
const existing = await this.userRepository.findByEmail(userData.email);
|
|
406
|
+
if (existing) {
|
|
407
|
+
throw new Error('User with this email already exists');
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Create user (repository handles the database operation)
|
|
411
|
+
const user = await this.userRepository.create({
|
|
412
|
+
name: userData.name,
|
|
413
|
+
email: userData.email,
|
|
414
|
+
active: true
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
this.logger.info('User created successfully', { userId: user.id });
|
|
418
|
+
return user;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
async getUserById(id: number): Promise<User> {
|
|
422
|
+
const user = await this.userRepository.findById(id);
|
|
423
|
+
if (!user) {
|
|
424
|
+
throw new Error('User not found');
|
|
425
|
+
}
|
|
426
|
+
return user;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
## ๐ก Best Practices
|
|
432
|
+
|
|
433
|
+
### 1. Keep Repositories Simple
|
|
434
|
+
Repositories should focus on data access, not business logic:
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
// โ
Good - Simple data access
|
|
438
|
+
async findActiveUsers(): Promise<User[]> {
|
|
439
|
+
return this.findMany('active = ?', [true]);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// โ Bad - Business logic in repository
|
|
443
|
+
async createUserWithValidation(userData: any): Promise<User> {
|
|
444
|
+
// Validation should be in service layer
|
|
445
|
+
if (!userData.email) throw new Error('Email required');
|
|
446
|
+
return this.create(userData);
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### 2. Use Type Safety
|
|
451
|
+
Always define proper entity interfaces:
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
// โ
Good - Properly typed
|
|
455
|
+
interface User extends BaseEntity {
|
|
456
|
+
name: string;
|
|
457
|
+
email: string;
|
|
458
|
+
active: boolean;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// โ Bad - Losing type safety
|
|
462
|
+
interface User {
|
|
463
|
+
[key: string]: any;
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### 3. Custom Methods for Domain Logic
|
|
468
|
+
Add repository methods that make sense for your domain:
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
class UserRepository extends BaseRepository<User> {
|
|
472
|
+
// Domain-specific finder methods
|
|
473
|
+
async findByEmail(email: string): Promise<User | null> {
|
|
474
|
+
return this.findOne('email = ?', [email]);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
async findActiveUsersInDepartment(departmentId: number): Promise<User[]> {
|
|
478
|
+
return this.findMany(
|
|
479
|
+
'active = ? AND department_id = ?',
|
|
480
|
+
[true, departmentId]
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
### 4. Error Handling
|
|
487
|
+
Let errors bubble up to the service layer:
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
class UserService {
|
|
491
|
+
async getUser(id: number): Promise<User> {
|
|
492
|
+
try {
|
|
493
|
+
const user = await this.userRepository.findById(id);
|
|
494
|
+
if (!user) {
|
|
495
|
+
throw new Error('User not found');
|
|
496
|
+
}
|
|
497
|
+
return user;
|
|
498
|
+
} catch (error) {
|
|
499
|
+
this.logger.error('Failed to get user', error, { userId: id });
|
|
500
|
+
throw error; // Re-throw for caller to handle
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
## โ๏ธ Performance Tips
|
|
507
|
+
|
|
508
|
+
### 1. Use Appropriate Queries
|
|
509
|
+
```typescript
|
|
510
|
+
// โ
Good - Specific condition
|
|
511
|
+
const activeUsers = await userRepository.findMany('active = ?', [true]);
|
|
512
|
+
|
|
513
|
+
// โ Bad - Loading all then filtering
|
|
514
|
+
const allUsers = await userRepository.findAll();
|
|
515
|
+
const activeUsers = allUsers.filter(u => u.active);
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### 2. Pagination for Large Datasets
|
|
519
|
+
```typescript
|
|
520
|
+
// โ
Good - Paginated
|
|
521
|
+
const users = await userRepository.findPaginated({
|
|
522
|
+
pagination: { limit: 20, offset: 0 }
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
// โ Bad - Loading everything
|
|
526
|
+
const allUsers = await userRepository.findAll();
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### 3. Count Before Fetching
|
|
530
|
+
```typescript
|
|
531
|
+
// โ
Good - Check count first
|
|
532
|
+
const userCount = await userRepository.count('active = ?', [true]);
|
|
533
|
+
if (userCount > 1000) {
|
|
534
|
+
// Handle large dataset differently
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
## ๐งฌ Dependencies
|
|
539
|
+
|
|
540
|
+
This package depends on:
|
|
541
|
+
- `@groundbrick/db-core` - Database interfaces and types
|
|
542
|
+
- `@groundbrick/logger` - Logging functionality
|
|
543
|
+
|
|
544
|
+
## ๐ License
|
|
545
|
+
|
|
546
|
+
MIT
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { DatabaseClient, DatabaseTransaction, QueryResult } from '@groundbrick/db-core';
|
|
2
|
+
import { Logger } from '@groundbrick/logger';
|
|
3
|
+
import { RepositoryEntity, CreateEntityData, UpdateEntityData, QueryOptions, FindManyOptions, PaginatedResult, PaginationOptions } from '../types';
|
|
4
|
+
import { SqlAdapter } from './SqlAdapter.js';
|
|
5
|
+
/**
|
|
6
|
+
* Generic base repository providing CRUD operations for any entity type
|
|
7
|
+
*/
|
|
8
|
+
export declare abstract class BaseRepository<T extends RepositoryEntity> {
|
|
9
|
+
protected readonly db: DatabaseClient;
|
|
10
|
+
protected readonly tableName: string;
|
|
11
|
+
protected readonly logger: Logger;
|
|
12
|
+
protected readonly sqlAdapter: SqlAdapter;
|
|
13
|
+
protected readonly dbType: 'postgresql' | 'mysql';
|
|
14
|
+
constructor(db: DatabaseClient, tableName: string, logger?: Logger);
|
|
15
|
+
/**
|
|
16
|
+
* Find a record by ID
|
|
17
|
+
*/
|
|
18
|
+
findById(id: number | string): Promise<T | null>;
|
|
19
|
+
/**
|
|
20
|
+
* Find one record by condition
|
|
21
|
+
*/
|
|
22
|
+
findOne(condition: string, params?: any[]): Promise<T | null>;
|
|
23
|
+
/**
|
|
24
|
+
* Find multiple records by condition
|
|
25
|
+
*/
|
|
26
|
+
findMany(condition?: string, params?: any[]): Promise<T[]>;
|
|
27
|
+
/**
|
|
28
|
+
* Find all records with optional query options
|
|
29
|
+
*/
|
|
30
|
+
findAll(options?: QueryOptions): Promise<T[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Create a new record
|
|
33
|
+
*/
|
|
34
|
+
create(data: CreateEntityData<T>): Promise<T>;
|
|
35
|
+
/**
|
|
36
|
+
* Update a record by ID
|
|
37
|
+
*/
|
|
38
|
+
update(id: number | string, data: UpdateEntityData<T>): Promise<T | null>;
|
|
39
|
+
/**
|
|
40
|
+
* Delete a record by ID
|
|
41
|
+
*/
|
|
42
|
+
delete(id: number | string): Promise<boolean>;
|
|
43
|
+
/**
|
|
44
|
+
* Count records with optional condition
|
|
45
|
+
*/
|
|
46
|
+
count(condition?: string, params?: any[]): Promise<number>;
|
|
47
|
+
/**
|
|
48
|
+
* Check if a record exists by ID
|
|
49
|
+
*/
|
|
50
|
+
exists(id: number | string): Promise<boolean>;
|
|
51
|
+
/**
|
|
52
|
+
* Find records with pagination
|
|
53
|
+
*/
|
|
54
|
+
findPaginated(options: FindManyOptions & {
|
|
55
|
+
pagination: PaginationOptions;
|
|
56
|
+
}): Promise<PaginatedResult<T>>;
|
|
57
|
+
/**
|
|
58
|
+
* Execute a raw SQL query (use with caution)
|
|
59
|
+
*/
|
|
60
|
+
rawQuery<R = any>(sql: string, params?: any[]): Promise<QueryResult<R>>;
|
|
61
|
+
/**
|
|
62
|
+
* Helper method for transaction-aware create operations
|
|
63
|
+
*/
|
|
64
|
+
protected createWithTransaction(tx: DatabaseTransaction, data: Partial<T>): Promise<T>;
|
|
65
|
+
/**
|
|
66
|
+
* Helper method for transaction-aware update operations
|
|
67
|
+
*/
|
|
68
|
+
protected updateWithTransaction(tx: DatabaseTransaction, id: number, data: Partial<T>): Promise<T | null>;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=BaseRepository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaseRepository.d.ts","sourceRoot":"","sources":["../../src/core/BaseRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxF,OAAO,EAAE,MAAM,EAAgB,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EACH,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,eAAe,EACf,iBAAiB,EACpB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C;;GAEG;AACH,8BAAsB,cAAc,CAAC,CAAC,SAAS,gBAAgB;IAMvD,SAAS,CAAC,QAAQ,CAAC,EAAE,EAAE,cAAc;IACrC,SAAS,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM;IANxC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAClC,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAC1C,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC;gBAG3B,EAAE,EAAE,cAAc,EAClB,SAAS,EAAE,MAAM,EACpC,MAAM,CAAC,EAAE,MAAM;IAmBnB;;OAEG;IACG,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IA4BtD;;OAEG;IACG,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,GAAE,GAAG,EAAO,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAiCvE;;OAEG;IACG,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,GAAE,GAAG,EAAO,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAoCpE;;OAEG;IACG,OAAO,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IA2CvD;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAqDnD;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IA+D/E;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAkCnD;;OAEG;IACG,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,GAAE,GAAG,EAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAiCpE;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUnD;;OAEG;IACG,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG;QAAE,UAAU,EAAE,iBAAiB,CAAA;KAAE,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAwC9G;;OAEG;IACG,QAAQ,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,GAAG,EAAO,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAgCjF;;OAEG;cACa,qBAAqB,CAAC,EAAE,EAAE,mBAAmB,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAsB5F;;OAEG;cACa,qBAAqB,CAAC,EAAE,EAAE,mBAAmB,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;CA8BlH"}
|