outlet-orm 5.5.1 → 5.5.3
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 +369 -395
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/outlet-orm)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
A JavaScript ORM inspired by Laravel Eloquent for Node.js with support for MySQL, PostgreSQL and SQLite.
|
|
7
7
|
|
|
8
|
-
📚 **[
|
|
8
|
+
📚 **[Complete documentation available in `/docs`](./docs/INDEX.md)**
|
|
9
9
|
|
|
10
|
-
## ✅
|
|
10
|
+
## ✅ Prerequisites and compatibility
|
|
11
11
|
|
|
12
|
-
- Node.js >= 18 (
|
|
13
|
-
-
|
|
12
|
+
- Node.js >= 18 (recommended/required)
|
|
13
|
+
- Install the database driver corresponding to your DBMS (see below)
|
|
14
14
|
|
|
15
15
|
## 🚀 Installation
|
|
16
16
|
|
|
@@ -18,61 +18,50 @@ Un ORM JavaScript inspiré de Laravel Eloquent pour Node.js avec support pour My
|
|
|
18
18
|
npm install outlet-orm
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
###
|
|
21
|
+
### Install the database driver
|
|
22
22
|
|
|
23
|
-
Outlet ORM
|
|
23
|
+
Outlet ORM uses optional peer dependencies for database drivers. Install only the driver you need:
|
|
24
24
|
|
|
25
25
|
- MySQL/MariaDB: `npm install mysql2`
|
|
26
26
|
- PostgreSQL: `npm install pg`
|
|
27
27
|
- SQLite: `npm install sqlite3`
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
If no driver is installed, an explicit error message will tell you which one to install when connecting.
|
|
30
30
|
|
|
31
|
-
## 📁
|
|
31
|
+
## 📁 Recommended Project Structure
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
Organise your project using Outlet ORM with a **2-layer architecture** — Controllers call Models directly, keeping the codebase lean and productive:
|
|
34
34
|
|
|
35
|
-
> 🔐 **
|
|
35
|
+
> 🔐 **Security**: See the [Security Guide](./docs/SECURITY.md) for best practices.
|
|
36
36
|
|
|
37
37
|
```
|
|
38
|
-
|
|
39
|
-
├── .env # ⚠️
|
|
40
|
-
├── .env.example # Template
|
|
38
|
+
my-project/
|
|
39
|
+
├── .env # ⚠️ NEVER committed (in .gitignore)
|
|
40
|
+
├── .env.example # Template without secrets
|
|
41
41
|
├── .gitignore
|
|
42
42
|
├── package.json
|
|
43
43
|
│
|
|
44
|
-
├── src/ # 📦
|
|
45
|
-
│ ├── index.js #
|
|
44
|
+
├── src/ # 📦 Centralised source code
|
|
45
|
+
│ ├── index.js # Application entry point
|
|
46
46
|
│ │
|
|
47
47
|
│ ├── config/ # ⚙️ Configuration
|
|
48
|
-
│ │ ├── app.js #
|
|
49
|
-
│ │ ├── database.js #
|
|
48
|
+
│ │ ├── app.js # General config (port, env)
|
|
49
|
+
│ │ ├── database.js # DB config (reads .env)
|
|
50
50
|
│ │ └── security.js # CORS, helmet, rate limit
|
|
51
51
|
│ │
|
|
52
|
-
│ ├── models/ # 📊
|
|
53
|
-
│ │ ├── index.js #
|
|
52
|
+
│ ├── models/ # 📊 outlet-orm Models (entities)
|
|
53
|
+
│ │ ├── index.js # Centralised model exports
|
|
54
54
|
│ │ ├── User.js
|
|
55
55
|
│ │ ├── Post.js
|
|
56
56
|
│ │ └── Comment.js
|
|
57
57
|
│ │
|
|
58
|
-
│ ├──
|
|
59
|
-
│ │ ├── BaseRepository.js # Méthodes CRUD génériques
|
|
60
|
-
│ │ ├── UserRepository.js # Requêtes spécifiques User
|
|
61
|
-
│ │ └── PostRepository.js
|
|
62
|
-
│ │
|
|
63
|
-
│ ├── services/ # 💼 Couche Métier (Business Logic)
|
|
64
|
-
│ │ ├── AuthService.js # Logique d'authentification
|
|
65
|
-
│ │ ├── UserService.js # Logique métier utilisateur
|
|
66
|
-
│ │ ├── PostService.js
|
|
67
|
-
│ │ └── EmailService.js # Service externe (emails)
|
|
68
|
-
│ │
|
|
69
|
-
│ ├── controllers/ # 🎮 Couche Présentation (HTTP)
|
|
58
|
+
│ ├── controllers/ # 🎮 HTTP handling + business logic (direct ORM calls)
|
|
70
59
|
│ │ ├── AuthController.js
|
|
71
60
|
│ │ ├── UserController.js
|
|
72
61
|
│ │ └── PostController.js
|
|
73
62
|
│ │
|
|
74
|
-
│ ├── routes/ # 🛤️
|
|
75
|
-
│ │ ├── index.js #
|
|
63
|
+
│ ├── routes/ # 🛤️ Route definitions
|
|
64
|
+
│ │ ├── index.js # Route aggregator
|
|
76
65
|
│ │ ├── auth.routes.js
|
|
77
66
|
│ │ ├── user.routes.js
|
|
78
67
|
│ │ └── post.routes.js
|
|
@@ -82,44 +71,42 @@ mon-projet/
|
|
|
82
71
|
│ │ ├── authorize.js # RBAC / permissions
|
|
83
72
|
│ │ ├── rateLimiter.js # Protection DDoS
|
|
84
73
|
│ │ ├── validator.js # Validation request body
|
|
85
|
-
│ │ └── errorHandler.js #
|
|
74
|
+
│ │ └── errorHandler.js # Centralised error handling
|
|
86
75
|
│ │
|
|
87
|
-
│ ├── validators/ # ✅
|
|
76
|
+
│ ├── validators/ # ✅ Validation schemas
|
|
88
77
|
│ │ ├── authValidator.js
|
|
89
78
|
│ │ └── userValidator.js
|
|
90
79
|
│ │
|
|
91
|
-
│ └── utils/ # 🔧
|
|
80
|
+
│ └── utils/ # 🔧 Utilities
|
|
92
81
|
│ ├── hash.js # bcrypt wrapper
|
|
93
82
|
│ ├── token.js # JWT helpers
|
|
94
83
|
│ ├── logger.js # Winston/Pino config
|
|
95
|
-
│ └── response.js #
|
|
84
|
+
│ └── response.js # API response formatting
|
|
96
85
|
│
|
|
97
86
|
├── database/
|
|
98
87
|
│ ├── config.js # Config migrations (outlet-init)
|
|
99
|
-
│ ├── migrations/ #
|
|
100
|
-
│ └── seeds/ #
|
|
88
|
+
│ ├── migrations/ # Migration files
|
|
89
|
+
│ └── seeds/ # Test/demo data
|
|
101
90
|
│ └── UserSeeder.js
|
|
102
91
|
│
|
|
103
|
-
├── public/ # ✅
|
|
92
|
+
├── public/ # ✅ Public static files
|
|
104
93
|
│ ├── images/
|
|
105
94
|
│ ├── css/
|
|
106
95
|
│ └── js/
|
|
107
96
|
│
|
|
108
|
-
├── uploads/ # ⚠️
|
|
97
|
+
├── uploads/ # ⚠️ Uploaded files
|
|
109
98
|
│
|
|
110
|
-
├── logs/ # 📋
|
|
99
|
+
├── logs/ # 📋 Logs (not versioned)
|
|
111
100
|
│
|
|
112
101
|
└── tests/ # 🧪 Tests
|
|
113
|
-
├── unit/ #
|
|
114
|
-
|
|
115
|
-
│ └── models/
|
|
116
|
-
├── integration/ # Tests d'intégration
|
|
102
|
+
├── unit/ # Unit tests (models)
|
|
103
|
+
├── integration/ # Integration tests (API)
|
|
117
104
|
│ └── api/
|
|
118
|
-
└── fixtures/ #
|
|
105
|
+
└── fixtures/ # Test data
|
|
119
106
|
└── users.json
|
|
120
107
|
```
|
|
121
108
|
|
|
122
|
-
### 🏗️ Architecture
|
|
109
|
+
### 🏗️ Architecture Flow
|
|
123
110
|
|
|
124
111
|
```
|
|
125
112
|
┌─────────────────────────────────────────────────────────────┐
|
|
@@ -133,45 +120,32 @@ mon-projet/
|
|
|
133
120
|
│
|
|
134
121
|
▼
|
|
135
122
|
┌─────────────────────────────────────────────────────────────┐
|
|
136
|
-
│ ROUTES → CONTROLLERS
|
|
137
|
-
│
|
|
138
|
-
└─────────────────────────────────────────────────────────────┘
|
|
139
|
-
│
|
|
140
|
-
▼
|
|
141
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
142
|
-
│ SERVICES (Couche Métier / Business Logic) │
|
|
143
|
-
│ Logique métier, orchestration, règles business │
|
|
144
|
-
└─────────────────────────────────────────────────────────────┘
|
|
145
|
-
│
|
|
146
|
-
▼
|
|
147
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
148
|
-
│ REPOSITORIES (Couche Accès Données) │
|
|
149
|
-
│ Abstraction des requêtes DB, utilise les Models │
|
|
123
|
+
│ ROUTES → CONTROLLERS HTTP handling + business logic │
|
|
124
|
+
│ Direct outlet-orm Model calls │
|
|
150
125
|
└─────────────────────────────────────────────────────────────┘
|
|
151
126
|
│
|
|
152
127
|
▼
|
|
153
128
|
┌─────────────────────────────────────────────────────────────┐
|
|
154
|
-
│ MODELS (
|
|
129
|
+
│ MODELS (outlet-orm) → DATABASE │
|
|
155
130
|
└─────────────────────────────────────────────────────────────┘
|
|
156
131
|
```
|
|
157
132
|
|
|
158
|
-
### 📋
|
|
133
|
+
### 📋 Role of each layer
|
|
159
134
|
|
|
160
|
-
|
|
|
161
|
-
|
|
162
|
-
| **
|
|
163
|
-
| **
|
|
164
|
-
| **
|
|
165
|
-
| **Entités** | `models/` | Définition des entités, relations, validations | Outlet ORM |
|
|
135
|
+
| Layer | Folder | Responsibility | Security |
|
|
136
|
+
|--------|---------|----------------|----------|
|
|
137
|
+
| **Controllers** | `controllers/` | Handle HTTP, call ORM models, return responses | Input validation, ownership checks |
|
|
138
|
+
| **Models** | `models/` | Entity definition, relationships, validations | `fillable`, `hidden`, rules |
|
|
139
|
+
| **Middlewares** | `middlewares/` | Auth, rate limiting, error handling | 🔒 Critical |
|
|
166
140
|
|
|
167
|
-
### ✅
|
|
141
|
+
### ✅ Benefits of this architecture
|
|
168
142
|
|
|
169
|
-
- **
|
|
170
|
-
- **
|
|
171
|
-
- **
|
|
172
|
-
- **
|
|
143
|
+
- **Simplicity**: Less files, less abstraction, faster to navigate
|
|
144
|
+
- **Productivity**: Outlet ORM's expressive API handles data access directly
|
|
145
|
+
- **Testability**: Integration tests with SQLite in-memory cover full request flows
|
|
146
|
+
- **Flexibility**: Add a `services/` or `repositories/` layer later if complexity grows
|
|
173
147
|
|
|
174
|
-
### 📝
|
|
148
|
+
### 📝 Example workflow
|
|
175
149
|
|
|
176
150
|
```javascript
|
|
177
151
|
// routes/user.routes.js
|
|
@@ -179,88 +153,88 @@ router.get('/users/:id', auth, UserController.show);
|
|
|
179
153
|
|
|
180
154
|
// controllers/UserController.js
|
|
181
155
|
async show(req, res) {
|
|
182
|
-
const user = await
|
|
156
|
+
const user = await User.with('posts').find(req.params.id);
|
|
157
|
+
if (!user) return res.status(404).json({ message: 'User not found' });
|
|
183
158
|
res.json({ data: user });
|
|
184
159
|
}
|
|
185
160
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
161
|
+
async store(req, res) {
|
|
162
|
+
const existing = await User.where('email', req.body.email).first();
|
|
163
|
+
if (existing) return res.status(409).json({ message: 'Email already in use' });
|
|
164
|
+
|
|
165
|
+
const data = { ...req.body };
|
|
166
|
+
data.password = await bcrypt.hash(data.password, 10);
|
|
192
167
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
return User.with('posts').find(id);
|
|
168
|
+
const user = await User.create(data);
|
|
169
|
+
res.status(201).json({ success: true, data: user });
|
|
196
170
|
}
|
|
197
171
|
```
|
|
198
172
|
|
|
199
|
-
## ✨
|
|
200
|
-
|
|
201
|
-
- **API
|
|
202
|
-
- **Query Builder
|
|
203
|
-
- **
|
|
204
|
-
- **Eager Loading**
|
|
205
|
-
- **
|
|
206
|
-
- `hasOne`, `hasMany`, `belongsTo`, `belongsToMany` (
|
|
207
|
-
- `hasManyThrough`, `hasOneThrough` (
|
|
208
|
-
- `morphOne`, `morphMany`, `morphTo` (
|
|
209
|
-
- **Transactions
|
|
210
|
-
- **Soft Deletes**:
|
|
211
|
-
- **Scopes**:
|
|
173
|
+
## ✨ Key features
|
|
174
|
+
|
|
175
|
+
- **Eloquent-inspired API** (Active Record) for a fluent developer experience
|
|
176
|
+
- **Expressive Query Builder**: where/joins/order/limit/offset/paginate
|
|
177
|
+
- **Laravel-style relationship filters**: `whereHas()`, `has()`, `whereDoesntHave()`, `withCount()`
|
|
178
|
+
- **Eager Loading** of relationships via `.with(...)` with constraints and dot-notation
|
|
179
|
+
- **Complete relations**:
|
|
180
|
+
- `hasOne`, `hasMany`, `belongsTo`, `belongsToMany` (with attach/detach/sync)
|
|
181
|
+
- `hasManyThrough`, `hasOneThrough` (transitive relationships)
|
|
182
|
+
- `morphOne`, `morphMany`, `morphTo` (polymorphic relationships)
|
|
183
|
+
- **Complete Transactions**: `beginTransaction()`, `commit()`, `rollback()`, `transaction()`
|
|
184
|
+
- **Soft Deletes**: soft deletion with `deleted_at`, `withTrashed()`, `onlyTrashed()`, `restore()`
|
|
185
|
+
- **Scopes**: global and local, to reuse your query filters
|
|
212
186
|
- **Events/Hooks**: `creating`, `created`, `updating`, `updated`, `deleting`, `deleted`, etc.
|
|
213
|
-
- **Validation**:
|
|
214
|
-
- **Query Logging**: mode
|
|
215
|
-
- **Pool
|
|
216
|
-
- **Protection
|
|
217
|
-
- **Casts
|
|
218
|
-
- **
|
|
219
|
-
- **
|
|
220
|
-
- **
|
|
221
|
-
- **
|
|
222
|
-
- **
|
|
223
|
-
- **Migrations
|
|
224
|
-
- **CLI
|
|
225
|
-
- **
|
|
226
|
-
- **Multi-
|
|
227
|
-
- **
|
|
228
|
-
|
|
229
|
-
## ⚡
|
|
230
|
-
|
|
231
|
-
### Initialisation
|
|
187
|
+
- **Validation**: built-in basic rules (`required`, `email`, `min`, `max`, etc.)
|
|
188
|
+
- **Query Logging**: debug mode with `enableQueryLog()` and `getQueryLog()`
|
|
189
|
+
- **PostgreSQL Pool**: pooled connections for better performance
|
|
190
|
+
- **SQL Protection**: automatic sanitisation of identifiers
|
|
191
|
+
- **Automatic Casts** (int, float, boolean, json, date...)
|
|
192
|
+
- **Hidden attributes** (`hidden`) and automatic timestamps
|
|
193
|
+
- **Visibility control** of hidden attributes: `withHidden()` and `withoutHidden()`
|
|
194
|
+
- **Atomic increment/decrement**: `increment()` and `decrement()`
|
|
195
|
+
- **Ergonomic aliases**: `columns([...])`, `ordrer()` (typo alias for `orderBy`)
|
|
196
|
+
- **Raw queries**: `executeRawQuery()` and `execute()` (native driver results)
|
|
197
|
+
- **Complete Migrations** (create/alter/drop, index, foreign keys, batch tracking)
|
|
198
|
+
- **Handy CLI tools**: `outlet-init`, `outlet-migrate`, `outlet-convert`
|
|
199
|
+
- **`.env` configuration** (loaded automatically)
|
|
200
|
+
- **Multi-database**: MySQL, PostgreSQL, and SQLite
|
|
201
|
+
- **Complete TypeScript types** with Generic Model and typed Schema Builder (v4.0.0+)
|
|
202
|
+
|
|
203
|
+
## ⚡ Quick Start
|
|
204
|
+
|
|
205
|
+
### Project Initialisation
|
|
232
206
|
|
|
233
207
|
```bash
|
|
234
|
-
#
|
|
208
|
+
# Create initial configuration
|
|
235
209
|
outlet-init
|
|
236
210
|
|
|
237
|
-
#
|
|
211
|
+
# Create a migration
|
|
238
212
|
outlet-migrate make create_users_table
|
|
239
213
|
|
|
240
|
-
#
|
|
214
|
+
# Run migrations
|
|
241
215
|
outlet-migrate migrate
|
|
242
216
|
```
|
|
243
217
|
|
|
244
|
-
### 🌱 Seeding
|
|
218
|
+
### 🌱 Quick Seeding
|
|
245
219
|
|
|
246
220
|
```bash
|
|
247
|
-
#
|
|
221
|
+
# Create a seeder
|
|
248
222
|
outlet-migrate make:seed UserSeeder
|
|
249
223
|
|
|
250
|
-
#
|
|
224
|
+
# Run seeders (DatabaseSeeder runs first)
|
|
251
225
|
outlet-migrate seed
|
|
252
226
|
|
|
253
|
-
#
|
|
227
|
+
# Run a specific seeder
|
|
254
228
|
outlet-migrate seed --class UserSeeder
|
|
255
229
|
```
|
|
256
230
|
|
|
257
|
-
## 📖
|
|
231
|
+
## 📖 Usage
|
|
258
232
|
|
|
259
|
-
###
|
|
233
|
+
### Connection configuration
|
|
260
234
|
|
|
261
|
-
Outlet ORM
|
|
235
|
+
Outlet ORM automatically loads the connection from the `.env` file. **No need to import DatabaseConnection!**
|
|
262
236
|
|
|
263
|
-
####
|
|
237
|
+
#### `.env` file
|
|
264
238
|
|
|
265
239
|
```env
|
|
266
240
|
DB_DRIVER=mysql
|
|
@@ -271,7 +245,7 @@ DB_PASSWORD=secret
|
|
|
271
245
|
DB_PORT=3306
|
|
272
246
|
```
|
|
273
247
|
|
|
274
|
-
####
|
|
248
|
+
#### Simplified usage
|
|
275
249
|
|
|
276
250
|
```javascript
|
|
277
251
|
const { Model } = require('outlet-orm');
|
|
@@ -280,21 +254,21 @@ class User extends Model {
|
|
|
280
254
|
static table = 'users';
|
|
281
255
|
}
|
|
282
256
|
|
|
283
|
-
//
|
|
257
|
+
// That's it! The connection is automatic
|
|
284
258
|
const users = await User.all();
|
|
285
259
|
```
|
|
286
260
|
|
|
287
|
-
####
|
|
261
|
+
#### Manual configuration (optional)
|
|
288
262
|
|
|
289
|
-
|
|
263
|
+
If you need to control the connection :
|
|
290
264
|
|
|
291
265
|
```javascript
|
|
292
266
|
const { DatabaseConnection, Model } = require('outlet-orm');
|
|
293
267
|
|
|
294
|
-
// Option 1 – via .env (
|
|
268
|
+
// Option 1 – via .env (no parameters required)
|
|
295
269
|
const db = new DatabaseConnection();
|
|
296
270
|
|
|
297
|
-
// Option 2 – via
|
|
271
|
+
// Option 2 – via configuration object
|
|
298
272
|
const db = new DatabaseConnection({
|
|
299
273
|
driver: 'mysql',
|
|
300
274
|
host: 'localhost',
|
|
@@ -304,48 +278,48 @@ const db = new DatabaseConnection({
|
|
|
304
278
|
port: 3306
|
|
305
279
|
});
|
|
306
280
|
|
|
307
|
-
//
|
|
281
|
+
// Set the connection manually (optional)
|
|
308
282
|
Model.setConnection(db);
|
|
309
283
|
```
|
|
310
284
|
|
|
311
|
-
####
|
|
285
|
+
#### Environment variables (.env)
|
|
312
286
|
|
|
313
|
-
| Variable | Description |
|
|
287
|
+
| Variable | Description | Default |
|
|
314
288
|
|----------|-------------|------------|
|
|
315
289
|
| `DB_DRIVER` | `mysql`, `postgres`, `sqlite` | `mysql` |
|
|
316
|
-
| `DB_HOST` |
|
|
317
|
-
| `DB_PORT` |
|
|
318
|
-
| `DB_USER` / `DB_USERNAME` |
|
|
319
|
-
| `DB_PASSWORD` |
|
|
320
|
-
| `DB_DATABASE` / `DB_NAME` |
|
|
321
|
-
| `DB_FILE` / `SQLITE_DB` |
|
|
290
|
+
| `DB_HOST` | Database host | `localhost` |
|
|
291
|
+
| `DB_PORT` | Connection port | Driver default |
|
|
292
|
+
| `DB_USER` / `DB_USERNAME` | Username | - |
|
|
293
|
+
| `DB_PASSWORD` | Password | - |
|
|
294
|
+
| `DB_DATABASE` / `DB_NAME` | Database name | - |
|
|
295
|
+
| `DB_FILE` / `SQLITE_DB` | SQLite file | `:memory:` |
|
|
322
296
|
|
|
323
297
|
### Importation
|
|
324
298
|
|
|
325
299
|
```javascript
|
|
326
|
-
// CommonJS -
|
|
300
|
+
// CommonJS - Simple import (automatic connection via .env)
|
|
327
301
|
const { Model } = require('outlet-orm');
|
|
328
302
|
|
|
329
303
|
// ES Modules
|
|
330
304
|
import { Model } from 'outlet-orm';
|
|
331
305
|
|
|
332
|
-
//
|
|
306
|
+
// If you need manual control over the connection
|
|
333
307
|
const { DatabaseConnection, Model } = require('outlet-orm');
|
|
334
308
|
```
|
|
335
309
|
|
|
336
|
-
###
|
|
310
|
+
### Define a model
|
|
337
311
|
|
|
338
312
|
```javascript
|
|
339
313
|
const { Model } = require('outlet-orm');
|
|
340
314
|
|
|
341
|
-
//
|
|
315
|
+
// Define related models (see Relationships)
|
|
342
316
|
class Post extends Model { static table = 'posts'; }
|
|
343
317
|
class Profile extends Model { static table = 'profiles'; }
|
|
344
318
|
|
|
345
319
|
class User extends Model {
|
|
346
320
|
static table = 'users';
|
|
347
|
-
static primaryKey = 'id'; //
|
|
348
|
-
static timestamps = true; //
|
|
321
|
+
static primaryKey = 'id'; // Default: 'id'
|
|
322
|
+
static timestamps = true; // Default: true
|
|
349
323
|
static fillable = ['name', 'email', 'password'];
|
|
350
324
|
static hidden = ['password'];
|
|
351
325
|
static casts = {
|
|
@@ -366,19 +340,19 @@ class User extends Model {
|
|
|
366
340
|
}
|
|
367
341
|
```
|
|
368
342
|
|
|
369
|
-
###
|
|
343
|
+
### CRUD operations
|
|
370
344
|
|
|
371
|
-
####
|
|
345
|
+
#### Create
|
|
372
346
|
|
|
373
347
|
```javascript
|
|
374
|
-
//
|
|
348
|
+
// Method 1: create()
|
|
375
349
|
const user = await User.create({
|
|
376
350
|
name: 'John Doe',
|
|
377
351
|
email: 'john@example.com',
|
|
378
352
|
password: 'secret123'
|
|
379
353
|
});
|
|
380
354
|
|
|
381
|
-
//
|
|
355
|
+
// Method 2: new + save()
|
|
382
356
|
const user = new User({
|
|
383
357
|
name: 'Jane Doe',
|
|
384
358
|
email: 'jane@example.com'
|
|
@@ -386,30 +360,30 @@ const user = new User({
|
|
|
386
360
|
user.setAttribute('password', 'secret456');
|
|
387
361
|
await user.save();
|
|
388
362
|
|
|
389
|
-
//
|
|
363
|
+
// Raw insert (without creating an instance)
|
|
390
364
|
await User.insert({ name: 'Bob', email: 'bob@example.com' });
|
|
391
365
|
```
|
|
392
366
|
|
|
393
|
-
####
|
|
367
|
+
#### Read
|
|
394
368
|
|
|
395
369
|
```javascript
|
|
396
|
-
//
|
|
370
|
+
// All records
|
|
397
371
|
const users = await User.all();
|
|
398
372
|
|
|
399
|
-
//
|
|
373
|
+
// By ID
|
|
400
374
|
const user = await User.find(1);
|
|
401
|
-
const user = await User.findOrFail(1); //
|
|
375
|
+
const user = await User.findOrFail(1); // Throws an error if not found
|
|
402
376
|
|
|
403
|
-
//
|
|
377
|
+
// First result
|
|
404
378
|
const firstUser = await User.first();
|
|
405
379
|
|
|
406
|
-
//
|
|
380
|
+
// With conditions
|
|
407
381
|
const activeUsers = await User
|
|
408
382
|
.where('status', 'active')
|
|
409
383
|
.where('age', '>', 18)
|
|
410
384
|
.get();
|
|
411
385
|
|
|
412
|
-
//
|
|
386
|
+
// With relationships (Eager Loading)
|
|
413
387
|
const usersWithPosts = await User
|
|
414
388
|
.with('posts', 'profile')
|
|
415
389
|
.get();
|
|
@@ -421,7 +395,7 @@ const recentUsers = await User
|
|
|
421
395
|
.get();
|
|
422
396
|
```
|
|
423
397
|
|
|
424
|
-
####
|
|
398
|
+
#### Update
|
|
425
399
|
|
|
426
400
|
```javascript
|
|
427
401
|
// Instance
|
|
@@ -439,12 +413,12 @@ const updated = await User
|
|
|
439
413
|
.where('id', 1)
|
|
440
414
|
.updateAndFetch({ name: 'Neo' }, ['profile', 'posts']);
|
|
441
415
|
|
|
442
|
-
// Helpers
|
|
416
|
+
// Helpers by ID
|
|
443
417
|
const user = await User.updateAndFetchById(1, { name: 'Trinity' }, ['profile']);
|
|
444
418
|
await User.updateById(2, { status: 'active' });
|
|
445
419
|
```
|
|
446
420
|
|
|
447
|
-
####
|
|
421
|
+
#### Delete
|
|
448
422
|
|
|
449
423
|
```javascript
|
|
450
424
|
// Instance
|
|
@@ -494,37 +468,37 @@ const result = await User
|
|
|
494
468
|
.select('users.*', 'profiles.bio', 'countries.name as country')
|
|
495
469
|
.get();
|
|
496
470
|
|
|
497
|
-
//
|
|
471
|
+
// Aggregations
|
|
498
472
|
const stats = await User
|
|
499
473
|
.distinct()
|
|
500
474
|
.groupBy('status')
|
|
501
475
|
.having('COUNT(*)', '>', 5)
|
|
502
476
|
.get();
|
|
503
477
|
|
|
504
|
-
//
|
|
478
|
+
// Atomic increment / decrement
|
|
505
479
|
await User.where('id', 1).increment('login_count');
|
|
506
480
|
await User.where('id', 1).decrement('credits', 10);
|
|
507
481
|
```
|
|
508
482
|
|
|
509
|
-
###
|
|
483
|
+
### Relationship filters
|
|
510
484
|
|
|
511
485
|
```javascript
|
|
512
|
-
// whereHas:
|
|
486
|
+
// whereHas: Users who have at least one published post
|
|
513
487
|
const authors = await User
|
|
514
488
|
.whereHas('posts', (q) => {
|
|
515
489
|
q.where('status', 'published');
|
|
516
490
|
})
|
|
517
491
|
.get();
|
|
518
492
|
|
|
519
|
-
// has:
|
|
493
|
+
// has: At least N children
|
|
520
494
|
const prolific = await User.has('posts', '>=', 10).get();
|
|
521
495
|
|
|
522
|
-
// whereDoesntHave:
|
|
496
|
+
// whereDoesntHave: No children
|
|
523
497
|
const noPostUsers = await User.whereDoesntHave('posts').get();
|
|
524
498
|
|
|
525
|
-
// withCount:
|
|
499
|
+
// withCount: Add a {relation}_count column
|
|
526
500
|
const withCounts = await User.withCount('posts').get();
|
|
527
|
-
//
|
|
501
|
+
// Each user will have: user.getAttribute('posts_count')
|
|
528
502
|
```
|
|
529
503
|
|
|
530
504
|
## 🔗 Relations
|
|
@@ -609,15 +583,15 @@ class User extends Model {
|
|
|
609
583
|
const user = await User.find(1);
|
|
610
584
|
const roles = await user.roles().get();
|
|
611
585
|
|
|
612
|
-
//
|
|
613
|
-
await user.roles().attach([1, 2]); //
|
|
614
|
-
await user.roles().detach(2); //
|
|
615
|
-
await user.roles().sync([1, 3]); //
|
|
586
|
+
// Pivot methods
|
|
587
|
+
await user.roles().attach([1, 2]); // Attach roles
|
|
588
|
+
await user.roles().detach(2); // Detach a role
|
|
589
|
+
await user.roles().sync([1, 3]); // Synchronise (replaces all)
|
|
616
590
|
```
|
|
617
591
|
|
|
618
592
|
### Has Many Through (hasManyThrough)
|
|
619
593
|
|
|
620
|
-
|
|
594
|
+
Access a distant relationship via an intermediate model.
|
|
621
595
|
|
|
622
596
|
```javascript
|
|
623
597
|
const { Model } = require('outlet-orm');
|
|
@@ -649,20 +623,20 @@ const user = await User.find(1);
|
|
|
649
623
|
const country = await user.country().get();
|
|
650
624
|
```
|
|
651
625
|
|
|
652
|
-
###
|
|
626
|
+
### Polymorphic relationships
|
|
653
627
|
|
|
654
|
-
|
|
628
|
+
Polymorphic relationships allow a model to belong to multiple other models.
|
|
655
629
|
|
|
656
630
|
```javascript
|
|
657
631
|
const { Model } = require('outlet-orm');
|
|
658
632
|
|
|
659
|
-
//
|
|
633
|
+
// Set up the morph map
|
|
660
634
|
Model.setMorphMap({
|
|
661
635
|
'posts': Post,
|
|
662
636
|
'videos': Video
|
|
663
637
|
});
|
|
664
638
|
|
|
665
|
-
//
|
|
639
|
+
// Models
|
|
666
640
|
class Post extends Model {
|
|
667
641
|
comments() {
|
|
668
642
|
return this.morphMany(Comment, 'commentable');
|
|
@@ -686,37 +660,37 @@ const post = await Post.find(1);
|
|
|
686
660
|
const comments = await post.comments().get();
|
|
687
661
|
|
|
688
662
|
const comment = await Comment.find(1);
|
|
689
|
-
const parent = await comment.commentable().get(); // Post
|
|
663
|
+
const parent = await comment.commentable().get(); // Post or Video
|
|
690
664
|
```
|
|
691
665
|
|
|
692
|
-
**
|
|
693
|
-
- `morphOne(Related, 'morphName')` - One-to-One
|
|
694
|
-
- `morphMany(Related, 'morphName')` - One-to-Many
|
|
695
|
-
- `morphTo('morphName')` - Inverse
|
|
666
|
+
**Available polymorphic relationships:**
|
|
667
|
+
- `morphOne(Related, 'morphName')` - One-to-One polymorphic
|
|
668
|
+
- `morphMany(Related, 'morphName')` - One-to-Many polymorphic
|
|
669
|
+
- `morphTo('morphName')` - Inverse polymorphic
|
|
696
670
|
|
|
697
671
|
### Eager Loading
|
|
698
672
|
|
|
699
673
|
```javascript
|
|
700
|
-
//
|
|
674
|
+
// Load multiple relationships
|
|
701
675
|
const users = await User.with('posts', 'profile', 'roles').get();
|
|
702
676
|
|
|
703
|
-
//
|
|
677
|
+
// Load with constraints
|
|
704
678
|
const users = await User.with({
|
|
705
679
|
posts: (q) => q.where('status', 'published').orderBy('created_at', 'desc')
|
|
706
680
|
}).get();
|
|
707
681
|
|
|
708
|
-
//
|
|
682
|
+
// Load nested relationships (dot notation)
|
|
709
683
|
const users = await User.with('posts.comments.author').get();
|
|
710
684
|
|
|
711
|
-
//
|
|
685
|
+
// Load on an existing instance
|
|
712
686
|
const user = await User.find(1);
|
|
713
687
|
await user.load('posts', 'profile');
|
|
714
688
|
await user.load(['roles', 'posts.comments']);
|
|
715
689
|
|
|
716
|
-
//
|
|
690
|
+
// Access loaded relationships
|
|
717
691
|
users.forEach(user => {
|
|
718
|
-
console.log(user.
|
|
719
|
-
console.log(user.
|
|
692
|
+
console.log(user.relationships.posts);
|
|
693
|
+
console.log(user.relationships.profile);
|
|
720
694
|
});
|
|
721
695
|
```
|
|
722
696
|
|
|
@@ -724,7 +698,7 @@ users.forEach(user => {
|
|
|
724
698
|
|
|
725
699
|
### Casts
|
|
726
700
|
|
|
727
|
-
|
|
701
|
+
Casts automatically convert attributes:
|
|
728
702
|
|
|
729
703
|
```javascript
|
|
730
704
|
const { Model } = require('outlet-orm');
|
|
@@ -736,13 +710,13 @@ class User extends Model {
|
|
|
736
710
|
balance: 'float', // ou 'double'
|
|
737
711
|
email_verified: 'boolean', // ou 'bool'
|
|
738
712
|
metadata: 'json', // Parse JSON
|
|
739
|
-
settings: 'array', // Parse JSON
|
|
740
|
-
birthday: 'date' //
|
|
713
|
+
settings: 'array', // Parse JSON as array
|
|
714
|
+
birthday: 'date' // Converts to Date
|
|
741
715
|
};
|
|
742
716
|
}
|
|
743
717
|
```
|
|
744
718
|
|
|
745
|
-
###
|
|
719
|
+
### Hidden attributes
|
|
746
720
|
|
|
747
721
|
```javascript
|
|
748
722
|
const { Model } = require('outlet-orm');
|
|
@@ -752,24 +726,24 @@ class User extends Model {
|
|
|
752
726
|
}
|
|
753
727
|
|
|
754
728
|
const user = await User.find(1);
|
|
755
|
-
console.log(user.toJSON()); // password
|
|
729
|
+
console.log(user.toJSON()); // password and secret_token excluded
|
|
756
730
|
```
|
|
757
731
|
|
|
758
|
-
####
|
|
732
|
+
#### Show hidden attributes
|
|
759
733
|
|
|
760
734
|
```javascript
|
|
761
|
-
//
|
|
735
|
+
// Include hidden attributes
|
|
762
736
|
const user = await User.withHidden().where('email', 'john@example.com').first();
|
|
763
|
-
console.log(user.toJSON()); // password
|
|
737
|
+
console.log(user.toJSON()); // password included
|
|
764
738
|
|
|
765
|
-
//
|
|
766
|
-
const user = await User.withoutHidden(true).first(); // true =
|
|
767
|
-
const user = await User.withoutHidden(false).first(); // false =
|
|
739
|
+
// Control with a boolean
|
|
740
|
+
const user = await User.withoutHidden(true).first(); // true = show
|
|
741
|
+
const user = await User.withoutHidden(false).first(); // false = hide (default)
|
|
768
742
|
|
|
769
|
-
//
|
|
743
|
+
// Use case: authentication
|
|
770
744
|
const user = await User.withHidden().where('email', email).first();
|
|
771
745
|
if (user && await bcrypt.compare(password, user.getAttribute('password'))) {
|
|
772
|
-
//
|
|
746
|
+
// Authentication successful
|
|
773
747
|
}
|
|
774
748
|
```
|
|
775
749
|
|
|
@@ -778,12 +752,12 @@ if (user && await bcrypt.compare(password, user.getAttribute('password'))) {
|
|
|
778
752
|
```javascript
|
|
779
753
|
const { Model } = require('outlet-orm');
|
|
780
754
|
|
|
781
|
-
//
|
|
755
|
+
// Enabled by default (created_at, updated_at)
|
|
782
756
|
class User extends Model {
|
|
783
757
|
static timestamps = true;
|
|
784
758
|
}
|
|
785
759
|
|
|
786
|
-
//
|
|
760
|
+
// Disable
|
|
787
761
|
class Log extends Model {
|
|
788
762
|
static timestamps = false;
|
|
789
763
|
}
|
|
@@ -791,21 +765,21 @@ class Log extends Model {
|
|
|
791
765
|
|
|
792
766
|
## 🔄 Transactions
|
|
793
767
|
|
|
794
|
-
Outlet ORM
|
|
768
|
+
Outlet ORM supports transactions to guarantee data integrity:
|
|
795
769
|
|
|
796
770
|
```javascript
|
|
797
771
|
const { DatabaseConnection, Model } = require('outlet-orm');
|
|
798
772
|
|
|
799
|
-
//
|
|
773
|
+
// Method 1: Automatic callback (recommended)
|
|
800
774
|
const db = Model.connection;
|
|
801
775
|
const result = await db.transaction(async (connection) => {
|
|
802
776
|
const user = await User.create({ name: 'John', email: 'john@example.com' });
|
|
803
777
|
await Account.create({ user_id: user.getAttribute('id'), balance: 0 });
|
|
804
778
|
return user;
|
|
805
779
|
});
|
|
806
|
-
//
|
|
780
|
+
// Automatic commit, rollback on error
|
|
807
781
|
|
|
808
|
-
//
|
|
782
|
+
// Method 2: Manual control
|
|
809
783
|
await db.beginTransaction();
|
|
810
784
|
try {
|
|
811
785
|
await User.create({ name: 'Jane' });
|
|
@@ -818,7 +792,7 @@ try {
|
|
|
818
792
|
|
|
819
793
|
## 🗑️ Soft Deletes
|
|
820
794
|
|
|
821
|
-
|
|
795
|
+
Soft deletion using a `deleted_at` column:
|
|
822
796
|
|
|
823
797
|
```javascript
|
|
824
798
|
const { Model } = require('outlet-orm');
|
|
@@ -826,31 +800,31 @@ const { Model } = require('outlet-orm');
|
|
|
826
800
|
class Post extends Model {
|
|
827
801
|
static table = 'posts';
|
|
828
802
|
static softDeletes = true;
|
|
829
|
-
// static DELETED_AT = 'deleted_at'; //
|
|
803
|
+
// static DELETED_AT = 'deleted_at'; // Customisable
|
|
830
804
|
}
|
|
831
805
|
|
|
832
|
-
//
|
|
833
|
-
const posts = await Post.all(); //
|
|
806
|
+
// Queries automatically exclude soft-deleted records
|
|
807
|
+
const posts = await Post.all(); // Only non-deleted records
|
|
834
808
|
|
|
835
|
-
//
|
|
809
|
+
// Include deleted records
|
|
836
810
|
const allPosts = await Post.withTrashed().get();
|
|
837
811
|
|
|
838
|
-
//
|
|
812
|
+
// Only deleted records
|
|
839
813
|
const trashedPosts = await Post.onlyTrashed().get();
|
|
840
814
|
|
|
841
|
-
//
|
|
815
|
+
// Delete (soft delete)
|
|
842
816
|
const post = await Post.find(1);
|
|
843
|
-
await post.destroy(); //
|
|
817
|
+
await post.destroy(); // Sets deleted_at to the current date
|
|
844
818
|
|
|
845
|
-
//
|
|
819
|
+
// Check if deleted
|
|
846
820
|
if (post.trashed()) {
|
|
847
|
-
console.log('
|
|
821
|
+
console.log('This post is deleted');
|
|
848
822
|
}
|
|
849
823
|
|
|
850
824
|
// Restaurer
|
|
851
825
|
await post.restore();
|
|
852
826
|
|
|
853
|
-
//
|
|
827
|
+
// Delete permanently
|
|
854
828
|
await post.forceDelete();
|
|
855
829
|
```
|
|
856
830
|
|
|
@@ -858,7 +832,7 @@ await post.forceDelete();
|
|
|
858
832
|
|
|
859
833
|
### Scopes Globaux
|
|
860
834
|
|
|
861
|
-
|
|
835
|
+
Applied automatically to all queries:
|
|
862
836
|
|
|
863
837
|
```javascript
|
|
864
838
|
const { Model } = require('outlet-orm');
|
|
@@ -867,24 +841,24 @@ class Post extends Model {
|
|
|
867
841
|
static table = 'posts';
|
|
868
842
|
}
|
|
869
843
|
|
|
870
|
-
//
|
|
844
|
+
// Add a global scope
|
|
871
845
|
Post.addGlobalScope('published', (query) => {
|
|
872
846
|
query.where('status', 'published');
|
|
873
847
|
});
|
|
874
848
|
|
|
875
|
-
//
|
|
876
|
-
const posts = await Post.all(); //
|
|
849
|
+
// All queries filter automatically
|
|
850
|
+
const posts = await Post.all(); // Published only
|
|
877
851
|
|
|
878
|
-
//
|
|
852
|
+
// Temporarily disable a scope
|
|
879
853
|
const allPosts = await Post.withoutGlobalScope('published').get();
|
|
880
854
|
|
|
881
|
-
//
|
|
855
|
+
// Disable all scopes
|
|
882
856
|
const rawPosts = await Post.withoutGlobalScopes().get();
|
|
883
857
|
```
|
|
884
858
|
|
|
885
859
|
## 📣 Events / Hooks
|
|
886
860
|
|
|
887
|
-
|
|
861
|
+
Intercept operations on your models:
|
|
888
862
|
|
|
889
863
|
```javascript
|
|
890
864
|
const { Model } = require('outlet-orm');
|
|
@@ -893,53 +867,53 @@ class User extends Model {
|
|
|
893
867
|
static table = 'users';
|
|
894
868
|
}
|
|
895
869
|
|
|
896
|
-
//
|
|
870
|
+
// Before creation
|
|
897
871
|
User.creating((user) => {
|
|
898
872
|
user.setAttribute('uuid', generateUUID());
|
|
899
|
-
//
|
|
873
|
+
// Return false to roll back
|
|
900
874
|
});
|
|
901
875
|
|
|
902
|
-
//
|
|
876
|
+
// After creation
|
|
903
877
|
User.created((user) => {
|
|
904
|
-
console.log(`
|
|
878
|
+
console.log(`User ${user.getAttribute('id')} created`);
|
|
905
879
|
});
|
|
906
880
|
|
|
907
|
-
//
|
|
881
|
+
// Before update
|
|
908
882
|
User.updating((user) => {
|
|
909
883
|
user.setAttribute('updated_at', new Date());
|
|
910
884
|
});
|
|
911
885
|
|
|
912
|
-
//
|
|
886
|
+
// After update
|
|
913
887
|
User.updated((user) => {
|
|
914
|
-
//
|
|
888
|
+
// Notify external systems
|
|
915
889
|
});
|
|
916
890
|
|
|
917
|
-
//
|
|
891
|
+
// saving/saved events (creation AND update)
|
|
918
892
|
User.saving((user) => {
|
|
919
|
-
//
|
|
893
|
+
// Data cleanup
|
|
920
894
|
});
|
|
921
895
|
|
|
922
896
|
User.saved((user) => {
|
|
923
897
|
// Cache invalidation
|
|
924
898
|
});
|
|
925
899
|
|
|
926
|
-
//
|
|
900
|
+
// Before/after deletion
|
|
927
901
|
User.deleting((user) => {
|
|
928
|
-
//
|
|
902
|
+
// Checks before deletion
|
|
929
903
|
});
|
|
930
904
|
|
|
931
905
|
User.deleted((user) => {
|
|
932
|
-
//
|
|
906
|
+
// Clean up relationships
|
|
933
907
|
});
|
|
934
908
|
|
|
935
|
-
//
|
|
909
|
+
// For soft deletes
|
|
936
910
|
User.restoring((user) => {});
|
|
937
911
|
User.restored((user) => {});
|
|
938
912
|
```
|
|
939
913
|
|
|
940
914
|
## ✅ Validation
|
|
941
915
|
|
|
942
|
-
|
|
916
|
+
Built-in basic validation:
|
|
943
917
|
|
|
944
918
|
```javascript
|
|
945
919
|
const { Model } = require('outlet-orm');
|
|
@@ -971,7 +945,7 @@ console.log(errors);
|
|
|
971
945
|
// age: ['age must not exceed 150']
|
|
972
946
|
// }
|
|
973
947
|
|
|
974
|
-
//
|
|
948
|
+
// Validate or throw an error
|
|
975
949
|
try {
|
|
976
950
|
user.validateOrFail();
|
|
977
951
|
} catch (error) {
|
|
@@ -979,24 +953,24 @@ try {
|
|
|
979
953
|
}
|
|
980
954
|
```
|
|
981
955
|
|
|
982
|
-
###
|
|
956
|
+
### Available rules
|
|
983
957
|
|
|
984
|
-
|
|
|
958
|
+
| Rule | Description |
|
|
985
959
|
|-------|-------------|
|
|
986
|
-
| `required` |
|
|
987
|
-
| `string` |
|
|
988
|
-
| `number` / `numeric` |
|
|
989
|
-
| `email` |
|
|
990
|
-
| `boolean` |
|
|
991
|
-
| `date` |
|
|
992
|
-
| `min:N` | Minimum N (longueur ou
|
|
993
|
-
| `max:N` | Maximum N (longueur ou
|
|
960
|
+
| `required` | Required field |
|
|
961
|
+
| `string` | Must be a string |
|
|
962
|
+
| `number` / `numeric` | Must be a number |
|
|
963
|
+
| `email` | Valid email format |
|
|
964
|
+
| `boolean` | Must be a boolean |
|
|
965
|
+
| `date` | Valid date |
|
|
966
|
+
| `min:N` | Minimum N (longueur ou value) |
|
|
967
|
+
| `max:N` | Maximum N (longueur ou value) |
|
|
994
968
|
| `in:a,b,c` | Valeur parmi la liste |
|
|
995
969
|
| `regex:pattern` | Match le pattern regex |
|
|
996
970
|
|
|
997
971
|
## 📊 Query Logging
|
|
998
972
|
|
|
999
|
-
|
|
973
|
+
Debug mode to analyse your queries:
|
|
1000
974
|
|
|
1001
975
|
```javascript
|
|
1002
976
|
const { Model } = require('outlet-orm');
|
|
@@ -1005,11 +979,11 @@ const { Model } = require('outlet-orm');
|
|
|
1005
979
|
const db = Model.getConnection();
|
|
1006
980
|
db.enableQueryLog();
|
|
1007
981
|
|
|
1008
|
-
//
|
|
982
|
+
// Run queries
|
|
1009
983
|
await User.where('status', 'active').get();
|
|
1010
984
|
await Post.with('author').get();
|
|
1011
985
|
|
|
1012
|
-
//
|
|
986
|
+
// Retrieve the log
|
|
1013
987
|
const queries = db.getQueryLog();
|
|
1014
988
|
console.log(queries);
|
|
1015
989
|
// [
|
|
@@ -1020,12 +994,12 @@ console.log(queries);
|
|
|
1020
994
|
// Vider le log
|
|
1021
995
|
db.flushQueryLog();
|
|
1022
996
|
|
|
1023
|
-
//
|
|
997
|
+
// Disable le logging
|
|
1024
998
|
db.disableQueryLog();
|
|
1025
999
|
|
|
1026
|
-
//
|
|
1000
|
+
// Check if active
|
|
1027
1001
|
if (db.isLogging()) {
|
|
1028
|
-
console.log('Logging
|
|
1002
|
+
console.log('Logging active');
|
|
1029
1003
|
}
|
|
1030
1004
|
```
|
|
1031
1005
|
|
|
@@ -1033,103 +1007,103 @@ if (db.isLogging()) {
|
|
|
1033
1007
|
|
|
1034
1008
|
### DatabaseConnection
|
|
1035
1009
|
|
|
1036
|
-
|
|
|
1010
|
+
| Method | Description |
|
|
1037
1011
|
|---------|-------------|
|
|
1038
|
-
| `new DatabaseConnection(config?)` |
|
|
1039
|
-
| `connect()` |
|
|
1040
|
-
| `beginTransaction()` |
|
|
1041
|
-
| `commit()` |
|
|
1042
|
-
| `rollback()` |
|
|
1043
|
-
| `transaction(callback)` |
|
|
1044
|
-
| `select(table, query)` |
|
|
1045
|
-
| `insert(table, data)` |
|
|
1046
|
-
| `insertMany(table, data[])` |
|
|
1047
|
-
| `update(table, data, query)` |
|
|
1048
|
-
| `delete(table, query)` |
|
|
1049
|
-
| `count(table, query)` |
|
|
1050
|
-
| `executeRawQuery(sql, params?)` |
|
|
1051
|
-
| `execute(sql, params?)` |
|
|
1052
|
-
| `increment(table, column, query, amount?)` |
|
|
1053
|
-
| `decrement(table, column, query, amount?)` |
|
|
1054
|
-
| `close()` / `disconnect()` |
|
|
1012
|
+
| `new DatabaseConnection(config?)` | Creates a connection (reads `.env` if config is omitted) |
|
|
1013
|
+
| `connect()` | Establishes the connection (called automatically) |
|
|
1014
|
+
| `beginTransaction()` | Starts a transaction |
|
|
1015
|
+
| `commit()` | Commits the transaction |
|
|
1016
|
+
| `rollback()` | Rolls back the transaction |
|
|
1017
|
+
| `transaction(callback)` | Runs in a transaction (auto commit/rollback) |
|
|
1018
|
+
| `select(table, query)` | Runs a SELECT |
|
|
1019
|
+
| `insert(table, data)` | Inserts a record |
|
|
1020
|
+
| `insertMany(table, data[])` | Inserts multiple records |
|
|
1021
|
+
| `update(table, data, query)` | Updates records |
|
|
1022
|
+
| `delete(table, query)` | Deletes records |
|
|
1023
|
+
| `count(table, query)` | Counts records |
|
|
1024
|
+
| `executeRawQuery(sql, params?)` | Raw query (normalised results) |
|
|
1025
|
+
| `execute(sql, params?)` | Raw query (native driver results) |
|
|
1026
|
+
| `increment(table, column, query, amount?)` | Atomic increment |
|
|
1027
|
+
| `decrement(table, column, query, amount?)` | Atomic decrement |
|
|
1028
|
+
| `close()` / `disconnect()` | Closes the connection |
|
|
1055
1029
|
| **Query Logging (static)** | |
|
|
1056
|
-
| `enableQueryLog()` |
|
|
1057
|
-
| `disableQueryLog()` |
|
|
1058
|
-
| `getQueryLog()` |
|
|
1059
|
-
| `flushQueryLog()` |
|
|
1060
|
-
| `isLogging()` |
|
|
1030
|
+
| `enableQueryLog()` | Enables query logging |
|
|
1031
|
+
| `disableQueryLog()` | Disables logging |
|
|
1032
|
+
| `getQueryLog()` | Returns the query log |
|
|
1033
|
+
| `flushQueryLog()` | Clears the log |
|
|
1034
|
+
| `isLogging()` | Checks whether logging is active |
|
|
1061
1035
|
|
|
1062
|
-
### Model (
|
|
1036
|
+
### Model (static methods)
|
|
1063
1037
|
|
|
1064
|
-
|
|
|
1038
|
+
| Method | Description |
|
|
1065
1039
|
|---------|-------------|
|
|
1066
|
-
| `setConnection(db)` |
|
|
1067
|
-
| `getConnection()` |
|
|
1068
|
-
| `setMorphMap(map)` |
|
|
1069
|
-
| `query()` |
|
|
1070
|
-
| `all()` |
|
|
1071
|
-
| `find(id)` |
|
|
1072
|
-
| `findOrFail(id)` |
|
|
1073
|
-
| `first()` |
|
|
1040
|
+
| `setConnection(db)` | Sets the default connection |
|
|
1041
|
+
| `getConnection()` | Gets the connection (v3.0.0+) |
|
|
1042
|
+
| `setMorphMap(map)` | Defines polymorphic mapping |
|
|
1043
|
+
| `query()` | Returns a QueryBuilder |
|
|
1044
|
+
| `all()` | All records |
|
|
1045
|
+
| `find(id)` | Finds by ID |
|
|
1046
|
+
| `findOrFail(id)` | Finds or throws an error |
|
|
1047
|
+
| `first()` | First record |
|
|
1074
1048
|
| `where(col, op?, val)` | Clause WHERE |
|
|
1075
1049
|
| `whereIn(col, vals)` | Clause WHERE IN |
|
|
1076
1050
|
| `whereNull(col)` | Clause WHERE NULL |
|
|
1077
1051
|
| `whereNotNull(col)` | Clause WHERE NOT NULL |
|
|
1078
|
-
| `create(attrs)` |
|
|
1079
|
-
| `insert(data)` |
|
|
1052
|
+
| `create(attrs)` | Creates and saves |
|
|
1053
|
+
| `insert(data)` | Raw insert |
|
|
1080
1054
|
| `update(attrs)` | Update bulk |
|
|
1081
|
-
| `updateById(id, attrs)` | Update
|
|
1082
|
-
| `updateAndFetchById(id, attrs, rels?)` | Update + fetch
|
|
1055
|
+
| `updateById(id, attrs)` | Update by ID |
|
|
1056
|
+
| `updateAndFetchById(id, attrs, rels?)` | Update + fetch with relationships |
|
|
1083
1057
|
| `delete()` | Delete bulk |
|
|
1084
1058
|
| `with(...rels)` | Eager loading |
|
|
1085
|
-
| `withHidden()` |
|
|
1086
|
-
| `withoutHidden(show?)` |
|
|
1087
|
-
| `orderBy(col, dir?)` |
|
|
1088
|
-
| `limit(n)` / `offset(n)` |
|
|
1059
|
+
| `withHidden()` | Includes hidden attributes |
|
|
1060
|
+
| `withoutHidden(show?)` | Control attribute visibility |
|
|
1061
|
+
| `orderBy(col, dir?)` | Sort |
|
|
1062
|
+
| `limit(n)` / `offset(n)` | Limit/Offset |
|
|
1089
1063
|
| `paginate(page, perPage)` | Pagination |
|
|
1090
|
-
| `count()` |
|
|
1064
|
+
| `count()` | Count |
|
|
1091
1065
|
| **Soft Deletes** | |
|
|
1092
|
-
| `withTrashed()` |
|
|
1093
|
-
| `onlyTrashed()` |
|
|
1066
|
+
| `withTrashed()` | Includes soft-deleted records |
|
|
1067
|
+
| `onlyTrashed()` | Only soft-deleted records |
|
|
1094
1068
|
| **Scopes** | |
|
|
1095
|
-
| `addGlobalScope(name, cb)` |
|
|
1096
|
-
| `removeGlobalScope(name)` |
|
|
1097
|
-
| `withoutGlobalScope(name)` |
|
|
1098
|
-
| `withoutGlobalScopes()` |
|
|
1069
|
+
| `addGlobalScope(name, cb)` | Adds a global scope |
|
|
1070
|
+
| `removeGlobalScope(name)` | Removes a scope |
|
|
1071
|
+
| `withoutGlobalScope(name)` | Query without one scope |
|
|
1072
|
+
| `withoutGlobalScopes()` | Query without all scopes |
|
|
1099
1073
|
| **Events** | |
|
|
1100
|
-
| `on(event, callback)` |
|
|
1101
|
-
| `creating(cb)` / `created(cb)` |
|
|
1102
|
-
| `updating(cb)` / `updated(cb)` |
|
|
1103
|
-
| `saving(cb)` / `saved(cb)` |
|
|
1104
|
-
| `deleting(cb)` / `deleted(cb)` |
|
|
1105
|
-
| `restoring(cb)` / `restored(cb)` |
|
|
1074
|
+
| `on(event, callback)` | Registers a listener |
|
|
1075
|
+
| `creating(cb)` / `created(cb)` | Creation events |
|
|
1076
|
+
| `updating(cb)` / `updated(cb)` | Update events |
|
|
1077
|
+
| `saving(cb)` / `saved(cb)` | Save events |
|
|
1078
|
+
| `deleting(cb)` / `deleted(cb)` | Delete events |
|
|
1079
|
+
| `restoring(cb)` / `restored(cb)` | Restore events |
|
|
1106
1080
|
|
|
1107
|
-
### Model (
|
|
1081
|
+
### Model (instance methods)
|
|
1108
1082
|
|
|
1109
|
-
|
|
|
1083
|
+
| Method | Description |
|
|
1110
1084
|
|---------|-------------|
|
|
1111
|
-
| `fill(attrs)` |
|
|
1112
|
-
| `setAttribute(key, val)` |
|
|
1113
|
-
| `getAttribute(key)` |
|
|
1114
|
-
| `save()` |
|
|
1115
|
-
| `destroy()` |
|
|
1116
|
-
| `load(...rels)` |
|
|
1117
|
-
| `getDirty()` |
|
|
1118
|
-
| `isDirty()` |
|
|
1119
|
-
| `toJSON()` |
|
|
1085
|
+
| `fill(attrs)` | Fills attributes |
|
|
1086
|
+
| `setAttribute(key, val)` | Sets an attribute |
|
|
1087
|
+
| `getAttribute(key)` | Gets an attribute |
|
|
1088
|
+
| `save()` | Save (insert or update) |
|
|
1089
|
+
| `destroy()` | Delete the instance (soft if enabled) |
|
|
1090
|
+
| `load(...rels)` | Load relationships |
|
|
1091
|
+
| `getDirty()` | Modified attributes |
|
|
1092
|
+
| `isDirty()` | Has been modified? |
|
|
1093
|
+
| `toJSON()` | Convert to plain object |
|
|
1120
1094
|
| **Soft Deletes** | |
|
|
1121
|
-
| `trashed()` |
|
|
1122
|
-
| `restore()` |
|
|
1123
|
-
| `forceDelete()` |
|
|
1095
|
+
| `trashed()` | Is deleted? |
|
|
1096
|
+
| `restore()` | Restore the model |
|
|
1097
|
+
| `forceDelete()` | Permanent deletion |
|
|
1124
1098
|
| **Validation** | |
|
|
1125
|
-
| `validate()` |
|
|
1126
|
-
| `validateOrFail()` |
|
|
1099
|
+
| `validate()` | Validate against rules |
|
|
1100
|
+
| `validateOrFail()` | Validate or throw error |
|
|
1127
1101
|
|
|
1128
1102
|
### QueryBuilder
|
|
1129
1103
|
|
|
1130
|
-
|
|
|
1104
|
+
| Method | Description |
|
|
1131
1105
|
|---------|-------------|
|
|
1132
|
-
| `select(...cols)` / `columns([...])` |
|
|
1106
|
+
| `select(...cols)` / `columns([...])` | Column selection |
|
|
1133
1107
|
| `distinct()` | SELECT DISTINCT |
|
|
1134
1108
|
| `where(col, op?, val)` | Clause WHERE |
|
|
1135
1109
|
| `whereIn(col, vals)` | WHERE IN |
|
|
@@ -1139,70 +1113,70 @@ if (db.isLogging()) {
|
|
|
1139
1113
|
| `orWhere(col, op?, val)` | OR WHERE |
|
|
1140
1114
|
| `whereBetween(col, [min, max])` | WHERE BETWEEN |
|
|
1141
1115
|
| `whereLike(col, pattern)` | WHERE LIKE |
|
|
1142
|
-
| `whereHas(rel, cb?)` |
|
|
1143
|
-
| `has(rel, op?, count)` |
|
|
1144
|
-
| `whereDoesntHave(rel)` | Absence
|
|
1145
|
-
| `orderBy(col, dir?)` / `ordrer(...)` |
|
|
1146
|
-
| `limit(n)` / `take(n)` |
|
|
1116
|
+
| `whereHas(rel, cb?)` | Filter by existence of relation |
|
|
1117
|
+
| `has(rel, op?, count)` | Relational existence |
|
|
1118
|
+
| `whereDoesntHave(rel)` | Absence of relation |
|
|
1119
|
+
| `orderBy(col, dir?)` / `ordrer(...)` | Sort |
|
|
1120
|
+
| `limit(n)` / `take(n)` | Limit |
|
|
1147
1121
|
| `offset(n)` / `skip(n)` | Offset |
|
|
1148
1122
|
| `groupBy(...cols)` | GROUP BY |
|
|
1149
1123
|
| `having(col, op, val)` | HAVING |
|
|
1150
1124
|
| `join(table, first, op?, second)` | INNER JOIN |
|
|
1151
1125
|
| `leftJoin(table, first, op?, second)` | LEFT JOIN |
|
|
1152
1126
|
| `with(...rels)` | Eager loading |
|
|
1153
|
-
| `withCount(rels)` |
|
|
1154
|
-
| `withTrashed()` |
|
|
1155
|
-
| `onlyTrashed()` |
|
|
1156
|
-
| `withoutGlobalScope(name)` |
|
|
1157
|
-
| `withoutGlobalScopes()` |
|
|
1158
|
-
| `get()` |
|
|
1159
|
-
| `first()` |
|
|
1127
|
+
| `withCount(rels)` | Adds `{rel}_count` |
|
|
1128
|
+
| `withTrashed()` | Includes soft-deleted records |
|
|
1129
|
+
| `onlyTrashed()` | Only soft-deleted records |
|
|
1130
|
+
| `withoutGlobalScope(name)` | Without one global scope |
|
|
1131
|
+
| `withoutGlobalScopes()` | Without all global scopes |
|
|
1132
|
+
| `get()` | Runs and returns all |
|
|
1133
|
+
| `first()` | First result |
|
|
1160
1134
|
| `firstOrFail()` | Premier ou erreur |
|
|
1161
1135
|
| `paginate(page, perPage)` | Pagination |
|
|
1162
1136
|
| `count()` | Compte |
|
|
1163
|
-
| `exists()` |
|
|
1137
|
+
| `exists()` | Checks existence |
|
|
1164
1138
|
| `insert(data)` | Insert |
|
|
1165
1139
|
| `update(attrs)` | Update |
|
|
1166
1140
|
| `updateAndFetch(attrs, rels?)` | Update + fetch |
|
|
1167
1141
|
| `delete()` | Delete |
|
|
1168
|
-
| `increment(col, amount?)` |
|
|
1169
|
-
| `decrement(col, amount?)` |
|
|
1170
|
-
| `clone()` |
|
|
1142
|
+
| `increment(col, amount?)` | Atomic increment |
|
|
1143
|
+
| `decrement(col, amount?)` | Atomic decrement |
|
|
1144
|
+
| `clone()` | Clones the query builder |
|
|
1171
1145
|
|
|
1172
|
-
## 🛠️
|
|
1146
|
+
## 🛠️ CLI tools
|
|
1173
1147
|
|
|
1174
1148
|
### outlet-init
|
|
1175
1149
|
|
|
1176
|
-
|
|
1150
|
+
Initialises a new project with database configuration.
|
|
1177
1151
|
|
|
1178
1152
|
```bash
|
|
1179
1153
|
outlet-init
|
|
1180
1154
|
```
|
|
1181
1155
|
|
|
1182
|
-
|
|
1183
|
-
-
|
|
1184
|
-
-
|
|
1185
|
-
-
|
|
1186
|
-
-
|
|
1156
|
+
Generates:
|
|
1157
|
+
- Configuration file `database/config.js`
|
|
1158
|
+
- `.env` file with settings
|
|
1159
|
+
- Example model
|
|
1160
|
+
- Usage file
|
|
1187
1161
|
|
|
1188
1162
|
### outlet-migrate
|
|
1189
1163
|
|
|
1190
|
-
|
|
1164
|
+
complete migration system.
|
|
1191
1165
|
|
|
1192
1166
|
```bash
|
|
1193
|
-
#
|
|
1167
|
+
# Create a migration
|
|
1194
1168
|
outlet-migrate make create_users_table
|
|
1195
1169
|
|
|
1196
|
-
#
|
|
1170
|
+
# Run migrations
|
|
1197
1171
|
outlet-migrate migrate
|
|
1198
1172
|
|
|
1199
|
-
#
|
|
1173
|
+
# See migration status
|
|
1200
1174
|
outlet-migrate status
|
|
1201
1175
|
|
|
1202
|
-
#
|
|
1176
|
+
# Roll back the latest migration
|
|
1203
1177
|
outlet-migrate rollback --steps 1
|
|
1204
1178
|
|
|
1205
|
-
# Reset
|
|
1179
|
+
# Reset all migrations
|
|
1206
1180
|
outlet-migrate reset --yes
|
|
1207
1181
|
|
|
1208
1182
|
# Refresh (reset + migrate)
|
|
@@ -1212,52 +1186,52 @@ outlet-migrate refresh --yes
|
|
|
1212
1186
|
outlet-migrate fresh --yes
|
|
1213
1187
|
```
|
|
1214
1188
|
|
|
1215
|
-
**
|
|
1189
|
+
**Migration Features:**
|
|
1216
1190
|
|
|
1217
|
-
- ✅
|
|
1218
|
-
- ✅
|
|
1219
|
-
- ✅
|
|
1220
|
-
- ✅
|
|
1191
|
+
- ✅ Creation and management of migrations (create, alter, drop tables)
|
|
1192
|
+
- ✅ Column types: id, string, text, integer, boolean, date, datetime, timestamp, decimal, float, json, enum, uuid, foreignId
|
|
1193
|
+
- ✅ Modifiers: nullable, default, unique, index, unsigned, autoIncrement, comment, after, first
|
|
1194
|
+
- ✅ Foreign keys: foreign(), constrained(), onDelete(), onUpdate(), CASCADE
|
|
1221
1195
|
- ✅ Index: index(), unique(), fullText()
|
|
1222
1196
|
- ✅ Manipulation: renameColumn(), dropColumn(), dropTimestamps()
|
|
1223
|
-
- ✅
|
|
1224
|
-
- ✅ Batch tracking:
|
|
1225
|
-
- ✅ SQL
|
|
1197
|
+
- ✅ Reversible migrations: up() and down() methods
|
|
1198
|
+
- ✅ Batch tracking: Precise rollback by batch
|
|
1199
|
+
- ✅ Custom SQL: execute() for advanced commands
|
|
1226
1200
|
|
|
1227
1201
|
### outlet-convert
|
|
1228
1202
|
|
|
1229
|
-
|
|
1203
|
+
Converts SQL schemas into ORM models.
|
|
1230
1204
|
|
|
1231
1205
|
```bash
|
|
1232
1206
|
outlet-convert
|
|
1233
1207
|
```
|
|
1234
1208
|
|
|
1235
1209
|
**Options:**
|
|
1236
|
-
1.
|
|
1237
|
-
2.
|
|
1210
|
+
1. From a local SQL file
|
|
1211
|
+
2. From a connected database
|
|
1238
1212
|
|
|
1239
|
-
**
|
|
1240
|
-
- ✅
|
|
1241
|
-
- ✅
|
|
1242
|
-
- ✅
|
|
1243
|
-
- ✅
|
|
1244
|
-
- ✅
|
|
1245
|
-
- ✅
|
|
1213
|
+
**Features:**
|
|
1214
|
+
- ✅ Automatic type and cast detection
|
|
1215
|
+
- ✅ Automatic generation of ALL relationships (belongsTo, hasMany, hasOne, belongsToMany)
|
|
1216
|
+
- ✅ Recursive relationships (auto-relationships)
|
|
1217
|
+
- ✅ Detection of sensitive fields (password, token, etc.)
|
|
1218
|
+
- ✅ Automatic timestamps support
|
|
1219
|
+
- ✅ Class names converted to PascalCase
|
|
1246
1220
|
|
|
1247
1221
|
## 📚 Documentation
|
|
1248
1222
|
|
|
1249
|
-
- [Guide
|
|
1250
|
-
- [Conversion
|
|
1251
|
-
- [
|
|
1252
|
-
- [
|
|
1223
|
+
- [Migrations Guide](docs/MIGRATIONS.md)
|
|
1224
|
+
- [SQL Conversion](docs/SQL_CONVERSION.md)
|
|
1225
|
+
- [Relation Detection](docs/RELATIONS_DETECTION.md)
|
|
1226
|
+
- [Quick Start Guide](docs/QUICKSTART.md)
|
|
1253
1227
|
- [Architecture](docs/ARCHITECTURE.md)
|
|
1254
1228
|
- [**TypeScript (complet)**](docs/TYPESCRIPT.md)
|
|
1255
1229
|
|
|
1256
1230
|
## 📘 TypeScript Support
|
|
1257
1231
|
|
|
1258
|
-
Outlet ORM v4.0.0
|
|
1232
|
+
Outlet ORM v4.0.0 includes complete TypeScript definitions with support for **generics for typed model attributes**.
|
|
1259
1233
|
|
|
1260
|
-
###
|
|
1234
|
+
### Typed models
|
|
1261
1235
|
|
|
1262
1236
|
```typescript
|
|
1263
1237
|
import { Model, HasManyRelation } from 'outlet-orm';
|
|
@@ -1281,11 +1255,11 @@ class User extends Model<UserAttributes> {
|
|
|
1281
1255
|
|
|
1282
1256
|
// Type-safe getAttribute/setAttribute
|
|
1283
1257
|
const user = await User.find(1);
|
|
1284
|
-
const name: string = user.getAttribute('name'); // ✅
|
|
1258
|
+
const name: string = user.getAttribute('name'); // ✅ Inferred type
|
|
1285
1259
|
const role: 'admin' | 'user' = user.getAttribute('role');
|
|
1286
1260
|
```
|
|
1287
1261
|
|
|
1288
|
-
### Migrations
|
|
1262
|
+
### Migrations typedes
|
|
1289
1263
|
|
|
1290
1264
|
```typescript
|
|
1291
1265
|
import { MigrationInterface, Schema, TableBuilder } from 'outlet-orm';
|
|
@@ -1310,16 +1284,16 @@ export const migration: MigrationInterface = {
|
|
|
1310
1284
|
|
|
1311
1285
|
📖 [Guide TypeScript complet](docs/TYPESCRIPT.md)
|
|
1312
1286
|
|
|
1313
|
-
## 🤝
|
|
1287
|
+
## 🤝 Contributions
|
|
1314
1288
|
|
|
1315
|
-
|
|
1289
|
+
Contributions are welcome! Feel free to open an issue or pull request.
|
|
1316
1290
|
|
|
1317
|
-
|
|
1291
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines.
|
|
1318
1292
|
|
|
1319
1293
|
## 📄 Licence
|
|
1320
1294
|
|
|
1321
|
-
MIT -
|
|
1295
|
+
MIT - See [LICENSE](LICENSE) for details.
|
|
1322
1296
|
|
|
1323
1297
|
---
|
|
1324
1298
|
|
|
1325
|
-
|
|
1299
|
+
Created by [omgbwa-yasse](https://github.com/omgbwa-yasse)
|