bigal 15.3.0 → 15.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/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ # [15.5.0](https://github.com/bigalorm/bigal/compare/v15.4.0...v15.5.0) (2026-01-13)
2
+
3
+ ### Features
4
+
5
+ - Add toJSON() method for returning plain objects instead of class instances ([#271](https://github.com/bigalorm/bigal/issues/271)) ([3e2ee5d](https://github.com/bigalorm/bigal/commit/3e2ee5d803d705bc54bc8b08044f5848088563bb))
6
+
7
+ # [15.4.0](https://github.com/bigalorm/bigal/compare/v15.3.0...v15.4.0) (2026-01-08)
8
+
9
+ ### Features
10
+
11
+ - Add withCount() for efficient pagination with total count ([#266](https://github.com/bigalorm/bigal/issues/266)) ([233221e](https://github.com/bigalorm/bigal/commit/233221e7902af8ca3a4b24d1f60f0857bcd7803f))
12
+
1
13
  # [15.3.0](https://github.com/bigalorm/bigal/compare/v15.2.0...v15.3.0) (2026-01-08)
2
14
 
3
15
  ### Features
package/README.md CHANGED
@@ -183,8 +183,8 @@ export function startup({
183
183
  });
184
184
 
185
185
  let categoryRepository: Repository<Category>;
186
- let productRepository: Repository<Category>;
187
- let storeRepository: Repository<Category>;
186
+ let productRepository: Repository<Product>;
187
+ let storeRepository: Repository<Store>;
188
188
  for (const [modelName, repository] = Object.entries(repositoriesByName)) {
189
189
  switch (modelName) {
190
190
  case 'Category':
@@ -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];
@@ -1957,6 +1961,7 @@ class ReadonlyRepository {
1957
1961
  const manuallySetFields = [];
1958
1962
  const sorts = sort ? this._convertSortsToOrderBy(sort) : [];
1959
1963
  const joins = [];
1964
+ let returnAsPlainObjects = false;
1960
1965
  const modelInstance = this;
1961
1966
  return {
1962
1967
  /**
@@ -2046,6 +2051,10 @@ class ReadonlyRepository {
2046
2051
  });
2047
2052
  return this;
2048
2053
  },
2054
+ toJSON() {
2055
+ returnAsPlainObjects = true;
2056
+ return this;
2057
+ },
2049
2058
  async then(resolve, reject) {
2050
2059
  try {
2051
2060
  if (typeof where === "string") {
@@ -2065,9 +2074,10 @@ class ReadonlyRepository {
2065
2074
  const results = await pool.query(query, params);
2066
2075
  const firstResult = results.rows[0];
2067
2076
  if (firstResult) {
2068
- const result = modelInstance._buildInstance(firstResult);
2077
+ const result = returnAsPlainObjects ? modelInstance._buildPlainObject(firstResult) : modelInstance._buildInstance(firstResult);
2069
2078
  if (populates.length) {
2070
- await modelInstance.populateFields([result], populates);
2079
+ const populatesWithFlag = populates.map((pop) => ({ ...pop, asPlainObjects: returnAsPlainObjects }));
2080
+ await modelInstance.populateFields([result], populatesWithFlag);
2071
2081
  }
2072
2082
  for (const manuallySetField of manuallySetFields) {
2073
2083
  result[manuallySetField.propertyName] = manuallySetField.value;
@@ -2145,6 +2155,8 @@ ${stack ?? ""}`;
2145
2155
  const populates = [];
2146
2156
  const sorts = sort ? this._convertSortsToOrderBy(sort) : [];
2147
2157
  const joins = [];
2158
+ let includeCount = false;
2159
+ let returnAsPlainObjects = false;
2148
2160
  const modelInstance = this;
2149
2161
  return {
2150
2162
  /**
@@ -2255,6 +2267,14 @@ ${stack ?? ""}`;
2255
2267
  const safePage = Math.max(page, 1);
2256
2268
  return this.skip(safePage * paginateLimit - paginateLimit).limit(paginateLimit);
2257
2269
  },
2270
+ withCount() {
2271
+ includeCount = true;
2272
+ return this;
2273
+ },
2274
+ toJSON() {
2275
+ returnAsPlainObjects = true;
2276
+ return this;
2277
+ },
2258
2278
  async then(resolve, reject) {
2259
2279
  try {
2260
2280
  if (typeof where === "string") {
@@ -2268,13 +2288,29 @@ ${stack ?? ""}`;
2268
2288
  sorts,
2269
2289
  skip: skip ?? 0,
2270
2290
  limit: limit ?? 0,
2271
- joins
2291
+ joins,
2292
+ includeCount
2272
2293
  });
2273
2294
  const pool = poolOverride ?? modelInstance._readonlyPool;
2274
2295
  const results = await pool.query(query, params);
2275
- const entities = modelInstance._buildInstances(results.rows);
2296
+ let totalCount = 0;
2297
+ if (includeCount && results.rows.length > 0 && results.rows[0]?.__total_count__ !== void 0) {
2298
+ totalCount = Number(results.rows[0].__total_count__);
2299
+ }
2300
+ const rows = includeCount ? results.rows.map((row) => {
2301
+ const { __total_count__, ...rest } = row;
2302
+ return rest;
2303
+ }) : results.rows;
2304
+ const entities = returnAsPlainObjects ? modelInstance._buildPlainObjects(rows) : modelInstance._buildInstances(rows);
2276
2305
  if (populates.length) {
2277
- await modelInstance.populateFields(entities, populates);
2306
+ const populatesWithFlag = populates.map((pop) => ({ ...pop, asPlainObjects: returnAsPlainObjects }));
2307
+ await modelInstance.populateFields(entities, populatesWithFlag);
2308
+ }
2309
+ if (includeCount) {
2310
+ return await resolve({
2311
+ results: entities,
2312
+ totalCount
2313
+ });
2278
2314
  }
2279
2315
  return await resolve(entities);
2280
2316
  } catch (ex) {
@@ -2390,6 +2426,40 @@ ${stack ?? ""}`;
2390
2426
  _buildInstances(rows) {
2391
2427
  return rows.map((row) => this._buildInstance(row));
2392
2428
  }
2429
+ _buildPlainObject(row) {
2430
+ const plainObject = { ...row };
2431
+ for (const name of this._floatProperties) {
2432
+ const originalValue = plainObject[name];
2433
+ if (originalValue != null && typeof originalValue === "string") {
2434
+ try {
2435
+ const value = Number(originalValue);
2436
+ if (Number.isFinite(value) && value.toString() === originalValue) {
2437
+ plainObject[name] = value;
2438
+ }
2439
+ } catch {
2440
+ }
2441
+ }
2442
+ }
2443
+ for (const name of this._intProperties) {
2444
+ const originalValue = plainObject[name];
2445
+ if (originalValue != null && typeof originalValue === "string") {
2446
+ try {
2447
+ const value = Number(originalValue);
2448
+ if (Number.isFinite(value) && value.toString() === originalValue) {
2449
+ const valueAsInt = Math.trunc(value);
2450
+ if (Number.isSafeInteger(valueAsInt)) {
2451
+ plainObject[name] = valueAsInt;
2452
+ }
2453
+ }
2454
+ } catch {
2455
+ }
2456
+ }
2457
+ }
2458
+ return plainObject;
2459
+ }
2460
+ _buildPlainObjects(rows) {
2461
+ return rows.map((row) => this._buildPlainObject(row));
2462
+ }
2393
2463
  _convertSortsToOrderBy(sorts) {
2394
2464
  const result = [];
2395
2465
  if (sorts) {
@@ -2506,12 +2576,13 @@ ${stack ?? ""}`;
2506
2576
  [populateRepository.model.primaryKeyColumn.propertyName]: Array.from(populateIds),
2507
2577
  ...populate.where
2508
2578
  };
2509
- const populateResults = await populateRepository.find({
2579
+ const findQuery = populateRepository.find({
2510
2580
  select: populate.select,
2511
2581
  where: populateWhere,
2512
2582
  sort: populate.sort,
2513
2583
  pool: populate.pool
2514
2584
  });
2585
+ const populateResults = populate.asPlainObjects ? await findQuery.toJSON() : await findQuery;
2515
2586
  const populateResultsById = keyBy(populateResults, populateRepository.model.primaryKeyColumn.propertyName);
2516
2587
  for (const entity of entities) {
2517
2588
  entity[propertyName] = populateResultsById[entity[propertyName]];
@@ -2525,7 +2596,7 @@ ${stack ?? ""}`;
2525
2596
  [column.via]: entityIds,
2526
2597
  ...populate.where
2527
2598
  };
2528
- const populateResults = await populateRepository.find({
2599
+ const findQuery = populateRepository.find({
2529
2600
  select: populate.select,
2530
2601
  where: populateWhere,
2531
2602
  sort: populate.sort,
@@ -2533,6 +2604,7 @@ ${stack ?? ""}`;
2533
2604
  limit: populate.limit,
2534
2605
  pool: populate.pool
2535
2606
  });
2607
+ const populateResults = populate.asPlainObjects ? await findQuery.toJSON() : await findQuery;
2536
2608
  if (entities.length === 1) {
2537
2609
  for (const entity of entities) {
2538
2610
  entity[populate.propertyName] = populateResults;
@@ -2585,7 +2657,7 @@ ${stack ?? ""}`;
2585
2657
  [populateModelPrimaryKeyPropertyName]: Array.from(populateIds),
2586
2658
  ...populate.where
2587
2659
  };
2588
- const populateResults = await populateRepository.find({
2660
+ const findQuery = populateRepository.find({
2589
2661
  select: populate.select,
2590
2662
  where: populateWhere,
2591
2663
  sort: populate.sort,
@@ -2593,6 +2665,7 @@ ${stack ?? ""}`;
2593
2665
  limit: populate.limit,
2594
2666
  pool: populate.pool
2595
2667
  });
2668
+ const populateResults = populate.asPlainObjects ? await findQuery.toJSON() : await findQuery;
2596
2669
  const populateResultsById = keyBy(populateResults, populateModelPrimaryKeyPropertyName);
2597
2670
  for (const entity of entities) {
2598
2671
  const populatedItems = [];
package/dist/index.d.cts CHANGED
@@ -85,6 +85,15 @@ type PickFunctions<T> = {
85
85
  [K in keyof T as IncludeFunctions<T[K], K>]: T[K];
86
86
  };
87
87
 
88
+ /**
89
+ * Recursively converts entity types to plain object types.
90
+ * This is primarily for documentation/intent - at runtime, this strips the prototype chain.
91
+ * Preserves Date objects as-is since they're serializable by most frameworks.
92
+ */
93
+ type PlainObject<T> = T extends Date ? Date : T extends (infer U)[] ? PlainObject<U>[] : T extends object ? {
94
+ [K in keyof T]: PlainObject<T[K]>;
95
+ } : T;
96
+
88
97
  /**
89
98
  * Changes all properties with Entity values to Primitive (string|number). Removes any properties that with values
90
99
  * of Entity arrays
@@ -553,6 +562,16 @@ interface PopulateArgs<T extends Entity, K extends keyof T> {
553
562
  pool?: PoolLike;
554
563
  }
555
564
 
565
+ interface FindOneResultJSON<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<PlainObject<TReturn> | null> {
566
+ select<TKeys extends string & keyof T>(keys: TKeys[]): FindOneResultJSON<T, Pick<T, TKeys>, TJoins>;
567
+ where(args: JoinedWhereQuery<T, TJoins>): FindOneResultJSON<T, TReturn, TJoins>;
568
+ 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>): FindOneResultJSON<T, Omit<TReturn, TProperty> & Populated<T, TProperty, TPopulateType, TPopulateSelectKeys>, TJoins>;
569
+ join<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias): FindOneResultJSON<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
570
+ leftJoin<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias, on?: WhereQuery<GetValueType<T[TProperty], Entity>>): FindOneResultJSON<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
571
+ sort(value?: JoinedSort<T, TJoins>): FindOneResultJSON<T, TReturn, TJoins>;
572
+ UNSAFE_withOriginalFieldType<TProperty extends string & keyof PickByValueType<T, Entity> & keyof T>(propertyName: TProperty): FindOneResultJSON<T, Omit<TReturn, TProperty> & Pick<T, TProperty>, TJoins>;
573
+ UNSAFE_withFieldValue<TProperty extends string & keyof T, TValue extends T[TProperty]>(propertyName: TProperty, value: TValue): FindOneResultJSON<T, Omit<TReturn, TProperty> & PickAsType<T, TProperty, TValue>, TJoins>;
574
+ }
556
575
  interface FindOneResult<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<TReturn | null> {
557
576
  select<TKeys extends string & keyof T>(keys: TKeys[]): FindOneResult<T, Pick<T, TKeys>, TJoins>;
558
577
  where(args: JoinedWhereQuery<T, TJoins>): FindOneResult<T, TReturn, TJoins>;
@@ -562,6 +581,11 @@ interface FindOneResult<T extends Entity, TReturn, TJoins extends JoinInfo = nev
562
581
  sort(value?: JoinedSort<T, TJoins>): FindOneResult<T, TReturn, TJoins>;
563
582
  UNSAFE_withOriginalFieldType<TProperty extends string & keyof PickByValueType<T, Entity> & keyof T>(propertyName: TProperty): FindOneResult<T, Omit<TReturn, TProperty> & Pick<T, TProperty>, TJoins>;
564
583
  UNSAFE_withFieldValue<TProperty extends string & keyof T, TValue extends T[TProperty]>(propertyName: TProperty, value: TValue): FindOneResult<T, Omit<TReturn, TProperty> & PickAsType<T, TProperty, TValue>, TJoins>;
584
+ /**
585
+ * Returns result as a plain object instead of an entity class instance.
586
+ * Useful for when data must be serializable.
587
+ */
588
+ toJSON(): FindOneResultJSON<T, TReturn, TJoins>;
565
589
  }
566
590
 
567
591
  interface PaginateOptions {
@@ -569,6 +593,51 @@ interface PaginateOptions {
569
593
  page: number;
570
594
  }
571
595
 
596
+ interface FindWithCountResult<TReturn> {
597
+ results: TReturn[];
598
+ totalCount: number;
599
+ }
600
+ interface FindQueryWithCountJSON<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<FindWithCountResult<PlainObject<TReturn>>> {
601
+ select<TKeys extends string & keyof T>(keys: TKeys[]): FindQueryWithCountJSON<T, Pick<T, TKeys>, TJoins>;
602
+ where(args: JoinedWhereQuery<T, TJoins>): FindQueryWithCountJSON<T, TReturn, TJoins>;
603
+ 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>): FindQueryWithCountJSON<T, Omit<TReturn, TProperty> & Populated<T, TProperty, TPopulateType, TPopulateSelectKeys>, TJoins>;
604
+ join<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias): FindQueryWithCountJSON<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
605
+ leftJoin<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias, on?: WhereQuery<GetValueType<T[TProperty], Entity>>): FindQueryWithCountJSON<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
606
+ sort(value?: JoinedSort<T, TJoins>): FindQueryWithCountJSON<T, TReturn, TJoins>;
607
+ limit(value: number): FindQueryWithCountJSON<T, TReturn, TJoins>;
608
+ skip(value: number): FindQueryWithCountJSON<T, TReturn, TJoins>;
609
+ paginate(options: PaginateOptions): FindQueryWithCountJSON<T, TReturn, TJoins>;
610
+ }
611
+ interface FindQueryWithCount<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<FindWithCountResult<TReturn>> {
612
+ select<TKeys extends string & keyof T>(keys: TKeys[]): FindQueryWithCount<T, Pick<T, TKeys>, TJoins>;
613
+ where(args: JoinedWhereQuery<T, TJoins>): FindQueryWithCount<T, TReturn, TJoins>;
614
+ 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>;
615
+ join<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias): FindQueryWithCount<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
616
+ 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>;
617
+ sort(value?: JoinedSort<T, TJoins>): FindQueryWithCount<T, TReturn, TJoins>;
618
+ limit(value: number): FindQueryWithCount<T, TReturn, TJoins>;
619
+ skip(value: number): FindQueryWithCount<T, TReturn, TJoins>;
620
+ paginate(options: PaginateOptions): FindQueryWithCount<T, TReturn, TJoins>;
621
+ /**
622
+ * Returns results as plain objects instead of entity class instances.
623
+ * Useful for when data must be serializable.
624
+ */
625
+ toJSON(): FindQueryWithCountJSON<T, TReturn, TJoins>;
626
+ }
627
+
628
+ interface FindResultJSON<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<PlainObject<TReturn>[]> {
629
+ select<TKeys extends string & keyof T>(keys: TKeys[]): FindResultJSON<T, Pick<T, TKeys>, TJoins>;
630
+ where(args: JoinedWhereQuery<T, TJoins>): FindResultJSON<T, TReturn, TJoins>;
631
+ 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>): FindResultJSON<T, Omit<TReturn, TProperty> & Populated<T, TProperty, TPopulateType, TPopulateSelectKeys>, TJoins>;
632
+ join<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias): FindResultJSON<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
633
+ leftJoin<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias, on?: WhereQuery<GetValueType<T[TProperty], Entity>>): FindResultJSON<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
634
+ sort(value?: JoinedSort<T, TJoins>): FindResultJSON<T, TReturn, TJoins>;
635
+ limit(value: number): FindResultJSON<T, TReturn, TJoins>;
636
+ skip(value: number): FindResultJSON<T, TReturn, TJoins>;
637
+ paginate(options: PaginateOptions): FindResultJSON<T, TReturn, TJoins>;
638
+ withCount(): FindQueryWithCountJSON<T, TReturn, TJoins>;
639
+ UNSAFE_withOriginalFieldType<TProperty extends string & keyof PickByValueType<T, Entity> & keyof T>(propertyName: TProperty): FindResultJSON<T, Omit<TReturn, TProperty> & Pick<T, TProperty>, TJoins>;
640
+ }
572
641
  interface FindResult<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<TReturn[]> {
573
642
  select<TKeys extends string & keyof T>(keys: TKeys[]): FindResult<T, Pick<T, TKeys>, TJoins>;
574
643
  where(args: JoinedWhereQuery<T, TJoins>): FindResult<T, TReturn, TJoins>;
@@ -579,7 +648,13 @@ interface FindResult<T extends Entity, TReturn, TJoins extends JoinInfo = never>
579
648
  limit(value: number): FindResult<T, TReturn, TJoins>;
580
649
  skip(value: number): FindResult<T, TReturn, TJoins>;
581
650
  paginate(options: PaginateOptions): FindResult<T, TReturn, TJoins>;
651
+ withCount(): FindQueryWithCount<T, TReturn, TJoins>;
582
652
  UNSAFE_withOriginalFieldType<TProperty extends string & keyof PickByValueType<T, Entity> & keyof T>(propertyName: TProperty): FindResult<T, Omit<TReturn, TProperty> & Pick<T, TProperty>, TJoins>;
653
+ /**
654
+ * Returns results as plain objects instead of entity class instances.
655
+ * Useful for when data must be serializable.
656
+ */
657
+ toJSON(): FindResultJSON<T, TReturn, TJoins>;
583
658
  }
584
659
 
585
660
  interface IRepository<T extends Entity> extends IReadonlyRepository<T> {
@@ -791,6 +866,7 @@ interface Populate {
791
866
  skip?: number;
792
867
  limit?: number;
793
868
  pool?: PoolLike;
869
+ asPlainObjects?: boolean;
794
870
  }
795
871
  declare class ReadonlyRepository<T extends Entity> implements IReadonlyRepository<T> {
796
872
  private readonly _modelMetadata;
@@ -832,6 +908,8 @@ declare class ReadonlyRepository<T extends Entity> implements IReadonlyRepositor
832
908
  count(args?: CountArgs<T> | WhereQuery<T>): CountResult<T>;
833
909
  protected _buildInstance(row: Partial<QueryResult<T>>): QueryResult<T>;
834
910
  protected _buildInstances(rows: Partial<QueryResult<T>>[]): QueryResult<T>[];
911
+ protected _buildPlainObject(row: Partial<QueryResult<T>>): QueryResult<T>;
912
+ protected _buildPlainObjects(rows: Partial<QueryResult<T>>[]): QueryResult<T>[];
835
913
  protected _convertSortsToOrderBy(sorts: SortObject<T> | string): OrderBy<T>[];
836
914
  protected populateFields(entities: QueryResult<T>[], populates: Populate[]): Promise<void>;
837
915
  private populateSingleAssociation;
@@ -1029,4 +1107,4 @@ interface InitializeOptions extends IConnection {
1029
1107
  declare function initialize({ models, pool, readonlyPool, connections, expose }: InitializeOptions): Record<string, IReadonlyRepository<Entity> | IRepository<Entity>>;
1030
1108
 
1031
1109
  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 };
1110
+ export type { ClassLike, ColumnBaseMetadataOptions, ColumnCollectionMetadataOptions, ColumnMetadata, ColumnModelMetadataOptions, ColumnModifierMetadata, ColumnTypeMetadataOptions, Comparer, CountResult, CreateUpdateOptions, CreateUpdateParams, DeleteOptions, DestroyResult, DoNotReturnRecords, EntityFieldValue, EntityPrimitiveOrId, EntityStatic, ExcludeEntityCollections, ExcludeFunctions, FindArgs, FindOneArgs, FindOneResult, FindOneResultJSON, FindQueryWithCount, FindQueryWithCountJSON, FindResult, FindResultJSON, 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, PlainObject, 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
@@ -85,6 +85,15 @@ type PickFunctions<T> = {
85
85
  [K in keyof T as IncludeFunctions<T[K], K>]: T[K];
86
86
  };
87
87
 
88
+ /**
89
+ * Recursively converts entity types to plain object types.
90
+ * This is primarily for documentation/intent - at runtime, this strips the prototype chain.
91
+ * Preserves Date objects as-is since they're serializable by most frameworks.
92
+ */
93
+ type PlainObject<T> = T extends Date ? Date : T extends (infer U)[] ? PlainObject<U>[] : T extends object ? {
94
+ [K in keyof T]: PlainObject<T[K]>;
95
+ } : T;
96
+
88
97
  /**
89
98
  * Changes all properties with Entity values to Primitive (string|number). Removes any properties that with values
90
99
  * of Entity arrays
@@ -553,6 +562,16 @@ interface PopulateArgs<T extends Entity, K extends keyof T> {
553
562
  pool?: PoolLike;
554
563
  }
555
564
 
565
+ interface FindOneResultJSON<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<PlainObject<TReturn> | null> {
566
+ select<TKeys extends string & keyof T>(keys: TKeys[]): FindOneResultJSON<T, Pick<T, TKeys>, TJoins>;
567
+ where(args: JoinedWhereQuery<T, TJoins>): FindOneResultJSON<T, TReturn, TJoins>;
568
+ 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>): FindOneResultJSON<T, Omit<TReturn, TProperty> & Populated<T, TProperty, TPopulateType, TPopulateSelectKeys>, TJoins>;
569
+ join<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias): FindOneResultJSON<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
570
+ leftJoin<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias, on?: WhereQuery<GetValueType<T[TProperty], Entity>>): FindOneResultJSON<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
571
+ sort(value?: JoinedSort<T, TJoins>): FindOneResultJSON<T, TReturn, TJoins>;
572
+ UNSAFE_withOriginalFieldType<TProperty extends string & keyof PickByValueType<T, Entity> & keyof T>(propertyName: TProperty): FindOneResultJSON<T, Omit<TReturn, TProperty> & Pick<T, TProperty>, TJoins>;
573
+ UNSAFE_withFieldValue<TProperty extends string & keyof T, TValue extends T[TProperty]>(propertyName: TProperty, value: TValue): FindOneResultJSON<T, Omit<TReturn, TProperty> & PickAsType<T, TProperty, TValue>, TJoins>;
574
+ }
556
575
  interface FindOneResult<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<TReturn | null> {
557
576
  select<TKeys extends string & keyof T>(keys: TKeys[]): FindOneResult<T, Pick<T, TKeys>, TJoins>;
558
577
  where(args: JoinedWhereQuery<T, TJoins>): FindOneResult<T, TReturn, TJoins>;
@@ -562,6 +581,11 @@ interface FindOneResult<T extends Entity, TReturn, TJoins extends JoinInfo = nev
562
581
  sort(value?: JoinedSort<T, TJoins>): FindOneResult<T, TReturn, TJoins>;
563
582
  UNSAFE_withOriginalFieldType<TProperty extends string & keyof PickByValueType<T, Entity> & keyof T>(propertyName: TProperty): FindOneResult<T, Omit<TReturn, TProperty> & Pick<T, TProperty>, TJoins>;
564
583
  UNSAFE_withFieldValue<TProperty extends string & keyof T, TValue extends T[TProperty]>(propertyName: TProperty, value: TValue): FindOneResult<T, Omit<TReturn, TProperty> & PickAsType<T, TProperty, TValue>, TJoins>;
584
+ /**
585
+ * Returns result as a plain object instead of an entity class instance.
586
+ * Useful for when data must be serializable.
587
+ */
588
+ toJSON(): FindOneResultJSON<T, TReturn, TJoins>;
565
589
  }
566
590
 
567
591
  interface PaginateOptions {
@@ -569,6 +593,51 @@ interface PaginateOptions {
569
593
  page: number;
570
594
  }
571
595
 
596
+ interface FindWithCountResult<TReturn> {
597
+ results: TReturn[];
598
+ totalCount: number;
599
+ }
600
+ interface FindQueryWithCountJSON<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<FindWithCountResult<PlainObject<TReturn>>> {
601
+ select<TKeys extends string & keyof T>(keys: TKeys[]): FindQueryWithCountJSON<T, Pick<T, TKeys>, TJoins>;
602
+ where(args: JoinedWhereQuery<T, TJoins>): FindQueryWithCountJSON<T, TReturn, TJoins>;
603
+ 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>): FindQueryWithCountJSON<T, Omit<TReturn, TProperty> & Populated<T, TProperty, TPopulateType, TPopulateSelectKeys>, TJoins>;
604
+ join<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias): FindQueryWithCountJSON<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
605
+ leftJoin<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias, on?: WhereQuery<GetValueType<T[TProperty], Entity>>): FindQueryWithCountJSON<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
606
+ sort(value?: JoinedSort<T, TJoins>): FindQueryWithCountJSON<T, TReturn, TJoins>;
607
+ limit(value: number): FindQueryWithCountJSON<T, TReturn, TJoins>;
608
+ skip(value: number): FindQueryWithCountJSON<T, TReturn, TJoins>;
609
+ paginate(options: PaginateOptions): FindQueryWithCountJSON<T, TReturn, TJoins>;
610
+ }
611
+ interface FindQueryWithCount<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<FindWithCountResult<TReturn>> {
612
+ select<TKeys extends string & keyof T>(keys: TKeys[]): FindQueryWithCount<T, Pick<T, TKeys>, TJoins>;
613
+ where(args: JoinedWhereQuery<T, TJoins>): FindQueryWithCount<T, TReturn, TJoins>;
614
+ 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>;
615
+ join<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias): FindQueryWithCount<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
616
+ 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>;
617
+ sort(value?: JoinedSort<T, TJoins>): FindQueryWithCount<T, TReturn, TJoins>;
618
+ limit(value: number): FindQueryWithCount<T, TReturn, TJoins>;
619
+ skip(value: number): FindQueryWithCount<T, TReturn, TJoins>;
620
+ paginate(options: PaginateOptions): FindQueryWithCount<T, TReturn, TJoins>;
621
+ /**
622
+ * Returns results as plain objects instead of entity class instances.
623
+ * Useful for when data must be serializable.
624
+ */
625
+ toJSON(): FindQueryWithCountJSON<T, TReturn, TJoins>;
626
+ }
627
+
628
+ interface FindResultJSON<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<PlainObject<TReturn>[]> {
629
+ select<TKeys extends string & keyof T>(keys: TKeys[]): FindResultJSON<T, Pick<T, TKeys>, TJoins>;
630
+ where(args: JoinedWhereQuery<T, TJoins>): FindResultJSON<T, TReturn, TJoins>;
631
+ 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>): FindResultJSON<T, Omit<TReturn, TProperty> & Populated<T, TProperty, TPopulateType, TPopulateSelectKeys>, TJoins>;
632
+ join<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias): FindResultJSON<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
633
+ leftJoin<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias, on?: WhereQuery<GetValueType<T[TProperty], Entity>>): FindResultJSON<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
634
+ sort(value?: JoinedSort<T, TJoins>): FindResultJSON<T, TReturn, TJoins>;
635
+ limit(value: number): FindResultJSON<T, TReturn, TJoins>;
636
+ skip(value: number): FindResultJSON<T, TReturn, TJoins>;
637
+ paginate(options: PaginateOptions): FindResultJSON<T, TReturn, TJoins>;
638
+ withCount(): FindQueryWithCountJSON<T, TReturn, TJoins>;
639
+ UNSAFE_withOriginalFieldType<TProperty extends string & keyof PickByValueType<T, Entity> & keyof T>(propertyName: TProperty): FindResultJSON<T, Omit<TReturn, TProperty> & Pick<T, TProperty>, TJoins>;
640
+ }
572
641
  interface FindResult<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<TReturn[]> {
573
642
  select<TKeys extends string & keyof T>(keys: TKeys[]): FindResult<T, Pick<T, TKeys>, TJoins>;
574
643
  where(args: JoinedWhereQuery<T, TJoins>): FindResult<T, TReturn, TJoins>;
@@ -579,7 +648,13 @@ interface FindResult<T extends Entity, TReturn, TJoins extends JoinInfo = never>
579
648
  limit(value: number): FindResult<T, TReturn, TJoins>;
580
649
  skip(value: number): FindResult<T, TReturn, TJoins>;
581
650
  paginate(options: PaginateOptions): FindResult<T, TReturn, TJoins>;
651
+ withCount(): FindQueryWithCount<T, TReturn, TJoins>;
582
652
  UNSAFE_withOriginalFieldType<TProperty extends string & keyof PickByValueType<T, Entity> & keyof T>(propertyName: TProperty): FindResult<T, Omit<TReturn, TProperty> & Pick<T, TProperty>, TJoins>;
653
+ /**
654
+ * Returns results as plain objects instead of entity class instances.
655
+ * Useful for when data must be serializable.
656
+ */
657
+ toJSON(): FindResultJSON<T, TReturn, TJoins>;
583
658
  }
584
659
 
585
660
  interface IRepository<T extends Entity> extends IReadonlyRepository<T> {
@@ -791,6 +866,7 @@ interface Populate {
791
866
  skip?: number;
792
867
  limit?: number;
793
868
  pool?: PoolLike;
869
+ asPlainObjects?: boolean;
794
870
  }
795
871
  declare class ReadonlyRepository<T extends Entity> implements IReadonlyRepository<T> {
796
872
  private readonly _modelMetadata;
@@ -832,6 +908,8 @@ declare class ReadonlyRepository<T extends Entity> implements IReadonlyRepositor
832
908
  count(args?: CountArgs<T> | WhereQuery<T>): CountResult<T>;
833
909
  protected _buildInstance(row: Partial<QueryResult<T>>): QueryResult<T>;
834
910
  protected _buildInstances(rows: Partial<QueryResult<T>>[]): QueryResult<T>[];
911
+ protected _buildPlainObject(row: Partial<QueryResult<T>>): QueryResult<T>;
912
+ protected _buildPlainObjects(rows: Partial<QueryResult<T>>[]): QueryResult<T>[];
835
913
  protected _convertSortsToOrderBy(sorts: SortObject<T> | string): OrderBy<T>[];
836
914
  protected populateFields(entities: QueryResult<T>[], populates: Populate[]): Promise<void>;
837
915
  private populateSingleAssociation;
@@ -1029,4 +1107,4 @@ interface InitializeOptions extends IConnection {
1029
1107
  declare function initialize({ models, pool, readonlyPool, connections, expose }: InitializeOptions): Record<string, IReadonlyRepository<Entity> | IRepository<Entity>>;
1030
1108
 
1031
1109
  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 };
1110
+ export type { ClassLike, ColumnBaseMetadataOptions, ColumnCollectionMetadataOptions, ColumnMetadata, ColumnModelMetadataOptions, ColumnModifierMetadata, ColumnTypeMetadataOptions, Comparer, CountResult, CreateUpdateOptions, CreateUpdateParams, DeleteOptions, DestroyResult, DoNotReturnRecords, EntityFieldValue, EntityPrimitiveOrId, EntityStatic, ExcludeEntityCollections, ExcludeFunctions, FindArgs, FindOneArgs, FindOneResult, FindOneResultJSON, FindQueryWithCount, FindQueryWithCountJSON, FindResult, FindResultJSON, 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, PlainObject, 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
@@ -85,6 +85,15 @@ type PickFunctions<T> = {
85
85
  [K in keyof T as IncludeFunctions<T[K], K>]: T[K];
86
86
  };
87
87
 
88
+ /**
89
+ * Recursively converts entity types to plain object types.
90
+ * This is primarily for documentation/intent - at runtime, this strips the prototype chain.
91
+ * Preserves Date objects as-is since they're serializable by most frameworks.
92
+ */
93
+ type PlainObject<T> = T extends Date ? Date : T extends (infer U)[] ? PlainObject<U>[] : T extends object ? {
94
+ [K in keyof T]: PlainObject<T[K]>;
95
+ } : T;
96
+
88
97
  /**
89
98
  * Changes all properties with Entity values to Primitive (string|number). Removes any properties that with values
90
99
  * of Entity arrays
@@ -553,6 +562,16 @@ interface PopulateArgs<T extends Entity, K extends keyof T> {
553
562
  pool?: PoolLike;
554
563
  }
555
564
 
565
+ interface FindOneResultJSON<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<PlainObject<TReturn> | null> {
566
+ select<TKeys extends string & keyof T>(keys: TKeys[]): FindOneResultJSON<T, Pick<T, TKeys>, TJoins>;
567
+ where(args: JoinedWhereQuery<T, TJoins>): FindOneResultJSON<T, TReturn, TJoins>;
568
+ 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>): FindOneResultJSON<T, Omit<TReturn, TProperty> & Populated<T, TProperty, TPopulateType, TPopulateSelectKeys>, TJoins>;
569
+ join<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias): FindOneResultJSON<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
570
+ leftJoin<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias, on?: WhereQuery<GetValueType<T[TProperty], Entity>>): FindOneResultJSON<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
571
+ sort(value?: JoinedSort<T, TJoins>): FindOneResultJSON<T, TReturn, TJoins>;
572
+ UNSAFE_withOriginalFieldType<TProperty extends string & keyof PickByValueType<T, Entity> & keyof T>(propertyName: TProperty): FindOneResultJSON<T, Omit<TReturn, TProperty> & Pick<T, TProperty>, TJoins>;
573
+ UNSAFE_withFieldValue<TProperty extends string & keyof T, TValue extends T[TProperty]>(propertyName: TProperty, value: TValue): FindOneResultJSON<T, Omit<TReturn, TProperty> & PickAsType<T, TProperty, TValue>, TJoins>;
574
+ }
556
575
  interface FindOneResult<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<TReturn | null> {
557
576
  select<TKeys extends string & keyof T>(keys: TKeys[]): FindOneResult<T, Pick<T, TKeys>, TJoins>;
558
577
  where(args: JoinedWhereQuery<T, TJoins>): FindOneResult<T, TReturn, TJoins>;
@@ -562,6 +581,11 @@ interface FindOneResult<T extends Entity, TReturn, TJoins extends JoinInfo = nev
562
581
  sort(value?: JoinedSort<T, TJoins>): FindOneResult<T, TReturn, TJoins>;
563
582
  UNSAFE_withOriginalFieldType<TProperty extends string & keyof PickByValueType<T, Entity> & keyof T>(propertyName: TProperty): FindOneResult<T, Omit<TReturn, TProperty> & Pick<T, TProperty>, TJoins>;
564
583
  UNSAFE_withFieldValue<TProperty extends string & keyof T, TValue extends T[TProperty]>(propertyName: TProperty, value: TValue): FindOneResult<T, Omit<TReturn, TProperty> & PickAsType<T, TProperty, TValue>, TJoins>;
584
+ /**
585
+ * Returns result as a plain object instead of an entity class instance.
586
+ * Useful for when data must be serializable.
587
+ */
588
+ toJSON(): FindOneResultJSON<T, TReturn, TJoins>;
565
589
  }
566
590
 
567
591
  interface PaginateOptions {
@@ -569,6 +593,51 @@ interface PaginateOptions {
569
593
  page: number;
570
594
  }
571
595
 
596
+ interface FindWithCountResult<TReturn> {
597
+ results: TReturn[];
598
+ totalCount: number;
599
+ }
600
+ interface FindQueryWithCountJSON<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<FindWithCountResult<PlainObject<TReturn>>> {
601
+ select<TKeys extends string & keyof T>(keys: TKeys[]): FindQueryWithCountJSON<T, Pick<T, TKeys>, TJoins>;
602
+ where(args: JoinedWhereQuery<T, TJoins>): FindQueryWithCountJSON<T, TReturn, TJoins>;
603
+ 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>): FindQueryWithCountJSON<T, Omit<TReturn, TProperty> & Populated<T, TProperty, TPopulateType, TPopulateSelectKeys>, TJoins>;
604
+ join<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias): FindQueryWithCountJSON<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
605
+ leftJoin<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias, on?: WhereQuery<GetValueType<T[TProperty], Entity>>): FindQueryWithCountJSON<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
606
+ sort(value?: JoinedSort<T, TJoins>): FindQueryWithCountJSON<T, TReturn, TJoins>;
607
+ limit(value: number): FindQueryWithCountJSON<T, TReturn, TJoins>;
608
+ skip(value: number): FindQueryWithCountJSON<T, TReturn, TJoins>;
609
+ paginate(options: PaginateOptions): FindQueryWithCountJSON<T, TReturn, TJoins>;
610
+ }
611
+ interface FindQueryWithCount<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<FindWithCountResult<TReturn>> {
612
+ select<TKeys extends string & keyof T>(keys: TKeys[]): FindQueryWithCount<T, Pick<T, TKeys>, TJoins>;
613
+ where(args: JoinedWhereQuery<T, TJoins>): FindQueryWithCount<T, TReturn, TJoins>;
614
+ 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>;
615
+ join<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias): FindQueryWithCount<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
616
+ 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>;
617
+ sort(value?: JoinedSort<T, TJoins>): FindQueryWithCount<T, TReturn, TJoins>;
618
+ limit(value: number): FindQueryWithCount<T, TReturn, TJoins>;
619
+ skip(value: number): FindQueryWithCount<T, TReturn, TJoins>;
620
+ paginate(options: PaginateOptions): FindQueryWithCount<T, TReturn, TJoins>;
621
+ /**
622
+ * Returns results as plain objects instead of entity class instances.
623
+ * Useful for when data must be serializable.
624
+ */
625
+ toJSON(): FindQueryWithCountJSON<T, TReturn, TJoins>;
626
+ }
627
+
628
+ interface FindResultJSON<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<PlainObject<TReturn>[]> {
629
+ select<TKeys extends string & keyof T>(keys: TKeys[]): FindResultJSON<T, Pick<T, TKeys>, TJoins>;
630
+ where(args: JoinedWhereQuery<T, TJoins>): FindResultJSON<T, TReturn, TJoins>;
631
+ 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>): FindResultJSON<T, Omit<TReturn, TProperty> & Populated<T, TProperty, TPopulateType, TPopulateSelectKeys>, TJoins>;
632
+ join<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias): FindResultJSON<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
633
+ leftJoin<TProperty extends ModelRelationshipKeys<T>, TAlias extends string = TProperty>(propertyName: TProperty, alias?: TAlias, on?: WhereQuery<GetValueType<T[TProperty], Entity>>): FindResultJSON<T, TReturn, JoinInfo<TProperty, TAlias, GetValueType<T[TProperty], Entity>> | TJoins>;
634
+ sort(value?: JoinedSort<T, TJoins>): FindResultJSON<T, TReturn, TJoins>;
635
+ limit(value: number): FindResultJSON<T, TReturn, TJoins>;
636
+ skip(value: number): FindResultJSON<T, TReturn, TJoins>;
637
+ paginate(options: PaginateOptions): FindResultJSON<T, TReturn, TJoins>;
638
+ withCount(): FindQueryWithCountJSON<T, TReturn, TJoins>;
639
+ UNSAFE_withOriginalFieldType<TProperty extends string & keyof PickByValueType<T, Entity> & keyof T>(propertyName: TProperty): FindResultJSON<T, Omit<TReturn, TProperty> & Pick<T, TProperty>, TJoins>;
640
+ }
572
641
  interface FindResult<T extends Entity, TReturn, TJoins extends JoinInfo = never> extends PromiseLike<TReturn[]> {
573
642
  select<TKeys extends string & keyof T>(keys: TKeys[]): FindResult<T, Pick<T, TKeys>, TJoins>;
574
643
  where(args: JoinedWhereQuery<T, TJoins>): FindResult<T, TReturn, TJoins>;
@@ -579,7 +648,13 @@ interface FindResult<T extends Entity, TReturn, TJoins extends JoinInfo = never>
579
648
  limit(value: number): FindResult<T, TReturn, TJoins>;
580
649
  skip(value: number): FindResult<T, TReturn, TJoins>;
581
650
  paginate(options: PaginateOptions): FindResult<T, TReturn, TJoins>;
651
+ withCount(): FindQueryWithCount<T, TReturn, TJoins>;
582
652
  UNSAFE_withOriginalFieldType<TProperty extends string & keyof PickByValueType<T, Entity> & keyof T>(propertyName: TProperty): FindResult<T, Omit<TReturn, TProperty> & Pick<T, TProperty>, TJoins>;
653
+ /**
654
+ * Returns results as plain objects instead of entity class instances.
655
+ * Useful for when data must be serializable.
656
+ */
657
+ toJSON(): FindResultJSON<T, TReturn, TJoins>;
583
658
  }
584
659
 
585
660
  interface IRepository<T extends Entity> extends IReadonlyRepository<T> {
@@ -791,6 +866,7 @@ interface Populate {
791
866
  skip?: number;
792
867
  limit?: number;
793
868
  pool?: PoolLike;
869
+ asPlainObjects?: boolean;
794
870
  }
795
871
  declare class ReadonlyRepository<T extends Entity> implements IReadonlyRepository<T> {
796
872
  private readonly _modelMetadata;
@@ -832,6 +908,8 @@ declare class ReadonlyRepository<T extends Entity> implements IReadonlyRepositor
832
908
  count(args?: CountArgs<T> | WhereQuery<T>): CountResult<T>;
833
909
  protected _buildInstance(row: Partial<QueryResult<T>>): QueryResult<T>;
834
910
  protected _buildInstances(rows: Partial<QueryResult<T>>[]): QueryResult<T>[];
911
+ protected _buildPlainObject(row: Partial<QueryResult<T>>): QueryResult<T>;
912
+ protected _buildPlainObjects(rows: Partial<QueryResult<T>>[]): QueryResult<T>[];
835
913
  protected _convertSortsToOrderBy(sorts: SortObject<T> | string): OrderBy<T>[];
836
914
  protected populateFields(entities: QueryResult<T>[], populates: Populate[]): Promise<void>;
837
915
  private populateSingleAssociation;
@@ -1029,4 +1107,4 @@ interface InitializeOptions extends IConnection {
1029
1107
  declare function initialize({ models, pool, readonlyPool, connections, expose }: InitializeOptions): Record<string, IReadonlyRepository<Entity> | IRepository<Entity>>;
1030
1108
 
1031
1109
  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 };
1110
+ export type { ClassLike, ColumnBaseMetadataOptions, ColumnCollectionMetadataOptions, ColumnMetadata, ColumnModelMetadataOptions, ColumnModifierMetadata, ColumnTypeMetadataOptions, Comparer, CountResult, CreateUpdateOptions, CreateUpdateParams, DeleteOptions, DestroyResult, DoNotReturnRecords, EntityFieldValue, EntityPrimitiveOrId, EntityStatic, ExcludeEntityCollections, ExcludeFunctions, FindArgs, FindOneArgs, FindOneResult, FindOneResultJSON, FindQueryWithCount, FindQueryWithCountJSON, FindResult, FindResultJSON, 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, PlainObject, 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];
@@ -1955,6 +1959,7 @@ class ReadonlyRepository {
1955
1959
  const manuallySetFields = [];
1956
1960
  const sorts = sort ? this._convertSortsToOrderBy(sort) : [];
1957
1961
  const joins = [];
1962
+ let returnAsPlainObjects = false;
1958
1963
  const modelInstance = this;
1959
1964
  return {
1960
1965
  /**
@@ -2044,6 +2049,10 @@ class ReadonlyRepository {
2044
2049
  });
2045
2050
  return this;
2046
2051
  },
2052
+ toJSON() {
2053
+ returnAsPlainObjects = true;
2054
+ return this;
2055
+ },
2047
2056
  async then(resolve, reject) {
2048
2057
  try {
2049
2058
  if (typeof where === "string") {
@@ -2063,9 +2072,10 @@ class ReadonlyRepository {
2063
2072
  const results = await pool.query(query, params);
2064
2073
  const firstResult = results.rows[0];
2065
2074
  if (firstResult) {
2066
- const result = modelInstance._buildInstance(firstResult);
2075
+ const result = returnAsPlainObjects ? modelInstance._buildPlainObject(firstResult) : modelInstance._buildInstance(firstResult);
2067
2076
  if (populates.length) {
2068
- await modelInstance.populateFields([result], populates);
2077
+ const populatesWithFlag = populates.map((pop) => ({ ...pop, asPlainObjects: returnAsPlainObjects }));
2078
+ await modelInstance.populateFields([result], populatesWithFlag);
2069
2079
  }
2070
2080
  for (const manuallySetField of manuallySetFields) {
2071
2081
  result[manuallySetField.propertyName] = manuallySetField.value;
@@ -2143,6 +2153,8 @@ ${stack ?? ""}`;
2143
2153
  const populates = [];
2144
2154
  const sorts = sort ? this._convertSortsToOrderBy(sort) : [];
2145
2155
  const joins = [];
2156
+ let includeCount = false;
2157
+ let returnAsPlainObjects = false;
2146
2158
  const modelInstance = this;
2147
2159
  return {
2148
2160
  /**
@@ -2253,6 +2265,14 @@ ${stack ?? ""}`;
2253
2265
  const safePage = Math.max(page, 1);
2254
2266
  return this.skip(safePage * paginateLimit - paginateLimit).limit(paginateLimit);
2255
2267
  },
2268
+ withCount() {
2269
+ includeCount = true;
2270
+ return this;
2271
+ },
2272
+ toJSON() {
2273
+ returnAsPlainObjects = true;
2274
+ return this;
2275
+ },
2256
2276
  async then(resolve, reject) {
2257
2277
  try {
2258
2278
  if (typeof where === "string") {
@@ -2266,13 +2286,29 @@ ${stack ?? ""}`;
2266
2286
  sorts,
2267
2287
  skip: skip ?? 0,
2268
2288
  limit: limit ?? 0,
2269
- joins
2289
+ joins,
2290
+ includeCount
2270
2291
  });
2271
2292
  const pool = poolOverride ?? modelInstance._readonlyPool;
2272
2293
  const results = await pool.query(query, params);
2273
- const entities = modelInstance._buildInstances(results.rows);
2294
+ let totalCount = 0;
2295
+ if (includeCount && results.rows.length > 0 && results.rows[0]?.__total_count__ !== void 0) {
2296
+ totalCount = Number(results.rows[0].__total_count__);
2297
+ }
2298
+ const rows = includeCount ? results.rows.map((row) => {
2299
+ const { __total_count__, ...rest } = row;
2300
+ return rest;
2301
+ }) : results.rows;
2302
+ const entities = returnAsPlainObjects ? modelInstance._buildPlainObjects(rows) : modelInstance._buildInstances(rows);
2274
2303
  if (populates.length) {
2275
- await modelInstance.populateFields(entities, populates);
2304
+ const populatesWithFlag = populates.map((pop) => ({ ...pop, asPlainObjects: returnAsPlainObjects }));
2305
+ await modelInstance.populateFields(entities, populatesWithFlag);
2306
+ }
2307
+ if (includeCount) {
2308
+ return await resolve({
2309
+ results: entities,
2310
+ totalCount
2311
+ });
2276
2312
  }
2277
2313
  return await resolve(entities);
2278
2314
  } catch (ex) {
@@ -2388,6 +2424,40 @@ ${stack ?? ""}`;
2388
2424
  _buildInstances(rows) {
2389
2425
  return rows.map((row) => this._buildInstance(row));
2390
2426
  }
2427
+ _buildPlainObject(row) {
2428
+ const plainObject = { ...row };
2429
+ for (const name of this._floatProperties) {
2430
+ const originalValue = plainObject[name];
2431
+ if (originalValue != null && typeof originalValue === "string") {
2432
+ try {
2433
+ const value = Number(originalValue);
2434
+ if (Number.isFinite(value) && value.toString() === originalValue) {
2435
+ plainObject[name] = value;
2436
+ }
2437
+ } catch {
2438
+ }
2439
+ }
2440
+ }
2441
+ for (const name of this._intProperties) {
2442
+ const originalValue = plainObject[name];
2443
+ if (originalValue != null && typeof originalValue === "string") {
2444
+ try {
2445
+ const value = Number(originalValue);
2446
+ if (Number.isFinite(value) && value.toString() === originalValue) {
2447
+ const valueAsInt = Math.trunc(value);
2448
+ if (Number.isSafeInteger(valueAsInt)) {
2449
+ plainObject[name] = valueAsInt;
2450
+ }
2451
+ }
2452
+ } catch {
2453
+ }
2454
+ }
2455
+ }
2456
+ return plainObject;
2457
+ }
2458
+ _buildPlainObjects(rows) {
2459
+ return rows.map((row) => this._buildPlainObject(row));
2460
+ }
2391
2461
  _convertSortsToOrderBy(sorts) {
2392
2462
  const result = [];
2393
2463
  if (sorts) {
@@ -2504,12 +2574,13 @@ ${stack ?? ""}`;
2504
2574
  [populateRepository.model.primaryKeyColumn.propertyName]: Array.from(populateIds),
2505
2575
  ...populate.where
2506
2576
  };
2507
- const populateResults = await populateRepository.find({
2577
+ const findQuery = populateRepository.find({
2508
2578
  select: populate.select,
2509
2579
  where: populateWhere,
2510
2580
  sort: populate.sort,
2511
2581
  pool: populate.pool
2512
2582
  });
2583
+ const populateResults = populate.asPlainObjects ? await findQuery.toJSON() : await findQuery;
2513
2584
  const populateResultsById = keyBy(populateResults, populateRepository.model.primaryKeyColumn.propertyName);
2514
2585
  for (const entity of entities) {
2515
2586
  entity[propertyName] = populateResultsById[entity[propertyName]];
@@ -2523,7 +2594,7 @@ ${stack ?? ""}`;
2523
2594
  [column.via]: entityIds,
2524
2595
  ...populate.where
2525
2596
  };
2526
- const populateResults = await populateRepository.find({
2597
+ const findQuery = populateRepository.find({
2527
2598
  select: populate.select,
2528
2599
  where: populateWhere,
2529
2600
  sort: populate.sort,
@@ -2531,6 +2602,7 @@ ${stack ?? ""}`;
2531
2602
  limit: populate.limit,
2532
2603
  pool: populate.pool
2533
2604
  });
2605
+ const populateResults = populate.asPlainObjects ? await findQuery.toJSON() : await findQuery;
2534
2606
  if (entities.length === 1) {
2535
2607
  for (const entity of entities) {
2536
2608
  entity[populate.propertyName] = populateResults;
@@ -2583,7 +2655,7 @@ ${stack ?? ""}`;
2583
2655
  [populateModelPrimaryKeyPropertyName]: Array.from(populateIds),
2584
2656
  ...populate.where
2585
2657
  };
2586
- const populateResults = await populateRepository.find({
2658
+ const findQuery = populateRepository.find({
2587
2659
  select: populate.select,
2588
2660
  where: populateWhere,
2589
2661
  sort: populate.sort,
@@ -2591,6 +2663,7 @@ ${stack ?? ""}`;
2591
2663
  limit: populate.limit,
2592
2664
  pool: populate.pool
2593
2665
  });
2666
+ const populateResults = populate.asPlainObjects ? await findQuery.toJSON() : await findQuery;
2594
2667
  const populateResultsById = keyBy(populateResults, populateModelPrimaryKeyPropertyName);
2595
2668
  for (const entity of entities) {
2596
2669
  const populatedItems = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bigal",
3
- "version": "15.3.0",
3
+ "version": "15.5.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",
@@ -46,7 +46,7 @@
46
46
  "@types/node": ">=20",
47
47
  "chai": "6.2.2",
48
48
  "eslint": "9.39.2",
49
- "eslint-config-decent": "3.1.90",
49
+ "eslint-config-decent": "3.1.93",
50
50
  "husky": "9.1.7",
51
51
  "lint-staged": "16.2.7",
52
52
  "markdownlint-cli": "0.47.0",
@@ -56,7 +56,7 @@
56
56
  "prettier": "3.7.4",
57
57
  "semantic-release": "25.0.2",
58
58
  "strict-event-emitter-types": "2.0.0",
59
- "postgres-pool": "11.0.1",
59
+ "postgres-pool": "11.0.2",
60
60
  "ts-mockito": "2.6.1",
61
61
  "ts-node": "10.9.2",
62
62
  "typescript": "5.9.3",