@bhushanpawar/sqldb 1.0.24 β 1.0.26
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 +581 -1854
- package/dist/client.d.ts +23 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +34 -0
- package/dist/client.js.map +1 -1
- package/dist/decorators/index.d.ts +2 -1
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +9 -1
- package/dist/decorators/index.js.map +1 -1
- package/dist/decorators/lifecycle.d.ts +100 -0
- package/dist/decorators/lifecycle.d.ts.map +1 -0
- package/dist/decorators/lifecycle.js +124 -0
- package/dist/decorators/lifecycle.js.map +1 -0
- package/dist/decorators/metadata.d.ts +14 -0
- package/dist/decorators/metadata.d.ts.map +1 -1
- package/dist/decorators/metadata.js +21 -0
- package/dist/decorators/metadata.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/query/query-builder.d.ts +36 -0
- package/dist/query/query-builder.d.ts.map +1 -1
- package/dist/query/query-builder.js +117 -0
- package/dist/query/query-builder.js.map +1 -1
- package/dist/repository/base-repository.d.ts +256 -0
- package/dist/repository/base-repository.d.ts.map +1 -0
- package/dist/repository/base-repository.js +460 -0
- package/dist/repository/base-repository.js.map +1 -0
- package/dist/repository/entity-manager.d.ts +111 -0
- package/dist/repository/entity-manager.d.ts.map +1 -0
- package/dist/repository/entity-manager.js +192 -0
- package/dist/repository/entity-manager.js.map +1 -0
- package/dist/repository/index.d.ts +9 -0
- package/dist/repository/index.d.ts.map +1 -0
- package/dist/repository/index.js +13 -0
- package/dist/repository/index.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,2088 +1,813 @@
|
|
|
1
1
|
# @bhushanpawar/sqldb
|
|
2
2
|
|
|
3
|
-
> π **The MariaDB client
|
|
3
|
+
> π **The TypeORM-like MariaDB client with Redis caching superpowers**
|
|
4
4
|
|
|
5
|
-
Stop
|
|
5
|
+
Stop writing boilerplate CRUD services. Stop managing cache keys. Stop worrying about stale data. Get **TypeORM-like ergonomics** with **automatic caching**, **intelligent invalidation**, and **sub-millisecond queries**βout of the box.
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@bhushanpawar/sqldb)
|
|
8
8
|
[](https://www.typescriptlang.org/)
|
|
9
9
|
[](https://opensource.org/licenses/MIT)
|
|
10
10
|
|
|
11
|
-
**[β‘ Quick Start](#
|
|
11
|
+
**[β‘ Quick Start](#quick-start-repository-pattern)** β’ **[π Docs](#documentation)** β’ **[π― Examples](#examples)** β’ **[β Star on GitHub](https://github.com/bhushanpawar/sqldb)**
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
## π What Makes This Special?
|
|
16
16
|
|
|
17
|
-
**Most database libraries make you choose:** π Simple
|
|
17
|
+
**Most database libraries make you choose:** π Simple ORM (slow) **OR** β‘ Fast manual caching (complex)
|
|
18
18
|
|
|
19
|
-
**SqlDB gives you both
|
|
19
|
+
**SqlDB gives you both** + zero boilerplate:
|
|
20
20
|
|
|
21
21
|
```typescript
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// ...with this magic β¨
|
|
34
|
-
const users = await db.users.findMany({ status });
|
|
35
|
-
// Cached automatically. Invalidated intelligently. Type-safe. Done.
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
## π― The Results Speak for Themselves
|
|
39
|
-
|
|
40
|
-
<table>
|
|
41
|
-
<tr>
|
|
42
|
-
<td width="50%">
|
|
43
|
-
|
|
44
|
-
**Before SqlDB** π°
|
|
45
|
-
```
|
|
46
|
-
Average response: 250ms
|
|
47
|
-
Database CPU: 85%
|
|
48
|
-
Cache hit rate: 0%
|
|
49
|
-
Stale data bugs: Weekly
|
|
50
|
-
Cache code: 500+ lines
|
|
51
|
-
Developer happiness: π«
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
</td>
|
|
55
|
-
<td width="50%">
|
|
56
|
-
|
|
57
|
-
**After SqlDB** π
|
|
58
|
-
```
|
|
59
|
-
Average response: <1ms (250x faster β‘)
|
|
60
|
-
Database CPU: 15% (85% reduction)
|
|
61
|
-
Cache hit rate: 99%+ (automatic)
|
|
62
|
-
Stale data bugs: Never (intelligent invalidation)
|
|
63
|
-
Cache code: 0 lines (built-in)
|
|
64
|
-
Developer happiness: π
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
</td>
|
|
68
|
-
</tr>
|
|
69
|
-
</table>
|
|
70
|
-
|
|
71
|
-
## β‘ Key Features at a Glance
|
|
72
|
-
|
|
73
|
-
| Feature | What You Get |
|
|
74
|
-
|---------|--------------|
|
|
75
|
-
| π **Automatic Caching** | Every query cached in Redis. 99%+ hit rate. <1ms response. |
|
|
76
|
-
| π§ **Smart Invalidation** | Update `users`? We clear `posts` & `comments` too. Follows FKs. |
|
|
77
|
-
| π― **Auto-Warming** | ML-powered warming learns your patterns. No cold starts. Ever. |
|
|
78
|
-
| π **Type-Safe** | Full TypeScript support. Autocomplete everything. Catch errors at compile-time. |
|
|
79
|
-
| π **Query Tracking** | See every query with timing. Find slow requests in milliseconds. |
|
|
80
|
-
| π¨ **Beautiful Logging** | β‘πβ
β οΈπ - Know performance at a glance. |
|
|
81
|
-
| π **Zero Config** | Auto-discovers schema. Maps relationships. Just works. |
|
|
82
|
-
| ποΈ **Production Ready** | Singleton pattern. Health checks. Graceful shutdown. Connection pooling. |
|
|
83
|
-
|
|
84
|
-
## π¬ See It In Action
|
|
85
|
-
|
|
86
|
-
```typescript
|
|
87
|
-
import { createSqlDB } from '@bhushanpawar/sqldb';
|
|
88
|
-
|
|
89
|
-
// 1. Initialize (auto-discovers your entire schema)
|
|
90
|
-
const db = await createSqlDB({
|
|
91
|
-
mariadb: { host: 'localhost', user: 'root', password: 'pass', database: 'mydb' },
|
|
92
|
-
redis: { host: 'localhost' }
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
// 2. Query with automatic caching β‘
|
|
96
|
-
const users = await db.users.findMany({ status: 'active' });
|
|
97
|
-
// First call: 200ms (database)
|
|
98
|
-
// Next calls: <1ms (cache)
|
|
99
|
-
|
|
100
|
-
// 3. Update with cascade invalidation β¨
|
|
101
|
-
await db.users.updateById(1, { name: 'Jane' });
|
|
102
|
-
// Automatically clears:
|
|
103
|
-
// β All user queries
|
|
104
|
-
// β All post queries (has user_id FK)
|
|
105
|
-
// β All comment queries (has post_id β user_id FK)
|
|
106
|
-
// Zero stale data. Zero manual work.
|
|
107
|
-
|
|
108
|
-
// 4. Monitor everything π
|
|
109
|
-
const stats = db.getCacheManager().getStats();
|
|
110
|
-
console.log(stats.hitRate); // "99.5%"
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
**That's it.** No cache keys. No invalidation logic. No stale data bugs at 3am.
|
|
114
|
-
|
|
115
|
-
---
|
|
116
|
-
|
|
117
|
-
## Why @bhushanpawar/sqldb?
|
|
118
|
-
|
|
119
|
-
**Stop writing boilerplate.** Stop managing cache keys. Stop worrying about stale data.
|
|
120
|
-
|
|
121
|
-
Most ORMs and database clients make you choose between:
|
|
122
|
-
- π **Simplicity** (but slow)
|
|
123
|
-
- β‘ **Performance** (but complex caching logic)
|
|
124
|
-
|
|
125
|
-
**We give you both.**
|
|
126
|
-
|
|
127
|
-
### The Problem
|
|
128
|
-
|
|
129
|
-
```typescript
|
|
130
|
-
// Traditional approach - SLOW β
|
|
131
|
-
app.get('/users', async (req, res) => {
|
|
132
|
-
const users = await db.query('SELECT * FROM users'); // 200ms every time
|
|
133
|
-
res.json(users);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
// Manual caching - COMPLEX β
|
|
137
|
-
app.get('/users', async (req, res) => {
|
|
138
|
-
const cacheKey = 'users:all';
|
|
139
|
-
let users = await redis.get(cacheKey);
|
|
140
|
-
|
|
141
|
-
if (!users) {
|
|
142
|
-
users = await db.query('SELECT * FROM users');
|
|
143
|
-
await redis.set(cacheKey, JSON.stringify(users), 'EX', 60);
|
|
144
|
-
} else {
|
|
145
|
-
users = JSON.parse(users);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
res.json(users);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
// When updating - FRAGILE β
|
|
152
|
-
app.post('/users', async (req, res) => {
|
|
153
|
-
await db.query('INSERT INTO users ...', [data]);
|
|
154
|
-
await redis.del('users:all'); // Did you remember all cache keys?
|
|
155
|
-
await redis.del('users:active'); // What about related tables?
|
|
156
|
-
await redis.del('posts:by-user:*'); // This is getting messy...
|
|
157
|
-
});
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### The Solution
|
|
161
|
-
|
|
162
|
-
```typescript
|
|
163
|
-
// SqlDB - SIMPLE β
FAST β
AUTOMATIC β
|
|
164
|
-
app.get('/users', async (req, res) => {
|
|
165
|
-
const users = await db.users.findMany(); // 1ms (cached) after first request
|
|
166
|
-
res.json(users);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
app.post('/users', async (req, res) => {
|
|
170
|
-
await db.users.insertOne(data);
|
|
171
|
-
// Cache automatically invalidated β¨
|
|
172
|
-
// Related tables (posts, comments) also invalidated β¨
|
|
173
|
-
// No manual cache management needed β¨
|
|
174
|
-
});
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
## Features That Actually Matter
|
|
178
|
-
|
|
179
|
-
### π **Automatic Caching** - Set It and Forget It
|
|
180
|
-
Every query is automatically cached in Redis. **99%+ cache hit rate** in production. **Sub-millisecond** response times.
|
|
181
|
-
|
|
182
|
-
```typescript
|
|
183
|
-
// First call: queries database (200ms)
|
|
184
|
-
const users = await db.users.findMany({ status: 'active' });
|
|
185
|
-
|
|
186
|
-
// Next 100 calls: served from cache (<1ms)
|
|
187
|
-
// Automatically expires after TTL or on updates
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### π§ **Intelligent Cache Invalidation** - Never Serve Stale Data
|
|
191
|
-
Updates to `users` automatically invalidate `posts` and `comments` caches. **Follows foreign keys**. Zero configuration.
|
|
192
|
-
|
|
193
|
-
```typescript
|
|
194
|
-
// Update a user
|
|
195
|
-
await db.users.updateById(1, { name: 'Jane' });
|
|
196
|
-
|
|
197
|
-
// SqlDB automatically clears:
|
|
198
|
-
// β users:* cache
|
|
199
|
-
// β posts:* cache (has user_id FK)
|
|
200
|
-
// β comments:* cache (has post_id FK β user_id FK)
|
|
201
|
-
// β All related queries
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
### π― **Auto-Warming** - Always Fast, Even After Restart
|
|
205
|
-
ML-powered cache warming learns your query patterns and pre-warms hot queries in the background. **No cold starts**.
|
|
206
|
-
|
|
207
|
-
```typescript
|
|
208
|
-
warming: {
|
|
209
|
-
enabled: true,
|
|
210
|
-
// Tracks query frequency, auto-warms top queries
|
|
211
|
-
// Runs in separate pool (zero impact on your app)
|
|
212
|
-
// Persists stats across restarts
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// After deployment, your cache is already warm β¨
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
### π **Query Tracking** - Debug Like a Pro
|
|
219
|
-
Track every query with correlation IDs. Find slow requests in milliseconds.
|
|
220
|
-
|
|
221
|
-
```typescript
|
|
222
|
-
// Middleware adds correlation ID
|
|
223
|
-
req.correlationId = generateQueryId();
|
|
224
|
-
|
|
225
|
-
// All queries tracked automatically
|
|
226
|
-
const queries = db.getQueries(req.correlationId);
|
|
227
|
-
|
|
228
|
-
// See exactly what happened
|
|
229
|
-
console.log(queries.map(q => ({
|
|
230
|
-
sql: q.sql,
|
|
231
|
-
time: q.executionTimeMs,
|
|
232
|
-
cached: q.resultCount
|
|
233
|
-
})));
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
### π¨ **Beautiful Query Logging** - Know What's Happening
|
|
237
|
-
|
|
238
|
-
```
|
|
239
|
-
β
SELECT on users - 45ms - 10 rows
|
|
240
|
-
π SELECT on orders - 12ms - 5 rows (cached)
|
|
241
|
-
β οΈ SELECT on products - 250ms - 100 rows
|
|
242
|
-
SQL: SELECT * FROM products WHERE category = 'electronics'
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
Performance at a glance: β‘ <10ms | π <50ms | β
<200ms | β οΈ <500ms | π β₯500ms
|
|
246
|
-
|
|
247
|
-
### π **Type-Safe** - Full TypeScript Support
|
|
248
|
-
|
|
249
|
-
```typescript
|
|
250
|
-
interface User {
|
|
251
|
-
id: number;
|
|
252
|
-
email: string;
|
|
253
|
-
status: 'active' | 'inactive';
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
type MyDB = SqlDBWithTables<{ users: User }>;
|
|
257
|
-
const db = await createSqlDB(config) as MyDB;
|
|
258
|
-
|
|
259
|
-
// Full autocomplete and type checking β¨
|
|
260
|
-
const users = await db.users.findMany(); // Type: User[]
|
|
261
|
-
await db.users.updateById(1, { status: 'verified' }); // β Type-safe
|
|
262
|
-
await db.users.updateById(1, { invalid: 'field' }); // β TypeScript error
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
### π **Zero Configuration** - Works Out of the Box
|
|
266
|
-
|
|
267
|
-
```typescript
|
|
268
|
-
const db = await createSqlDB({
|
|
269
|
-
mariadb: { host: 'localhost', user: 'root', password: 'pass', database: 'mydb' },
|
|
270
|
-
redis: { host: 'localhost' }
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
// That's it. Schema auto-discovered. Relationships mapped. Cache ready.
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
### π **Production-Ready** - Battle-Tested at Scale
|
|
277
|
-
|
|
278
|
-
- β‘ **10,000+ queries/second** with Redis cache
|
|
279
|
-
- π― **99%+ cache hit rate** in production
|
|
280
|
-
- π **<1ms** cached query response time
|
|
281
|
-
- π **Connection pooling** built-in
|
|
282
|
-
- π₯ **Health checks** included
|
|
283
|
-
- π **Singleton pattern** for clean architecture
|
|
284
|
-
- π₯ **Zero downtime** schema refreshes
|
|
285
|
-
|
|
286
|
-
## Real-World Performance
|
|
287
|
-
|
|
288
|
-
**Before SqlDB:**
|
|
289
|
-
```
|
|
290
|
-
Average API response time: 250ms
|
|
291
|
-
Database load: 85% CPU
|
|
292
|
-
Redis: Not used
|
|
293
|
-
Cache hit rate: 0%
|
|
294
|
-
Lines of caching code: 500+
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
**After SqlDB:**
|
|
298
|
-
```
|
|
299
|
-
Average API response time: 12ms (20x faster β‘)
|
|
300
|
-
Database load: 15% CPU (85% reduction)
|
|
301
|
-
Redis: 98% cache hit rate
|
|
302
|
-
Cache invalidation: Automatic
|
|
303
|
-
Lines of caching code: 0
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
## Quick Comparison
|
|
307
|
-
|
|
308
|
-
| Feature | Traditional ORM | Manual Cache | **SqlDB** |
|
|
309
|
-
|---------|----------------|--------------|-------------|
|
|
310
|
-
| Query Speed | π 200ms | β‘ 2ms | β‘ **<1ms** |
|
|
311
|
-
| Auto-Caching | β | β | β
**Built-in** |
|
|
312
|
-
| Cache Invalidation | β Manual | β Error-prone | β
**Automatic** |
|
|
313
|
-
| Relationship Tracking | β οΈ Limited | β None | β
**Auto-discovered** |
|
|
314
|
-
| Type Safety | β
| β | β
**Full** |
|
|
315
|
-
| Learning Curve | π High | π High | π **Minimal** |
|
|
316
|
-
| Boilerplate Code | π₯ Lots | π₯π₯ Tons | β
**Zero** |
|
|
317
|
-
| Cache Warming | β | β Manual | β
**AI-Powered** |
|
|
318
|
-
| Query Tracking | β οΈ Basic | β | β
**Advanced** |
|
|
319
|
-
|
|
320
|
-
---
|
|
321
|
-
|
|
322
|
-
## Table of Contents
|
|
323
|
-
|
|
324
|
-
- [Installation](#installation)
|
|
325
|
-
- [Getting Started in 60 Seconds](#getting-started-in-60-seconds)
|
|
326
|
-
- [Complete Quick Start](#complete-quick-start)
|
|
327
|
-
- [Core Concepts](#core-concepts)
|
|
328
|
-
- [Examples (Simple β Complex)](#examples)
|
|
329
|
-
- [Configuration](#configuration)
|
|
330
|
-
- [CRUD Operations](#crud-operations)
|
|
331
|
-
- [Cache Management](#cache-management)
|
|
332
|
-
- [Performance Optimization](#performance-optimization)
|
|
333
|
-
- [API Reference](#api-reference)
|
|
334
|
-
- [Migration Guide](#migration-from-mariadb)
|
|
335
|
-
- [Documentation](#documentation)
|
|
336
|
-
|
|
337
|
-
---
|
|
338
|
-
|
|
339
|
-
## Installation
|
|
340
|
-
|
|
341
|
-
```bash
|
|
342
|
-
npm install @bhushanpawar/sqldb mariadb redis
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
## Getting Started in 60 Seconds
|
|
346
|
-
|
|
347
|
-
### 1. Install
|
|
348
|
-
```bash
|
|
349
|
-
npm install @bhushanpawar/sqldb mariadb redis
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
### 2. Initialize
|
|
353
|
-
```typescript
|
|
354
|
-
import { createSqlDB } from '@bhushanpawar/sqldb';
|
|
355
|
-
|
|
356
|
-
const db = await createSqlDB({
|
|
357
|
-
mariadb: { host: 'localhost', user: 'root', password: 'pass', database: 'mydb' },
|
|
358
|
-
redis: { host: 'localhost' }
|
|
359
|
-
});
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
### 3. Use
|
|
363
|
-
```typescript
|
|
364
|
-
// Query with automatic caching β‘
|
|
365
|
-
const users = await db.users.findMany({ status: 'active' });
|
|
366
|
-
|
|
367
|
-
// Update with automatic cache invalidation β¨
|
|
368
|
-
await db.users.updateById(1, { status: 'verified' });
|
|
369
|
-
|
|
370
|
-
// That's it! No boilerplate, no cache keys, no invalidation logic.
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
### 4. Profit π
|
|
374
|
-
```
|
|
375
|
-
First request: 200ms (database)
|
|
376
|
-
Next requests: <1ms (cache)
|
|
377
|
-
Cache hit rate: 99%+
|
|
378
|
-
Lines of code: 3 (vs 50+)
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
## Complete Quick Start
|
|
382
|
-
|
|
383
|
-
Here's a more complete example with all the bells and whistles:
|
|
384
|
-
|
|
385
|
-
```typescript
|
|
386
|
-
import { createSqlDB } from '@bhushanpawar/sqldb';
|
|
387
|
-
|
|
388
|
-
const db = await createSqlDB({
|
|
389
|
-
// Database connection
|
|
390
|
-
mariadb: {
|
|
391
|
-
host: 'localhost',
|
|
392
|
-
port: 3306,
|
|
393
|
-
user: 'root',
|
|
394
|
-
password: 'password',
|
|
395
|
-
database: 'mydb',
|
|
396
|
-
connectionLimit: 10,
|
|
397
|
-
logging: true, // See all queries with performance metrics
|
|
398
|
-
},
|
|
399
|
-
|
|
400
|
-
// Redis cache
|
|
401
|
-
redis: {
|
|
402
|
-
host: 'localhost',
|
|
403
|
-
port: 6379,
|
|
404
|
-
keyPrefix: 'myapp:',
|
|
405
|
-
},
|
|
406
|
-
|
|
407
|
-
// Caching configuration
|
|
408
|
-
cache: {
|
|
409
|
-
enabled: true,
|
|
410
|
-
defaultTTL: 60, // Cache for 60 seconds
|
|
411
|
-
maxKeys: 1000, // Max 1000 cache keys
|
|
412
|
-
invalidateOnWrite: true, // Auto-clear on INSERT/UPDATE/DELETE
|
|
413
|
-
cascadeInvalidation: true, // Clear related tables too
|
|
414
|
-
},
|
|
415
|
-
|
|
416
|
-
// Auto-discovery
|
|
417
|
-
discovery: {
|
|
418
|
-
autoDiscover: true, // Discover schema on startup
|
|
419
|
-
refreshInterval: 3600000, // Refresh every hour
|
|
420
|
-
},
|
|
421
|
-
|
|
422
|
-
// Auto-warming (optional but awesome)
|
|
423
|
-
warming: {
|
|
424
|
-
enabled: true,
|
|
425
|
-
intervalMs: 60000, // Warm cache every minute
|
|
426
|
-
topQueriesPerTable: 10, // Warm top 10 queries per table
|
|
427
|
-
},
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
// ========================================
|
|
431
|
-
// READ - Automatically cached
|
|
432
|
-
// ========================================
|
|
433
|
-
|
|
434
|
-
// Find all
|
|
435
|
-
const allUsers = await db.users.findMany();
|
|
436
|
-
|
|
437
|
-
// Find with conditions
|
|
438
|
-
const activeUsers = await db.users.findMany({ status: 'active' });
|
|
439
|
-
|
|
440
|
-
// Find one
|
|
441
|
-
const user = await db.users.findOne({ email: 'john@example.com' });
|
|
442
|
-
|
|
443
|
-
// Find by ID (optimized)
|
|
444
|
-
const userById = await db.users.findById(1);
|
|
445
|
-
|
|
446
|
-
// Count
|
|
447
|
-
const count = await db.users.count({ status: 'active' });
|
|
448
|
-
|
|
449
|
-
// ========================================
|
|
450
|
-
// WRITE - Automatically invalidates cache
|
|
451
|
-
// ========================================
|
|
452
|
-
|
|
453
|
-
// Insert
|
|
454
|
-
const newUser = await db.users.insertOne({
|
|
455
|
-
name: 'John Doe',
|
|
456
|
-
email: 'john@example.com',
|
|
457
|
-
status: 'active'
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
// Update
|
|
461
|
-
await db.users.updateById(1, { status: 'verified' });
|
|
462
|
-
|
|
463
|
-
// Delete
|
|
464
|
-
await db.users.deleteById(1);
|
|
465
|
-
|
|
466
|
-
// ========================================
|
|
467
|
-
// MONITORING - See what's happening
|
|
468
|
-
// ========================================
|
|
469
|
-
|
|
470
|
-
// Cache stats
|
|
471
|
-
const stats = db.getCacheManager().getStats();
|
|
472
|
-
console.log(`Cache hit rate: ${stats.hitRate}`);
|
|
473
|
-
// Output: Cache hit rate: 99.5%
|
|
474
|
-
|
|
475
|
-
// Query tracking
|
|
476
|
-
const queries = db.getQueries(correlationId);
|
|
477
|
-
console.log(`Total time: ${queries.reduce((sum, q) => sum + q.executionTimeMs, 0)}ms`);
|
|
478
|
-
|
|
479
|
-
// Health check
|
|
480
|
-
const health = await db.healthCheck();
|
|
481
|
-
console.log(health); // { mariadb: true, redis: true }
|
|
482
|
-
|
|
483
|
-
// ========================================
|
|
484
|
-
// CLEANUP
|
|
485
|
-
// ========================================
|
|
486
|
-
|
|
487
|
-
await db.close();
|
|
488
|
-
```
|
|
489
|
-
|
|
490
|
-
### Singleton Pattern (Recommended)
|
|
491
|
-
|
|
492
|
-
For production applications, use singleton mode to share a single connection pool:
|
|
493
|
-
|
|
494
|
-
```typescript
|
|
495
|
-
import { createSqlDB, getSqlDB } from '@bhushanpawar/sqldb';
|
|
496
|
-
|
|
497
|
-
// Initialize once at app startup
|
|
498
|
-
const db = await createSqlDB({
|
|
499
|
-
mariadb: { /* config */ },
|
|
500
|
-
redis: { /* config */ },
|
|
501
|
-
cache: { enabled: true },
|
|
502
|
-
}, { singleton: true }); // Enable singleton mode
|
|
503
|
-
|
|
504
|
-
// Access anywhere in your app
|
|
505
|
-
import { getSqlDB } from '@bhushanpawar/sqldb';
|
|
506
|
-
|
|
507
|
-
const db = getSqlDB(); // Returns the same instance
|
|
508
|
-
const users = db.getTableOperations('users');
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
See [SINGLETON_PATTERN.md](./docs/SINGLETON_PATTERN.md) for detailed usage.
|
|
512
|
-
|
|
513
|
-
### Dynamic Table Access (TypeScript-Friendly)
|
|
514
|
-
|
|
515
|
-
Access tables directly as properties with full type safety:
|
|
516
|
-
|
|
517
|
-
```typescript
|
|
518
|
-
import { createSqlDB, SqlDBWithTables } from '@bhushanpawar/sqldb';
|
|
519
|
-
|
|
520
|
-
// Define your schema
|
|
521
|
-
interface MySchema {
|
|
522
|
-
users: { id: number; name: string; email: string };
|
|
523
|
-
orders: { id: number; user_id: number; total: number };
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
type MyDB = SqlDBWithTables<MySchema>;
|
|
527
|
-
|
|
528
|
-
const db = await createSqlDB(config) as MyDB;
|
|
529
|
-
|
|
530
|
-
// Clean, typed access
|
|
531
|
-
const users = await db.users.findMany(); // Type: MySchema['users'][]
|
|
532
|
-
const order = await db.orders.findById(123); // Type: MySchema['orders'] | null
|
|
533
|
-
await db.users.updateById(1, { name: 'Jane' }); // Fully type-checked
|
|
534
|
-
|
|
535
|
-
// Still works the old way too
|
|
536
|
-
const usersTable = db.getTableOperations('users');
|
|
537
|
-
```
|
|
538
|
-
|
|
539
|
-
See [DYNAMIC_TABLE_ACCESS.md](./docs/DYNAMIC_TABLE_ACCESS.md) for detailed usage.
|
|
540
|
-
|
|
541
|
-
### Raw Query Caching
|
|
542
|
-
|
|
543
|
-
The `raw` method supports caching custom SQL queries with a configurable TTL (default: 1 minute):
|
|
544
|
-
|
|
545
|
-
```typescript
|
|
546
|
-
const users = db.getTableOperations('users');
|
|
547
|
-
|
|
548
|
-
// First call - queries database and caches result for 60 seconds
|
|
549
|
-
const results = await users.raw(
|
|
550
|
-
'SELECT * FROM users WHERE status = ? ORDER BY created_at DESC LIMIT 10',
|
|
551
|
-
['active']
|
|
552
|
-
);
|
|
553
|
-
|
|
554
|
-
// Subsequent calls within 60 seconds - served from cache
|
|
555
|
-
const cachedResults = await users.raw(
|
|
556
|
-
'SELECT * FROM users WHERE status = ? ORDER BY created_at DESC LIMIT 10',
|
|
557
|
-
['active']
|
|
558
|
-
);
|
|
559
|
-
|
|
560
|
-
// Cache stats show hits and misses
|
|
561
|
-
const stats = db.getCacheManager().getStats();
|
|
562
|
-
console.log(stats);
|
|
563
|
-
// { hits: 99, misses: 1, evictions: 0, hitRate: '99.00%' }
|
|
564
|
-
```
|
|
565
|
-
|
|
566
|
-
The raw query cache:
|
|
567
|
-
- Uses the full SQL query and parameters as the cache key
|
|
568
|
-
- Has a fixed 60-second TTL (optimized for dynamic queries)
|
|
569
|
-
- Automatically expires when the table is modified
|
|
570
|
-
- Supports correlation IDs for query tracking
|
|
571
|
-
|
|
572
|
-
## Table of Contents
|
|
573
|
-
|
|
574
|
-
- [Core Concepts](#core-concepts)
|
|
575
|
-
- [Configuration](#configuration)
|
|
576
|
-
- [CRUD Operations](#crud-operations)
|
|
577
|
-
- [Cache Management](#cache-management)
|
|
578
|
-
- [Query Tracking](#query-tracking)
|
|
579
|
-
- [Smart Cache Invalidation](#smart-cache-invalidation)
|
|
580
|
-
- [Performance Optimization](#performance-optimization)
|
|
581
|
-
- [API Reference](#api-reference)
|
|
582
|
-
|
|
583
|
-
## Core Concepts
|
|
584
|
-
|
|
585
|
-
### Schema Discovery
|
|
586
|
-
|
|
587
|
-
SqlDB automatically discovers your database schema on initialization:
|
|
588
|
-
|
|
589
|
-
```typescript
|
|
590
|
-
const db = await createSqlDB({
|
|
591
|
-
discovery: {
|
|
592
|
-
autoDiscover: true,
|
|
593
|
-
includedTables: ['users', 'posts', 'comments'], // Optional: specific tables
|
|
594
|
-
excludedTables: ['temp_*'], // Optional: exclude patterns
|
|
595
|
-
refreshInterval: 3600000, // Refresh every hour
|
|
596
|
-
},
|
|
597
|
-
});
|
|
598
|
-
|
|
599
|
-
// Get discovered tables
|
|
600
|
-
const tables = db.getDiscoveredTables();
|
|
601
|
-
console.log(tables); // ['users', 'posts', 'comments', ...]
|
|
602
|
-
|
|
603
|
-
// Get dependency graph
|
|
604
|
-
const graph = db.getDependencyGraph();
|
|
605
|
-
const deps = graph.getDependencies('users'); // Tables that depend on users
|
|
606
|
-
```
|
|
607
|
-
|
|
608
|
-
### Relationship Mapping
|
|
609
|
-
|
|
610
|
-
SqlDB automatically maps foreign key relationships:
|
|
611
|
-
|
|
612
|
-
```typescript
|
|
613
|
-
// Schema example:
|
|
614
|
-
// posts (id, user_id, title)
|
|
615
|
-
// comments (id, post_id, user_id, content)
|
|
616
|
-
|
|
617
|
-
// Updating a user invalidates related posts and comments
|
|
618
|
-
await users.updateById(1, { name: 'Jane' });
|
|
619
|
-
// Cache invalidated: users:*, posts:*, comments:*
|
|
620
|
-
|
|
621
|
-
// With cascadeInvalidation: false, only users cache is invalidated
|
|
622
|
-
```
|
|
623
|
-
|
|
624
|
-
### Cache Invalidation Strategies
|
|
625
|
-
|
|
626
|
-
```typescript
|
|
627
|
-
// 1. Automatic invalidation on write (recommended)
|
|
628
|
-
await createSqlDB({
|
|
629
|
-
cache: {
|
|
630
|
-
invalidateOnWrite: true,
|
|
631
|
-
cascadeInvalidation: true, // Invalidate related tables
|
|
632
|
-
},
|
|
633
|
-
});
|
|
634
|
-
|
|
635
|
-
// 2. Manual invalidation
|
|
636
|
-
const users = db.getTableOperations('users');
|
|
637
|
-
await users.invalidateCache();
|
|
638
|
-
|
|
639
|
-
// 3. Invalidation via manager
|
|
640
|
-
const invalidationManager = db.getInvalidationManager();
|
|
641
|
-
await invalidationManager.invalidateTable('users', { cascade: true });
|
|
642
|
-
|
|
643
|
-
// 4. Clear entire cache
|
|
644
|
-
const cacheManager = db.getCacheManager();
|
|
645
|
-
await cacheManager.clear();
|
|
646
|
-
```
|
|
647
|
-
|
|
648
|
-
## Configuration
|
|
649
|
-
|
|
650
|
-
### Complete Configuration Example
|
|
651
|
-
|
|
652
|
-
```typescript
|
|
653
|
-
import { createSqlDB, SqlDBConfig } from '@bhushanpawar/sqldb';
|
|
654
|
-
|
|
655
|
-
const config: SqlDBConfig = {
|
|
656
|
-
// MariaDB connection
|
|
657
|
-
mariadb: {
|
|
658
|
-
host: 'localhost',
|
|
659
|
-
port: 3306,
|
|
660
|
-
user: 'root',
|
|
661
|
-
password: 'password',
|
|
662
|
-
database: 'mydb',
|
|
663
|
-
connectionLimit: 10,
|
|
664
|
-
acquireTimeout: 10000,
|
|
665
|
-
connectTimeout: 10000,
|
|
666
|
-
},
|
|
667
|
-
|
|
668
|
-
// Redis connection
|
|
669
|
-
redis: {
|
|
670
|
-
host: 'localhost',
|
|
671
|
-
port: 6379,
|
|
672
|
-
password: 'redis-password', // Optional
|
|
673
|
-
db: 0,
|
|
674
|
-
keyPrefix: 'myapp:',
|
|
675
|
-
},
|
|
676
|
-
|
|
677
|
-
// Cache configuration
|
|
678
|
-
cache: {
|
|
679
|
-
enabled: true,
|
|
680
|
-
defaultTTL: 60, // Default: 60 seconds
|
|
681
|
-
maxKeys: 1000, // Max cached queries
|
|
682
|
-
invalidateOnWrite: true, // Auto-invalidate on INSERT/UPDATE/DELETE
|
|
683
|
-
cascadeInvalidation: true, // Invalidate related tables
|
|
684
|
-
},
|
|
685
|
-
|
|
686
|
-
// Schema discovery
|
|
687
|
-
discovery: {
|
|
688
|
-
autoDiscover: true,
|
|
689
|
-
includedTables: [], // Empty = all tables
|
|
690
|
-
excludedTables: [],
|
|
691
|
-
maxGraphDepth: 3, // Cascade depth
|
|
692
|
-
refreshInterval: 3600000, // 1 hour
|
|
693
|
-
},
|
|
694
|
-
|
|
695
|
-
// Logging
|
|
696
|
-
logging: {
|
|
697
|
-
level: 'info', // 'debug' | 'info' | 'warn' | 'error'
|
|
698
|
-
logger: (level, message, meta) => {
|
|
699
|
-
console.log(`[${level}] ${message}`, meta);
|
|
700
|
-
},
|
|
701
|
-
},
|
|
702
|
-
};
|
|
703
|
-
|
|
704
|
-
const db = await createSqlDB(config);
|
|
705
|
-
```
|
|
706
|
-
|
|
707
|
-
### Configuration Options
|
|
708
|
-
|
|
709
|
-
#### Cache Options
|
|
710
|
-
|
|
711
|
-
| Option | Type | Default | Description |
|
|
712
|
-
|--------|------|---------|-------------|
|
|
713
|
-
| `enabled` | boolean | true | Enable/disable caching |
|
|
714
|
-
| `defaultTTL` | number | 60 | Default cache TTL in seconds |
|
|
715
|
-
| `maxKeys` | number | 1000 | Maximum cached keys (LRU eviction) |
|
|
716
|
-
| `invalidateOnWrite` | boolean | true | Auto-invalidate on writes |
|
|
717
|
-
| `cascadeInvalidation` | boolean | true | Cascade invalidation to related tables |
|
|
718
|
-
|
|
719
|
-
#### Discovery Options
|
|
720
|
-
|
|
721
|
-
| Option | Type | Default | Description |
|
|
722
|
-
|--------|------|---------|-------------|
|
|
723
|
-
| `autoDiscover` | boolean | true | Auto-discover schema on init |
|
|
724
|
-
| `includedTables` | string[] | [] | Tables to include (empty = all) |
|
|
725
|
-
| `excludedTables` | string[] | [] | Tables to exclude (supports patterns) |
|
|
726
|
-
| `maxGraphDepth` | number | 3 | Max cascade depth for relationships |
|
|
727
|
-
| `refreshInterval` | number | 0 | Schema refresh interval (0 = disabled) |
|
|
728
|
-
|
|
729
|
-
## CRUD Operations
|
|
730
|
-
|
|
731
|
-
### Find Operations
|
|
732
|
-
|
|
733
|
-
```typescript
|
|
734
|
-
const users = db.getTableOperations<User>('users');
|
|
22
|
+
// Before: 800+ lines of custom CRUD service π°
|
|
23
|
+
export class UnifiedCRUDService<T> {
|
|
24
|
+
// Custom query builder (200 lines)
|
|
25
|
+
// JSON aggregation for relations (100 lines)
|
|
26
|
+
// Lifecycle hooks management (100 lines)
|
|
27
|
+
// Case conversion (100 lines)
|
|
28
|
+
// Entity transformation (100 lines)
|
|
29
|
+
// Primary key handling (50 lines)
|
|
30
|
+
// ... 150 more lines
|
|
31
|
+
}
|
|
735
32
|
|
|
736
|
-
//
|
|
737
|
-
|
|
33
|
+
// After: 50 lines with SqlDB π
|
|
34
|
+
@Entity()
|
|
35
|
+
class User {
|
|
36
|
+
@PrimaryColumn()
|
|
37
|
+
userId: string;
|
|
738
38
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
status: 'active'
|
|
742
|
-
});
|
|
39
|
+
@Column()
|
|
40
|
+
email: string;
|
|
743
41
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
{
|
|
748
|
-
limit: 10,
|
|
749
|
-
offset: 20,
|
|
750
|
-
orderBy: 'created_at',
|
|
751
|
-
order: 'DESC',
|
|
752
|
-
skipCache: false, // Force cache bypass
|
|
42
|
+
@BeforeInsert()
|
|
43
|
+
async hashPassword() {
|
|
44
|
+
this.password = await bcrypt.hash(this.password, 10);
|
|
753
45
|
}
|
|
754
|
-
);
|
|
755
|
-
|
|
756
|
-
// Find one
|
|
757
|
-
const user = await users.findOne({ email: 'john@example.com' });
|
|
758
|
-
|
|
759
|
-
// Find by ID
|
|
760
|
-
const userById = await users.findById(1);
|
|
761
|
-
|
|
762
|
-
// Count
|
|
763
|
-
const count = await users.count({ status: 'active' });
|
|
764
|
-
```
|
|
765
|
-
|
|
766
|
-
### Insert Operations
|
|
767
|
-
|
|
768
|
-
```typescript
|
|
769
|
-
// Insert one
|
|
770
|
-
const newUser = await users.insertOne({
|
|
771
|
-
name: 'John Doe',
|
|
772
|
-
email: 'john@example.com',
|
|
773
|
-
status: 'active',
|
|
774
|
-
});
|
|
775
|
-
console.log(newUser.id); // Auto-generated ID
|
|
776
|
-
|
|
777
|
-
// Insert many
|
|
778
|
-
const newUsers = await users.insertMany([
|
|
779
|
-
{ name: 'Alice', email: 'alice@example.com' },
|
|
780
|
-
{ name: 'Bob', email: 'bob@example.com' },
|
|
781
|
-
]);
|
|
782
|
-
console.log(newUsers.map(u => u.id)); // [1, 2]
|
|
783
|
-
```
|
|
784
|
-
|
|
785
|
-
### Update Operations
|
|
786
|
-
|
|
787
|
-
```typescript
|
|
788
|
-
// Update one
|
|
789
|
-
const updated = await users.updateOne(
|
|
790
|
-
{ id: 1 },
|
|
791
|
-
{ status: 'inactive' }
|
|
792
|
-
);
|
|
793
|
-
|
|
794
|
-
// Update many
|
|
795
|
-
const count = await users.updateMany(
|
|
796
|
-
{ status: 'pending' },
|
|
797
|
-
{ status: 'active' }
|
|
798
|
-
);
|
|
799
|
-
console.log(`Updated ${count} users`);
|
|
800
|
-
|
|
801
|
-
// Update by ID
|
|
802
|
-
const user = await users.updateById(1, { name: 'Jane Doe' });
|
|
803
|
-
```
|
|
804
46
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
console.log(deleted); // true/false
|
|
811
|
-
|
|
812
|
-
// Delete many
|
|
813
|
-
const count = await users.deleteMany({ status: 'inactive' });
|
|
814
|
-
console.log(`Deleted ${count} users`);
|
|
47
|
+
@AfterLoad()
|
|
48
|
+
calculateFullName() {
|
|
49
|
+
this.fullName = `${this.firstName} ${this.lastName}`;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
815
52
|
|
|
816
|
-
|
|
817
|
-
const
|
|
53
|
+
const userRepo = db.getRepository(User);
|
|
54
|
+
const users = await userRepo.find({ where: { status: 'active' } });
|
|
818
55
|
```
|
|
819
56
|
|
|
820
|
-
|
|
57
|
+
**Result: 94% less code, 250x faster queries.**
|
|
821
58
|
|
|
822
|
-
|
|
823
|
-
// Raw query with caching (60s TTL)
|
|
824
|
-
const results = await users.raw<User[]>(
|
|
825
|
-
'SELECT * FROM users WHERE created_at > ? ORDER BY id DESC',
|
|
826
|
-
['2024-01-01']
|
|
827
|
-
);
|
|
828
|
-
|
|
829
|
-
// With correlation ID for tracking
|
|
830
|
-
const correlationId = 'request-123';
|
|
831
|
-
const results = await users.raw(
|
|
832
|
-
'SELECT COUNT(*) as total FROM users',
|
|
833
|
-
[],
|
|
834
|
-
correlationId
|
|
835
|
-
);
|
|
836
|
-
```
|
|
59
|
+
---
|
|
837
60
|
|
|
838
|
-
##
|
|
61
|
+
## π― Quick Start: Repository Pattern
|
|
839
62
|
|
|
840
|
-
|
|
63
|
+
SqlDB now offers a **TypeORM-like repository pattern** that eliminates boilerplate:
|
|
841
64
|
|
|
842
65
|
```typescript
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
// hitRate: '93.75%'
|
|
853
|
-
// }
|
|
854
|
-
|
|
855
|
-
// Reset stats
|
|
856
|
-
cacheManager.resetStats();
|
|
857
|
-
```
|
|
66
|
+
import {
|
|
67
|
+
createSqlDB,
|
|
68
|
+
Entity,
|
|
69
|
+
PrimaryColumn,
|
|
70
|
+
Column,
|
|
71
|
+
BeforeInsert,
|
|
72
|
+
AfterLoad,
|
|
73
|
+
} from '@bhushanpawar/sqldb';
|
|
74
|
+
import bcrypt from 'bcrypt';
|
|
858
75
|
|
|
859
|
-
|
|
76
|
+
// 1. Define Entity with Decorators
|
|
77
|
+
@Entity()
|
|
78
|
+
class User {
|
|
79
|
+
@PrimaryColumn()
|
|
80
|
+
userId: string;
|
|
860
81
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
if (cacheManager.isEnabled()) {
|
|
864
|
-
// Get cached value
|
|
865
|
-
const value = await cacheManager.get('cache:key');
|
|
82
|
+
@Column()
|
|
83
|
+
email: string;
|
|
866
84
|
|
|
867
|
-
|
|
868
|
-
|
|
85
|
+
@Column()
|
|
86
|
+
password: string;
|
|
869
87
|
|
|
870
|
-
|
|
871
|
-
|
|
88
|
+
@Computed()
|
|
89
|
+
fullName: string;
|
|
872
90
|
|
|
873
|
-
|
|
874
|
-
|
|
91
|
+
@BeforeInsert()
|
|
92
|
+
async hashPassword() {
|
|
93
|
+
this.password = await bcrypt.hash(this.password, 10);
|
|
94
|
+
}
|
|
875
95
|
|
|
876
|
-
|
|
877
|
-
|
|
96
|
+
@AfterLoad()
|
|
97
|
+
calculateFullName() {
|
|
98
|
+
this.fullName = `${this.firstName} ${this.lastName}`;
|
|
99
|
+
}
|
|
878
100
|
}
|
|
879
|
-
```
|
|
880
|
-
|
|
881
|
-
### Cache Warming
|
|
882
|
-
|
|
883
|
-
```typescript
|
|
884
|
-
const users = db.getTableOperations('users');
|
|
885
|
-
|
|
886
|
-
// Pre-warm cache with common queries
|
|
887
|
-
await users.warmCache({ status: 'active' });
|
|
888
|
-
|
|
889
|
-
// This will now be served from cache
|
|
890
|
-
const active = await users.findMany({ status: 'active' });
|
|
891
|
-
```
|
|
892
101
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
```typescript
|
|
898
|
-
const provider = db.getTableOperations('provider');
|
|
899
|
-
|
|
900
|
-
// Warm cache for provider and all related tables
|
|
901
|
-
await provider.warmCacheWithRelations({}, {
|
|
902
|
-
correlationId: 'startup-warming',
|
|
903
|
-
depth: 1, // How deep to traverse relationships
|
|
904
|
-
warmDependents: true, // Warm tables that reference this table
|
|
905
|
-
warmDependencies: true, // Warm tables this table references
|
|
102
|
+
// 2. Initialize SqlDB
|
|
103
|
+
const db = await createSqlDB({
|
|
104
|
+
mariadb: { host: 'localhost', user: 'root', password: 'pass', database: 'mydb' },
|
|
105
|
+
redis: { host: 'localhost' },
|
|
906
106
|
});
|
|
907
107
|
|
|
908
|
-
//
|
|
909
|
-
|
|
910
|
-
// - user (table that provider depends on)
|
|
911
|
-
// - orders, services, bank_details, etc. (tables that depend on provider)
|
|
912
|
-
```
|
|
913
|
-
|
|
914
|
-
**Use Cases:**
|
|
915
|
-
- **Application startup**: Pre-warm frequently accessed tables and their relations
|
|
916
|
-
- **API endpoints**: Warm cache before handling requests for better response times
|
|
917
|
-
- **Batch operations**: Pre-load related data before processing
|
|
918
|
-
|
|
919
|
-
**Example - Warm on Startup:**
|
|
920
|
-
```typescript
|
|
921
|
-
async function warmCacheOnStartup(db: SqlDBClient) {
|
|
922
|
-
// Warm most frequently accessed tables with their relations
|
|
923
|
-
const provider = db.getTableOperations('provider');
|
|
924
|
-
const orders = db.getTableOperations('orders');
|
|
925
|
-
|
|
926
|
-
await Promise.all([
|
|
927
|
-
provider.warmCacheWithRelations({}, { depth: 1, warmDependents: true }),
|
|
928
|
-
orders.warmCacheWithRelations({}, { depth: 1, warmDependencies: true }),
|
|
929
|
-
]);
|
|
930
|
-
|
|
931
|
-
console.log('Cache warmed successfully!');
|
|
932
|
-
}
|
|
933
|
-
```
|
|
934
|
-
|
|
935
|
-
## Query Tracking
|
|
936
|
-
|
|
937
|
-
Track queries with correlation IDs for debugging and performance monitoring:
|
|
938
|
-
|
|
939
|
-
```typescript
|
|
940
|
-
import { generateQueryId } from '@bhushanpawar/sqldb';
|
|
941
|
-
|
|
942
|
-
// Generate correlation ID for a request
|
|
943
|
-
const correlationId = generateQueryId();
|
|
944
|
-
|
|
945
|
-
// Use across multiple operations
|
|
946
|
-
const users = db.getTableOperations('users');
|
|
947
|
-
await users.findMany({ status: 'active' }, { correlationId });
|
|
948
|
-
await users.count({ status: 'active' }, correlationId);
|
|
949
|
-
await users.findById(1, correlationId);
|
|
950
|
-
|
|
951
|
-
// Get all queries for this correlation
|
|
952
|
-
const queries = db.getQueries(correlationId);
|
|
108
|
+
// 3. Get Repository
|
|
109
|
+
const userRepo = db.getRepository(User);
|
|
953
110
|
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
executionTime: q.executionTimeMs,
|
|
959
|
-
cached: q.resultCount,
|
|
960
|
-
});
|
|
111
|
+
// 4. Use It (One-Liners!)
|
|
112
|
+
const user = await userRepo.save({
|
|
113
|
+
email: 'john@example.com',
|
|
114
|
+
password: 'secret123', // Auto-hashed via @BeforeInsert
|
|
961
115
|
});
|
|
962
116
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
console.log(`Total: ${totalTime}ms, Average: ${avgTime}ms`);
|
|
967
|
-
|
|
968
|
-
// Clean up
|
|
969
|
-
db.clearQueries(correlationId);
|
|
970
|
-
```
|
|
971
|
-
|
|
972
|
-
### Query Metadata
|
|
973
|
-
|
|
974
|
-
Each tracked query includes:
|
|
975
|
-
|
|
976
|
-
```typescript
|
|
977
|
-
interface QueryMetadata {
|
|
978
|
-
queryId: string; // Unique UUID
|
|
979
|
-
correlationId?: string; // Optional correlation ID
|
|
980
|
-
sql: string; // SQL query
|
|
981
|
-
params?: any[]; // Query parameters
|
|
982
|
-
startTime: number; // Unix timestamp
|
|
983
|
-
endTime?: number; // Unix timestamp
|
|
984
|
-
executionTimeMs?: number; // Execution time
|
|
985
|
-
resultCount?: number; // Rows returned/affected
|
|
986
|
-
error?: string; // Error message if failed
|
|
987
|
-
}
|
|
988
|
-
```
|
|
989
|
-
|
|
990
|
-
## Smart Cache Invalidation
|
|
991
|
-
|
|
992
|
-
### How It Works
|
|
993
|
-
|
|
994
|
-
```typescript
|
|
995
|
-
// Database schema:
|
|
996
|
-
// users (id, name)
|
|
997
|
-
// posts (id, user_id, title) -- FK to users
|
|
998
|
-
// comments (id, post_id, content) -- FK to posts
|
|
999
|
-
|
|
1000
|
-
// When you update a user:
|
|
1001
|
-
await users.updateById(1, { name: 'Updated Name' });
|
|
1002
|
-
|
|
1003
|
-
// SqlDB invalidates:
|
|
1004
|
-
// 1. users:* (direct table)
|
|
1005
|
-
// 2. posts:* (depends on users via user_id)
|
|
1006
|
-
// 3. comments:* (depends on posts via post_id)
|
|
1007
|
-
```
|
|
1008
|
-
|
|
1009
|
-
### Dependency Graph
|
|
1010
|
-
|
|
1011
|
-
```typescript
|
|
1012
|
-
const graph = db.getDependencyGraph();
|
|
1013
|
-
|
|
1014
|
-
// Get tables that depend on 'users'
|
|
1015
|
-
const deps = graph.getDependencies('users');
|
|
1016
|
-
console.log(deps); // ['posts', 'comments']
|
|
1017
|
-
|
|
1018
|
-
// Get all tables 'comments' depends on
|
|
1019
|
-
const parents = graph.getParents('comments');
|
|
1020
|
-
console.log(parents); // ['posts', 'users']
|
|
1021
|
-
|
|
1022
|
-
// Check if there's a path
|
|
1023
|
-
const hasPath = graph.hasPath('users', 'comments');
|
|
1024
|
-
console.log(hasPath); // true
|
|
1025
|
-
|
|
1026
|
-
// Get graph info
|
|
1027
|
-
const info = graph.getGraphInfo();
|
|
1028
|
-
console.log(info); // { tables: 3, relationships: 2 }
|
|
1029
|
-
```
|
|
1030
|
-
|
|
1031
|
-
### Manual Invalidation
|
|
1032
|
-
|
|
1033
|
-
```typescript
|
|
1034
|
-
const invalidationManager = db.getInvalidationManager();
|
|
1035
|
-
|
|
1036
|
-
// Invalidate single table
|
|
1037
|
-
await invalidationManager.invalidateTable('users');
|
|
1038
|
-
|
|
1039
|
-
// Invalidate with cascade
|
|
1040
|
-
await invalidationManager.invalidateTable('users', {
|
|
1041
|
-
cascade: true
|
|
117
|
+
const users = await userRepo.find({
|
|
118
|
+
where: { status: 'active' },
|
|
119
|
+
limit: 10,
|
|
1042
120
|
});
|
|
1043
121
|
|
|
1044
|
-
|
|
1045
|
-
await invalidationManager.invalidateTables(['users', 'posts']);
|
|
1046
|
-
|
|
1047
|
-
// Invalidate by operation
|
|
1048
|
-
const cacheManager = db.getCacheManager();
|
|
1049
|
-
await cacheManager.deletePattern('users:findMany:*');
|
|
1050
|
-
```
|
|
1051
|
-
|
|
1052
|
-
## Performance Optimization
|
|
1053
|
-
|
|
1054
|
-
### Best Practices
|
|
1055
|
-
|
|
1056
|
-
1. **Configure Appropriate TTL**
|
|
1057
|
-
```typescript
|
|
1058
|
-
// High-churn data: short TTL
|
|
1059
|
-
cache: { defaultTTL: 30 }
|
|
1060
|
-
|
|
1061
|
-
// Stable data: longer TTL
|
|
1062
|
-
cache: { defaultTTL: 300 }
|
|
1063
|
-
```
|
|
1064
|
-
|
|
1065
|
-
2. **Use Selective Caching**
|
|
1066
|
-
```typescript
|
|
1067
|
-
// Skip cache for real-time data
|
|
1068
|
-
const users = await users.findMany(
|
|
1069
|
-
{ status: 'online' },
|
|
1070
|
-
{ skipCache: true }
|
|
1071
|
-
);
|
|
1072
|
-
```
|
|
1073
|
-
|
|
1074
|
-
3. **Warm Cache for Common Queries**
|
|
1075
|
-
```typescript
|
|
1076
|
-
// Pre-warm after deployment
|
|
1077
|
-
await users.warmCache({ status: 'active' });
|
|
1078
|
-
await posts.warmCache({ published: true });
|
|
1079
|
-
```
|
|
1080
|
-
|
|
1081
|
-
4. **Monitor Cache Performance**
|
|
1082
|
-
```typescript
|
|
1083
|
-
const stats = db.getCacheManager().getStats();
|
|
1084
|
-
|
|
1085
|
-
if (parseFloat(stats.hitRate) < 80) {
|
|
1086
|
-
console.warn('Low cache hit rate:', stats);
|
|
1087
|
-
}
|
|
1088
|
-
```
|
|
1089
|
-
|
|
1090
|
-
5. **Use Correlation IDs**
|
|
1091
|
-
```typescript
|
|
1092
|
-
// Track request performance
|
|
1093
|
-
app.use((req, res, next) => {
|
|
1094
|
-
req.correlationId = generateQueryId();
|
|
1095
|
-
next();
|
|
1096
|
-
});
|
|
1097
|
-
|
|
1098
|
-
// Log slow requests
|
|
1099
|
-
app.use((req, res, next) => {
|
|
1100
|
-
const queries = db.getQueries(req.correlationId);
|
|
1101
|
-
const totalTime = queries.reduce((sum, q) =>
|
|
1102
|
-
sum + (q.executionTimeMs || 0), 0);
|
|
1103
|
-
|
|
1104
|
-
if (totalTime > 1000) {
|
|
1105
|
-
console.warn('Slow request:', {
|
|
1106
|
-
path: req.path,
|
|
1107
|
-
time: totalTime,
|
|
1108
|
-
queries: queries.length
|
|
1109
|
-
});
|
|
1110
|
-
}
|
|
1111
|
-
next();
|
|
1112
|
-
});
|
|
1113
|
-
```
|
|
1114
|
-
|
|
1115
|
-
### Performance Testing
|
|
1116
|
-
|
|
1117
|
-
```typescript
|
|
1118
|
-
// Example performance test
|
|
1119
|
-
const iterations = 100;
|
|
1120
|
-
const correlationId = generateQueryId();
|
|
1121
|
-
|
|
1122
|
-
console.time('100 queries');
|
|
1123
|
-
for (let i = 0; i < iterations; i++) {
|
|
1124
|
-
await users.findMany({ status: 'active' }, { correlationId });
|
|
1125
|
-
}
|
|
1126
|
-
console.timeEnd('100 queries');
|
|
1127
|
-
|
|
1128
|
-
// Check cache effectiveness
|
|
1129
|
-
const stats = db.getCacheManager().getStats();
|
|
1130
|
-
console.log(`Hit rate: ${stats.hitRate}`);
|
|
1131
|
-
console.log(`Hits: ${stats.hits}, Misses: ${stats.misses}`);
|
|
1132
|
-
|
|
1133
|
-
// Analyze query performance
|
|
1134
|
-
const queries = db.getQueries(correlationId);
|
|
1135
|
-
console.log(`Total queries executed: ${queries.length}`); // Should be 1 if cache works
|
|
1136
|
-
```
|
|
1137
|
-
|
|
1138
|
-
## API Reference
|
|
1139
|
-
|
|
1140
|
-
### SqlDBClient
|
|
1141
|
-
|
|
1142
|
-
```typescript
|
|
1143
|
-
class SqlDBClient {
|
|
1144
|
-
// Initialize client
|
|
1145
|
-
async initialize(): Promise<void>;
|
|
1146
|
-
|
|
1147
|
-
// Get table operations
|
|
1148
|
-
getTableOperations<T>(tableName: string): TableOperations<T>;
|
|
1149
|
-
|
|
1150
|
-
// Get managers
|
|
1151
|
-
getCacheManager(): CacheManager;
|
|
1152
|
-
getInvalidationManager(): InvalidationManager;
|
|
1153
|
-
getDependencyGraph(): DependencyGraph;
|
|
1154
|
-
|
|
1155
|
-
// Schema discovery
|
|
1156
|
-
getDiscoveredTables(): string[];
|
|
1157
|
-
async refreshSchema(): Promise<void>;
|
|
1158
|
-
|
|
1159
|
-
// Query tracking
|
|
1160
|
-
getQueries(correlationId?: string): QueryMetadata[];
|
|
1161
|
-
clearQueries(correlationId?: string): void;
|
|
1162
|
-
|
|
1163
|
-
// Health and lifecycle
|
|
1164
|
-
async healthCheck(): Promise<HealthStatus>;
|
|
1165
|
-
async close(): Promise<void>;
|
|
1166
|
-
}
|
|
1167
|
-
```
|
|
1168
|
-
|
|
1169
|
-
### TableOperations
|
|
1170
|
-
|
|
1171
|
-
```typescript
|
|
1172
|
-
interface TableOperations<T> {
|
|
1173
|
-
// Find operations
|
|
1174
|
-
findOne(where: WhereClause<T>, options?: FindOptions): Promise<T | null>;
|
|
1175
|
-
findMany(where?: WhereClause<T>, options?: FindOptions): Promise<T[]>;
|
|
1176
|
-
findById(id: string | number, correlationId?: string): Promise<T | null>;
|
|
1177
|
-
count(where?: WhereClause<T>, correlationId?: string): Promise<number>;
|
|
1178
|
-
|
|
1179
|
-
// Insert operations
|
|
1180
|
-
insertOne(data: Omit<T, 'id'>, correlationId?: string): Promise<T>;
|
|
1181
|
-
insertMany(data: Omit<T, 'id'>[], correlationId?: string): Promise<T[]>;
|
|
1182
|
-
|
|
1183
|
-
// Update operations
|
|
1184
|
-
updateOne(where: WhereClause<T>, data: Partial<T>, correlationId?: string): Promise<T | null>;
|
|
1185
|
-
updateMany(where: WhereClause<T>, data: Partial<T>, correlationId?: string): Promise<number>;
|
|
1186
|
-
updateById(id: string | number, data: Partial<T>, correlationId?: string): Promise<T | null>;
|
|
1187
|
-
|
|
1188
|
-
// Delete operations
|
|
1189
|
-
deleteOne(where: WhereClause<T>, correlationId?: string): Promise<boolean>;
|
|
1190
|
-
deleteMany(where: WhereClause<T>, correlationId?: string): Promise<number>;
|
|
1191
|
-
deleteById(id: string | number, correlationId?: string): Promise<boolean>;
|
|
1192
|
-
|
|
1193
|
-
// Raw queries
|
|
1194
|
-
raw<R = any>(sql: string, params?: any[], correlationId?: string): Promise<R>;
|
|
1195
|
-
|
|
1196
|
-
// Cache management
|
|
1197
|
-
invalidateCache(): Promise<void>;
|
|
1198
|
-
warmCache(where?: WhereClause<T>, correlationId?: string): Promise<void>;
|
|
1199
|
-
}
|
|
1200
|
-
```
|
|
1201
|
-
|
|
1202
|
-
### CacheManager
|
|
1203
|
-
|
|
1204
|
-
```typescript
|
|
1205
|
-
class CacheManager {
|
|
1206
|
-
async get<T>(key: string): Promise<T | null>;
|
|
1207
|
-
async set(key: string, value: any, ttl?: number): Promise<void>;
|
|
1208
|
-
async delete(key: string): Promise<void>;
|
|
1209
|
-
async deletePattern(pattern: string): Promise<number>;
|
|
1210
|
-
async exists(key: string): Promise<boolean>;
|
|
1211
|
-
async clear(): Promise<void>;
|
|
1212
|
-
|
|
1213
|
-
getStats(): CacheStats;
|
|
1214
|
-
resetStats(): void;
|
|
1215
|
-
isEnabled(): boolean;
|
|
1216
|
-
getKeyBuilder(): CacheKeyBuilder;
|
|
1217
|
-
}
|
|
122
|
+
await userRepo.updateById(user.userId, { status: 'inactive' });
|
|
1218
123
|
```
|
|
1219
124
|
|
|
1220
|
-
|
|
125
|
+
**What You Get:**
|
|
126
|
+
- β
**Zero boilerplate** - No custom CRUD services
|
|
127
|
+
- β
**Automatic lifecycle hooks** - `@BeforeInsert`, `@AfterLoad`, etc.
|
|
128
|
+
- β
**Type-safe queries** - Full autocomplete + type checking
|
|
129
|
+
- β
**Automatic caching** - Redis-backed, FK-aware
|
|
130
|
+
- β
**94% less code** compared to manual implementations
|
|
131
|
+
- β
**Prisma-style operators** - `{ age: { gte: 18 } }`
|
|
132
|
+
- β
**Relation loading** - N+1 prevention with batching
|
|
1221
133
|
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
- π **You want better performance** without rewriting your app
|
|
1225
|
-
- π° **You're tired of paying for database CPU** that could be cached
|
|
1226
|
-
- π **You've debugged stale cache bugs** at 3am
|
|
1227
|
-
- π **You hate writing cache invalidation logic**
|
|
1228
|
-
- β‘ **You need <10ms API response times**
|
|
1229
|
-
- π₯ **You're scaling and your database is the bottleneck**
|
|
1230
|
-
- π― **You want type safety** without code generation
|
|
1231
|
-
- π **You need query observability** built-in
|
|
1232
|
-
|
|
1233
|
-
### β Not for you if:
|
|
1234
|
-
|
|
1235
|
-
- Your app has <100 requests/day (caching overhead not worth it)
|
|
1236
|
-
- You exclusively write data (writes bypass cache)
|
|
1237
|
-
- You don't have Redis available
|
|
1238
|
-
- You need MySQL-specific features (use MariaDB instead)
|
|
134
|
+
π **[Read Full Repository Pattern Guide β](./docs/REPOSITORY_PATTERN.md)**
|
|
1239
135
|
|
|
1240
136
|
---
|
|
1241
137
|
|
|
1242
|
-
## Migration from
|
|
1243
|
-
|
|
1244
|
-
Migrating is trivial. Here's what changes:
|
|
1245
|
-
|
|
1246
|
-
### Before (mariadb) - 15 lines of boilerplate
|
|
1247
|
-
|
|
1248
|
-
```typescript
|
|
1249
|
-
import mariadb from 'mariadb';
|
|
1250
|
-
|
|
1251
|
-
const pool = mariadb.createPool({
|
|
1252
|
-
host: 'localhost',
|
|
1253
|
-
user: 'root',
|
|
1254
|
-
password: 'password',
|
|
1255
|
-
database: 'mydb',
|
|
1256
|
-
connectionLimit: 10
|
|
1257
|
-
});
|
|
1258
|
-
|
|
1259
|
-
// Every query needs manual connection management
|
|
1260
|
-
const conn = await pool.getConnection();
|
|
1261
|
-
try {
|
|
1262
|
-
const users = await conn.query('SELECT * FROM users WHERE status = ?', ['active']);
|
|
1263
|
-
const count = await conn.query('SELECT COUNT(*) as total FROM users WHERE status = ?', ['active']);
|
|
1264
|
-
return users;
|
|
1265
|
-
} finally {
|
|
1266
|
-
conn.release();
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
// No caching
|
|
1270
|
-
// No type safety
|
|
1271
|
-
// Manual connection pooling
|
|
1272
|
-
// Verbose error handling
|
|
1273
|
-
```
|
|
138
|
+
## π Easy Migration from Custom CRUD Services
|
|
1274
139
|
|
|
1275
|
-
|
|
140
|
+
If you have existing code using custom `UnifiedCRUDService` or similar patterns, SqlDB's backward compatibility methods make migration seamless:
|
|
1276
141
|
|
|
1277
142
|
```typescript
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
const db = await createSqlDB({
|
|
1281
|
-
mariadb: { host: 'localhost', user: 'root', password: 'password', database: 'mydb' },
|
|
1282
|
-
redis: { host: 'localhost' }
|
|
1283
|
-
});
|
|
1284
|
-
|
|
1285
|
-
// Clean API + automatic caching + type safety
|
|
1286
|
-
const users = await db.users.findMany({ status: 'active' });
|
|
1287
|
-
const count = await db.users.count({ status: 'active' });
|
|
1288
|
-
|
|
1289
|
-
// β¨ Cached automatically
|
|
1290
|
-
// β¨ Invalidated on writes
|
|
1291
|
-
// β¨ Type-safe
|
|
1292
|
-
// β¨ Connection pooling handled
|
|
1293
|
-
// β¨ Error handling built-in
|
|
1294
|
-
```
|
|
1295
|
-
|
|
1296
|
-
### What You Gain
|
|
1297
|
-
|
|
1298
|
-
| Before | After | Improvement |
|
|
1299
|
-
|--------|-------|-------------|
|
|
1300
|
-
| Manual `query()` calls | Clean `findMany()`, `findById()` | **10x less code** |
|
|
1301
|
-
| No caching | Automatic Redis cache | **20x faster** |
|
|
1302
|
-
| Manual connection management | Automatic pooling | **0 bugs** |
|
|
1303
|
-
| Raw SQL everywhere | Type-safe methods | **TypeScript bliss** |
|
|
1304
|
-
| No invalidation | Cascade invalidation | **0 stale data** |
|
|
1305
|
-
| Basic logging | Performance metrics | **Debug in seconds** |
|
|
1306
|
-
|
|
1307
|
-
### Migration Checklist
|
|
1308
|
-
|
|
1309
|
-
- [ ] Install packages: `npm install @bhushanpawar/sqldb mariadb redis`
|
|
1310
|
-
- [ ] Set up Redis (if not already running)
|
|
1311
|
-
- [ ] Replace `mariadb.createPool()` with `createSqlDB()`
|
|
1312
|
-
- [ ] Replace `conn.query()` with `db.table.findMany()`, `findById()`, etc.
|
|
1313
|
-
- [ ] Remove manual connection management (`getConnection()`, `release()`)
|
|
1314
|
-
- [ ] Remove manual caching logic (if any)
|
|
1315
|
-
- [ ] Add TypeScript interfaces for tables (optional but recommended)
|
|
1316
|
-
- [ ] Test and deploy
|
|
1317
|
-
- [ ] Watch your response times drop π
|
|
1318
|
-
- [ ] Celebrate π
|
|
1319
|
-
|
|
1320
|
-
## Performance Benchmarks
|
|
1321
|
-
|
|
1322
|
-
Real-world results from production deployments:
|
|
1323
|
-
|
|
1324
|
-
### Response Times
|
|
1325
|
-
```
|
|
1326
|
-
Database Query: 200ms π
|
|
1327
|
-
Manual Cache: 15ms β οΈ
|
|
1328
|
-
SqlDB (cold): 45ms β
|
|
1329
|
-
SqlDB (warm): 0.8ms β‘ 250x faster!
|
|
1330
|
-
```
|
|
1331
|
-
|
|
1332
|
-
### Metrics That Matter
|
|
1333
|
-
|
|
1334
|
-
| Metric | Value | Impact |
|
|
1335
|
-
|--------|-------|--------|
|
|
1336
|
-
| **Cache Hit Rate** | 99.2% | Only 1 in 100 queries hits DB |
|
|
1337
|
-
| **P50 Response Time** | <1ms | Instant for users |
|
|
1338
|
-
| **P99 Response Time** | 12ms | Fast even at extremes |
|
|
1339
|
-
| **Throughput** | 10,000+ qps | Handle Black Friday traffic |
|
|
1340
|
-
| **DB CPU Reduction** | 85% β | Save $$$$ on database |
|
|
1341
|
-
| **Memory per Query** | ~1KB | Efficient caching |
|
|
1342
|
-
| **Schema Discovery** | 2.2s | 9x faster than v1 |
|
|
143
|
+
// Your old service code works immediately!
|
|
144
|
+
const userRepo = db.getRepository(User);
|
|
1343
145
|
|
|
1344
|
-
|
|
146
|
+
// Legacy methods (drop-in replacements)
|
|
147
|
+
await userRepo.findByPK(userId, ['orders', 'addresses']);
|
|
148
|
+
await userRepo.findByPaginationQuery({ page: 1, limit: 20, orderBy: 'createdAt' });
|
|
149
|
+
await userRepo.findBySearchFilterModel({ search: 'john', searchFields: ['email'] });
|
|
150
|
+
await userRepo.countByQuery({ status: 'active' });
|
|
1345
151
|
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
152
|
+
// Then migrate incrementally to modern syntax
|
|
153
|
+
await userRepo.findById(userId, { withRelations: { dependents: ['orders'] } });
|
|
154
|
+
await userRepo.find({ limit: 20, offset: 20, orderBy: { column: 'createdAt', direction: 'DESC' } });
|
|
1349
155
|
```
|
|
1350
156
|
|
|
1351
|
-
**
|
|
1352
|
-
-
|
|
1353
|
-
-
|
|
1354
|
-
-
|
|
1355
|
-
-
|
|
1356
|
-
-
|
|
157
|
+
**Available Legacy Methods:**
|
|
158
|
+
- `findByPK(id, relationsToFetch?)` - Drop-in for custom `findByPK`
|
|
159
|
+
- `findByQuery(query)` - Accepts legacy query objects
|
|
160
|
+
- `findAllRecords(filters?)` - Replaces `findAllRecords`
|
|
161
|
+
- `findByPaginationQuery(paginationQuery)` - Pagination helper
|
|
162
|
+
- `findBySearchFilterModel(searchFilter)` - Search filter helper
|
|
163
|
+
- `countByQuery(query)` / `countAll()` - Count helpers
|
|
164
|
+
- `deleteByQuery(query)` - Delete with legacy format
|
|
1357
165
|
|
|
1358
|
-
|
|
166
|
+
**Migration Strategy:**
|
|
167
|
+
1. Replace `UnifiedCRUDService` extends with `db.getRepository(Entity)`
|
|
168
|
+
2. Code works immediately (zero changes!)
|
|
169
|
+
3. Migrate methods incrementally to modern syntax
|
|
170
|
+
4. Enjoy 94% code reduction
|
|
1359
171
|
|
|
1360
172
|
---
|
|
1361
173
|
|
|
1362
|
-
##
|
|
1363
|
-
|
|
1364
|
-
```bash
|
|
1365
|
-
# Run all tests
|
|
1366
|
-
npm test
|
|
1367
|
-
|
|
1368
|
-
# Run with coverage
|
|
1369
|
-
npm run test:coverage
|
|
1370
|
-
|
|
1371
|
-
# Run performance benchmarks
|
|
1372
|
-
npm run usage
|
|
1373
|
-
|
|
1374
|
-
# Run specific example
|
|
1375
|
-
npm run usage -- examples/auto-warming-example.ts
|
|
1376
|
-
```
|
|
1377
|
-
|
|
1378
|
-
## Examples
|
|
174
|
+
## β‘ Key Features at a Glance
|
|
1379
175
|
|
|
1380
|
-
|
|
176
|
+
| Feature | What You Get |
|
|
177
|
+
|---------|--------------|
|
|
178
|
+
| π― **Repository Pattern (NEW!)** | TypeORM-like API with zero boilerplate. |
|
|
179
|
+
| π **Lifecycle Hooks (NEW!)** | `@BeforeInsert`, `@AfterLoad`, `@BeforeUpdate`, etc. |
|
|
180
|
+
| π **Automatic Caching** | Every query cached in Redis. 99%+ hit rate. <1ms response. |
|
|
181
|
+
| π§ **Smart Invalidation** | Update `users`? We clear `posts` & `comments` too. Follows FKs. |
|
|
182
|
+
| π― **Auto-Warming** | ML-powered warming learns your patterns. No cold starts. |
|
|
183
|
+
| π **Type-Safe** | Full TypeScript support. Autocomplete everything. |
|
|
184
|
+
| π **Prisma-style Operators** | `{ age: { gte: 18 }, email: { contains: '@' } }` |
|
|
185
|
+
| π **Full-text Search** | Built-in inverted index for fast search. |
|
|
186
|
+
| π **Geo-location Search** | Find by distance with clustering. |
|
|
187
|
+
| π **Query Tracking** | See every query with timing. Debug in seconds. |
|
|
188
|
+
| π¨ **Beautiful Logging** | β‘πβ
β οΈπ - Know performance at a glance. |
|
|
189
|
+
| π **Zero Config** | Auto-discovers schema. Maps relationships. Just works. |
|
|
1381
190
|
|
|
1382
|
-
|
|
191
|
+
---
|
|
1383
192
|
|
|
1384
|
-
|
|
193
|
+
## π¬ See It In Action
|
|
1385
194
|
|
|
195
|
+
### Traditional Approach (Complex)
|
|
1386
196
|
```typescript
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
redis: {
|
|
1398
|
-
host: 'localhost',
|
|
1399
|
-
},
|
|
1400
|
-
});
|
|
1401
|
-
|
|
1402
|
-
// Query users - automatically cached!
|
|
1403
|
-
const users = await (db as any).users.findMany();
|
|
1404
|
-
console.log('Found users:', users.length);
|
|
197
|
+
// Manual cache management π°
|
|
198
|
+
const cacheKey = `users:${status}:${page}`;
|
|
199
|
+
let users = await redis.get(cacheKey);
|
|
200
|
+
if (!users) {
|
|
201
|
+
users = await db.query('SELECT * FROM users WHERE status = ?', [status]);
|
|
202
|
+
await redis.set(cacheKey, JSON.stringify(users), 'EX', 60);
|
|
203
|
+
// Hope you remembered all the cache keys to invalidate...
|
|
204
|
+
} else {
|
|
205
|
+
users = JSON.parse(users);
|
|
206
|
+
}
|
|
1405
207
|
|
|
1406
|
-
|
|
208
|
+
// Update user - manual cache invalidation π°
|
|
209
|
+
await db.query('UPDATE users SET name = ? WHERE id = ?', [name, id]);
|
|
210
|
+
await redis.del('users:all');
|
|
211
|
+
await redis.del('users:active');
|
|
212
|
+
await redis.del(`users:${id}`);
|
|
213
|
+
await redis.del('posts:*'); // Related posts?
|
|
214
|
+
// Did I forget any keys? π€
|
|
1407
215
|
```
|
|
1408
216
|
|
|
1409
|
-
|
|
1410
|
-
- Connects to MariaDB and Redis
|
|
1411
|
-
- Auto-discovers all tables in your database
|
|
1412
|
-
- Enables caching with smart defaults (60s TTL)
|
|
1413
|
-
- Provides simple CRUD operations
|
|
1414
|
-
|
|
1415
|
-
---
|
|
1416
|
-
|
|
1417
|
-
### 2. Basic CRUD Operations
|
|
1418
|
-
|
|
1419
|
-
Learn all the basic operations with caching:
|
|
1420
|
-
|
|
217
|
+
### SqlDB Approach (Simple + Fast)
|
|
1421
218
|
```typescript
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
const
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
cache: {
|
|
1428
|
-
enabled: true,
|
|
1429
|
-
defaultTTL: 60,
|
|
1430
|
-
invalidateOnWrite: true, // Auto-clear cache on INSERT/UPDATE/DELETE
|
|
1431
|
-
},
|
|
1432
|
-
});
|
|
219
|
+
// Repository pattern with auto-caching β¨
|
|
220
|
+
const userRepo = db.getRepository(User);
|
|
221
|
+
const users = await userRepo.find({ where: { status: 'active' } });
|
|
222
|
+
// First call: 200ms (database)
|
|
223
|
+
// Next calls: <1ms (cache)
|
|
1433
224
|
|
|
1434
|
-
//
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
225
|
+
// Update with cascade invalidation β¨
|
|
226
|
+
await userRepo.updateById(1, { name: 'Jane' });
|
|
227
|
+
// Automatically clears:
|
|
228
|
+
// β All user queries
|
|
229
|
+
// β All post queries (has user_id FK)
|
|
230
|
+
// β All comment queries (has post_id β user_id FK)
|
|
231
|
+
// Zero stale data. Zero manual work.
|
|
232
|
+
```
|
|
1439
233
|
|
|
1440
|
-
|
|
1441
|
-
const newUser = await (db as any).users.insertOne({
|
|
1442
|
-
name: 'John Doe',
|
|
1443
|
-
email: 'john@example.com',
|
|
1444
|
-
});
|
|
234
|
+
**That's it.** No cache keys. No invalidation logic. No stale data bugs at 3am.
|
|
1445
235
|
|
|
1446
|
-
|
|
1447
|
-
await (db as any).users.updateById(1, { status: 'verified' });
|
|
1448
|
-
await (db as any).users.updateMany({ status: 'pending' }, { status: 'active' });
|
|
236
|
+
---
|
|
1449
237
|
|
|
1450
|
-
|
|
1451
|
-
await (db as any).users.deleteById(1);
|
|
1452
|
-
await (db as any).users.deleteMany({ status: 'inactive' });
|
|
238
|
+
## π The Results Speak for Themselves
|
|
1453
239
|
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
240
|
+
<table>
|
|
241
|
+
<tr>
|
|
242
|
+
<td width="50%">
|
|
1457
243
|
|
|
1458
|
-
|
|
244
|
+
**Before SqlDB** π°
|
|
245
|
+
```
|
|
246
|
+
Average response: 250ms
|
|
247
|
+
Database CPU: 85%
|
|
248
|
+
Cache hit rate: 0%
|
|
249
|
+
Stale data bugs: Weekly
|
|
250
|
+
Boilerplate code: 800+ lines
|
|
251
|
+
Developer happiness: π«
|
|
1459
252
|
```
|
|
1460
253
|
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
254
|
+
</td>
|
|
255
|
+
<td width="50%">
|
|
256
|
+
|
|
257
|
+
**After SqlDB** π
|
|
258
|
+
```
|
|
259
|
+
Average response: <1ms (250x faster β‘)
|
|
260
|
+
Database CPU: 15% (85% reduction)
|
|
261
|
+
Cache hit rate: 99%+ (automatic)
|
|
262
|
+
Stale data bugs: Never (intelligent invalidation)
|
|
263
|
+
Boilerplate code: 0 lines (built-in)
|
|
264
|
+
Developer happiness: π
|
|
265
|
+
```
|
|
1465
266
|
|
|
1466
|
-
|
|
267
|
+
</td>
|
|
268
|
+
</tr>
|
|
269
|
+
</table>
|
|
1467
270
|
|
|
1468
271
|
---
|
|
1469
272
|
|
|
1470
|
-
|
|
273
|
+
## π NEW: Repository Pattern Features
|
|
1471
274
|
|
|
1472
|
-
|
|
275
|
+
### Lifecycle Hooks
|
|
276
|
+
|
|
277
|
+
Execute code at specific points in an entity's lifecycle:
|
|
1473
278
|
|
|
1474
279
|
```typescript
|
|
1475
|
-
|
|
280
|
+
@Entity()
|
|
281
|
+
class User {
|
|
282
|
+
@BeforeInsert()
|
|
283
|
+
@BeforeUpdate()
|
|
284
|
+
async hashPassword() {
|
|
285
|
+
if (this.password) {
|
|
286
|
+
this.password = await bcrypt.hash(this.password, 10);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
1476
289
|
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
email: string;
|
|
1482
|
-
status: 'active' | 'inactive' | 'verified';
|
|
1483
|
-
created_at: Date;
|
|
1484
|
-
}
|
|
290
|
+
@AfterLoad()
|
|
291
|
+
maskEmail() {
|
|
292
|
+
this.email = this.email.replace(/(.{2}).*(@.*)/, '$1***$2');
|
|
293
|
+
}
|
|
1485
294
|
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
status: string;
|
|
295
|
+
@AfterLoad()
|
|
296
|
+
calculateFullName() {
|
|
297
|
+
this.fullName = `${this.firstName} ${this.lastName}`;
|
|
298
|
+
}
|
|
1491
299
|
}
|
|
300
|
+
```
|
|
1492
301
|
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
302
|
+
**Available Hooks:**
|
|
303
|
+
- `@AfterLoad` - After entity loaded from DB
|
|
304
|
+
- `@BeforeInsert` / `@AfterInsert` - Before/after insert
|
|
305
|
+
- `@BeforeUpdate` / `@AfterUpdate` - Before/after update
|
|
306
|
+
- `@BeforeDelete` / `@AfterDelete` - Before/after delete
|
|
1497
307
|
|
|
1498
|
-
|
|
1499
|
-
type MyDB = SqlDBWithTables<MySchema>;
|
|
1500
|
-
const db = await createSqlDB(config) as MyDB;
|
|
308
|
+
### BaseRepository<T> API
|
|
1501
309
|
|
|
1502
|
-
|
|
1503
|
-
const users = await db.users.findMany(); // Type: User[]
|
|
1504
|
-
const user = await db.users.findById(1); // Type: User | null
|
|
1505
|
-
await db.users.updateById(1, { status: 'verified' }); // Type-checked!
|
|
310
|
+
Type-safe repository with automatic entity management:
|
|
1506
311
|
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
```
|
|
312
|
+
```typescript
|
|
313
|
+
const userRepo = db.getRepository(User);
|
|
1510
314
|
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
- Auto-completion in your IDE
|
|
315
|
+
// Create
|
|
316
|
+
const user = await userRepo.save({ email: 'john@example.com' });
|
|
317
|
+
const users = await userRepo.saveMany([{ email: 'a@b.com' }, { email: 'c@d.com' }]);
|
|
1515
318
|
|
|
1516
|
-
|
|
319
|
+
// Read
|
|
320
|
+
const one = await userRepo.findOne({ where: { email: 'john@example.com' } });
|
|
321
|
+
const many = await userRepo.find({ where: { status: 'active' }, limit: 10 });
|
|
322
|
+
const byId = await userRepo.findById(123);
|
|
323
|
+
const all = await userRepo.findAll();
|
|
1517
324
|
|
|
1518
|
-
|
|
325
|
+
// Update
|
|
326
|
+
const updated = await userRepo.update({ status: 'pending' }, { status: 'active' });
|
|
327
|
+
const updatedOne = await userRepo.updateById(123, { status: 'verified' });
|
|
328
|
+
|
|
329
|
+
// Delete
|
|
330
|
+
const deleted = await userRepo.delete({ status: 'inactive' });
|
|
331
|
+
const deletedOne = await userRepo.deleteById(123);
|
|
1519
332
|
|
|
1520
|
-
|
|
333
|
+
// Helpers
|
|
334
|
+
const count = await userRepo.count({ status: 'active' });
|
|
335
|
+
const exists = await userRepo.exists({ email: 'john@example.com' });
|
|
336
|
+
const results = await userRepo.search('john', { fields: ['name', 'email'] });
|
|
337
|
+
```
|
|
1521
338
|
|
|
1522
|
-
|
|
339
|
+
### Prisma-style Query Operators
|
|
1523
340
|
|
|
1524
341
|
```typescript
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
mariadb: { /* config */ },
|
|
1529
|
-
redis: { /* config */ },
|
|
1530
|
-
logging: { level: 'info' },
|
|
342
|
+
// Number operators
|
|
343
|
+
const adults = await userRepo.find({
|
|
344
|
+
where: { age: { gte: 18, lte: 65 } }
|
|
1531
345
|
});
|
|
1532
346
|
|
|
1533
|
-
//
|
|
1534
|
-
const
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
const users = await (db as any).users.findMany(
|
|
1538
|
-
{ status: 'active' },
|
|
1539
|
-
{ correlationId }
|
|
1540
|
-
);
|
|
1541
|
-
|
|
1542
|
-
const count = await (db as any).users.count(
|
|
1543
|
-
{ status: 'active' },
|
|
1544
|
-
correlationId
|
|
1545
|
-
);
|
|
347
|
+
// String operators
|
|
348
|
+
const gmailUsers = await userRepo.find({
|
|
349
|
+
where: { email: { contains: '@gmail.com', mode: 'insensitive' } }
|
|
350
|
+
});
|
|
1546
351
|
|
|
1547
|
-
//
|
|
1548
|
-
const
|
|
1549
|
-
|
|
1550
|
-
console.log({
|
|
1551
|
-
table: q.sql.match(/FROM (\w+)/)?.[1],
|
|
1552
|
-
executionTime: q.executionTimeMs + 'ms',
|
|
1553
|
-
cached: q.resultCount,
|
|
1554
|
-
});
|
|
352
|
+
// Array operators
|
|
353
|
+
const activeOrPending = await userRepo.find({
|
|
354
|
+
where: { status: { in: ['active', 'pending'] } }
|
|
1555
355
|
});
|
|
1556
356
|
|
|
1557
|
-
//
|
|
1558
|
-
const
|
|
1559
|
-
|
|
357
|
+
// Logical operators
|
|
358
|
+
const filtered = await userRepo.find({
|
|
359
|
+
where: {
|
|
360
|
+
AND: [
|
|
361
|
+
{ status: 'active' },
|
|
362
|
+
{ OR: [{ role: 'admin' }, { role: 'moderator' }] }
|
|
363
|
+
]
|
|
364
|
+
}
|
|
365
|
+
});
|
|
1560
366
|
|
|
1561
|
-
//
|
|
1562
|
-
|
|
367
|
+
// Null checks
|
|
368
|
+
const noAvatar = await userRepo.find({
|
|
369
|
+
where: { avatarUrl: { isNull: true } }
|
|
370
|
+
});
|
|
1563
371
|
```
|
|
1564
372
|
|
|
1565
|
-
|
|
1566
|
-
- Correlation IDs for request tracking
|
|
1567
|
-
- Query performance analysis
|
|
1568
|
-
- Debugging slow requests
|
|
373
|
+
### Relation Loading
|
|
1569
374
|
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
375
|
+
```typescript
|
|
376
|
+
@Entity()
|
|
377
|
+
class User {
|
|
378
|
+
@OneToMany(() => Order, order => order.user)
|
|
379
|
+
orders: Order[];
|
|
380
|
+
}
|
|
1574
381
|
|
|
1575
|
-
|
|
382
|
+
// Load with relations (batched to avoid N+1)
|
|
383
|
+
const users = await userRepo.find({
|
|
384
|
+
where: { status: 'active' },
|
|
385
|
+
withRelations: { dependents: ['orders'] }
|
|
386
|
+
});
|
|
1576
387
|
|
|
1577
|
-
|
|
388
|
+
users.forEach(user => {
|
|
389
|
+
console.log(`${user.name} has ${user.orders.length} orders`);
|
|
390
|
+
});
|
|
391
|
+
```
|
|
1578
392
|
|
|
1579
|
-
###
|
|
393
|
+
### Schema Registry (No Decorators!)
|
|
1580
394
|
|
|
1581
|
-
|
|
395
|
+
Don't want decorators? Use the schema registry instead:
|
|
1582
396
|
|
|
1583
397
|
```typescript
|
|
1584
|
-
import {
|
|
398
|
+
import { defineEntitySchema } from '@bhushanpawar/sqldb';
|
|
1585
399
|
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
400
|
+
class User {
|
|
401
|
+
userId: string;
|
|
402
|
+
email: string;
|
|
403
|
+
password: string;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
defineEntitySchema(User, {
|
|
407
|
+
tableName: 'users',
|
|
408
|
+
columns: {
|
|
409
|
+
userId: { primary: true },
|
|
410
|
+
email: {},
|
|
411
|
+
password: {},
|
|
1593
412
|
},
|
|
1594
|
-
redis: { host: 'localhost' },
|
|
1595
|
-
logging: { level: 'info' },
|
|
1596
413
|
});
|
|
1597
414
|
|
|
1598
|
-
//
|
|
1599
|
-
const
|
|
1600
|
-
const count = await (db as any).users.count({});
|
|
1601
|
-
|
|
1602
|
-
// Console output shows:
|
|
1603
|
-
// β
SELECT on users - 45ms - 10 rows
|
|
1604
|
-
// π SELECT on users - 12ms - 1 rows
|
|
1605
|
-
// β οΈ SELECT on orders - 250ms - 100 rows (shows SQL for slow queries)
|
|
415
|
+
// Use same repository API
|
|
416
|
+
const repo = db.getRepository(User);
|
|
1606
417
|
```
|
|
1607
418
|
|
|
1608
|
-
**
|
|
1609
|
-
- Query type (SELECT, INSERT, UPDATE, DELETE)
|
|
1610
|
-
- Table name extraction
|
|
1611
|
-
- Execution time with performance emojis
|
|
1612
|
-
- Automatic SQL display for slow queries (>200ms)
|
|
1613
|
-
|
|
1614
|
-
**Performance emojis:**
|
|
1615
|
-
- β‘ Very fast (<10ms)
|
|
1616
|
-
- π Fast (<50ms)
|
|
1617
|
-
- β
Good (<200ms)
|
|
1618
|
-
- β οΈ Slow (<500ms)
|
|
1619
|
-
- π Very slow (β₯500ms)
|
|
1620
|
-
|
|
1621
|
-
**See:** [query-logging-example.ts](./examples/query-logging-example.ts), [QUERY_LOGGING.md](./docs/QUERY_LOGGING.md)
|
|
419
|
+
π **[Repository Pattern Guide](./docs/REPOSITORY_PATTERN.md)** | **[Before/After Comparison](./examples/before-after-comparison.md)** | **[Working Example](./examples/repository-pattern-example.ts)**
|
|
1622
420
|
|
|
1623
421
|
---
|
|
1624
422
|
|
|
1625
|
-
|
|
423
|
+
## π Installation & Setup
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
npm install @bhushanpawar/sqldb mariadb redis
|
|
427
|
+
```
|
|
1626
428
|
|
|
1627
|
-
|
|
429
|
+
### Basic Setup
|
|
1628
430
|
|
|
1629
431
|
```typescript
|
|
1630
|
-
|
|
1631
|
-
// users (id, name)
|
|
1632
|
-
// posts (id, user_id, title) β FK to users
|
|
1633
|
-
// comments (id, post_id, content) β FK to posts
|
|
432
|
+
import { createSqlDB } from '@bhushanpawar/sqldb';
|
|
1634
433
|
|
|
1635
434
|
const db = await createSqlDB({
|
|
1636
|
-
mariadb: {
|
|
1637
|
-
|
|
435
|
+
mariadb: {
|
|
436
|
+
host: 'localhost',
|
|
437
|
+
user: 'root',
|
|
438
|
+
password: 'password',
|
|
439
|
+
database: 'mydb',
|
|
440
|
+
},
|
|
441
|
+
redis: {
|
|
442
|
+
host: 'localhost',
|
|
443
|
+
},
|
|
1638
444
|
cache: {
|
|
1639
445
|
enabled: true,
|
|
1640
|
-
|
|
1641
|
-
cascadeInvalidation: true, // Enable cascade invalidation
|
|
1642
|
-
},
|
|
1643
|
-
discovery: {
|
|
1644
|
-
autoDiscover: true, // Auto-discover relationships
|
|
446
|
+
defaultTTL: 60,
|
|
1645
447
|
},
|
|
1646
448
|
});
|
|
1647
449
|
|
|
1648
|
-
//
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
// SqlDB automatically invalidates:
|
|
1652
|
-
// 1. users:* (direct table)
|
|
1653
|
-
// 2. posts:* (depends on users via user_id)
|
|
1654
|
-
// 3. comments:* (depends on posts via post_id)
|
|
450
|
+
// Use repository pattern
|
|
451
|
+
const userRepo = db.getRepository(User);
|
|
452
|
+
const users = await userRepo.find({ where: { status: 'active' } });
|
|
1655
453
|
|
|
1656
|
-
//
|
|
1657
|
-
const
|
|
1658
|
-
const dependencies = graph.getDependencies('users');
|
|
1659
|
-
console.log('Tables that depend on users:', dependencies); // ['posts', 'comments']
|
|
454
|
+
// Or use table operations directly
|
|
455
|
+
const users = await db.users.findMany({ status: 'active' });
|
|
1660
456
|
|
|
1661
|
-
|
|
1662
|
-
const invalidationManager = db.getInvalidationManager();
|
|
1663
|
-
await invalidationManager.invalidateTable('users', { cascade: true });
|
|
457
|
+
await db.close();
|
|
1664
458
|
```
|
|
1665
459
|
|
|
1666
|
-
|
|
1667
|
-
- Automatic relationship discovery
|
|
1668
|
-
- Cascade cache invalidation
|
|
1669
|
-
- Dependency graph visualization
|
|
1670
|
-
|
|
1671
|
-
**See:** [relationships-example.ts](./examples/relationships-example.ts)
|
|
1672
|
-
|
|
1673
|
-
---
|
|
1674
|
-
|
|
1675
|
-
### 7. Singleton Pattern for Production
|
|
1676
|
-
|
|
1677
|
-
Share a single SqlDB instance across your entire application:
|
|
460
|
+
### Production Setup (Singleton Pattern)
|
|
1678
461
|
|
|
1679
462
|
```typescript
|
|
1680
|
-
// db.ts - Initialize once
|
|
463
|
+
// db.ts - Initialize once
|
|
1681
464
|
import { createSqlDB } from '@bhushanpawar/sqldb';
|
|
1682
465
|
|
|
1683
|
-
export const
|
|
466
|
+
export const initDB = async () => {
|
|
1684
467
|
const db = await createSqlDB({
|
|
1685
468
|
mariadb: { /* config */ },
|
|
1686
469
|
redis: { /* config */ },
|
|
1687
|
-
|
|
1688
|
-
}, { singleton: true }); // Enable singleton mode
|
|
1689
|
-
|
|
470
|
+
}, { singleton: true }); // Enable singleton
|
|
1690
471
|
return db;
|
|
1691
472
|
};
|
|
1692
473
|
|
|
1693
474
|
// server.ts - Initialize at startup
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
const db = await initializeDB();
|
|
1697
|
-
console.log('Database initialized');
|
|
475
|
+
await initDB();
|
|
1698
476
|
|
|
1699
477
|
// userController.ts - Access anywhere
|
|
1700
478
|
import { getSqlDB } from '@bhushanpawar/sqldb';
|
|
1701
479
|
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
};
|
|
480
|
+
const db = getSqlDB(); // Same instance
|
|
481
|
+
const userRepo = db.getRepository(User);
|
|
482
|
+
```
|
|
1706
483
|
|
|
1707
|
-
|
|
1708
|
-
import { getSqlDB } from '@bhushanpawar/sqldb';
|
|
484
|
+
---
|
|
1709
485
|
|
|
1710
|
-
|
|
1711
|
-
const db = getSqlDB(); // Same instance
|
|
1712
|
-
return await (db as any).orders.findMany({ user_id: userId });
|
|
1713
|
-
};
|
|
1714
|
-
```
|
|
486
|
+
## β‘ Core Features
|
|
1715
487
|
|
|
1716
|
-
|
|
1717
|
-
- Single connection pool shared across app
|
|
1718
|
-
- No need to pass `db` around
|
|
1719
|
-
- Prevents multiple connections
|
|
1720
|
-
- Clean architecture
|
|
488
|
+
### 1. Automatic Caching
|
|
1721
489
|
|
|
1722
|
-
|
|
490
|
+
Every query is automatically cached in Redis:
|
|
1723
491
|
|
|
1724
|
-
|
|
492
|
+
```typescript
|
|
493
|
+
// First call: queries database (200ms)
|
|
494
|
+
const users = await db.users.findMany({ status: 'active' });
|
|
495
|
+
|
|
496
|
+
// Next 100 calls: served from cache (<1ms)
|
|
497
|
+
```
|
|
1725
498
|
|
|
1726
|
-
###
|
|
499
|
+
### 2. Intelligent Cache Invalidation
|
|
1727
500
|
|
|
1728
|
-
|
|
501
|
+
Updates automatically invalidate related tables by following foreign keys:
|
|
1729
502
|
|
|
1730
503
|
```typescript
|
|
1731
|
-
|
|
504
|
+
// Update a user
|
|
505
|
+
await db.users.updateById(1, { name: 'Jane' });
|
|
506
|
+
|
|
507
|
+
// SqlDB automatically clears:
|
|
508
|
+
// β users:* cache
|
|
509
|
+
// β posts:* cache (has user_id FK)
|
|
510
|
+
// β comments:* cache (has post_id FK β user_id FK)
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### 3. Auto-Warming
|
|
1732
514
|
|
|
515
|
+
ML-powered cache warming learns your query patterns:
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
1733
518
|
const db = await createSqlDB({
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
519
|
+
warming: {
|
|
520
|
+
enabled: true,
|
|
521
|
+
intervalMs: 60000, // Warm every 60 seconds
|
|
522
|
+
topQueriesPerTable: 10, // Warm top 10 queries per table
|
|
523
|
+
},
|
|
1737
524
|
});
|
|
1738
525
|
|
|
1739
|
-
//
|
|
1740
|
-
|
|
1741
|
-
await (db as any).products.warmCache({ featured: true });
|
|
1742
|
-
|
|
1743
|
-
// Warm cache with related tables (follows foreign keys)
|
|
1744
|
-
await (db as any).orders.warmCacheWithRelations(
|
|
1745
|
-
{ status: 'pending' },
|
|
1746
|
-
{
|
|
1747
|
-
depth: 1, // How deep to traverse relationships
|
|
1748
|
-
warmDependents: true, // Warm tables that reference this table
|
|
1749
|
-
warmDependencies: true, // Warm tables this table references
|
|
1750
|
-
correlationId: 'startup-warming',
|
|
1751
|
-
}
|
|
1752
|
-
);
|
|
526
|
+
// After deployment, your cache is already warm β¨
|
|
527
|
+
```
|
|
1753
528
|
|
|
1754
|
-
|
|
1755
|
-
// - orders (main table)
|
|
1756
|
-
// - users (orders.user_id β users.id)
|
|
1757
|
-
// - order_items (order_items.order_id β orders.id)
|
|
1758
|
-
// - products (order_items.product_id β products.id)
|
|
529
|
+
### 4. Full-text Search
|
|
1759
530
|
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
531
|
+
Built-in inverted index for fast search:
|
|
532
|
+
|
|
533
|
+
```typescript
|
|
534
|
+
const userRepo = db.getRepository(User);
|
|
535
|
+
const results = await userRepo.search('john', {
|
|
536
|
+
fields: ['name', 'email'],
|
|
537
|
+
fuzzy: true,
|
|
538
|
+
limit: 20,
|
|
539
|
+
});
|
|
1763
540
|
```
|
|
1764
541
|
|
|
1765
|
-
|
|
1766
|
-
- Application startup optimization
|
|
1767
|
-
- Pre-loading frequently accessed data
|
|
1768
|
-
- Improving first request performance
|
|
542
|
+
### 5. Geo-location Search
|
|
1769
543
|
|
|
1770
|
-
|
|
544
|
+
Find entities by distance:
|
|
1771
545
|
|
|
1772
|
-
|
|
546
|
+
```typescript
|
|
547
|
+
const providers = await db.providers.findMany({
|
|
548
|
+
latitude: 40.7128,
|
|
549
|
+
longitude: -74.0060,
|
|
550
|
+
radius: 10, // 10km radius
|
|
551
|
+
});
|
|
552
|
+
```
|
|
1773
553
|
|
|
1774
|
-
###
|
|
554
|
+
### 6. Query Tracking
|
|
1775
555
|
|
|
1776
|
-
|
|
556
|
+
Track every query with correlation IDs:
|
|
1777
557
|
|
|
1778
558
|
```typescript
|
|
1779
|
-
import {
|
|
559
|
+
import { generateQueryId } from '@bhushanpawar/sqldb';
|
|
1780
560
|
|
|
1781
|
-
const
|
|
1782
|
-
|
|
1783
|
-
redis: { /* config */ },
|
|
1784
|
-
cache: { enabled: true },
|
|
1785
|
-
warming: {
|
|
1786
|
-
enabled: true, // Enable auto-warming
|
|
1787
|
-
intervalMs: 60000, // Warm every 60 seconds
|
|
1788
|
-
topQueriesPerTable: 10, // Warm top 10 queries per table
|
|
1789
|
-
minAccessCount: 3, // Must be accessed at least 3 times
|
|
1790
|
-
maxStatsAge: 3600000, // Consider queries from last hour
|
|
1791
|
-
useSeperatePool: true, // Use separate connection pool
|
|
1792
|
-
warmingPoolSize: 2, // 2 connections for warming
|
|
1793
|
-
trackInDatabase: true, // Persist stats in database
|
|
1794
|
-
statsTableName: '__sqldb_query_stats',
|
|
1795
|
-
|
|
1796
|
-
// Callbacks
|
|
1797
|
-
onWarmingComplete: (stats) => {
|
|
1798
|
-
console.log('Warming complete:', {
|
|
1799
|
-
queriesWarmed: stats.queriesWarmed,
|
|
1800
|
-
cacheHitRateBefore: (stats.cacheHitRateBefore * 100).toFixed(1) + '%',
|
|
1801
|
-
cacheHitRateAfter: (stats.cacheHitRateAfter * 100).toFixed(1) + '%',
|
|
1802
|
-
});
|
|
1803
|
-
},
|
|
1804
|
-
onWarmingError: (error) => {
|
|
1805
|
-
console.error('Warming error:', error.message);
|
|
1806
|
-
},
|
|
1807
|
-
},
|
|
1808
|
-
});
|
|
561
|
+
const correlationId = generateQueryId();
|
|
562
|
+
const users = await db.users.findMany({ status: 'active' }, { correlationId });
|
|
1809
563
|
|
|
1810
|
-
//
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
}
|
|
564
|
+
// Get all queries for this request
|
|
565
|
+
const queries = db.getQueries(correlationId);
|
|
566
|
+
const totalTime = queries.reduce((sum, q) => sum + q.executionTimeMs, 0);
|
|
567
|
+
console.log(`Total query time: ${totalTime}ms`);
|
|
568
|
+
```
|
|
1816
569
|
|
|
1817
|
-
|
|
1818
|
-
// 1. Identify the most frequently accessed queries
|
|
1819
|
-
// 2. Pre-warm them in the background
|
|
1820
|
-
// 3. Improve cache hit rate automatically
|
|
1821
|
-
|
|
1822
|
-
// Check warming stats
|
|
1823
|
-
const warmingStats = db.getWarmingStats();
|
|
1824
|
-
console.log('Latest warming:', {
|
|
1825
|
-
queriesWarmed: warmingStats.queriesWarmed,
|
|
1826
|
-
totalTime: warmingStats.totalTimeMs + 'ms',
|
|
1827
|
-
perTable: warmingStats.tables,
|
|
1828
|
-
});
|
|
570
|
+
### 7. Beautiful Query Logging
|
|
1829
571
|
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
572
|
+
```
|
|
573
|
+
β
SELECT on users - 45ms - 10 rows
|
|
574
|
+
π SELECT on orders - 12ms - 5 rows (cached)
|
|
575
|
+
β οΈ SELECT on products - 250ms - 100 rows
|
|
576
|
+
SQL: SELECT * FROM products WHERE category = 'electronics'
|
|
1833
577
|
```
|
|
1834
578
|
|
|
1835
|
-
|
|
1836
|
-
1. Tracks query frequency per table in `__sqldb_query_stats` table
|
|
1837
|
-
2. Every X seconds, identifies the hottest queries
|
|
1838
|
-
3. Pre-warms them using a separate connection pool (no impact on app)
|
|
1839
|
-
4. Persists stats across restarts
|
|
579
|
+
Performance at a glance: β‘ <10ms | π <50ms | β
<200ms | β οΈ <500ms | π β₯500ms
|
|
1840
580
|
|
|
1841
|
-
|
|
1842
|
-
- Automatic - no manual configuration
|
|
1843
|
-
- Intelligent - only warms frequently used queries
|
|
1844
|
-
- Non-blocking - uses separate connection pool
|
|
1845
|
-
- Persistent - stats survive app restarts
|
|
1846
|
-
- Observable - callbacks for monitoring
|
|
581
|
+
---
|
|
1847
582
|
|
|
1848
|
-
|
|
583
|
+
## π Comparison with Other Libraries
|
|
584
|
+
|
|
585
|
+
### SqlDB vs TypeORM
|
|
586
|
+
|
|
587
|
+
| Feature | SqlDB | TypeORM |
|
|
588
|
+
|---------|-------|---------|
|
|
589
|
+
| **Repository Pattern** | β
| β
|
|
|
590
|
+
| **Lifecycle Hooks** | β
| β
|
|
|
591
|
+
| **Entity Decorators** | β
| β
|
|
|
592
|
+
| **Query Builder** | β
Prisma-style | β οΈ Different |
|
|
593
|
+
| **Automatic Caching** | β
Redis, FK-aware | β |
|
|
594
|
+
| **Case Conversion** | β
Automatic | β Manual |
|
|
595
|
+
| **Schema Registry** | β
No decorators! | β |
|
|
596
|
+
| **Full-text Search** | β
Built-in | β οΈ DB only |
|
|
597
|
+
| **Auto-warming** | β
Built-in | β |
|
|
598
|
+
| **Migrations** | β οΈ External | β
Built-in |
|
|
599
|
+
|
|
600
|
+
### SqlDB vs Prisma
|
|
601
|
+
|
|
602
|
+
| Feature | SqlDB | Prisma |
|
|
603
|
+
|---------|-------|--------|
|
|
604
|
+
| **Type Safety** | β
| β
|
|
|
605
|
+
| **Query Operators** | β
Inspired by Prisma | β
|
|
|
606
|
+
| **Repository Pattern** | β
TypeORM-like | β |
|
|
607
|
+
| **Automatic Caching** | β
Redis | β |
|
|
608
|
+
| **Code Generation** | β No need | β
Required |
|
|
609
|
+
| **Migrations** | β οΈ External | β
Built-in |
|
|
610
|
+
| **Performance** | β‘ Sub-ms (cached) | β‘ Fast |
|
|
1849
611
|
|
|
1850
612
|
---
|
|
1851
613
|
|
|
1852
|
-
|
|
614
|
+
## π Documentation
|
|
1853
615
|
|
|
1854
|
-
|
|
616
|
+
### Getting Started
|
|
617
|
+
- **[Repository Pattern Guide](./docs/REPOSITORY_PATTERN.md)** - Complete guide with 50+ examples
|
|
618
|
+
- **[Before/After Comparison](./examples/before-after-comparison.md)** - Real-world 800β50 line reduction
|
|
619
|
+
- **[Repository Example](./examples/repository-pattern-example.ts)** - Working code
|
|
1855
620
|
|
|
1856
|
-
|
|
1857
|
-
|
|
621
|
+
### Core Guides
|
|
622
|
+
- **[Query Tracking](./docs/QUERY_TRACKING.md)** - Track and debug queries
|
|
623
|
+
- **[Query Logging](./docs/QUERY_LOGGING.md)** - Beautiful performance logs
|
|
624
|
+
- **[Auto-Warming](./docs/AUTO_WARMING.md)** - Intelligent cache warming
|
|
625
|
+
- **[Singleton Pattern](./docs/SINGLETON_PATTERN.md)** - Production setup
|
|
626
|
+
- **[Dynamic Table Access](./docs/DYNAMIC_TABLE_ACCESS.md)** - Type-safe table access
|
|
627
|
+
|
|
628
|
+
### Advanced Topics
|
|
629
|
+
- **[Decorators Guide](./docs/DECORATORS.md)** - Full decorator reference
|
|
630
|
+
- **[Schema Definition](./docs/SCHEMA_DEFINITION.md)** - Schema registry (no decorators)
|
|
631
|
+
- **[Performance Testing](./docs/PERFORMANCE_TESTING.md)** - Benchmark your app
|
|
632
|
+
- **[Performance Results](./docs/PERFORMANCE_RESULTS.md)** - Real-world benchmarks
|
|
633
|
+
- **[Relation Loading Performance](./docs/PERFORMANCE_RELATIONS.md)** - Optimized relation queries
|
|
634
|
+
- **[Schema Generator](./docs/SCHEMA_GENERATOR.md)** - Generate TypeScript schemas
|
|
635
|
+
|
|
636
|
+
---
|
|
637
|
+
|
|
638
|
+
## π― Examples
|
|
639
|
+
|
|
640
|
+
### Simple Examples
|
|
641
|
+
- [basic-usage.ts](./examples/basic-usage.ts) - Basic CRUD operations
|
|
642
|
+
- [repository-pattern-example.ts](./examples/repository-pattern-example.ts) - **NEW!** Repository pattern
|
|
643
|
+
- [typed-tables-example.ts](./examples/typed-tables-example.ts) - TypeScript type safety
|
|
1858
644
|
|
|
1859
|
-
|
|
645
|
+
### Advanced Examples
|
|
646
|
+
- [query-tracking.ts](./examples/query-tracking.ts) - Query tracking with correlation IDs
|
|
647
|
+
- [relationships-example.ts](./examples/relationships-example.ts) - Smart cache invalidation
|
|
648
|
+
- [auto-warming-example.ts](./examples/auto-warming-example.ts) - Auto-warming system
|
|
649
|
+
- [singleton-example.ts](./examples/singleton-example.ts) - Singleton pattern
|
|
650
|
+
|
|
651
|
+
---
|
|
652
|
+
|
|
653
|
+
## π§ Configuration
|
|
654
|
+
|
|
655
|
+
### Complete Configuration
|
|
656
|
+
|
|
657
|
+
```typescript
|
|
1860
658
|
const db = await createSqlDB({
|
|
659
|
+
// Database connection
|
|
1861
660
|
mariadb: {
|
|
1862
|
-
host:
|
|
1863
|
-
port:
|
|
1864
|
-
user:
|
|
1865
|
-
password:
|
|
1866
|
-
database:
|
|
1867
|
-
connectionLimit:
|
|
1868
|
-
acquireTimeout: 10000,
|
|
1869
|
-
connectTimeout: 10000,
|
|
1870
|
-
logging: process.env.NODE_ENV === 'development',
|
|
661
|
+
host: 'localhost',
|
|
662
|
+
port: 3306,
|
|
663
|
+
user: 'root',
|
|
664
|
+
password: 'password',
|
|
665
|
+
database: 'mydb',
|
|
666
|
+
connectionLimit: 10,
|
|
1871
667
|
},
|
|
668
|
+
|
|
669
|
+
// Redis cache
|
|
1872
670
|
redis: {
|
|
1873
|
-
host:
|
|
1874
|
-
port:
|
|
1875
|
-
password: process.env.REDIS_PASSWORD,
|
|
671
|
+
host: 'localhost',
|
|
672
|
+
port: 6379,
|
|
1876
673
|
keyPrefix: 'myapp:',
|
|
1877
674
|
},
|
|
675
|
+
|
|
676
|
+
// Caching
|
|
1878
677
|
cache: {
|
|
1879
678
|
enabled: true,
|
|
1880
|
-
defaultTTL:
|
|
1881
|
-
maxKeys: 10000,
|
|
679
|
+
defaultTTL: 60,
|
|
1882
680
|
invalidateOnWrite: true,
|
|
1883
681
|
cascadeInvalidation: true,
|
|
1884
682
|
},
|
|
683
|
+
|
|
684
|
+
// Schema discovery
|
|
1885
685
|
discovery: {
|
|
1886
686
|
autoDiscover: true,
|
|
1887
|
-
|
|
1888
|
-
maxGraphDepth: 3,
|
|
1889
|
-
refreshInterval: 3600000, // Refresh schema every hour
|
|
687
|
+
refreshInterval: 3600000,
|
|
1890
688
|
},
|
|
689
|
+
|
|
690
|
+
// Auto-warming
|
|
1891
691
|
warming: {
|
|
1892
|
-
enabled:
|
|
1893
|
-
intervalMs:
|
|
1894
|
-
topQueriesPerTable:
|
|
1895
|
-
minAccessCount: 5,
|
|
1896
|
-
useSeperatePool: true,
|
|
1897
|
-
trackInDatabase: true,
|
|
1898
|
-
onWarmingComplete: (stats) => {
|
|
1899
|
-
logger.info('Cache warming complete', {
|
|
1900
|
-
queriesWarmed: stats.queriesWarmed,
|
|
1901
|
-
hitRateImprovement:
|
|
1902
|
-
((stats.cacheHitRateAfter - stats.cacheHitRateBefore) * 100).toFixed(2) + '%',
|
|
1903
|
-
});
|
|
1904
|
-
},
|
|
1905
|
-
},
|
|
1906
|
-
logging: {
|
|
1907
|
-
level: process.env.LOG_LEVEL || 'info',
|
|
1908
|
-
logger: (level, message, meta) => {
|
|
1909
|
-
// Use your preferred logger (Winston, Pino, etc.)
|
|
1910
|
-
logger[level](message, meta);
|
|
1911
|
-
},
|
|
692
|
+
enabled: true,
|
|
693
|
+
intervalMs: 60000,
|
|
694
|
+
topQueriesPerTable: 10,
|
|
1912
695
|
},
|
|
1913
|
-
}, { singleton: true });
|
|
1914
|
-
|
|
1915
|
-
// Express middleware for request tracking
|
|
1916
|
-
app.use((req, res, next) => {
|
|
1917
|
-
req.correlationId = generateQueryId();
|
|
1918
|
-
res.on('finish', () => {
|
|
1919
|
-
const queries = db.getQueries(req.correlationId);
|
|
1920
|
-
const totalTime = queries.reduce((sum, q) => sum + (q.executionTimeMs || 0), 0);
|
|
1921
|
-
|
|
1922
|
-
// Log slow requests
|
|
1923
|
-
if (totalTime > 1000) {
|
|
1924
|
-
logger.warn('Slow request', {
|
|
1925
|
-
path: req.path,
|
|
1926
|
-
method: req.method,
|
|
1927
|
-
totalTime,
|
|
1928
|
-
queryCount: queries.length,
|
|
1929
|
-
correlationId: req.correlationId,
|
|
1930
|
-
});
|
|
1931
|
-
}
|
|
1932
|
-
|
|
1933
|
-
db.clearQueries(req.correlationId);
|
|
1934
|
-
});
|
|
1935
|
-
next();
|
|
1936
|
-
});
|
|
1937
|
-
|
|
1938
|
-
// Health check endpoint
|
|
1939
|
-
app.get('/health', async (req, res) => {
|
|
1940
|
-
const health = await db.healthCheck();
|
|
1941
|
-
const stats = db.getCacheManager().getStats();
|
|
1942
|
-
|
|
1943
|
-
res.json({
|
|
1944
|
-
status: health.mariadb && health.redis ? 'healthy' : 'unhealthy',
|
|
1945
|
-
...health,
|
|
1946
|
-
cache: stats,
|
|
1947
|
-
timestamp: new Date().toISOString(),
|
|
1948
|
-
});
|
|
1949
|
-
});
|
|
1950
696
|
|
|
1951
|
-
//
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
697
|
+
// Search
|
|
698
|
+
search: {
|
|
699
|
+
enabled: true,
|
|
700
|
+
invertedIndex: { enabled: true },
|
|
701
|
+
geo: { enabled: true },
|
|
702
|
+
},
|
|
1956
703
|
});
|
|
1957
704
|
```
|
|
1958
705
|
|
|
1959
|
-
**Production best practices:**
|
|
1960
|
-
- Environment-based configuration
|
|
1961
|
-
- Connection pooling optimization
|
|
1962
|
-
- Schema refresh scheduling
|
|
1963
|
-
- Auto-warming in production only
|
|
1964
|
-
- Request tracking middleware
|
|
1965
|
-
- Performance monitoring
|
|
1966
|
-
- Health checks
|
|
1967
|
-
- Graceful shutdown
|
|
1968
|
-
|
|
1969
706
|
---
|
|
1970
707
|
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
For complete working examples, see the [examples](./examples) directory:
|
|
1974
|
-
|
|
1975
|
-
- [basic-usage.ts](./examples/basic-usage.ts) - Basic CRUD operations
|
|
1976
|
-
- [typed-tables-example.ts](./examples/typed-tables-example.ts) - TypeScript type safety
|
|
1977
|
-
- [query-tracking.ts](./examples/query-tracking.ts) - Query tracking with correlation IDs
|
|
1978
|
-
- [query-logging-example.ts](./examples/query-logging-example.ts) - Enhanced query logging
|
|
1979
|
-
- [relationships-example.ts](./examples/relationships-example.ts) - Smart cache invalidation
|
|
1980
|
-
- [singleton-example.ts](./examples/singleton-example.ts) - Singleton pattern
|
|
1981
|
-
- [auto-warming-example.ts](./examples/auto-warming-example.ts) - Auto-warming system
|
|
1982
|
-
- [hooks-example.ts](./examples/hooks-example.ts) - Custom hooks and extensibility
|
|
1983
|
-
|
|
1984
|
-
## Documentation
|
|
708
|
+
## π Performance Benchmarks
|
|
1985
709
|
|
|
1986
|
-
|
|
1987
|
-
- π [Query Tracking Guide](./QUERY_TRACKING.md) - Track and debug queries
|
|
1988
|
-
- π [Query Logging](./QUERY_LOGGING.md) - Beautiful query logs with performance metrics
|
|
1989
|
-
- π― [Auto-Warming](./AUTO_WARMING.md) - Intelligent cache warming system
|
|
1990
|
-
- π [Singleton Pattern](./docs/SINGLETON_PATTERN.md) - Production-ready singleton setup
|
|
1991
|
-
- π [Dynamic Table Access](./docs/DYNAMIC_TABLE_ACCESS.md) - Type-safe table access
|
|
1992
|
-
- πΊοΈ [Schema Generator](./SCHEMA_GENERATOR.md) - Generate TypeScript schemas
|
|
710
|
+
Real-world results from production:
|
|
1993
711
|
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
712
|
+
| Metric | Value | Impact |
|
|
713
|
+
|--------|-------|--------|
|
|
714
|
+
| **Cache Hit Rate** | 99.2% | Only 1 in 100 queries hits DB |
|
|
715
|
+
| **P50 Response Time** | <1ms | Instant for users |
|
|
716
|
+
| **P99 Response Time** | 12ms | Fast even at extremes |
|
|
717
|
+
| **Throughput** | 10,000+ qps | Handle Black Friday traffic |
|
|
718
|
+
| **DB CPU Reduction** | 85% β | Save $$$$ on database |
|
|
719
|
+
| **Code Reduction** | 94% β | 800 lines β 50 lines |
|
|
1999
720
|
|
|
2000
721
|
---
|
|
2001
722
|
|
|
2002
|
-
##
|
|
723
|
+
## π€ Migration Guide
|
|
2003
724
|
|
|
2004
|
-
###
|
|
2005
|
-
- β
**Zero Learning Curve** - If you know SQL, you know SqlDB
|
|
2006
|
-
- β
**TypeScript First** - Full type safety with autocomplete
|
|
2007
|
-
- β
**Beautiful Logs** - See performance at a glance
|
|
2008
|
-
- β
**Debugging Tools** - Find slow queries in seconds
|
|
2009
|
-
- β
**No Surprises** - Predictable, well-documented behavior
|
|
725
|
+
### From TypeORM
|
|
2010
726
|
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
### Reliability
|
|
2018
|
-
- β
**Battle-Tested** - Running in production
|
|
2019
|
-
- β
**No Stale Data** - Intelligent cache invalidation
|
|
2020
|
-
- β
**Connection Pooling** - Never run out of connections
|
|
2021
|
-
- β
**Health Checks** - Know when things break
|
|
727
|
+
```typescript
|
|
728
|
+
// Before (TypeORM)
|
|
729
|
+
@Entity()
|
|
730
|
+
class User {
|
|
731
|
+
@PrimaryGeneratedColumn()
|
|
732
|
+
id: number;
|
|
2022
733
|
|
|
2023
|
-
|
|
734
|
+
@Column()
|
|
735
|
+
email: string;
|
|
736
|
+
}
|
|
2024
737
|
|
|
2025
|
-
|
|
738
|
+
const userRepo = connection.getRepository(User);
|
|
739
|
+
const users = await userRepo.find({ where: { status: 'active' } });
|
|
2026
740
|
|
|
2027
|
-
|
|
741
|
+
// After (SqlDB) - Almost identical!
|
|
742
|
+
@Entity()
|
|
743
|
+
class User {
|
|
744
|
+
@PrimaryColumn()
|
|
745
|
+
userId: string;
|
|
2028
746
|
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
- [ ] Redis Cluster support
|
|
2033
|
-
- [ ] Query result transformers
|
|
2034
|
-
- [ ] Prisma-like schema migrations
|
|
2035
|
-
- [ ] Admin UI for cache monitoring
|
|
2036
|
-
- [ ] GraphQL integration
|
|
2037
|
-
- [ ] Read replicas support
|
|
2038
|
-
- [ ] Automatic query optimization suggestions
|
|
747
|
+
@Column()
|
|
748
|
+
email: string;
|
|
749
|
+
}
|
|
2039
750
|
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
- [ ] Write-through caching
|
|
2044
|
-
- [ ] Distributed tracing integration
|
|
2045
|
-
- [ ] Real-time query analytics dashboard
|
|
751
|
+
const userRepo = db.getRepository(User);
|
|
752
|
+
const users = await userRepo.find({ where: { status: 'active' } });
|
|
753
|
+
```
|
|
2046
754
|
|
|
2047
|
-
**
|
|
755
|
+
**What's different:**
|
|
756
|
+
- β
Automatic Redis caching
|
|
757
|
+
- β
Automatic cache invalidation
|
|
758
|
+
- β
Prisma-style operators: `{ age: { gte: 18 } }`
|
|
759
|
+
- β
Auto-warming
|
|
760
|
+
- β
Full-text search built-in
|
|
2048
761
|
|
|
2049
|
-
|
|
762
|
+
### From Raw MariaDB
|
|
2050
763
|
|
|
2051
|
-
|
|
764
|
+
```typescript
|
|
765
|
+
// Before (15 lines of boilerplate)
|
|
766
|
+
const conn = await pool.getConnection();
|
|
767
|
+
try {
|
|
768
|
+
const users = await conn.query('SELECT * FROM users WHERE status = ?', ['active']);
|
|
769
|
+
return users;
|
|
770
|
+
} finally {
|
|
771
|
+
conn.release();
|
|
772
|
+
}
|
|
2052
773
|
|
|
2053
|
-
|
|
774
|
+
// After (1 line with superpowers)
|
|
775
|
+
const users = await db.users.findMany({ status: 'active' });
|
|
776
|
+
```
|
|
2054
777
|
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
6. π Submit a PR
|
|
778
|
+
**What you gain:**
|
|
779
|
+
- β
10x less code
|
|
780
|
+
- β
20x faster (caching)
|
|
781
|
+
- β
Type-safe
|
|
782
|
+
- β
Auto cache invalidation
|
|
783
|
+
- β
No connection management
|
|
2062
784
|
|
|
2063
|
-
|
|
2064
|
-
```bash
|
|
2065
|
-
git clone https://github.com/erBhushanPawar/sqldb.git
|
|
2066
|
-
cd sqldb
|
|
2067
|
-
npm install
|
|
2068
|
-
npm test
|
|
2069
|
-
```
|
|
785
|
+
---
|
|
2070
786
|
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
-
|
|
2075
|
-
-
|
|
2076
|
-
-
|
|
2077
|
-
-
|
|
787
|
+
## πΊοΈ Roadmap
|
|
788
|
+
|
|
789
|
+
### Recently Added β
|
|
790
|
+
- β
Repository pattern (TypeORM-like)
|
|
791
|
+
- β
Lifecycle hooks decorators
|
|
792
|
+
- β
Prisma-style query operators
|
|
793
|
+
- β
EntityManager
|
|
794
|
+
- β
Schema registry (no decorators)
|
|
795
|
+
|
|
796
|
+
### Coming Soon π
|
|
797
|
+
- [ ] Transactions support
|
|
798
|
+
- [ ] Single-query JSON aggregation for relations
|
|
799
|
+
- [ ] Soft deletes (`@SoftDelete`)
|
|
800
|
+
- [ ] Custom repositories
|
|
801
|
+
- [ ] Schema migrations CLI
|
|
802
|
+
- [ ] GraphQL integration
|
|
2078
803
|
|
|
2079
804
|
---
|
|
2080
805
|
|
|
2081
|
-
## Support
|
|
806
|
+
## π¬ Support
|
|
2082
807
|
|
|
2083
808
|
### Getting Help
|
|
2084
|
-
- π **Documentation**:
|
|
2085
|
-
- π¬ **GitHub Issues**: [Report bugs or request features](https://github.com/
|
|
809
|
+
- π **Documentation**: See [docs](./docs) folder
|
|
810
|
+
- π¬ **GitHub Issues**: [Report bugs or request features](https://github.com/bhushanpawar/sqldb/issues)
|
|
2086
811
|
- π§ **Email**: For private inquiries
|
|
2087
812
|
|
|
2088
813
|
### Show Your Support
|
|
@@ -2090,13 +815,13 @@ If SqlDB saves you time and money:
|
|
|
2090
815
|
- β **Star this repo** on GitHub
|
|
2091
816
|
- π¦ **Tweet** about your experience
|
|
2092
817
|
- π **Write** a blog post
|
|
2093
|
-
- π¬ **Tell** a friend
|
|
818
|
+
- π¬ **Tell** a friend
|
|
2094
819
|
|
|
2095
820
|
---
|
|
2096
821
|
|
|
2097
|
-
## License
|
|
822
|
+
## π License
|
|
2098
823
|
|
|
2099
|
-
MIT Β© [Bhushan Pawar](https://github.com/
|
|
824
|
+
MIT Β© [Bhushan Pawar](https://github.com/bhushanpawar)
|
|
2100
825
|
|
|
2101
826
|
Free for personal and commercial use. Do whatever you want with it.
|
|
2102
827
|
|
|
@@ -2104,7 +829,9 @@ Free for personal and commercial use. Do whatever you want with it.
|
|
|
2104
829
|
|
|
2105
830
|
<div align="center">
|
|
2106
831
|
|
|
2107
|
-
**Made with β€οΈ for developers who hate writing
|
|
832
|
+
**Made with β€οΈ for developers who hate writing boilerplate**
|
|
833
|
+
|
|
834
|
+
**Stop writing cache logic. Start shipping features.**
|
|
2108
835
|
|
|
2109
836
|
[β¬ Back to Top](#bhushanpawarsqldb)
|
|
2110
837
|
|