@arikajs/database 0.0.4 → 0.0.6
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 +392 -25
- package/dist/Connections/MongoDBConnection.d.ts +81 -0
- package/dist/Connections/MongoDBConnection.d.ts.map +1 -0
- package/dist/Connections/MongoDBConnection.js +203 -0
- package/dist/Connections/MongoDBConnection.js.map +1 -0
- package/dist/Connections/MySQLConnection.d.ts +1 -0
- package/dist/Connections/MySQLConnection.d.ts.map +1 -1
- package/dist/Connections/MySQLConnection.js +47 -10
- package/dist/Connections/MySQLConnection.js.map +1 -1
- package/dist/Connections/PostgreSQLConnection.d.ts +1 -0
- package/dist/Connections/PostgreSQLConnection.d.ts.map +1 -1
- package/dist/Connections/PostgreSQLConnection.js +43 -9
- package/dist/Connections/PostgreSQLConnection.js.map +1 -1
- package/dist/Connections/SQLiteConnection.d.ts +1 -0
- package/dist/Connections/SQLiteConnection.d.ts.map +1 -1
- package/dist/Connections/SQLiteConnection.js +38 -7
- package/dist/Connections/SQLiteConnection.js.map +1 -1
- package/dist/Contracts/Database.d.ts +71 -4
- package/dist/Contracts/Database.d.ts.map +1 -1
- package/dist/Contracts/Schema.d.ts +4 -0
- package/dist/Contracts/Schema.d.ts.map +1 -1
- package/dist/Database.d.ts +30 -3
- package/dist/Database.d.ts.map +1 -1
- package/dist/Database.js +39 -1
- package/dist/Database.js.map +1 -1
- package/dist/DatabaseManager.d.ts +17 -3
- package/dist/DatabaseManager.d.ts.map +1 -1
- package/dist/DatabaseManager.js +27 -11
- package/dist/DatabaseManager.js.map +1 -1
- package/dist/Migrations/Migrator.d.ts.map +1 -1
- package/dist/Migrations/Migrator.js +35 -3
- package/dist/Migrations/Migrator.js.map +1 -1
- package/dist/Model/GlobalScope.d.ts +44 -0
- package/dist/Model/GlobalScope.d.ts.map +1 -0
- package/dist/Model/GlobalScope.js +64 -0
- package/dist/Model/GlobalScope.js.map +1 -0
- package/dist/Model/Model.d.ts +168 -4
- package/dist/Model/Model.d.ts.map +1 -1
- package/dist/Model/Model.js +476 -16
- package/dist/Model/Model.js.map +1 -1
- package/dist/Model/Observer.d.ts +39 -0
- package/dist/Model/Observer.d.ts.map +1 -0
- package/dist/Model/Observer.js +48 -0
- package/dist/Model/Observer.js.map +1 -0
- package/dist/Model/Relations.d.ts +201 -10
- package/dist/Model/Relations.d.ts.map +1 -1
- package/dist/Model/Relations.js +472 -27
- package/dist/Model/Relations.js.map +1 -1
- package/dist/Query/Expression.d.ts +16 -0
- package/dist/Query/Expression.d.ts.map +1 -0
- package/dist/Query/Expression.js +25 -0
- package/dist/Query/Expression.js.map +1 -0
- package/dist/Query/QueryBuilder.d.ts +64 -6
- package/dist/Query/QueryBuilder.d.ts.map +1 -1
- package/dist/Query/QueryBuilder.js +234 -15
- package/dist/Query/QueryBuilder.js.map +1 -1
- package/dist/Query/QueryLogger.d.ts +55 -0
- package/dist/Query/QueryLogger.d.ts.map +1 -0
- package/dist/Query/QueryLogger.js +82 -0
- package/dist/Query/QueryLogger.js.map +1 -0
- package/dist/Schema/Grammars/Grammar.d.ts +5 -0
- package/dist/Schema/Grammars/Grammar.d.ts.map +1 -1
- package/dist/Schema/Grammars/Grammar.js.map +1 -1
- package/dist/Schema/Grammars/MySQLGrammar.d.ts +1 -0
- package/dist/Schema/Grammars/MySQLGrammar.d.ts.map +1 -1
- package/dist/Schema/Grammars/MySQLGrammar.js +42 -0
- package/dist/Schema/Grammars/MySQLGrammar.js.map +1 -1
- package/dist/Schema/Grammars/PostgreSQLGrammar.d.ts +1 -0
- package/dist/Schema/Grammars/PostgreSQLGrammar.d.ts.map +1 -1
- package/dist/Schema/Grammars/PostgreSQLGrammar.js +46 -0
- package/dist/Schema/Grammars/PostgreSQLGrammar.js.map +1 -1
- package/dist/Schema/Grammars/SQLiteGrammar.d.ts +1 -0
- package/dist/Schema/Grammars/SQLiteGrammar.d.ts.map +1 -1
- package/dist/Schema/Grammars/SQLiteGrammar.js +31 -0
- package/dist/Schema/Grammars/SQLiteGrammar.js.map +1 -1
- package/dist/Schema/Schema.d.ts +6 -0
- package/dist/Schema/Schema.d.ts.map +1 -1
- package/dist/Schema/Schema.js +10 -0
- package/dist/Schema/Schema.js.map +1 -1
- package/dist/Schema/SchemaBuilder.d.ts +4 -0
- package/dist/Schema/SchemaBuilder.d.ts.map +1 -1
- package/dist/Schema/SchemaBuilder.js +15 -0
- package/dist/Schema/SchemaBuilder.js.map +1 -1
- package/dist/Transactions/TransactionManager.d.ts +28 -0
- package/dist/Transactions/TransactionManager.d.ts.map +1 -0
- package/dist/Transactions/TransactionManager.js +68 -0
- package/dist/Transactions/TransactionManager.js.map +1 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -1
- package/dist/index.js.map +1 -1
- package/package.json +10 -6
package/README.md
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
# @arikajs/database
|
|
2
2
|
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
5
|
+
<img src="../cli/templates/app/public/assets/img/logo.png" alt="ArikaJS Logo" width="400">
|
|
6
|
+
|
|
7
|
+
</div>
|
|
8
|
+
|
|
3
9
|
**@arikajs/database** is the official database layer for the ArikaJS framework.
|
|
4
|
-
It provides a powerful, extensible, and framework-integrated database system
|
|
10
|
+
It provides a powerful, extensible, and framework-integrated database system with an elegant, fluent API — but designed natively for **Node.js & TypeScript**.
|
|
5
11
|
|
|
6
12
|
This package powers **DB facade**, **Models**, **Migrations**, and **Query Builder** across all ArikaJS applications.
|
|
7
13
|
|
|
@@ -11,7 +17,7 @@ This package powers **DB facade**, **Models**, **Migrations**, and **Query Build
|
|
|
11
17
|
|
|
12
18
|
### ✅ Available (v0.x)
|
|
13
19
|
- ✅ Multiple database connections
|
|
14
|
-
- ✅ MySQL, PostgreSQL &
|
|
20
|
+
- ✅ MySQL, PostgreSQL, SQLite, & MongoDB support
|
|
15
21
|
- ✅ Fluent Query Builder
|
|
16
22
|
- ✅ Static Model querying
|
|
17
23
|
- ✅ Instance-style Models (Active Record)
|
|
@@ -25,6 +31,15 @@ This package powers **DB facade**, **Models**, **Migrations**, and **Query Build
|
|
|
25
31
|
- ✅ Framework-integrated configuration
|
|
26
32
|
- ✅ TypeScript-first design
|
|
27
33
|
- ✅ Works with native ArikaJS DI container
|
|
34
|
+
- ✅ Query result caching
|
|
35
|
+
- ✅ Query Builder & Model Pagination
|
|
36
|
+
- ✅ Attribute Casting & Magic Accessors/Mutators
|
|
37
|
+
- ✅ Bulk Operations (Bulk Insert)
|
|
38
|
+
- ✅ Connection Pooling & Read/Write Splitting
|
|
39
|
+
- ✅ Transactions (with nested savepoints)
|
|
40
|
+
- ✅ Model Observers (lifecycle hooks)
|
|
41
|
+
- ✅ Query Logging & Debug Mode
|
|
42
|
+
- ✅ Global Scopes
|
|
28
43
|
|
|
29
44
|
---
|
|
30
45
|
|
|
@@ -35,6 +50,7 @@ This package powers **DB facade**, **Models**, **Migrations**, and **Query Build
|
|
|
35
50
|
| MySQL | ✅ Supported |
|
|
36
51
|
| PostgreSQL | ✅ Supported |
|
|
37
52
|
| SQLite | ✅ Supported |
|
|
53
|
+
| MongoDB | ✅ Supported |
|
|
38
54
|
|
|
39
55
|
---
|
|
40
56
|
|
|
@@ -79,6 +95,15 @@ export default {
|
|
|
79
95
|
driver: 'sqlite',
|
|
80
96
|
database: env('DB_DATABASE', 'database.sqlite'),
|
|
81
97
|
},
|
|
98
|
+
|
|
99
|
+
mongodb: {
|
|
100
|
+
driver: 'mongodb',
|
|
101
|
+
host: env('DB_HOST', '127.0.0.1'),
|
|
102
|
+
port: env('DB_PORT', 27017),
|
|
103
|
+
database: env('DB_DATABASE', 'arikajs'),
|
|
104
|
+
username: env('DB_USERNAME', ''),
|
|
105
|
+
password: env('DB_PASSWORD', ''),
|
|
106
|
+
},
|
|
82
107
|
},
|
|
83
108
|
};
|
|
84
109
|
```
|
|
@@ -139,6 +164,34 @@ console.log(user.isDirty()); // true
|
|
|
139
164
|
console.log(user.getDirty()); // { name: 'New Name' }
|
|
140
165
|
```
|
|
141
166
|
|
|
167
|
+
### 🪄 Attribute Casting & Magic Accessors/Mutators
|
|
168
|
+
ArikaJS Model system supports defining exact types your database inputs should cast to. Using a Proxy behind the scenes, you can retrieve any attribute implicitly and add overrides (mutators/accessors).
|
|
169
|
+
```typescript
|
|
170
|
+
class User extends Model {
|
|
171
|
+
// Instruct ArikaJS to cast these properties when saving/retrieving
|
|
172
|
+
protected casts = {
|
|
173
|
+
is_active: 'boolean',
|
|
174
|
+
metadata: 'json',
|
|
175
|
+
last_login: 'datetime'
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// Mutator: Set Password Hash
|
|
179
|
+
setPasswordAttribute(value: string) {
|
|
180
|
+
this.attributes['password'] = hash(value);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Accessor: Get Full Name
|
|
184
|
+
getFullNameAttribute() {
|
|
185
|
+
return `${this.first_name} ${this.last_name}`;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const user = new User();
|
|
190
|
+
user.is_active = true; // Implicit proxy saving!
|
|
191
|
+
user.password = 'my_secure_pass'; // Triggers setPasswordAttribute!
|
|
192
|
+
console.log(user.full_name); // Magic accessor triggered!
|
|
193
|
+
```
|
|
194
|
+
|
|
142
195
|
### 🔗 Relationships
|
|
143
196
|
|
|
144
197
|
```typescript
|
|
@@ -232,35 +285,82 @@ await User.restore(1);
|
|
|
232
285
|
await User.forceDelete(1);
|
|
233
286
|
```
|
|
234
287
|
|
|
235
|
-
### 🧱
|
|
288
|
+
### 🧱 Schema Builder & Migrations
|
|
236
289
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
290
|
+
ArikaJS offers a fluent schema builder for creating and altering tables.
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
import { Schema } from '@arikajs/database';
|
|
294
|
+
|
|
295
|
+
// 1. Create a table
|
|
296
|
+
await Schema.create('users', (table) => {
|
|
297
|
+
table.id();
|
|
298
|
+
table.string('name');
|
|
299
|
+
table.string('email').unique();
|
|
300
|
+
table.boolean('is_active').default(true);
|
|
301
|
+
table.timestamps();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// 2. Alter an existing table
|
|
305
|
+
await Schema.table('users', (table) => {
|
|
306
|
+
table.string('phone_number').nullable(); // Adds phone_number
|
|
307
|
+
table.dropColumn('is_active'); // Drops a column
|
|
308
|
+
table.index(['name', 'email']); // Adds a compound index
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// 3. Drop tables
|
|
312
|
+
await Schema.dropIfExists('users');
|
|
240
313
|
```
|
|
241
314
|
|
|
315
|
+
(Note: A CLI tool to automatically generate and run these migrations is planned: `arika make:migration create_users_table`).
|
|
316
|
+
|
|
242
317
|
---
|
|
243
318
|
|
|
244
|
-
##
|
|
319
|
+
## 🏗 Architecture
|
|
245
320
|
|
|
246
|
-
```
|
|
321
|
+
```text
|
|
247
322
|
database/
|
|
248
323
|
├── src/
|
|
249
|
-
│ ├── Connections
|
|
250
|
-
│ │ ├──
|
|
251
|
-
│ │ ├──
|
|
252
|
-
│ │
|
|
253
|
-
│
|
|
254
|
-
│
|
|
255
|
-
│ │ ├──
|
|
256
|
-
│ │ └──
|
|
257
|
-
│ ├──
|
|
324
|
+
│ ├── Connections
|
|
325
|
+
│ │ ├── MongoDBConnection.ts
|
|
326
|
+
│ │ ├── MySQLConnection.ts
|
|
327
|
+
│ │ ├── PostgreSQLConnection.ts
|
|
328
|
+
│ │ └── SQLiteConnection.ts
|
|
329
|
+
│ ├── Contracts
|
|
330
|
+
│ │ ├── Database.ts
|
|
331
|
+
│ │ └── Schema.ts
|
|
332
|
+
│ ├── Migrations
|
|
333
|
+
│ │ ├── Migration.ts
|
|
334
|
+
│ │ └── Migrator.ts
|
|
335
|
+
│ ├── Model
|
|
336
|
+
│ │ ├── GlobalScope.ts
|
|
337
|
+
│ │ ├── Model.ts
|
|
338
|
+
│ │ ├── Observer.ts
|
|
339
|
+
│ │ ├── Relations.ts
|
|
340
|
+
│ │ └── SoftDeletes.ts
|
|
341
|
+
│ ├── Query
|
|
342
|
+
│ │ ├── Expression.ts
|
|
343
|
+
│ │ ├── QueryBuilder.ts
|
|
344
|
+
│ │ └── QueryLogger.ts
|
|
258
345
|
│ ├── Schema
|
|
259
|
-
│ ├──
|
|
260
|
-
│
|
|
261
|
-
│
|
|
262
|
-
├──
|
|
263
|
-
│ └──
|
|
346
|
+
│ │ ├── Grammars
|
|
347
|
+
│ │ │ ├── Grammar.ts
|
|
348
|
+
│ │ │ ├── MySQLGrammar.ts
|
|
349
|
+
│ │ │ ├── PostgreSQLGrammar.ts
|
|
350
|
+
│ │ │ └── SQLiteGrammar.ts
|
|
351
|
+
│ │ ├── Schema.ts
|
|
352
|
+
│ │ └── SchemaBuilder.ts
|
|
353
|
+
│ ├── Seeders
|
|
354
|
+
│ │ ├── Seeder.ts
|
|
355
|
+
│ │ └── SeedRunner.ts
|
|
356
|
+
│ ├── Transactions
|
|
357
|
+
│ │ └── TransactionManager.ts
|
|
358
|
+
│ ├── Database.ts
|
|
359
|
+
│ ├── DatabaseManager.ts
|
|
360
|
+
│ └── index.ts
|
|
361
|
+
├── tests/
|
|
362
|
+
├── package.json
|
|
363
|
+
├── tsconfig.json
|
|
264
364
|
└── README.md
|
|
265
365
|
```
|
|
266
366
|
|
|
@@ -285,14 +385,281 @@ class User extends Model {
|
|
|
285
385
|
}
|
|
286
386
|
```
|
|
287
387
|
|
|
288
|
-
### ⚡ Query Result Caching
|
|
388
|
+
### ⚡ Query Result Caching (Inline Caching)
|
|
389
|
+
Caching is natively supported inline using the `.cache()` builder method. It integrates directly with `@arikajs/cache` when registered inside the framework.
|
|
289
390
|
```typescript
|
|
290
391
|
const users = await User.where('active', true)
|
|
291
392
|
.cache(3600) // Cache for 1 hour
|
|
292
393
|
.get();
|
|
293
394
|
```
|
|
294
395
|
|
|
295
|
-
###
|
|
396
|
+
### 📄 Pagination
|
|
397
|
+
Quickly retrieve paginated models using `.paginate()`. It directly queries out metadata alongside the specific models.
|
|
398
|
+
```typescript
|
|
399
|
+
const { data, meta } = await User.where('status', 'active').paginate(1, 15);
|
|
400
|
+
|
|
401
|
+
// Output:
|
|
402
|
+
// {
|
|
403
|
+
// data: [...], // Array of 15 User model instances
|
|
404
|
+
// meta: {
|
|
405
|
+
// total: 50,
|
|
406
|
+
// per_page: 15,
|
|
407
|
+
// current_page: 1,
|
|
408
|
+
// last_page: 4,
|
|
409
|
+
// first_page: 1
|
|
410
|
+
// }
|
|
411
|
+
// }
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### 🏁 Bulk Operations
|
|
415
|
+
Executing a bulk operation gives you massive optimization boosts across a large SaaS platform. ArikaJS simplifies doing arrays of records sequentially.
|
|
416
|
+
```typescript
|
|
417
|
+
await User.insert([
|
|
418
|
+
{ first_name: 'John', last_name: 'Doe' },
|
|
419
|
+
{ first_name: 'Jane', last_name: 'Smith' },
|
|
420
|
+
{ first_name: 'William', last_name: 'James' }
|
|
421
|
+
]);
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### 🔀 Connection Pooling & Read/Write Splitting
|
|
425
|
+
Scale-out your database layer without touching any model or query code. ArikaJS automatically routes `SELECT` queries to the read replica pool and `INSERT/UPDATE/DELETE` to the write master.
|
|
426
|
+
```typescript
|
|
427
|
+
// config/database.ts
|
|
428
|
+
{
|
|
429
|
+
driver: 'mysql',
|
|
430
|
+
database: 'myapp',
|
|
431
|
+
// One write master
|
|
432
|
+
write: { host: 'db-master.internal', username: 'root', password: 'secret' },
|
|
433
|
+
// Multiple read replicas — one is chosen at random per query
|
|
434
|
+
read: [
|
|
435
|
+
{ host: 'db-replica-1.internal' },
|
|
436
|
+
{ host: 'db-replica-2.internal' },
|
|
437
|
+
],
|
|
438
|
+
pool: { min: 2, max: 20 }
|
|
439
|
+
}
|
|
440
|
+
// No code changes needed — ArikaJS routes automatically!
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### 💼 Transactions
|
|
444
|
+
Use `Database.transaction()` for clean, auto-rollback-on-error transactions. Nested calls automatically use SQL **savepoints**.
|
|
445
|
+
```typescript
|
|
446
|
+
await Database.transaction(async (trx) => {
|
|
447
|
+
await DB.table('accounts').where('id', fromId).update({ balance: DB.raw('balance - 500') });
|
|
448
|
+
await DB.table('accounts').where('id', toId).update({ balance: DB.raw('balance + 500') });
|
|
449
|
+
await DB.table('transfers').insert({ from_id: fromId, to_id: toId, amount: 500 });
|
|
450
|
+
});
|
|
451
|
+
// If any statement throws, the entire block is rolled back automatically!
|
|
452
|
+
|
|
453
|
+
// Manual control:
|
|
454
|
+
const txm = await Database.getTransactionManager();
|
|
455
|
+
await txm.begin();
|
|
456
|
+
try {
|
|
457
|
+
await txm.commit();
|
|
458
|
+
} catch {
|
|
459
|
+
await txm.rollback();
|
|
460
|
+
}
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### 👁️ Model Observers
|
|
464
|
+
Keep your models lean. Attach lifecycle listeners externally without polluting the model with inline logic.
|
|
465
|
+
```typescript
|
|
466
|
+
class UserObserver {
|
|
467
|
+
async creating(user: User) {
|
|
468
|
+
user.role = user.role ?? 'member';
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async created(user: User) {
|
|
472
|
+
await EmailService.sendWelcomeEmail(user.email);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Return false to CANCEL the operation
|
|
476
|
+
async deleting(user: User) {
|
|
477
|
+
if (user.role === 'admin') return false; // Block admin deletions!
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async deleted(user: User) {
|
|
481
|
+
await AuditLog.record(`User ${user.id} was deleted`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Register once in a ServiceProvider or boot file:
|
|
486
|
+
User.observe(new UserObserver());
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### 🔍 Query Logging & Debug Mode
|
|
490
|
+
Introspect every SQL query fired — perfect for debugging slow endpoints or verifying your query plans.
|
|
491
|
+
```typescript
|
|
492
|
+
// Enable logging:
|
|
493
|
+
Database.enableQueryLog();
|
|
494
|
+
|
|
495
|
+
await User.where('status', 'active').get();
|
|
496
|
+
await Post.where('user_id', 1).limit(5).get();
|
|
497
|
+
|
|
498
|
+
// Inspect queries:
|
|
499
|
+
const logs = Database.getQueryLog();
|
|
500
|
+
console.log(logs);
|
|
501
|
+
// [
|
|
502
|
+
// { sql: 'SELECT * FROM users WHERE status = ?', bindings: ['active'], time: 1.2, timestamp: Date },
|
|
503
|
+
// { sql: 'SELECT * FROM posts WHERE user_id = ? LIMIT ?', bindings: [1, 5], time: 0.8, timestamp: Date },
|
|
504
|
+
// ]
|
|
505
|
+
|
|
506
|
+
// Or listen live:
|
|
507
|
+
QueryLogger.listen((log) => {
|
|
508
|
+
if (log.time > 100) console.warn(`[SLOW QUERY] ${log.sql} (${log.time}ms)`);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
Database.flushQueryLog(); // Clear when done
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### 🌐 Global Scopes
|
|
515
|
+
Automatic query constraints applied to every query without the caller needing to remember them — ideal for multi-tenancy and similar patterns.
|
|
516
|
+
```typescript
|
|
517
|
+
// Register a scope at boot time:
|
|
518
|
+
User.addGlobalScope('active_only', (builder) => {
|
|
519
|
+
builder.where('status', 'active');
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
// From now on ALL User queries are scoped:
|
|
523
|
+
await User.all(); // SELECT * FROM users WHERE status = 'active'
|
|
524
|
+
await User.where('role', 'admin').get(); // Adds status constraint automatically
|
|
525
|
+
|
|
526
|
+
// Escape the scope when needed:
|
|
527
|
+
await User.withoutGlobalScopes().all(); // SELECT * FROM users (no scope)
|
|
528
|
+
|
|
529
|
+
// Or use a full class:
|
|
530
|
+
class TenantScope implements GlobalScope {
|
|
531
|
+
constructor(private tenantId: number) {}
|
|
532
|
+
apply(builder: any) {
|
|
533
|
+
builder.where('tenant_id', this.tenantId);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
Post.addGlobalScope('tenant', new TenantScope(currentTenantId));
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### 🔗 Advanced Relationships
|
|
540
|
+
|
|
541
|
+
ArikaJS supports advanced relationship types, bringing you power usually found only in heavy, complex ORMs.
|
|
542
|
+
|
|
543
|
+
#### Through Relationships
|
|
544
|
+
Relate models through an intermediate model.
|
|
545
|
+
```typescript
|
|
546
|
+
class Country extends Model {
|
|
547
|
+
// Country has many Posts, through Users
|
|
548
|
+
posts() {
|
|
549
|
+
return this.hasManyThrough(Post, User, 'country_id', 'user_id');
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
// Get all posts for users in a specific country
|
|
553
|
+
const posts = await country.posts().get();
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
#### Polymorphic Relationships
|
|
557
|
+
A model can belong to more than one other model on a single association.
|
|
558
|
+
```typescript
|
|
559
|
+
class Image extends Model {
|
|
560
|
+
imageable() {
|
|
561
|
+
return this.morphTo('imageable');
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
class Post extends Model {
|
|
566
|
+
image() {
|
|
567
|
+
return this.morphOne(Image, 'imageable');
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
class User extends Model {
|
|
572
|
+
image() {
|
|
573
|
+
return this.morphOne(Image, 'imageable');
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Inserting polymorphic models
|
|
578
|
+
const post = await Post.find(1);
|
|
579
|
+
await post.image().create({ url: '/foo.png' }); // sets imageable_id=1, imageable_type='Post'
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
#### Pivot Table Operations (Many-to-Many)
|
|
583
|
+
ArikaJS provides robust helpers for `BelongsToMany` pivot tables:
|
|
584
|
+
```typescript
|
|
585
|
+
// 1. Fetching extra pivot columns:
|
|
586
|
+
class User extends Model {
|
|
587
|
+
roles() {
|
|
588
|
+
return this.belongsToMany(Role).withPivot('expires_at', 'assigned_by');
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
const user = await User.find(1);
|
|
592
|
+
const firstRole = (await user.roles().get())[0];
|
|
593
|
+
console.log(firstRole.pivot.expires_at);
|
|
594
|
+
|
|
595
|
+
// 2. Modifying the relationship (Attach/Detach/Sync/Toggle)
|
|
596
|
+
const roleId = 5;
|
|
597
|
+
|
|
598
|
+
// Simply add the link:
|
|
599
|
+
await user.roles().attach(roleId, { assigned_by: 'admin' });
|
|
600
|
+
|
|
601
|
+
// Remove the link:
|
|
602
|
+
await user.roles().detach(roleId);
|
|
603
|
+
|
|
604
|
+
// Sync: Only keep these exact IDs and remove the rest
|
|
605
|
+
await user.roles().sync([1, 2, 3]);
|
|
606
|
+
|
|
607
|
+
// Toggle: Add if missing, remove if present
|
|
608
|
+
await user.roles().toggle([1, 4]);
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
#### Relationship Counting
|
|
612
|
+
You can dynamically count the number of related objects without loading them simply by using `withCount()`. ArikaJS uses optimized subqueries.
|
|
613
|
+
```typescript
|
|
614
|
+
const posts = await Post.withCount('comments').all();
|
|
615
|
+
console.log(posts[0].comments_count); // e.g: 4
|
|
616
|
+
|
|
617
|
+
// Count multiple relationships at once
|
|
618
|
+
const users = await User.withCount(['posts', 'followers']).all();
|
|
619
|
+
console.log(users[0].posts_count);
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
### 🔍 Enterprise ORM Features
|
|
623
|
+
|
|
624
|
+
#### Advanced Relationship Filtering
|
|
625
|
+
Filter parents based on the existence of children using `whereHas` or `whereDoesntHave`. This runs powerful `EXISTS` subqueries behind the scenes:
|
|
626
|
+
```typescript
|
|
627
|
+
// Fetch users who have at least one published post
|
|
628
|
+
const activeBloggers = await User.whereHas('posts', q => q.where('status', 'published')).get();
|
|
629
|
+
|
|
630
|
+
// Fetch users with no comments
|
|
631
|
+
const lurkers = await User.whereDoesntHave('comments').get();
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
#### API-Ready Pagination
|
|
635
|
+
Simply call `.paginate()`, `.simplePaginate()`, or `.cursorPaginate()` to instantly receive a JSON response structure that is ready to be sent to your frontend:
|
|
636
|
+
```typescript
|
|
637
|
+
// Traditional offset pagination (page 2, 15 items per page)
|
|
638
|
+
const results = await User.paginate(2, 15, '/api/users');
|
|
639
|
+
/*
|
|
640
|
+
{
|
|
641
|
+
"data": [...],
|
|
642
|
+
"meta": { "total": 100, "current_page": 2, "last_page": 7, ... },
|
|
643
|
+
"links": { "prev_page_url": "/api/users?page=1", "next_page_url": "/api/users?page=3" }
|
|
644
|
+
}
|
|
645
|
+
*/
|
|
646
|
+
|
|
647
|
+
// Ultra-fast Cursor pagination for infinite scrolling (WHERE id > last_id LIMIT)
|
|
648
|
+
const infinite = await User.cursorPaginate('cursor_id_here', 15);
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
#### Memory-Safe Chunking
|
|
652
|
+
If you need to process tens of thousands of records, `all()` will crash your Node process. Use `chunk` to iterate over them safely:
|
|
653
|
+
```typescript
|
|
654
|
+
// Fetches 1000 users at a time, keeping RAM usage perfectly flat
|
|
655
|
+
await User.chunk(1000, async (users, page) => {
|
|
656
|
+
for (const user of users) {
|
|
657
|
+
await emailService.sendNewsletter(user.email);
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
#### Other Advanced Features include:
|
|
296
663
|
- Subqueries
|
|
297
664
|
- Union queries
|
|
298
665
|
- Raw expressions
|
|
@@ -307,7 +674,7 @@ const users = await User.where('active', true)
|
|
|
307
674
|
- One Query Builder
|
|
308
675
|
- No hidden globals
|
|
309
676
|
- Predictable SQL
|
|
310
|
-
-
|
|
677
|
+
- Elegant DX, Node.js performance
|
|
311
678
|
|
|
312
679
|
---
|
|
313
680
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Connection, ConnectionConfig } from '../Contracts/Database';
|
|
2
|
+
import { MongoClient, Db, ClientSession } from 'mongodb';
|
|
3
|
+
/**
|
|
4
|
+
* MongoDB database connection
|
|
5
|
+
*
|
|
6
|
+
* Supports:
|
|
7
|
+
* - Single-host standalone
|
|
8
|
+
* - Replica sets with read/write splitting via ReadPreference
|
|
9
|
+
* - ACID transactions via Client Sessions (requires a replica set)
|
|
10
|
+
* - Connection pooling via the native MongoClient built-in pool
|
|
11
|
+
*/
|
|
12
|
+
export declare class MongoDBConnection implements Connection {
|
|
13
|
+
private writeClient;
|
|
14
|
+
private readClient;
|
|
15
|
+
private writeDb;
|
|
16
|
+
private readDb;
|
|
17
|
+
private config;
|
|
18
|
+
private session;
|
|
19
|
+
private inTransaction;
|
|
20
|
+
constructor(config: ConnectionConfig);
|
|
21
|
+
/**
|
|
22
|
+
* Build a MongoDB connection URI from a config object
|
|
23
|
+
*/
|
|
24
|
+
private buildUri;
|
|
25
|
+
/**
|
|
26
|
+
* Build a replica-set URI from an array of read-replica host configs
|
|
27
|
+
*/
|
|
28
|
+
private buildReplicaSetUri;
|
|
29
|
+
/**
|
|
30
|
+
* Lazily establish both the write (primary) and read (secondary) connections
|
|
31
|
+
*/
|
|
32
|
+
private ensureConnected;
|
|
33
|
+
/**
|
|
34
|
+
* Raw SQL is not supported in MongoDB.
|
|
35
|
+
* Use getDatabase() / getCollection() for native MongoDB operations.
|
|
36
|
+
*/
|
|
37
|
+
query(_sql: string, _bindings?: any[]): Promise<any>;
|
|
38
|
+
/**
|
|
39
|
+
* Get the Db instance routed to the correct pool.
|
|
40
|
+
* - Read operations → secondary preferred (read replica)
|
|
41
|
+
* - Write operations → primary
|
|
42
|
+
* - During a transaction → always primary
|
|
43
|
+
*/
|
|
44
|
+
getDatabase(forWrite?: boolean): Promise<Db>;
|
|
45
|
+
/**
|
|
46
|
+
* Convenience: get a collection automatically routed to the right pool
|
|
47
|
+
*/
|
|
48
|
+
getCollection(name: string, forWrite?: boolean): Promise<import("mongodb").Collection<import("bson").Document>>;
|
|
49
|
+
/**
|
|
50
|
+
* Get the underlying write MongoClient instance
|
|
51
|
+
*/
|
|
52
|
+
getClient(): Promise<MongoClient>;
|
|
53
|
+
/**
|
|
54
|
+
* Begin a MongoDB ACID transaction.
|
|
55
|
+
* Requires a replica set — will throw on a standalone server.
|
|
56
|
+
*/
|
|
57
|
+
beginTransaction(): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Commit the active transaction
|
|
60
|
+
*/
|
|
61
|
+
commit(): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Rollback (abort) the active transaction
|
|
64
|
+
*/
|
|
65
|
+
rollback(): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Get the active ClientSession — pass this to collection operations
|
|
68
|
+
* when running queries inside a transaction.
|
|
69
|
+
*/
|
|
70
|
+
getSession(): ClientSession | null;
|
|
71
|
+
/**
|
|
72
|
+
* Close both read and write connections
|
|
73
|
+
*/
|
|
74
|
+
close(): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Returns the write Db instance as the generic driver handle
|
|
77
|
+
*/
|
|
78
|
+
getDriver(): any;
|
|
79
|
+
getSchemaGrammar(): any;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=MongoDBConnection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MongoDBConnection.d.ts","sourceRoot":"","sources":["../../src/Connections/MongoDBConnection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,EAAE,EAAkB,aAAa,EAAE,MAAM,SAAS,CAAC;AAEzE;;;;;;;;GAQG;AACH,qBAAa,iBAAkB,YAAW,UAAU;IAChD,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,aAAa,CAAkB;gBAE3B,MAAM,EAAE,gBAAgB;IAMpC;;OAEG;IACH,OAAO,CAAC,QAAQ;IAShB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAY1B;;OAEG;YACW,eAAe;IA2C7B;;;OAGG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,GAAG,EAAO,GAAG,OAAO,CAAC,GAAG,CAAC;IAO9D;;;;;OAKG;IACG,WAAW,CAAC,QAAQ,GAAE,OAAe,GAAG,OAAO,CAAC,EAAE,CAAC;IAMzD;;OAEG;IACG,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,OAAe;IAK3D;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,WAAW,CAAC;IAOvC;;;OAGG;IACG,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAUvC;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAU7B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAU/B;;;OAGG;IACH,UAAU,IAAI,aAAa,GAAG,IAAI;IAMlC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB5B;;OAEG;IACH,SAAS,IAAI,GAAG;IAIhB,gBAAgB,IAAI,GAAG;CAM1B"}
|