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