findMany is a method that retrieves multiple records matching conditions. It supports pagination, search, filtering, and sorting, returning results along with the total count.
findMany is not defined in BaseModelClass. Itβs a standard pattern automatically generated
by the Syncer in each Model class when you create an Entity.
Type Signature
async findMany < T extends SubsetKey , LP extends ListParams > (
subset : T ,
params ?: LP ,
): Promise < ListResult < LP , SubsetMapping [ T ] >>
Auto-Generated Code
Sonamu automatically generates the following code based on your Entity:
// src/application/user/user.model.ts (auto-generated)
class UserModelClass extends BaseModelClass {
@ api ({ httpMethod: "GET" , clients: [ "axios" , "tanstack-query" ], resourceName: "Users" })
async findMany < T extends UserSubsetKey , LP extends UserListParams >(
subset : T ,
rawParams ?: LP ,
) : Promise < ListResult < LP , UserSubsetMapping [ T ]>> {
// 1. Set default values
const params = {
num: 24 ,
page: 1 ,
search: "id" as const ,
orderBy: "id-desc" as const ,
... rawParams ,
} satisfies UserListParams ;
// 2. Get subset query builder
const { qb } = this . getSubsetQueries ( subset );
// 3. Add filtering conditions
if ( params . id ) {
qb . whereIn ( "users.id" , asArray ( params . id ));
}
// 4. Search conditions
if ( params . search && params . keyword ) {
if ( params . search === "id" ) {
qb . where ( "users.id" , Number ( params . keyword ));
} else if ( params . search === "email" ) {
qb . where ( "users.email" , "like" , `% ${ params . keyword } %` );
}
}
// 5. Sorting
if ( params . orderBy === "id-desc" ) {
qb . orderBy ( "users.id" , "desc" );
}
// 6. Create enhancers
const enhancers = this . createEnhancers ({
A : ( row ) => ({ ... row }),
B : ( row ) => ({ ... row }),
// ... Calculate virtual fields per subset
});
// 7. Execute query
return this . executeSubsetQuery ({
subset ,
qb ,
params ,
enhancers ,
debug: false ,
});
}
}
How it works:
getSubsetQueries() : Gets subset query builder from BaseModelClass method
Add conditions : Add WHERE, search, and sorting conditions
createEnhancers() : Creates virtual field calculation functions from BaseModelClass method
executeSubsetQuery() : Executes actual query from BaseModelClass method
COUNT query (total)
SELECT query (rows)
Loader execution (load relationship data)
Hydrate (flat β nested objects)
Apply enhancers (calculate virtual fields)
Parameters
Specifies the subset of data to retrieve.
Type: SubsetKey (e.g., "A", "B", "C")
// Fetch basic info only
const { rows } = await UserModel . findMany ( "A" , { num: 10 , page: 1 });
// Include relationship data
const { rows } = await UserModel . findMany ( "B" , { num: 10 , page: 1 });
Query conditions and pagination settings.
Type: ListParams (optional)
type UserListParams = {
// Pagination
num ?: number ; // Items per page (default: 24)
page ?: number ; // Page number (default: 1)
// Filtering
id ?: number | number []; // ID filter
status ?: "active" | "inactive" ; // Custom filter
// Search
search ?: "id" | "email" | "name" ; // Search field
keyword ?: string ; // Search keyword
// Sorting
orderBy ?: "id-desc" | "created-desc" ; // Sort order
// Query mode
queryMode ?: "list" | "count" | "both" ; // Query mode
// Semantic search (vector search)
semanticQuery ?: Record < string , unknown >; // Semantic search parameters
};
Default Values
const params = {
num: 24 ,
page: 1 ,
search: "id" ,
orderBy: "id-desc" ,
... rawParams , // Overwrite with user input
};
Return Value
Type: Promise<ListResult<LP, SubsetMapping[T]>>
// When semanticQuery is provided, a similarity field is added to each row.
type WithSimilarity < LP , T > = LP extends { semanticQuery : Record < string , unknown > }
? T & { similarity : number }
: T ;
type ListResult < LP , T > =
// queryMode: "list"
| { rows : WithSimilarity < LP , T >[] }
// queryMode: "count"
| { total : number }
// queryMode: "both" (default)
| { rows : WithSimilarity < LP , T >[]; total : number };
When semanticQuery is provided, a similarity field (a score between 0 and 1) is automatically added to each row.
// Normal query
const { rows } = await PostModel . findMany ( "A" , { num: 10 });
// rows[0].id, rows[0].title (no similarity)
// Semantic search
const { rows } = await PostModel . findMany ( "A" , {
num: 10 ,
semanticQuery: { embedding: queryVector },
});
// rows[0].id, rows[0].title, rows[0].similarity (similarity included)
Array of retrieved records.
const { rows } = await UserModel . findMany ( "A" , { num: 10 , page: 1 });
rows . forEach (( user ) => {
console . log ( user . id , user . name );
});
Total number of records matching the conditions.
const { total } = await UserModel . findMany ( "A" , { num: 10 , page: 1 });
const totalPages = Math . ceil ( total / 10 );
console . log ( `Total pages: ${ totalPages } ` );
num (Items per page)
Number of records to display per page.
Default: 24
// Fetch 10 at a time
const { rows } = await UserModel . findMany ( "A" , { num: 10 });
// Fetch 50 at a time
const { rows } = await UserModel . findMany ( "A" , { num: 50 });
// Fetch all (num: 0)
const { rows } = await UserModel . findMany ( "A" , { num: 0 });
page (Page number)
Page number to fetch (starts from 1).
Default: 1
// First page
const { rows : page1 } = await UserModel . findMany ( "A" , {
num: 10 ,
page: 1 , // Records 1-10
});
// Second page
const { rows : page2 } = await UserModel . findMany ( "A" , {
num: 10 ,
page: 2 , // Records 11-20
});
// Third page
const { rows : page3 } = await UserModel . findMany ( "A" , {
num: 10 ,
page: 3 , // Records 21-30
});
Filtering
ID Filter
// Single ID
const { rows } = await UserModel . findMany ( "A" , { id: 1 });
// Multiple IDs
const { rows } = await UserModel . findMany ( "A" , { id: [ 1 , 2 , 3 ] });
Custom Filters
Additional filters available depending on Entity definition.
// Status filter
const { rows } = await UserModel . findMany ( "A" , {
status: "active" ,
});
// Date range filter
const { rows } = await UserModel . findMany ( "A" , {
created_from: "2024-01-01" ,
created_to: "2024-12-31" ,
});
// Combine multiple filters
const { rows } = await UserModel . findMany ( "A" , {
status: "active" ,
role: "admin" ,
verified: true ,
});
search + keyword
// Search by ID
const { rows } = await UserModel . findMany ( "A" , {
search: "id" ,
keyword: "123" ,
});
// Search by email
const { rows } = await UserModel . findMany ( "A" , {
search: "email" ,
keyword: "john" ,
});
// Search by name (partial match)
const { rows } = await UserModel . findMany ( "A" , {
search: "name" ,
keyword: "Smith" ,
});
Implementation Example
Generated Model code:
// search-keyword
if ( params . search && params . keyword && params . keyword . length > 0 ) {
if ( params . search === "id" ) {
qb . where ( "users.id" , Number ( params . keyword ));
} else if ( params . search === "email" ) {
qb . where ( "users.email" , "like" , `% ${ params . keyword } %` );
} else if ( params . search === "name" ) {
qb . where ( "users.name" , "like" , `% ${ params . keyword } %` );
} else {
throw new BadRequestException ( `Unimplemented search field ${ params . search } ` );
}
}
Sorting
orderBy
// ID descending (default)
const { rows } = await UserModel . findMany ( "A" , {
orderBy: "id-desc" ,
});
// Created date descending
const { rows } = await UserModel . findMany ( "A" , {
orderBy: "created-desc" ,
});
// Name ascending
const { rows } = await UserModel . findMany ( "A" , {
orderBy: "name-asc" ,
});
Implementation Example
// orderBy
if ( params . orderBy ) {
if ( params . orderBy === "id-desc" ) {
qb . orderBy ( "users.id" , "desc" );
} else if ( params . orderBy === "created-desc" ) {
qb . orderBy ( "users.created_at" , "desc" );
} else if ( params . orderBy === "name-asc" ) {
qb . orderBy ( "users.name" , "asc" );
} else {
exhaustive ( params . orderBy );
}
}
Query Mode
queryMode: βbothβ (default)
Returns both list and total count.
const { rows , total } = await UserModel . findMany ( "A" , {
num: 10 ,
page: 1 ,
queryMode: "both" , // Default value
});
console . log ( ` ${ rows . length } of ${ total } ` );
queryMode: βlistβ
Returns list only (skips COUNT query).
const { rows } = await UserModel . findMany ( "A" , {
num: 10 ,
page: 1 ,
queryMode: "list" , // No total
});
// total is undefined
Use queryMode: "list" for performance improvement when COUNT query is heavy.
queryMode: βcountβ
Returns total count only (skips SELECT query).
const { total } = await UserModel . findMany ( "A" , {
status: "active" ,
queryMode: "count" , // No rows
});
console . log ( `Total active users: ${ total } ` );
Basic Usage
Simple List
import { UserModel } from "./user/user.model" ;
class UserService {
async getUsers ( page : number = 1 ) {
const { rows , total } = await UserModel . findMany ( "A" , {
num: 20 ,
page ,
});
return {
users: rows ,
total ,
page ,
totalPages: Math . ceil ( total / 20 ),
};
}
}
async getActiveUsers ( page : number = 1 ) {
const { rows , total } = await UserModel . findMany ( "A" , {
status: "active" ,
num: 20 ,
page
});
return { rows , total };
}
Search + Sorting
async searchUsers ( keyword : string ) {
const { rows } = await UserModel . findMany ( "A" , {
search: "name" ,
keyword ,
orderBy: "name-asc" ,
num: 50
});
return rows ;
}
Practical Examples
Using BaseModelClass Methods
findMany internally uses the following BaseModelClass methods:
getSubsetQueries()
Gets subset query builder.
const { qb } = this . getSubsetQueries ( subset );
createEnhancers()
Creates virtual field calculation functions.
const enhancers = this . createEnhancers ({
A : ( row ) => ({
... row ,
full_name: ` ${ row . first_name } ${ row . last_name } ` ,
}),
});
executeSubsetQuery()
Executes query and returns results.
return this . executeSubsetQuery ({
subset ,
qb ,
params ,
enhancers ,
debug: false ,
optimizeCountQuery: false ,
});
Type Safety
Subset Type Inference
// Subset A
const { rows : usersA } = await UserModel . findMany ( "A" , {});
usersA [ 0 ]. posts ; // β Type error
// Subset B (includes posts)
const { rows : usersB } = await UserModel . findMany ( "B" , {});
usersB [ 0 ]. posts ; // β
Post[]
ListParams Type
// params is limited to UserListParams type
await UserModel . findMany ( "A" , {
unknownField: true , // β Type error
});
await UserModel . findMany ( "A" , {
status: "active" , // β
Defined field
});
queryMode Type Inference
// queryMode: "both"
const { rows , total } = await UserModel . findMany ( "A" , {
queryMode: "both" ,
});
rows ; // β
User[]
total ; // β
number
// queryMode: "list"
const { rows } = await UserModel . findMany ( "A" , {
queryMode: "list" ,
});
total ; // β Type error (no total)
// queryMode: "count"
const { total } = await UserModel . findMany ( "A" , {
queryMode: "count" ,
});
rows ; // β Type error (no rows)
1. Use queryMode: βlistβ
When COUNT query is heavy:
// β Slow: executes COUNT(*)
const { rows , total } = await UserModel . findMany ( "A" , {
num: 20 ,
page: 1 ,
});
// β
Fast: skips COUNT
const { rows } = await UserModel . findMany ( "A" , {
num: 20 ,
page: 1 ,
queryMode: "list" ,
});
2. Use Indexes
Add indexes on frequently filtered fields:
CREATE INDEX idx_users_status ON users( status );
CREATE INDEX idx_users_created_at ON users(created_at);
3. Minimize Subsets
Use subsets that include only necessary fields:
// β Heavy: loads all relationships
const { rows } = await UserModel . findMany ( "Full" , { num: 100 });
// β
Light: basic fields only
const { rows } = await UserModel . findMany ( "A" , { num: 100 });
4. Limit num
Prevent fetching too much data at once:
// β Dangerous: possible memory exhaustion
const { rows } = await UserModel . findMany ( "A" , { num: 0 });
// β
Safe: appropriate page size
const { rows } = await UserModel . findMany ( "A" , { num: 100 });
Cautions
1. Caution with num: 0
num: 0 fetches all records, use carefully.
// β Dangerous: can fetch millions of records
const { rows } = await UserModel . findMany ( "A" , { num: 0 });
// β
Safe: pagination
const { rows } = await UserModel . findMany ( "A" , { num: 100 });
2. page Starts from 1
// β Wrong: starts from 0
const { rows } = await UserModel . findMany ( "A" , { page: 0 });
// β
Correct: starts from 1
const { rows } = await UserModel . findMany ( "A" , { page: 1 });
3. Subset Selection
// β Inefficient: loads unnecessary relationships
const { rows } = await UserModel . findMany ( "Full" , { num: 1000 });
// β
Efficient: only what's needed
const { rows } = await UserModel . findMany ( "A" , { num: 1000 });
Next Steps
findById Retrieve single record by ID
save Save and update records
Subset Understanding subsets
Puri Query Builder Advanced query building