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.
- package/README.md +103 -5
- package/bin/reverse.js +0 -1
- package/package.json +1 -1
- package/skills/outlet-orm/ADVANCED.md +29 -0
- package/skills/outlet-orm/AI.md +23 -23
- package/skills/outlet-orm/API.md +33 -3
- package/skills/outlet-orm/MODELS.md +144 -2
- package/skills/outlet-orm/QUERIES.md +136 -3
- package/skills/outlet-orm/RELATIONS.md +44 -0
- package/skills/outlet-orm/SEEDS.md +2 -2
- package/skills/outlet-orm/SKILL.md +8 -6
- package/skills/outlet-orm/TYPESCRIPT.md +98 -0
- package/src/AI/{AiBridgeManager.js → AIManager.js} +61 -61
- package/src/AI/AIPromptEnhancer.js +1 -1
- package/src/AI/AIQueryBuilder.js +4 -4
- package/src/AI/AIQueryOptimizer.js +3 -3
- package/src/AI/AISeeder.js +1 -1
- package/src/AI/Builders/TextBuilder.js +2 -2
- package/src/AI/Contracts/AudioProviderContract.js +2 -2
- package/src/AI/Contracts/ChatProviderContract.js +3 -2
- package/src/AI/Contracts/EmbeddingsProviderContract.js +1 -1
- package/src/AI/Contracts/ImageProviderContract.js +1 -1
- package/src/AI/Contracts/ModelsProviderContract.js +1 -1
- package/src/AI/Contracts/ToolContract.js +1 -1
- package/src/AI/Facades/{AiBridge.js → AI.js} +11 -11
- package/src/AI/MCPServer.js +16 -16
- package/src/AI/Providers/CustomOpenAIProvider.js +0 -2
- package/src/AI/Providers/GeminiProvider.js +2 -2
- package/src/AI/Providers/OpenAIProvider.js +0 -5
- package/src/AI/Support/DocumentAttachmentMapper.js +37 -37
- package/src/AI/Support/FileSecurity.js +1 -1
- package/src/AI/Support/ToolChatRunner.js +1 -1
- package/src/Backup/BackupManager.js +6 -6
- package/src/Backup/BackupScheduler.js +1 -1
- package/src/Backup/BackupSocketServer.js +2 -2
- package/src/DatabaseConnection.js +51 -0
- package/src/Model.js +245 -5
- package/src/QueryBuilder.js +191 -0
- package/src/Relations/HasOneRelation.js +114 -114
- package/src/Relations/HasOneThroughRelation.js +105 -105
- package/src/Relations/MorphOneRelation.js +4 -2
- package/src/Relations/Relation.js +35 -0
- package/src/index.js +6 -6
- 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
|
-
- **🤖
|
|
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
|
-
###
|
|
1334
|
+
### AI — Multi-Provider LLM Abstraction
|
|
1237
1335
|
|
|
1238
1336
|
```javascript
|
|
1239
|
-
const {
|
|
1337
|
+
const { AIManager } = require('outlet-orm');
|
|
1240
1338
|
|
|
1241
|
-
const ai = new
|
|
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
|
-
- [
|
|
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
|
@@ -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
|
package/skills/outlet-orm/AI.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: outlet-orm-ai-integration
|
|
3
|
-
description: Guide for AI agents to use Outlet ORM's
|
|
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
|
-
- **
|
|
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
|
-
##
|
|
26
|
+
## AI — Multi-Provider LLM Abstraction
|
|
27
27
|
|
|
28
28
|
> Since v8.0.0
|
|
29
29
|
|
|
30
|
-
|
|
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/
|
|
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 {
|
|
87
|
+
const { AIManager } = require('outlet-orm');
|
|
88
88
|
|
|
89
|
-
const ai = new
|
|
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
|
-
###
|
|
182
|
+
### AI Facade
|
|
183
183
|
|
|
184
184
|
```javascript
|
|
185
|
-
const {
|
|
185
|
+
const { AI, AIManager } = require('outlet-orm');
|
|
186
186
|
|
|
187
|
-
|
|
187
|
+
AI.setManager(new AIManager(config));
|
|
188
188
|
|
|
189
|
-
const { text } = await
|
|
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 {
|
|
229
|
+
const { AIManager, AIQueryBuilder, DatabaseConnection } = require('outlet-orm');
|
|
230
230
|
|
|
231
|
-
const ai = new
|
|
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 {
|
|
270
|
+
const { AIManager, AISeeder, DatabaseConnection } = require('outlet-orm');
|
|
271
271
|
|
|
272
|
-
const seeder = new AISeeder(new
|
|
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 {
|
|
312
|
+
const { AIManager, AIQueryOptimizer, DatabaseConnection } = require('outlet-orm');
|
|
313
313
|
|
|
314
|
-
const optimizer = new AIQueryOptimizer(new
|
|
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 {
|
|
355
|
+
const { AIManager, AIPromptEnhancer } = require('outlet-orm');
|
|
356
356
|
|
|
357
|
-
const enhancer = new AIPromptEnhancer(new
|
|
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
|
-
//
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
//
|
|
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
|
package/skills/outlet-orm/API.md
CHANGED
|
@@ -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
|
-
##
|
|
548
|
+
## AIManager
|
|
519
549
|
|
|
520
550
|
> Since v8.0.0
|
|
521
551
|
|
|
522
552
|
### Constructor
|
|
523
553
|
|
|
524
554
|
```javascript
|
|
525
|
-
const {
|
|
526
|
-
const ai = new
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|