outlet-orm 6.5.0 → 7.0.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.
@@ -0,0 +1,427 @@
1
+ # Outlet ORM - Models & CRUD
2
+
3
+ [← Back to Index](SKILL.md) | [Next: Queries →](QUERIES.md)
4
+
5
+ > 📘 **TypeScript** : Use`Model<TAttributes>`for typed attributes. See [TYPESCRIPT.md](TYPESCRIPT.md#generic-model-v400)
6
+
7
+ ---
8
+
9
+ ## Recommended Project Structure (Layered Architecture)
10
+
11
+ > 🔐 **Security**: Use`hidden`for sensitive fields,`fillable`for mass assignment protection.
12
+
13
+ ```
14
+ my-project/
15
+ ├── .env # ⚠️ NEVER commit
16
+ ├── src/
17
+ │ ├── controllers/ # 🎮 HTTP handling only
18
+ │ ├── services/ # ⚙️ Business logic
19
+ │ ├── repositories/ # 📦 Data access layer
20
+ │ ├── models/ # 📊 Your Model classes
21
+ │ │ ├── User.js # hidden: ['password']
22
+ │ │ └── Post.js
23
+ │ ├── middlewares/ # 🔒 Auth, validation
24
+ │ ├── config/ # 🔒 Configuration
25
+ │ └── utils/ # 🔒 Hash, tokens
26
+ ├── database/
27
+ │ └── migrations/
28
+ ├── public/ # ✅ Only public folder
29
+ └── tests/
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Model Definition
35
+
36
+ ### Complete Model Example
37
+
38
+ ```javascript
39
+ const { Model } = require('outlet-orm');
40
+
41
+ // Define related models first
42
+ class Post extends Model {
43
+ static table = 'posts';
44
+ }
45
+
46
+ class Profile extends Model {
47
+ static table = 'profiles';
48
+ }
49
+
50
+ class User extends Model {
51
+ // Required: Table name
52
+ static table = 'users';
53
+
54
+ // Optional: Primary key (default: 'id')
55
+ static primaryKey = 'id';
56
+
57
+ // Auto-manage created_at/updated_at
58
+ static timestamps = true;
59
+
60
+ // Enable soft deletes (deleted_at)
61
+ static softDeletes = true;
62
+
63
+ // Mass assignable fields
64
+ static fillable = ['name', 'email', 'password', 'role'];
65
+
66
+ // Hidden from JSON output
67
+ static hidden = ['password', 'remember_token'];
68
+
69
+ // Auto type casting
70
+ static casts = {
71
+ id: 'int',
72
+ email_verified: 'boolean',
73
+ preferences: 'json',
74
+ birthday: 'date',
75
+ balance: 'float'
76
+ };
77
+
78
+ // Validation rules
79
+ static rules = {
80
+ name: 'required|string|min:2|max:100',
81
+ email: 'required|email',
82
+ password: 'required|min:8',
83
+ role: 'in:admin,user,guest'
84
+ };
85
+
86
+ // Relationships
87
+ posts() {
88
+ return this.hasMany(Post, 'user_id');
89
+ }
90
+
91
+ profile() {
92
+ return this.hasOne(Profile, 'user_id');
93
+ }
94
+ }
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Static Properties
100
+
101
+ | Property | Type | Default | Description |
102
+ |----------|------|---------|-------------|
103
+ |`table`| string | **required** | Table name |
104
+ |`primaryKey`| string |`'id'`| Primary key column |
105
+ |`timestamps`| boolean |`true`| Auto-manage created_at/updated_at |
106
+ |`softDeletes`| boolean |`false`| Enable soft delete |
107
+ |`DELETED_AT`| string |`'deleted_at'`| Soft delete column name |
108
+ |`fillable`| array |`[]`| Mass assignable fields |
109
+ |`hidden`| array |`[]`| Hidden from JSON |
110
+ |`casts`| object |`{}`| Type casting definitions |
111
+ |`rules`| object |`{}`| Validation rules |
112
+ |`connection`| object |`null`| Custom DB connection |
113
+
114
+ ---
115
+
116
+ ## Type Casting
117
+
118
+ ```javascript
119
+ class User extends Model {
120
+ static casts = {
121
+ id: 'int', // or 'integer'
122
+ age: 'integer',
123
+ balance: 'float', // or 'double'
124
+ is_active: 'boolean', // or 'bool'
125
+ metadata: 'json', // Parse as JSON object
126
+ settings: 'array', // Parse as JSON array
127
+ birthday: 'date' // Convert to Date
128
+ };
129
+ }
130
+
131
+ const user = await User.find(1);
132
+ console.log(typeof user.getAttribute('age')); // 'number'
133
+ console.log(typeof user.getAttribute('is_active')); // 'boolean'
134
+ console.log(user.getAttribute('metadata')); // Object
135
+ ```
136
+
137
+ ### Cast Types
138
+
139
+ | Type | Description |
140
+ |------|-------------|
141
+ |`int`/`integer`| Integer number |
142
+ |`float`/`double`| Floating point number |
143
+ |`boolean`/`bool`| Boolean value |
144
+ |`json`| Parse JSON to object |
145
+ |`array`| Parse JSON to array |
146
+ |`date`| Convert to Date object |
147
+
148
+ ---
149
+
150
+ ## CRUD Operations
151
+
152
+ ### Create
153
+
154
+ ```javascript
155
+ // Method 1: create() - Create and save
156
+ const user = await User.create({
157
+ name: 'John Doe',
158
+ email: 'john@example.com',
159
+ password: 'secret123'
160
+ });
161
+
162
+ // Method 2: new + save()
163
+ const user = new User({
164
+ name: 'Jane Doe',
165
+ email: 'jane@example.com'
166
+ });
167
+ user.setAttribute('password', 'secret456');
168
+ await user.save();
169
+
170
+ // Method 3: Raw insert (no model instance returned)
171
+ await User.insert({ name: 'Bob', email: 'bob@example.com' });
172
+
173
+ // Insert multiple
174
+ await User.insert([
175
+ { name: 'User 1', email: 'user1@example.com' },
176
+ { name: 'User 2', email: 'user2@example.com' }
177
+ ]);
178
+ ```
179
+
180
+ ### Read
181
+
182
+ ```javascript
183
+ // All records
184
+ const users = await User.all();
185
+
186
+ // Find by ID
187
+ const user = await User.find(1);
188
+ const user = await User.findOrFail(1); // Throws if not found
189
+
190
+ // First result
191
+ const firstUser = await User.first();
192
+
193
+ // With conditions
194
+ const activeUsers = await User
195
+ .where('status', 'active')
196
+ .where('age', '>', 18)
197
+ .get();
198
+
199
+ // With relationships (Eager Loading)
200
+ const usersWithPosts = await User
201
+ .with('posts', 'profile')
202
+ .get();
203
+
204
+ // Order and limit
205
+ const recentUsers = await User
206
+ .orderBy('created_at', 'desc')
207
+ .limit(10)
208
+ .get();
209
+ ```
210
+
211
+ ### Update
212
+
213
+ ```javascript
214
+ // Instance update
215
+ const user = await User.find(1);
216
+ user.setAttribute('name', 'Updated Name');
217
+ await user.save();
218
+
219
+ // Bulk update
220
+ await User
221
+ .where('status', 'pending')
222
+ .update({ status: 'active' });
223
+
224
+ // Update and fetch (like Prisma)
225
+ const updated = await User
226
+ .where('id', 1)
227
+ .updateAndFetch({ name: 'Neo' }, ['profile', 'posts']);
228
+
229
+ // Helpers by ID
230
+ const user = await User.updateAndFetchById(1, { name: 'Trinity' }, ['profile']);
231
+ await User.updateById(2, { status: 'active' });
232
+ ```
233
+
234
+ ### Delete
235
+
236
+ ```javascript
237
+ // Instance delete
238
+ const user = await User.find(1);
239
+ await user.destroy(); // Soft delete if enabled
240
+
241
+ // Bulk delete
242
+ await User
243
+ .where('status', 'banned')
244
+ .delete();
245
+
246
+ // Force delete (permanent, even with soft deletes)
247
+ await user.forceDelete();
248
+ ```
249
+
250
+ ---
251
+
252
+ ## Attribute Methods
253
+
254
+ ### Getting Attributes
255
+
256
+ ```javascript
257
+ const user = await User.find(1);
258
+
259
+ // Get single attribute
260
+ const name = user.getAttribute('name');
261
+
262
+ // Get all attributes as object
263
+ const attrs = user.toJSON();
264
+
265
+ // Check if modified
266
+ const isDirty = user.isDirty();
267
+ const dirty = user.getDirty(); // Get modified attributes
268
+ ```
269
+
270
+ ### Setting Attributes
271
+
272
+ ```javascript
273
+ const user = new User();
274
+
275
+ // Set single attribute
276
+ user.setAttribute('name', 'John');
277
+
278
+ // Fill multiple attributes
279
+ user.fill({
280
+ name: 'John',
281
+ email: 'john@example.com'
282
+ });
283
+ ```
284
+
285
+ ---
286
+
287
+ ## Hidden Attributes
288
+
289
+ ```javascript
290
+ class User extends Model {
291
+ static hidden = ['password', 'secret_token'];
292
+ }
293
+
294
+ // Normal query - hidden fields excluded
295
+ const user = await User.find(1);
296
+ console.log(user.toJSON()); // password excluded
297
+
298
+ // Include hidden fields
299
+ const user = await User.withHidden().where('email', email).first();
300
+ console.log(user.toJSON()); // password included
301
+
302
+ // Control with boolean
303
+ const user = await User.withoutHidden(true).first(); // true = show
304
+ const user = await User.withoutHidden(false).first(); // false = hide
305
+
306
+ // Use case: Authentication
307
+ const user = await User.withHidden().where('email', email).first();
308
+ if (user && await bcrypt.compare(password, user.getAttribute('password'))) {
309
+ // Authentication successful
310
+ }
311
+ ```
312
+
313
+ ---
314
+
315
+ ## Timestamps
316
+
317
+ ```javascript
318
+ // Enabled by default
319
+ class User extends Model {
320
+ static timestamps = true; // created_at, updated_at
321
+ }
322
+
323
+ // Disable timestamps
324
+ class Log extends Model {
325
+ static timestamps = false;
326
+ }
327
+
328
+ // Auto-managed on create/update
329
+ const user = await User.create({ name: 'John' });
330
+ console.log(user.getAttribute('created_at')); // Current date
331
+
332
+ user.setAttribute('name', 'Jane');
333
+ await user.save();
334
+ console.log(user.getAttribute('updated_at')); // Updated automatically
335
+ ```
336
+
337
+ ---
338
+
339
+ ## Mass Assignment Protection
340
+
341
+ ```javascript
342
+ class User extends Model {
343
+ static fillable = ['name', 'email', 'age'];
344
+ }
345
+
346
+ // OK - all fields are in fillable
347
+ const user = await User.create({
348
+ name: 'John',
349
+ email: 'john@example.com',
350
+ age: 30
351
+ });
352
+
353
+ // 'role' will be IGNORED (not in fillable)
354
+ const user2 = await User.create({
355
+ name: 'Jane',
356
+ role: 'admin' // Ignored!
357
+ });
358
+ ```
359
+
360
+ ---
361
+
362
+ ## Multiple Connections
363
+
364
+ ```javascript
365
+ const { DatabaseConnection, Model } = require('outlet-orm');
366
+
367
+ // Create connections
368
+ const mysqlDb = new DatabaseConnection({
369
+ driver: 'mysql',
370
+ host: 'localhost',
371
+ database: 'app_db'
372
+ });
373
+
374
+ const postgresDb = new DatabaseConnection({
375
+ driver: 'postgres',
376
+ host: 'localhost',
377
+ database: 'analytics_db'
378
+ });
379
+
380
+ // Assign to models
381
+ class User extends Model {
382
+ static table = 'users';
383
+ static connection = mysqlDb;
384
+ }
385
+
386
+ class Analytics extends Model {
387
+ static table = 'events';
388
+ static connection = postgresDb;
389
+ }
390
+
391
+ // Close when done
392
+ await mysqlDb.close();
393
+ await postgresDb.close();
394
+ ```
395
+
396
+ ---
397
+
398
+ ## Environment Variables
399
+
400
+ Configure via`.env`file (auto-loaded):
401
+
402
+ ```env
403
+ DB_DRIVER=mysql
404
+ DB_HOST=localhost
405
+ DB_PORT=3306
406
+ DB_DATABASE=myapp
407
+ DB_USER=root
408
+ DB_PASSWORD=secret
409
+ ```
410
+
411
+ | Variable | Description | Default |
412
+ |----------|-------------|---------|
413
+ |`DB_DRIVER`|`mysql`,`postgres`,`sqlite`|`mysql`|
414
+ |`DB_HOST`| Database host |`localhost`|
415
+ |`DB_PORT`| Connection port | Driver default |
416
+ |`DB_USER`/`DB_USERNAME`| Username | - |
417
+ |`DB_PASSWORD`| Password | - |
418
+ |`DB_DATABASE`/`DB_NAME`| Database name | - |
419
+ |`DB_FILE`/`SQLITE_DB`| SQLite file path |`:memory:`|
420
+
421
+ ---
422
+
423
+ ## Next Steps
424
+
425
+ - [Query Builder →](QUERIES.md)
426
+ - [Relationships →](RELATIONS.md)
427
+ - [Advanced Features →](ADVANCED.md)