@reldens/items-system Architecture

Class structure of the @reldens/items-system package: inventories, item groups, item types, the exchange platform, data generators and client/server synchronization.

Overview

@reldens/items-system is the items and inventory runtime used by Reldens. It provides:

  • Inventory management: add / remove items, quantity control, total and per-item limits, inventory locking.
  • Item types: base, single-instance (stackable), equipment, usable, and their combinations.
  • Item groups: categorized inventories (bags) with their own data.
  • Exchange platform: player-to-player trades and NPC buy / sell flows with requirements and rewards.
  • Item modifiers through @reldens/modifiers (stat bonuses applied on equip or use).
  • Client / server synchronization through a Sender (server) and a Receiver (client).
  • Events for every inventory action and structured error codes.
npm install @reldens/items-system

Dependencies: @reldens/modifiers and @reldens/utils (Shortcuts, Logger, EventsManager). All item operations are meant to run on the server (server authoritative); the client only mirrors the state it receives.

Class Hierarchy

  • ItemsServer - server-side wrapper
    • ItemsManager (property manager)
    • Sender (property client, only when a client is provided)
  • Inventory - base inventory
    • ItemsManager extends Inventory (uses ItemTypes)
    • ItemGroup extends Inventory
  • ItemBase - base item
    • ItemSingle
    • ItemEquipment
      • ItemSingleEquipment
    • ItemUsable
      • ItemSingleUsable
  • ExchangePlatform
    • RequirementsProcessor - validates and processes a RequirementsCollection of ExchangeRequirement entries.
    • RewardsProcessor - validates and processes a RewardsCollection of ExchangeReward entries.

Main Exports

const {
    ItemsServer, ItemsManager, Inventory, ItemGroup,
    ItemBase, ItemEquipment, ItemUsable, ItemSingle, ItemSingleEquipment, ItemSingleUsable,
    ModelEntity, ItemsConst, ItemsEvents, Receiver,
    ItemsDataGenerator, GroupsDataGenerator,
    ExchangePlatform, ExchangeRequirement, RequirementsCollection, RequirementsProcessor,
    ExchangeReward, RewardsCollection, RewardsProcessor, ItemsError
} = require('@reldens/items-system');

ItemsServer

Entry point for server-side item management. It creates an ItemsManager and, when a client is provided, a Sender that pushes item updates to that client.

Constructor props:

  • owner (required) - the entity that owns the items (for example, a player).
  • client (optional) - connection object that must implement send() and broadcast().
  • All ItemsManager props.

Properties: manager (ItemsManager), client (Sender), hasError (true when the owner is missing).

let itemsServer = new ItemsServer({
    owner: player,
    client: playerClient,
    itemClasses: customItemClasses,
    itemsModelData: itemsModelData
});
await itemsServer.manager.setup({
    items: {},
    groups: {}
});

ItemsManager

Extends Inventory with owner handling, item groups and item instance creation from model data.

Constructor props:

  • owner (required) - owner entity.
  • itemClasses - map of item key to a custom item class.
  • groupClasses - map of group key to a custom group class.
  • itemsModelData - map of item key to {class, data} (see ItemsDataGenerator below).
  • ownerIdProperty - owner property used as ID (default id).
  • eventsPrefix - optional suffix appended to the owner event key.
  • All Inventory props.

Properties: owner, groups, types (ItemTypes registry), eventsPrefix, plus all Inventory properties.

Key methods:

  • async setup(props) - fires the manager init event, then calls setItems(props.items) and setGroups(props.groups) when present.
  • createItemInstance(key, qty) - builds item instances from itemsModelData[key] and sets the owner as target of the item modifiers. Single-instance classes return one instance holding the full quantity; other classes return one instance when the quantity is 1, or an array of instances (each with quantity 1) otherwise. Returns false when the key is unknown.
  • getOwnerId() - returns owner[ownerIdProperty].
  • getOwnerEventKey() - returns owner.eventsPrefix or items.ownerId.{ownerId}.
  • getOwnerUniqueEventKey(suffix) - unique listener key, uses owner.eventUniqueKey() when available.

Inventory

Core inventory with add / remove / quantity operations, limits, locking and events.

Constructor props:

  • eventsManager - EventsManager instance (default: EventsManagerSingleton).
  • limitPerItem - maximum quantity per item (-1 disables the limit, default).
  • itemsLimit - maximum number of inventory entries (-1 disables the limit, default).
  • applyModifiersAuto - apply equipment modifiers automatically on equip (default true).
  • revertModifiersAuto - revert equipment modifiers automatically on unequip (default true).
  • eventsPrefix - prefix used to build the full event names.

Properties:

  • items - item instances indexed by inventory ID (see getInventoryId()).
  • locked - when true, add / set / remove / quantity changes are rejected.
  • lastError - last ItemsError instance.
  • frozenItems - snapshots of items taken during an exchange, before quantities are decreased.

Key methods (all item operations are async and return false on failure, with the details in lastError):

  • validate(item) - checks the item exists, implements getInventoryId() and has a key.
  • addItem(item) - validates, then checks lock, total limit, duplicates (non-single items) and per-item limit. If a single-instance item with the same key already exists, its quantity is increased instead and the existing instance is returned.
  • addItems(itemsArray) - adds each item, stops at the first failure.
  • setItem(item), removeItem(key).
  • setItemQty(key, qty), increaseItemQty(key, qty), decreaseItemQty(key, qty) - shortcuts for modifyItemQty(op, key, qty).
  • setItems(items), setGroups(groups).
  • findItemByKey(itemKey), findItemsByPropertyValue(propertyKey, propertyValue).
  • fireEvent(eventName, ...args), listenEvent(eventName, callback, removeKey, masterKey).

Quantity rules in modifyItemQty:

  • The quantity must be a number; negative values are rejected for increase and decrease.
  • For set and increase, a requested quantity above limitPerItem is rejected.
  • Decrease never goes below 0.
  • When the quantity reaches 0 and the item has autoRemoveItemOnZeroQty (default true), the item is removed.

ItemGroup

Extends Inventory to represent a category or bag of items. Use cases: equipment bag, consumables bag, quest items, materials, currency pouch.

Constructor props: id and key (both required, an error is thrown if missing), label, description, files_name (icon file), sort, items_limit, limit_per_item, plus all Inventory props.

The items_limit and limit_per_item values are stored as group data (they come from the groups table). The limits enforced by the inventory logic are the Inventory props itemsLimit and limitPerItem.

Item Types

ItemBase

Base class for every item. Holds the core properties, the modifiers and the event helpers.

Constructor props:

  • key (required) - item type key.
  • manager (required) - ItemsManager instance.
  • uid - unique instance ID (auto-generated from the key plus random characters when not provided).
  • id - inventory row ID in storage (can be null without storage).
  • item_id - item type ID in storage.
  • label, description, type (default ITEM_BASE), qty (default 0).
  • remaining_uses, is_active, group_id, qty_limit, uses_limit.
  • autoRemoveItemOnZeroQty - default true.
  • modifiers - object of Modifier instances.
  • customData - object or JSON string; its properties are copied onto the item instance.

Key methods:

  • static isSingleInstance() - false for ItemBase.
  • getInventoryId() - returns the key for single-instance items and the uid otherwise.
  • async applyModifiers() / async revertModifiers() - fire the "before" event, call apply() / revert() on every modifier, then fire the "applied" / "reverted" event.
  • isType(type), fireEvent(), listenEvent() (both go through the manager).

ItemSingle

Single-instance items: the quantity is grouped in one inventory entry indexed by the item key. Adding the same key again increases the quantity of the existing entry. Use cases: stackable potions, arrows, crafting materials, currency.

ItemEquipment

Equippable items that apply and revert their modifiers.

  • equipped - current state (default false).
  • async equip(applyMods) - sets equipped, fires the equip event and applies the modifiers unless applyMods === false or the manager has applyModifiersAuto disabled.
  • async unequip(revertMods) - same flow for reverting.
  • Modifiers can only be applied while equipped and reverted while unequipped; otherwise the manager error is set with the items.equipment.modifiersApply / items.equipment.modifiersRevert codes.

Use cases: weapons, armor, accessories, any item that modifies stats while worn.

ItemUsable

Consumable items executed through use().

  • Props: uses (default 1), removeQtyAfterUse (default 1), autoRemoveItemOnZeroQty.
  • async use(target) - returns false when the item cannot be used yet; otherwise fires the executing event and runs executeItem().
  • executeItem() - applies the modifiers and decrements the remaining uses; when the uses run out, the quantity is decreased by removeQtyAfterUse and the uses counter resets. Fires the executed event.
  • useTimeOut (re-use delay) and execTimeOut (execution delay) are instance properties in milliseconds; the Usable constructor resets them to false, so assign them on the instance after creating it.

Use cases: health / mana potions, food, scrolls, one-time use items.

ItemSingleEquipment and ItemSingleUsable

Combinations of the single-instance behavior with Equipment and Usable respectively: stackable equippable items and stackable consumables.

ItemTypes

Registry mapping type IDs to classes. classByTypeId(typeId) falls back to ItemBase for unknown IDs.

  • ITEM_BASE: 10
  • EQUIPMENT: 1
  • USABLE: 2
  • SINGLE: 3
  • SINGLE_EQUIPMENT: 4
  • SINGLE_USABLE: 5

Exchange System

ExchangePlatform

Trades items between two inventories, identified as A and B. Both inventories should be ItemsManager instances, since the received items are created with createItemInstance().

Constructor props: eventsManager, exchangeInitializerId.

initializeExchangeBetween(props) locks both inventories and accepts:

  • inventoryA, inventoryB (required).
  • exchangeRequirementsA, exchangeRequirementsB - RequirementsCollection instances.
  • exchangeRewardsA, exchangeRewardsB - RewardsCollection instances.
  • dropExchangeA, dropExchangeB - do not add the traded items to that inventory (for example, an NPC buyer that does not store what it buys).
  • avoidExchangeDecreaseA, avoidExchangeDecreaseB - do not decrease the traded items from that inventory (for example, unlimited merchant stock).

Other methods:

  • async pushForExchange(itemInventoryId, qty, inventoryKey) - adds an item to the offer. The quantity must be an integer greater than 0 and not above the item quantity (an item quantity of -1 means infinite). Requirements and rewards are re-validated on every push.
  • async removeFromExchange(itemInventoryId, inventoryKey).
  • async confirmExchange(inventoryKey), async disconfirmExchange(inventoryKey).
  • async finalizeExchange() - requires both confirmations, validates requirements and rewards, unlocks the inventories and executes A to B, then B to A.
  • cancelExchange() - unlocks the inventories and resets the exchange state.
  • validateRequirements(inventoryKey), validateRewards(inventoryKey), lockInventories(), unlockInventories().

Exchange flow:

  1. initializeExchangeBetween() - set up and lock both inventories.
  2. pushForExchange() - each party adds items to the offer.
  3. confirmExchange() - each party confirms. Once any side has confirmed, pushing and removing items is blocked.
  4. finalizeExchange() - executes the trade when both sides confirmed.

Errors are stored in exchange.lastError. See Items Events and Error Codes for the exchange events and codes.

Requirements

Requirements define what the opposite inventory must have for each unit of an item pushed from an inventory. For example, a merchant item that costs 10 coins per unit.

  • ExchangeRequirement fields: itemUid, itemKey, requiredItemKey, requiredQuantity, autoRemoveRequirement.
  • RequirementsCollection.add(itemUid, itemKey, requiredItemKey, requiredQuantity, autoRemoveRequirement). Requirements are matched by the pushed item uid or key.
  • Validation: the required item must exist in the opposite inventory with at least requiredQuantity * pushedQuantity. When autoRemoveRequirement is false, the required item must also be pushed into the exchange with enough quantity.
  • Processing: on finalize, the required quantity is removed from the opposite inventory.

Rewards

Rewards define what an inventory receives for each unit of an item it pushes. For example, selling a sword to an NPC for 5 coins per unit.

  • ExchangeReward fields: itemUid, itemKey, rewardItemKey, rewardQuantity, rewardItemIsRequired.
  • RewardsCollection.add(itemUid, itemKey, rewardItemKey, rewardQuantity, rewardItemIsRequired).
  • When rewardItemIsRequired is false, the reward item is created for the pushing inventory.
  • When it is true, the reward item must exist in the opposite inventory with enough quantity, and it is taken from there.

Client / Server Communication

Sender (server)

Created by ItemsServer when a client is provided. It listens to the manager events and sends messages with the shape {act, owner, item} (or items / groups for the bulk actions).

  • Default behaviors: add, remove, quantity change, equip, unequip, modifiers applied / reverted and executed are sent to the owner client; the executing item action is broadcast so other clients can play animations.
  • Each action sends only a configured list of item properties (for example, only id and key on remove). Pass sendProperties to define your own behaviors and properties, and sendTargetProps to include target properties.
  • Messages for a different owner are ignored, since one EventsManager serves all owners.

Receiver (client)

Processes the server messages and replays them on a client-side ItemsManager, so the client can use its own item classes (for display and animations) while the server keeps the authoritative logic.

  • Requires an owner; accepts a manager or creates one from the props.
  • Ignores messages whose action does not start with the rinv prefix.
  • Default handlers: onSetItems, onSetGroups, onAddItem, onRemoveItem, onSetQty, onEquipItem, onUnequipItem, plus empty onModifiersApplied, onModifiersReverted, onExecuting, onExecuted to override.
  • Pass actions to map extra actions and avoidDefaults: true to skip the default mapping.

Data Generators

ItemsDataGenerator

Converts item models (database rows or plain objects) into the itemsModelData structure used by ItemsManager.

  • static itemsListMappedData(inventoryClasses, itemsModelsList) - returns an object indexed by item key where each entry is {class, data}. The class is taken from inventoryClasses[itemKey] when present, otherwise from the item type ID.
  • static generateItemModifiers(itemModel) - builds Modifier instances from the related_items_item_modifiers relation (Reldens related_* naming convention), indexed by modifier ID. Values are converted to numbers for every operation except SET.

GroupsDataGenerator

  • static groupsListMappedData(inventoryClasses, groupModelsList) - returns {groupList, groupBaseData, groupModels}, where each groupList entry is {class, data} (custom group class or ItemGroup).

Supporting classes

  • ModelEntity - minimal item model wrapper (id, key, type).
  • ItemsError - structured error with message, code, data and withError (nested error).
  • ItemsEvents and ItemsConst - event names and constants, see Items Events and Error Codes.

Common Patterns

Creating a manager from models

const { ItemsManager, ItemsDataGenerator } = require('@reldens/items-system');

// itemsModels: item rows (id, key, type, label, ...) with their related_items_item_modifiers
let itemsModelData = ItemsDataGenerator.itemsListMappedData(customItemClasses, itemsModels);
let itemsManager = new ItemsManager({
    owner: player,
    itemsModelData: itemsModelData,
    limitPerItem: 99,
    itemsLimit: 50
});
await itemsManager.setup({
    items: {},
    groups: {}
});

Adding items

let created = itemsManager.createItemInstance('health_potion', 5);
let result = Array.isArray(created)
    ? await itemsManager.addItems(created)
    : await itemsManager.addItem(created);
if(false === result){
    console.log(itemsManager.lastError.code, itemsManager.lastError.message);
}

Player to player exchange

const { ExchangePlatform } = require('@reldens/items-system');

let exchange = new ExchangePlatform({exchangeInitializerId: playerA.id});
exchange.initializeExchangeBetween({
    inventoryA: playerAItemsManager,
    inventoryB: playerBItemsManager
});
await exchange.pushForExchange(swordInventoryId, 1, 'A');
await exchange.pushForExchange('coins', 100, 'B');
await exchange.confirmExchange('A');
await exchange.confirmExchange('B');
if(!await exchange.finalizeExchange()){
    console.log(exchange.lastError.code);
}

Merchant with a price requirement

const { ExchangePlatform, RequirementsCollection } = require('@reldens/items-system');

let requirements = new RequirementsCollection();
// each "potion" pushed by the merchant (A) requires 10 "coins" from the buyer (B), removed on finalize:
requirements.add('', 'potion', 'coins', 10, true);
let exchange = new ExchangePlatform({});
exchange.initializeExchangeBetween({
    inventoryA: merchantItemsManager,
    inventoryB: playerItemsManager,
    exchangeRequirementsA: requirements,
    avoidExchangeDecreaseA: true
});

Integration with Reldens

  • The items database models (items, inventory rows, groups, item modifiers) are provided by the Reldens inventory feature and configured through the administration panel.
  • This package provides the runtime: Reldens builds the item classes with the data generators, attaches an ItemsServer to each player and persists changes by listening to the item events.
  • Item modifiers are @reldens/modifiers instances; events go through the shared EventsManager from @reldens/utils.

Related Documentation

Go Up