@@ -7,7 +7,22 @@ const { Client } = pg;
77const clientBase = new Client ( ) ;
88
99/**
10- * @typedef {FreeObj } TagCriteria
10+ * Tag group definition used to build dynamic SQL clauses for tag filtering.
11+ *
12+ * @typedef {Object } TagCriteria - Tag group definition to build the clause from.
13+ * @property {string } [group.column] - SQL column name for tag data (defaults to `this.getColumnName()`).
14+ * @property {string } [group.valueName] - Alias used for JSON values (defaults to `this.defaultValueName`).
15+ * @property {boolean } [group.allowWildcards=false] - Whether wildcards are allowed in matching.
16+ * @property {Array<string|string[]> } [group.include=[]] - Tag values or grouped OR conditions to include.
17+ */
18+
19+ /**
20+ * Represents the result of a paginated query.
21+ *
22+ * @typedef {Object } PaginationResult
23+ * @property {unknown[] } items - Array of items returned for the current page.
24+ * @property {number } totalPages - Total number of available pages based on the query and per-page limit.
25+ * @property {number } totalItems - Total number of items matching the query without pagination.
1126 */
1227
1328/**
@@ -21,7 +36,7 @@ const clientBase = new Client();
2136 * values?: string[]; // List of column names to select.
2237 * boost?: { // Boost configuration for weighted ranking.
2338 * alias?: string; // The alias to associate with the boost configuration.
24- * value?: boostValue []; // List of boost rules to apply.
39+ * value?: BoostValue []; // List of boost rules to apply.
2540 * };
2641 * } |
2742 * null
@@ -61,7 +76,7 @@ const clientBase = new Client();
6176/**
6277 * Represents a boosting rule for weighted query ranking.
6378 *
64- * @typedef {Object } boostValue
79+ * @typedef {Object } BoostValue
6580 * @property {string[] } columns - List of columns to apply the boost on.
6681 * @property {string } operator - Operator used in the condition (e.g., '=', 'LIKE').
6782 * @property {string } value - Value to match in the condition.
@@ -74,7 +89,7 @@ const clientBase = new Client();
7489 * - `compare`: The ON clause condition.
7590 * - `type` (optional): One of the supported JOIN types (e.g., 'left', 'inner'). Defaults to 'left'.
7691 *
77- * @typedef {{ table: string; compare: string; type?: string; } } joinObj
92+ * @typedef {{ table: string; compare: string; type?: string; } } JoinObj
7893 */
7994
8095/**
@@ -488,7 +503,7 @@ class PuddySqlQuery {
488503 /**
489504 * Boost parser helper
490505 *
491- * @param {boostValue [] } boostArray
506+ * @param {BoostValue [] } boostArray
492507 * @param {string } alias
493508 * @returns {string }
494509 */
@@ -1129,8 +1144,9 @@ class PuddySqlQuery {
11291144 escapeValuesFix ( v , name ) {
11301145 const column = this . #table?. [ name ] ;
11311146 const type = column . type || '' ;
1132- if ( typeof this . #jsonEscapeFix[ type ] !== 'function' ) return v ;
1133- else return this . #jsonEscapeFix[ type ] ( v ) ;
1147+ const func = this . #jsonEscapeFix[ type ] ;
1148+ if ( typeof func !== 'function' ) return v ;
1149+ else return func ( v ) ;
11341150 }
11351151
11361152 /**
@@ -1414,7 +1430,7 @@ class PuddySqlQuery {
14141430 * @param {number } perPage - The number of items per page.
14151431 * @param {number } page - The current page number (starting from 1).
14161432 * @param {string } queryName - The query name to insert into the sql debug.
1417- * @returns {Promise<{ items: any[], totalPages: number, totalItems: number } > }
1433+ * @returns {Promise<PaginationResult > }
14181434 */
14191435 async execPagination ( query , params , perPage , page , queryName = '' ) {
14201436 if ( typeof query !== 'string' )
@@ -1434,18 +1450,27 @@ class PuddySqlQuery {
14341450
14351451 // Count total items
14361452 const countQuery = `SELECT COUNT(*) as total FROM (${ query } ) AS count_wrapper` ;
1437- // @ts -ignore
1438- const { total } = ! isZero
1453+ const countResult = ! isZero
14391454 ? await db . get ( countQuery , params , `pagination-${ queryName } ` )
14401455 : { total : 0 } ;
14411456
1457+ const total = isJsonObject ( countResult )
1458+ ? typeof countResult . total === 'number' &&
1459+ ! Number . isNaN ( countResult . total ) &&
1460+ Number . isFinite ( countResult . total ) &&
1461+ countResult . total >= 0
1462+ ? countResult . total
1463+ : 0
1464+ : 0 ;
1465+
14421466 // Fetch paginated items
14431467 const paginatedQuery = `${ query } LIMIT ? OFFSET ?` ;
14441468 const items = ! isZero
14451469 ? await db . all ( paginatedQuery , [ ...params , perPage , offset ] , `pagination-${ queryName } ` )
14461470 : [ ] ;
14471471
14481472 const totalPages = ! isZero ? Math . ceil ( total / perPage ) : 0 ;
1473+ for ( const index in items ) this . resultChecker ( items [ index ] ) ;
14491474
14501475 return {
14511476 items,
@@ -1654,12 +1679,12 @@ class PuddySqlQuery {
16541679 * - If `join` is an array of objects: generates multiple JOINs with aliases (`j1`, `j2`, ...).
16551680 * - If `join` is invalid or empty: falls back to `insertJoin()` using internal settings.
16561681 *
1657- * @param {joinObj|joinObj []|string|null } [join] - The join configuration(s).
1682+ * @param {JoinObj|JoinObj []|string|null } [join] - The join configuration(s).
16581683 * @returns {string } One or more JOIN SQL snippets.
16591684 */
16601685 parseJoin ( join ) {
16611686 /**
1662- * @param {joinObj } j
1687+ * @param {JoinObj } j
16631688 * @param {number } idx
16641689 * @returns {string }
16651690 */
@@ -1687,6 +1712,10 @@ class PuddySqlQuery {
16871712 : this . insertJoin ( ) ;
16881713 }
16891714
1715+ /**
1716+ * @typedef {{ page: number, pages: number, total: number, position: number, item?: FreeObj } } FindResult
1717+ */
1718+
16901719 /**
16911720 * Finds the first item matching the filter, along with its position, page, and total info.
16921721 * Uses a single SQL query to calculate everything efficiently.
@@ -1700,8 +1729,8 @@ class PuddySqlQuery {
17001729 * @param {number } [searchData.perPage] - Number of items per page.
17011730 * @param {SelectQuery } [searchData.select='*'] - Which columns to select. Set to null to skip item data.
17021731 * @param {string } [searchData.order] - SQL ORDER BY clause. Defaults to configured order.
1703- * @param {string|joinObj|joinObj [] } [searchData.join] - JOIN definitions with table, compare, and optional type.
1704- * @returns {Promise<{ page: number, pages: number, total: number, position: number, item?: object } | null> }
1732+ * @param {string|JoinObj|JoinObj [] } [searchData.join] - JOIN definitions with table, compare, and optional type.
1733+ * @returns {Promise<FindResult | null> }
17051734 */
17061735 async find ( searchData = { } ) {
17071736 const db = this . getDb ( ) ;
@@ -1779,6 +1808,7 @@ class PuddySqlQuery {
17791808 const position = parseInt ( row . position ) ;
17801809 const page = Math . floor ( ( position - 1 ) / perPage ) + 1 ;
17811810
1811+ /** @type {FindResult } */
17821812 const response = { page, pages, total, position } ;
17831813
17841814 // If selectValue is NOT null, return the item
@@ -1805,16 +1835,16 @@ class PuddySqlQuery {
18051835 * @param {Object } [searchData={}] - Main search configuration.
18061836 * @param {FreeObj } [searchData.q={}] - Nested criteria object.
18071837 * Can be a flat object style or grouped with `{ group: 'AND'|'OR', conditions: [...] }`.
1808- * @param {TagCriteria[]|TagCriteria|null } [searchData.tagCriteria ] - One or multiple tag criteria groups.
1809- * @param {string[] } [searchData.tagCriteriaOps ] - Optional logical operators between tag groups (e.g., ['AND', 'OR']).
1838+ * @param {TagCriteria[]|TagCriteria|null } [searchData.tagsQ ] - One or multiple tag criteria groups.
1839+ * @param {string[] } [searchData.tagsOpsQ ] - Optional logical operators between tag groups (e.g., ['AND', 'OR']).
18101840 * @param {SelectQuery } [searchData.select='*'] - Defines which columns or expressions should be selected in the query.
18111841 * @param {number|null } [searchData.perPage=null] - Number of results per page. If set, pagination is applied.
18121842 * @param {number } [searchData.page=1] - Page number to retrieve when `perPage` is used.
18131843 * @param {string } [searchData.order] - Custom `ORDER BY` clause (e.g. `'created_at DESC'`).
1814- * @param {string|joinObj|joinObj [] } [searchData.join] - A string for single join or array of objects for multiple joins.
1844+ * @param {string|JoinObj|JoinObj [] } [searchData.join] - A string for single join or array of objects for multiple joins.
18151845 * Each object should contain `{ table: 'name', compare: 'ON clause' }`.
18161846 * @param {number } [searchData.limit] - Max number of results to return (ignored when `perPage` is used).
1817- * @returns {Promise<FreeObj[]> } - Result rows matching the query.
1847+ * @returns {Promise<FreeObj[]|PaginationResult > } - Result rows matching the query.
18181848 *
18191849 * @example
18201850 * // Flat search:
@@ -1878,7 +1908,7 @@ class PuddySqlQuery {
18781908 const operators = Array . isArray ( tagCriteriaOps ) ? tagCriteriaOps : [ ] ;
18791909
18801910 tagCriteria . forEach ( ( group , i ) => {
1881- const column = group ? .column || 'tags' ; // default name if not set
1911+ const column = typeof group . column === 'string' ? group . column : 'tags' ; // default name if not set
18821912 const tag = this . getTagEditor ( column ) ;
18831913 if ( ! tag ) return ;
18841914
0 commit comments