stabilize-orm 1.3.9 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1097 -561
- package/dist/auto-migrate.d.ts +34 -0
- package/dist/auto-migrate.d.ts.map +1 -0
- package/dist/auto-migrate.js +3003 -0
- package/dist/auto-migrate.js.map +163 -0
- package/dist/cache.d.ts +90 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +166 -0
- package/dist/cache.js.map +64 -0
- package/dist/client.d.ts +73 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +2997 -0
- package/dist/client.js.map +162 -0
- package/dist/hooks.d.ts +31 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +4 -0
- package/dist/hooks.js.map +11 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3183 -0
- package/dist/index.js.map +222 -0
- package/dist/logger.d.ts +40 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +8 -0
- package/dist/logger.js.map +11 -0
- package/dist/migrations.d.ts +31 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +3009 -0
- package/dist/migrations.js.map +164 -0
- package/{model.ts → dist/model.d.ts} +124 -189
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +4 -0
- package/dist/model.js.map +10 -0
- package/dist/query-builder.d.ts +91 -0
- package/dist/query-builder.d.ts.map +1 -0
- package/dist/query-builder.js +14 -0
- package/dist/query-builder.js.map +12 -0
- package/dist/repository.d.ts +165 -0
- package/dist/repository.d.ts.map +1 -0
- package/dist/repository.js +176 -0
- package/dist/repository.js.map +69 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types.d.ts +110 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +10 -0
- package/dist/utils/encryption.d.ts +13 -0
- package/dist/utils/encryption.d.ts.map +1 -0
- package/dist/utils/encryption.js +4 -0
- package/dist/utils/encryption.js.map +10 -0
- package/package.json +96 -17
- package/.eslintrc.json +0 -10
- package/.github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md +0 -23
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -25
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -17
- package/.github/workflows/ci-cd.yml +0 -22
- package/CHANGELOG.md +0 -75
- package/CODE_OF_CONDUCT.md +0 -87
- package/CONTRIBUTING.md +0 -48
- package/FUNDING.md +0 -14
- package/SECURITY.md +0 -35
- package/SUPPORT.md +0 -18
- package/bun.lock +0 -667
- package/cache.ts +0 -181
- package/client.ts +0 -249
- package/docker-compose.yml +0 -22
- package/hooks.ts +0 -76
- package/index.ts +0 -158
- package/logger.ts +0 -127
- package/migrations.ts +0 -318
- package/public/logo_both-transparent.png +0 -0
- package/public/logo_iamge-transparent.png +0 -0
- package/public/logo_text-transparent.png +0 -0
- package/query-builder.ts +0 -209
- package/repository.ts +0 -1096
- package/tests/migrations.test.ts +0 -141
- package/tsconfig.json +0 -32
- package/types.ts +0 -106
package/README.md
CHANGED
|
@@ -1,561 +1,1097 @@
|
|
|
1
|
-
# Stabilize ORM
|
|
2
|
-
|
|
3
|
-
_A Modern, Type-Safe, and Expressive ORM for Bun_
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
<a href="https://
|
|
11
|
-
<a href="https://github.com/ElectronSz/stabilize-cli"><img src="https://img.shields.io/
|
|
12
|
-
<a href="
|
|
13
|
-
<a href="#"><img src="https://img.shields.io/badge/
|
|
14
|
-
<a href="#"><img src="https://img.shields.io/badge/
|
|
15
|
-
<a href="https://
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
**Stabilize** is a lightweight, feature-rich ORM designed for performance and developer experience. It provides a unified, database-agnostic API for **PostgreSQL**, **MySQL**, and **SQLite**. Powered by a robust query builder, programmatic model definitions, automatic versioning, and a full-featured command-line interface, Stabilize is built to scale with your app.
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## 🚀 Features
|
|
27
|
-
|
|
28
|
-
- **Unified API**: Write once, run on PostgreSQL, MySQL, or SQLite.
|
|
29
|
-
- **Programmatic Model Definitions**: Define models and columns using the `defineModel` API with the `DataTypes` enum for database-agnostic schemas.
|
|
30
|
-
- **Full-Featured CLI**: Generate models, manage migrations, seed data, and reset your database from the command line with [stabilize-cli](https://github.com/ElectronSz/stabilize-cli).
|
|
31
|
-
- **Automatic Migrations**: Generate database-specific SQL schemas directly from your model definitions.
|
|
32
|
-
- **Versioned Models & Time-Travel**: Enable versioning in your model configuration for automatic history tables and snapshot queries.
|
|
33
|
-
- **Retry Logic**: Automatic exponential backoff for database queries to handle transient connection issues.
|
|
34
|
-
- **Connection Pooling**: Efficient connection management for PostgreSQL and MySQL.
|
|
35
|
-
- **Transactional Integrity**: Built-in support for atomic transactions with automatic rollback on failure.
|
|
36
|
-
- **Advanced Query Builder**: Fluent, chainable API for building complex queries, including joins, filters, ordering, and pagination.
|
|
37
|
-
- **
|
|
38
|
-
- **
|
|
39
|
-
- **
|
|
40
|
-
- **
|
|
41
|
-
- **
|
|
42
|
-
- **
|
|
43
|
-
- **Custom
|
|
44
|
-
- **
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
###
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
1
|
+
# Stabilize ORM
|
|
2
|
+
|
|
3
|
+
_A Modern, Type-Safe, and Expressive ORM for Bun_
|
|
4
|
+
|
|
5
|
+
<p align="left">
|
|
6
|
+
<img src="./public/logo_both-transparent.png" alt="Stabilize ORM Logo" width="280" />
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="left">
|
|
10
|
+
<a href="https://www.npmjs.com/package/stabilize-orm"><img src="https://img.shields.io/npm/v/stabilize-orm.svg?label=version&color=blue" alt="NPM Version"></a>
|
|
11
|
+
<a href="https://github.com/ElectronSz/stabilize-cli/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/stabilize-orm.svg?color=green" alt="License"></a>
|
|
12
|
+
<a href="https://github.com/ElectronSz/stabilize-cli"><img src="https://img.shields.io/badge/Cli-Stabilize%202.1.0-blue.svg" alt="Stabilize CLI"></a>
|
|
13
|
+
<a href="#"><img src="https://img.shields.io/badge/PostgreSQL-supported-blue" alt="PostgreSQL"></a>
|
|
14
|
+
<a href="#"><img src="https://img.shields.io/badge/MySQL-supported-blue" alt="MySQL"></a>
|
|
15
|
+
<a href="#"><img src="https://img.shields.io/badge/SQLite-supported-blue" alt="SQLite"></a>
|
|
16
|
+
<a href="https://github.com/ElectronSz/stabilize-orm/actions/workflows/ci-cd.yml">
|
|
17
|
+
<img src="https://github.com/ElectronSz/stabilize-orm/actions/workflows/ci-cd.yml/badge.svg" alt="Build Status">
|
|
18
|
+
</a>
|
|
19
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-green.svg" alt="MIT License"></a>
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
**Stabilize** is a lightweight, feature-rich ORM designed for performance and developer experience. It provides a unified, database-agnostic API for **PostgreSQL**, **MySQL**, and **SQLite**. Powered by a robust query builder, programmatic model definitions, automatic versioning, and a full-featured command-line interface, Stabilize is built to scale with your app.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 🚀 Features
|
|
27
|
+
|
|
28
|
+
- **Unified API**: Write once, run on PostgreSQL, MySQL, or SQLite.
|
|
29
|
+
- **Programmatic Model Definitions**: Define models and columns using the `defineModel` API with the `DataTypes` enum for database-agnostic schemas.
|
|
30
|
+
- **Full-Featured CLI**: Generate models, manage migrations, seed data, and reset your database from the command line with [stabilize-cli](https://github.com/ElectronSz/stabilize-cli).
|
|
31
|
+
- **Automatic Migrations**: Generate database-specific SQL schemas directly from your model definitions.
|
|
32
|
+
- **Versioned Models & Time-Travel**: Enable versioning in your model configuration for automatic history tables and snapshot queries.
|
|
33
|
+
- **Retry Logic**: Automatic exponential backoff for database queries to handle transient connection issues.
|
|
34
|
+
- **Connection Pooling**: Efficient connection management for PostgreSQL and MySQL.
|
|
35
|
+
- **Transactional Integrity**: Built-in support for atomic transactions with automatic rollback on failure.
|
|
36
|
+
- **Advanced Query Builder**: Fluent, chainable API for building complex queries, including joins, filters, ordering, and pagination.
|
|
37
|
+
- **Pagination Helper**: Easily paginate any query with `.paginate(page, pageSize)` and get `{ data, total, page, pageSize }`.
|
|
38
|
+
- **Advanced Model Validation**: Enforce rules like `required`, `minLength`, `maxLength`, `pattern`, and custom validators—errors are thrown on invalid input.
|
|
39
|
+
- **Model Relationships**: Define `OneToOne`, `ManyToOne`, `OneToMany`, and `ManyToMany` relationships in the model configuration.
|
|
40
|
+
- **Soft Deletes**: Enable soft deletes in the model configuration for transparent "deleted" flags and safe row removal.
|
|
41
|
+
- **Lifecycle Hooks**: Define hooks in the model configuration or as class methods for lifecycle events like `beforeCreate`, `afterUpdate`, etc.
|
|
42
|
+
- **Pluggable Logging**: Includes a robust `StabilizeLogger` with support for file-based, rotating logs.
|
|
43
|
+
- **Custom Errors**: `StabilizeError` provides clear, consistent error handling.
|
|
44
|
+
- **Caching Layer**: Optional Redis-backed caching with `cache-aside` and `write-through` strategies.
|
|
45
|
+
- **Custom Query Scopes**: Define reusable query conditions (scopes) in models for simplified, reusable filtering logic.
|
|
46
|
+
- **Timestamps**: Automatically manage `createdAt` and `updatedAt` columns for tracking record creation and update times.
|
|
47
|
+
- **SQL Default Expressions**: Support database-side default expressions (e.g., `gen_random_uuid()`, `NOW()`) for columns using the `sqlDefault()` helper.
|
|
48
|
+
- **Nested Relations (Eager Loading)**: Load deeply nested relations using dot notation like `"roles.permissions"`.
|
|
49
|
+
- **AutoMigrate with Index Management**: Automatically create, detect, and remove indexes and unique constraints during migration.
|
|
50
|
+
- **Advanced Query Builder Filters**: Chainable `.orWhere()`, `.whereIn()`, `.whereNotIn()`, `.whereNull()`, `.whereNotNull()`, `.whereBetween()`, `.groupBy()`, `.having()`, `.lock()` methods.
|
|
51
|
+
- **Optimistic Locking**: Add `optimisticLock: true` to a version column to automatically detect concurrent modification conflicts and throw `CONCURRENT_MODIFICATION` errors.
|
|
52
|
+
- **findAndCount**: Get paginated results with a total count in one call.
|
|
53
|
+
- **findOneBy / findBy**: TypeORM-style conditional finders without writing raw SQL.
|
|
54
|
+
- **Aggregate Queries**: Run `count()`, `sum()`, `avg()`, `min()`, `max()` directly from the repository or query builder.
|
|
55
|
+
- **Cursor-Based Pagination**: Efficient forward/backward cursor pagination for large datasets (Prisma-style `findMany`).
|
|
56
|
+
- **exists**: Check if a record exists without loading it.
|
|
57
|
+
- **recoverAll**: Bulk restore all soft-deleted records.
|
|
58
|
+
- **truncate**: Clear all rows from a table.
|
|
59
|
+
- **seed / defineSeed**: Laravel-style seeding framework with `defineSeed` and `runSeeds`.
|
|
60
|
+
- **resetDatabase**: Drop tables, re-migrate, and optionally re-seed for development.
|
|
61
|
+
- **healthCheck**: Get database and table health status with latency for monitoring endpoints.
|
|
62
|
+
- **rawQuery / rawExec**: Execute raw SQL directly from the `Stabilize` instance.
|
|
63
|
+
- **bulkUpsert**: Upsert multiple records in a single transaction.
|
|
64
|
+
- **findMany**: Prisma-style query with `where`, `cursor`, `take`, `skip`, `orderBy`.
|
|
65
|
+
- **countDistinct**: Count unique values in a column.
|
|
66
|
+
- **increment / decrement**: Atomically increment or decrement a numeric field.
|
|
67
|
+
- **pluck**: Get an array of a single column's values (Rails-style).
|
|
68
|
+
- **selectColumns**: Get only specific columns from a query.
|
|
69
|
+
- **toggle**: Toggle a boolean field (Rails-style).
|
|
70
|
+
- **updateBy / deleteBy**: Conditional bulk updates and deletes without writing SQL.
|
|
71
|
+
- **restoreBy**: Restore soft-deleted records matching conditions.
|
|
72
|
+
- **findDeleted / withTrashed**: Query soft-deleted or all records.
|
|
73
|
+
- **upsertMany**: Batch upsert in configurable batch sizes.
|
|
74
|
+
- **map / each / eachBatch**: Transform, iterate, and batch-process query results.
|
|
75
|
+
- **lockForUpdate**: Pessimistic row locking for read-modify-write.
|
|
76
|
+
- **firstOrCreate / updateOrCreate**: Laravel-style find-or-create patterns.
|
|
77
|
+
- **first / last / random**: Single-record shortcuts.
|
|
78
|
+
- **StabilizeEmitter**: Event system for `query`, `error`, `connection:open/close`, `transaction:start/complete/error`.
|
|
79
|
+
- **TransactionIsolationLevel**: Type for `READ UNCOMMITTED`, `READ COMMITTED`, `REPEATABLE READ`, `SERIALIZABLE`.
|
|
80
|
+
- **generateUUID**: Cross-runtime UUID generation helper.
|
|
81
|
+
- **poolStats**: Get connection pool statistics.
|
|
82
|
+
- **Database Backup & Restore**: `db:backup` and `db:restore` commands for database backup management.
|
|
83
|
+
- **API Generation**: `generate:api` command scaffolds full CRUD REST API routes from models.
|
|
84
|
+
- **Fresh Migrations**: `migrate:fresh` drops all tables and re-runs migrations without seeding.
|
|
85
|
+
- **Database Size Analysis**: `db:size` command shows table sizes and row count statistics.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 📦 Installation
|
|
90
|
+
|
|
91
|
+
Stabilize ORM requires a modern JavaScript runtime (Bun v1.3+).
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Using Bun
|
|
95
|
+
bun add stabilize-orm
|
|
96
|
+
|
|
97
|
+
# Using npm
|
|
98
|
+
npm install stabilize-orm
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 📃 Documentation & Community
|
|
104
|
+
|
|
105
|
+
- [Changelog](./CHANGELOG.md)
|
|
106
|
+
- [License](./LICENSE)
|
|
107
|
+
- [Code of Conduct](./CODE_OF_CONDUCT.md)
|
|
108
|
+
- [Contributing Guide](./CONTRIBUTING.md)
|
|
109
|
+
- [Security Policy](./SECURITY.md)
|
|
110
|
+
- [Support](./SUPPORT.md)
|
|
111
|
+
- [Funding](./FUNDING.md)
|
|
112
|
+
|
|
113
|
+
### Examples
|
|
114
|
+
|
|
115
|
+
- [Blog](./examples/blog.ts) - Blog with users, posts, comments, and versioning
|
|
116
|
+
- [E-Commerce](./examples/ecommerce.ts) - Products, categories, orders with transactions
|
|
117
|
+
- [REST API](./examples/rest-api.ts) - Express.js REST API with pagination and optimistic locking
|
|
118
|
+
- [SaaS](./examples/saas.ts) - Multi-tenant SaaS with tenants, members, and scoped projects
|
|
119
|
+
- [CMS](./examples/cms.ts) - Content management with authors, categories, articles, and versioning
|
|
120
|
+
- [Analytics](./examples/analytics.ts) - Event tracking with aggregations and metrics
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## ⚙️ Configuration
|
|
125
|
+
|
|
126
|
+
Create a database configuration file.
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// config/database.ts
|
|
130
|
+
import { DBType, type DBConfig } from "stabilize-orm";
|
|
131
|
+
|
|
132
|
+
const dbConfig: DBConfig = {
|
|
133
|
+
type: DBType.Postgres,
|
|
134
|
+
connectionString:
|
|
135
|
+
process.env.DATABASE_URL || "postgres://user:password@localhost:5432/mydb",
|
|
136
|
+
retryAttempts: 3,
|
|
137
|
+
retryDelay: 1000,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export default dbConfig;
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Next, create a central ORM instance for your application.
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// db.ts
|
|
147
|
+
import {
|
|
148
|
+
Stabilize,
|
|
149
|
+
type CacheConfig,
|
|
150
|
+
type LoggerConfig,
|
|
151
|
+
LogLevel,
|
|
152
|
+
} from "stabilize-orm";
|
|
153
|
+
import dbConfig from "./database";
|
|
154
|
+
|
|
155
|
+
const cacheConfig: CacheConfig = {
|
|
156
|
+
enabled: process.env.CACHE_ENABLED === "true",
|
|
157
|
+
redisUrl: process.env.REDIS_URL,
|
|
158
|
+
ttl: 60,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const loggerConfig: LoggerConfig = {
|
|
162
|
+
level: LogLevel.Info,
|
|
163
|
+
filePath: "logs/stabilize.log",
|
|
164
|
+
maxFileSize: 5 * 1024 * 1024, // 5MB
|
|
165
|
+
maxFiles: 3,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export const orm = new Stabilize(dbConfig, cacheConfig, loggerConfig);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## 🏗️ Models & Relationships
|
|
174
|
+
|
|
175
|
+
Define your tables as classes using the `defineModel` function. The `DataTypes` enum ensures database-agnostic schemas.
|
|
176
|
+
|
|
177
|
+
### Example: Users and Roles (Many-to-Many) with Versioning
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
// models/User.ts
|
|
181
|
+
import { defineModel, DataTypes, RelationType } from "stabilize-orm";
|
|
182
|
+
import { UserRole } from "./UserRole";
|
|
183
|
+
|
|
184
|
+
const User = defineModel({
|
|
185
|
+
tableName: "users",
|
|
186
|
+
versioned: true,
|
|
187
|
+
columns: {
|
|
188
|
+
id: { type: DataTypes.Integer, required: true },
|
|
189
|
+
email: {
|
|
190
|
+
type: DataTypes.String,
|
|
191
|
+
length: 100,
|
|
192
|
+
required: true,
|
|
193
|
+
unique: true,
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
relations: [
|
|
197
|
+
{
|
|
198
|
+
type: RelationType.OneToMany,
|
|
199
|
+
target: () => UserRole,
|
|
200
|
+
property: "roles",
|
|
201
|
+
foreignKey: "userId",
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
hooks: {
|
|
205
|
+
beforeCreate: (entity) => console.log(`Creating user: ${entity.email}`),
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
export { User };
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 🔍 Pagination
|
|
215
|
+
|
|
216
|
+
The built-in pagination helper makes it easy to retrieve a page of rows using LIMIT/OFFSET semantics.
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
const users = await userRepository.find().paginate(2, 10).execute(dbClient);
|
|
220
|
+
// users = [...] // Returns an array of rows for that page
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Or, use the query builder:
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
const page = await userRepository
|
|
227
|
+
.find()
|
|
228
|
+
.where("isActive = ?", true)
|
|
229
|
+
.paginate(1, 20)
|
|
230
|
+
.execute();
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## 🛡️ Advanced Validation
|
|
236
|
+
|
|
237
|
+
Models can define advanced validation rules for columns, including:
|
|
238
|
+
|
|
239
|
+
- `required`
|
|
240
|
+
- `minLength` / `maxLength`
|
|
241
|
+
- `pattern` (RegExp)
|
|
242
|
+
- `customValidator` (function)
|
|
243
|
+
|
|
244
|
+
Validation errors are thrown on create/update if data is invalid.
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
const User = defineModel({
|
|
248
|
+
tableName: "users",
|
|
249
|
+
columns: {
|
|
250
|
+
id: { type: DataTypes.Integer, required: true },
|
|
251
|
+
email: {
|
|
252
|
+
type: DataTypes.String,
|
|
253
|
+
required: true,
|
|
254
|
+
unique: true,
|
|
255
|
+
minLength: 6,
|
|
256
|
+
pattern: /^[^@]+@[^@]+\.[^@]+$/,
|
|
257
|
+
customValidator: (val) =>
|
|
258
|
+
val.endsWith("@offbytesecure.com") ||
|
|
259
|
+
"Must use an @offbytesecure.com email",
|
|
260
|
+
},
|
|
261
|
+
password: { type: DataTypes.String, minLength: 8 },
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## ⏳ Versioning & Auditing
|
|
269
|
+
|
|
270
|
+
Enable automatic history tracking and time-travel queries by setting `versioned: true` in your model configuration.
|
|
271
|
+
|
|
272
|
+
- Each change is recorded in a `<table>_history` table with version, operation, and audit columns.
|
|
273
|
+
- Supports snapshot queries, rollbacks, audits, and time-travel.
|
|
274
|
+
|
|
275
|
+
### **Versioning Example**
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
import { defineModel, DataTypes } from "stabilize-orm";
|
|
279
|
+
|
|
280
|
+
const User = defineModel({
|
|
281
|
+
tableName: "users",
|
|
282
|
+
versioned: true,
|
|
283
|
+
columns: {
|
|
284
|
+
id: { type: DataTypes.Integer, required: true },
|
|
285
|
+
name: { type: DataTypes.String, length: 100 },
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// --- Using versioning features:
|
|
290
|
+
|
|
291
|
+
const userRepository = orm.getRepository(User);
|
|
292
|
+
|
|
293
|
+
// Rollback to a previous version
|
|
294
|
+
await userRepository.rollback(1, 3); // roll back user with id=1 to version 3
|
|
295
|
+
|
|
296
|
+
// Get a snapshot as of a specific date
|
|
297
|
+
const userAsOf = await userRepository.asOf(1, new Date("2025-01-01T00:00:00Z"));
|
|
298
|
+
console.log(userAsOf);
|
|
299
|
+
|
|
300
|
+
// View full version history
|
|
301
|
+
const history = await userRepository.history(1);
|
|
302
|
+
console.log(history);
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## 🔄 Model Lifecycle Hooks
|
|
308
|
+
|
|
309
|
+
Stabilize ORM supports lifecycle hooks defined in the model configuration or as class methods. You can run logic before/after create, update, delete, or save.
|
|
310
|
+
|
|
311
|
+
### **Hooks Example**
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
import { defineModel, DataTypes } from "stabilize-orm";
|
|
315
|
+
|
|
316
|
+
const User = defineModel({
|
|
317
|
+
tableName: "users",
|
|
318
|
+
columns: {
|
|
319
|
+
id: { type: DataTypes.Integer, required: true },
|
|
320
|
+
name: { type: DataTypes.String, length: 100 },
|
|
321
|
+
createdAt: { type: DataTypes.DateTime },
|
|
322
|
+
updatedAt: { type: DataTypes.DateTime },
|
|
323
|
+
},
|
|
324
|
+
hooks: {
|
|
325
|
+
beforeCreate: (entity) => {
|
|
326
|
+
entity.createdAt = new Date();
|
|
327
|
+
},
|
|
328
|
+
beforeUpdate: (entity) => {
|
|
329
|
+
entity.updatedAt = new Date();
|
|
330
|
+
},
|
|
331
|
+
afterCreate: (entity) => {
|
|
332
|
+
console.log(`User created: ${entity.name}`);
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Add a hook as a class method
|
|
338
|
+
User.prototype.afterUpdate = async function () {
|
|
339
|
+
console.log(`Updated user: ${this.name}`);
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
export { User };
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Supported hooks: `beforeCreate`, `afterCreate`, `beforeUpdate`, `afterUpdate`, `beforeDelete`, `afterDelete`, `beforeSave`, `afterSave`.
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## 💻 Command-Line Interface (CLI)
|
|
350
|
+
|
|
351
|
+
Stabilize includes a powerful CLI for managing your workflow. See: [stabilize-cli on GitHub](https://github.com/ElectronSz/stabilize-cli)
|
|
352
|
+
|
|
353
|
+
### Generating Files
|
|
354
|
+
|
|
355
|
+
- **Generate a model**:
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
stabilize-cli generate:model Product
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
- **Generate a migration from a model**:
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
stabilize-cli generate:migration User
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
- **Generate a seed file**:
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
stabilize-cli generate:seed InitialRoles
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
- **Generate a REST API scaffold**:
|
|
374
|
+
```bash
|
|
375
|
+
stabilize-cli generate:api User
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Database & Migration Management
|
|
379
|
+
|
|
380
|
+
- **Run all pending migrations**:
|
|
381
|
+
|
|
382
|
+
```bash
|
|
383
|
+
stabilize-cli migrate
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
- **Roll back the last migration**:
|
|
387
|
+
|
|
388
|
+
```bash
|
|
389
|
+
stabilize-cli migrate:rollback
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
- **Fresh migration (drop + re-migrate)**:
|
|
393
|
+
|
|
394
|
+
```bash
|
|
395
|
+
stabilize-cli migrate:fresh --force
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
- **Run all pending seeds (in dependency order)**:
|
|
399
|
+
|
|
400
|
+
```bash
|
|
401
|
+
stabilize-cli seed
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
- **Check the status of migrations and seeds**:
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
stabilize-cli status
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
- **Reset the database (drop, migrate, seed)**:
|
|
411
|
+
```bash
|
|
412
|
+
stabilize-cli db:reset
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Backup & Restore
|
|
416
|
+
|
|
417
|
+
- **Backup the database**:
|
|
418
|
+
|
|
419
|
+
```bash
|
|
420
|
+
stabilize-cli db:backup
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
- **Restore from a backup**:
|
|
424
|
+
```bash
|
|
425
|
+
stabilize-cli db:restore backups/backup_20250101120000.db --force
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### Diagnostics
|
|
429
|
+
|
|
430
|
+
- **Database size statistics**:
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
stabilize-cli db:size
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
- **Health check**:
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
stabilize-cli health
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
- **CLI info**:
|
|
443
|
+
```bash
|
|
444
|
+
stabilize-cli info
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## 🧑💻 Querying Data
|
|
450
|
+
|
|
451
|
+
### Basic CRUD with Repositories
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
import { orm } from "./db";
|
|
455
|
+
import { User } from "./models/User";
|
|
456
|
+
|
|
457
|
+
const userRepository = orm.getRepository(User);
|
|
458
|
+
|
|
459
|
+
const newUser = await userRepository.create({ email: "lwazicd@icloud.com" });
|
|
460
|
+
const foundUser = await userRepository.findOne(newUser.id);
|
|
461
|
+
const updatedUser = await userRepository.update(newUser.id, {
|
|
462
|
+
email: "admin@offbytesecure.com",
|
|
463
|
+
});
|
|
464
|
+
await userRepository.delete(newUser.id);
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Advanced Queries with the Query Builder
|
|
468
|
+
|
|
469
|
+
```typescript
|
|
470
|
+
const activeAdmins = await orm
|
|
471
|
+
.getRepository(UserRole)
|
|
472
|
+
.find()
|
|
473
|
+
.join("users", "user_roles.user_id = users.id")
|
|
474
|
+
.join("roles", "user_roles.role_id = roles.id")
|
|
475
|
+
.select("users.id", "users.email", "roles.name as role_name")
|
|
476
|
+
.where("roles.name = ?", "Admin")
|
|
477
|
+
.orderBy("users.email ASC")
|
|
478
|
+
.execute();
|
|
479
|
+
|
|
480
|
+
console.log(activeAdmins);
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
#### Query Builder API
|
|
484
|
+
|
|
485
|
+
```typescript
|
|
486
|
+
{
|
|
487
|
+
select(...fields: string[]): QueryBuilder<User>;
|
|
488
|
+
where(condition: string, ...params: any[]): QueryBuilder<User>;
|
|
489
|
+
orWhere(condition: string, ...params: any[]): QueryBuilder<User>;
|
|
490
|
+
whereIn(column: string, values: any[]): QueryBuilder<User>;
|
|
491
|
+
whereNotIn(column: string, values: any[]): QueryBuilder<User>;
|
|
492
|
+
whereNull(column: string): QueryBuilder<User>;
|
|
493
|
+
whereNotNull(column: string): QueryBuilder<User>;
|
|
494
|
+
whereBetween(column: string, start: any, end: any): QueryBuilder<User>;
|
|
495
|
+
groupBy(clause: string): QueryBuilder<User>;
|
|
496
|
+
having(condition: string, ...params: any[]): QueryBuilder<User>;
|
|
497
|
+
join(table: string, condition: string): QueryBuilder<User>;
|
|
498
|
+
orderBy(clause: string): QueryBuilder<User>;
|
|
499
|
+
limit(limit: number): QueryBuilder<User>;
|
|
500
|
+
offset(offset: number): QueryBuilder<User>;
|
|
501
|
+
lock(mode?: "FOR UPDATE" | "FOR SHARE"): QueryBuilder<User>;
|
|
502
|
+
withRelations(...relations: string[]): QueryBuilder<User>;
|
|
503
|
+
scope(name: string, ...args: any[]): QueryBuilder<User>;
|
|
504
|
+
paginate(page: number, pageSize: number): QueryBuilder<User>;
|
|
505
|
+
build(): { query: string; params: any[] };
|
|
506
|
+
execute(client?: DBClient, cache?: Cache, cacheKey?: string): Promise<User[]>;
|
|
507
|
+
}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### Custom Query Scopes
|
|
511
|
+
|
|
512
|
+
Define reusable query conditions (scopes) in your model configuration to simplify and reuse common filtering logic. Scopes are applied via the `scope` method on `Repository` or `QueryBuilder`, allowing you to chain them with other query operations.
|
|
513
|
+
|
|
514
|
+
#### **Scopes Example**
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
import { defineModel, DataTypes } from "stabilize-orm";
|
|
518
|
+
import { orm } from "./db";
|
|
519
|
+
|
|
520
|
+
const User = defineModel({
|
|
521
|
+
tableName: "users",
|
|
522
|
+
columns: {
|
|
523
|
+
id: { type: DataTypes.Integer, required: true },
|
|
524
|
+
email: { type: DataTypes.String, length: 100, required: true },
|
|
525
|
+
isActive: { type: DataTypes.Boolean, required: true },
|
|
526
|
+
createdAt: { type: DataTypes.DateTime },
|
|
527
|
+
updatedAt: { type: DataTypes.DateTime },
|
|
528
|
+
},
|
|
529
|
+
scopes: {
|
|
530
|
+
active: (qb) => qb.where("isActive = ?", true),
|
|
531
|
+
recent: (qb, days: number) =>
|
|
532
|
+
qb.where(
|
|
533
|
+
"createdAt >= ?",
|
|
534
|
+
new Date(Date.now() - days * 24 * 60 * 60 * 1000),
|
|
535
|
+
),
|
|
536
|
+
},
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
const userRepository = orm.getRepository(User);
|
|
540
|
+
|
|
541
|
+
// Fetch active users
|
|
542
|
+
const activeUsers = await userRepository.scope("active").execute();
|
|
543
|
+
|
|
544
|
+
// Fetch users created in the last 7 days
|
|
545
|
+
const recentUsers = await userRepository.scope("recent", 7).execute();
|
|
546
|
+
|
|
547
|
+
// Combine scopes with other query operations
|
|
548
|
+
const recentActiveUsers = await userRepository
|
|
549
|
+
.scope("active")
|
|
550
|
+
.scope("recent", 7)
|
|
551
|
+
.orderBy("createdAt DESC")
|
|
552
|
+
.limit(10)
|
|
553
|
+
.execute();
|
|
554
|
+
|
|
555
|
+
console.log(recentActiveUsers);
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
### Timestamps
|
|
559
|
+
|
|
560
|
+
Enable automatic management of `createdAt` and `updatedAt` columns by setting `timestamps` in your model configuration. The ORM automatically sets these fields during `create`, `update`, `bulkCreate`, `bulkUpdate`, and `upsert` operations in a TypeScript-safe manner, eliminating the need for manual hooks.
|
|
561
|
+
|
|
562
|
+
#### **Timestamps Example**
|
|
563
|
+
|
|
564
|
+
```typescript
|
|
565
|
+
import { defineModel, DataTypes } from "stabilize-orm";
|
|
566
|
+
import { orm } from "./db";
|
|
567
|
+
|
|
568
|
+
const User = defineModel({
|
|
569
|
+
tableName: "users",
|
|
570
|
+
columns: {
|
|
571
|
+
id: { type: DataTypes.Integer, required: true },
|
|
572
|
+
email: { type: DataTypes.String, length: 100, required: true },
|
|
573
|
+
createdAt: { type: DataTypes.DateTime },
|
|
574
|
+
updatedAt: { type: DataTypes.DateTime },
|
|
575
|
+
},
|
|
576
|
+
timestamps: {
|
|
577
|
+
createdAt: "createdAt",
|
|
578
|
+
updatedAt: "updatedAt",
|
|
579
|
+
},
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
const userRepository = orm.getRepository(User);
|
|
583
|
+
|
|
584
|
+
// Create a user (createdAt and updatedAt set automatically)
|
|
585
|
+
const newUser = await userRepository.create({ email: "lwazicd@icloud.com" });
|
|
586
|
+
console.log(newUser.createdAt, newUser.updatedAt); // Outputs current timestamp
|
|
587
|
+
|
|
588
|
+
// Update a user (updatedAt updated automatically)
|
|
589
|
+
const updatedUser = await userRepository.update(newUser.id, {
|
|
590
|
+
email: "admin@offbytesecure.com",
|
|
591
|
+
});
|
|
592
|
+
console.log(updatedUser.updatedAt); // Outputs new timestamp
|
|
593
|
+
|
|
594
|
+
// Bulk create users
|
|
595
|
+
const newUsers = await userRepository.bulkCreate([
|
|
596
|
+
{ email: "user1@example.com" },
|
|
597
|
+
{ email: "user2@example.com" },
|
|
598
|
+
]);
|
|
599
|
+
console.log(newUsers.map((u) => u.createdAt)); // Outputs timestamps for each user
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
---
|
|
603
|
+
|
|
604
|
+
## 🗑️ Soft Deletes
|
|
605
|
+
|
|
606
|
+
Enable soft deletes by setting `softDelete: true` and marking a column (e.g., `deletedAt`) with `softDelete: true` in the model configuration.
|
|
607
|
+
|
|
608
|
+
- Use `repository.delete(id)` to mark an entity as deleted.
|
|
609
|
+
- Use `repository.recover(id)` to restore a soft-deleted entity.
|
|
610
|
+
- Queries automatically exclude soft-deleted rows unless specified otherwise.
|
|
611
|
+
|
|
612
|
+
### **Soft Delete Example**
|
|
613
|
+
|
|
614
|
+
```typescript
|
|
615
|
+
import { defineModel, DataTypes } from "stabilize-orm";
|
|
616
|
+
|
|
617
|
+
const User = defineModel({
|
|
618
|
+
tableName: "users",
|
|
619
|
+
softDelete: true,
|
|
620
|
+
columns: {
|
|
621
|
+
id: { type: DataTypes.Integer, required: true },
|
|
622
|
+
email: { type: DataTypes.String, length: 100, required: true },
|
|
623
|
+
deletedAt: { type: DataTypes.DateTime, softDelete: true },
|
|
624
|
+
},
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
const userRepository = orm.getRepository(User);
|
|
628
|
+
await userRepository.create({ email: "lwazicd@icloud.com" });
|
|
629
|
+
await userRepository.delete(1); // Soft delete
|
|
630
|
+
await userRepository.recover(1); // Recover
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
---
|
|
634
|
+
|
|
635
|
+
## 🌐 Express.js Integration
|
|
636
|
+
|
|
637
|
+
Stabilize ORM works seamlessly with web frameworks like Express.
|
|
638
|
+
|
|
639
|
+
```typescript
|
|
640
|
+
import express from "express";
|
|
641
|
+
import { orm } from "./db";
|
|
642
|
+
import { User } from "./models/User";
|
|
643
|
+
|
|
644
|
+
const app = express();
|
|
645
|
+
app.use(express.json());
|
|
646
|
+
|
|
647
|
+
const userRepository = orm.getRepository(User);
|
|
648
|
+
|
|
649
|
+
app.get("/users", async (req, res) => {
|
|
650
|
+
try {
|
|
651
|
+
const users = await userRepository.find().execute();
|
|
652
|
+
res.json(users);
|
|
653
|
+
} catch (err) {
|
|
654
|
+
res.status(500).json({ error: "Failed to fetch users." });
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
app.post("/users", async (req, res) => {
|
|
659
|
+
try {
|
|
660
|
+
const user = await userRepository.create(req.body);
|
|
661
|
+
res.status(201).json(user);
|
|
662
|
+
} catch (err) {
|
|
663
|
+
res.status(500).json({ error: "User creation failed." });
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
app.listen(3000, () => {
|
|
668
|
+
console.log("Server listening on port 3000");
|
|
669
|
+
});
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
---
|
|
673
|
+
|
|
674
|
+
## 🧑🔬 Testing & Time-Travel
|
|
675
|
+
|
|
676
|
+
- Use time-travel queries to inspect historical entity states.
|
|
677
|
+
- Assert audit trails and rollback operations in your tests.
|
|
678
|
+
|
|
679
|
+
---
|
|
680
|
+
|
|
681
|
+
## 🔒 Optimistic Locking
|
|
682
|
+
|
|
683
|
+
Enable optimistic locking to detect concurrent modifications. Add `optimisticLock: true` to a version column in your model.
|
|
684
|
+
|
|
685
|
+
```typescript
|
|
686
|
+
import { defineModel, DataTypes } from "stabilize-orm";
|
|
687
|
+
|
|
688
|
+
const User = defineModel({
|
|
689
|
+
tableName: "users",
|
|
690
|
+
columns: {
|
|
691
|
+
id: { type: DataTypes.Integer, required: true },
|
|
692
|
+
name: { type: DataTypes.String },
|
|
693
|
+
version: { type: DataTypes.Integer, optimisticLock: true },
|
|
694
|
+
},
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
const userRepository = orm.getRepository(User);
|
|
698
|
+
|
|
699
|
+
// Create with initial version
|
|
700
|
+
const user = await userRepository.create({ name: "Lwazi", version: 1 });
|
|
701
|
+
|
|
702
|
+
// Update - version is automatically incremented
|
|
703
|
+
// If another transaction modified the record, a CONCURRENT_MODIFICATION error is thrown
|
|
704
|
+
try {
|
|
705
|
+
await userRepository.update(user.id, {
|
|
706
|
+
name: "Updated",
|
|
707
|
+
version: user.version,
|
|
708
|
+
});
|
|
709
|
+
} catch (err) {
|
|
710
|
+
if (err.code === "CONCURRENT_MODIFICATION") {
|
|
711
|
+
console.log("Record was modified by another transaction");
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
---
|
|
717
|
+
|
|
718
|
+
## ⏱️ SQL Default Expressions
|
|
719
|
+
|
|
720
|
+
Use `sqlDefault()` to set database-side default values for columns (e.g., `gen_random_uuid()`, `NOW()`).
|
|
721
|
+
|
|
722
|
+
```typescript
|
|
723
|
+
import { defineModel, DataTypes, sqlDefault } from "stabilize-orm";
|
|
724
|
+
|
|
725
|
+
const User = defineModel({
|
|
726
|
+
tableName: "users",
|
|
727
|
+
columns: {
|
|
728
|
+
id: {
|
|
729
|
+
type: DataTypes.UUID,
|
|
730
|
+
required: true,
|
|
731
|
+
defaultExpression: sqlDefault("gen_random_uuid()"),
|
|
732
|
+
},
|
|
733
|
+
name: { type: DataTypes.String },
|
|
734
|
+
createdAt: {
|
|
735
|
+
type: DataTypes.DateTime,
|
|
736
|
+
defaultExpression: sqlDefault("NOW()"),
|
|
737
|
+
},
|
|
738
|
+
},
|
|
739
|
+
});
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
---
|
|
743
|
+
|
|
744
|
+
## 📊 Advanced Query Builder
|
|
745
|
+
|
|
746
|
+
The query builder now supports additional filter methods:
|
|
747
|
+
|
|
748
|
+
```typescript
|
|
749
|
+
const results = await userRepository
|
|
750
|
+
.find()
|
|
751
|
+
.where("status = ?", "active")
|
|
752
|
+
.orWhere("role = ?", "admin")
|
|
753
|
+
.whereIn("age", [25, 30, 35])
|
|
754
|
+
.whereBetween("createdAt", new Date("2025-01-01"), new Date("2025-12-31"))
|
|
755
|
+
.whereNull("deletedAt")
|
|
756
|
+
.groupBy("department")
|
|
757
|
+
.having("COUNT(*) > ?", 5)
|
|
758
|
+
.orderBy("createdAt DESC")
|
|
759
|
+
.limit(10)
|
|
760
|
+
.execute();
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
---
|
|
764
|
+
|
|
765
|
+
## 🔗 Nested Relations
|
|
766
|
+
|
|
767
|
+
Load deeply nested relations using dot notation:
|
|
768
|
+
|
|
769
|
+
```typescript
|
|
770
|
+
const user = await userRepository.findOne(1, {
|
|
771
|
+
relations: ["roles", "roles.permissions"],
|
|
772
|
+
});
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
---
|
|
776
|
+
|
|
777
|
+
## 📊 Aggregation Queries
|
|
778
|
+
|
|
779
|
+
Run aggregate queries directly on the repository or query builder.
|
|
780
|
+
|
|
781
|
+
```typescript
|
|
782
|
+
// Repository-level aggregates
|
|
783
|
+
const total = await userRepository.count();
|
|
784
|
+
const exists = await userRepository.exists({
|
|
785
|
+
email: "admin@offbytesecure.com",
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
const stats = await userRepository.aggregate({
|
|
789
|
+
count: "*",
|
|
790
|
+
sum: ["salary"],
|
|
791
|
+
avg: ["salary"],
|
|
792
|
+
min: ["salary"],
|
|
793
|
+
max: ["salary"],
|
|
794
|
+
});
|
|
795
|
+
// stats = { count_: 100, sum_salary: 5000000, avg_salary: 50000, min_salary: 20000, max_salary: 150000 }
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
---
|
|
799
|
+
|
|
800
|
+
## 🔎 findOneBy / findBy
|
|
801
|
+
|
|
802
|
+
TypeORM-style conditional finders without writing raw SQL.
|
|
803
|
+
|
|
804
|
+
```typescript
|
|
805
|
+
// Find one record by condition
|
|
806
|
+
const user = await userRepository.findOneBy({ email: "lwazicd@icloud.com" });
|
|
807
|
+
|
|
808
|
+
// Find multiple records
|
|
809
|
+
const admins = await userRepository.findBy(
|
|
810
|
+
{ role: "admin" },
|
|
811
|
+
{ limit: 10, orderBy: "createdAt DESC" },
|
|
812
|
+
);
|
|
813
|
+
|
|
814
|
+
// Combined with relations
|
|
815
|
+
const user = await userRepository.findOneBy(
|
|
816
|
+
{ email: "lwazicd@icloud.com" },
|
|
817
|
+
{ relations: ["roles"] },
|
|
818
|
+
);
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
---
|
|
822
|
+
|
|
823
|
+
## 🔢 findAndCount
|
|
824
|
+
|
|
825
|
+
Get paginated results with a total count in a single call.
|
|
826
|
+
|
|
827
|
+
```typescript
|
|
828
|
+
const { data, total } = await userRepository.findAndCount();
|
|
829
|
+
console.log(`Showing ${data.length} of ${total} total records`);
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
---
|
|
833
|
+
|
|
834
|
+
## 🖱️ Cursor-Based Pagination
|
|
835
|
+
|
|
836
|
+
Efficient cursor-based pagination for large datasets.
|
|
837
|
+
|
|
838
|
+
```typescript
|
|
839
|
+
// First page
|
|
840
|
+
const page1 = await userRepository.findMany({
|
|
841
|
+
take: 10,
|
|
842
|
+
orderBy: { field: "id", direction: "ASC" },
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
// Next page using cursor
|
|
846
|
+
const lastId = page1[page1.length - 1].id;
|
|
847
|
+
const page2 = await userRepository.findMany({
|
|
848
|
+
cursor: { field: "id", value: lastId, direction: "forward" },
|
|
849
|
+
take: 10,
|
|
850
|
+
orderBy: { field: "id", direction: "ASC" },
|
|
851
|
+
});
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
---
|
|
855
|
+
|
|
856
|
+
## 🌱 Database Seeding
|
|
857
|
+
|
|
858
|
+
Define and run seeds for populating development/test databases.
|
|
859
|
+
|
|
860
|
+
```typescript
|
|
861
|
+
import { defineSeed, runSeeds, resetDatabase } from "stabilize-orm";
|
|
862
|
+
|
|
863
|
+
// Define a seed
|
|
864
|
+
defineSeed("create-default-roles", async (db) => {
|
|
865
|
+
const roleRepo = orm.getRepository(Role);
|
|
866
|
+
await roleRepo.bulkCreate([
|
|
867
|
+
{ name: "Admin", permissions: "all" },
|
|
868
|
+
{ name: "User", permissions: "read" },
|
|
869
|
+
]);
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
// Run all seeds
|
|
873
|
+
await runSeeds(orm.client);
|
|
874
|
+
|
|
875
|
+
// Reset database (drop, migrate, seed)
|
|
876
|
+
await resetDatabase(orm.client, [User, Role]);
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
---
|
|
880
|
+
|
|
881
|
+
## 🏥 Health Check
|
|
882
|
+
|
|
883
|
+
Monitor database and cache connectivity with latency.
|
|
884
|
+
|
|
885
|
+
```typescript
|
|
886
|
+
const health = await orm.healthCheck();
|
|
887
|
+
// { status: "healthy", database: "postgres", latencyMs: 12.5, cacheStatus: "connected" }
|
|
888
|
+
|
|
889
|
+
// Per-table health
|
|
890
|
+
const userHealth = await userRepository.healthCheck();
|
|
891
|
+
// { status: "healthy", table: "users", rows: 142, latencyMs: 8.3 }
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
---
|
|
895
|
+
|
|
896
|
+
## 📂 Bulk Upsert
|
|
897
|
+
|
|
898
|
+
Upsert multiple records in a single transaction.
|
|
899
|
+
|
|
900
|
+
```typescript
|
|
901
|
+
const users = await userRepository.bulkUpsert(
|
|
902
|
+
[
|
|
903
|
+
{ email: "lwazicd@icloud.com", name: "Lwazi" },
|
|
904
|
+
{ email: "ciniso@icloud.com", name: "Ciniso" },
|
|
905
|
+
],
|
|
906
|
+
["email"], // unique key(s)
|
|
907
|
+
);
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
---
|
|
911
|
+
|
|
912
|
+
## 🔧 Raw SQL
|
|
913
|
+
|
|
914
|
+
Execute raw SQL queries directly from the ORM instance.
|
|
915
|
+
|
|
916
|
+
```typescript
|
|
917
|
+
const results = await orm.rawQuery("SELECT * FROM users WHERE age > ?", [25]);
|
|
918
|
+
const { affectedRows } = await orm.rawExec(
|
|
919
|
+
"UPDATE users SET active = false WHERE last_login < ?",
|
|
920
|
+
[oneYearAgo],
|
|
921
|
+
);
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
---
|
|
925
|
+
|
|
926
|
+
## 🗑️ recoverAll / truncate
|
|
927
|
+
|
|
928
|
+
```typescript
|
|
929
|
+
// Restore all soft-deleted records
|
|
930
|
+
const recovered = await userRepository.recoverAll();
|
|
931
|
+
console.log(`Recovered ${recovered} records`);
|
|
932
|
+
|
|
933
|
+
// Clear the table
|
|
934
|
+
await userRepository.truncate();
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
---
|
|
938
|
+
|
|
939
|
+
## ⬆️⬇️ Increment / Decrement
|
|
940
|
+
|
|
941
|
+
Atomically update numeric fields without loading the record.
|
|
942
|
+
|
|
943
|
+
```typescript
|
|
944
|
+
const updated = await userRepository.increment(user.id, "loginCount", 1);
|
|
945
|
+
const updated = await userRepository.decrement(user.id, "credits", 5);
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
---
|
|
949
|
+
|
|
950
|
+
## 🏷️ Pluck / SelectColumns
|
|
951
|
+
|
|
952
|
+
```typescript
|
|
953
|
+
// Get just the email column as an array
|
|
954
|
+
const emails = await userRepository.pluck("email");
|
|
955
|
+
// ["lwazicd@icloud.com", "ciniso@icloud.com", ...]
|
|
956
|
+
|
|
957
|
+
// Get specific columns
|
|
958
|
+
const users = await userRepository.selectColumns("id", "email");
|
|
959
|
+
// [{ id: 1, email: "lwazicd@icloud.com" }, ...]
|
|
960
|
+
```
|
|
961
|
+
|
|
962
|
+
---
|
|
963
|
+
|
|
964
|
+
## 🔄 Toggle
|
|
965
|
+
|
|
966
|
+
Toggle a boolean field.
|
|
967
|
+
|
|
968
|
+
```typescript
|
|
969
|
+
const toggled = await userRepository.toggle(user.id, "isActive");
|
|
970
|
+
// isActive was true, now false (or vice versa)
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
---
|
|
974
|
+
|
|
975
|
+
## ✏️ updateBy / deleteBy
|
|
976
|
+
|
|
977
|
+
```typescript
|
|
978
|
+
// Update all active users' role to "member"
|
|
979
|
+
const updated = await userRepository.updateBy(
|
|
980
|
+
{ isActive: true },
|
|
981
|
+
{ role: "member" },
|
|
982
|
+
);
|
|
983
|
+
|
|
984
|
+
// Delete all users with null email
|
|
985
|
+
const deleted = await userRepository.deleteBy({ email: null });
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
---
|
|
989
|
+
|
|
990
|
+
## 🕳️ findDeleted / withTrashed
|
|
991
|
+
|
|
992
|
+
```typescript
|
|
993
|
+
// Get only soft-deleted records
|
|
994
|
+
const deletedUsers = await userRepository.findDeleted().execute();
|
|
995
|
+
|
|
996
|
+
// Get all records including soft-deleted
|
|
997
|
+
const allUsers = await userRepository.withTrashed().execute();
|
|
998
|
+
|
|
999
|
+
// Restore matching soft-deleted records
|
|
1000
|
+
const restored = await userRepository.restoreBy({ role: "admin" });
|
|
1001
|
+
```
|
|
1002
|
+
|
|
1003
|
+
---
|
|
1004
|
+
|
|
1005
|
+
## 🔄 firstOrCreate / updateOrCreate
|
|
1006
|
+
|
|
1007
|
+
```typescript
|
|
1008
|
+
// Find or create in one call
|
|
1009
|
+
const user = await userRepository.firstOrCreate(
|
|
1010
|
+
{ email: "lwazicd@icloud.com" },
|
|
1011
|
+
{ name: "Lwazi" },
|
|
1012
|
+
);
|
|
1013
|
+
|
|
1014
|
+
// Find and update, or create if not found
|
|
1015
|
+
const user = await userRepository.updateOrCreate(
|
|
1016
|
+
{ email: "lwazicd@icloud.com" },
|
|
1017
|
+
{ name: "Updated Name" },
|
|
1018
|
+
);
|
|
1019
|
+
```
|
|
1020
|
+
|
|
1021
|
+
---
|
|
1022
|
+
|
|
1023
|
+
## 🥇 first / last / random
|
|
1024
|
+
|
|
1025
|
+
```typescript
|
|
1026
|
+
const firstUser = await userRepository.first();
|
|
1027
|
+
const admin = await userRepository.first({ role: "admin" });
|
|
1028
|
+
const lastUser = await userRepository.last();
|
|
1029
|
+
const randomUser = await userRepository.random();
|
|
1030
|
+
```
|
|
1031
|
+
|
|
1032
|
+
---
|
|
1033
|
+
|
|
1034
|
+
## 🔒 Pessimistic Locking (lockForUpdate)
|
|
1035
|
+
|
|
1036
|
+
```typescript
|
|
1037
|
+
const user = await userRepository.lockForUpdate(user.id);
|
|
1038
|
+
// The row is now locked for the duration of the transaction
|
|
1039
|
+
```
|
|
1040
|
+
|
|
1041
|
+
---
|
|
1042
|
+
|
|
1043
|
+
## 📡 Event Emitter
|
|
1044
|
+
|
|
1045
|
+
Subscribe to ORM lifecycle events.
|
|
1046
|
+
|
|
1047
|
+
```typescript
|
|
1048
|
+
const orm = new Stabilize(dbConfig);
|
|
1049
|
+
|
|
1050
|
+
orm.events.on("query", (entry) => {
|
|
1051
|
+
console.log(`[${entry.durationMs}ms] ${entry.query}`);
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
orm.events.on("error", (err) => {
|
|
1055
|
+
console.error("ORM error:", err);
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
orm.events.on("connection:open", (dbType) => {
|
|
1059
|
+
console.log(`Connected to ${dbType}`);
|
|
1060
|
+
});
|
|
1061
|
+
```
|
|
1062
|
+
|
|
1063
|
+
---
|
|
1064
|
+
|
|
1065
|
+
## 📦 Pool Stats
|
|
1066
|
+
|
|
1067
|
+
```typescript
|
|
1068
|
+
const stats = await orm.poolStats();
|
|
1069
|
+
// { active: 5, idle: 10, total: 15 }
|
|
1070
|
+
```
|
|
1071
|
+
|
|
1072
|
+
---
|
|
1073
|
+
|
|
1074
|
+
## 🆔 generateUUID
|
|
1075
|
+
|
|
1076
|
+
```typescript
|
|
1077
|
+
import { generateUUID } from "stabilize-orm";
|
|
1078
|
+
|
|
1079
|
+
const id = generateUUID();
|
|
1080
|
+
// "550e8400-e29b-41d4-a716-446655440000"
|
|
1081
|
+
```
|
|
1082
|
+
|
|
1083
|
+
---
|
|
1084
|
+
|
|
1085
|
+
## 📑 License
|
|
1086
|
+
|
|
1087
|
+
Licensed under the MIT License. See [LICENSE.md](./LICENSE.md) for details.
|
|
1088
|
+
|
|
1089
|
+
---
|
|
1090
|
+
|
|
1091
|
+
<div align="center">
|
|
1092
|
+
|
|
1093
|
+
Created with ❤️ by **ElectronSz**
|
|
1094
|
+
<br/>
|
|
1095
|
+
<em>File last updated: 2026-04-02</em>
|
|
1096
|
+
|
|
1097
|
+
</div>
|