@plyaz/db 0.4.1 → 0.5.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/dist/builder/query/QueryBuilder.d.ts +850 -0
- package/dist/builder/query/QueryBuilder.d.ts.map +1 -0
- package/dist/builder/query/index.d.ts +2 -1
- package/dist/builder/query/index.d.ts.map +1 -1
- package/dist/index.cjs +1291 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1291 -7
- package/dist/index.mjs.map +1 -1
- package/dist/repository/BaseRepository.d.ts +72 -6
- package/dist/repository/BaseRepository.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -7180,6 +7180,1201 @@ async function createDatabaseService(config) {
|
|
|
7180
7180
|
}
|
|
7181
7181
|
__name(createDatabaseService, "createDatabaseService");
|
|
7182
7182
|
|
|
7183
|
+
// src/builder/query/QueryBuilder.ts
|
|
7184
|
+
var QueryBuilder = class _QueryBuilder {
|
|
7185
|
+
static {
|
|
7186
|
+
__name(this, "QueryBuilder");
|
|
7187
|
+
}
|
|
7188
|
+
_filters = [];
|
|
7189
|
+
_rawConditions = [];
|
|
7190
|
+
_sort = [];
|
|
7191
|
+
_pagination = {};
|
|
7192
|
+
_schema;
|
|
7193
|
+
_executor;
|
|
7194
|
+
_operationConfig;
|
|
7195
|
+
_countExecutor;
|
|
7196
|
+
// Advanced query features
|
|
7197
|
+
_joins = [];
|
|
7198
|
+
_groupByFields = [];
|
|
7199
|
+
_havingConditions = [];
|
|
7200
|
+
_selectFields = [];
|
|
7201
|
+
_selectRawExpressions = [];
|
|
7202
|
+
_distinct = false;
|
|
7203
|
+
/**
|
|
7204
|
+
* Create a new QueryBuilder instance (standalone)
|
|
7205
|
+
*
|
|
7206
|
+
* @returns New QueryBuilder instance
|
|
7207
|
+
*
|
|
7208
|
+
* @example
|
|
7209
|
+
* ```typescript
|
|
7210
|
+
* const builder = QueryBuilder.create<User>();
|
|
7211
|
+
* ```
|
|
7212
|
+
*/
|
|
7213
|
+
static create() {
|
|
7214
|
+
return new _QueryBuilder();
|
|
7215
|
+
}
|
|
7216
|
+
/**
|
|
7217
|
+
* Create a QueryBuilder bound to a repository for direct execution
|
|
7218
|
+
*
|
|
7219
|
+
* @param executor - Repository or object with findMany method
|
|
7220
|
+
* @returns New QueryBuilder instance bound to executor
|
|
7221
|
+
*
|
|
7222
|
+
* @example
|
|
7223
|
+
* ```typescript
|
|
7224
|
+
* // Usually called via repository.query()
|
|
7225
|
+
* const builder = QueryBuilder.forRepository(userRepository);
|
|
7226
|
+
* const result = await builder.where('status', 'eq', 'active').execute();
|
|
7227
|
+
* ```
|
|
7228
|
+
*/
|
|
7229
|
+
static forRepository(executor) {
|
|
7230
|
+
const builder = new _QueryBuilder();
|
|
7231
|
+
builder._executor = executor;
|
|
7232
|
+
return builder;
|
|
7233
|
+
}
|
|
7234
|
+
/**
|
|
7235
|
+
* Private constructor - use QueryBuilder.create() or forRepository() instead
|
|
7236
|
+
*/
|
|
7237
|
+
constructor() {
|
|
7238
|
+
}
|
|
7239
|
+
/**
|
|
7240
|
+
* Add a WHERE condition with AND logical operator
|
|
7241
|
+
*
|
|
7242
|
+
* @param field - Field name to filter on
|
|
7243
|
+
* @param operator - Comparison operator
|
|
7244
|
+
* @param value - Value to compare against
|
|
7245
|
+
* @returns This builder for chaining
|
|
7246
|
+
*
|
|
7247
|
+
* @example
|
|
7248
|
+
* ```typescript
|
|
7249
|
+
* builder.where('status', 'eq', 'active')
|
|
7250
|
+
* ```
|
|
7251
|
+
*/
|
|
7252
|
+
where(field, operator, value) {
|
|
7253
|
+
this._filters.push({
|
|
7254
|
+
field,
|
|
7255
|
+
operator,
|
|
7256
|
+
value,
|
|
7257
|
+
logical: this._filters.length === 0 ? void 0 : "and"
|
|
7258
|
+
});
|
|
7259
|
+
return this;
|
|
7260
|
+
}
|
|
7261
|
+
/**
|
|
7262
|
+
* Add a WHERE condition with explicit AND logical operator
|
|
7263
|
+
*
|
|
7264
|
+
* @param field - Field name to filter on
|
|
7265
|
+
* @param operator - Comparison operator
|
|
7266
|
+
* @param value - Value to compare against
|
|
7267
|
+
* @returns This builder for chaining
|
|
7268
|
+
*
|
|
7269
|
+
* @example
|
|
7270
|
+
* ```typescript
|
|
7271
|
+
* builder.where('status', 'eq', 'active')
|
|
7272
|
+
* .andWhere('role', 'eq', 'admin')
|
|
7273
|
+
* ```
|
|
7274
|
+
*/
|
|
7275
|
+
andWhere(field, operator, value) {
|
|
7276
|
+
this._filters.push({
|
|
7277
|
+
field,
|
|
7278
|
+
operator,
|
|
7279
|
+
value,
|
|
7280
|
+
logical: "and"
|
|
7281
|
+
});
|
|
7282
|
+
return this;
|
|
7283
|
+
}
|
|
7284
|
+
/**
|
|
7285
|
+
* Add a WHERE condition with OR logical operator
|
|
7286
|
+
*
|
|
7287
|
+
* @param field - Field name to filter on
|
|
7288
|
+
* @param operator - Comparison operator
|
|
7289
|
+
* @param value - Value to compare against
|
|
7290
|
+
* @returns This builder for chaining
|
|
7291
|
+
*
|
|
7292
|
+
* @example
|
|
7293
|
+
* ```typescript
|
|
7294
|
+
* builder.where('status', 'eq', 'active')
|
|
7295
|
+
* .orWhere('status', 'eq', 'pending')
|
|
7296
|
+
* ```
|
|
7297
|
+
*/
|
|
7298
|
+
orWhere(field, operator, value) {
|
|
7299
|
+
this._filters.push({
|
|
7300
|
+
field,
|
|
7301
|
+
operator,
|
|
7302
|
+
value,
|
|
7303
|
+
logical: "or"
|
|
7304
|
+
});
|
|
7305
|
+
return this;
|
|
7306
|
+
}
|
|
7307
|
+
/**
|
|
7308
|
+
* Add a WHERE IN condition
|
|
7309
|
+
*
|
|
7310
|
+
* @param field - Field name to filter on
|
|
7311
|
+
* @param values - Array of values to match
|
|
7312
|
+
* @returns This builder for chaining
|
|
7313
|
+
*
|
|
7314
|
+
* @example
|
|
7315
|
+
* ```typescript
|
|
7316
|
+
* builder.whereIn('status', ['active', 'pending', 'review'])
|
|
7317
|
+
* ```
|
|
7318
|
+
*/
|
|
7319
|
+
whereIn(field, values) {
|
|
7320
|
+
this._filters.push({
|
|
7321
|
+
field,
|
|
7322
|
+
operator: "in",
|
|
7323
|
+
value: values,
|
|
7324
|
+
logical: this._filters.length === 0 ? void 0 : "and"
|
|
7325
|
+
});
|
|
7326
|
+
return this;
|
|
7327
|
+
}
|
|
7328
|
+
/**
|
|
7329
|
+
* Add a WHERE NOT IN condition
|
|
7330
|
+
*
|
|
7331
|
+
* @param field - Field name to filter on
|
|
7332
|
+
* @param values - Array of values to exclude
|
|
7333
|
+
* @returns This builder for chaining
|
|
7334
|
+
*
|
|
7335
|
+
* @example
|
|
7336
|
+
* ```typescript
|
|
7337
|
+
* builder.whereNotIn('status', ['deleted', 'archived'])
|
|
7338
|
+
* ```
|
|
7339
|
+
*/
|
|
7340
|
+
whereNotIn(field, values) {
|
|
7341
|
+
this._filters.push({
|
|
7342
|
+
field,
|
|
7343
|
+
operator: "notIn",
|
|
7344
|
+
value: values,
|
|
7345
|
+
logical: this._filters.length === 0 ? void 0 : "and"
|
|
7346
|
+
});
|
|
7347
|
+
return this;
|
|
7348
|
+
}
|
|
7349
|
+
/**
|
|
7350
|
+
* Add a WHERE BETWEEN condition
|
|
7351
|
+
*
|
|
7352
|
+
* @param field - Field name to filter on
|
|
7353
|
+
* @param min - Minimum value (inclusive)
|
|
7354
|
+
* @param max - Maximum value (inclusive)
|
|
7355
|
+
* @returns This builder for chaining
|
|
7356
|
+
*
|
|
7357
|
+
* @example
|
|
7358
|
+
* ```typescript
|
|
7359
|
+
* builder.whereBetween('price', 10, 100)
|
|
7360
|
+
* builder.whereBetween('createdAt', startDate, endDate)
|
|
7361
|
+
* ```
|
|
7362
|
+
*/
|
|
7363
|
+
whereBetween(field, min, max) {
|
|
7364
|
+
this._filters.push({
|
|
7365
|
+
field,
|
|
7366
|
+
operator: "between",
|
|
7367
|
+
value: [min, max],
|
|
7368
|
+
logical: this._filters.length === 0 ? void 0 : "and"
|
|
7369
|
+
});
|
|
7370
|
+
return this;
|
|
7371
|
+
}
|
|
7372
|
+
/**
|
|
7373
|
+
* Add a WHERE LIKE condition (pattern matching)
|
|
7374
|
+
*
|
|
7375
|
+
* @param field - Field name to filter on
|
|
7376
|
+
* @param pattern - LIKE pattern (use % for wildcards)
|
|
7377
|
+
* @returns This builder for chaining
|
|
7378
|
+
*
|
|
7379
|
+
* @example
|
|
7380
|
+
* ```typescript
|
|
7381
|
+
* builder.whereLike('email', '%@example.com')
|
|
7382
|
+
* builder.whereLike('name', 'John%')
|
|
7383
|
+
* ```
|
|
7384
|
+
*/
|
|
7385
|
+
whereLike(field, pattern) {
|
|
7386
|
+
this._filters.push({
|
|
7387
|
+
field,
|
|
7388
|
+
operator: "like",
|
|
7389
|
+
value: pattern,
|
|
7390
|
+
logical: this._filters.length === 0 ? void 0 : "and"
|
|
7391
|
+
});
|
|
7392
|
+
return this;
|
|
7393
|
+
}
|
|
7394
|
+
/**
|
|
7395
|
+
* Add a WHERE IS NULL condition
|
|
7396
|
+
*
|
|
7397
|
+
* @param field - Field name to check for NULL
|
|
7398
|
+
* @returns This builder for chaining
|
|
7399
|
+
*
|
|
7400
|
+
* @example
|
|
7401
|
+
* ```typescript
|
|
7402
|
+
* builder.whereNull('deletedAt')
|
|
7403
|
+
* ```
|
|
7404
|
+
*/
|
|
7405
|
+
whereNull(field) {
|
|
7406
|
+
this._filters.push({
|
|
7407
|
+
field,
|
|
7408
|
+
operator: "isNull",
|
|
7409
|
+
value: null,
|
|
7410
|
+
logical: this._filters.length === 0 ? void 0 : "and"
|
|
7411
|
+
});
|
|
7412
|
+
return this;
|
|
7413
|
+
}
|
|
7414
|
+
/**
|
|
7415
|
+
* Add a WHERE IS NOT NULL condition
|
|
7416
|
+
*
|
|
7417
|
+
* @param field - Field name to check for non-NULL
|
|
7418
|
+
* @returns This builder for chaining
|
|
7419
|
+
*
|
|
7420
|
+
* @example
|
|
7421
|
+
* ```typescript
|
|
7422
|
+
* builder.whereNotNull('verifiedAt')
|
|
7423
|
+
* ```
|
|
7424
|
+
*/
|
|
7425
|
+
whereNotNull(field) {
|
|
7426
|
+
this._filters.push({
|
|
7427
|
+
field,
|
|
7428
|
+
operator: "isNotNull",
|
|
7429
|
+
value: null,
|
|
7430
|
+
logical: this._filters.length === 0 ? void 0 : "and"
|
|
7431
|
+
});
|
|
7432
|
+
return this;
|
|
7433
|
+
}
|
|
7434
|
+
/**
|
|
7435
|
+
* Add a raw SQL WHERE condition for complex queries
|
|
7436
|
+
*
|
|
7437
|
+
* Use this for conditions that cannot be expressed with the standard operators,
|
|
7438
|
+
* such as subqueries, JSON operations, full-text search, or database-specific functions.
|
|
7439
|
+
*
|
|
7440
|
+
* @param clause - Raw SQL clause (use $1, $2, etc. for parameter placeholders)
|
|
7441
|
+
* @param params - Parameter values for the clause (prevents SQL injection)
|
|
7442
|
+
* @returns This builder for chaining
|
|
7443
|
+
*
|
|
7444
|
+
* @example
|
|
7445
|
+
* ```typescript
|
|
7446
|
+
* // JSON field query (PostgreSQL)
|
|
7447
|
+
* builder.whereRaw('"metadata"->\'tags\' @> $1', [JSON.stringify(['featured'])])
|
|
7448
|
+
*
|
|
7449
|
+
* // Full-text search
|
|
7450
|
+
* builder.whereRaw('to_tsvector(name) @@ plainto_tsquery($1)', ['search term'])
|
|
7451
|
+
*
|
|
7452
|
+
* // Subquery
|
|
7453
|
+
* builder.whereRaw('"id" IN (SELECT user_id FROM orders WHERE total > $1)', [1000])
|
|
7454
|
+
*
|
|
7455
|
+
* // Date functions
|
|
7456
|
+
* builder.whereRaw('DATE_TRUNC(\'month\', "createdAt") = DATE_TRUNC(\'month\', $1)', [new Date()])
|
|
7457
|
+
*
|
|
7458
|
+
* // Complex conditions
|
|
7459
|
+
* builder
|
|
7460
|
+
* .where('status', 'eq', 'active')
|
|
7461
|
+
* .whereRaw('("score" > $1 OR "isPremium" = $2)', [80, true])
|
|
7462
|
+
* ```
|
|
7463
|
+
*/
|
|
7464
|
+
whereRaw(clause, params = []) {
|
|
7465
|
+
const hasConditions = this._filters.length > 0 || this._rawConditions.length > 0;
|
|
7466
|
+
this._rawConditions.push({
|
|
7467
|
+
clause,
|
|
7468
|
+
params,
|
|
7469
|
+
logical: hasConditions ? "and" : void 0
|
|
7470
|
+
});
|
|
7471
|
+
return this;
|
|
7472
|
+
}
|
|
7473
|
+
/**
|
|
7474
|
+
* Add a raw SQL WHERE condition with OR logical operator
|
|
7475
|
+
*
|
|
7476
|
+
* @param clause - Raw SQL clause (use $1, $2, etc. for parameter placeholders)
|
|
7477
|
+
* @param params - Parameter values for the clause
|
|
7478
|
+
* @returns This builder for chaining
|
|
7479
|
+
*
|
|
7480
|
+
* @example
|
|
7481
|
+
* ```typescript
|
|
7482
|
+
* builder
|
|
7483
|
+
* .where('status', 'eq', 'active')
|
|
7484
|
+
* .orWhereRaw('"metadata"->\'priority\' = $1', ['"high"'])
|
|
7485
|
+
* ```
|
|
7486
|
+
*/
|
|
7487
|
+
orWhereRaw(clause, params = []) {
|
|
7488
|
+
this._rawConditions.push({
|
|
7489
|
+
clause,
|
|
7490
|
+
params,
|
|
7491
|
+
logical: "or"
|
|
7492
|
+
});
|
|
7493
|
+
return this;
|
|
7494
|
+
}
|
|
7495
|
+
// ============================================================
|
|
7496
|
+
// SELECT Methods
|
|
7497
|
+
// ============================================================
|
|
7498
|
+
/**
|
|
7499
|
+
* Select specific fields (columns) to return
|
|
7500
|
+
*
|
|
7501
|
+
* @param fields - Field names to select
|
|
7502
|
+
* @returns This builder for chaining
|
|
7503
|
+
*
|
|
7504
|
+
* @example
|
|
7505
|
+
* ```typescript
|
|
7506
|
+
* builder.select('id', 'name', 'email')
|
|
7507
|
+
*
|
|
7508
|
+
* // With table prefix for joins
|
|
7509
|
+
* builder
|
|
7510
|
+
* .select('users.id', 'users.name', 'orders.total')
|
|
7511
|
+
* .leftJoin('orders', 'users.id = orders.user_id')
|
|
7512
|
+
* ```
|
|
7513
|
+
*/
|
|
7514
|
+
select(...fields) {
|
|
7515
|
+
this._selectFields.push(...fields);
|
|
7516
|
+
return this;
|
|
7517
|
+
}
|
|
7518
|
+
/**
|
|
7519
|
+
* Add raw SELECT expression
|
|
7520
|
+
*
|
|
7521
|
+
* @param expression - Raw SQL expression
|
|
7522
|
+
* @returns This builder for chaining
|
|
7523
|
+
*
|
|
7524
|
+
* @example
|
|
7525
|
+
* ```typescript
|
|
7526
|
+
* builder
|
|
7527
|
+
* .select('id', 'name')
|
|
7528
|
+
* .selectRaw('COUNT(*) as order_count')
|
|
7529
|
+
* .selectRaw('SUM(orders.total) as total_spent')
|
|
7530
|
+
* ```
|
|
7531
|
+
*/
|
|
7532
|
+
selectRaw(expression) {
|
|
7533
|
+
this._selectRawExpressions.push(expression);
|
|
7534
|
+
return this;
|
|
7535
|
+
}
|
|
7536
|
+
/**
|
|
7537
|
+
* Add DISTINCT to SELECT
|
|
7538
|
+
*
|
|
7539
|
+
* @returns This builder for chaining
|
|
7540
|
+
*
|
|
7541
|
+
* @example
|
|
7542
|
+
* ```typescript
|
|
7543
|
+
* builder
|
|
7544
|
+
* .distinct()
|
|
7545
|
+
* .select('category')
|
|
7546
|
+
* .orderBy('category', 'asc')
|
|
7547
|
+
* ```
|
|
7548
|
+
*/
|
|
7549
|
+
distinct() {
|
|
7550
|
+
this._distinct = true;
|
|
7551
|
+
return this;
|
|
7552
|
+
}
|
|
7553
|
+
// ============================================================
|
|
7554
|
+
// JOIN Methods
|
|
7555
|
+
// ============================================================
|
|
7556
|
+
/**
|
|
7557
|
+
* Add INNER JOIN clause
|
|
7558
|
+
*
|
|
7559
|
+
* @param table - Table to join
|
|
7560
|
+
* @param condition - Join condition
|
|
7561
|
+
* @param alias - Optional alias for the joined table
|
|
7562
|
+
* @returns This builder for chaining
|
|
7563
|
+
*
|
|
7564
|
+
* @example
|
|
7565
|
+
* ```typescript
|
|
7566
|
+
* builder
|
|
7567
|
+
* .join('orders', 'users.id = orders.user_id')
|
|
7568
|
+
* .where('orders.status', 'eq', 'completed')
|
|
7569
|
+
*
|
|
7570
|
+
* // With alias
|
|
7571
|
+
* builder.join('orders', 'u.id = o.user_id', 'o')
|
|
7572
|
+
*
|
|
7573
|
+
* // With schema
|
|
7574
|
+
* builder.innerJoin('analytics.events', 'users.id = events.user_id')
|
|
7575
|
+
* ```
|
|
7576
|
+
*/
|
|
7577
|
+
join(table, condition, alias) {
|
|
7578
|
+
return this.innerJoin(table, condition, alias);
|
|
7579
|
+
}
|
|
7580
|
+
/**
|
|
7581
|
+
* Add INNER JOIN clause (explicit)
|
|
7582
|
+
*
|
|
7583
|
+
* @param table - Table to join (can include schema: 'schema.table')
|
|
7584
|
+
* @param condition - Join condition
|
|
7585
|
+
* @param alias - Optional alias for the joined table
|
|
7586
|
+
* @returns This builder for chaining
|
|
7587
|
+
*/
|
|
7588
|
+
innerJoin(table, condition, alias) {
|
|
7589
|
+
const [schema, tableName] = this.parseTableName(table);
|
|
7590
|
+
this._joins.push({
|
|
7591
|
+
type: "inner",
|
|
7592
|
+
table: tableName,
|
|
7593
|
+
condition,
|
|
7594
|
+
alias,
|
|
7595
|
+
schema
|
|
7596
|
+
});
|
|
7597
|
+
return this;
|
|
7598
|
+
}
|
|
7599
|
+
/**
|
|
7600
|
+
* Add LEFT JOIN clause
|
|
7601
|
+
*
|
|
7602
|
+
* @param table - Table to join (can include schema: 'schema.table')
|
|
7603
|
+
* @param condition - Join condition
|
|
7604
|
+
* @param alias - Optional alias for the joined table
|
|
7605
|
+
* @returns This builder for chaining
|
|
7606
|
+
*
|
|
7607
|
+
* @example
|
|
7608
|
+
* ```typescript
|
|
7609
|
+
* builder
|
|
7610
|
+
* .leftJoin('orders', 'users.id = orders.user_id')
|
|
7611
|
+
* .select('users.*', 'orders.total')
|
|
7612
|
+
* ```
|
|
7613
|
+
*/
|
|
7614
|
+
leftJoin(table, condition, alias) {
|
|
7615
|
+
const [schema, tableName] = this.parseTableName(table);
|
|
7616
|
+
this._joins.push({
|
|
7617
|
+
type: "left",
|
|
7618
|
+
table: tableName,
|
|
7619
|
+
condition,
|
|
7620
|
+
alias,
|
|
7621
|
+
schema
|
|
7622
|
+
});
|
|
7623
|
+
return this;
|
|
7624
|
+
}
|
|
7625
|
+
/**
|
|
7626
|
+
* Add RIGHT JOIN clause
|
|
7627
|
+
*
|
|
7628
|
+
* @param table - Table to join (can include schema: 'schema.table')
|
|
7629
|
+
* @param condition - Join condition
|
|
7630
|
+
* @param alias - Optional alias for the joined table
|
|
7631
|
+
* @returns This builder for chaining
|
|
7632
|
+
*/
|
|
7633
|
+
rightJoin(table, condition, alias) {
|
|
7634
|
+
const [schema, tableName] = this.parseTableName(table);
|
|
7635
|
+
this._joins.push({
|
|
7636
|
+
type: "right",
|
|
7637
|
+
table: tableName,
|
|
7638
|
+
condition,
|
|
7639
|
+
alias,
|
|
7640
|
+
schema
|
|
7641
|
+
});
|
|
7642
|
+
return this;
|
|
7643
|
+
}
|
|
7644
|
+
/**
|
|
7645
|
+
* Add FULL OUTER JOIN clause
|
|
7646
|
+
*
|
|
7647
|
+
* @param table - Table to join (can include schema: 'schema.table')
|
|
7648
|
+
* @param condition - Join condition
|
|
7649
|
+
* @param alias - Optional alias for the joined table
|
|
7650
|
+
* @returns This builder for chaining
|
|
7651
|
+
*/
|
|
7652
|
+
fullJoin(table, condition, alias) {
|
|
7653
|
+
const [schema, tableName] = this.parseTableName(table);
|
|
7654
|
+
this._joins.push({
|
|
7655
|
+
type: "full",
|
|
7656
|
+
table: tableName,
|
|
7657
|
+
condition,
|
|
7658
|
+
alias,
|
|
7659
|
+
schema
|
|
7660
|
+
});
|
|
7661
|
+
return this;
|
|
7662
|
+
}
|
|
7663
|
+
/**
|
|
7664
|
+
* Parse table name that may include schema (schema.table)
|
|
7665
|
+
*/
|
|
7666
|
+
parseTableName(table) {
|
|
7667
|
+
const SCHEMA_TABLE_PARTS = 2;
|
|
7668
|
+
const parts = table.split(".");
|
|
7669
|
+
if (parts.length === SCHEMA_TABLE_PARTS) {
|
|
7670
|
+
return [parts[0], parts[1]];
|
|
7671
|
+
}
|
|
7672
|
+
return [void 0, table];
|
|
7673
|
+
}
|
|
7674
|
+
// ============================================================
|
|
7675
|
+
// GROUP BY / HAVING Methods
|
|
7676
|
+
// ============================================================
|
|
7677
|
+
/**
|
|
7678
|
+
* Add GROUP BY clause
|
|
7679
|
+
*
|
|
7680
|
+
* @param fields - Fields to group by
|
|
7681
|
+
* @returns This builder for chaining
|
|
7682
|
+
*
|
|
7683
|
+
* @example
|
|
7684
|
+
* ```typescript
|
|
7685
|
+
* builder
|
|
7686
|
+
* .select('status')
|
|
7687
|
+
* .selectRaw('COUNT(*) as count')
|
|
7688
|
+
* .groupBy('status')
|
|
7689
|
+
*
|
|
7690
|
+
* // Multiple fields
|
|
7691
|
+
* builder
|
|
7692
|
+
* .select('region', 'status')
|
|
7693
|
+
* .selectRaw('SUM(amount) as total')
|
|
7694
|
+
* .groupBy('region', 'status')
|
|
7695
|
+
* ```
|
|
7696
|
+
*/
|
|
7697
|
+
groupBy(...fields) {
|
|
7698
|
+
this._groupByFields.push(...fields);
|
|
7699
|
+
return this;
|
|
7700
|
+
}
|
|
7701
|
+
/**
|
|
7702
|
+
* Add HAVING condition (for use with GROUP BY)
|
|
7703
|
+
*
|
|
7704
|
+
* @param clause - Raw SQL HAVING condition (use $1, $2 for params)
|
|
7705
|
+
* @param params - Parameter values
|
|
7706
|
+
* @returns This builder for chaining
|
|
7707
|
+
*
|
|
7708
|
+
* @example
|
|
7709
|
+
* ```typescript
|
|
7710
|
+
* builder
|
|
7711
|
+
* .select('status')
|
|
7712
|
+
* .selectRaw('COUNT(*) as count')
|
|
7713
|
+
* .groupBy('status')
|
|
7714
|
+
* .having('COUNT(*) > $1', [10])
|
|
7715
|
+
*
|
|
7716
|
+
* // Multiple having conditions
|
|
7717
|
+
* builder
|
|
7718
|
+
* .groupBy('category')
|
|
7719
|
+
* .selectRaw('AVG(price) as avg_price')
|
|
7720
|
+
* .having('AVG(price) > $1', [100])
|
|
7721
|
+
* .having('COUNT(*) >= $1', [5])
|
|
7722
|
+
* ```
|
|
7723
|
+
*/
|
|
7724
|
+
having(clause, params = []) {
|
|
7725
|
+
this._havingConditions.push({
|
|
7726
|
+
clause,
|
|
7727
|
+
params,
|
|
7728
|
+
logical: this._havingConditions.length === 0 ? void 0 : "and"
|
|
7729
|
+
});
|
|
7730
|
+
return this;
|
|
7731
|
+
}
|
|
7732
|
+
/**
|
|
7733
|
+
* Add HAVING condition with OR
|
|
7734
|
+
*
|
|
7735
|
+
* @param clause - Raw SQL HAVING condition
|
|
7736
|
+
* @param params - Parameter values
|
|
7737
|
+
* @returns This builder for chaining
|
|
7738
|
+
*/
|
|
7739
|
+
orHaving(clause, params = []) {
|
|
7740
|
+
this._havingConditions.push({
|
|
7741
|
+
clause,
|
|
7742
|
+
params,
|
|
7743
|
+
logical: "or"
|
|
7744
|
+
});
|
|
7745
|
+
return this;
|
|
7746
|
+
}
|
|
7747
|
+
/**
|
|
7748
|
+
* Add ORDER BY clause
|
|
7749
|
+
*
|
|
7750
|
+
* @param field - Field name to sort by
|
|
7751
|
+
* @param direction - Sort direction ('asc' or 'desc')
|
|
7752
|
+
* @returns This builder for chaining
|
|
7753
|
+
*
|
|
7754
|
+
* @example
|
|
7755
|
+
* ```typescript
|
|
7756
|
+
* builder.orderBy('createdAt', 'desc')
|
|
7757
|
+
* ```
|
|
7758
|
+
*/
|
|
7759
|
+
orderBy(field, direction = "asc") {
|
|
7760
|
+
this._sort.push({ field, direction });
|
|
7761
|
+
return this;
|
|
7762
|
+
}
|
|
7763
|
+
/**
|
|
7764
|
+
* Add ORDER BY ASC clause (convenience method)
|
|
7765
|
+
*
|
|
7766
|
+
* @param field - Field name to sort by ascending
|
|
7767
|
+
* @returns This builder for chaining
|
|
7768
|
+
*
|
|
7769
|
+
* @example
|
|
7770
|
+
* ```typescript
|
|
7771
|
+
* builder.orderByAsc('name')
|
|
7772
|
+
* ```
|
|
7773
|
+
*/
|
|
7774
|
+
orderByAsc(field) {
|
|
7775
|
+
return this.orderBy(field, "asc");
|
|
7776
|
+
}
|
|
7777
|
+
/**
|
|
7778
|
+
* Add ORDER BY DESC clause (convenience method)
|
|
7779
|
+
*
|
|
7780
|
+
* @param field - Field name to sort by descending
|
|
7781
|
+
* @returns This builder for chaining
|
|
7782
|
+
*
|
|
7783
|
+
* @example
|
|
7784
|
+
* ```typescript
|
|
7785
|
+
* builder.orderByDesc('createdAt')
|
|
7786
|
+
* ```
|
|
7787
|
+
*/
|
|
7788
|
+
orderByDesc(field) {
|
|
7789
|
+
return this.orderBy(field, "desc");
|
|
7790
|
+
}
|
|
7791
|
+
/**
|
|
7792
|
+
* Set LIMIT for results
|
|
7793
|
+
*
|
|
7794
|
+
* @param limit - Maximum number of results to return
|
|
7795
|
+
* @returns This builder for chaining
|
|
7796
|
+
*
|
|
7797
|
+
* @example
|
|
7798
|
+
* ```typescript
|
|
7799
|
+
* builder.limit(20)
|
|
7800
|
+
* ```
|
|
7801
|
+
*/
|
|
7802
|
+
limit(limit) {
|
|
7803
|
+
this._pagination.limit = limit;
|
|
7804
|
+
return this;
|
|
7805
|
+
}
|
|
7806
|
+
/**
|
|
7807
|
+
* Set OFFSET for results
|
|
7808
|
+
*
|
|
7809
|
+
* @param offset - Number of results to skip
|
|
7810
|
+
* @returns This builder for chaining
|
|
7811
|
+
*
|
|
7812
|
+
* @example
|
|
7813
|
+
* ```typescript
|
|
7814
|
+
* builder.offset(40)
|
|
7815
|
+
* ```
|
|
7816
|
+
*/
|
|
7817
|
+
offset(offset) {
|
|
7818
|
+
this._pagination.offset = offset;
|
|
7819
|
+
return this;
|
|
7820
|
+
}
|
|
7821
|
+
/**
|
|
7822
|
+
* Set pagination by page number
|
|
7823
|
+
*
|
|
7824
|
+
* @param page - Page number (1-indexed)
|
|
7825
|
+
* @param pageSize - Number of items per page
|
|
7826
|
+
* @returns This builder for chaining
|
|
7827
|
+
*
|
|
7828
|
+
* @example
|
|
7829
|
+
* ```typescript
|
|
7830
|
+
* builder.paginate(2, 25) // Page 2, 25 items per page
|
|
7831
|
+
* ```
|
|
7832
|
+
*/
|
|
7833
|
+
paginate(page, pageSize) {
|
|
7834
|
+
this._pagination.limit = pageSize;
|
|
7835
|
+
this._pagination.offset = (page - 1) * pageSize;
|
|
7836
|
+
return this;
|
|
7837
|
+
}
|
|
7838
|
+
/**
|
|
7839
|
+
* Set cursor for cursor-based pagination
|
|
7840
|
+
*
|
|
7841
|
+
* @param cursor - Cursor string from previous query
|
|
7842
|
+
* @returns This builder for chaining
|
|
7843
|
+
*
|
|
7844
|
+
* @example
|
|
7845
|
+
* ```typescript
|
|
7846
|
+
* builder.afterCursor('eyJpZCI6MTIzfQ==')
|
|
7847
|
+
* ```
|
|
7848
|
+
*/
|
|
7849
|
+
afterCursor(cursor) {
|
|
7850
|
+
this._pagination.cursor = cursor;
|
|
7851
|
+
return this;
|
|
7852
|
+
}
|
|
7853
|
+
/**
|
|
7854
|
+
* Set database schema
|
|
7855
|
+
*
|
|
7856
|
+
* @param schema - Schema name to query from
|
|
7857
|
+
* @returns This builder for chaining
|
|
7858
|
+
*
|
|
7859
|
+
* @example
|
|
7860
|
+
* ```typescript
|
|
7861
|
+
* builder.schema('analytics')
|
|
7862
|
+
* ```
|
|
7863
|
+
*/
|
|
7864
|
+
schema(schema) {
|
|
7865
|
+
this._schema = schema;
|
|
7866
|
+
return this;
|
|
7867
|
+
}
|
|
7868
|
+
/**
|
|
7869
|
+
* Set operation config for execute()
|
|
7870
|
+
*
|
|
7871
|
+
* @param config - Operation configuration (adapter, schema override, etc.)
|
|
7872
|
+
* @returns This builder for chaining
|
|
7873
|
+
*
|
|
7874
|
+
* @example
|
|
7875
|
+
* ```typescript
|
|
7876
|
+
* const result = await userRepository.query()
|
|
7877
|
+
* .where('status', 'eq', 'active')
|
|
7878
|
+
* .withConfig({ adapter: 'analytics' })
|
|
7879
|
+
* .execute();
|
|
7880
|
+
* ```
|
|
7881
|
+
*/
|
|
7882
|
+
withConfig(config) {
|
|
7883
|
+
this._operationConfig = config;
|
|
7884
|
+
return this;
|
|
7885
|
+
}
|
|
7886
|
+
/**
|
|
7887
|
+
* Execute the query using the bound repository
|
|
7888
|
+
*
|
|
7889
|
+
* Requires QueryBuilder to be created via repository.query() or forRepository()
|
|
7890
|
+
*
|
|
7891
|
+
* @returns Promise resolving to paginated results
|
|
7892
|
+
* @throws Error if no executor is bound
|
|
7893
|
+
*
|
|
7894
|
+
* @example
|
|
7895
|
+
* ```typescript
|
|
7896
|
+
* const result = await userRepository.query()
|
|
7897
|
+
* .where('status', 'eq', 'active')
|
|
7898
|
+
* .orderByDesc('createdAt')
|
|
7899
|
+
* .limit(20)
|
|
7900
|
+
* .execute();
|
|
7901
|
+
*
|
|
7902
|
+
* if (result.success) {
|
|
7903
|
+
* console.log('Found users:', result.value.data);
|
|
7904
|
+
* }
|
|
7905
|
+
* ```
|
|
7906
|
+
*/
|
|
7907
|
+
async execute() {
|
|
7908
|
+
if (!this._executor) {
|
|
7909
|
+
throw new Error(
|
|
7910
|
+
"QueryBuilder has no executor. Use repository.query() or QueryBuilder.forRepository() to enable execute()."
|
|
7911
|
+
);
|
|
7912
|
+
}
|
|
7913
|
+
return this._executor.findMany(this.build(), this._operationConfig);
|
|
7914
|
+
}
|
|
7915
|
+
/**
|
|
7916
|
+
* Execute and return just the data array (convenience method)
|
|
7917
|
+
*
|
|
7918
|
+
* @returns Promise resolving to array of records
|
|
7919
|
+
* @throws Error if query fails or no executor
|
|
7920
|
+
*
|
|
7921
|
+
* @example
|
|
7922
|
+
* ```typescript
|
|
7923
|
+
* const users = await userRepository.query()
|
|
7924
|
+
* .where('status', 'eq', 'active')
|
|
7925
|
+
* .getMany();
|
|
7926
|
+
* ```
|
|
7927
|
+
*/
|
|
7928
|
+
async getMany() {
|
|
7929
|
+
const result = await this.execute();
|
|
7930
|
+
if (!result.success || !result.value) {
|
|
7931
|
+
throw result.error ?? new Error("Query failed");
|
|
7932
|
+
}
|
|
7933
|
+
return result.value.data;
|
|
7934
|
+
}
|
|
7935
|
+
/**
|
|
7936
|
+
* Execute and return the first result (convenience method)
|
|
7937
|
+
*
|
|
7938
|
+
* @returns Promise resolving to first record or null
|
|
7939
|
+
* @throws Error if query fails or no executor
|
|
7940
|
+
*
|
|
7941
|
+
* @example
|
|
7942
|
+
* ```typescript
|
|
7943
|
+
* const user = await userRepository.query()
|
|
7944
|
+
* .where('email', 'eq', 'john@example.com')
|
|
7945
|
+
* .getOne();
|
|
7946
|
+
* ```
|
|
7947
|
+
*/
|
|
7948
|
+
async getOne() {
|
|
7949
|
+
const originalLimit = this._pagination.limit;
|
|
7950
|
+
this._pagination.limit = 1;
|
|
7951
|
+
const result = await this.execute();
|
|
7952
|
+
this._pagination.limit = originalLimit;
|
|
7953
|
+
if (!result.success || !result.value) {
|
|
7954
|
+
throw result.error ?? new Error("Query failed");
|
|
7955
|
+
}
|
|
7956
|
+
return result.value.data[0] ?? null;
|
|
7957
|
+
}
|
|
7958
|
+
/**
|
|
7959
|
+
* Execute a count query
|
|
7960
|
+
*
|
|
7961
|
+
* Returns the count of records matching the filter conditions.
|
|
7962
|
+
* Requires QueryBuilder to be created via repository.query() or forRepository()
|
|
7963
|
+
*
|
|
7964
|
+
* @returns Promise resolving to the count
|
|
7965
|
+
* @throws Error if no executor or count not supported
|
|
7966
|
+
*
|
|
7967
|
+
* @example
|
|
7968
|
+
* ```typescript
|
|
7969
|
+
* const count = await userRepository.query()
|
|
7970
|
+
* .where('status', 'eq', 'active')
|
|
7971
|
+
* .count();
|
|
7972
|
+
*
|
|
7973
|
+
* // Complex count
|
|
7974
|
+
* const premiumCount = await userRepository.query()
|
|
7975
|
+
* .where('role', 'eq', 'premium')
|
|
7976
|
+
* .andWhere('verified', 'eq', true)
|
|
7977
|
+
* .count();
|
|
7978
|
+
* ```
|
|
7979
|
+
*/
|
|
7980
|
+
async count() {
|
|
7981
|
+
if (!this._executor) {
|
|
7982
|
+
throw new Error(
|
|
7983
|
+
"QueryBuilder has no executor. Use repository.query() to enable count()."
|
|
7984
|
+
);
|
|
7985
|
+
}
|
|
7986
|
+
if (!this._executor.count) {
|
|
7987
|
+
throw new Error("Executor does not support count().");
|
|
7988
|
+
}
|
|
7989
|
+
const filter = this._filters.length > 0 ? this._filters[0] : void 0;
|
|
7990
|
+
const result = await this._executor.count(filter, this._operationConfig);
|
|
7991
|
+
if (!result.success) {
|
|
7992
|
+
throw result.error ?? new Error("Count query failed");
|
|
7993
|
+
}
|
|
7994
|
+
return result.value ?? 0;
|
|
7995
|
+
}
|
|
7996
|
+
/**
|
|
7997
|
+
* Check if any records exist matching the conditions
|
|
7998
|
+
*
|
|
7999
|
+
* @returns Promise resolving to true if at least one record exists
|
|
8000
|
+
* @throws Error if query fails or no executor
|
|
8001
|
+
*
|
|
8002
|
+
* @example
|
|
8003
|
+
* ```typescript
|
|
8004
|
+
* const hasActiveUsers = await userRepository.query()
|
|
8005
|
+
* .where('status', 'eq', 'active')
|
|
8006
|
+
* .exists();
|
|
8007
|
+
* ```
|
|
8008
|
+
*/
|
|
8009
|
+
async exists() {
|
|
8010
|
+
const count = await this.count();
|
|
8011
|
+
return count > 0;
|
|
8012
|
+
}
|
|
8013
|
+
/**
|
|
8014
|
+
* Build QueryOptions for BaseRepository.findMany()
|
|
8015
|
+
*
|
|
8016
|
+
* For single filter queries, returns standard QueryOptions.
|
|
8017
|
+
* For multi-filter queries, returns the first filter (use toFilters() for full array).
|
|
8018
|
+
*
|
|
8019
|
+
* @returns QueryOptions compatible with BaseRepository
|
|
8020
|
+
*
|
|
8021
|
+
* @example
|
|
8022
|
+
* ```typescript
|
|
8023
|
+
* const options = builder.build();
|
|
8024
|
+
* const result = await repository.findMany(options);
|
|
8025
|
+
* ```
|
|
8026
|
+
*/
|
|
8027
|
+
build() {
|
|
8028
|
+
const options = {};
|
|
8029
|
+
if (this._filters.length > 0) {
|
|
8030
|
+
options.filter = this._filters[0];
|
|
8031
|
+
}
|
|
8032
|
+
if (this._sort.length > 0) {
|
|
8033
|
+
options.sort = this._sort;
|
|
8034
|
+
}
|
|
8035
|
+
if (this._pagination.limit !== void 0 || this._pagination.offset !== void 0 || this._pagination.cursor !== void 0) {
|
|
8036
|
+
options.pagination = this._pagination;
|
|
8037
|
+
}
|
|
8038
|
+
if (this._schema) {
|
|
8039
|
+
options.schema = this._schema;
|
|
8040
|
+
}
|
|
8041
|
+
return options;
|
|
8042
|
+
}
|
|
8043
|
+
/**
|
|
8044
|
+
* Build and return full result including filters array and raw conditions
|
|
8045
|
+
*
|
|
8046
|
+
* Use this when you need access to all filters for direct SQL building
|
|
8047
|
+
* or when using multi-condition queries with raw SQL.
|
|
8048
|
+
*
|
|
8049
|
+
* @returns QueryBuilderResult with options, filters, and raw conditions
|
|
8050
|
+
*
|
|
8051
|
+
* @example
|
|
8052
|
+
* ```typescript
|
|
8053
|
+
* const { options, filters, rawConditions } = builder.buildFull();
|
|
8054
|
+
*
|
|
8055
|
+
* // Use options for repository
|
|
8056
|
+
* const result = await repository.findMany(options);
|
|
8057
|
+
*
|
|
8058
|
+
* // Use filters for custom SQL building
|
|
8059
|
+
* const whereClause = buildWhereClause(filters);
|
|
8060
|
+
*
|
|
8061
|
+
* // Append raw conditions manually
|
|
8062
|
+
* rawConditions.forEach(raw => {
|
|
8063
|
+
* whereClause += ` ${raw.logical?.toUpperCase() ?? ''} ${raw.clause}`;
|
|
8064
|
+
* });
|
|
8065
|
+
* ```
|
|
8066
|
+
*/
|
|
8067
|
+
buildFull() {
|
|
8068
|
+
const result = {
|
|
8069
|
+
options: this.build(),
|
|
8070
|
+
filters: [...this._filters],
|
|
8071
|
+
rawConditions: [...this._rawConditions],
|
|
8072
|
+
joins: [...this._joins]
|
|
8073
|
+
};
|
|
8074
|
+
if (this._groupByFields.length > 0) {
|
|
8075
|
+
result.groupBy = {
|
|
8076
|
+
fields: [...this._groupByFields],
|
|
8077
|
+
having: this._havingConditions.length > 0 ? this._havingConditions.map((h) => ({ ...h })) : void 0
|
|
8078
|
+
};
|
|
8079
|
+
}
|
|
8080
|
+
if (this._selectFields.length > 0 || this._selectRawExpressions.length > 0 || this._distinct) {
|
|
8081
|
+
result.select = {
|
|
8082
|
+
fields: [...this._selectFields],
|
|
8083
|
+
rawExpressions: [...this._selectRawExpressions],
|
|
8084
|
+
distinct: this._distinct
|
|
8085
|
+
};
|
|
8086
|
+
}
|
|
8087
|
+
return result;
|
|
8088
|
+
}
|
|
8089
|
+
/**
|
|
8090
|
+
* Get array of all filters
|
|
8091
|
+
*
|
|
8092
|
+
* Use for direct SQL building with buildWhereClause()
|
|
8093
|
+
*
|
|
8094
|
+
* @returns Array of Filter objects
|
|
8095
|
+
*
|
|
8096
|
+
* @example
|
|
8097
|
+
* ```typescript
|
|
8098
|
+
* const filters = builder.toFilters();
|
|
8099
|
+
* const whereClause = buildWhereClause(filters);
|
|
8100
|
+
* ```
|
|
8101
|
+
*/
|
|
8102
|
+
toFilters() {
|
|
8103
|
+
return [...this._filters];
|
|
8104
|
+
}
|
|
8105
|
+
/**
|
|
8106
|
+
* Get array of raw SQL conditions
|
|
8107
|
+
*
|
|
8108
|
+
* Use for manual SQL building with complex conditions
|
|
8109
|
+
*
|
|
8110
|
+
* @returns Array of RawCondition objects
|
|
8111
|
+
*
|
|
8112
|
+
* @example
|
|
8113
|
+
* ```typescript
|
|
8114
|
+
* const rawConditions = builder.toRawConditions();
|
|
8115
|
+
* // Manually append to WHERE clause
|
|
8116
|
+
* ```
|
|
8117
|
+
*/
|
|
8118
|
+
toRawConditions() {
|
|
8119
|
+
return [...this._rawConditions];
|
|
8120
|
+
}
|
|
8121
|
+
/**
|
|
8122
|
+
* Get array of sort options
|
|
8123
|
+
*
|
|
8124
|
+
* @returns Array of SortOptions
|
|
8125
|
+
*/
|
|
8126
|
+
toSortOptions() {
|
|
8127
|
+
return [...this._sort];
|
|
8128
|
+
}
|
|
8129
|
+
/**
|
|
8130
|
+
* Get pagination options
|
|
8131
|
+
*
|
|
8132
|
+
* @returns PaginationOptions
|
|
8133
|
+
*/
|
|
8134
|
+
toPaginationOptions() {
|
|
8135
|
+
return { ...this._pagination };
|
|
8136
|
+
}
|
|
8137
|
+
/**
|
|
8138
|
+
* Check if any filters are defined (standard or raw)
|
|
8139
|
+
*
|
|
8140
|
+
* @returns True if filters exist
|
|
8141
|
+
*/
|
|
8142
|
+
hasFilters() {
|
|
8143
|
+
return this._filters.length > 0 || this._rawConditions.length > 0;
|
|
8144
|
+
}
|
|
8145
|
+
/**
|
|
8146
|
+
* Check if raw SQL conditions are defined
|
|
8147
|
+
*
|
|
8148
|
+
* @returns True if raw conditions exist
|
|
8149
|
+
*/
|
|
8150
|
+
hasRawConditions() {
|
|
8151
|
+
return this._rawConditions.length > 0;
|
|
8152
|
+
}
|
|
8153
|
+
/**
|
|
8154
|
+
* Check if sorting is defined
|
|
8155
|
+
*
|
|
8156
|
+
* @returns True if sort options exist
|
|
8157
|
+
*/
|
|
8158
|
+
hasSort() {
|
|
8159
|
+
return this._sort.length > 0;
|
|
8160
|
+
}
|
|
8161
|
+
/**
|
|
8162
|
+
* Check if pagination is defined
|
|
8163
|
+
*
|
|
8164
|
+
* @returns True if pagination options exist
|
|
8165
|
+
*/
|
|
8166
|
+
hasPagination() {
|
|
8167
|
+
return this._pagination.limit !== void 0 || this._pagination.offset !== void 0 || this._pagination.cursor !== void 0;
|
|
8168
|
+
}
|
|
8169
|
+
/**
|
|
8170
|
+
* Reset all query parameters
|
|
8171
|
+
*
|
|
8172
|
+
* @returns This builder for chaining
|
|
8173
|
+
*/
|
|
8174
|
+
reset() {
|
|
8175
|
+
this._filters = [];
|
|
8176
|
+
this._rawConditions = [];
|
|
8177
|
+
this._sort = [];
|
|
8178
|
+
this._pagination = {};
|
|
8179
|
+
this._schema = void 0;
|
|
8180
|
+
this._operationConfig = void 0;
|
|
8181
|
+
this._joins = [];
|
|
8182
|
+
this._groupByFields = [];
|
|
8183
|
+
this._havingConditions = [];
|
|
8184
|
+
this._selectFields = [];
|
|
8185
|
+
this._selectRawExpressions = [];
|
|
8186
|
+
this._distinct = false;
|
|
8187
|
+
return this;
|
|
8188
|
+
}
|
|
8189
|
+
/**
|
|
8190
|
+
* Clone this query builder
|
|
8191
|
+
*
|
|
8192
|
+
* @returns New QueryBuilder with same parameters
|
|
8193
|
+
*
|
|
8194
|
+
* @example
|
|
8195
|
+
* ```typescript
|
|
8196
|
+
* const baseQuery = QueryBuilder.create<User>()
|
|
8197
|
+
* .where('status', 'eq', 'active');
|
|
8198
|
+
*
|
|
8199
|
+
* const adminQuery = baseQuery.clone()
|
|
8200
|
+
* .andWhere('role', 'eq', 'admin');
|
|
8201
|
+
*
|
|
8202
|
+
* const userQuery = baseQuery.clone()
|
|
8203
|
+
* .andWhere('role', 'eq', 'user');
|
|
8204
|
+
* ```
|
|
8205
|
+
*/
|
|
8206
|
+
clone() {
|
|
8207
|
+
const cloned = new _QueryBuilder();
|
|
8208
|
+
cloned._filters = [...this._filters];
|
|
8209
|
+
cloned._rawConditions = this._rawConditions.map((rc) => ({ ...rc }));
|
|
8210
|
+
cloned._sort = [...this._sort];
|
|
8211
|
+
cloned._pagination = { ...this._pagination };
|
|
8212
|
+
cloned._schema = this._schema;
|
|
8213
|
+
cloned._executor = this._executor;
|
|
8214
|
+
cloned._operationConfig = this._operationConfig ? { ...this._operationConfig } : void 0;
|
|
8215
|
+
cloned._joins = this._joins.map((j) => ({ ...j }));
|
|
8216
|
+
cloned._groupByFields = [...this._groupByFields];
|
|
8217
|
+
cloned._havingConditions = this._havingConditions.map((h) => ({ ...h }));
|
|
8218
|
+
cloned._selectFields = [...this._selectFields];
|
|
8219
|
+
cloned._selectRawExpressions = [...this._selectRawExpressions];
|
|
8220
|
+
cloned._distinct = this._distinct;
|
|
8221
|
+
return cloned;
|
|
8222
|
+
}
|
|
8223
|
+
// ============================================================
|
|
8224
|
+
// Additional Helper Methods
|
|
8225
|
+
// ============================================================
|
|
8226
|
+
/**
|
|
8227
|
+
* Check if JOINs are defined
|
|
8228
|
+
*
|
|
8229
|
+
* @returns True if joins exist
|
|
8230
|
+
*/
|
|
8231
|
+
hasJoins() {
|
|
8232
|
+
return this._joins.length > 0;
|
|
8233
|
+
}
|
|
8234
|
+
/**
|
|
8235
|
+
* Check if GROUP BY is defined
|
|
8236
|
+
*
|
|
8237
|
+
* @returns True if group by fields exist
|
|
8238
|
+
*/
|
|
8239
|
+
hasGroupBy() {
|
|
8240
|
+
return this._groupByFields.length > 0;
|
|
8241
|
+
}
|
|
8242
|
+
/**
|
|
8243
|
+
* Check if custom SELECT is defined
|
|
8244
|
+
*
|
|
8245
|
+
* @returns True if select fields or expressions exist
|
|
8246
|
+
*/
|
|
8247
|
+
hasSelect() {
|
|
8248
|
+
return this._selectFields.length > 0 || this._selectRawExpressions.length > 0 || this._distinct;
|
|
8249
|
+
}
|
|
8250
|
+
/**
|
|
8251
|
+
* Get JOIN clauses
|
|
8252
|
+
*
|
|
8253
|
+
* @returns Array of JoinClause objects
|
|
8254
|
+
*/
|
|
8255
|
+
toJoins() {
|
|
8256
|
+
return [...this._joins];
|
|
8257
|
+
}
|
|
8258
|
+
/**
|
|
8259
|
+
* Get GROUP BY fields
|
|
8260
|
+
*
|
|
8261
|
+
* @returns Array of field names
|
|
8262
|
+
*/
|
|
8263
|
+
toGroupByFields() {
|
|
8264
|
+
return [...this._groupByFields];
|
|
8265
|
+
}
|
|
8266
|
+
/**
|
|
8267
|
+
* Get HAVING conditions
|
|
8268
|
+
*
|
|
8269
|
+
* @returns Array of RawCondition objects
|
|
8270
|
+
*/
|
|
8271
|
+
toHavingConditions() {
|
|
8272
|
+
return [...this._havingConditions];
|
|
8273
|
+
}
|
|
8274
|
+
/**
|
|
8275
|
+
* Get SELECT fields
|
|
8276
|
+
*
|
|
8277
|
+
* @returns Array of field names
|
|
8278
|
+
*/
|
|
8279
|
+
toSelectFields() {
|
|
8280
|
+
return [...this._selectFields];
|
|
8281
|
+
}
|
|
8282
|
+
/**
|
|
8283
|
+
* Get SELECT raw expressions
|
|
8284
|
+
*
|
|
8285
|
+
* @returns Array of raw SQL expressions
|
|
8286
|
+
*/
|
|
8287
|
+
toSelectRawExpressions() {
|
|
8288
|
+
return [...this._selectRawExpressions];
|
|
8289
|
+
}
|
|
8290
|
+
/**
|
|
8291
|
+
* Check if DISTINCT is enabled
|
|
8292
|
+
*
|
|
8293
|
+
* @returns True if distinct is enabled
|
|
8294
|
+
*/
|
|
8295
|
+
isDistinct() {
|
|
8296
|
+
return this._distinct;
|
|
8297
|
+
}
|
|
8298
|
+
/**
|
|
8299
|
+
* Generate SQL query string (for debugging/logging)
|
|
8300
|
+
*
|
|
8301
|
+
* Note: This is a simplified SQL representation. The actual query
|
|
8302
|
+
* execution uses the adapter's SQL generation.
|
|
8303
|
+
*
|
|
8304
|
+
* @param tableName - Base table name
|
|
8305
|
+
* @returns SQL query string
|
|
8306
|
+
*
|
|
8307
|
+
* @example
|
|
8308
|
+
* ```typescript
|
|
8309
|
+
* const sql = builder
|
|
8310
|
+
* .select('id', 'name')
|
|
8311
|
+
* .leftJoin('orders', 'users.id = orders.user_id')
|
|
8312
|
+
* .where('status', 'eq', 'active')
|
|
8313
|
+
* .toSQL('users');
|
|
8314
|
+
*
|
|
8315
|
+
* console.log(sql);
|
|
8316
|
+
* // SELECT "id", "name" FROM "users"
|
|
8317
|
+
* // LEFT JOIN "orders" ON users.id = orders.user_id
|
|
8318
|
+
* // WHERE "status" = $1
|
|
8319
|
+
* ```
|
|
8320
|
+
*/
|
|
8321
|
+
// eslint-disable-next-line complexity
|
|
8322
|
+
toSQL(tableName) {
|
|
8323
|
+
const parts = [];
|
|
8324
|
+
const selectParts = [];
|
|
8325
|
+
if (this._selectFields.length > 0) {
|
|
8326
|
+
selectParts.push(...this._selectFields.map((f) => `"${f}"`));
|
|
8327
|
+
}
|
|
8328
|
+
if (this._selectRawExpressions.length > 0) {
|
|
8329
|
+
selectParts.push(...this._selectRawExpressions);
|
|
8330
|
+
}
|
|
8331
|
+
const selectClause = selectParts.length > 0 ? selectParts.join(", ") : "*";
|
|
8332
|
+
parts.push(
|
|
8333
|
+
`SELECT ${this._distinct ? "DISTINCT " : ""}${selectClause} FROM "${tableName}"`
|
|
8334
|
+
);
|
|
8335
|
+
for (const join4 of this._joins) {
|
|
8336
|
+
const joinType = join4.type.toUpperCase();
|
|
8337
|
+
const tableRef = join4.schema ? `"${join4.schema}"."${join4.table}"` : `"${join4.table}"`;
|
|
8338
|
+
const aliasStr = join4.alias ? ` AS "${join4.alias}"` : "";
|
|
8339
|
+
parts.push(
|
|
8340
|
+
`${joinType} JOIN ${tableRef}${aliasStr} ON ${join4.condition}`
|
|
8341
|
+
);
|
|
8342
|
+
}
|
|
8343
|
+
if (this._filters.length > 0 || this._rawConditions.length > 0) {
|
|
8344
|
+
const whereParts = [];
|
|
8345
|
+
for (const filter of this._filters) {
|
|
8346
|
+
whereParts.push(`"${filter.field}" ${filter.operator} ?`);
|
|
8347
|
+
}
|
|
8348
|
+
for (const raw of this._rawConditions) {
|
|
8349
|
+
whereParts.push(raw.clause);
|
|
8350
|
+
}
|
|
8351
|
+
parts.push(`WHERE ${whereParts.join(" AND ")}`);
|
|
8352
|
+
}
|
|
8353
|
+
if (this._groupByFields.length > 0) {
|
|
8354
|
+
parts.push(
|
|
8355
|
+
`GROUP BY ${this._groupByFields.map((f) => `"${f}"`).join(", ")}`
|
|
8356
|
+
);
|
|
8357
|
+
}
|
|
8358
|
+
if (this._havingConditions.length > 0) {
|
|
8359
|
+
const havingParts = this._havingConditions.map((h) => h.clause);
|
|
8360
|
+
parts.push(`HAVING ${havingParts.join(" AND ")}`);
|
|
8361
|
+
}
|
|
8362
|
+
if (this._sort.length > 0) {
|
|
8363
|
+
const orderParts = this._sort.map(
|
|
8364
|
+
(s) => `"${s.field}" ${s.direction.toUpperCase()}`
|
|
8365
|
+
);
|
|
8366
|
+
parts.push(`ORDER BY ${orderParts.join(", ")}`);
|
|
8367
|
+
}
|
|
8368
|
+
if (this._pagination.limit !== void 0) {
|
|
8369
|
+
parts.push(`LIMIT ${this._pagination.limit}`);
|
|
8370
|
+
}
|
|
8371
|
+
if (this._pagination.offset !== void 0) {
|
|
8372
|
+
parts.push(`OFFSET ${this._pagination.offset}`);
|
|
8373
|
+
}
|
|
8374
|
+
return parts.join("\n");
|
|
8375
|
+
}
|
|
8376
|
+
};
|
|
8377
|
+
|
|
7183
8378
|
// src/repository/BaseRepository.ts
|
|
7184
8379
|
var BaseRepository = class {
|
|
7185
8380
|
constructor(db, tableName, defaultConfig) {
|
|
@@ -7191,6 +8386,47 @@ var BaseRepository = class {
|
|
|
7191
8386
|
__name(this, "BaseRepository");
|
|
7192
8387
|
}
|
|
7193
8388
|
defaultConfig;
|
|
8389
|
+
/**
|
|
8390
|
+
* Create a fluent QueryBuilder for this repository
|
|
8391
|
+
*
|
|
8392
|
+
* Returns a type-safe, chainable query builder that can execute queries
|
|
8393
|
+
* directly against this repository.
|
|
8394
|
+
*
|
|
8395
|
+
* @returns QueryBuilder bound to this repository
|
|
8396
|
+
*
|
|
8397
|
+
* @example
|
|
8398
|
+
* ```typescript
|
|
8399
|
+
* // Fluent query with execute
|
|
8400
|
+
* const result = await userRepository.query()
|
|
8401
|
+
* .where('status', 'eq', 'active')
|
|
8402
|
+
* .orderByDesc('createdAt')
|
|
8403
|
+
* .limit(20)
|
|
8404
|
+
* .execute();
|
|
8405
|
+
*
|
|
8406
|
+
* // Get data directly
|
|
8407
|
+
* const users = await userRepository.query()
|
|
8408
|
+
* .where('role', 'eq', 'admin')
|
|
8409
|
+
* .getMany();
|
|
8410
|
+
*
|
|
8411
|
+
* // Get single record
|
|
8412
|
+
* const user = await userRepository.query()
|
|
8413
|
+
* .where('email', 'eq', 'john@example.com')
|
|
8414
|
+
* .getOne();
|
|
8415
|
+
*
|
|
8416
|
+
* // Complex queries
|
|
8417
|
+
* const orders = await orderRepository.query()
|
|
8418
|
+
* .where('status', 'eq', 'pending')
|
|
8419
|
+
* .andWhere('totalAmount', 'gte', 100)
|
|
8420
|
+
* .orWhere('priority', 'eq', 'high')
|
|
8421
|
+
* .whereIn('region', ['US', 'EU'])
|
|
8422
|
+
* .orderBy('createdAt', 'desc')
|
|
8423
|
+
* .paginate(1, 25)
|
|
8424
|
+
* .execute();
|
|
8425
|
+
* ```
|
|
8426
|
+
*/
|
|
8427
|
+
query() {
|
|
8428
|
+
return QueryBuilder.forRepository(this);
|
|
8429
|
+
}
|
|
7194
8430
|
/**
|
|
7195
8431
|
* Get the table name for this repository
|
|
7196
8432
|
*
|
|
@@ -7241,18 +8477,28 @@ var BaseRepository = class {
|
|
|
7241
8477
|
/**
|
|
7242
8478
|
* Find multiple entities with optional filtering, sorting, and pagination
|
|
7243
8479
|
*
|
|
7244
|
-
*
|
|
8480
|
+
* Accepts either QueryOptions object or a QueryBuilder instance.
|
|
8481
|
+
*
|
|
8482
|
+
* @param {QueryOptions<T> | QueryBuilder<T>} [options] - Query configuration or QueryBuilder
|
|
7245
8483
|
* @param {OperationConfig} [config] - Optional per-operation configuration (adapter selection, schema override, etc.)
|
|
7246
8484
|
* @returns {Promise<DatabaseResult<PaginatedResult<T>>>} Promise resolving to paginated results
|
|
7247
8485
|
*
|
|
7248
8486
|
* @example
|
|
7249
8487
|
* ```typescript
|
|
8488
|
+
* // Using QueryOptions (traditional)
|
|
7250
8489
|
* const result = await userRepository.findMany({
|
|
7251
8490
|
* filter: { field: 'status', operator: 'eq', value: 'active' },
|
|
7252
8491
|
* sort: [{ field: 'createdAt', direction: 'desc' }],
|
|
7253
8492
|
* pagination: { limit: 20, offset: 0 }
|
|
7254
8493
|
* });
|
|
7255
8494
|
*
|
|
8495
|
+
* // Using QueryBuilder
|
|
8496
|
+
* const query = QueryBuilder.create<User>()
|
|
8497
|
+
* .where('status', 'eq', 'active')
|
|
8498
|
+
* .orderByDesc('createdAt')
|
|
8499
|
+
* .limit(20);
|
|
8500
|
+
* const result = await userRepository.findMany(query);
|
|
8501
|
+
*
|
|
7256
8502
|
* // Query from analytics database
|
|
7257
8503
|
* const analyticsResult = await userRepository.findMany({}, {
|
|
7258
8504
|
* adapter: 'analytics'
|
|
@@ -7260,7 +8506,12 @@ var BaseRepository = class {
|
|
|
7260
8506
|
* ```
|
|
7261
8507
|
*/
|
|
7262
8508
|
async findMany(options, config) {
|
|
7263
|
-
|
|
8509
|
+
const queryOptions = options instanceof QueryBuilder ? options.build() : options;
|
|
8510
|
+
return this.db.list(
|
|
8511
|
+
this.tableName,
|
|
8512
|
+
queryOptions,
|
|
8513
|
+
this.mergeConfig(config)
|
|
8514
|
+
);
|
|
7264
8515
|
}
|
|
7265
8516
|
/**
|
|
7266
8517
|
* Create a new entity in the database
|
|
@@ -7346,7 +8597,9 @@ var BaseRepository = class {
|
|
|
7346
8597
|
/**
|
|
7347
8598
|
* Count entities matching optional filter criteria
|
|
7348
8599
|
*
|
|
7349
|
-
*
|
|
8600
|
+
* Accepts either a Filter object or a QueryBuilder instance.
|
|
8601
|
+
*
|
|
8602
|
+
* @param {Filter<T> | QueryBuilder<T>} [filter] - Filter conditions or QueryBuilder
|
|
7350
8603
|
* @param {OperationConfig} [config] - Optional per-operation configuration (adapter selection, schema override, etc.)
|
|
7351
8604
|
* @returns {Promise<DatabaseResult<number>>} Promise resolving to the count
|
|
7352
8605
|
*
|
|
@@ -7357,6 +8610,12 @@ var BaseRepository = class {
|
|
|
7357
8610
|
* field: 'status', operator: 'eq', value: 'active'
|
|
7358
8611
|
* });
|
|
7359
8612
|
*
|
|
8613
|
+
* // Using QueryBuilder
|
|
8614
|
+
* const query = QueryBuilder.create<User>()
|
|
8615
|
+
* .where('status', 'eq', 'active')
|
|
8616
|
+
* .andWhere('verified', 'eq', true);
|
|
8617
|
+
* const result = await userRepository.count(query);
|
|
8618
|
+
*
|
|
7360
8619
|
* // Count in specific adapter
|
|
7361
8620
|
* const archiveCount = await userRepository.count(undefined, {
|
|
7362
8621
|
* adapter: 'archive'
|
|
@@ -7364,7 +8623,12 @@ var BaseRepository = class {
|
|
|
7364
8623
|
* ```
|
|
7365
8624
|
*/
|
|
7366
8625
|
async count(filter, config) {
|
|
7367
|
-
|
|
8626
|
+
const filterOptions = filter instanceof QueryBuilder ? filter.toFilters()[0] : filter;
|
|
8627
|
+
return this.db.count(
|
|
8628
|
+
this.tableName,
|
|
8629
|
+
filterOptions,
|
|
8630
|
+
this.mergeConfig(config)
|
|
8631
|
+
);
|
|
7368
8632
|
}
|
|
7369
8633
|
/**
|
|
7370
8634
|
* Check if an entity exists by ID
|
|
@@ -7401,7 +8665,9 @@ var BaseRepository = class {
|
|
|
7401
8665
|
/**
|
|
7402
8666
|
* Find the first entity matching filter criteria
|
|
7403
8667
|
*
|
|
7404
|
-
*
|
|
8668
|
+
* Accepts either a Filter object or a QueryBuilder instance.
|
|
8669
|
+
*
|
|
8670
|
+
* @param {Filter<T> | QueryBuilder<T>} filter - Filter conditions or QueryBuilder
|
|
7405
8671
|
* @param {OperationConfig} [config] - Optional per-operation configuration (adapter selection, schema override, etc.)
|
|
7406
8672
|
* @returns {Promise<DatabaseResult<T | null>>} Promise resolving to first match or null
|
|
7407
8673
|
*
|
|
@@ -7411,6 +8677,12 @@ var BaseRepository = class {
|
|
|
7411
8677
|
* field: 'email', operator: 'eq', value: 'john@example.com'
|
|
7412
8678
|
* });
|
|
7413
8679
|
*
|
|
8680
|
+
* // Using QueryBuilder
|
|
8681
|
+
* const query = QueryBuilder.create<User>()
|
|
8682
|
+
* .where('email', 'eq', 'john@example.com')
|
|
8683
|
+
* .andWhere('status', 'eq', 'active');
|
|
8684
|
+
* const result = await userRepository.findOne(query);
|
|
8685
|
+
*
|
|
7414
8686
|
* // Find in specific adapter
|
|
7415
8687
|
* const archivedUser = await userRepository.findOne({
|
|
7416
8688
|
* field: 'email', operator: 'eq', value: 'john@example.com'
|
|
@@ -7421,7 +8693,19 @@ var BaseRepository = class {
|
|
|
7421
8693
|
* ```
|
|
7422
8694
|
*/
|
|
7423
8695
|
async findOne(filter, config) {
|
|
7424
|
-
|
|
8696
|
+
const filterOptions = filter instanceof QueryBuilder ? filter.toFilters()[0] : filter;
|
|
8697
|
+
if (!filterOptions) {
|
|
8698
|
+
return {
|
|
8699
|
+
success: false,
|
|
8700
|
+
value: null,
|
|
8701
|
+
error: new Error("findOne requires at least one filter condition")
|
|
8702
|
+
};
|
|
8703
|
+
}
|
|
8704
|
+
return this.db.findOne(
|
|
8705
|
+
this.tableName,
|
|
8706
|
+
filterOptions,
|
|
8707
|
+
this.mergeConfig(config)
|
|
8708
|
+
);
|
|
7425
8709
|
}
|
|
7426
8710
|
/**
|
|
7427
8711
|
* Soft delete an entity by ID (recoverable deletion)
|
|
@@ -11371,6 +12655,6 @@ var SeedManager = class {
|
|
|
11371
12655
|
}
|
|
11372
12656
|
};
|
|
11373
12657
|
|
|
11374
|
-
export { AdapterFactory, AlertManager, AuditAdapter, BackupService, BaseRepository, CacheEvict, Cacheable, CachingAdapter, ConfigMerger, DataValidationPipe, DatabaseEventEmitter, DatabaseService, DrizzleAdapter, DynamicPool, EncryptionAdapter, HealthManager, MetricsCollector, MigrationManager, MockAdapter, MultiReadAdapter, MultiWriteAdapter, ReadReplicaAdapter, RedisCache, SQLAdapter, SanitizeHtmlPipe, SeedManager, ShardKeyManager, ShardRouter, SoftDeleteAdapter, SupabaseAdapter, TenantContext, TenantRepository, UseReplica, buildOrderClause, buildOrderClauseORM, buildPaginationClause, buildPaginationClauseORM, buildWhereClause, buildWhereClauseORM, createDatabaseService };
|
|
12658
|
+
export { AdapterFactory, AlertManager, AuditAdapter, BackupService, BaseRepository, CacheEvict, Cacheable, CachingAdapter, ConfigMerger, DataValidationPipe, DatabaseEventEmitter, DatabaseService, DrizzleAdapter, DynamicPool, EncryptionAdapter, HealthManager, MetricsCollector, MigrationManager, MockAdapter, MultiReadAdapter, MultiWriteAdapter, QueryBuilder, ReadReplicaAdapter, RedisCache, SQLAdapter, SanitizeHtmlPipe, SeedManager, ShardKeyManager, ShardRouter, SoftDeleteAdapter, SupabaseAdapter, TenantContext, TenantRepository, UseReplica, buildOrderClause, buildOrderClauseORM, buildPaginationClause, buildPaginationClauseORM, buildWhereClause, buildWhereClauseORM, createDatabaseService };
|
|
11375
12659
|
//# sourceMappingURL=index.mjs.map
|
|
11376
12660
|
//# sourceMappingURL=index.mjs.map
|