Skip to main content
The proxy pattern is the core architectural pattern of Mythic Framework. It enables modular, extensible development through a component registration and dependency injection system. Understanding this pattern is essential for working with Mythic.
Reference Implementation: MythicFramework/resources/[mythic]/mythic-base/core/sh_proxy.luaThis file contains the complete proxy system implementation.

What is the Proxy Pattern?

The proxy pattern in Mythic Framework provides a centralized component registry that:
  • Registers components from any resource
  • Fetches components for use by other resources
  • Extends existing components with new functionality
  • Resolves dependencies automatically
Think of it as a service container or dependency injection container for FiveM resources.

The COMPONENTS Global Table

At the heart of the proxy system is the global COMPONENTS table:
-- Global table accessible from all resources
COMPONENTS = {}

-- Example of what it contains after initialization:
COMPONENTS = {
    Proxy = {...},        -- The proxy system itself
    Logger = {...},       -- Logging component
    Database = {...},     -- Database operations
    Inventory = {...},    -- Inventory management
    Characters = {...},   -- Character system
    Jobs = {...},         -- Job system
    -- ... 60+ more components
}
Every registered component becomes available in this global table.

The Four Core Exports

The proxy system provides four essential exports:

1. RegisterComponent

Purpose: Register a new component or override an existing one Location: mythic-base/core/sh_proxy.lua:32
exports['mythic-base']:RegisterComponent(component_name, component_data)
Parameters:
  • component_name (string) - Unique name for the component
  • component_data (table) - The component object with methods and data
Example:
-- In mythic-inventory/server/component.lua
exports['mythic-base']:RegisterComponent('Inventory', {
    -- Component methods
    Get = function(self, characterId, callback)
        -- Fetch inventory from database
        Database.Game:findOne({
            collection = 'inventory',
            query = {
                owner = characterId
            }
        }, function(success, inventory)
            if callback then
                callback(success, inventory)
            end
        end)
    end,

    AddItem = function(self, characterId, item, count, metadata, callback)
        -- Add item logic (async)
        self:Get(characterId, function(success, inventory)
            if success and inventory then
                -- ... add item to inventory
                if callback then callback(true) end
            else
                if callback then callback(false) end
            end
        end)
    end,

    RemoveItem = function(self, characterId, slot, count)
        -- Remove item logic
        return true
    end,

    -- Mark as protected to prevent overriding
    _protected = true,

    -- Specify required dependencies
    _required = { 'Get', 'AddItem', 'RemoveItem' },

    -- Component name for logging
    _name = 'inventory'
})
Protected Components: Components can be marked as _protected = true to prevent other resources from overriding them:
-- This component cannot be overridden
Inventory = {
    _protected = true,
    Get = function(self) ... end
}

-- Attempting to register again will fail with warning
exports['mythic-base']:RegisterComponent('Inventory', {...})
-- Warning: "Attempt To Override Protected Component: Inventory"
Required Attributes: Components can specify _required to enforce that certain methods must exist:
Payment = {
    _required = { 'Charge', 'Refund' },
    Charge = function(self, amount) ... end,
    Refund = function(self, amount) ... end
}

-- Extending without required methods will fail
exports['mythic-base']:ExtendComponent('Payment', {
    AddBonus = function(self) ... end
    -- Missing Charge and Refund - will fail
})

2. FetchComponent

Purpose: Retrieve a component for use Location: mythic-base/core/sh_proxy.lua:67
local component = exports['mythic-base']:FetchComponent(component_name)
Parameters:
  • component_name (string) - Name of the component to fetch
Returns:
  • Component object or nil if not found
Example:
-- Fetch the Inventory component
local Inventory = exports['mythic-base']:FetchComponent('Inventory')

if Inventory then
    -- Use the component
    local items = Inventory:Get(characterId)
    Inventory:AddItem(characterId, 'water', 5)
else
    print('Inventory component not found!')
end
Best Practice:
-- Fetch once at resource start, reuse throughout
local Inventory, Jobs, Characters

AddEventHandler('Core:Shared:Ready', function()
    Inventory = exports['mythic-base']:FetchComponent('Inventory')
    Jobs = exports['mythic-base']:FetchComponent('Jobs')
    Characters = exports['mythic-base']:FetchComponent('Characters')
end)

-- Now use them anywhere
AddEventHandler('myresource:doSomething', function()
    local items = Inventory:Get(source)
end)
Always check if component exists before using! A component may not be loaded yet, or the resource providing it may not be started.

3. ExtendComponent

Purpose: Add new functionality to an existing component without modifying it Location: mythic-base/core/sh_proxy.lua:76
exports['mythic-base']:ExtendComponent(component_name, extension_data)
Parameters:
  • component_name (string) - Name of component to extend
  • extension_data (table) - Methods/data to add
Example:
-- Original Inventory component
Inventory = {
    Get = function(self, charId) ... end,
    AddItem = function(self, charId, item) ... end
}

-- Extend with new method
exports['mythic-base']:ExtendComponent('Inventory', {
    -- Add a new method
    GetWeight = function(self, charId)
        local inv = self:Get(charId)
        local totalWeight = 0
        for _, item in ipairs(inv) do
            totalWeight = totalWeight + (item.weight * item.count)
        end
        return totalWeight
    end,

    -- Add a new property
    maxWeight = 100
})

-- Now you can use the new method
local weight = Inventory:GetWeight(characterId)
Use Cases:
  1. Adding helper methods:
    exports['mythic-base']:ExtendComponent('Characters', {
        GetOnlinePlayers = function(self)
            return GetPlayers()
        end
    })
    
  2. Adding configuration:
    exports['mythic-base']:ExtendComponent('Economy', {
        taxRate = 0.15,
        minWage = 50
    })
    
  3. Monkey-patching:
    -- Override a method while preserving original
    local originalAddItem = Inventory.AddItem
    exports['mythic-base']:ExtendComponent('Inventory', {
        AddItem = function(self, charId, item, count)
            print('Adding item:', item)
            return originalAddItem(self, charId, item, count)
        end
    })
    
ExtendComponent cannot modify protected components. It will fail with a warning.

4. RequestDependencies

Purpose: Asynchronously wait for dependencies to load before initializing Location: mythic-base/core/sh_proxy.lua:96
exports['mythic-base']:RequestDependencies(component_name, dependencies, callback)
Parameters:
  • component_name (string) - Name of your component (for logging)
  • dependencies (table) - Array of component names to wait for
  • callback (function) - Called when dependencies are ready (or failed)
Example:
-- mythic-shops/server/main.lua

-- Request dependencies before initializing
exports['mythic-base']:RequestDependencies('Shops', {
    'Inventory',  -- Need Inventory component
    'Economy',    -- Need Economy component
    'Logger'      -- Need Logger component
}, function(errors)
    if #errors > 0 then
        print('Failed to load dependencies:', json.encode(errors))
        return
    end

    -- All dependencies loaded, safe to initialize
    local Inventory = Inventory
    local Economy = Economy
    local Logger = Logger

    -- Register shop component
    exports['mythic-base']:RegisterComponent('Shops', {
        Purchase = function(self, player, item, price)
            if Economy:Charge(player, price) then
                Inventory:AddItem(player, item, 1)
                Logger:Info('Shops', player .. ' purchased ' .. item)
                return true
            end
            return false
        end
    })

    print('Shops component initialized successfully')
end)
How it Works:
  1. Polls for dependencies every 100ms
  2. Times out after 50 attempts (~5 seconds)
  3. Calls callback with any errors
  4. Tracks which components depend on others for updates
Advanced Usage:
-- Multiple resources depending on same component
exports['mythic-base']:RequestDependencies('Banking', { 'Economy' }, function(errors)
    if #errors == 0 then
        Economy:RegisterAccount('bank', 'Main Bank')
    end
end)

exports['mythic-base']:RequestDependencies('CryptoWallet', { 'Economy' }, function(errors)
    if #errors == 0 then
        Economy:RegisterAccount('crypto', 'Crypto Wallet')
    end
end)

-- When Economy component updates, both Banking and CryptoWallet are notified

Component Lifecycle

Understanding when components are registered and available:
EventWhenUse
Proxy:Shared:RegisterReadyProxy system readyRegister your components
Proxy:Shared:ExtendReadyA component registered/extendedReact to component changes
Core:Shared:ReadyAll core components loadedFetch dependencies, start logic

Real-World Examples

Example 1: Simple Utility Component

-- mythic-utils/shared/component.lua

exports['mythic-base']:RegisterComponent('Utils', {
    -- Format currency
    FormatMoney = function(self, amount)
        return '$' .. string.format('%,.2f', amount)
    end,

    -- Generate UUID
    GenerateUUID = function(self)
        local template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
        return string.gsub(template, '[xy]', function(c)
            local v = (c == 'x') and math.random(0, 0xf) or math.random(8, 0xb)
            return string.format('%x', v)
        end)
    end,

    -- Distance calculation
    GetDistance = function(self, coords1, coords2)
        return #(vector3(coords1.x, coords1.y, coords1.z) - vector3(coords2.x, coords2.y, coords2.z))
    end
})

-- Usage anywhere:
local money = Utils:FormatMoney(1500.50)  -- "$1,500.50"
local uuid = Utils:GenerateUUID()
local distance = Utils:GetDistance(pos1, pos2)

Example 2: Complex Feature Component

-- mythic-garage/server/component.lua

exports['mythic-base']:RequestDependencies('Garage', {
    'Vehicles',
    'Characters',
    'Database'
}, function(errors)
    if #errors > 0 then return end

    exports['mythic-base']:RegisterComponent('Garage', {
        _protected = true,
        _name = 'garage',

        -- Get player's vehicles
        GetVehicles = function(self, characterId, callback)
            Database.Game:find({
                collection = 'vehicles',
                query = {
                    owner = characterId
                }
            }, function(success, vehicles)
                if callback then
                    callback(success, vehicles)
                end
            end)
        end,

        -- Spawn vehicle from garage
        SpawnVehicle = function(self, player, vehicleId, callback)
            Database.Game:findOne({
                collection = 'vehicles',
                query = {
                    _id = vehicleId
                }
            }, function(success, vehicle)
                if not success or not vehicle then
                    if callback then callback(false) end
                    return
                end

                -- Check ownership
                local playerData = Fetch:Source(player)
                local char = playerData and playerData:GetData('Character')
                if not char or vehicle.owner ~= char:GetData('SID') then
                    if callback then callback(false, 'Not your vehicle') end
                    return
                end

                -- Spawn logic
                local coords = GetEntityCoords(GetPlayerPed(player))
                local veh = Vehicles:SpawnVehicle(vehicle.model, coords, vehicle.customization)

                if callback then callback(true, veh) end
            end)
        end,

        -- Store vehicle in garage
        StoreVehicle = function(self, player, vehicleEntity)
            local plate = GetVehicleNumberPlateText(vehicleEntity)

            -- Save customization
            local mods = Vehicles:GetVehicleMods(vehicleEntity)
            Database.Game:updateOne({
                collection = 'vehicles',
                query = {
                    plate = plate
                },
                update = {
                    ['$set'] = {
                        customization = mods,
                        stored = true
                    }
                }
            }, function(success)
                if success then
                    -- Delete vehicle after successful save
                    DeleteEntity(vehicleEntity)
                end
            end)
            return true
        end
    })
end)

Example 3: Extending Core Component

-- mythic-inventory-weight/server/main.lua

-- Add weight management to existing Inventory component
exports['mythic-base']:RequestDependencies('InventoryWeight', {
    'Inventory'
}, function(errors)
    if #errors > 0 then return end

    -- Store original AddItem
    local originalAddItem = Inventory.AddItem

    -- Extend with weight checking
    exports['mythic-base']:ExtendComponent('Inventory', {
        maxWeight = 100,

        GetWeight = function(self, charId)
            local inv = self:Get(charId)
            local weight = 0
            for _, item in pairs(inv) do
                weight = weight + (item.weight * item.count)
            end
            return weight
        end,

        AddItem = function(self, charId, item, count, metadata)
            -- Check weight first
            local currentWeight = self:GetWeight(charId)
            local itemWeight = Items:GetItemWeight(item) * count

            if currentWeight + itemWeight > self.maxWeight then
                return false, 'Inventory too heavy'
            end

            -- Call original
            return originalAddItem(self, charId, item, count, metadata)
        end
    })
end)

Dependency Updates

When a component is extended or re-registered, all resources that depend on it are notified:
-- mythic-inventory registers component
exports['mythic-base']:RegisterComponent('Inventory', {...})

-- mythic-shops requests it as dependency
exports['mythic-base']:RequestDependencies('Shops', { 'Inventory' }, ...)

-- Later, mythic-inventory-addon extends it
exports['mythic-base']:ExtendComponent('Inventory', {...})

-- mythic-shops receives 'Inventory:Shared:DependencyUpdate' event
AddEventHandler('Inventory:Shared:DependencyUpdate', function()
    -- Re-fetch updated component
    local Inventory = Inventory
    -- Inventory now has extended functionality
end)

Best Practices

Never assume components are available immediately:
-- ❌ BAD
local Inventory = Inventory  -- May be nil!

-- ✅ GOOD
exports['mythic-base']:RequestDependencies('MyResource', { 'Inventory' }, function(errors)
    if #errors == 0 then
        local Inventory = Inventory  -- Guaranteed to exist
    end
end)
Don’t fetch on every use:
-- ❌ BAD
AddEventHandler('myevent', function()
    local Inventory = exports['mythic-base']:FetchComponent('Inventory')
    Inventory:Get(source)
end)

-- ✅ GOOD
local Inventory
AddEventHandler('Core:Shared:Ready', function()
    Inventory = exports['mythic-base']:FetchComponent('Inventory')
end)

AddEventHandler('myevent', function()
    Inventory:Get(source)
end)
Prevent accidental overrides:
exports['mythic-base']:RegisterComponent('Inventory', {
    _protected = true,  -- Can't be overridden
    -- ... methods
})
Use PascalCase for component names:
-- Good
Inventory
Characters
VehicleShop

-- Bad
inventory
vehicle_shop
Add comments explaining methods:
exports['mythic-base']:RegisterComponent('Garage', {
    ---@param characterId number Character ID
    ---@param vehicleId number Vehicle ID
    ---@return boolean success
    ---@return table|string vehicle or error message
    SpawnVehicle = function(self, characterId, vehicleId)
        -- implementation
    end
})

Common Patterns

Singleton Pattern

-- Ensure component is only registered once
if not MyComponent then
    exports['mythic-base']:RegisterComponent('MyComponent', {...})
end

Factory Pattern

exports['mythic-base']:RegisterComponent('VehicleFactory', {
    Create = function(self, type)
        if type == 'car' then
            return { wheels = 4, engine = 'V8' }
        elseif type == 'bike' then
            return { wheels = 2, engine = 'Single' }
        end
    end
})

local car = VehicleFactory:Create('car')

Next Steps

Component System

Learn more about component-based architecture

Resource Structure

How to structure resources using components

Event System

Complement components with events

API Reference

Complete API documentation
Master the proxy pattern and you’ve mastered Mythic Framework. This is the foundation everything else is built on.