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
|
@@ -211,6 +211,30 @@ if (hasAdmins) {
|
|
|
211
211
|
}
|
|
212
212
|
```
|
|
213
213
|
|
|
214
|
+
### Sum / Avg / Min / Max (v11.0.0)
|
|
215
|
+
|
|
216
|
+
```javascript
|
|
217
|
+
const totalBalance = await User.query().sum('balance');
|
|
218
|
+
const averageAge = await User.query().avg('age');
|
|
219
|
+
const youngest = await User.query().min('age');
|
|
220
|
+
const oldest = await User.query().max('age');
|
|
221
|
+
|
|
222
|
+
// With conditions
|
|
223
|
+
const activeTotal = await User.where('status', 'active').sum('balance');
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Pluck / Value (v11.0.0)
|
|
227
|
+
|
|
228
|
+
```javascript
|
|
229
|
+
// pluck() — get an array of values from a single column
|
|
230
|
+
const emails = await User.query().pluck('email');
|
|
231
|
+
// ['john@example.com', 'jane@example.com', ...]
|
|
232
|
+
|
|
233
|
+
// value() — get a single value from the first row
|
|
234
|
+
const name = await User.where('id', 1).value('name');
|
|
235
|
+
// 'John Doe'
|
|
236
|
+
```
|
|
237
|
+
|
|
214
238
|
### Group By & Having
|
|
215
239
|
|
|
216
240
|
```javascript
|
|
@@ -280,6 +304,104 @@ await User.where('id', 1).decrement('credits', 50);
|
|
|
280
304
|
|
|
281
305
|
---
|
|
282
306
|
|
|
307
|
+
## Batch Processing — chunk() (v11.0.0)
|
|
308
|
+
|
|
309
|
+
Process large datasets in manageable batches:
|
|
310
|
+
|
|
311
|
+
```javascript
|
|
312
|
+
// Process 100 records at a time
|
|
313
|
+
await User.query().chunk(100, async (users) => {
|
|
314
|
+
for (const user of users) {
|
|
315
|
+
await sendNewsletter(user);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// With conditions
|
|
320
|
+
await User.where('status', 'active').chunk(50, async (batch) => {
|
|
321
|
+
console.log(`Processing ${batch.length} users`);
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Conditional Queries — when() / tap() (v11.0.0)
|
|
328
|
+
|
|
329
|
+
### when()
|
|
330
|
+
|
|
331
|
+
Conditionally apply query clauses:
|
|
332
|
+
|
|
333
|
+
```javascript
|
|
334
|
+
const status = req.query.status; // may be undefined
|
|
335
|
+
|
|
336
|
+
const users = await User.query()
|
|
337
|
+
.when(status, (query, value) => query.where('status', value))
|
|
338
|
+
.when(req.query.role, (query, value) => query.where('role', value))
|
|
339
|
+
.get();
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### tap()
|
|
343
|
+
|
|
344
|
+
Execute a callback for debugging without modifying the query:
|
|
345
|
+
|
|
346
|
+
```javascript
|
|
347
|
+
const users = await User.query()
|
|
348
|
+
.where('status', 'active')
|
|
349
|
+
.tap((query) => console.log('Query so far:', query.toSQL()))
|
|
350
|
+
.orderBy('name')
|
|
351
|
+
.get();
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## Query Debugging — toSQL() / dd() (v11.0.0)
|
|
357
|
+
|
|
358
|
+
```javascript
|
|
359
|
+
// toSQL() — get the SQL string and bindings
|
|
360
|
+
const { sql, bindings } = User.where('status', 'active').toSQL();
|
|
361
|
+
console.log(sql); // 'SELECT * FROM users WHERE status = ?'
|
|
362
|
+
console.log(bindings); // ['active']
|
|
363
|
+
|
|
364
|
+
// dd() — dump and die (logs to console and throws)
|
|
365
|
+
User.where('status', 'active').dd();
|
|
366
|
+
// Logs: { sql: '...', bindings: [...] } then throws
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## Fluent Local Scopes (v11.0.0)
|
|
372
|
+
|
|
373
|
+
Define reusable query constraints as static methods on the model:
|
|
374
|
+
|
|
375
|
+
```javascript
|
|
376
|
+
class User extends Model {
|
|
377
|
+
static table = 'users';
|
|
378
|
+
|
|
379
|
+
// Define scope as static scopeXxx(query, ...params)
|
|
380
|
+
static scopeActive(query) {
|
|
381
|
+
return query.where('status', 'active');
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
static scopeRole(query, role) {
|
|
385
|
+
return query.where('role', role);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
static scopeRecent(query, days = 7) {
|
|
389
|
+
const date = new Date(Date.now() - days * 86400000).toISOString();
|
|
390
|
+
return query.where('created_at', '>', date);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Use fluently on the query builder
|
|
395
|
+
const users = await User.query().active().role('admin').recent(30).get();
|
|
396
|
+
|
|
397
|
+
// Combine with other query methods
|
|
398
|
+
const count = await User.query().active().count();
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
> See [ADVANCED.md](ADVANCED.md) for more details on global and local scopes.
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
283
405
|
## Raw Queries
|
|
284
406
|
|
|
285
407
|
```javascript
|
|
@@ -304,12 +426,12 @@ const native = await db.execute(
|
|
|
304
426
|
|
|
305
427
|
> Since v8.0.0
|
|
306
428
|
|
|
307
|
-
Convert natural language into SQL queries using any LLM provider via
|
|
429
|
+
Convert natural language into SQL queries using any LLM provider via AI.
|
|
308
430
|
|
|
309
431
|
```javascript
|
|
310
|
-
const {
|
|
432
|
+
const { AIManager, AIQueryBuilder, DatabaseConnection } = require('outlet-orm');
|
|
311
433
|
|
|
312
|
-
const ai = new
|
|
434
|
+
const ai = new AIManager({ providers: { openai: { api_key: process.env.OPENAI_API_KEY, model: 'gpt-4o-mini' } } });
|
|
313
435
|
const db = new DatabaseConnection();
|
|
314
436
|
const qb = new AIQueryBuilder(ai, db);
|
|
315
437
|
|
|
@@ -394,6 +516,17 @@ See [AI.md](AI.md) for full details.
|
|
|
394
516
|
|`paginate(page, perPage)`| Pagination |
|
|
395
517
|
|`count()`| Count results |
|
|
396
518
|
|`exists()`| Check existence |
|
|
519
|
+
|`sum(col)`| Sum of column (v11) |
|
|
520
|
+
|`avg(col)`| Average of column (v11) |
|
|
521
|
+
|`min(col)`| Minimum of column (v11) |
|
|
522
|
+
|`max(col)`| Maximum of column (v11) |
|
|
523
|
+
|`pluck(col)`| Array of column values (v11) |
|
|
524
|
+
|`value(col)`| Single value from first row (v11) |
|
|
525
|
+
|`chunk(size, callback)`| Batch processing (v11) |
|
|
526
|
+
|`when(condition, callback)`| Conditional clause (v11) |
|
|
527
|
+
|`tap(callback)`| Debug callback (v11) |
|
|
528
|
+
|`toSQL()`| Get SQL + bindings (v11) |
|
|
529
|
+
|`dd()`| Dump & die debug (v11) |
|
|
397
530
|
|`insert(data)`| Insert record(s) |
|
|
398
531
|
|`update(attrs)`| Update records |
|
|
399
532
|
|`delete()`| Delete records |
|
|
@@ -546,6 +546,50 @@ class Category extends Model {
|
|
|
546
546
|
|`attach(ids)`| Attach (many-to-many) |
|
|
547
547
|
|`detach(ids?)`| Detach (many-to-many) |
|
|
548
548
|
|`sync(ids)`| Sync (many-to-many) |
|
|
549
|
+
|`withDefault(attrs?)`| Default for empty HasOne/MorphOne (v11) |
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
## Relation Defaults — withDefault() (v11.0.0)
|
|
554
|
+
|
|
555
|
+
Return a default model instead of `null` when a `hasOne`, `morphOne` or `hasOneThrough` relationship is empty:
|
|
556
|
+
|
|
557
|
+
```javascript
|
|
558
|
+
class User extends Model {
|
|
559
|
+
static table = 'users';
|
|
560
|
+
|
|
561
|
+
profile() {
|
|
562
|
+
// Returns empty Profile instance instead of null
|
|
563
|
+
return this.hasOne(Profile, 'user_id').withDefault();
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
avatar() {
|
|
567
|
+
// Returns a Profile with default values
|
|
568
|
+
return this.morphOne(Image, 'imageable').withDefault({
|
|
569
|
+
url: '/images/default-avatar.png'
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
settings() {
|
|
574
|
+
// Dynamic defaults via callback
|
|
575
|
+
return this.hasOne(Settings, 'user_id').withDefault((model) => {
|
|
576
|
+
model.setAttribute('theme', 'light');
|
|
577
|
+
model.setAttribute('locale', 'en');
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Usage
|
|
583
|
+
const user = await User.with('profile').find(1);
|
|
584
|
+
console.log(user.relationships.profile); // Profile instance (never null)
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
**Signatures:**
|
|
588
|
+
- `withDefault()` — empty model instance
|
|
589
|
+
- `withDefault({ key: value, ... })` — model with attributes
|
|
590
|
+
- `withDefault((model) => { ... })` — dynamic defaults via callback
|
|
591
|
+
|
|
592
|
+
**Supported on:** `hasOne`, `morphOne`, `hasOneThrough`
|
|
549
593
|
|
|
550
594
|
---
|
|
551
595
|
|
|
@@ -106,9 +106,9 @@ outlet-migrate seed
|
|
|
106
106
|
Generate realistic domain-specific seed data using AI instead of generic lorem ipsum.
|
|
107
107
|
|
|
108
108
|
```javascript
|
|
109
|
-
const {
|
|
109
|
+
const { AIManager, AISeeder, DatabaseConnection } = require('outlet-orm');
|
|
110
110
|
|
|
111
|
-
const ai = new
|
|
111
|
+
const ai = new AIManager({
|
|
112
112
|
providers: { openai: { api_key: process.env.OPENAI_API_KEY, model: 'gpt-4o-mini' } }
|
|
113
113
|
});
|
|
114
114
|
const seeder = new AISeeder(ai, new DatabaseConnection());
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: outlet-orm-best-practices
|
|
3
|
-
description: Outlet ORM is a Laravel Eloquent-inspired ORM for Node.js with MySQL, PostgreSQL, and SQLite support. Use this skill when working with Outlet ORM models, queries, relationships, migrations, backup, AI integration (
|
|
3
|
+
description: Outlet ORM is a Laravel Eloquent-inspired ORM for Node.js with MySQL, PostgreSQL, and SQLite support. Use this skill when working with Outlet ORM models, queries, relationships, migrations, backup, AI integration (AI multi-provider LLM, AI Query Builder, AI Seeder, AI Optimizer, MCP Server) and database operations.
|
|
4
4
|
license: MIT
|
|
5
5
|
metadata:
|
|
6
6
|
author: omgbwa-yasse
|
|
7
|
-
version: "
|
|
7
|
+
version: "11.0.0"
|
|
8
8
|
source: https://github.com/omgbwa-yasse/outlet-orm
|
|
9
9
|
npm: https://www.npmjs.com/package/outlet-orm
|
|
10
10
|
---
|
|
@@ -13,9 +13,11 @@ npm: https://www.npmjs.com/package/outlet-orm
|
|
|
13
13
|
|
|
14
14
|
Comprehensive guide for using Outlet ORM - a Laravel Eloquent-inspired ORM for Node.js/TypeScript with support for MySQL, PostgreSQL, and SQLite.
|
|
15
15
|
|
|
16
|
-
> 🆕 **
|
|
16
|
+
> 🆕 **v11.0.0**: Proxy property access (`user.name`), Eloquent-style model methods (`fresh`, `refresh`, `replicate`, `is`, `isNot`, `only`, `except`, `makeVisible`, `makeHidden`, `wasChanged`, `getChanges`), computed `appends`, QueryBuilder enhancements (`value`, `chunk`, `when`, `tap`, `toSQL`, `dd`), fluent local scopes (`static scopeActive(query)` → `User.query().active()`), relation `withDefault()`. See [MODELS.md](MODELS.md), [QUERIES.md](QUERIES.md), [ADVANCED.md](ADVANCED.md), [RELATIONS.md](RELATIONS.md).
|
|
17
17
|
>
|
|
18
|
-
> 🔖 **
|
|
18
|
+
> 🔖 **v9.0.0**: Complete AI documentation — AI multi-provider LLM, AI Query Builder, AI Seeder, AI Optimizer, AI Prompt Enhancer, AI Safety Guardrails. See [AI.md](AI.md).
|
|
19
|
+
>
|
|
20
|
+
> 🔖 **v8.0.0**: AI multi-provider LLM abstraction (9 providers), AI Query Builder, AI Seeder, AI Query Optimizer, AI Prompt Enhancer.
|
|
19
21
|
>
|
|
20
22
|
> 🔖 **v7.0.0**: AI Integration — MCP Server (Model Context Protocol), AI Safety Guardrails, Prompt-based project initialization.
|
|
21
23
|
>
|
|
@@ -37,7 +39,7 @@ Comprehensive guide for using Outlet ORM - a Laravel Eloquent-inspired ORM for N
|
|
|
37
39
|
| **[TYPESCRIPT.md](TYPESCRIPT.md)** | TypeScript types, generics, typed models, migrations |
|
|
38
40
|
| **[SECURITY.md](SECURITY.md)** | 🔐 Security best practices, authentication, authorisation |
|
|
39
41
|
| **[BACKUP.md](BACKUP.md)** | 🗄️ Backups, scheduling, AES-256-GCM encryption, TCP daemon, restore |
|
|
40
|
-
| **[AI.md](AI.md)** | 🤖
|
|
42
|
+
| **[AI.md](AI.md)** | 🤖 AI (9 LLM providers), AI Query Builder, AI Seeder, AI Optimizer, AI Prompt Enhancer, MCP Server, AI Safety Guardrails |
|
|
41
43
|
| **[API.md](API.md)** | Complete API Reference |
|
|
42
44
|
|
|
43
45
|
---
|
|
@@ -56,7 +58,7 @@ Reference these guidelines when:
|
|
|
56
58
|
- Scheduling or encrypting database backups [BACKUP.md](BACKUP.md)
|
|
57
59
|
- Restoring a database from a backup file [BACKUP.md](BACKUP.md)
|
|
58
60
|
- Running a long-lived backup daemon over TCP [BACKUP.md](BACKUP.md)
|
|
59
|
-
- Configuring LLM providers (
|
|
61
|
+
- Configuring LLM providers (AI) [AI.md](AI.md)
|
|
60
62
|
- Converting natural language to SQL (AI Query Builder) [AI.md](AI.md)
|
|
61
63
|
- Generating realistic seed data with AI [AI.md](AI.md)
|
|
62
64
|
- Optimizing SQL queries with AI [AI.md](AI.md)
|
|
@@ -384,6 +384,104 @@ table.foreign('user_id')
|
|
|
384
384
|
|
|
385
385
|
---
|
|
386
386
|
|
|
387
|
+
## v11.0.0 Type Additions
|
|
388
|
+
|
|
389
|
+
### Property-Style Attribute Access
|
|
390
|
+
|
|
391
|
+
Model instances are wrapped in a `Proxy`, so attributes can be read and written as direct properties:
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
const user = await User.find(1);
|
|
395
|
+
|
|
396
|
+
// Property access (via Proxy)
|
|
397
|
+
console.log(user.name); // equivalent to user.getAttribute('name')
|
|
398
|
+
user.email = 'new@mail.com'; // equivalent to user.setAttribute('email', ...)
|
|
399
|
+
|
|
400
|
+
// Both styles coexist
|
|
401
|
+
user.getAttribute('name'); // still works
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
The index signature `[K: string]: any` on `Model` enables this in TypeScript.
|
|
405
|
+
|
|
406
|
+
### Computed Appends
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
class User extends Model<UserAttributes> {
|
|
410
|
+
static table = 'users';
|
|
411
|
+
static appends = ['full_name'] as const;
|
|
412
|
+
|
|
413
|
+
getFullNameAttribute(): string {
|
|
414
|
+
return `${this.attributes.first_name} ${this.attributes.last_name}`;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// full_name is included in toJSON() output
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### New Model Instance Methods
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
// Reload from DB
|
|
425
|
+
const freshUser: User | null = await user.fresh('posts');
|
|
426
|
+
await user.refresh(); // mutates in place
|
|
427
|
+
|
|
428
|
+
// Clone without primary key
|
|
429
|
+
const clone: User = user.replicate('id', 'created_at');
|
|
430
|
+
|
|
431
|
+
// Identity comparison
|
|
432
|
+
user.is(otherUser); // same table + same PK
|
|
433
|
+
user.isNot(otherUser);
|
|
434
|
+
|
|
435
|
+
// Attribute subsets
|
|
436
|
+
const partial: Partial<UserAttributes> = user.only('name', 'email');
|
|
437
|
+
const rest: Partial<UserAttributes> = user.except('password');
|
|
438
|
+
|
|
439
|
+
// Instance-level visibility
|
|
440
|
+
user.makeVisible('password');
|
|
441
|
+
user.makeHidden('email');
|
|
442
|
+
|
|
443
|
+
// Change tracking (after save)
|
|
444
|
+
user.wasChanged('name'); // boolean
|
|
445
|
+
user.getChanges(); // { name: 'new value' }
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### New QueryBuilder Methods
|
|
449
|
+
|
|
450
|
+
```typescript
|
|
451
|
+
// Single column value
|
|
452
|
+
const email: any = await User.where('id', 1).value('email');
|
|
453
|
+
|
|
454
|
+
// Batch processing
|
|
455
|
+
await User.where('active', true).chunk(100, (users, page) => {
|
|
456
|
+
console.log(`Page ${page}:`, users.length);
|
|
457
|
+
// return false to stop
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
// Conditional query building
|
|
461
|
+
User.query()
|
|
462
|
+
.when(role, (qb, val) => qb.where('role', val))
|
|
463
|
+
.tap(qb => console.log(qb.toSQL()))
|
|
464
|
+
.get();
|
|
465
|
+
|
|
466
|
+
// Debug
|
|
467
|
+
const sql = User.where('active', true).toSQL();
|
|
468
|
+
User.where('active', true).dd(); // dumps SQL + throws
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### withDefault on Relations
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
class User extends Model<UserAttributes> {
|
|
475
|
+
profile(): HasOneRelation<Profile> {
|
|
476
|
+
return this.hasOne(Profile, 'user_id').withDefault();
|
|
477
|
+
// or .withDefault({ bio: 'N/A' })
|
|
478
|
+
// or .withDefault(() => new Profile({ bio: 'N/A' }))
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
---
|
|
484
|
+
|
|
387
485
|
## Common Patterns
|
|
388
486
|
|
|
389
487
|
### Repository Pattern
|
|
@@ -16,12 +16,12 @@ const ToolChatRunner = require('./Support/ToolChatRunner');
|
|
|
16
16
|
const BEARER_PREFIX = 'Bearer ';
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
19
|
+
* AIManager
|
|
20
20
|
* Central orchestrator for all AI providers. Supports provider registration,
|
|
21
21
|
* per-call overrides, capability delegation (chat, stream, embeddings, images,
|
|
22
22
|
* audio, models), tool registration, and chatWithTools loop.
|
|
23
23
|
*/
|
|
24
|
-
class
|
|
24
|
+
class AIManager {
|
|
25
25
|
/**
|
|
26
26
|
* @param {Object} config
|
|
27
27
|
*/
|
|
@@ -90,66 +90,66 @@ class AiBridgeManager {
|
|
|
90
90
|
/** @private */
|
|
91
91
|
_buildProviderFromOptions(name, options) {
|
|
92
92
|
switch (name) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
break;
|
|
93
|
+
case 'openai': {
|
|
94
|
+
const api = options.api_key;
|
|
95
|
+
if (api) return new OpenAIProvider(api, options.chat_endpoint || 'https://api.openai.com/v1/chat/completions');
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case 'ollama': return new OllamaProvider(options.endpoint || 'http://localhost:11434');
|
|
99
|
+
case 'ollama_turbo': {
|
|
100
|
+
const api = options.api_key;
|
|
101
|
+
if (api) return new OllamaTurboProvider(api, options.endpoint || 'https://ollama.com');
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case 'onn': {
|
|
105
|
+
const api = options.api_key;
|
|
106
|
+
if (api) return new OnnProvider(api, options.endpoint || 'https://api.onn.ai/v1/chat');
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case 'gemini': {
|
|
110
|
+
const api = options.api_key;
|
|
111
|
+
if (api) return new GeminiProvider(api, options.endpoint);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
case 'grok': {
|
|
115
|
+
const api = options.api_key;
|
|
116
|
+
if (api) return new GrokProvider(api, options.endpoint);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
case 'claude': {
|
|
120
|
+
const api = options.api_key;
|
|
121
|
+
if (api) return new ClaudeProvider(api, options.endpoint);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case 'mistral': {
|
|
125
|
+
const api = options.api_key;
|
|
126
|
+
if (api) return new MistralProvider(api, options.endpoint || 'https://api.mistral.ai/v1/chat/completions');
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
case 'openai_custom': {
|
|
130
|
+
const api = options.api_key;
|
|
131
|
+
const base = options.base_url;
|
|
132
|
+
if (api && base) {
|
|
133
|
+
return new CustomOpenAIProvider(api, base, options.paths || {},
|
|
134
|
+
options.auth_header || 'Authorization', options.auth_prefix || BEARER_PREFIX,
|
|
135
|
+
options.extra_headers || {});
|
|
138
136
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
case 'openrouter': {
|
|
140
|
+
const api = options.api_key;
|
|
141
|
+
if (api) {
|
|
142
|
+
const base = options.base_url || 'https://openrouter.ai/api/v1';
|
|
143
|
+
const hdrs = {};
|
|
144
|
+
if (options.referer) hdrs['HTTP-Referer'] = options.referer;
|
|
145
|
+
if (options.title) hdrs['X-Title'] = options.title;
|
|
146
|
+
return new CustomOpenAIProvider(api, base,
|
|
147
|
+
{ chat: '/chat/completions', embeddings: '/embeddings', image: '/images/generations', tts: '/audio/speech', stt: '/audio/transcriptions' },
|
|
148
|
+
'Authorization', BEARER_PREFIX, hdrs
|
|
149
|
+
);
|
|
152
150
|
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
153
|
}
|
|
154
154
|
return null;
|
|
155
155
|
}
|
|
@@ -284,4 +284,4 @@ class AiBridgeManager {
|
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
-
module.exports =
|
|
287
|
+
module.exports = AIManager;
|
package/src/AI/AIQueryBuilder.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* AIQueryBuilder
|
|
5
|
-
* Natural Language → SQL conversion using any
|
|
5
|
+
* Natural Language → SQL conversion using any AI provider.
|
|
6
6
|
* Introspects the database schema, sends it with the NL prompt to an LLM,
|
|
7
7
|
* and returns a safe, parameterized SQL query.
|
|
8
8
|
*
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
class AIQueryBuilder {
|
|
12
12
|
/**
|
|
13
|
-
* @param {import('./
|
|
13
|
+
* @param {import('./AIManager')} manager - AI manager instance
|
|
14
14
|
* @param {Object} connection - DatabaseConnection instance (outlet-orm)
|
|
15
15
|
*/
|
|
16
16
|
constructor(manager, connection) {
|
|
@@ -153,12 +153,12 @@ class AIQueryBuilder {
|
|
|
153
153
|
let tables = [];
|
|
154
154
|
if (dialect === 'pg' || dialect === 'postgresql') {
|
|
155
155
|
const res = await this._connection.raw(
|
|
156
|
-
|
|
156
|
+
'SELECT table_name FROM information_schema.tables WHERE table_schema = \'public\' AND table_type = \'BASE TABLE\''
|
|
157
157
|
);
|
|
158
158
|
tables = (res.rows || res).map(r => r.table_name);
|
|
159
159
|
} else if (dialect === 'sqlite' || dialect === 'sqlite3') {
|
|
160
160
|
const res = await this._connection.raw(
|
|
161
|
-
|
|
161
|
+
'SELECT name FROM sqlite_master WHERE type=\'table\' AND name NOT LIKE \'sqlite_%\''
|
|
162
162
|
);
|
|
163
163
|
tables = (Array.isArray(res) ? res : (res.rows || [])).map(r => r.name);
|
|
164
164
|
} else {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
class AIQueryOptimizer {
|
|
11
11
|
/**
|
|
12
|
-
* @param {import('./
|
|
12
|
+
* @param {import('./AIManager')} manager
|
|
13
13
|
* @param {Object} [connection] - DatabaseConnection instance (optional, for schema introspection)
|
|
14
14
|
*/
|
|
15
15
|
constructor(manager, connection = null) {
|
|
@@ -143,10 +143,10 @@ RULES:
|
|
|
143
143
|
const dialect = this._connection.config?.client || 'mysql';
|
|
144
144
|
let tables = [];
|
|
145
145
|
if (dialect === 'pg' || dialect === 'postgresql') {
|
|
146
|
-
const res = await this._connection.raw(
|
|
146
|
+
const res = await this._connection.raw('SELECT table_name FROM information_schema.tables WHERE table_schema = \'public\'');
|
|
147
147
|
tables = (res.rows || res).map(r => r.table_name);
|
|
148
148
|
} else if (dialect === 'sqlite' || dialect === 'sqlite3') {
|
|
149
|
-
const res = await this._connection.raw(
|
|
149
|
+
const res = await this._connection.raw('SELECT name FROM sqlite_master WHERE type=\'table\' AND name NOT LIKE \'sqlite_%\'');
|
|
150
150
|
tables = (Array.isArray(res) ? res : []).map(r => r.name);
|
|
151
151
|
} else {
|
|
152
152
|
const res = await this._connection.raw('SHOW TABLES');
|
package/src/AI/AISeeder.js
CHANGED
|
@@ -5,7 +5,7 @@ const StreamChunk = require('../Support/StreamChunk');
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* TextBuilder
|
|
8
|
-
* Fluent builder for text generation over
|
|
8
|
+
* Fluent builder for text generation over AI providers.
|
|
9
9
|
* Keeps method names short and explicit, reducing array option errors.
|
|
10
10
|
*
|
|
11
11
|
* @example
|
|
@@ -19,7 +19,7 @@ class TextBuilder {
|
|
|
19
19
|
static ERR_MISSING_USING = 'Provider and model must be set via using().';
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* @param {import('../
|
|
22
|
+
* @param {import('../AIManager')} manager
|
|
23
23
|
*/
|
|
24
24
|
constructor(manager) {
|
|
25
25
|
this._manager = manager;
|
|
@@ -11,7 +11,7 @@ class AudioProviderContract {
|
|
|
11
11
|
* @param {Object} [options={}]
|
|
12
12
|
* @returns {Promise<{audio: string, mime: string}>} audio is base64-encoded
|
|
13
13
|
*/
|
|
14
|
-
async textToSpeech(text,
|
|
14
|
+
async textToSpeech(text, _options = {}) {
|
|
15
15
|
throw new Error('Not implemented: textToSpeech()');
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -21,7 +21,7 @@ class AudioProviderContract {
|
|
|
21
21
|
* @param {Object} [options={}]
|
|
22
22
|
* @returns {Promise<{text: string, raw?: Object}>}
|
|
23
23
|
*/
|
|
24
|
-
async speechToText(filePath,
|
|
24
|
+
async speechToText(filePath, _options = {}) {
|
|
25
25
|
throw new Error('Not implemented: speechToText()');
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -12,7 +12,7 @@ class ChatProviderContract {
|
|
|
12
12
|
* @param {Object} [options={}]
|
|
13
13
|
* @returns {Promise<Object>}
|
|
14
14
|
*/
|
|
15
|
-
async chat(messages,
|
|
15
|
+
async chat(messages, _options = {}) {
|
|
16
16
|
throw new Error('Not implemented: chat()');
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -22,7 +22,8 @@ class ChatProviderContract {
|
|
|
22
22
|
* @param {Object} [options={}]
|
|
23
23
|
* @yields {string|Object}
|
|
24
24
|
*/
|
|
25
|
-
|
|
25
|
+
// eslint-disable-next-line require-yield
|
|
26
|
+
async *stream(messages, _options = {}) {
|
|
26
27
|
throw new Error('Not implemented: stream()');
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -11,7 +11,7 @@ class EmbeddingsProviderContract {
|
|
|
11
11
|
* @param {Object} [options={}]
|
|
12
12
|
* @returns {Promise<{embeddings: number[][], usage?: Object, raw?: Object}>}
|
|
13
13
|
*/
|
|
14
|
-
async embeddings(inputs,
|
|
14
|
+
async embeddings(inputs, _options = {}) {
|
|
15
15
|
throw new Error('Not implemented: embeddings()');
|
|
16
16
|
}
|
|
17
17
|
}
|