outlet-orm 9.0.2 → 11.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +103 -5
  2. package/bin/reverse.js +0 -1
  3. package/package.json +1 -1
  4. package/skills/outlet-orm/ADVANCED.md +29 -0
  5. package/skills/outlet-orm/AI.md +23 -23
  6. package/skills/outlet-orm/API.md +33 -3
  7. package/skills/outlet-orm/MODELS.md +144 -2
  8. package/skills/outlet-orm/QUERIES.md +136 -3
  9. package/skills/outlet-orm/RELATIONS.md +44 -0
  10. package/skills/outlet-orm/SEEDS.md +2 -2
  11. package/skills/outlet-orm/SKILL.md +8 -6
  12. package/skills/outlet-orm/TYPESCRIPT.md +98 -0
  13. package/src/AI/{AiBridgeManager.js → AIManager.js} +61 -61
  14. package/src/AI/AIPromptEnhancer.js +1 -1
  15. package/src/AI/AIQueryBuilder.js +4 -4
  16. package/src/AI/AIQueryOptimizer.js +3 -3
  17. package/src/AI/AISeeder.js +1 -1
  18. package/src/AI/Builders/TextBuilder.js +2 -2
  19. package/src/AI/Contracts/AudioProviderContract.js +2 -2
  20. package/src/AI/Contracts/ChatProviderContract.js +3 -2
  21. package/src/AI/Contracts/EmbeddingsProviderContract.js +1 -1
  22. package/src/AI/Contracts/ImageProviderContract.js +1 -1
  23. package/src/AI/Contracts/ModelsProviderContract.js +1 -1
  24. package/src/AI/Contracts/ToolContract.js +1 -1
  25. package/src/AI/Facades/{AiBridge.js → AI.js} +11 -11
  26. package/src/AI/MCPServer.js +16 -16
  27. package/src/AI/Providers/CustomOpenAIProvider.js +0 -2
  28. package/src/AI/Providers/GeminiProvider.js +2 -2
  29. package/src/AI/Providers/OpenAIProvider.js +0 -5
  30. package/src/AI/Support/DocumentAttachmentMapper.js +37 -37
  31. package/src/AI/Support/FileSecurity.js +1 -1
  32. package/src/AI/Support/ToolChatRunner.js +1 -1
  33. package/src/Backup/BackupManager.js +6 -6
  34. package/src/Backup/BackupScheduler.js +1 -1
  35. package/src/Backup/BackupSocketServer.js +2 -2
  36. package/src/DatabaseConnection.js +51 -0
  37. package/src/Model.js +245 -5
  38. package/src/QueryBuilder.js +191 -0
  39. package/src/Relations/HasOneRelation.js +114 -114
  40. package/src/Relations/HasOneThroughRelation.js +105 -105
  41. package/src/Relations/MorphOneRelation.js +4 -2
  42. package/src/Relations/Relation.js +35 -0
  43. package/src/index.js +6 -6
  44. package/types/index.d.ts +78 -12
package/README.md CHANGED
@@ -7,6 +7,72 @@ A JavaScript ORM inspired by Laravel Eloquent for Node.js with support for MySQL
7
7
 
8
8
  📚 **[Complete documentation available in `/docs`](./docs/INDEX.md)**
9
9
 
10
+ ## Table of Contents
11
+
12
+ - [✅ Prerequisites and compatibility](#prerequisites-and-compatibility)
13
+ - [🚀 Installation](#installation)
14
+ - [Install the database driver](#install-the-database-driver)
15
+ - [📁 Recommended Project Structure](#recommended-project-structure)
16
+ - [🏗️ Architecture Flow](#architecture-flow)
17
+ - [📋 Role of each layer](#role-of-each-layer)
18
+ - [✅ Benefits of this architecture](#benefits-of-this-architecture)
19
+ - [📝 Example workflow](#example-workflow)
20
+ - [✨ Key features](#key-features)
21
+ - [⚡ Quick Start](#quick-start)
22
+ - [Project Initialisation](#project-initialisation)
23
+ - [🌱 Quick Seeding](#quick-seeding)
24
+ - [📖 Usage](#usage)
25
+ - [Connection configuration](#connection-configuration)
26
+ - [Importation](#importation)
27
+ - [Define a model](#define-a-model)
28
+ - [CRUD operations](#crud-operations)
29
+ - [Query Builder](#query-builder)
30
+ - [Relationship filters](#relationship-filters)
31
+ - [🔗 Relations](#relations)
32
+ - [One to One (hasOne)](#one-to-one-hasone)
33
+ - [One to Many (hasMany)](#one-to-many-hasmany)
34
+ - [Belongs To (belongsTo)](#belongs-to-belongsto)
35
+ - [Many to Many (belongsToMany)](#many-to-many-belongstomany)
36
+ - [Has Many Through (hasManyThrough)](#has-many-through-hasmanythrough)
37
+ - [Has One Through (hasOneThrough)](#has-one-through-hasonethrough)
38
+ - [Polymorphic relationships](#polymorphic-relationships)
39
+ - [Eager Loading](#eager-loading)
40
+ - [🎭 Attributs](#attributs)
41
+ - [Casts](#casts)
42
+ - [Hidden attributes](#hidden-attributes)
43
+ - [Timestamps](#timestamps)
44
+ - [🔄 Transactions](#transactions)
45
+ - [🗑️ Soft Deletes](#soft-deletes)
46
+ - [🔬 Scopes](#scopes)
47
+ - [Scopes Globaux](#scopes-globaux)
48
+ - [📣 Events / Hooks](#events-hooks)
49
+ - [✅ Validation](#validation)
50
+ - [Available rules](#available-rules)
51
+ - [📊 Query Logging](#query-logging)
52
+ - [📝 API Reference](#api-reference)
53
+ - [DatabaseConnection](#databaseconnection)
54
+ - [Model (static methods)](#model-static-methods)
55
+ - [Model (instance methods)](#model-instance-methods)
56
+ - [QueryBuilder](#querybuilder)
57
+ - [🛠️ CLI tools](#cli-tools)
58
+ - [outlet-init](#outlet-init)
59
+ - [outlet-migrate](#outlet-migrate)
60
+ - [outlet-convert](#outlet-convert)
61
+ - [🤖 AI Integration](#ai-integration)
62
+ - [AI — Multi-Provider LLM Abstraction](#ai-multi-provider-llm-abstraction)
63
+ - [AI Query Builder — Natural Language → SQL](#ai-query-builder-natural-language-sql)
64
+ - [AI Seeder — Realistic Data Generation](#ai-seeder-realistic-data-generation)
65
+ - [AI Query Optimizer](#ai-query-optimizer)
66
+ - [MCP Server — AI Agent Integration](#mcp-server-ai-agent-integration)
67
+ - [📚 Documentation](#documentation)
68
+ - [📘 TypeScript Support](#typescript-support)
69
+ - [Typed models](#typed-models)
70
+ - [Migrations typedes](#migrations-typedes)
71
+ - [🤝 Contributions](#contributions)
72
+ - [📄 Licence](#licence)
73
+
74
+ ---
75
+
10
76
  ## ✅ Prerequisites and compatibility
11
77
 
12
78
  - Node.js >= 18 (recommended/required)
@@ -197,7 +263,7 @@ async store(req, res) {
197
263
  - **Raw queries**: `executeRawQuery()` and `execute()` (native driver results)
198
264
  - **Complete Migrations** (create/alter/drop, index, foreign keys, batch tracking)
199
265
  - **Database Backup** (v6.0.0): full/partial/journal backups, recurring scheduler, AES-256-GCM encryption, TCP daemon + remote client, automatic restore
200
- - **🤖 AiBridge** (v8.0.0): Multi-provider LLM abstraction — chat, stream, embeddings, images, TTS, STT with 9+ providers
266
+ - **🤖 AI** (v8.0.0): Multi-provider LLM abstraction — chat, stream, embeddings, images, TTS, STT with 9+ providers
201
267
  - **🤖 AI Query Builder** (v8.0.0): Natural language → SQL with schema introspection
202
268
  - **🤖 AI Seeder** (v8.0.0): LLM-powered realistic, domain-specific data generation
203
269
  - **🤖 AI Query Optimizer** (v8.0.0): SQL analysis, optimization, and index recommendations
@@ -208,6 +274,17 @@ async store(req, res) {
208
274
  - **`.env` configuration** (loaded automatically)
209
275
  - **Multi-database**: MySQL, PostgreSQL, and SQLite
210
276
  - **Complete TypeScript types** with Generic Model and typed Schema Builder (v4.0.0+)
277
+ - **🆕 Property-style attribute access** (v11.0.0): `user.name` instead of `user.getAttribute('name')`
278
+ - **🆕 Computed Appends** (v11.0.0): `static appends` for virtual attributes auto-included in `toJSON()`
279
+ - **🆕 Model Utility Methods** (v11.0.0): `fresh()`, `refresh()`, `replicate()`, `is()` / `isNot()`, `only()` / `except()`
280
+ - **🆕 Instance-Level Visibility** (v11.0.0): `makeVisible()` / `makeHidden()` per-instance control
281
+ - **🆕 Change Tracking** (v11.0.0): `wasChanged()` / `getChanges()` after `save()`
282
+ - **🆕 Batch Processing** (v11.0.0): `chunk(size, callback)` for memory-efficient iteration
283
+ - **🆕 Conditional Queries** (v11.0.0): `when(condition, callback)` and `tap(callback)`
284
+ - **🆕 Query Debugging** (v11.0.0): `dd()` dumps SQL + bindings to console and throws
285
+ - **🆕 Fluent Local Scopes** (v11.0.0): `static scopeActive(query)` → `User.query().active()`
286
+ - **🆕 Relation Defaults** (v11.0.0): `withDefault()` on HasOne/MorphOne/HasOneThrough
287
+ - **🆕 Aggregates & Pluck** (v11.0.0): `sum()`, `avg()`, `min()`, `max()`, `value()`, keyed `pluck(col, key)`
211
288
 
212
289
  ## ⚡ Quick Start
213
290
 
@@ -1034,6 +1111,7 @@ if (db.isLogging()) {
1034
1111
  | `execute(sql, params?)` | Raw query (native driver results) |
1035
1112
  | `increment(table, column, query, amount?)` | Atomic increment |
1036
1113
  | `decrement(table, column, query, amount?)` | Atomic decrement |
1114
+ | `aggregate(table, fn, column, query)` | Execute aggregate function (SUM/AVG/MIN/MAX) |
1037
1115
  | `close()` / `disconnect()` | Closes the connection |
1038
1116
  | **Query Logging (static)** | |
1039
1117
  | `enableQueryLog()` | Enables query logging |
@@ -1100,6 +1178,17 @@ if (db.isLogging()) {
1100
1178
  | `getDirty()` | Modified attributes |
1101
1179
  | `isDirty()` | Has been modified? |
1102
1180
  | `toJSON()` | Convert to plain object |
1181
+ | `fresh()` | Reload from DB (new instance) |
1182
+ | `refresh()` | Reload in place |
1183
+ | `replicate()` | Clone without ID/timestamps |
1184
+ | `is(model)` | Same type and primary key? |
1185
+ | `isNot(model)` | Negation of `is()` |
1186
+ | `only(...keys)` | Subset of attributes |
1187
+ | `except(...keys)` | All attributes except listed |
1188
+ | `makeVisible(...attrs)` | Unhide attributes for this instance |
1189
+ | `makeHidden(...attrs)` | Hide attributes for this instance |
1190
+ | `wasChanged(attr?)` | Changed after last `save()`? |
1191
+ | `getChanges()` | Attributes changed by last `save()` |
1103
1192
  | **Soft Deletes** | |
1104
1193
  | `trashed()` | Is deleted? |
1105
1194
  | `restore()` | Restore the model |
@@ -1150,6 +1239,15 @@ if (db.isLogging()) {
1150
1239
  | `delete()` | Delete |
1151
1240
  | `increment(col, amount?)` | Atomic increment |
1152
1241
  | `decrement(col, amount?)` | Atomic decrement |
1242
+ | `pluck(col, keyCol?)` | Array of values or keyed object |
1243
+ | `value(col)` | Single scalar value |
1244
+ | `sum(col)` / `avg(col)` | Aggregate sum / average |
1245
+ | `min(col)` / `max(col)` | Aggregate min / max |
1246
+ | `chunk(size, callback)` | Process results in batches |
1247
+ | `when(cond, cb, fallback?)` | Conditional query building |
1248
+ | `tap(callback)` | Inspect builder without modifying |
1249
+ | `toSQL()` | Returns `{ sql, bindings }` |
1250
+ | `dd()` | Dumps SQL + bindings and throws |
1153
1251
  | `clone()` | Clones the query builder |
1154
1252
 
1155
1253
  ## 🛠️ CLI tools
@@ -1233,12 +1331,12 @@ Outlet ORM includes a complete AI subsystem with multi-provider LLM support and
1233
1331
 
1234
1332
  📚 **[Complete AI documentation available in `/docs`](./docs/AI_BRIDGE.md)**
1235
1333
 
1236
- ### AiBridge — Multi-Provider LLM Abstraction
1334
+ ### AI — Multi-Provider LLM Abstraction
1237
1335
 
1238
1336
  ```javascript
1239
- const { AiBridgeManager } = require('outlet-orm');
1337
+ const { AIManager } = require('outlet-orm');
1240
1338
 
1241
- const ai = new AiBridgeManager({
1339
+ const ai = new AIManager({
1242
1340
  providers: {
1243
1341
  openai: { api_key: process.env.OPENAI_API_KEY, model: 'gpt-4o' },
1244
1342
  claude: { api_key: process.env.ANTHROPIC_API_KEY, model: 'claude-sonnet-4-20250514' },
@@ -1339,7 +1437,7 @@ Configure your AI editor:
1339
1437
  **13 MCP tools**: migrations, schema introspection, queries, seeds, backups, AI query, query optimization
1340
1438
 
1341
1439
  📖 Full documentation:
1342
- - [AiBridge Manager](docs/AI_BRIDGE.md) — Multi-provider LLM abstraction
1440
+ - [AI Manager](docs/AI_BRIDGE.md) — Multi-provider LLM abstraction
1343
1441
  - [AI Query Builder](docs/AI_QUERY.md) — Natural language to SQL
1344
1442
  - [AI Seeder](docs/AI_SEEDER.md) — Realistic data generation
1345
1443
  - [AI Query Optimizer](docs/AI_OPTIMIZER.md) — SQL optimization
package/bin/reverse.js CHANGED
@@ -82,7 +82,6 @@ function parseCreateTable(sql) {
82
82
  for (const line of splitDefinitions(body)) {
83
83
  const trimmed = line.trim();
84
84
  if (!trimmed) continue;
85
- const upperTrimmed = trimmed.toUpperCase();
86
85
 
87
86
  // ── Table-level FOREIGN KEY constraint ───────────────────────────────────
88
87
  if (/^FOREIGN\s+KEY/i.test(trimmed) || /^CONSTRAINT\s+\S+\s+FOREIGN\s+KEY/i.test(trimmed)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "outlet-orm",
3
- "version": "9.0.2",
3
+ "version": "11.1.0",
4
4
  "description": "A Laravel Eloquent-inspired ORM for Node.js with support for MySQL, PostgreSQL, and SQLite",
5
5
  "main": "src/index.js",
6
6
  "types": "types/index.d.ts",
@@ -157,6 +157,35 @@ Log.addGlobalScope('recent', (q) => q.where('created_at', '>', '2024-01-01'));
157
157
  Model.addGlobalScope('tenant', (q) => q.where('tenant_id', currentTenantId));
158
158
  ```
159
159
 
160
+ ### Local Scopes (v11.0.0)
161
+
162
+ Define reusable query constraints as static `scopeXxx` methods. They become fluent methods on the QueryBuilder:
163
+
164
+ ```javascript
165
+ class User extends Model {
166
+ static table = 'users';
167
+
168
+ static scopeActive(query) {
169
+ return query.where('status', 'active');
170
+ }
171
+
172
+ static scopeRole(query, role) {
173
+ return query.where('role', role);
174
+ }
175
+
176
+ static scopeRecent(query, days = 7) {
177
+ const date = new Date(Date.now() - days * 86400000).toISOString();
178
+ return query.where('created_at', '>', date);
179
+ }
180
+ }
181
+
182
+ // Fluent usage — scopes become methods on the query builder
183
+ const users = await User.query().active().role('admin').recent(30).get();
184
+ const count = await User.query().active().count();
185
+ ```
186
+
187
+ > **Note**: The legacy `static scopes = {}` and `.scope('name')` syntax still works. Fluent local scopes are the recommended approach from v11.
188
+
160
189
  ---
161
190
 
162
191
  ## Events / Hooks
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: outlet-orm-ai-integration
3
- description: Guide for AI agents to use Outlet ORM's AiBridge multi-provider LLM, AI Query Builder, AI Seeder, AI Optimizer, AI Prompt Enhancer, MCP Server, and AI Safety Guardrails. Use this skill when an AI agent needs to interact with LLMs, databases, run migrations, generate data, optimize queries, or create projects safely.
3
+ description: Guide for AI agents to use Outlet ORM's AI multi-provider LLM, AI Query Builder, AI Seeder, AI Optimizer, AI Prompt Enhancer, MCP Server, and AI Safety Guardrails. Use this skill when an AI agent needs to interact with LLMs, databases, run migrations, generate data, optimize queries, or create projects safely.
4
4
  license: MIT
5
5
  metadata:
6
6
  author: omgbwa-yasse
@@ -13,7 +13,7 @@ metadata:
13
13
 
14
14
  This skill covers Outlet ORM's complete AI feature set (v7.0.0 – v9.0.0):
15
15
 
16
- - **AiBridge** — Multi-provider LLM abstraction (9 providers, chat, stream, embeddings, images, TTS, STT, tool calling)
16
+ - **AI** — Multi-provider LLM abstraction (9 providers, chat, stream, embeddings, images, TTS, STT, tool calling)
17
17
  - **AI Query Builder** — Natural language to SQL
18
18
  - **AI Seeder** — LLM-powered realistic data generation
19
19
  - **AI Query Optimizer** — SQL optimization and EXPLAIN analysis
@@ -23,16 +23,16 @@ This skill covers Outlet ORM's complete AI feature set (v7.0.0 – v9.0.0):
23
23
 
24
24
  ---
25
25
 
26
- ## AiBridge — Multi-Provider LLM Abstraction
26
+ ## AI — Multi-Provider LLM Abstraction
27
27
 
28
28
  > Since v8.0.0
29
29
 
30
- AiBridge provides a unified API to interact with 9+ LLM providers. Zero production dependencies (Node 18+ native `fetch`).
30
+ AI provides a unified API to interact with 9+ LLM providers. Zero production dependencies (Node 18+ native `fetch`).
31
31
 
32
32
  ### Configuration
33
33
 
34
34
  ```javascript
35
- // config/aibridge.js
35
+ // config/ai.js
36
36
  module.exports = {
37
37
  default: process.env.AI_DEFAULT_PROVIDER || 'openai',
38
38
  providers: {
@@ -84,9 +84,9 @@ module.exports = {
84
84
  ### Chat
85
85
 
86
86
  ```javascript
87
- const { AiBridgeManager } = require('outlet-orm');
87
+ const { AIManager } = require('outlet-orm');
88
88
 
89
- const ai = new AiBridgeManager(config);
89
+ const ai = new AIManager(config);
90
90
 
91
91
  const response = await ai.chat('openai', [
92
92
  { role: 'system', content: 'You are a helpful assistant.' },
@@ -179,14 +179,14 @@ const response = await ai.chatWithTools('openai', [
179
179
  ]);
180
180
  ```
181
181
 
182
- ### AiBridge Facade
182
+ ### AI Facade
183
183
 
184
184
  ```javascript
185
- const { AiBridge, AiBridgeManager } = require('outlet-orm');
185
+ const { AI, AIManager } = require('outlet-orm');
186
186
 
187
- AiBridge.setManager(new AiBridgeManager(config));
187
+ AI.setManager(new AIManager(config));
188
188
 
189
- const { text } = await AiBridge.text()
189
+ const { text } = await AI.text()
190
190
  .using('openai', 'gpt-4o')
191
191
  .withPrompt('Hello!')
192
192
  .asText();
@@ -226,9 +226,9 @@ const { text } = await AiBridge.text()
226
226
  Convert natural language questions into SQL queries using any LLM.
227
227
 
228
228
  ```javascript
229
- const { AiBridgeManager, AIQueryBuilder, DatabaseConnection } = require('outlet-orm');
229
+ const { AIManager, AIQueryBuilder, DatabaseConnection } = require('outlet-orm');
230
230
 
231
- const ai = new AiBridgeManager(config);
231
+ const ai = new AIManager(config);
232
232
  const db = new DatabaseConnection();
233
233
  const qb = new AIQueryBuilder(ai, db);
234
234
 
@@ -267,9 +267,9 @@ const { sql } = await qb.toSql('Find duplicate emails');
267
267
  Generate realistic, domain-specific seed data using AI.
268
268
 
269
269
  ```javascript
270
- const { AiBridgeManager, AISeeder, DatabaseConnection } = require('outlet-orm');
270
+ const { AIManager, AISeeder, DatabaseConnection } = require('outlet-orm');
271
271
 
272
- const seeder = new AISeeder(new AiBridgeManager(config), new DatabaseConnection());
272
+ const seeder = new AISeeder(new AIManager(config), new DatabaseConnection());
273
273
 
274
274
  // Generate and insert
275
275
  const { records, inserted } = await seeder.seed('users', 10, {
@@ -309,9 +309,9 @@ const records = await seeder.generate('products', 20, {
309
309
  Analyze and optimize SQL queries using AI with index recommendations.
310
310
 
311
311
  ```javascript
312
- const { AiBridgeManager, AIQueryOptimizer, DatabaseConnection } = require('outlet-orm');
312
+ const { AIManager, AIQueryOptimizer, DatabaseConnection } = require('outlet-orm');
313
313
 
314
- const optimizer = new AIQueryOptimizer(new AiBridgeManager(config), new DatabaseConnection());
314
+ const optimizer = new AIQueryOptimizer(new AIManager(config), new DatabaseConnection());
315
315
 
316
316
  // Optimize
317
317
  const result = await optimizer.optimize(
@@ -352,9 +352,9 @@ const { plan, analysis } = await optimizer.explain('SELECT ...');
352
352
  Generate complete schemas, models, and migrations from natural language.
353
353
 
354
354
  ```javascript
355
- const { AiBridgeManager, AIPromptEnhancer } = require('outlet-orm');
355
+ const { AIManager, AIPromptEnhancer } = require('outlet-orm');
356
356
 
357
- const enhancer = new AIPromptEnhancer(new AiBridgeManager(config));
357
+ const enhancer = new AIPromptEnhancer(new AIManager(config));
358
358
 
359
359
  // Generate full schema
360
360
  const schema = await enhancer.generateSchema(
@@ -546,10 +546,10 @@ console.log(result.allowed); // true
546
546
 
547
547
  ```javascript
548
548
  const {
549
- // AiBridge — Multi-Provider LLM
550
- AiBridgeManager, // Main manager (chat, stream, embeddings, images, TTS, STT, tools)
551
- AiBridge, // Static facade
552
- // AiBridge Support
549
+ // AI — Multi-Provider LLM
550
+ AIManager, // Main manager (chat, stream, embeddings, images, TTS, STT, tools)
551
+ AI, // Static facade
552
+ // AI Support
553
553
  Message, // Chat message value object
554
554
  Document, // File/URL/base64 attachment
555
555
  StreamChunk, // Streaming DTO
@@ -54,6 +54,7 @@ const db = new DatabaseConnection({
54
54
  |`update(table, data, query)`| Update records |
55
55
  |`delete(table, query)`| Delete records |
56
56
  |`count(table, query)`| Count records |
57
+ |`aggregate(table, func, col, query)`| Run aggregate function (v11) |
57
58
  |`executeRawQuery(sql, params?)`| Raw query (normalised results) |
58
59
  |`execute(sql, params?)`| Raw query (native driver results) |
59
60
  |`increment(table, column, query, amount?)`| Atomic increment |
@@ -191,10 +192,27 @@ const db = new DatabaseConnection({
191
192
  |`fill(attrs)`| Fill attributes |
192
193
  |`setAttribute(key, val)`| Set single attribute |
193
194
  |`getAttribute(key)`| Get single attribute |
195
+ |`user.key` (Proxy)| Property-style get/set (v11) |
194
196
  |`getDirty()`| Get modified attributes |
195
197
  |`isDirty()`| Check if modified |
198
+ |`wasChanged(key?)`| Check if changed after save (v11) |
199
+ |`getChanges()`| Get post-save changes (v11) |
200
+ |`only(...keys)`| Subset of attributes (v11) |
201
+ |`except(...keys)`| All attributes except keys (v11) |
202
+ |`makeVisible(...keys)`| Show hidden attrs on instance (v11) |
203
+ |`makeHidden(...keys)`| Hide attrs on instance (v11) |
196
204
  |`toJSON()`| Convert to plain object |
197
205
 
206
+ ### Model Utility (v11.0.0)
207
+
208
+ | Method | Description |
209
+ |--------|-------------|
210
+ |`fresh()`| New reloaded instance from DB |
211
+ |`refresh()`| Reload current instance in-place |
212
+ |`replicate()`| Clone without primary key |
213
+ |`is(model)`| Same table + same PK |
214
+ |`isNot(model)`| Different identity |
215
+
198
216
  ### Persistence
199
217
 
200
218
  | Method | Description |
@@ -322,6 +340,13 @@ const db = new DatabaseConnection({
322
340
  |`paginate(page, perPage)`| Paginated results |
323
341
  |`count()`| Count results |
324
342
  |`exists()`| Check existence |
343
+ |`sum(col)`| Sum of column (v11) |
344
+ |`avg(col)`| Average of column (v11) |
345
+ |`min(col)`| Minimum of column (v11) |
346
+ |`max(col)`| Maximum of column (v11) |
347
+ |`pluck(col)`| Array of column values (v11) |
348
+ |`value(col)`| Single value from first row (v11) |
349
+ |`chunk(size, callback)`| Batch processing (v11) |
325
350
 
326
351
  ### Mutations
327
352
 
@@ -339,6 +364,10 @@ const db = new DatabaseConnection({
339
364
  | Method | Description |
340
365
  |--------|-------------|
341
366
  |`clone()`| Clone QueryBuilder |
367
+ |`when(condition, callback)`| Conditional clause (v11) |
368
+ |`tap(callback)`| Debug callback (v11) |
369
+ |`toSQL()`| Get SQL + bindings (v11) |
370
+ |`dd()`| Dump & die debug (v11) |
342
371
 
343
372
  ---
344
373
 
@@ -464,6 +493,7 @@ const db = new DatabaseConnection({
464
493
  |`hidden`| array |`[]`| Hidden from JSON |
465
494
  |`casts`| object |`{}`| Type casting |
466
495
  |`rules`| object |`{}`| Validation rules |
496
+ |`appends`| array |`[]`| Computed attrs in toJSON (v11) |
467
497
  |`connection`| object |`null`| Custom connection |
468
498
 
469
499
  ---
@@ -515,15 +545,15 @@ const db = new DatabaseConnection({
515
545
 
516
546
  ---
517
547
 
518
- ## AiBridgeManager
548
+ ## AIManager
519
549
 
520
550
  > Since v8.0.0
521
551
 
522
552
  ### Constructor
523
553
 
524
554
  ```javascript
525
- const { AiBridgeManager } = require('outlet-orm');
526
- const ai = new AiBridgeManager(config); // From config/aibridge.js or inline
555
+ const { AIManager } = require('outlet-orm');
556
+ const ai = new AIManager(config); // From config/ai.js or inline
527
557
  ```
528
558
 
529
559
  ### Methods
@@ -109,6 +109,7 @@ class User extends Model {
109
109
  |`hidden`| array |`[]`| Hidden from JSON |
110
110
  |`casts`| object |`{}`| Type casting definitions |
111
111
  |`rules`| object |`{}`| Validation rules |
112
+ |`appends`| array |`[]`| Computed attributes included in toJSON (v11) |
112
113
  |`connection`| object |`null`| Custom DB connection |
113
114
 
114
115
  ---
@@ -256,7 +257,10 @@ await user.forceDelete();
256
257
  ```javascript
257
258
  const user = await User.find(1);
258
259
 
259
- // Get single attribute
260
+ // Property access (v11+)
261
+ const name = user.name;
262
+
263
+ // Method access
260
264
  const name = user.getAttribute('name');
261
265
 
262
266
  // Get all attributes as object
@@ -272,7 +276,10 @@ const dirty = user.getDirty(); // Get modified attributes
272
276
  ```javascript
273
277
  const user = new User();
274
278
 
275
- // Set single attribute
279
+ // Property access (v11+)
280
+ user.name = 'John';
281
+
282
+ // Method access
276
283
  user.setAttribute('name', 'John');
277
284
 
278
285
  // Fill multiple attributes
@@ -310,6 +317,141 @@ if (user && await bcrypt.compare(password, user.getAttribute('password'))) {
310
317
  }
311
318
  ```
312
319
 
320
+ ### Instance-Level Visibility (v11.0.0)
321
+
322
+ ```javascript
323
+ const user = await User.find(1);
324
+
325
+ // Temporarily show hidden attributes on this instance
326
+ user.makeVisible('password', 'secret_token');
327
+ console.log(user.toJSON()); // password & secret_token included
328
+
329
+ // Temporarily hide additional attributes on this instance
330
+ user.makeHidden('email', 'phone');
331
+ console.log(user.toJSON()); // email & phone excluded
332
+ ```
333
+
334
+ ---
335
+
336
+ ## Property Access (v11.0.0)
337
+
338
+ Access model attributes directly as properties via Proxy, in addition to `getAttribute()`/`setAttribute()`:
339
+
340
+ ```javascript
341
+ const user = await User.find(1);
342
+
343
+ // Before v11 - only method access
344
+ const name = user.getAttribute('name');
345
+ user.setAttribute('name', 'New Name');
346
+
347
+ // v11+ - property-style access (equivalent)
348
+ const name = user.name;
349
+ user.name = 'New Name';
350
+ await user.save();
351
+
352
+ // Works with casts
353
+ console.log(user.email_verified); // boolean (thanks to casts)
354
+ console.log(user.metadata); // object (JSON cast)
355
+
356
+ // Check dirty state
357
+ user.name = 'Changed';
358
+ console.log(user.isDirty()); // true
359
+ ```
360
+
361
+ > **Note**: Native Model methods and properties (`save`, `destroy`, `fill`, etc.) always take precedence over attribute names.
362
+
363
+ ---
364
+
365
+ ## Computed Appends (v11.0.0)
366
+
367
+ Include computed attributes in `toJSON()` output:
368
+
369
+ ```javascript
370
+ class User extends Model {
371
+ static table = 'users';
372
+ static appends = ['full_name', 'is_admin'];
373
+
374
+ // Accessor for computed attribute
375
+ getFullNameAttribute() {
376
+ return `${this.getAttribute('first_name')} ${this.getAttribute('last_name')}`;
377
+ }
378
+
379
+ getIsAdminAttribute() {
380
+ return this.getAttribute('role') === 'admin';
381
+ }
382
+ }
383
+
384
+ const user = await User.find(1);
385
+ console.log(user.toJSON());
386
+ // { id: 1, first_name: 'John', last_name: 'Doe', full_name: 'John Doe', is_admin: false, ... }
387
+ ```
388
+
389
+ ---
390
+
391
+ ## Model Utility Methods (v11.0.0)
392
+
393
+ ### fresh() / refresh()
394
+
395
+ ```javascript
396
+ const user = await User.find(1);
397
+
398
+ // fresh() returns a NEW instance reloaded from DB (original unchanged)
399
+ const freshUser = await user.fresh();
400
+
401
+ // refresh() reloads the CURRENT instance in-place
402
+ user.name = 'temp';
403
+ await user.refresh();
404
+ console.log(user.name); // Back to DB value
405
+ ```
406
+
407
+ ### replicate()
408
+
409
+ ```javascript
410
+ const user = await User.find(1);
411
+ const clone = user.replicate();
412
+ // clone has same attributes but NO primary key
413
+ clone.name = 'Clone of ' + user.name;
414
+ await clone.save(); // Inserts as new record
415
+ ```
416
+
417
+ ### is() / isNot()
418
+
419
+ ```javascript
420
+ const user1 = await User.find(1);
421
+ const user2 = await User.find(1);
422
+ const user3 = await User.find(2);
423
+
424
+ user1.is(user2); // true (same table + same PK)
425
+ user1.isNot(user3); // true (different PK)
426
+ ```
427
+
428
+ ### only() / except()
429
+
430
+ ```javascript
431
+ const user = await User.find(1);
432
+
433
+ // Get a subset of attributes
434
+ const subset = user.only('name', 'email');
435
+ // { name: 'John', email: 'john@example.com' }
436
+
437
+ // Get all attributes except some
438
+ const filtered = user.except('password', 'secret_token');
439
+ // { id: 1, name: 'John', email: 'john@example.com', ... }
440
+ ```
441
+
442
+ ### wasChanged() / getChanges()
443
+
444
+ ```javascript
445
+ const user = await User.find(1);
446
+ user.name = 'Updated';
447
+ await user.save();
448
+
449
+ user.wasChanged(); // true
450
+ user.wasChanged('name'); // true
451
+ user.wasChanged('email'); // false
452
+ user.getChanges(); // { name: 'Updated' }
453
+ ```
454
+
313
455
  ---
314
456
 
315
457
  ## Timestamps