Packages@robojs/xp
XP
Variable: XP
const XP: {
add: (guildId, userId, amount, options?) => Promise<XPChangeResult>;
addXP: (guildId, userId, amount, options?) => Promise<XPChangeResult>;
get: (guildId, userId, options?) => Promise<number>;
getLevel: (guildId, userId, options?) => Promise<number>;
getUser: (guildId, userId, options?) => Promise<UserXP | null>;
getXP: (guildId, userId, options?) => Promise<number>;
recalc: (guildId, userId, options?) => Promise<RecalcResult>;
remove: (guildId, userId, amount, options?) => Promise<XPRemoveResult>;
removeXP: (guildId, userId, amount, options?) => Promise<XPRemoveResult>;
set: (guildId, userId, totalXp, options?) => Promise<XPSetResult>;
setXP: (guildId, userId, totalXp, options?) => Promise<XPSetResult>;
};XP manipulation API for programmatic XP management
Core API for adding, removing, setting, and querying user XP. All XP mutations are transactional and automatically trigger events and role reconciliation.
Features:
- Add/remove/set XP values with type-safe result objects
- Automatic level calculation based on default curve
- Event emission (levelUp, levelDown, xpChange) after persistence
- Automatic role reward reconciliation via event listeners
- Flashcore persistence with consistency guarantees
- Error handling for invalid inputs and missing users
Method Aliases:
add()oraddXP()- Award XP to a userremove()orremoveXP()- Remove XP from a userset()orsetXP()- Set absolute XP valueget()orgetXP()- Get user's total XP
All XP Mutations Automatically:
- Validate inputs (non-negative amounts, valid IDs)
- Load or create user record
- Calculate new level using default formula
- Persist to Flashcore
- Emit events (after successful persistence)
- Trigger role reconciliation (via event listeners)
Type declaration
| Name | Type | Default value | Description |
|---|---|---|---|
add | (guildId, userId, amount, options?) => Promise<XPChangeResult> | addXPCore | Add XP to a user (emits events, triggers role reconciliation) |
addXP | (guildId, userId, amount, options?) => Promise<XPChangeResult> | addXPCore | Add XP to a user - alias for add() |
get | (guildId, userId, options?) => Promise<number> | getXPCore | Get user's total XP (returns 0 if not found) |
getLevel | (guildId, userId, options?) => Promise<number> | getLevelCore | Get user's level (returns 0 if not found) |
getUser | (guildId, userId, options?) => Promise<UserXP | null> | getUserDataCore | Get full user XP record (returns null if not found) |
getXP | (guildId, userId, options?) => Promise<number> | getXPCore | Get user's total XP - alias for get() |
recalc | (guildId, userId, options?) => Promise<RecalcResult> | recalcLevelCore | Recalculate level from total XP and reconcile roles |
remove | (guildId, userId, amount, options?) => Promise<XPRemoveResult> | removeXPCore | Remove XP from a user (emits events, triggers role reconciliation) |
removeXP | (guildId, userId, amount, options?) => Promise<XPRemoveResult> | removeXPCore | Remove XP from a user - alias for remove() |
set | (guildId, userId, totalXp, options?) => Promise<XPSetResult> | setXPCore | Set absolute XP value for a user (emits events, triggers role reconciliation) |
setXP | (guildId, userId, totalXp, options?) => Promise<XPSetResult> | setXPCore | Set absolute XP value for a user - alias for set() |
Examples
Award XP with Level-Up Detection (using addXP)
import { xp } from '@robojs/xp'
import type { XPChangeResult } from '@robojs/xp'
// Award XP to a user (default store)
const result: XPChangeResult = await xp.addXP('guildId', 'userId', 100, {
reason: 'contest_winner'
})
console.log(`Old XP: ${result.oldXp}, New XP: ${result.newXp}`)
console.log(`Old Level: ${result.oldLevel}, New Level: ${result.newLevel}`)
if (result.leveledUp) {
console.log(`User leveled up to ${result.newLevel}!`)
// Role rewards already applied automatically via event listeners
}
// Award XP to custom store (parallel progression)
await xp.add('guildId', 'userId', 50, { reason: 'helped_user', storeId: 'reputation' })Remove XP with Error Handling (using removeXP)
import { xp } from '@robojs/xp'
import type { XPRemoveResult } from '@robojs/xp'
// Remove XP for moderation - using removeXP alias
try {
const result: XPRemoveResult = await xp.removeXP('guildId', 'userId', 100, {
reason: 'spam_violation'
})
if (result.leveledDown) {
console.log(`User dropped from level ${result.oldLevel} to ${result.newLevel}`)
// Roles removed automatically if removeRewardsOnLoss is true
}
} catch (error) {
console.error('Failed to remove XP:', error.message)
// Handle user not found or other errors
}
// Or use the shorthand remove() method (equivalent)
const result2 = await xp.remove('guildId', 'userId', 100, { reason: 'spam_violation' })Set Absolute XP Value (using setXP)
import { xp } from '@robojs/xp'
import type { XPSetResult } from '@robojs/xp'
// Set absolute XP value (admin tool) - using setXP alias
const result: XPSetResult = await xp.setXP('guildId', 'userId', 10000)
console.log(`XP changed from ${result.oldXp} to ${result.newXp}`)
console.log(`Level changed from ${result.oldLevel} to ${result.newLevel}`)
// Events emitted based on level change:
// - If newLevel > oldLevel: 'levelUp' event
// - If newLevel < oldLevel: 'levelDown' event
// - Always: 'xpChange' event
// Or use the shorthand set() method (equivalent)
const result2 = await xp.set('guildId', 'userId', 10000)Query User XP Data (using getXP)
import { xp } from '@robojs/xp'
// Get user's total XP - using getXP alias
const totalXp = await xp.getXP('guildId', 'userId')
console.log(`User has ${totalXp} XP`) // Returns 0 if not found
// Or use the shorthand get() method (equivalent)
const totalXp2 = await xp.get('guildId', 'userId')
// Get user's level
const level = await xp.getLevel('guildId', 'userId')
console.log(`User is level ${level}`) // Returns 0 if not found
// Get full user XP record
const user = await xp.getUser('guildId', 'userId')
if (user) {
console.log(`XP: ${user.xp}, Level: ${user.level}`)
console.log(`Messages: ${user.messages}`)
console.log(`Last awarded: ${new Date(user.lastAwardedAt)}`)
} else {
console.log('User has no XP record')
}Remove XP with Error Handling
import { xp } from '@robojs/xp'
import type { XPRemoveResult } from '@robojs/xp'
// Remove XP for moderation (with error handling)
try {
const result: XPRemoveResult = await xp.remove('guildId', 'userId', 100, {
reason: 'spam_violation'
})
if (result.leveledDown) {
console.log(`User dropped from level ${result.oldLevel} to ${result.newLevel}`)
// Roles removed automatically if removeRewardsOnLoss is true
}
} catch (error) {
console.error('Failed to remove XP:', error.message)
// Handle user not found or other errors
}Set Absolute XP Value
import { xp } from '@robojs/xp'
import type { XPSetResult } from '@robojs/xp'
// Set absolute XP value (admin tool)
const result: XPSetResult = await xp.set('guildId', 'userId', 10000)
console.log(`XP changed from ${result.oldXp} to ${result.newXp}`)
console.log(`Level changed from ${result.oldLevel} to ${result.newLevel}`)
// Events emitted based on level change:
// - If newLevel > oldLevel: 'levelUp' event
// - If newLevel < oldLevel: 'levelDown' event
// - Always: 'xpChange' eventQuery User XP Data
import { xp } from '@robojs/xp'
// Get user's total XP
const totalXp = await xp.get('guildId', 'userId')
console.log(`User has ${totalXp} XP`) // Returns 0 if not found
// Get user's level
const level = await xp.getLevel('guildId', 'userId')
console.log(`User is level ${level}`) // Returns 0 if not found
// Get full user XP record
const user = await xp.getUser('guildId', 'userId')
if (user) {
console.log(`XP: ${user.xp}, Level: ${user.level}`)
console.log(`Messages: ${user.messages}`)
console.log(`Last awarded: ${new Date(user.lastAwardedAt)}`)
} else {
console.log('User has no XP record')
}Recalculate Level After Config Changes
import { xp } from '@robojs/xp'
import type { RecalcResult } from '@robojs/xp'
// Recalculate level from total XP (useful after config changes)
const result: RecalcResult = await xp.recalc('guildId', 'userId')
if (result.reconciled) {
console.log(`Level corrected: ${result.oldLevel} → ${result.newLevel}`)
console.log(`Total XP: ${result.totalXp}`)
// Role rewards reconciled automatically
} else {
console.log('Level was already correct')
}Contest Plugin Integration
import { xp } from '@robojs/xp'
// Award bonus XP to contest winners
async function awardContestPrize(guildId: string, winners: string[]) {
const prizes = [500, 300, 100] // 1st, 2nd, 3rd place
for (let i = 0; i < winners.length; i++) {
const result = await xp.add(guildId, winners[i], prizes[i], {
reason: `contest_place_${i + 1}`
})
if (result.leveledUp) {
await announceWinner(winners[i], result.newLevel, prizes[i])
}
}
}Moderation Plugin Integration
import { xp } from '@robojs/xp'
// Remove XP for rule violations
async function penalizeUser(guildId: string, userId: string, severity: 'minor' | 'major') {
const penalty = severity === 'major' ? 500 : 100
try {
const result = await xp.remove(guildId, userId, penalty, {
reason: `${severity}_violation`
})
if (result.leveledDown) {
logger.info(`User ${userId} penalized: level ${result.oldLevel} → ${result.newLevel}`)
}
} catch (error) {
logger.warn(`Failed to penalize user ${userId}: ${error.message}`)
}
}Remarks
- All mutations validate inputs before execution (non-negative amounts, valid IDs)
- Events are emitted after successful Flashcore persistence
- Role rewards reconcile automatically via internal event listeners
get()andgetLevel()return 0 for users with no XP recordgetUser()returnsnullfor users with no XP recordadd()andremove()create user records if they don't existrecalc()is idempotent - safe to call multiple times
