@bhushanpawar/sqldb 1.0.23 β 1.0.25
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 +554 -1864
- package/dist/cli/index.js +0 -0
- 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/operations.d.ts.map +1 -1
- package/dist/query/operations.js +3 -1
- package/dist/query/operations.js.map +1 -1
- package/dist/repository/base-repository.d.ts +204 -0
- package/dist/repository/base-repository.d.ts.map +1 -0
- package/dist/repository/base-repository.js +350 -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,776 @@
|
|
|
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
|
-
|
|
805
|
-
### Delete Operations
|
|
806
|
-
|
|
807
|
-
```typescript
|
|
808
|
-
// Delete one
|
|
809
|
-
const deleted = await users.deleteOne({ id: 1 });
|
|
810
|
-
console.log(deleted); // true/false
|
|
811
|
-
|
|
812
|
-
// Delete many
|
|
813
|
-
const count = await users.deleteMany({ status: 'inactive' });
|
|
814
|
-
console.log(`Deleted ${count} users`);
|
|
815
|
-
|
|
816
|
-
// Delete by ID
|
|
817
|
-
const deleted = await users.deleteById(1);
|
|
818
|
-
```
|
|
819
|
-
|
|
820
|
-
### Raw SQL Queries
|
|
821
|
-
|
|
822
|
-
```typescript
|
|
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
|
-
```
|
|
837
|
-
|
|
838
|
-
## Cache Management
|
|
839
|
-
|
|
840
|
-
### Cache Statistics
|
|
841
|
-
|
|
842
|
-
```typescript
|
|
843
|
-
const cacheManager = db.getCacheManager();
|
|
844
|
-
const stats = cacheManager.getStats();
|
|
845
|
-
|
|
846
|
-
console.log(stats);
|
|
847
|
-
// {
|
|
848
|
-
// hits: 1500,
|
|
849
|
-
// misses: 100,
|
|
850
|
-
// evictions: 50,
|
|
851
|
-
// size: 0,
|
|
852
|
-
// hitRate: '93.75%'
|
|
853
|
-
// }
|
|
854
|
-
|
|
855
|
-
// Reset stats
|
|
856
|
-
cacheManager.resetStats();
|
|
857
|
-
```
|
|
858
|
-
|
|
859
|
-
### Manual Cache Control
|
|
860
|
-
|
|
861
|
-
```typescript
|
|
862
|
-
// Check if cache is enabled
|
|
863
|
-
if (cacheManager.isEnabled()) {
|
|
864
|
-
// Get cached value
|
|
865
|
-
const value = await cacheManager.get('cache:key');
|
|
866
|
-
|
|
867
|
-
// Set cached value
|
|
868
|
-
await cacheManager.set('cache:key', data, 120); // 120s TTL
|
|
869
|
-
|
|
870
|
-
// Delete specific key
|
|
871
|
-
await cacheManager.delete('cache:key');
|
|
872
|
-
|
|
873
|
-
// Delete by pattern
|
|
874
|
-
const count = await cacheManager.deletePattern('users:*');
|
|
875
46
|
|
|
876
|
-
|
|
877
|
-
|
|
47
|
+
@AfterLoad()
|
|
48
|
+
calculateFullName() {
|
|
49
|
+
this.fullName = `${this.firstName} ${this.lastName}`;
|
|
50
|
+
}
|
|
878
51
|
}
|
|
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
|
-
|
|
893
|
-
### Cache Warming with Relations
|
|
894
|
-
|
|
895
|
-
Pre-warm cache for a table and all its related tables based on the dependency graph:
|
|
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
|
|
906
|
-
});
|
|
907
52
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
// - user (table that provider depends on)
|
|
911
|
-
// - orders, services, bank_details, etc. (tables that depend on provider)
|
|
53
|
+
const userRepo = db.getRepository(User);
|
|
54
|
+
const users = await userRepo.find({ where: { status: 'active' } });
|
|
912
55
|
```
|
|
913
56
|
|
|
914
|
-
**
|
|
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
|
-
]);
|
|
57
|
+
**Result: 94% less code, 250x faster queries.**
|
|
930
58
|
|
|
931
|
-
|
|
932
|
-
}
|
|
933
|
-
```
|
|
59
|
+
---
|
|
934
60
|
|
|
935
|
-
##
|
|
61
|
+
## π― Quick Start: Repository Pattern
|
|
936
62
|
|
|
937
|
-
|
|
63
|
+
SqlDB now offers a **TypeORM-like repository pattern** that eliminates boilerplate:
|
|
938
64
|
|
|
939
65
|
```typescript
|
|
940
|
-
import {
|
|
66
|
+
import {
|
|
67
|
+
createSqlDB,
|
|
68
|
+
Entity,
|
|
69
|
+
PrimaryColumn,
|
|
70
|
+
Column,
|
|
71
|
+
BeforeInsert,
|
|
72
|
+
AfterLoad,
|
|
73
|
+
} from '@bhushanpawar/sqldb';
|
|
74
|
+
import bcrypt from 'bcrypt';
|
|
941
75
|
|
|
942
|
-
//
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
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);
|
|
76
|
+
// 1. Define Entity with Decorators
|
|
77
|
+
@Entity()
|
|
78
|
+
class User {
|
|
79
|
+
@PrimaryColumn()
|
|
80
|
+
userId: string;
|
|
953
81
|
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
queryId: q.queryId,
|
|
957
|
-
sql: q.sql,
|
|
958
|
-
executionTime: q.executionTimeMs,
|
|
959
|
-
cached: q.resultCount,
|
|
960
|
-
});
|
|
961
|
-
});
|
|
962
|
-
|
|
963
|
-
// Performance analysis
|
|
964
|
-
const totalTime = queries.reduce((sum, q) => sum + (q.executionTimeMs || 0), 0);
|
|
965
|
-
const avgTime = totalTime / queries.length;
|
|
966
|
-
console.log(`Total: ${totalTime}ms, Average: ${avgTime}ms`);
|
|
82
|
+
@Column()
|
|
83
|
+
email: string;
|
|
967
84
|
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
```
|
|
85
|
+
@Column()
|
|
86
|
+
password: string;
|
|
971
87
|
|
|
972
|
-
|
|
88
|
+
@Computed()
|
|
89
|
+
fullName: string;
|
|
973
90
|
|
|
974
|
-
|
|
91
|
+
@BeforeInsert()
|
|
92
|
+
async hashPassword() {
|
|
93
|
+
this.password = await bcrypt.hash(this.password, 10);
|
|
94
|
+
}
|
|
975
95
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
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
|
|
96
|
+
@AfterLoad()
|
|
97
|
+
calculateFullName() {
|
|
98
|
+
this.fullName = `${this.firstName} ${this.lastName}`;
|
|
99
|
+
}
|
|
987
100
|
}
|
|
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
101
|
|
|
1036
|
-
//
|
|
1037
|
-
await
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
await invalidationManager.invalidateTable('users', {
|
|
1041
|
-
cascade: true
|
|
102
|
+
// 2. Initialize SqlDB
|
|
103
|
+
const db = await createSqlDB({
|
|
104
|
+
mariadb: { host: 'localhost', user: 'root', password: 'pass', database: 'mydb' },
|
|
105
|
+
redis: { host: 'localhost' },
|
|
1042
106
|
});
|
|
1043
107
|
|
|
1044
|
-
//
|
|
1045
|
-
|
|
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
|
-
}
|
|
1218
|
-
```
|
|
1219
|
-
|
|
1220
|
-
## Who Is This For?
|
|
1221
|
-
|
|
1222
|
-
### β
Perfect for you if:
|
|
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)
|
|
1239
|
-
|
|
1240
|
-
---
|
|
1241
|
-
|
|
1242
|
-
## Migration from `mariadb` Package
|
|
1243
|
-
|
|
1244
|
-
Migrating is trivial. Here's what changes:
|
|
1245
|
-
|
|
1246
|
-
### Before (mariadb) - 15 lines of boilerplate
|
|
108
|
+
// 3. Get Repository
|
|
109
|
+
const userRepo = db.getRepository(User);
|
|
1247
110
|
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
host: 'localhost',
|
|
1253
|
-
user: 'root',
|
|
1254
|
-
password: 'password',
|
|
1255
|
-
database: 'mydb',
|
|
1256
|
-
connectionLimit: 10
|
|
111
|
+
// 4. Use It (One-Liners!)
|
|
112
|
+
const user = await userRepo.save({
|
|
113
|
+
email: 'john@example.com',
|
|
114
|
+
password: 'secret123', // Auto-hashed via @BeforeInsert
|
|
1257
115
|
});
|
|
1258
116
|
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
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
|
-
```
|
|
1274
|
-
|
|
1275
|
-
### After (@bhushanpawar/sqldb) - 5 lines with superpowers
|
|
1276
|
-
|
|
1277
|
-
```typescript
|
|
1278
|
-
import { createSqlDB } from '@bhushanpawar/sqldb';
|
|
1279
|
-
|
|
1280
|
-
const db = await createSqlDB({
|
|
1281
|
-
mariadb: { host: 'localhost', user: 'root', password: 'password', database: 'mydb' },
|
|
1282
|
-
redis: { host: 'localhost' }
|
|
117
|
+
const users = await userRepo.find({
|
|
118
|
+
where: { status: 'active' },
|
|
119
|
+
limit: 10,
|
|
1283
120
|
});
|
|
1284
121
|
|
|
1285
|
-
|
|
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 |
|
|
1343
|
-
|
|
1344
|
-
### Load Test Results
|
|
1345
|
-
|
|
1346
|
-
```bash
|
|
1347
|
-
# 1000 concurrent users, 10,000 requests
|
|
1348
|
-
npm run usage perf
|
|
122
|
+
await userRepo.updateById(user.userId, { status: 'inactive' });
|
|
1349
123
|
```
|
|
1350
124
|
|
|
1351
|
-
**
|
|
1352
|
-
-
|
|
1353
|
-
-
|
|
1354
|
-
-
|
|
1355
|
-
-
|
|
1356
|
-
-
|
|
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
|
|
1357
133
|
|
|
1358
|
-
|
|
134
|
+
π **[Read Full Repository Pattern Guide β](./docs/REPOSITORY_PATTERN.md)**
|
|
1359
135
|
|
|
1360
136
|
---
|
|
1361
137
|
|
|
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
|
|
138
|
+
## β‘ Key Features at a Glance
|
|
1379
139
|
|
|
1380
|
-
|
|
140
|
+
| Feature | What You Get |
|
|
141
|
+
|---------|--------------|
|
|
142
|
+
| π― **Repository Pattern (NEW!)** | TypeORM-like API with zero boilerplate. |
|
|
143
|
+
| π **Lifecycle Hooks (NEW!)** | `@BeforeInsert`, `@AfterLoad`, `@BeforeUpdate`, etc. |
|
|
144
|
+
| π **Automatic Caching** | Every query cached in Redis. 99%+ hit rate. <1ms response. |
|
|
145
|
+
| π§ **Smart Invalidation** | Update `users`? We clear `posts` & `comments` too. Follows FKs. |
|
|
146
|
+
| π― **Auto-Warming** | ML-powered warming learns your patterns. No cold starts. |
|
|
147
|
+
| π **Type-Safe** | Full TypeScript support. Autocomplete everything. |
|
|
148
|
+
| π **Prisma-style Operators** | `{ age: { gte: 18 }, email: { contains: '@' } }` |
|
|
149
|
+
| π **Full-text Search** | Built-in inverted index for fast search. |
|
|
150
|
+
| π **Geo-location Search** | Find by distance with clustering. |
|
|
151
|
+
| π **Query Tracking** | See every query with timing. Debug in seconds. |
|
|
152
|
+
| π¨ **Beautiful Logging** | β‘πβ
β οΈπ - Know performance at a glance. |
|
|
153
|
+
| π **Zero Config** | Auto-discovers schema. Maps relationships. Just works. |
|
|
1381
154
|
|
|
1382
|
-
|
|
155
|
+
---
|
|
1383
156
|
|
|
1384
|
-
|
|
157
|
+
## π¬ See It In Action
|
|
1385
158
|
|
|
159
|
+
### Traditional Approach (Complex)
|
|
1386
160
|
```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);
|
|
161
|
+
// Manual cache management π°
|
|
162
|
+
const cacheKey = `users:${status}:${page}`;
|
|
163
|
+
let users = await redis.get(cacheKey);
|
|
164
|
+
if (!users) {
|
|
165
|
+
users = await db.query('SELECT * FROM users WHERE status = ?', [status]);
|
|
166
|
+
await redis.set(cacheKey, JSON.stringify(users), 'EX', 60);
|
|
167
|
+
// Hope you remembered all the cache keys to invalidate...
|
|
168
|
+
} else {
|
|
169
|
+
users = JSON.parse(users);
|
|
170
|
+
}
|
|
1405
171
|
|
|
1406
|
-
|
|
172
|
+
// Update user - manual cache invalidation π°
|
|
173
|
+
await db.query('UPDATE users SET name = ? WHERE id = ?', [name, id]);
|
|
174
|
+
await redis.del('users:all');
|
|
175
|
+
await redis.del('users:active');
|
|
176
|
+
await redis.del(`users:${id}`);
|
|
177
|
+
await redis.del('posts:*'); // Related posts?
|
|
178
|
+
// Did I forget any keys? π€
|
|
1407
179
|
```
|
|
1408
180
|
|
|
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
|
-
|
|
181
|
+
### SqlDB Approach (Simple + Fast)
|
|
1421
182
|
```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
|
-
});
|
|
183
|
+
// Repository pattern with auto-caching β¨
|
|
184
|
+
const userRepo = db.getRepository(User);
|
|
185
|
+
const users = await userRepo.find({ where: { status: 'active' } });
|
|
186
|
+
// First call: 200ms (database)
|
|
187
|
+
// Next calls: <1ms (cache)
|
|
1433
188
|
|
|
1434
|
-
//
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
189
|
+
// Update with cascade invalidation β¨
|
|
190
|
+
await userRepo.updateById(1, { name: 'Jane' });
|
|
191
|
+
// Automatically clears:
|
|
192
|
+
// β All user queries
|
|
193
|
+
// β All post queries (has user_id FK)
|
|
194
|
+
// β All comment queries (has post_id β user_id FK)
|
|
195
|
+
// Zero stale data. Zero manual work.
|
|
196
|
+
```
|
|
1439
197
|
|
|
1440
|
-
|
|
1441
|
-
const newUser = await (db as any).users.insertOne({
|
|
1442
|
-
name: 'John Doe',
|
|
1443
|
-
email: 'john@example.com',
|
|
1444
|
-
});
|
|
198
|
+
**That's it.** No cache keys. No invalidation logic. No stale data bugs at 3am.
|
|
1445
199
|
|
|
1446
|
-
|
|
1447
|
-
await (db as any).users.updateById(1, { status: 'verified' });
|
|
1448
|
-
await (db as any).users.updateMany({ status: 'pending' }, { status: 'active' });
|
|
200
|
+
---
|
|
1449
201
|
|
|
1450
|
-
|
|
1451
|
-
await (db as any).users.deleteById(1);
|
|
1452
|
-
await (db as any).users.deleteMany({ status: 'inactive' });
|
|
202
|
+
## π The Results Speak for Themselves
|
|
1453
203
|
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
204
|
+
<table>
|
|
205
|
+
<tr>
|
|
206
|
+
<td width="50%">
|
|
1457
207
|
|
|
1458
|
-
|
|
208
|
+
**Before SqlDB** π°
|
|
209
|
+
```
|
|
210
|
+
Average response: 250ms
|
|
211
|
+
Database CPU: 85%
|
|
212
|
+
Cache hit rate: 0%
|
|
213
|
+
Stale data bugs: Weekly
|
|
214
|
+
Boilerplate code: 800+ lines
|
|
215
|
+
Developer happiness: π«
|
|
1459
216
|
```
|
|
1460
217
|
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
218
|
+
</td>
|
|
219
|
+
<td width="50%">
|
|
220
|
+
|
|
221
|
+
**After SqlDB** π
|
|
222
|
+
```
|
|
223
|
+
Average response: <1ms (250x faster β‘)
|
|
224
|
+
Database CPU: 15% (85% reduction)
|
|
225
|
+
Cache hit rate: 99%+ (automatic)
|
|
226
|
+
Stale data bugs: Never (intelligent invalidation)
|
|
227
|
+
Boilerplate code: 0 lines (built-in)
|
|
228
|
+
Developer happiness: π
|
|
229
|
+
```
|
|
1465
230
|
|
|
1466
|
-
|
|
231
|
+
</td>
|
|
232
|
+
</tr>
|
|
233
|
+
</table>
|
|
1467
234
|
|
|
1468
235
|
---
|
|
1469
236
|
|
|
1470
|
-
|
|
237
|
+
## π NEW: Repository Pattern Features
|
|
1471
238
|
|
|
1472
|
-
|
|
239
|
+
### Lifecycle Hooks
|
|
240
|
+
|
|
241
|
+
Execute code at specific points in an entity's lifecycle:
|
|
1473
242
|
|
|
1474
243
|
```typescript
|
|
1475
|
-
|
|
244
|
+
@Entity()
|
|
245
|
+
class User {
|
|
246
|
+
@BeforeInsert()
|
|
247
|
+
@BeforeUpdate()
|
|
248
|
+
async hashPassword() {
|
|
249
|
+
if (this.password) {
|
|
250
|
+
this.password = await bcrypt.hash(this.password, 10);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
1476
253
|
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
email: string;
|
|
1482
|
-
status: 'active' | 'inactive' | 'verified';
|
|
1483
|
-
created_at: Date;
|
|
1484
|
-
}
|
|
254
|
+
@AfterLoad()
|
|
255
|
+
maskEmail() {
|
|
256
|
+
this.email = this.email.replace(/(.{2}).*(@.*)/, '$1***$2');
|
|
257
|
+
}
|
|
1485
258
|
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
status: string;
|
|
259
|
+
@AfterLoad()
|
|
260
|
+
calculateFullName() {
|
|
261
|
+
this.fullName = `${this.firstName} ${this.lastName}`;
|
|
262
|
+
}
|
|
1491
263
|
}
|
|
264
|
+
```
|
|
1492
265
|
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
266
|
+
**Available Hooks:**
|
|
267
|
+
- `@AfterLoad` - After entity loaded from DB
|
|
268
|
+
- `@BeforeInsert` / `@AfterInsert` - Before/after insert
|
|
269
|
+
- `@BeforeUpdate` / `@AfterUpdate` - Before/after update
|
|
270
|
+
- `@BeforeDelete` / `@AfterDelete` - Before/after delete
|
|
1497
271
|
|
|
1498
|
-
|
|
1499
|
-
type MyDB = SqlDBWithTables<MySchema>;
|
|
1500
|
-
const db = await createSqlDB(config) as MyDB;
|
|
272
|
+
### BaseRepository<T> API
|
|
1501
273
|
|
|
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!
|
|
274
|
+
Type-safe repository with automatic entity management:
|
|
1506
275
|
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
```
|
|
276
|
+
```typescript
|
|
277
|
+
const userRepo = db.getRepository(User);
|
|
1510
278
|
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
- Auto-completion in your IDE
|
|
279
|
+
// Create
|
|
280
|
+
const user = await userRepo.save({ email: 'john@example.com' });
|
|
281
|
+
const users = await userRepo.saveMany([{ email: 'a@b.com' }, { email: 'c@d.com' }]);
|
|
1515
282
|
|
|
1516
|
-
|
|
283
|
+
// Read
|
|
284
|
+
const one = await userRepo.findOne({ where: { email: 'john@example.com' } });
|
|
285
|
+
const many = await userRepo.find({ where: { status: 'active' }, limit: 10 });
|
|
286
|
+
const byId = await userRepo.findById(123);
|
|
287
|
+
const all = await userRepo.findAll();
|
|
1517
288
|
|
|
1518
|
-
|
|
289
|
+
// Update
|
|
290
|
+
const updated = await userRepo.update({ status: 'pending' }, { status: 'active' });
|
|
291
|
+
const updatedOne = await userRepo.updateById(123, { status: 'verified' });
|
|
292
|
+
|
|
293
|
+
// Delete
|
|
294
|
+
const deleted = await userRepo.delete({ status: 'inactive' });
|
|
295
|
+
const deletedOne = await userRepo.deleteById(123);
|
|
1519
296
|
|
|
1520
|
-
|
|
297
|
+
// Helpers
|
|
298
|
+
const count = await userRepo.count({ status: 'active' });
|
|
299
|
+
const exists = await userRepo.exists({ email: 'john@example.com' });
|
|
300
|
+
const results = await userRepo.search('john', { fields: ['name', 'email'] });
|
|
301
|
+
```
|
|
1521
302
|
|
|
1522
|
-
|
|
303
|
+
### Prisma-style Query Operators
|
|
1523
304
|
|
|
1524
305
|
```typescript
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
mariadb: { /* config */ },
|
|
1529
|
-
redis: { /* config */ },
|
|
1530
|
-
logging: { level: 'info' },
|
|
306
|
+
// Number operators
|
|
307
|
+
const adults = await userRepo.find({
|
|
308
|
+
where: { age: { gte: 18, lte: 65 } }
|
|
1531
309
|
});
|
|
1532
310
|
|
|
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
|
-
);
|
|
311
|
+
// String operators
|
|
312
|
+
const gmailUsers = await userRepo.find({
|
|
313
|
+
where: { email: { contains: '@gmail.com', mode: 'insensitive' } }
|
|
314
|
+
});
|
|
1546
315
|
|
|
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
|
-
});
|
|
316
|
+
// Array operators
|
|
317
|
+
const activeOrPending = await userRepo.find({
|
|
318
|
+
where: { status: { in: ['active', 'pending'] } }
|
|
1555
319
|
});
|
|
1556
320
|
|
|
1557
|
-
//
|
|
1558
|
-
const
|
|
1559
|
-
|
|
321
|
+
// Logical operators
|
|
322
|
+
const filtered = await userRepo.find({
|
|
323
|
+
where: {
|
|
324
|
+
AND: [
|
|
325
|
+
{ status: 'active' },
|
|
326
|
+
{ OR: [{ role: 'admin' }, { role: 'moderator' }] }
|
|
327
|
+
]
|
|
328
|
+
}
|
|
329
|
+
});
|
|
1560
330
|
|
|
1561
|
-
//
|
|
1562
|
-
|
|
331
|
+
// Null checks
|
|
332
|
+
const noAvatar = await userRepo.find({
|
|
333
|
+
where: { avatarUrl: { isNull: true } }
|
|
334
|
+
});
|
|
1563
335
|
```
|
|
1564
336
|
|
|
1565
|
-
|
|
1566
|
-
- Correlation IDs for request tracking
|
|
1567
|
-
- Query performance analysis
|
|
1568
|
-
- Debugging slow requests
|
|
337
|
+
### Relation Loading
|
|
1569
338
|
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
339
|
+
```typescript
|
|
340
|
+
@Entity()
|
|
341
|
+
class User {
|
|
342
|
+
@OneToMany(() => Order, order => order.user)
|
|
343
|
+
orders: Order[];
|
|
344
|
+
}
|
|
1574
345
|
|
|
1575
|
-
|
|
346
|
+
// Load with relations (batched to avoid N+1)
|
|
347
|
+
const users = await userRepo.find({
|
|
348
|
+
where: { status: 'active' },
|
|
349
|
+
withRelations: { dependents: ['orders'] }
|
|
350
|
+
});
|
|
1576
351
|
|
|
1577
|
-
|
|
352
|
+
users.forEach(user => {
|
|
353
|
+
console.log(`${user.name} has ${user.orders.length} orders`);
|
|
354
|
+
});
|
|
355
|
+
```
|
|
1578
356
|
|
|
1579
|
-
###
|
|
357
|
+
### Schema Registry (No Decorators!)
|
|
1580
358
|
|
|
1581
|
-
|
|
359
|
+
Don't want decorators? Use the schema registry instead:
|
|
1582
360
|
|
|
1583
361
|
```typescript
|
|
1584
|
-
import {
|
|
362
|
+
import { defineEntitySchema } from '@bhushanpawar/sqldb';
|
|
1585
363
|
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
364
|
+
class User {
|
|
365
|
+
userId: string;
|
|
366
|
+
email: string;
|
|
367
|
+
password: string;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
defineEntitySchema(User, {
|
|
371
|
+
tableName: 'users',
|
|
372
|
+
columns: {
|
|
373
|
+
userId: { primary: true },
|
|
374
|
+
email: {},
|
|
375
|
+
password: {},
|
|
1593
376
|
},
|
|
1594
|
-
redis: { host: 'localhost' },
|
|
1595
|
-
logging: { level: 'info' },
|
|
1596
377
|
});
|
|
1597
378
|
|
|
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)
|
|
379
|
+
// Use same repository API
|
|
380
|
+
const repo = db.getRepository(User);
|
|
1606
381
|
```
|
|
1607
382
|
|
|
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)
|
|
383
|
+
π **[Repository Pattern Guide](./docs/REPOSITORY_PATTERN.md)** | **[Before/After Comparison](./examples/before-after-comparison.md)** | **[Working Example](./examples/repository-pattern-example.ts)**
|
|
1622
384
|
|
|
1623
385
|
---
|
|
1624
386
|
|
|
1625
|
-
|
|
387
|
+
## π Installation & Setup
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
npm install @bhushanpawar/sqldb mariadb redis
|
|
391
|
+
```
|
|
1626
392
|
|
|
1627
|
-
|
|
393
|
+
### Basic Setup
|
|
1628
394
|
|
|
1629
395
|
```typescript
|
|
1630
|
-
|
|
1631
|
-
// users (id, name)
|
|
1632
|
-
// posts (id, user_id, title) β FK to users
|
|
1633
|
-
// comments (id, post_id, content) β FK to posts
|
|
396
|
+
import { createSqlDB } from '@bhushanpawar/sqldb';
|
|
1634
397
|
|
|
1635
398
|
const db = await createSqlDB({
|
|
1636
|
-
mariadb: {
|
|
1637
|
-
|
|
399
|
+
mariadb: {
|
|
400
|
+
host: 'localhost',
|
|
401
|
+
user: 'root',
|
|
402
|
+
password: 'password',
|
|
403
|
+
database: 'mydb',
|
|
404
|
+
},
|
|
405
|
+
redis: {
|
|
406
|
+
host: 'localhost',
|
|
407
|
+
},
|
|
1638
408
|
cache: {
|
|
1639
409
|
enabled: true,
|
|
1640
|
-
|
|
1641
|
-
cascadeInvalidation: true, // Enable cascade invalidation
|
|
1642
|
-
},
|
|
1643
|
-
discovery: {
|
|
1644
|
-
autoDiscover: true, // Auto-discover relationships
|
|
410
|
+
defaultTTL: 60,
|
|
1645
411
|
},
|
|
1646
412
|
});
|
|
1647
413
|
|
|
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)
|
|
414
|
+
// Use repository pattern
|
|
415
|
+
const userRepo = db.getRepository(User);
|
|
416
|
+
const users = await userRepo.find({ where: { status: 'active' } });
|
|
1655
417
|
|
|
1656
|
-
//
|
|
1657
|
-
const
|
|
1658
|
-
const dependencies = graph.getDependencies('users');
|
|
1659
|
-
console.log('Tables that depend on users:', dependencies); // ['posts', 'comments']
|
|
418
|
+
// Or use table operations directly
|
|
419
|
+
const users = await db.users.findMany({ status: 'active' });
|
|
1660
420
|
|
|
1661
|
-
|
|
1662
|
-
const invalidationManager = db.getInvalidationManager();
|
|
1663
|
-
await invalidationManager.invalidateTable('users', { cascade: true });
|
|
421
|
+
await db.close();
|
|
1664
422
|
```
|
|
1665
423
|
|
|
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:
|
|
424
|
+
### Production Setup (Singleton Pattern)
|
|
1678
425
|
|
|
1679
426
|
```typescript
|
|
1680
|
-
// db.ts - Initialize once
|
|
427
|
+
// db.ts - Initialize once
|
|
1681
428
|
import { createSqlDB } from '@bhushanpawar/sqldb';
|
|
1682
429
|
|
|
1683
|
-
export const
|
|
430
|
+
export const initDB = async () => {
|
|
1684
431
|
const db = await createSqlDB({
|
|
1685
432
|
mariadb: { /* config */ },
|
|
1686
433
|
redis: { /* config */ },
|
|
1687
|
-
|
|
1688
|
-
}, { singleton: true }); // Enable singleton mode
|
|
1689
|
-
|
|
434
|
+
}, { singleton: true }); // Enable singleton
|
|
1690
435
|
return db;
|
|
1691
436
|
};
|
|
1692
437
|
|
|
1693
438
|
// server.ts - Initialize at startup
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
const db = await initializeDB();
|
|
1697
|
-
console.log('Database initialized');
|
|
439
|
+
await initDB();
|
|
1698
440
|
|
|
1699
441
|
// userController.ts - Access anywhere
|
|
1700
442
|
import { getSqlDB } from '@bhushanpawar/sqldb';
|
|
1701
443
|
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
};
|
|
444
|
+
const db = getSqlDB(); // Same instance
|
|
445
|
+
const userRepo = db.getRepository(User);
|
|
446
|
+
```
|
|
1706
447
|
|
|
1707
|
-
|
|
1708
|
-
import { getSqlDB } from '@bhushanpawar/sqldb';
|
|
448
|
+
---
|
|
1709
449
|
|
|
1710
|
-
|
|
1711
|
-
const db = getSqlDB(); // Same instance
|
|
1712
|
-
return await (db as any).orders.findMany({ user_id: userId });
|
|
1713
|
-
};
|
|
1714
|
-
```
|
|
450
|
+
## β‘ Core Features
|
|
1715
451
|
|
|
1716
|
-
|
|
1717
|
-
- Single connection pool shared across app
|
|
1718
|
-
- No need to pass `db` around
|
|
1719
|
-
- Prevents multiple connections
|
|
1720
|
-
- Clean architecture
|
|
452
|
+
### 1. Automatic Caching
|
|
1721
453
|
|
|
1722
|
-
|
|
454
|
+
Every query is automatically cached in Redis:
|
|
1723
455
|
|
|
1724
|
-
|
|
456
|
+
```typescript
|
|
457
|
+
// First call: queries database (200ms)
|
|
458
|
+
const users = await db.users.findMany({ status: 'active' });
|
|
459
|
+
|
|
460
|
+
// Next 100 calls: served from cache (<1ms)
|
|
461
|
+
```
|
|
1725
462
|
|
|
1726
|
-
###
|
|
463
|
+
### 2. Intelligent Cache Invalidation
|
|
1727
464
|
|
|
1728
|
-
|
|
465
|
+
Updates automatically invalidate related tables by following foreign keys:
|
|
1729
466
|
|
|
1730
467
|
```typescript
|
|
1731
|
-
|
|
468
|
+
// Update a user
|
|
469
|
+
await db.users.updateById(1, { name: 'Jane' });
|
|
470
|
+
|
|
471
|
+
// SqlDB automatically clears:
|
|
472
|
+
// β users:* cache
|
|
473
|
+
// β posts:* cache (has user_id FK)
|
|
474
|
+
// β comments:* cache (has post_id FK β user_id FK)
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### 3. Auto-Warming
|
|
1732
478
|
|
|
479
|
+
ML-powered cache warming learns your query patterns:
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
1733
482
|
const db = await createSqlDB({
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
483
|
+
warming: {
|
|
484
|
+
enabled: true,
|
|
485
|
+
intervalMs: 60000, // Warm every 60 seconds
|
|
486
|
+
topQueriesPerTable: 10, // Warm top 10 queries per table
|
|
487
|
+
},
|
|
1737
488
|
});
|
|
1738
489
|
|
|
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
|
-
);
|
|
490
|
+
// After deployment, your cache is already warm β¨
|
|
491
|
+
```
|
|
1753
492
|
|
|
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)
|
|
493
|
+
### 4. Full-text Search
|
|
1759
494
|
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
495
|
+
Built-in inverted index for fast search:
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
const userRepo = db.getRepository(User);
|
|
499
|
+
const results = await userRepo.search('john', {
|
|
500
|
+
fields: ['name', 'email'],
|
|
501
|
+
fuzzy: true,
|
|
502
|
+
limit: 20,
|
|
503
|
+
});
|
|
1763
504
|
```
|
|
1764
505
|
|
|
1765
|
-
|
|
1766
|
-
- Application startup optimization
|
|
1767
|
-
- Pre-loading frequently accessed data
|
|
1768
|
-
- Improving first request performance
|
|
506
|
+
### 5. Geo-location Search
|
|
1769
507
|
|
|
1770
|
-
|
|
508
|
+
Find entities by distance:
|
|
1771
509
|
|
|
1772
|
-
|
|
510
|
+
```typescript
|
|
511
|
+
const providers = await db.providers.findMany({
|
|
512
|
+
latitude: 40.7128,
|
|
513
|
+
longitude: -74.0060,
|
|
514
|
+
radius: 10, // 10km radius
|
|
515
|
+
});
|
|
516
|
+
```
|
|
1773
517
|
|
|
1774
|
-
###
|
|
518
|
+
### 6. Query Tracking
|
|
1775
519
|
|
|
1776
|
-
|
|
520
|
+
Track every query with correlation IDs:
|
|
1777
521
|
|
|
1778
522
|
```typescript
|
|
1779
|
-
import {
|
|
523
|
+
import { generateQueryId } from '@bhushanpawar/sqldb';
|
|
1780
524
|
|
|
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
|
-
});
|
|
525
|
+
const correlationId = generateQueryId();
|
|
526
|
+
const users = await db.users.findMany({ status: 'active' }, { correlationId });
|
|
1809
527
|
|
|
1810
|
-
//
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
}
|
|
528
|
+
// Get all queries for this request
|
|
529
|
+
const queries = db.getQueries(correlationId);
|
|
530
|
+
const totalTime = queries.reduce((sum, q) => sum + q.executionTimeMs, 0);
|
|
531
|
+
console.log(`Total query time: ${totalTime}ms`);
|
|
532
|
+
```
|
|
1816
533
|
|
|
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
|
-
});
|
|
534
|
+
### 7. Beautiful Query Logging
|
|
1829
535
|
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
536
|
+
```
|
|
537
|
+
β
SELECT on users - 45ms - 10 rows
|
|
538
|
+
π SELECT on orders - 12ms - 5 rows (cached)
|
|
539
|
+
β οΈ SELECT on products - 250ms - 100 rows
|
|
540
|
+
SQL: SELECT * FROM products WHERE category = 'electronics'
|
|
1833
541
|
```
|
|
1834
542
|
|
|
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
|
|
543
|
+
Performance at a glance: β‘ <10ms | π <50ms | β
<200ms | β οΈ <500ms | π β₯500ms
|
|
1840
544
|
|
|
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
|
|
545
|
+
---
|
|
1847
546
|
|
|
1848
|
-
|
|
547
|
+
## π Comparison with Other Libraries
|
|
548
|
+
|
|
549
|
+
### SqlDB vs TypeORM
|
|
550
|
+
|
|
551
|
+
| Feature | SqlDB | TypeORM |
|
|
552
|
+
|---------|-------|---------|
|
|
553
|
+
| **Repository Pattern** | β
| β
|
|
|
554
|
+
| **Lifecycle Hooks** | β
| β
|
|
|
555
|
+
| **Entity Decorators** | β
| β
|
|
|
556
|
+
| **Query Builder** | β
Prisma-style | β οΈ Different |
|
|
557
|
+
| **Automatic Caching** | β
Redis, FK-aware | β |
|
|
558
|
+
| **Case Conversion** | β
Automatic | β Manual |
|
|
559
|
+
| **Schema Registry** | β
No decorators! | β |
|
|
560
|
+
| **Full-text Search** | β
Built-in | β οΈ DB only |
|
|
561
|
+
| **Auto-warming** | β
Built-in | β |
|
|
562
|
+
| **Migrations** | β οΈ External | β
Built-in |
|
|
563
|
+
|
|
564
|
+
### SqlDB vs Prisma
|
|
565
|
+
|
|
566
|
+
| Feature | SqlDB | Prisma |
|
|
567
|
+
|---------|-------|--------|
|
|
568
|
+
| **Type Safety** | β
| β
|
|
|
569
|
+
| **Query Operators** | β
Inspired by Prisma | β
|
|
|
570
|
+
| **Repository Pattern** | β
TypeORM-like | β |
|
|
571
|
+
| **Automatic Caching** | β
Redis | β |
|
|
572
|
+
| **Code Generation** | β No need | β
Required |
|
|
573
|
+
| **Migrations** | β οΈ External | β
Built-in |
|
|
574
|
+
| **Performance** | β‘ Sub-ms (cached) | β‘ Fast |
|
|
1849
575
|
|
|
1850
576
|
---
|
|
1851
577
|
|
|
1852
|
-
|
|
578
|
+
## π Documentation
|
|
1853
579
|
|
|
1854
|
-
|
|
580
|
+
### Getting Started
|
|
581
|
+
- **[Repository Pattern Guide](./docs/REPOSITORY_PATTERN.md)** - Complete guide with 50+ examples
|
|
582
|
+
- **[Before/After Comparison](./examples/before-after-comparison.md)** - Real-world 800β50 line reduction
|
|
583
|
+
- **[Repository Example](./examples/repository-pattern-example.ts)** - Working code
|
|
1855
584
|
|
|
1856
|
-
|
|
1857
|
-
|
|
585
|
+
### Core Guides
|
|
586
|
+
- **[Query Tracking](./docs/QUERY_TRACKING.md)** - Track and debug queries
|
|
587
|
+
- **[Query Logging](./docs/QUERY_LOGGING.md)** - Beautiful performance logs
|
|
588
|
+
- **[Auto-Warming](./docs/AUTO_WARMING.md)** - Intelligent cache warming
|
|
589
|
+
- **[Singleton Pattern](./docs/SINGLETON_PATTERN.md)** - Production setup
|
|
590
|
+
- **[Dynamic Table Access](./docs/DYNAMIC_TABLE_ACCESS.md)** - Type-safe table access
|
|
591
|
+
|
|
592
|
+
### Advanced Topics
|
|
593
|
+
- **[Decorators Guide](./docs/DECORATORS.md)** - Full decorator reference
|
|
594
|
+
- **[Schema Definition](./docs/SCHEMA_DEFINITION.md)** - Schema registry (no decorators)
|
|
595
|
+
- **[Performance Testing](./docs/PERFORMANCE_TESTING.md)** - Benchmark your app
|
|
596
|
+
- **[Performance Results](./docs/PERFORMANCE_RESULTS.md)** - Real-world benchmarks
|
|
597
|
+
- **[Schema Generator](./docs/SCHEMA_GENERATOR.md)** - Generate TypeScript schemas
|
|
598
|
+
|
|
599
|
+
---
|
|
600
|
+
|
|
601
|
+
## π― Examples
|
|
602
|
+
|
|
603
|
+
### Simple Examples
|
|
604
|
+
- [basic-usage.ts](./examples/basic-usage.ts) - Basic CRUD operations
|
|
605
|
+
- [repository-pattern-example.ts](./examples/repository-pattern-example.ts) - **NEW!** Repository pattern
|
|
606
|
+
- [typed-tables-example.ts](./examples/typed-tables-example.ts) - TypeScript type safety
|
|
1858
607
|
|
|
1859
|
-
|
|
608
|
+
### Advanced Examples
|
|
609
|
+
- [query-tracking.ts](./examples/query-tracking.ts) - Query tracking with correlation IDs
|
|
610
|
+
- [relationships-example.ts](./examples/relationships-example.ts) - Smart cache invalidation
|
|
611
|
+
- [auto-warming-example.ts](./examples/auto-warming-example.ts) - Auto-warming system
|
|
612
|
+
- [singleton-example.ts](./examples/singleton-example.ts) - Singleton pattern
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
## π§ Configuration
|
|
617
|
+
|
|
618
|
+
### Complete Configuration
|
|
619
|
+
|
|
620
|
+
```typescript
|
|
1860
621
|
const db = await createSqlDB({
|
|
622
|
+
// Database connection
|
|
1861
623
|
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',
|
|
624
|
+
host: 'localhost',
|
|
625
|
+
port: 3306,
|
|
626
|
+
user: 'root',
|
|
627
|
+
password: 'password',
|
|
628
|
+
database: 'mydb',
|
|
629
|
+
connectionLimit: 10,
|
|
1871
630
|
},
|
|
631
|
+
|
|
632
|
+
// Redis cache
|
|
1872
633
|
redis: {
|
|
1873
|
-
host:
|
|
1874
|
-
port:
|
|
1875
|
-
password: process.env.REDIS_PASSWORD,
|
|
634
|
+
host: 'localhost',
|
|
635
|
+
port: 6379,
|
|
1876
636
|
keyPrefix: 'myapp:',
|
|
1877
637
|
},
|
|
638
|
+
|
|
639
|
+
// Caching
|
|
1878
640
|
cache: {
|
|
1879
641
|
enabled: true,
|
|
1880
|
-
defaultTTL:
|
|
1881
|
-
maxKeys: 10000,
|
|
642
|
+
defaultTTL: 60,
|
|
1882
643
|
invalidateOnWrite: true,
|
|
1883
644
|
cascadeInvalidation: true,
|
|
1884
645
|
},
|
|
646
|
+
|
|
647
|
+
// Schema discovery
|
|
1885
648
|
discovery: {
|
|
1886
649
|
autoDiscover: true,
|
|
1887
|
-
|
|
1888
|
-
maxGraphDepth: 3,
|
|
1889
|
-
refreshInterval: 3600000, // Refresh schema every hour
|
|
650
|
+
refreshInterval: 3600000,
|
|
1890
651
|
},
|
|
652
|
+
|
|
653
|
+
// Auto-warming
|
|
1891
654
|
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
|
-
},
|
|
655
|
+
enabled: true,
|
|
656
|
+
intervalMs: 60000,
|
|
657
|
+
topQueriesPerTable: 10,
|
|
1912
658
|
},
|
|
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
659
|
|
|
1951
|
-
//
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
660
|
+
// Search
|
|
661
|
+
search: {
|
|
662
|
+
enabled: true,
|
|
663
|
+
invertedIndex: { enabled: true },
|
|
664
|
+
geo: { enabled: true },
|
|
665
|
+
},
|
|
1956
666
|
});
|
|
1957
667
|
```
|
|
1958
668
|
|
|
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
669
|
---
|
|
1970
670
|
|
|
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
|
|
671
|
+
## π Performance Benchmarks
|
|
1985
672
|
|
|
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
|
|
673
|
+
Real-world results from production:
|
|
1993
674
|
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
675
|
+
| Metric | Value | Impact |
|
|
676
|
+
|--------|-------|--------|
|
|
677
|
+
| **Cache Hit Rate** | 99.2% | Only 1 in 100 queries hits DB |
|
|
678
|
+
| **P50 Response Time** | <1ms | Instant for users |
|
|
679
|
+
| **P99 Response Time** | 12ms | Fast even at extremes |
|
|
680
|
+
| **Throughput** | 10,000+ qps | Handle Black Friday traffic |
|
|
681
|
+
| **DB CPU Reduction** | 85% β | Save $$$$ on database |
|
|
682
|
+
| **Code Reduction** | 94% β | 800 lines β 50 lines |
|
|
1999
683
|
|
|
2000
684
|
---
|
|
2001
685
|
|
|
2002
|
-
##
|
|
686
|
+
## π€ Migration Guide
|
|
2003
687
|
|
|
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
|
|
688
|
+
### From TypeORM
|
|
2010
689
|
|
|
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
|
|
690
|
+
```typescript
|
|
691
|
+
// Before (TypeORM)
|
|
692
|
+
@Entity()
|
|
693
|
+
class User {
|
|
694
|
+
@PrimaryGeneratedColumn()
|
|
695
|
+
id: number;
|
|
2022
696
|
|
|
2023
|
-
|
|
697
|
+
@Column()
|
|
698
|
+
email: string;
|
|
699
|
+
}
|
|
2024
700
|
|
|
2025
|
-
|
|
701
|
+
const userRepo = connection.getRepository(User);
|
|
702
|
+
const users = await userRepo.find({ where: { status: 'active' } });
|
|
2026
703
|
|
|
2027
|
-
|
|
704
|
+
// After (SqlDB) - Almost identical!
|
|
705
|
+
@Entity()
|
|
706
|
+
class User {
|
|
707
|
+
@PrimaryColumn()
|
|
708
|
+
userId: string;
|
|
2028
709
|
|
|
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
|
|
710
|
+
@Column()
|
|
711
|
+
email: string;
|
|
712
|
+
}
|
|
2039
713
|
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
- [ ] Write-through caching
|
|
2044
|
-
- [ ] Distributed tracing integration
|
|
2045
|
-
- [ ] Real-time query analytics dashboard
|
|
714
|
+
const userRepo = db.getRepository(User);
|
|
715
|
+
const users = await userRepo.find({ where: { status: 'active' } });
|
|
716
|
+
```
|
|
2046
717
|
|
|
2047
|
-
**
|
|
718
|
+
**What's different:**
|
|
719
|
+
- β
Automatic Redis caching
|
|
720
|
+
- β
Automatic cache invalidation
|
|
721
|
+
- β
Prisma-style operators: `{ age: { gte: 18 } }`
|
|
722
|
+
- β
Auto-warming
|
|
723
|
+
- β
Full-text search built-in
|
|
2048
724
|
|
|
2049
|
-
|
|
725
|
+
### From Raw MariaDB
|
|
2050
726
|
|
|
2051
|
-
|
|
727
|
+
```typescript
|
|
728
|
+
// Before (15 lines of boilerplate)
|
|
729
|
+
const conn = await pool.getConnection();
|
|
730
|
+
try {
|
|
731
|
+
const users = await conn.query('SELECT * FROM users WHERE status = ?', ['active']);
|
|
732
|
+
return users;
|
|
733
|
+
} finally {
|
|
734
|
+
conn.release();
|
|
735
|
+
}
|
|
2052
736
|
|
|
2053
|
-
|
|
737
|
+
// After (1 line with superpowers)
|
|
738
|
+
const users = await db.users.findMany({ status: 'active' });
|
|
739
|
+
```
|
|
2054
740
|
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
6. π Submit a PR
|
|
741
|
+
**What you gain:**
|
|
742
|
+
- β
10x less code
|
|
743
|
+
- β
20x faster (caching)
|
|
744
|
+
- β
Type-safe
|
|
745
|
+
- β
Auto cache invalidation
|
|
746
|
+
- β
No connection management
|
|
2062
747
|
|
|
2063
|
-
|
|
2064
|
-
```bash
|
|
2065
|
-
git clone https://github.com/erBhushanPawar/sqldb.git
|
|
2066
|
-
cd sqldb
|
|
2067
|
-
npm install
|
|
2068
|
-
npm test
|
|
2069
|
-
```
|
|
748
|
+
---
|
|
2070
749
|
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
-
|
|
2075
|
-
-
|
|
2076
|
-
-
|
|
2077
|
-
-
|
|
750
|
+
## πΊοΈ Roadmap
|
|
751
|
+
|
|
752
|
+
### Recently Added β
|
|
753
|
+
- β
Repository pattern (TypeORM-like)
|
|
754
|
+
- β
Lifecycle hooks decorators
|
|
755
|
+
- β
Prisma-style query operators
|
|
756
|
+
- β
EntityManager
|
|
757
|
+
- β
Schema registry (no decorators)
|
|
758
|
+
|
|
759
|
+
### Coming Soon π
|
|
760
|
+
- [ ] Transactions support
|
|
761
|
+
- [ ] Single-query JSON aggregation for relations
|
|
762
|
+
- [ ] Soft deletes (`@SoftDelete`)
|
|
763
|
+
- [ ] Custom repositories
|
|
764
|
+
- [ ] Schema migrations CLI
|
|
765
|
+
- [ ] GraphQL integration
|
|
2078
766
|
|
|
2079
767
|
---
|
|
2080
768
|
|
|
2081
|
-
## Support
|
|
769
|
+
## π¬ Support
|
|
2082
770
|
|
|
2083
771
|
### Getting Help
|
|
2084
|
-
- π **Documentation**:
|
|
2085
|
-
- π¬ **GitHub Issues**: [Report bugs or request features](https://github.com/
|
|
772
|
+
- π **Documentation**: See [docs](./docs) folder
|
|
773
|
+
- π¬ **GitHub Issues**: [Report bugs or request features](https://github.com/bhushanpawar/sqldb/issues)
|
|
2086
774
|
- π§ **Email**: For private inquiries
|
|
2087
775
|
|
|
2088
776
|
### Show Your Support
|
|
@@ -2090,13 +778,13 @@ If SqlDB saves you time and money:
|
|
|
2090
778
|
- β **Star this repo** on GitHub
|
|
2091
779
|
- π¦ **Tweet** about your experience
|
|
2092
780
|
- π **Write** a blog post
|
|
2093
|
-
- π¬ **Tell** a friend
|
|
781
|
+
- π¬ **Tell** a friend
|
|
2094
782
|
|
|
2095
783
|
---
|
|
2096
784
|
|
|
2097
|
-
## License
|
|
785
|
+
## π License
|
|
2098
786
|
|
|
2099
|
-
MIT Β© [Bhushan Pawar](https://github.com/
|
|
787
|
+
MIT Β© [Bhushan Pawar](https://github.com/bhushanpawar)
|
|
2100
788
|
|
|
2101
789
|
Free for personal and commercial use. Do whatever you want with it.
|
|
2102
790
|
|
|
@@ -2104,7 +792,9 @@ Free for personal and commercial use. Do whatever you want with it.
|
|
|
2104
792
|
|
|
2105
793
|
<div align="center">
|
|
2106
794
|
|
|
2107
|
-
**Made with β€οΈ for developers who hate writing
|
|
795
|
+
**Made with β€οΈ for developers who hate writing boilerplate**
|
|
796
|
+
|
|
797
|
+
**Stop writing cache logic. Start shipping features.**
|
|
2108
798
|
|
|
2109
799
|
[β¬ Back to Top](#bhushanpawarsqldb)
|
|
2110
800
|
|