outlet-orm 10.0.0 → 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 +32 -0
- package/bin/reverse.js +0 -1
- package/package.json +1 -1
- package/skills/outlet-orm/ADVANCED.md +29 -0
- package/skills/outlet-orm/API.md +30 -0
- package/skills/outlet-orm/MODELS.md +144 -2
- package/skills/outlet-orm/QUERIES.md +133 -0
- package/skills/outlet-orm/RELATIONS.md +44 -0
- package/skills/outlet-orm/SKILL.md +4 -2
- package/skills/outlet-orm/TYPESCRIPT.md +98 -0
- package/src/AI/AIManager.js +58 -58
- package/src/AI/AIQueryBuilder.js +2 -2
- package/src/AI/AIQueryOptimizer.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/MCPServer.js +3 -3
- 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/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/types/index.d.ts +67 -1
package/src/Model.js
CHANGED
|
@@ -73,6 +73,19 @@ class Model {
|
|
|
73
73
|
this.morphMap = map;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// Appends: computed attributes included in serialization
|
|
77
|
+
static appends = [];
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Internal own-property names that must never be intercepted by the Proxy.
|
|
81
|
+
* @private
|
|
82
|
+
*/
|
|
83
|
+
static _ownProperties = new Set([
|
|
84
|
+
'attributes', 'original', 'relations', 'touches',
|
|
85
|
+
'exists', '_showHidden', '_withTrashed', '_onlyTrashed',
|
|
86
|
+
'_instanceVisible', '_instanceHidden', '_changes'
|
|
87
|
+
]);
|
|
88
|
+
|
|
76
89
|
constructor(attributes = {}) {
|
|
77
90
|
// Auto-initialize connection on first model instantiation if missing
|
|
78
91
|
this.constructor.ensureConnection();
|
|
@@ -84,7 +97,35 @@ class Model {
|
|
|
84
97
|
this._showHidden = false;
|
|
85
98
|
this._withTrashed = false;
|
|
86
99
|
this._onlyTrashed = false;
|
|
100
|
+
this._instanceVisible = null;
|
|
101
|
+
this._instanceHidden = null;
|
|
102
|
+
this._changes = {};
|
|
87
103
|
this.fill(attributes);
|
|
104
|
+
|
|
105
|
+
// Return a Proxy so that attribute access works as user.name / user.name = 'x'
|
|
106
|
+
return new Proxy(this, {
|
|
107
|
+
get(target, prop, receiver) {
|
|
108
|
+
// Symbols, own properties, and prototype members → normal access
|
|
109
|
+
if (typeof prop === 'symbol' || prop in target || typeof target[prop] === 'function') {
|
|
110
|
+
return Reflect.get(target, prop, receiver);
|
|
111
|
+
}
|
|
112
|
+
// Static / prototype properties that are not functions (e.g. constructor)
|
|
113
|
+
if (Object.prototype.hasOwnProperty.call(target, prop)) {
|
|
114
|
+
return Reflect.get(target, prop, receiver);
|
|
115
|
+
}
|
|
116
|
+
// Everything else → delegate to getAttribute (accessors, casts, relations)
|
|
117
|
+
return target.getAttribute(prop);
|
|
118
|
+
},
|
|
119
|
+
set(target, prop, value, receiver) {
|
|
120
|
+
// Known own properties → direct assignment
|
|
121
|
+
if (target.constructor._ownProperties.has(prop) || prop in target) {
|
|
122
|
+
return Reflect.set(target, prop, value, receiver);
|
|
123
|
+
}
|
|
124
|
+
// Everything else → delegate to setAttribute (mutators, casts)
|
|
125
|
+
target.setAttribute(prop, value);
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
88
129
|
}
|
|
89
130
|
|
|
90
131
|
// ==================== Events/Hooks ====================
|
|
@@ -469,7 +510,25 @@ class Model {
|
|
|
469
510
|
static query() {
|
|
470
511
|
// Ensure a connection exists even when using static APIs without instantiation
|
|
471
512
|
this.ensureConnection();
|
|
472
|
-
|
|
513
|
+
const qb = new QueryBuilder(this);
|
|
514
|
+
|
|
515
|
+
// Return a Proxy that recognizes local scopes (static scopeXxx methods on the model)
|
|
516
|
+
return new Proxy(qb, {
|
|
517
|
+
get(target, prop, receiver) {
|
|
518
|
+
if (prop in target || typeof prop === 'symbol') {
|
|
519
|
+
return Reflect.get(target, prop, receiver);
|
|
520
|
+
}
|
|
521
|
+
// Check for a local scope: model.scopeActive => User.query().active()
|
|
522
|
+
const scopeName = 'scope' + prop.charAt(0).toUpperCase() + prop.slice(1);
|
|
523
|
+
if (typeof target.model[scopeName] === 'function') {
|
|
524
|
+
return (...args) => {
|
|
525
|
+
target.model[scopeName](target, ...args);
|
|
526
|
+
return receiver; // keep the proxy chain
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
return Reflect.get(target, prop, receiver);
|
|
530
|
+
}
|
|
531
|
+
});
|
|
473
532
|
}
|
|
474
533
|
|
|
475
534
|
/**
|
|
@@ -952,6 +1011,7 @@ class Model {
|
|
|
952
1011
|
|
|
953
1012
|
this.setAttribute(this.constructor.primaryKey, result.insertId);
|
|
954
1013
|
this.exists = true;
|
|
1014
|
+
this._changes = { ...this.attributes };
|
|
955
1015
|
this.original = { ...this.attributes };
|
|
956
1016
|
|
|
957
1017
|
await this.touchParents();
|
|
@@ -986,6 +1046,7 @@ class Model {
|
|
|
986
1046
|
{ wheres: [{ type: 'basic', column: this.constructor.primaryKey, operator: '=', value: this.getAttribute(this.constructor.primaryKey) }] }
|
|
987
1047
|
);
|
|
988
1048
|
|
|
1049
|
+
this._changes = { ...dirty };
|
|
989
1050
|
this.original = { ...this.attributes };
|
|
990
1051
|
|
|
991
1052
|
await this.touchParents();
|
|
@@ -1077,15 +1138,31 @@ class Model {
|
|
|
1077
1138
|
* @returns {Object}
|
|
1078
1139
|
*/
|
|
1079
1140
|
toJSON() {
|
|
1080
|
-
|
|
1141
|
+
let json = { ...this.attributes };
|
|
1081
1142
|
|
|
1082
|
-
//
|
|
1083
|
-
if (
|
|
1084
|
-
this.
|
|
1143
|
+
// Instance-level visible: keep only these keys
|
|
1144
|
+
if (this._instanceVisible && this._instanceVisible.length > 0) {
|
|
1145
|
+
const visible = new Set(this._instanceVisible);
|
|
1146
|
+
for (const key of Object.keys(json)) {
|
|
1147
|
+
if (!visible.has(key)) delete json[key];
|
|
1148
|
+
}
|
|
1149
|
+
} else if (!this._showHidden) {
|
|
1150
|
+
// Hide specified attributes unless _showHidden is true
|
|
1151
|
+
// If _instanceHidden was explicitly set (via makeVisible/makeHidden), use it as-is
|
|
1152
|
+
const hidden = this._instanceHidden !== null
|
|
1153
|
+
? new Set(this._instanceHidden)
|
|
1154
|
+
: new Set(this.constructor.hidden);
|
|
1155
|
+
hidden.forEach(key => {
|
|
1085
1156
|
delete json[key];
|
|
1086
1157
|
});
|
|
1087
1158
|
}
|
|
1088
1159
|
|
|
1160
|
+
// Appends: include computed attributes via accessors
|
|
1161
|
+
const appends = this.constructor.appends || [];
|
|
1162
|
+
for (const attr of appends) {
|
|
1163
|
+
json[attr] = this.getAttribute(attr);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1089
1166
|
// Add relations
|
|
1090
1167
|
Object.assign(json, this.relations);
|
|
1091
1168
|
|
|
@@ -1144,6 +1221,169 @@ class Model {
|
|
|
1144
1221
|
}
|
|
1145
1222
|
}
|
|
1146
1223
|
|
|
1224
|
+
// ==================== Model Instance Utilities ====================
|
|
1225
|
+
|
|
1226
|
+
/**
|
|
1227
|
+
* Reload a fresh model instance from the database
|
|
1228
|
+
* @param {...string} relations - Relations to eager load
|
|
1229
|
+
* @returns {Promise<Model|null>}
|
|
1230
|
+
*/
|
|
1231
|
+
async fresh(...relations) {
|
|
1232
|
+
if (!this.exists) return null;
|
|
1233
|
+
const pk = this.getAttribute(this.constructor.primaryKey);
|
|
1234
|
+
if (pk == null) return null;
|
|
1235
|
+
|
|
1236
|
+
let query = this.constructor.query().where(this.constructor.primaryKey, pk);
|
|
1237
|
+
if (relations.length > 0) {
|
|
1238
|
+
query = query.with(...relations);
|
|
1239
|
+
}
|
|
1240
|
+
return query.first();
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
/**
|
|
1244
|
+
* Reload the model attributes from the database (mutates this instance)
|
|
1245
|
+
* @returns {Promise<this>}
|
|
1246
|
+
*/
|
|
1247
|
+
async refresh() {
|
|
1248
|
+
if (!this.exists) return this;
|
|
1249
|
+
const pk = this.getAttribute(this.constructor.primaryKey);
|
|
1250
|
+
if (pk == null) return this;
|
|
1251
|
+
|
|
1252
|
+
const freshModel = await this.constructor.query()
|
|
1253
|
+
.where(this.constructor.primaryKey, pk)
|
|
1254
|
+
.first();
|
|
1255
|
+
|
|
1256
|
+
if (freshModel) {
|
|
1257
|
+
this.attributes = { ...freshModel.attributes };
|
|
1258
|
+
this.original = { ...freshModel.attributes };
|
|
1259
|
+
this.relations = {};
|
|
1260
|
+
}
|
|
1261
|
+
return this;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
/**
|
|
1265
|
+
* Clone the model into a new, non-existing instance (without primary key)
|
|
1266
|
+
* @param {...string} except - Additional attributes to exclude
|
|
1267
|
+
* @returns {Model}
|
|
1268
|
+
*/
|
|
1269
|
+
replicate(...except) {
|
|
1270
|
+
const excluded = new Set([this.constructor.primaryKey, ...except]);
|
|
1271
|
+
const attrs = {};
|
|
1272
|
+
for (const [key, value] of Object.entries(this.attributes)) {
|
|
1273
|
+
if (!excluded.has(key)) {
|
|
1274
|
+
attrs[key] = value;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
const instance = new this.constructor();
|
|
1278
|
+
instance.attributes = attrs;
|
|
1279
|
+
return instance;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
/**
|
|
1283
|
+
* Determine if two models have the same ID and belong to the same table
|
|
1284
|
+
* @param {Model|null} model
|
|
1285
|
+
* @returns {boolean}
|
|
1286
|
+
*/
|
|
1287
|
+
is(model) {
|
|
1288
|
+
if (!model) return false;
|
|
1289
|
+
return this.constructor.table === model.constructor.table &&
|
|
1290
|
+
this.constructor.primaryKey === model.constructor.primaryKey &&
|
|
1291
|
+
this.getAttribute(this.constructor.primaryKey) === model.getAttribute(model.constructor.primaryKey) &&
|
|
1292
|
+
this.getAttribute(this.constructor.primaryKey) != null;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
/**
|
|
1296
|
+
* Determine if two models are not the same
|
|
1297
|
+
* @param {Model|null} model
|
|
1298
|
+
* @returns {boolean}
|
|
1299
|
+
*/
|
|
1300
|
+
isNot(model) {
|
|
1301
|
+
return !this.is(model);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
/**
|
|
1305
|
+
* Get a subset of the model's attributes as a plain object
|
|
1306
|
+
* @param {...string|Array<string>} keys
|
|
1307
|
+
* @returns {Object}
|
|
1308
|
+
*/
|
|
1309
|
+
only(...keys) {
|
|
1310
|
+
const flatKeys = keys.flat();
|
|
1311
|
+
const result = {};
|
|
1312
|
+
for (const key of flatKeys) {
|
|
1313
|
+
result[key] = this.getAttribute(key);
|
|
1314
|
+
}
|
|
1315
|
+
return result;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
/**
|
|
1319
|
+
* Get all attributes except the specified keys as a plain object
|
|
1320
|
+
* @param {...string|Array<string>} keys
|
|
1321
|
+
* @returns {Object}
|
|
1322
|
+
*/
|
|
1323
|
+
except(...keys) {
|
|
1324
|
+
const excluded = new Set(keys.flat());
|
|
1325
|
+
const result = {};
|
|
1326
|
+
for (const [key, value] of Object.entries(this.attributes)) {
|
|
1327
|
+
if (!excluded.has(key)) {
|
|
1328
|
+
result[key] = value;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
return result;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
/**
|
|
1335
|
+
* Make given attributes visible on this instance (overrides static hidden)
|
|
1336
|
+
* @param {...string|Array<string>} attrs
|
|
1337
|
+
* @returns {this}
|
|
1338
|
+
*/
|
|
1339
|
+
makeVisible(...attrs) {
|
|
1340
|
+
const flat = attrs.flat();
|
|
1341
|
+
// Initialize from static hidden if not yet overridden
|
|
1342
|
+
if (!this._instanceHidden) {
|
|
1343
|
+
this._instanceHidden = [...this.constructor.hidden];
|
|
1344
|
+
}
|
|
1345
|
+
this._instanceHidden = this._instanceHidden.filter(k => !flat.includes(k));
|
|
1346
|
+
return this;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
/**
|
|
1350
|
+
* Make given attributes hidden on this instance
|
|
1351
|
+
* @param {...string|Array<string>} attrs
|
|
1352
|
+
* @returns {this}
|
|
1353
|
+
*/
|
|
1354
|
+
makeHidden(...attrs) {
|
|
1355
|
+
const flat = attrs.flat();
|
|
1356
|
+
if (!this._instanceHidden) {
|
|
1357
|
+
this._instanceHidden = [...this.constructor.hidden];
|
|
1358
|
+
}
|
|
1359
|
+
for (const k of flat) {
|
|
1360
|
+
if (!this._instanceHidden.includes(k)) {
|
|
1361
|
+
this._instanceHidden.push(k);
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
return this;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
/**
|
|
1368
|
+
* Determine if the model or a given attribute was changed after the last save
|
|
1369
|
+
* @param {string} [attr]
|
|
1370
|
+
* @returns {boolean}
|
|
1371
|
+
*/
|
|
1372
|
+
wasChanged(attr) {
|
|
1373
|
+
if (attr) {
|
|
1374
|
+
return attr in this._changes;
|
|
1375
|
+
}
|
|
1376
|
+
return Object.keys(this._changes).length > 0;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
/**
|
|
1380
|
+
* Get the attributes that were changed on the last save
|
|
1381
|
+
* @returns {Object}
|
|
1382
|
+
*/
|
|
1383
|
+
getChanges() {
|
|
1384
|
+
return { ...this._changes };
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1147
1387
|
// ==================== Relationships ====================
|
|
1148
1388
|
|
|
1149
1389
|
/**
|
package/src/QueryBuilder.js
CHANGED
|
@@ -924,6 +924,197 @@ class QueryBuilder {
|
|
|
924
924
|
}
|
|
925
925
|
}
|
|
926
926
|
|
|
927
|
+
// ==================== Convenience Query Methods ====================
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* Get an array of values for a single column, optionally keyed by another column
|
|
931
|
+
* @param {string} column
|
|
932
|
+
* @param {string} [keyColumn]
|
|
933
|
+
* @returns {Promise<Array|Object>}
|
|
934
|
+
*/
|
|
935
|
+
async pluck(column, keyColumn) {
|
|
936
|
+
assertIdentifier(column, 'pluck column');
|
|
937
|
+
if (keyColumn) assertIdentifier(keyColumn, 'pluck key column');
|
|
938
|
+
|
|
939
|
+
const cols = keyColumn ? [column, keyColumn] : [column];
|
|
940
|
+
this.selectedColumns = cols;
|
|
941
|
+
this._applyGlobalScopes();
|
|
942
|
+
this._applySoftDeleteConstraints();
|
|
943
|
+
|
|
944
|
+
const rows = await this.model.connection.select(
|
|
945
|
+
this.model.table,
|
|
946
|
+
this.buildQuery()
|
|
947
|
+
);
|
|
948
|
+
|
|
949
|
+
if (keyColumn) {
|
|
950
|
+
const result = {};
|
|
951
|
+
for (const row of rows) {
|
|
952
|
+
result[row[keyColumn]] = row[column];
|
|
953
|
+
}
|
|
954
|
+
return result;
|
|
955
|
+
}
|
|
956
|
+
return rows.map(row => row[column]);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/**
|
|
960
|
+
* Get the value of a single column from the first matching row
|
|
961
|
+
* @param {string} column
|
|
962
|
+
* @returns {Promise<any>}
|
|
963
|
+
*/
|
|
964
|
+
async value(column) {
|
|
965
|
+
assertIdentifier(column, 'value column');
|
|
966
|
+
this.selectedColumns = [column];
|
|
967
|
+
this._applyGlobalScopes();
|
|
968
|
+
this._applySoftDeleteConstraints();
|
|
969
|
+
this.limitValue = 1;
|
|
970
|
+
|
|
971
|
+
const rows = await this.model.connection.select(
|
|
972
|
+
this.model.table,
|
|
973
|
+
this.buildQuery()
|
|
974
|
+
);
|
|
975
|
+
if (rows.length === 0) return null;
|
|
976
|
+
return rows[0][column];
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// ==================== Aggregate Methods ====================
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Get the sum of a column
|
|
983
|
+
* @param {string} column
|
|
984
|
+
* @returns {Promise<number>}
|
|
985
|
+
*/
|
|
986
|
+
async sum(column) {
|
|
987
|
+
return this._aggregate('SUM', column);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* Get the average of a column
|
|
992
|
+
* @param {string} column
|
|
993
|
+
* @returns {Promise<number>}
|
|
994
|
+
*/
|
|
995
|
+
async avg(column) {
|
|
996
|
+
return this._aggregate('AVG', column);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
/**
|
|
1000
|
+
* Get the minimum value of a column
|
|
1001
|
+
* @param {string} column
|
|
1002
|
+
* @returns {Promise<number>}
|
|
1003
|
+
*/
|
|
1004
|
+
async min(column) {
|
|
1005
|
+
return this._aggregate('MIN', column);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* Get the maximum value of a column
|
|
1010
|
+
* @param {string} column
|
|
1011
|
+
* @returns {Promise<number>}
|
|
1012
|
+
*/
|
|
1013
|
+
async max(column) {
|
|
1014
|
+
return this._aggregate('MAX', column);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* Execute an aggregate function on a column
|
|
1019
|
+
* @param {string} fn - SQL aggregate function
|
|
1020
|
+
* @param {string} column
|
|
1021
|
+
* @returns {Promise<number>}
|
|
1022
|
+
* @private
|
|
1023
|
+
*/
|
|
1024
|
+
async _aggregate(fn, column) {
|
|
1025
|
+
assertIdentifier(column, 'aggregate column');
|
|
1026
|
+
this._applyGlobalScopes();
|
|
1027
|
+
this._applySoftDeleteConstraints();
|
|
1028
|
+
|
|
1029
|
+
const result = await this.model.connection.aggregate(
|
|
1030
|
+
this.model.table,
|
|
1031
|
+
fn,
|
|
1032
|
+
column,
|
|
1033
|
+
this.buildQuery()
|
|
1034
|
+
);
|
|
1035
|
+
return result;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// ==================== Batch & Conditional Methods ====================
|
|
1039
|
+
|
|
1040
|
+
/**
|
|
1041
|
+
* Process query results in chunks
|
|
1042
|
+
* @param {number} size - Chunk size
|
|
1043
|
+
* @param {Function} callback - Receives (chunk, page). Return false to stop.
|
|
1044
|
+
* @returns {Promise<void>}
|
|
1045
|
+
*/
|
|
1046
|
+
async chunk(size, callback) {
|
|
1047
|
+
let page = 1;
|
|
1048
|
+
let offset = 0;
|
|
1049
|
+
|
|
1050
|
+
// eslint-disable-next-line no-constant-condition
|
|
1051
|
+
while (true) {
|
|
1052
|
+
const cloned = this.clone();
|
|
1053
|
+
cloned.limitValue = size;
|
|
1054
|
+
cloned.offsetValue = offset;
|
|
1055
|
+
const results = await cloned.get();
|
|
1056
|
+
|
|
1057
|
+
if (results.length === 0) break;
|
|
1058
|
+
|
|
1059
|
+
const shouldContinue = await callback(results, page);
|
|
1060
|
+
if (shouldContinue === false) break;
|
|
1061
|
+
if (results.length < size) break;
|
|
1062
|
+
|
|
1063
|
+
offset += size;
|
|
1064
|
+
page++;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
/**
|
|
1069
|
+
* Apply a callback to the query when a condition is truthy
|
|
1070
|
+
* @param {any} condition
|
|
1071
|
+
* @param {Function} callback - Receives the query builder when condition is truthy
|
|
1072
|
+
* @param {Function} [fallback] - Receives the query builder when condition is falsy
|
|
1073
|
+
* @returns {this}
|
|
1074
|
+
*/
|
|
1075
|
+
when(condition, callback, fallback) {
|
|
1076
|
+
if (condition) {
|
|
1077
|
+
callback(this, condition);
|
|
1078
|
+
} else if (typeof fallback === 'function') {
|
|
1079
|
+
fallback(this, condition);
|
|
1080
|
+
}
|
|
1081
|
+
return this;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* Pass the query builder to a callback for inspection without modifying the chain
|
|
1086
|
+
* @param {Function} callback
|
|
1087
|
+
* @returns {this}
|
|
1088
|
+
*/
|
|
1089
|
+
tap(callback) {
|
|
1090
|
+
callback(this);
|
|
1091
|
+
return this;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// ==================== Debugging ====================
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* Get the raw SQL representation of the current query (for debugging)
|
|
1098
|
+
* @returns {Object} Query object with all clauses
|
|
1099
|
+
*/
|
|
1100
|
+
toSQL() {
|
|
1101
|
+
this._applyGlobalScopes();
|
|
1102
|
+
this._applySoftDeleteConstraints();
|
|
1103
|
+
return {
|
|
1104
|
+
table: this.model.table,
|
|
1105
|
+
...this.buildQuery()
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/**
|
|
1110
|
+
* Dump the SQL representation and die (log + throw)
|
|
1111
|
+
*/
|
|
1112
|
+
dd() {
|
|
1113
|
+
const sql = this.toSQL();
|
|
1114
|
+
console.log('Query Dump:', JSON.stringify(sql, null, 2));
|
|
1115
|
+
throw new Error('dd(): Query dumped. See console output above.');
|
|
1116
|
+
}
|
|
1117
|
+
|
|
927
1118
|
/**
|
|
928
1119
|
* Build the query object
|
|
929
1120
|
* @returns {Object}
|