# new IconService()
Icon domain service with full mixin composition.
Manages icon assets including CRUD operations, caching, event emission, soft deletes, and activation state. Extends BaseService with additional mixins for production-ready features.
Mixin Stack (bottom to top):
- BaseService (with withObservable) - Core CRUD + automatic observability
- withPluggableCacheableAndSoftDeletable - Event emission, caching, soft deletes
- withActivatable - Activation state management
Custom Business Logic: In addition to inherited CRUD methods, IconService provides domain-specific queries via IconRepository (getIconByUniqueId, getIconsBySetId, etc.).
Examples
// Basic CRUD (inherited from BaseService)
const iconService = new IconService();
const icon = await iconService.create({
name: 'home',
svgPath: 'M3 9l9-7...',
setId: 10,
isActive: true
});
const found = await iconService.getById(icon.id); // Cached
const updated = await iconService.update(icon.id, { name: 'home-alt' });
await iconService.softDelete(icon.id); // Soft delete
// Custom queries (domain-specific)
const iconsBySet = await iconService.getIconsBySetId(10);
const activeIcons = await iconService.getAllActiveIcons();
const iconByUid = await iconService.getIconByUniqueId('home-123');
// Activation management (from withActivatable)
await iconService.activate(icon.id); // is_active=true
await iconService.deactivate(icon.id); // is_active=false
await iconService.toggleActive(icon.id); // Toggle state
// Pagination (inherited from BaseService)
const result = await iconService.paginate(
{ setId: 10, isActive: true },
1, // page
20 // pageSize
);
// Returns: { results: IconEntity[], total, page, pageSize, totalPages }
Extends
Methods
# async cursorPaginate(filters, cursor, limitopt, sortByopt, sortOrderopt, optionsopt) → {Promise.<Object>}
Paginate icons using cursor-based keyset pagination.
Provides efficient pagination for large icon datasets (750K+ icons) with support for complex search facets including:
- Price filtering (free, premium, all)
- Tag filtering (multiple tags)
- Style filtering
- User/creator filtering
- Set and family filtering
- Text search
- Elasticsearch result IDs
Performance:
- O(log n + limit) vs O(n) for offset pagination
- Consistent results even when data changes
- No "page drift" from concurrent modifications
Cursor Format:
Cursors are base64-encoded tokens containing the last seen item's sort fields.
Use pageInfo.endCursor from one page as the cursor parameter for the next page.
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
filters |
Object
|
Search facets and filter criteria |
||
price |
string
|
<optional> |
'free', 'premium', or 'all' |
|
tagIds |
Array.<number>
|
<optional> |
Array of tag IDs to filter by |
|
styleId |
number
|
<optional> |
Style ID to filter by |
|
userId |
number
|
<optional> |
Creator user ID to filter by |
|
setId |
number
|
<optional> |
Set ID to filter by |
|
familyId |
number
|
<optional> |
Family ID to filter by |
|
searchTerm |
string
|
<optional> |
Text search on icon name |
|
iconIds |
Array.<number>
|
<optional> |
Specific icon IDs (e.g., from Elasticsearch) |
|
isActive |
boolean
|
<optional> |
Filter by active status |
|
isDeleted |
boolean
|
<optional> |
Filter by deleted status |
|
cursor |
string
|
null
|
Cursor token from previous page (null for first page) |
||
limit |
number
|
<optional> |
20 | Page size (max 100) |
sortBy |
string
|
<optional> |
'createdAt' | Field to sort by |
sortOrder |
string
|
<optional> |
'desc' | 'asc' or 'desc' |
options |
Object
|
<optional> |
{} | Additional options |
includeTotalCount |
boolean
|
<optional> |
false | Whether to include total count (expensive!) |
trx |
Object
|
<optional> |
Knex transaction |
Pagination result with results and metadata
Promise.<Object>
Examples
// First page (newest free icons)
const page1 = await iconService.cursorPaginate({
price: 'free'
}, null, 20, 'createdAt', 'desc');
console.log(page1.results); // IconEntity[]
console.log(page1.pageInfo.hasNextPage); // true
console.log(page1.pageInfo.endCursor); // 'eyJpZC...'
// Next page (using cursor from page1)
const page2 = await iconService.cursorPaginate({
price: 'free'
}, page1.pageInfo.endCursor, 20);
// Complex search with multiple facets
const results = await iconService.cursorPaginate({
price: 'free',
tagIds: [1, 2, 3], // Icons with these tags
styleId: 5, // Specific design style
searchTerm: 'home', // Text search
userId: 123 // From specific creator
}, null, 20);
// With Elasticsearch integration
const elasticResults = await elasticsearchService.search('home icon');
const iconIds = elasticResults.hits.map(h => h.id);
const results = await iconService.cursorPaginate({
iconIds: iconIds, // Only these icon IDs
price: 'free'
}, null, 20);
// Oldest icons first (ascending sort)
const oldestIcons = await iconService.cursorPaginate({
price: 'all'
}, null, 20, 'createdAt', 'asc');
# async getAllActiveIcons(optionsopt) → {Promise.<Array.<IconEntity>>}
Get all active (published) icons across all sets.
Returns only icons where is_active=true and is_deleted=false. Useful for public-facing endpoints that should only show published content.
Warning: This can return 750,000+ icons. Consider using pagination instead:
await iconService.paginate({ isActive: true, isDeleted: false }, page, pageSize);
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
options |
Object
|
<optional> |
{} | Query options |
trx |
Object
|
<optional> |
Knex transaction |
Array of active icon entities
Promise.<Array.<IconEntity>>
Example
const activeIcons = await iconService.getAllActiveIcons();
console.log(activeIcons.length); // 750,000+
console.log(activeIcons.every(icon => icon.isActive)); // true
# async getIconByUniqueId(uniqueId, optionsopt) → {Promise.<(IconEntity|null)>}
Get icon by unique identifier.
Domain-specific query delegating to IconRepository. The unique ID is a combination of set identifier and icon name for human-readable URLs.
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
uniqueId |
string
|
Unique identifier (e.g., "material-design-home") |
||
options |
Object
|
<optional> |
{} | Query options |
trx |
Object
|
<optional> |
Knex transaction |
Icon entity or null if not found
Promise.<(IconEntity|null)>
Example
const icon = await iconService.getIconByUniqueId('material-design-home');
console.log(icon.name); // 'home'
console.log(icon.setId); // 10 (Material Design set)
# async getIconsBySetId(setId, optionsopt) → {Promise.<Array.<IconEntity>>}
Get all icons in a specific set.
Domain-specific query for fetching all icons that belong to a set (e.g., all icons in "Material Design Icons" set).
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
setId |
number
|
Set ID to filter by |
||
options |
Object
|
<optional> |
{} | Query options |
trx |
Object
|
<optional> |
Knex transaction |
Array of icon entities
Promise.<Array.<IconEntity>>
Example
const materialIcons = await iconService.getIconsBySetId(10);
console.log(materialIcons.length); // 2500+ icons
console.log(materialIcons[0].setId); // 10
# async searchIcons(params, optionsopt) → {Promise.<Object>}
Paginate icons with search facets (convenience method).
Simplified interface for cursor pagination with common search facets. Delegates to cursorPaginate() with pre-configured defaults.
Sorting Options:
'newest'- Sort by created_at DESC (PostgreSQL)'bestseller'- Sort by popularity DESC (PostgreSQL)'relevance'- Sort by Elasticsearch relevance (requires iconIds)
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
params |
Object
|
Search parameters |
||
price |
string
|
<optional> |
'all' | Price filter |
tagIds |
Array.<number>
|
<optional> |
[] | Tag IDs |
styleId |
number
|
<optional> |
Style ID |
|
searchTerm |
string
|
<optional> |
Text search |
|
iconIds |
Array.<number>
|
<optional> |
Icon IDs from Elasticsearch (for relevance sort) |
|
cursor |
string
|
<optional> |
Cursor token |
|
limit |
number
|
<optional> |
20 | Page size |
sort |
string
|
<optional> |
'newest' | Sort type ('newest', 'bestseller', or 'relevance') |
options |
Object
|
<optional> |
{} | Additional options |
Pagination result
Promise.<Object>
Examples
// Simple search (newest first)
const results = await iconService.searchIcons({
price: 'free',
searchTerm: 'home',
sort: 'newest',
limit: 20
});
// Bestsellers
const bestsellers = await iconService.searchIcons({
price: 'all',
sort: 'bestseller',
limit: 20
});
// Elasticsearch relevance sorting
const esResults = await elasticsearchService.search('home icon');
const iconIds = esResults.hits.map(h => h.id);
const results = await iconService.searchIcons({
iconIds: iconIds, // Ranked IDs from Elasticsearch
price: 'free',
sort: 'relevance', // Preserves Elasticsearch ranking
limit: 20
});
// Next page (same sort and filters)
const page2 = await iconService.searchIcons({
iconIds: iconIds, // SAME IDs for consistency
price: 'free',
sort: 'relevance',
cursor: results.pageInfo.endCursor,
limit: 20
});