bigal 15.3.0 → 15.4.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/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # [15.4.0](https://github.com/bigalorm/bigal/compare/v15.3.0...v15.4.0) (2026-01-08)
2
+
3
+ ### Features
4
+
5
+ - Add withCount() for efficient pagination with total count ([#266](https://github.com/bigalorm/bigal/issues/266)) ([233221e](https://github.com/bigalorm/bigal/commit/233221e7902af8ca3a4b24d1f60f0857bcd7803f))
6
+
1
7
  # [15.3.0](https://github.com/bigalorm/bigal/compare/v15.2.0...v15.3.0) (2026-01-08)
2
8
 
3
9
  ### Features
package/README.md CHANGED
@@ -325,14 +325,109 @@ const item = await ProductRepository.find({
325
325
  });
326
326
  ```
327
327
 
328
- #### Example of an OR statement
328
+ #### String matching operators
329
+
330
+ BigAl provides four string matching operators. All use case-insensitive matching (PostgreSQL `ILIKE`) and accept arrays for OR matching.
331
+
332
+ | Operator | Description | SQL Pattern |
333
+ | ------------ | --------------------------------------------- | ----------- |
334
+ | `like` | Raw ILIKE pattern with your own `%` wildcards | As provided |
335
+ | `contains` | Substring match anywhere in the string | `%value%` |
336
+ | `startsWith` | Matches strings starting with the value | `value%` |
337
+ | `endsWith` | Matches strings ending with the value | `%value` |
329
338
 
330
339
  ```ts
340
+ const items = await ProductRepository.find().where({
341
+ name: { contains: 'widget' },
342
+ });
343
+ // SQL: SELECT ... FROM product WHERE name ILIKE '%widget%'
344
+
345
+ const items = await ProductRepository.find().where({
346
+ name: { startsWith: 'Pro' },
347
+ });
348
+ // SQL: SELECT ... FROM product WHERE name ILIKE 'Pro%'
349
+
350
+ const items = await ProductRepository.find().where({
351
+ name: { endsWith: 'ter' },
352
+ });
353
+ // SQL: SELECT ... FROM product WHERE name ILIKE '%ter'
354
+
355
+ const items = await ProductRepository.find().where({
356
+ name: { like: 'Pro%Widget%' },
357
+ });
358
+ // SQL: SELECT ... FROM product WHERE name ILIKE 'Pro%Widget%'
359
+
360
+ // Arrays create OR conditions
331
361
  const items = await PersonRepository.find().where({
332
- firstName: {
333
- like: ['walter', 'Jess%'],
334
- },
362
+ firstName: { like: ['walter', 'Jess%'] },
363
+ });
364
+ // SQL: SELECT ... FROM person WHERE (first_name ILIKE 'walter' OR first_name ILIKE 'Jess%')
365
+ ```
366
+
367
+ #### Comparison operators
368
+
369
+ For number and date fields, use comparison operators:
370
+
371
+ | Operator | Description |
372
+ | -------- | ------------------------ |
373
+ | `<` | Less than |
374
+ | `<=` | Less than or equal to |
375
+ | `>` | Greater than |
376
+ | `>=` | Greater than or equal to |
377
+
378
+ ```ts
379
+ const items = await ProductRepository.find().where({
380
+ price: { '>=': 100 },
381
+ });
382
+ // SQL: SELECT ... FROM product WHERE price >= $1
383
+
384
+ // Multiple operators on same field create AND
385
+ const items = await ProductRepository.find().where({
386
+ createdAt: { '>=': startDate, '<': endDate },
387
+ });
388
+ // SQL: SELECT ... FROM product WHERE created_at >= $1 AND created_at < $2
389
+ ```
390
+
391
+ #### Array values (OR conditions)
392
+
393
+ When you pass an array of values, BigAl creates an OR condition:
394
+
395
+ ```ts
396
+ const items = await PersonRepository.find().where({
397
+ age: [22, 23, 24],
398
+ });
399
+ // SQL: SELECT ... FROM person WHERE age IN ($1, $2, $3)
400
+
401
+ const items = await ProductRepository.find().where({
402
+ name: { startsWith: ['Pro', 'Pre'] },
403
+ });
404
+ // SQL: SELECT ... FROM product WHERE (name ILIKE 'Pro%' OR name ILIKE 'Pre%')
405
+ ```
406
+
407
+ #### Negation operator (`!`)
408
+
409
+ Use `!` to negate any condition:
410
+
411
+ ```ts
412
+ const items = await ProductRepository.find().where({
413
+ status: { '!': 'discontinued' },
414
+ });
415
+ // SQL: SELECT ... FROM product WHERE status <> $1
416
+
417
+ const items = await ProductRepository.find().where({
418
+ status: { '!': ['discontinued', 'archived'] },
419
+ });
420
+ // SQL: SELECT ... FROM product WHERE status NOT IN ($1, $2)
421
+
422
+ const items = await ProductRepository.find().where({
423
+ name: { '!': { startsWith: 'Test' } },
335
424
  });
425
+ // SQL: SELECT ... FROM product WHERE name NOT ILIKE 'Test%'
426
+
427
+ const items = await ProductRepository.find().where({
428
+ deletedAt: { '!': null },
429
+ });
430
+ // SQL: SELECT ... FROM product WHERE deleted_at IS NOT NULL
336
431
  ```
337
432
 
338
433
  #### Example of an AND statement
@@ -392,22 +487,28 @@ Equivalent to:
392
487
  select * from person where ((first_name = $1) OR (last_name = $2)) AND ((first_name = $3) OR (last_name = $4))
393
488
  ```
394
489
 
395
- #### Fetch multiple objects and perform a db sort before returning result
490
+ #### Sorting results
491
+
492
+ Use `.sort()` to order results. Two syntax options are available:
493
+
494
+ **String syntax** - Use `asc` or `desc` (comma-separated for multiple columns):
396
495
 
397
496
  ```ts
398
- const items = await PersonRepository.find()
399
- .where({
400
- firstName: {
401
- like: 'walter',
402
- },
403
- lastName: {
404
- like: 'white',
405
- },
406
- })
407
- .sort({
408
- age: 1,
409
- occupation: -1,
410
- });
497
+ const items = await PersonRepository.find().where({ lastName: 'Smith' }).sort('age asc');
498
+ // SQL: SELECT ... FROM person WHERE last_name = $1 ORDER BY age ASC
499
+
500
+ const items = await PersonRepository.find().where({ lastName: 'Smith' }).sort('age asc, createdAt desc');
501
+ // SQL: SELECT ... FROM person WHERE last_name = $1 ORDER BY age ASC, created_at DESC
502
+ ```
503
+
504
+ **Object syntax** - Use `1` for ascending, `-1` for descending:
505
+
506
+ ```ts
507
+ const items = await PersonRepository.find().where({ lastName: 'Smith' }).sort({ age: 1 });
508
+ // SQL: SELECT ... FROM person WHERE last_name = $1 ORDER BY age ASC
509
+
510
+ const items = await PersonRepository.find().where({ lastName: 'Smith' }).sort({ age: 1, createdAt: -1 });
511
+ // SQL: SELECT ... FROM person WHERE last_name = $1 ORDER BY age ASC, created_at DESC
411
512
  ```
412
513
 
413
514
  #### Limit number results returned
@@ -460,6 +561,34 @@ const items = await FooRepository.find()
460
561
  .paginate(page, pageSize);
461
562
  ```
462
563
 
564
+ #### Page results with total count using `withCount`
565
+
566
+ Use `.withCount()` to get both paginated results and the total count of matching records in a single query. This uses PostgreSQL's `COUNT(*) OVER()` window function for efficient execution.
567
+
568
+ ```ts
569
+ const { results, totalCount } = await ProductRepository.find()
570
+ .where({
571
+ store: storeId,
572
+ })
573
+ .sort('name')
574
+ .limit(10)
575
+ .skip(20)
576
+ .withCount();
577
+
578
+ // results: Product[] (10 items from offset 20)
579
+ // totalCount: number (total matching products, ignoring LIMIT/OFFSET)
580
+ ```
581
+
582
+ This is useful for building paginated UIs where you need to display total pages:
583
+
584
+ ```ts
585
+ const page = 3;
586
+ const pageSize = 25;
587
+ const { results, totalCount } = await ProductRepository.find().where({ isActive: true }).paginate({ page, limit: pageSize }).withCount();
588
+
589
+ const totalPages = Math.ceil(totalCount / pageSize);
590
+ ```
591
+
463
592
  #### Join related tables
464
593
 
465
594
  Use `join()` for INNER JOIN or `leftJoin()` for LEFT JOIN to filter or sort by related table columns in a single query.
package/dist/index.cjs CHANGED
@@ -445,13 +445,17 @@ function getSelectQueryAndParams({
445
445
  sorts,
446
446
  skip,
447
447
  limit,
448
- joins
448
+ joins,
449
+ includeCount
449
450
  }) {
450
451
  let query = "SELECT ";
451
452
  query += getColumnsToSelect({
452
453
  model,
453
454
  select
454
455
  });
456
+ if (includeCount) {
457
+ query += ',count(*) OVER() AS "__total_count__"';
458
+ }
455
459
  query += ` FROM ${model.qualifiedTableName}`;
456
460
  const params = [];
457
461
  if (joins?.length) {
@@ -901,7 +905,7 @@ function buildJoinClauses({
901
905
  if (alias !== relatedModel.tableName) {
902
906
  joinSql += ` AS "${alias}"`;
903
907
  }
904
- joinSql += ` ON ${model.qualifiedTableName}."${column.name}" = "${alias}"."${relatedPrimaryKey.name}"`;
908
+ joinSql += ` ON ${model.qualifiedTableName}."${column.name}"="${alias}"."${relatedPrimaryKey.name}"`;
905
909
  if (join.on) {
906
910
  for (const [propertyName, value] of Object.entries(join.on)) {
907
911
  const onColumn = relatedModel.columnsByPropertyName[propertyName];
@@ -2145,6 +2149,7 @@ ${stack ?? ""}`;
2145
2149
  const populates = [];
2146
2150
  const sorts = sort ? this._convertSortsToOrderBy(sort) : [];
2147
2151
  const joins = [];
2152
+ let includeCount = false;
2148
2153
  const modelInstance = this;
2149
2154
  return {
2150
2155
  /**
@@ -2255,6 +2260,10 @@ ${stack ?? ""}`;
2255
2260
  const safePage = Math.max(page, 1);
2256
2261
  return this.skip(safePage * paginateLimit - paginateLimit).limit(paginateLimit);
2257
2262
  },
2263
+ withCount() {
2264
+ includeCount = true;
2265
+ return this;
2266
+ },
2258
2267
  async then(resolve, reject) {
2259
2268
  try {
2260
2269
  if (typeof where === "string") {
@@ -2268,14 +2277,29 @@ ${stack ?? ""}`;
2268
2277
  sorts,
2269
2278
  skip: skip ?? 0,
2270
2279
  limit: limit ?? 0,
2271
- joins
2280
+ joins,
2281
+ includeCount
2272
2282
  });
2273
2283
  const pool = poolOverride ?? modelInstance._readonlyPool;
2274
2284
  const results = await pool.query(query, params);
2275
- const entities = modelInstance._buildInstances(results.rows);
2285
+ let totalCount = 0;
2286
+ if (includeCount && results.rows.length > 0 && results.rows[0]?.__total_count__ !== void 0) {
2287
+ totalCount = Number(results.rows[0].__total_count__);
2288
+ }
2289
+ const rows = includeCount ? results.rows.map((row) => {
2290
+ const { __total_count__, ...rest } = row;
2291
+ return rest;
2292
+ }) : results.rows;
2293
+ const entities = modelInstance._buildInstances(rows);
2276
2294
  if (populates.length) {
2277
2295
  await modelInstance.populateFields(entities, populates);
2278
2296
  }
2297
+ if (includeCount) {
2298
+ return await resolve({
2299
+ results: entities,
2300
+ totalCount
2301
+ });
2302
+ }
2279
2303
  return await resolve(entities);
2280
2304
  } catch (ex) {
2281
2305
  const typedException = ex;
package/dist/index.d.cts CHANGED
@@ -569,6 +569,22 @@ interface PaginateOptions {
569
569
  page: number;
570
570
  }
571
571
 
572
+ interface FindWithCountResult<TReturn> {
573
+ results: TReturn[];
574
+ totalCount: number;
575
+ }
576
+ interface FindQueryWithCount<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<FindWithCountResult<TReturn>> {
577
+ select<TKeys extends string & keyof T>(keys: TKeys[]): FindQueryWithCount<T, Pick<T, TKeys>, TJoins>;
578
+ where(args: JoinedWhereQuery<T, TJoins>): FindQueryWithCount<T, TReturn, TJoins>;
579
+ populate<TProperty extends string & keyof PickByValueType<T, Entity> & keyof T, TPopulateType extends GetValueType<T[TProperty], Entity>, TPopulateSelectKeys extends string & keyof TPopulateType>(propertyName: TProperty, options?: PopulateArgs<TPopulateType, TPopulateSelectKeys>): FindQueryWithCount<T, Omit<TReturn, TProperty> & Populated<T, TProperty, TPopulateType, TPopulateSelectKeys>, TJoins>;
580
+ join<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias): FindQueryWithCount<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
581
+ leftJoin<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias, on?: WhereQuery<GetValueType<T[TProperty], Entity>>): FindQueryWithCount<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
582
+ sort(value?: JoinedSort<T, TJoins>): FindQueryWithCount<T, TReturn, TJoins>;
583
+ limit(value: number): FindQueryWithCount<T, TReturn, TJoins>;
584
+ skip(value: number): FindQueryWithCount<T, TReturn, TJoins>;
585
+ paginate(options: PaginateOptions): FindQueryWithCount<T, TReturn, TJoins>;
586
+ }
587
+
572
588
  interface FindResult<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<TReturn[]> {
573
589
  select<TKeys extends string & keyof T>(keys: TKeys[]): FindResult<T, Pick<T, TKeys>, TJoins>;
574
590
  where(args: JoinedWhereQuery<T, TJoins>): FindResult<T, TReturn, TJoins>;
@@ -579,6 +595,7 @@ interface FindResult<T extends Entity, TReturn, TJoins extends JoinInfo = never>
579
595
  limit(value: number): FindResult<T, TReturn, TJoins>;
580
596
  skip(value: number): FindResult<T, TReturn, TJoins>;
581
597
  paginate(options: PaginateOptions): FindResult<T, TReturn, TJoins>;
598
+ withCount(): FindQueryWithCount<T, TReturn, TJoins>;
582
599
  UNSAFE_withOriginalFieldType<TProperty extends string & keyof PickByValueType<T, Entity> & keyof T>(propertyName: TProperty): FindResult<T, Omit<TReturn, TProperty> & Pick<T, TProperty>, TJoins>;
583
600
  }
584
601
 
@@ -1029,4 +1046,4 @@ interface InitializeOptions extends IConnection {
1029
1046
  declare function initialize({ models, pool, readonlyPool, connections, expose }: InitializeOptions): Record<string, IReadonlyRepository<Entity> | IRepository<Entity>>;
1030
1047
 
1031
1048
  export { ColumnBaseMetadata, ColumnCollectionMetadata, ColumnModelMetadata, ColumnTypeMetadata, Entity, ModelMetadata, QueryError, ReadonlyRepository, Repository, ScalarSubquery, SubqueryBuilder, column, createDateColumn, getMetadataStorage, initialize, primaryColumn, subquery, table, updateDateColumn, versionColumn };
1032
- export type { ClassLike, ColumnBaseMetadataOptions, ColumnCollectionMetadataOptions, ColumnMetadata, ColumnModelMetadataOptions, ColumnModifierMetadata, ColumnTypeMetadataOptions, Comparer, CountResult, CreateUpdateOptions, CreateUpdateParams, DeleteOptions, DestroyResult, DoNotReturnRecords, EntityFieldValue, EntityPrimitiveOrId, EntityStatic, ExcludeEntityCollections, ExcludeFunctions, FindArgs, FindOneArgs, FindOneResult, FindResult, GetValueType, IConnection, IReadonlyRepository, IRepository, IRepositoryOptions, IncludeFunctions, InitializeOptions, IsValueOfType, JoinDefinition, JoinInfo, JoinType, JoinedSort, JoinedWhereQuery, LiteralValues, ModelMetadataOptions, ModelRelationshipKeys, MultipleSortString, NegatableConstraint, NotEntity, NotEntityBrand, NumberOrDateConstraint, NumberOrDateConstraintWithSubquery, OmitEntityCollections, OmitFunctions, OrderBy, PaginateOptions, PickAsType, PickByValueType, PickFunctions, PoolLike, PoolQueryResult, PopulateArgs, Populated, QueryResult, QueryResultOptionalPopulated, QueryResultPopulated, QueryResultRow, ReturnSelect$1 as ReturnSelect, ScalarSubqueryConstraint, Sort, SortObject, SortObjectValue, SortString, StringConstraint, SubqueryBuilderLike, SubqueryInConstraint, WhereClauseValue, WhereQuery, WhereQueryStatement };
1049
+ export type { ClassLike, ColumnBaseMetadataOptions, ColumnCollectionMetadataOptions, ColumnMetadata, ColumnModelMetadataOptions, ColumnModifierMetadata, ColumnTypeMetadataOptions, Comparer, CountResult, CreateUpdateOptions, CreateUpdateParams, DeleteOptions, DestroyResult, DoNotReturnRecords, EntityFieldValue, EntityPrimitiveOrId, EntityStatic, ExcludeEntityCollections, ExcludeFunctions, FindArgs, FindOneArgs, FindOneResult, FindQueryWithCount, FindResult, FindWithCountResult, GetValueType, IConnection, IReadonlyRepository, IRepository, IRepositoryOptions, IncludeFunctions, InitializeOptions, IsValueOfType, JoinDefinition, JoinInfo, JoinType, JoinedSort, JoinedWhereQuery, LiteralValues, ModelMetadataOptions, ModelRelationshipKeys, MultipleSortString, NegatableConstraint, NotEntity, NotEntityBrand, NumberOrDateConstraint, NumberOrDateConstraintWithSubquery, OmitEntityCollections, OmitFunctions, OrderBy, PaginateOptions, PickAsType, PickByValueType, PickFunctions, PoolLike, PoolQueryResult, PopulateArgs, Populated, QueryResult, QueryResultOptionalPopulated, QueryResultPopulated, QueryResultRow, ReturnSelect$1 as ReturnSelect, ScalarSubqueryConstraint, Sort, SortObject, SortObjectValue, SortString, StringConstraint, SubqueryBuilderLike, SubqueryInConstraint, WhereClauseValue, WhereQuery, WhereQueryStatement };
package/dist/index.d.mts CHANGED
@@ -569,6 +569,22 @@ interface PaginateOptions {
569
569
  page: number;
570
570
  }
571
571
 
572
+ interface FindWithCountResult<TReturn> {
573
+ results: TReturn[];
574
+ totalCount: number;
575
+ }
576
+ interface FindQueryWithCount<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<FindWithCountResult<TReturn>> {
577
+ select<TKeys extends string & keyof T>(keys: TKeys[]): FindQueryWithCount<T, Pick<T, TKeys>, TJoins>;
578
+ where(args: JoinedWhereQuery<T, TJoins>): FindQueryWithCount<T, TReturn, TJoins>;
579
+ populate<TProperty extends string & keyof PickByValueType<T, Entity> & keyof T, TPopulateType extends GetValueType<T[TProperty], Entity>, TPopulateSelectKeys extends string & keyof TPopulateType>(propertyName: TProperty, options?: PopulateArgs<TPopulateType, TPopulateSelectKeys>): FindQueryWithCount<T, Omit<TReturn, TProperty> & Populated<T, TProperty, TPopulateType, TPopulateSelectKeys>, TJoins>;
580
+ join<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias): FindQueryWithCount<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
581
+ leftJoin<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias, on?: WhereQuery<GetValueType<T[TProperty], Entity>>): FindQueryWithCount<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
582
+ sort(value?: JoinedSort<T, TJoins>): FindQueryWithCount<T, TReturn, TJoins>;
583
+ limit(value: number): FindQueryWithCount<T, TReturn, TJoins>;
584
+ skip(value: number): FindQueryWithCount<T, TReturn, TJoins>;
585
+ paginate(options: PaginateOptions): FindQueryWithCount<T, TReturn, TJoins>;
586
+ }
587
+
572
588
  interface FindResult<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<TReturn[]> {
573
589
  select<TKeys extends string & keyof T>(keys: TKeys[]): FindResult<T, Pick<T, TKeys>, TJoins>;
574
590
  where(args: JoinedWhereQuery<T, TJoins>): FindResult<T, TReturn, TJoins>;
@@ -579,6 +595,7 @@ interface FindResult<T extends Entity, TReturn, TJoins extends JoinInfo = never>
579
595
  limit(value: number): FindResult<T, TReturn, TJoins>;
580
596
  skip(value: number): FindResult<T, TReturn, TJoins>;
581
597
  paginate(options: PaginateOptions): FindResult<T, TReturn, TJoins>;
598
+ withCount(): FindQueryWithCount<T, TReturn, TJoins>;
582
599
  UNSAFE_withOriginalFieldType<TProperty extends string & keyof PickByValueType<T, Entity> & keyof T>(propertyName: TProperty): FindResult<T, Omit<TReturn, TProperty> & Pick<T, TProperty>, TJoins>;
583
600
  }
584
601
 
@@ -1029,4 +1046,4 @@ interface InitializeOptions extends IConnection {
1029
1046
  declare function initialize({ models, pool, readonlyPool, connections, expose }: InitializeOptions): Record<string, IReadonlyRepository<Entity> | IRepository<Entity>>;
1030
1047
 
1031
1048
  export { ColumnBaseMetadata, ColumnCollectionMetadata, ColumnModelMetadata, ColumnTypeMetadata, Entity, ModelMetadata, QueryError, ReadonlyRepository, Repository, ScalarSubquery, SubqueryBuilder, column, createDateColumn, getMetadataStorage, initialize, primaryColumn, subquery, table, updateDateColumn, versionColumn };
1032
- export type { ClassLike, ColumnBaseMetadataOptions, ColumnCollectionMetadataOptions, ColumnMetadata, ColumnModelMetadataOptions, ColumnModifierMetadata, ColumnTypeMetadataOptions, Comparer, CountResult, CreateUpdateOptions, CreateUpdateParams, DeleteOptions, DestroyResult, DoNotReturnRecords, EntityFieldValue, EntityPrimitiveOrId, EntityStatic, ExcludeEntityCollections, ExcludeFunctions, FindArgs, FindOneArgs, FindOneResult, FindResult, GetValueType, IConnection, IReadonlyRepository, IRepository, IRepositoryOptions, IncludeFunctions, InitializeOptions, IsValueOfType, JoinDefinition, JoinInfo, JoinType, JoinedSort, JoinedWhereQuery, LiteralValues, ModelMetadataOptions, ModelRelationshipKeys, MultipleSortString, NegatableConstraint, NotEntity, NotEntityBrand, NumberOrDateConstraint, NumberOrDateConstraintWithSubquery, OmitEntityCollections, OmitFunctions, OrderBy, PaginateOptions, PickAsType, PickByValueType, PickFunctions, PoolLike, PoolQueryResult, PopulateArgs, Populated, QueryResult, QueryResultOptionalPopulated, QueryResultPopulated, QueryResultRow, ReturnSelect$1 as ReturnSelect, ScalarSubqueryConstraint, Sort, SortObject, SortObjectValue, SortString, StringConstraint, SubqueryBuilderLike, SubqueryInConstraint, WhereClauseValue, WhereQuery, WhereQueryStatement };
1049
+ export type { ClassLike, ColumnBaseMetadataOptions, ColumnCollectionMetadataOptions, ColumnMetadata, ColumnModelMetadataOptions, ColumnModifierMetadata, ColumnTypeMetadataOptions, Comparer, CountResult, CreateUpdateOptions, CreateUpdateParams, DeleteOptions, DestroyResult, DoNotReturnRecords, EntityFieldValue, EntityPrimitiveOrId, EntityStatic, ExcludeEntityCollections, ExcludeFunctions, FindArgs, FindOneArgs, FindOneResult, FindQueryWithCount, FindResult, FindWithCountResult, GetValueType, IConnection, IReadonlyRepository, IRepository, IRepositoryOptions, IncludeFunctions, InitializeOptions, IsValueOfType, JoinDefinition, JoinInfo, JoinType, JoinedSort, JoinedWhereQuery, LiteralValues, ModelMetadataOptions, ModelRelationshipKeys, MultipleSortString, NegatableConstraint, NotEntity, NotEntityBrand, NumberOrDateConstraint, NumberOrDateConstraintWithSubquery, OmitEntityCollections, OmitFunctions, OrderBy, PaginateOptions, PickAsType, PickByValueType, PickFunctions, PoolLike, PoolQueryResult, PopulateArgs, Populated, QueryResult, QueryResultOptionalPopulated, QueryResultPopulated, QueryResultRow, ReturnSelect$1 as ReturnSelect, ScalarSubqueryConstraint, Sort, SortObject, SortObjectValue, SortString, StringConstraint, SubqueryBuilderLike, SubqueryInConstraint, WhereClauseValue, WhereQuery, WhereQueryStatement };
package/dist/index.d.ts CHANGED
@@ -569,6 +569,22 @@ interface PaginateOptions {
569
569
  page: number;
570
570
  }
571
571
 
572
+ interface FindWithCountResult<TReturn> {
573
+ results: TReturn[];
574
+ totalCount: number;
575
+ }
576
+ interface FindQueryWithCount<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<FindWithCountResult<TReturn>> {
577
+ select<TKeys extends string & keyof T>(keys: TKeys[]): FindQueryWithCount<T, Pick<T, TKeys>, TJoins>;
578
+ where(args: JoinedWhereQuery<T, TJoins>): FindQueryWithCount<T, TReturn, TJoins>;
579
+ populate<TProperty extends string & keyof PickByValueType<T, Entity> & keyof T, TPopulateType extends GetValueType<T[TProperty], Entity>, TPopulateSelectKeys extends string & keyof TPopulateType>(propertyName: TProperty, options?: PopulateArgs<TPopulateType, TPopulateSelectKeys>): FindQueryWithCount<T, Omit<TReturn, TProperty> & Populated<T, TProperty, TPopulateType, TPopulateSelectKeys>, TJoins>;
580
+ join<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias): FindQueryWithCount<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
581
+ leftJoin<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias, on?: WhereQuery<GetValueType<T[TProperty], Entity>>): FindQueryWithCount<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
582
+ sort(value?: JoinedSort<T, TJoins>): FindQueryWithCount<T, TReturn, TJoins>;
583
+ limit(value: number): FindQueryWithCount<T, TReturn, TJoins>;
584
+ skip(value: number): FindQueryWithCount<T, TReturn, TJoins>;
585
+ paginate(options: PaginateOptions): FindQueryWithCount<T, TReturn, TJoins>;
586
+ }
587
+
572
588
  interface FindResult<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<TReturn[]> {
573
589
  select<TKeys extends string & keyof T>(keys: TKeys[]): FindResult<T, Pick<T, TKeys>, TJoins>;
574
590
  where(args: JoinedWhereQuery<T, TJoins>): FindResult<T, TReturn, TJoins>;
@@ -579,6 +595,7 @@ interface FindResult<T extends Entity, TReturn, TJoins extends JoinInfo = never>
579
595
  limit(value: number): FindResult<T, TReturn, TJoins>;
580
596
  skip(value: number): FindResult<T, TReturn, TJoins>;
581
597
  paginate(options: PaginateOptions): FindResult<T, TReturn, TJoins>;
598
+ withCount(): FindQueryWithCount<T, TReturn, TJoins>;
582
599
  UNSAFE_withOriginalFieldType<TProperty extends string & keyof PickByValueType<T, Entity> & keyof T>(propertyName: TProperty): FindResult<T, Omit<TReturn, TProperty> & Pick<T, TProperty>, TJoins>;
583
600
  }
584
601
 
@@ -1029,4 +1046,4 @@ interface InitializeOptions extends IConnection {
1029
1046
  declare function initialize({ models, pool, readonlyPool, connections, expose }: InitializeOptions): Record<string, IReadonlyRepository<Entity> | IRepository<Entity>>;
1030
1047
 
1031
1048
  export { ColumnBaseMetadata, ColumnCollectionMetadata, ColumnModelMetadata, ColumnTypeMetadata, Entity, ModelMetadata, QueryError, ReadonlyRepository, Repository, ScalarSubquery, SubqueryBuilder, column, createDateColumn, getMetadataStorage, initialize, primaryColumn, subquery, table, updateDateColumn, versionColumn };
1032
- export type { ClassLike, ColumnBaseMetadataOptions, ColumnCollectionMetadataOptions, ColumnMetadata, ColumnModelMetadataOptions, ColumnModifierMetadata, ColumnTypeMetadataOptions, Comparer, CountResult, CreateUpdateOptions, CreateUpdateParams, DeleteOptions, DestroyResult, DoNotReturnRecords, EntityFieldValue, EntityPrimitiveOrId, EntityStatic, ExcludeEntityCollections, ExcludeFunctions, FindArgs, FindOneArgs, FindOneResult, FindResult, GetValueType, IConnection, IReadonlyRepository, IRepository, IRepositoryOptions, IncludeFunctions, InitializeOptions, IsValueOfType, JoinDefinition, JoinInfo, JoinType, JoinedSort, JoinedWhereQuery, LiteralValues, ModelMetadataOptions, ModelRelationshipKeys, MultipleSortString, NegatableConstraint, NotEntity, NotEntityBrand, NumberOrDateConstraint, NumberOrDateConstraintWithSubquery, OmitEntityCollections, OmitFunctions, OrderBy, PaginateOptions, PickAsType, PickByValueType, PickFunctions, PoolLike, PoolQueryResult, PopulateArgs, Populated, QueryResult, QueryResultOptionalPopulated, QueryResultPopulated, QueryResultRow, ReturnSelect$1 as ReturnSelect, ScalarSubqueryConstraint, Sort, SortObject, SortObjectValue, SortString, StringConstraint, SubqueryBuilderLike, SubqueryInConstraint, WhereClauseValue, WhereQuery, WhereQueryStatement };
1049
+ export type { ClassLike, ColumnBaseMetadataOptions, ColumnCollectionMetadataOptions, ColumnMetadata, ColumnModelMetadataOptions, ColumnModifierMetadata, ColumnTypeMetadataOptions, Comparer, CountResult, CreateUpdateOptions, CreateUpdateParams, DeleteOptions, DestroyResult, DoNotReturnRecords, EntityFieldValue, EntityPrimitiveOrId, EntityStatic, ExcludeEntityCollections, ExcludeFunctions, FindArgs, FindOneArgs, FindOneResult, FindQueryWithCount, FindResult, FindWithCountResult, GetValueType, IConnection, IReadonlyRepository, IRepository, IRepositoryOptions, IncludeFunctions, InitializeOptions, IsValueOfType, JoinDefinition, JoinInfo, JoinType, JoinedSort, JoinedWhereQuery, LiteralValues, ModelMetadataOptions, ModelRelationshipKeys, MultipleSortString, NegatableConstraint, NotEntity, NotEntityBrand, NumberOrDateConstraint, NumberOrDateConstraintWithSubquery, OmitEntityCollections, OmitFunctions, OrderBy, PaginateOptions, PickAsType, PickByValueType, PickFunctions, PoolLike, PoolQueryResult, PopulateArgs, Populated, QueryResult, QueryResultOptionalPopulated, QueryResultPopulated, QueryResultRow, ReturnSelect$1 as ReturnSelect, ScalarSubqueryConstraint, Sort, SortObject, SortObjectValue, SortString, StringConstraint, SubqueryBuilderLike, SubqueryInConstraint, WhereClauseValue, WhereQuery, WhereQueryStatement };
package/dist/index.mjs CHANGED
@@ -443,13 +443,17 @@ function getSelectQueryAndParams({
443
443
  sorts,
444
444
  skip,
445
445
  limit,
446
- joins
446
+ joins,
447
+ includeCount
447
448
  }) {
448
449
  let query = "SELECT ";
449
450
  query += getColumnsToSelect({
450
451
  model,
451
452
  select
452
453
  });
454
+ if (includeCount) {
455
+ query += ',count(*) OVER() AS "__total_count__"';
456
+ }
453
457
  query += ` FROM ${model.qualifiedTableName}`;
454
458
  const params = [];
455
459
  if (joins?.length) {
@@ -899,7 +903,7 @@ function buildJoinClauses({
899
903
  if (alias !== relatedModel.tableName) {
900
904
  joinSql += ` AS "${alias}"`;
901
905
  }
902
- joinSql += ` ON ${model.qualifiedTableName}."${column.name}" = "${alias}"."${relatedPrimaryKey.name}"`;
906
+ joinSql += ` ON ${model.qualifiedTableName}."${column.name}"="${alias}"."${relatedPrimaryKey.name}"`;
903
907
  if (join.on) {
904
908
  for (const [propertyName, value] of Object.entries(join.on)) {
905
909
  const onColumn = relatedModel.columnsByPropertyName[propertyName];
@@ -2143,6 +2147,7 @@ ${stack ?? ""}`;
2143
2147
  const populates = [];
2144
2148
  const sorts = sort ? this._convertSortsToOrderBy(sort) : [];
2145
2149
  const joins = [];
2150
+ let includeCount = false;
2146
2151
  const modelInstance = this;
2147
2152
  return {
2148
2153
  /**
@@ -2253,6 +2258,10 @@ ${stack ?? ""}`;
2253
2258
  const safePage = Math.max(page, 1);
2254
2259
  return this.skip(safePage * paginateLimit - paginateLimit).limit(paginateLimit);
2255
2260
  },
2261
+ withCount() {
2262
+ includeCount = true;
2263
+ return this;
2264
+ },
2256
2265
  async then(resolve, reject) {
2257
2266
  try {
2258
2267
  if (typeof where === "string") {
@@ -2266,14 +2275,29 @@ ${stack ?? ""}`;
2266
2275
  sorts,
2267
2276
  skip: skip ?? 0,
2268
2277
  limit: limit ?? 0,
2269
- joins
2278
+ joins,
2279
+ includeCount
2270
2280
  });
2271
2281
  const pool = poolOverride ?? modelInstance._readonlyPool;
2272
2282
  const results = await pool.query(query, params);
2273
- const entities = modelInstance._buildInstances(results.rows);
2283
+ let totalCount = 0;
2284
+ if (includeCount && results.rows.length > 0 && results.rows[0]?.__total_count__ !== void 0) {
2285
+ totalCount = Number(results.rows[0].__total_count__);
2286
+ }
2287
+ const rows = includeCount ? results.rows.map((row) => {
2288
+ const { __total_count__, ...rest } = row;
2289
+ return rest;
2290
+ }) : results.rows;
2291
+ const entities = modelInstance._buildInstances(rows);
2274
2292
  if (populates.length) {
2275
2293
  await modelInstance.populateFields(entities, populates);
2276
2294
  }
2295
+ if (includeCount) {
2296
+ return await resolve({
2297
+ results: entities,
2298
+ totalCount
2299
+ });
2300
+ }
2277
2301
  return await resolve(entities);
2278
2302
  } catch (ex) {
2279
2303
  const typedException = ex;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bigal",
3
- "version": "15.3.0",
3
+ "version": "15.4.0",
4
4
  "description": "A fast and lightweight orm for postgres and node.js, written in typescript.",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",