### Install Lyra with Wally Source: https://github.com/paradoxum-games/lyra/blob/main/docs/getting-started.md Add Lyra as a dependency to your `wally.toml` file to include it in your project. ```toml Lyra = "paradoxum-games/lyra@0.5.0-rc.0" ``` -------------------------------- ### Integrate Lyra with ProcessReceipt for Purchases Source: https://github.com/paradoxum-games/lyra/blob/main/docs/getting-started.md Demonstrate how to use Lyra within a `ProcessReceipt` callback to handle in-game purchases. This example includes logic for preventing duplicate purchases, updating player data (e.g., adding coins), and managing purchase history. ```lua local ProductCallbacks = { [12345] = function(player, receiptInfo, data) data.coins += 100 return true end, } local function processReceipt(receiptInfo) local player = Players:GetPlayerByUserId(receiptInfo.PlayerId) if not player then return Enum.ProductPurchaseDecision.NotProcessedYet end local productCallback = ProductCallbacks[receiptInfo.ProductId] if not productCallback then return Enum.ProductPurchaseDecision.NotProcessedYet end local ok, result = pcall(function() store:updateAsync(player, function(data) -- Assuming you have 'purchaseHistory' in your template and schema: if table.find(data.purchaseHistory, receiptInfo.PurchaseId) then return false -- Prevent duplicate purchases end table.insert(data.purchaseHistory, 1, receiptInfo.PurchaseId) for i = 1000, #data.purchaseHistory do data.purchaseHistory[i] = nil -- Remove old purchases end return productCallback(player, receiptInfo, data) end) store:saveAsync(player) end) if not ok then warn(`ProcessReceipt failed: {result}`) return Enum.ProductPurchaseDecision.NotProcessedYet end return Enum.ProductPurchaseDecision.PurchaseGranted end ``` -------------------------------- ### Perform Multi-Player Data Transactions Source: https://github.com/paradoxum-games/lyra/blob/main/docs/getting-started.md Execute atomic transactions involving multiple players using `txAsync`. This ensures that operations affecting multiple data sets are either fully completed or fully aborted, maintaining data integrity. ```lua store:txAsync({player1, player2}, function(state) -- Transfer coins if state[player1].coins < amount then return false -- Abort if not enough coins end state[player1].coins -= amount state[player2].coins += amount return true end) ``` -------------------------------- ### Set up Lyra Player Store with Schema Source: https://github.com/paradoxum-games/lyra/blob/main/docs/getting-started.md Initialize a Lyra player data store by defining a data template and a strict schema for validation. This snippet demonstrates loading data on player join, unloading on player leave, and ensuring data is saved when the game closes. ```lua local Players = game:GetService("Players") local Lyra = require(path.to.Lyra) -- Define your data template local template = { coins = 0, inventory = {}, } -- Create schema to validate data local schema = t.strictInterface({ coins = t.number, inventory = t.table, }) -- Create the store local store = Lyra.createPlayerStore({ name = "PlayerData", template = template, schema = schema, }) -- Load data when players join Players.PlayerAdded:Connect(function(player) store:loadAsync(player) end) -- Save and clean up when they leave Players.PlayerRemoving:Connect(function(player) store:unloadAsync(player) end) -- Ensure data is saved when the game closes game:BindToClose(function() store:closeAsync() end) ``` -------------------------------- ### Read Player Data from Lyra Store Source: https://github.com/paradoxum-games/lyra/blob/main/docs/getting-started.md Retrieve player data from the Lyra store for read-only purposes. It's important not to store this data for later use as it may become stale. ```lua -- ⚠️ Only use this data for reading -- Don't save it for later use local data = store:getAsync(player) print(`{player.Name} has {data.coins} coins`) ``` -------------------------------- ### Modify Player Data with Update Functions Source: https://github.com/paradoxum-games/lyra/blob/main/docs/getting-started.md Update player data safely using Lyra's `updateAsync` function. This method ensures data consistency and supports both simple and conditional updates, allowing transactions to be aborted if conditions are not met. ```lua -- Simple update store:updateAsync(player, function(data) data.coins += 100 return true end) -- Conditional update store:updateAsync(player, function(data) if data.coins < itemPrice then return false -- Abort the update end data.coins -= itemPrice table.insert(data.inventory, itemId) return true end) ``` -------------------------------- ### Import Legacy Data into Lyra Store Source: https://github.com/paradoxum-games/lyra/blob/main/docs/getting-started.md Configure Lyra to import existing player data from a previous data storage system. The `importLegacyData` function allows you to define custom logic for retrieving and migrating old data when a player's data is first loaded by Lyra. ```lua local store = Lyra.createPlayerStore({ name = "PlayerData", template = template, schema = schema, importLegacyData = function(key) local success, data = pcall(function() return YourCurrentSystem.getData(key) end) if not success then error("Failed to reach data system") -- Player will be kicked and can retry end if data ~= nil then return data -- Return existing data to import end return nil -- Return nil for new players to use template end, }) ``` -------------------------------- ### Use Lyra's Promise-based API for Asynchronous Operations Source: https://github.com/paradoxum-games/lyra/blob/main/docs/getting-started.md Illustrate Lyra's Promise-based API for handling asynchronous data operations. This approach allows chaining `andThen` for successful outcomes and `catch` for error handling, providing a more modern asynchronous programming pattern. ```lua store:update(player, function(data) data.coins -= itemPrice data.inventory.weapon = "starter_sword" return true end):andThen(function() print("Purchase successful!") end):catch(function(err) print(`Purchase failed: {err}`) end) ``` -------------------------------- ### Lyra Installation via Wally (TOML) Source: https://github.com/paradoxum-games/lyra/blob/main/docs/intro.md This snippet provides the necessary configuration for installing Lyra using Wally, a package manager. It specifies the Lyra dependency with its version in the `wally.toml` file. ```toml Lyra = "paradoxum-games/lyra@0.5.0-rc.0" ``` -------------------------------- ### Lyra Player Data Management Quick Example (Lua) Source: https://github.com/paradoxum-games/lyra/blob/main/docs/intro.md This example demonstrates basic Lyra usage, including creating a player store with a template and schema, loading player data on join, safely updating data with validation, and performing atomic trades between players. ```lua local store = Lyra.createPlayerStore({ name = "PlayerData", template = { coins = 0, inventory = {}, }, schema = t.strictInterface({ coins = t.number, inventory = t.table, }), }) -- Load data when players join Players.PlayerAdded:Connect(function(player) store:loadAsync(player) end) -- Safe updates with validation store:updateAsync(player, function(data) if data.coins < itemPrice then return false -- Abort if can't afford end data.coins -= itemPrice table.insert(data.inventory, itemId) return true end) -- Atomic trades between players store:txAsync({player1, player2}, function(state) -- Either both changes happen or neither does state[player1].coins -= 100 state[player2].coins += 100 return true end) ``` -------------------------------- ### Importing Legacy Player Data into Lyra (Lua) Source: https://github.com/paradoxum-games/lyra/blob/main/docs/intro.md This example shows how to configure a Lyra player store to import existing data from a legacy system. It defines an `importLegacyData` function that attempts to retrieve old player data, returning it if found or `nil` for new players. ```lua local store = Lyra.createPlayerStore({ name = "PlayerData", template = template, schema = schema, importLegacyData = function(key) local success, data = pcall(function() return YourCurrentSystem.getData(key) end) if not success then -- If there's an error, Lyra will kick the player and prompt them -- to rejoin to try again. error("Failed to reach data system") end if data ~= nil then return data -- Return existing data to import end return nil -- Return nil for new players to use template end, }) ``` -------------------------------- ### Implementing Basic Lyra Data Migrations with addFields Source: https://github.com/paradoxum-games/lyra/blob/main/docs/advanced/migrations.md This example illustrates how to use `Lyra.MigrationStep.addFields` for basic data migrations. It shows adding new top-level fields like 'level' or nested structures like 'settings' to player data, emphasizing that migrations run sequentially in the order they are defined. ```lua local store = Lyra.createPlayerStore({ name = "PlayerData", template = template, schema = schema, migrationSteps = { Lyra.MigrationStep.addFields("addSettings", { settings = { music = true, sfx = true, } }), Lyra.MigrationStep.addFields("addLevel", { level = 1, }), }, }) ``` -------------------------------- ### Install Lyra via Wally Package Manager (TOML) Source: https://github.com/paradoxum-games/lyra/blob/main/README.md This TOML snippet shows how to add Lyra as a dependency to your Roblox project using the Wally package manager. By including this line in your `wally.toml` file, you can easily integrate Lyra into your development environment. ```toml Lyra = "paradoxum-games/lyra@0.5.0-rc.0" ``` -------------------------------- ### Initialize and Manage Player Data with Lyra (Lua) Source: https://github.com/paradoxum-games/lyra/blob/main/README.md This Lua example demonstrates the core functionalities of Lyra for player data management. It shows how to create a player store with a template and schema, load and unload player data upon player join and leave events, update player-specific data, and perform atomic transactions involving multiple players to ensure data consistency. ```lua local store = Lyra.createPlayerStore({ name = "PlayerData", template = { coins = 0, inventory = {}, }, schema = t.strictInterface({ coins = t.number, inventory = t.table, }), }) -- Load data when players join Players.PlayerAdded:Connect(function(player) store:loadAsync(player) end) -- Free up resources when players leave Players.PlayerRemoving:Connect(function(player) store:unloadAsync(player) end) -- Update data store:updateAsync(player, function(data) data.coins += 100 return true end) -- Atomic transactions store:txAsync({player1, player2}, function(state) local amount = 50 state[player1].coins -= amount state[player2].coins += amount return true end) ``` -------------------------------- ### Chaining Lyra Migration Steps with Intermediate Data Formats Source: https://github.com/paradoxum-games/lyra/blob/main/docs/advanced/migrations.md This example clarifies that intermediate migration steps do not necessarily need to return data that immediately matches the final schema. It illustrates a two-step transformation where the first step returns a temporary data structure, and the subsequent step refines it to conform to the final schema. ```lua migrationSteps = { -- First migration returns data that doesn't match final schema Lyra.MigrationStep.transform("step1", function(data) return { temporaryField = data.oldField, } }), -- Second migration transforms it to match schema Lyra.MigrationStep.transform("step2", function(data) return { newField = data.temporaryField, } }), } ``` -------------------------------- ### Handling Lyra Logs by Severity Level Source: https://github.com/paradoxum-games/lyra/blob/main/docs/advanced/debugging.md This example illustrates how to create a `logCallback` function that differentiates and handles Lyra log messages based on their severity level (fatal, error, warn, info, debug, trace), allowing for specific actions like `warn` for critical issues or `print` for informational messages. ```lua local function handleLogs(message) -- Handle based on severity if message.level == "fatal" then -- Unrecoverable errors (e.g., corrupted data) warn("FATAL:", message.message) elseif message.level == "error" then -- Operation failures (e.g., update failed) warn("Error:", message.message) elseif message.level == "warn" then -- Potential issues (e.g., slow operations) warn("Warning:", message.message) elseif message.level == "info" then -- Important operations (e.g., session started) print("Info:", message.message) elseif message.level == "debug" then -- Detailed operation info print("Debug:", message.message) elseif message.level == "trace" then -- Very detailed debugging info print("Trace:", message.message) end end ``` -------------------------------- ### Avoiding Stale Data in Lyra Updates (Lua) Source: https://github.com/paradoxum-games/lyra/blob/main/docs/intro.md This snippet illustrates the importance of modifying data only through update functions to prevent stale data issues. It contrasts an incorrect approach using a prior `:get()` call with the correct method of directly accessing the current data within the `updateAsync` callback. ```lua -- 🚫 Don't do this: local oldData = store:getAsync(player) store:updateAsync(player, function(newData) if not oldData.claimedDailyReward then -- This data might be stale! return false end newData.coins += 500 newData.claimedDailyReward = true return true end) -- ✅ Do this instead: store:updateAsync(player, function(data) if not data.claimedDailyReward then -- This data is always current return false end data.coins += 500 data.claimedDailyReward = true return true end) ``` -------------------------------- ### Performing Atomic Data Updates with Lyra in Lua Source: https://github.com/paradoxum-games/lyra/blob/main/docs/core-concepts.md This example illustrates how to modify player data atomically using `store:updateAsync`. The provided function receives the current data, allows mutable changes, and must return `true` to commit or `false` to abort, ensuring data consistency. It's crucial that the update function does not yield. ```Lua store:updateAsync(player, function(data) if data.coins < itemPrice then return false -- Abort the update end data.coins -= itemPrice table.insert(data.inventory, itemId) return true end) ``` -------------------------------- ### Handling Non-Persistent Game State Outside Lyra Source: https://github.com/paradoxum-games/lyra/blob/main/docs/advanced/state-management.md This example shows how to manage game state that does not require persistence, such as current round numbers or active players, using standard Lua tables or other state management solutions. For persistent player data like total scores or rounds completed, Lyra is used to ensure data integrity and persistence across sessions. ```lua -- State that doesn't need persistence local gameState = { currentRound = 1, roundStartTime = os.time(), activePlayers = {}, } -- Player data that needs persistence uses Lyra function playerCompletedRound(player) local score = gameState.roundScores[player.UserId] or 0 store:updateAsync(player.UserId, function(data) data.roundsCompleted += 1 data.totalScore += score return true end) end ``` -------------------------------- ### Basic Lyra Log Handling Source: https://github.com/paradoxum-games/lyra/blob/main/docs/advanced/debugging.md This snippet demonstrates how to configure Lyra's `logCallback` to process and print all incoming log messages, including their severity level, message, and optional context data. ```lua local function handleLogs(message) print(`[Lyra][{string.upper(message.level)}] {message.message}`) if message.context then -- Context contains relevant data like keys, session info, etc. print("Context:", message.context) end end local store = Lyra.createPlayerStore({ name = "PlayerData", template = template, schema = schema, logCallback = handleLogs, }) ``` -------------------------------- ### Chaining Multiple Lyra Migration Steps Source: https://github.com/paradoxum-games/lyra/blob/main/docs/advanced/migrations.md This snippet demonstrates how to define and chain multiple migration steps, combining `addFields` and `transform` operations. It reinforces that these steps execute strictly in the order they are listed, and this order becomes permanent once published. ```lua migrationSteps = { -- Add new currency Lyra.MigrationStep.addFields("addGems", { gems = 0, }), -- Restructure inventory Lyra.MigrationStep.transform("inventoryV2", function(data) data.inventory = { items = data.inventory, maxSlots = 20, } return data end), } ``` -------------------------------- ### Registering Multiple Lyra Change Callbacks Source: https://github.com/paradoxum-games/lyra/blob/main/docs/advanced/networking.md Illustrates how to register multiple functions to the `changedCallbacks` array in Lyra's `createPlayerStore`. This allows different callbacks to be used for various purposes, such as client synchronization and debugging logs, all receiving the same read-only data. ```Lua local store = Lyra.createPlayerStore({ changedCallbacks = { -- Keep clients in sync syncWithClient, -- Log changes for debugging function(key, newData, oldData) print(`Player {key} data changed`) end, }, }) ``` -------------------------------- ### Configuring Lyra Logger for Development and Production Source: https://github.com/paradoxum-games/lyra/blob/main/docs/advanced/debugging.md This snippet demonstrates how to create a dynamic logger that provides verbose debugging information (all log levels) when running in Roblox Studio (development mode) and only critical error/fatal messages in a live game environment (production mode), utilizing `RunService:IsStudio()`. ```lua local RunService = game:GetService("RunService") local function createLogger() if RunService:IsStudio() then -- Show all logs in Studio return function(message) print(`[Lyra][{message.level}] {message.message}`) if message.context then print("Context:", message.context) end end else -- Only show errors in production return function(message) if message.level == "error" or message.level == "fatal" then warn(`[Lyra] {message.message}`) end end end end local store = Lyra.createPlayerStore({ logCallback = createLogger(), }) ``` -------------------------------- ### Managing Player Data Sessions with Lyra in Lua Source: https://github.com/paradoxum-games/lyra/blob/main/docs/core-concepts.md This snippet demonstrates how to establish and release exclusive access to a player's data using Lyra sessions. It shows connecting to `Players.PlayerAdded` to load data with `store:loadAsync` and `Players.PlayerRemoving` to unload and save changes with `store:unloadAsync`, preventing race conditions. ```Lua Players.PlayerAdded:Connect(function(player) -- Establish exclusive access to the player's data store:loadAsync(player) end) Players.PlayerRemoving:Connect(function(player) -- Release the lock and save any pending changes store:unloadAsync(player) end) ``` -------------------------------- ### Syncing Client UI with Lyra Player Data Changes Source: https://github.com/paradoxum-games/lyra/blob/main/docs/advanced/networking.md Demonstrates how to use Lyra's `changedCallbacks` to synchronize player data with clients. It shows how to detect changes between `newData` and `oldData` and send only the modified values to reduce network traffic. The callback receives the player's UserId, new data, and previous data. ```Lua local ReplicatedStorage = game:GetService("ReplicatedStorage") local Network = require(ReplicatedStorage.Network) local function syncWithClient(key: string, newData, oldData) local player = Players:GetPlayerByUserId(tonumber(key)) if not player then return end if oldData == nil then -- First time data is loaded Network.PlayerData:FireClient(player, newData) return end -- Send only changed data local changes = {} if newData.coins ~= oldData.coins then changes.coins = newData.coins end if not Tables.deepEquals(newData.inventory, oldData.inventory) then changes.inventory = newData.inventory end -- Send changes to client Network.PlayerData:FireClient(player, changes) end local store = Lyra.createPlayerStore({ name = "PlayerData", template = template, schema = schema, changedCallbacks = { syncWithClient }, }) ``` -------------------------------- ### Performing Complex Data Transformations with Lyra MigrationStep.transform Source: https://github.com/paradoxum-games/lyra/blob/main/docs/advanced/migrations.md This snippet demonstrates using `Lyra.MigrationStep.transform` for more complex data restructuring. It shows how to convert an existing simple inventory list into a more detailed items table, including adding new properties like 'acquired' and removing the old 'inventory' field. ```lua migrationSteps = { Lyra.MigrationStep.transform("inventoryToItems", function(data) -- Convert simple inventory list to detailed items data.items = {} for _, item in data.inventory do table.insert(data.items, { id = item, acquired = os.time(), }) end data.inventory = nil return data end), } ``` -------------------------------- ### Updating External State First (Not Recommended) Source: https://github.com/paradoxum-games/lyra/blob/main/docs/advanced/state-management.md This pattern updates an external game state first, then attempts to synchronize it with Lyra. This approach is not recommended because it compromises Lyra's atomicity, transaction guarantees, and data consistency, potentially leading to complex reconciliation issues if Lyra updates fail or diverge. ```lua -- External state is the primary source of truth function giveCoins(player, amount) -- Update external state first gameState.players[player.UserId].coins += amount -- Then flush to Lyra as a secondary step store:updateAsync(player.UserId, function(data) data.coins = gameState.players[player.UserId].coins return true end) end ``` -------------------------------- ### Executing Atomic Multi-Player Transactions with Lyra in Lua Source: https://github.com/paradoxum-games/lyra/blob/main/docs/core-concepts.md This code demonstrates how to use `store:txAsync` to perform atomic operations involving multiple players' data, such as a trading system. The transaction ensures that either all changes across involved players succeed, or none do, maintaining data consistency in complex multi-player interactions. ```Lua store:txAsync({player1, player2}, function(state) local item = table.remove(state[player1].inventory, 1) if not item then return false -- Abort the transaction end table.insert(state[player2].inventory, item) return true end) ``` -------------------------------- ### Updating Lyra First (Recommended) Source: https://github.com/paradoxum-games/lyra/blob/main/docs/advanced/state-management.md This recommended pattern updates Lyra as the primary source of truth for data changes. By updating Lyra first, developers leverage its built-in atomicity and transaction capabilities, ensuring data integrity. Other systems can then derive their state from Lyra through change callbacks, maintaining consistency. ```lua -- Lyra is the primary source of truth function giveCoins(player, amount) -- Update Lyra first store:updateAsync(player.UserId, function(data) data.coins += amount return true end) -- Other systems can derive from Lyra through changedCallbacks end ``` -------------------------------- ### Ensuring Data Persistence on Game Shutdown with Lyra in Lua Source: https://github.com/paradoxum-games/lyra/blob/main/docs/core-concepts.md This snippet shows the essential step of calling `store:closeAsync()` within `game:BindToClose`. This ensures that all active session data is properly saved when the game shuts down, complementing Lyra's automatic autosave feature and preventing data loss. ```Lua game:BindToClose(function() store:closeAsync() end) ``` -------------------------------- ### Managing Persistent Player Data with Lyra and Ephemeral Game State Separately Source: https://github.com/paradoxum-games/lyra/blob/main/docs/advanced/state-management.md This strategy demonstrates how to use Lyra for persistent player-specific data, such as quest progress, while managing ephemeral, non-persistent game state (e.g., active quest timers or locations) with a separate state management library. This separation ensures Lyra's integrity for critical data without overcomplicating temporary game logic. ```lua -- Lyra handles persistent data store:updateAsync(player.UserId, function(data) data.questProgress.questId = 5 data.questProgress.stepsCompleted += 1 return true end) -- Game systems can track active state separately gameState.activeQuests[player.UserId] = { questId = 5, timeRemaining = 600, location = workspace.QuestLocations.Cave } ``` -------------------------------- ### Implementing Badge Award System with Lyra changedCallbacks in Lua Source: https://github.com/paradoxum-games/lyra/blob/main/docs/advanced/state-management.md This Lua code snippet demonstrates how to set up a badge award system that reacts to player data changes using Lyra's `changedCallbacks`. It checks for specific milestones, such as reaching 1000 coins or completing 10 quests, by comparing `oldData` and `newData` to award corresponding badges. ```lua -- Set up a badge award system that reacts to data changes changedCallbacks = { function(key, newData, oldData) if oldData == nil then return end local playerId = tonumber(key) -- Check if player reached a milestone if oldData.coins < 1000 and newData.coins >= 1000 { BadgeService.awardBadge(playerId, BADGES.COIN_COLLECTOR) } -- Check if player completed all quests if #oldData.completedQuests < 10 and #newData.completedQuests >= 10 { BadgeService.awardBadge(playerId, BADGES.QUEST_MASTER) } end } ``` -------------------------------- ### Defining Data Schemas for Validation with Lyra in Lua Source: https://github.com/paradoxum-games/lyra/blob/main/docs/core-concepts.md This snippet illustrates how to enforce data validation by defining a schema when creating a Lyra player store. It shows setting a `template` for default values and a `schema` using `t.strictInterface` to ensure data integrity and reject operations that would result in invalid data. ```Lua local store = Lyra.createPlayerStore({ name = "PlayerData", template = { coins = 0, inventory = {}, }, schema = t.strictInterface({ coins = t.number, inventory = t.table, }), }) ``` -------------------------------- ### Deriving External State from Lyra via Callbacks Source: https://github.com/paradoxum-games/lyra/blob/main/docs/advanced/state-management.md This snippet illustrates how external state management systems can react to and update their internal state based on changes originating from Lyra. By registering `changedCallbacks`, any modifications to Lyra data automatically trigger updates in the external `gameState`, ensuring that all systems remain synchronized with Lyra as the source of truth. ```lua -- When Lyra data changes, update external state changedCallbacks = { function(key, newData, oldData) local playerId = tonumber(key) gameState.players[playerId][key] = newData[key] end } ``` -------------------------------- ### Adding New Fields to Lyra Player Data Source: https://github.com/paradoxum-games/lyra/blob/main/docs/advanced/migrations.md This snippet demonstrates the three-step process for introducing new fields to player data in Lyra. It involves updating the data template with a default value, defining the field's type in the schema, and then creating an 'addFields' migration step to apply the new field to existing player data. ```lua -- Step 1: Add to template local template = { coins = 0, -- Existing field gems = 0, -- New field } -- Step 2: Add to schema local schema = { coins = t.number, gems = t.number, -- New field } -- Step 3: Add migration local store = Lyra.createPlayerStore({ template = template, schema = schema, migrationSteps = { Lyra.MigrationStep.addFields("addGems", { gems = 0, }), }, }) ``` -------------------------------- ### Handling Frozen Data in Lyra Change Callbacks Source: https://github.com/paradoxum-games/lyra/blob/main/docs/advanced/networking.md Highlights the read-only nature of data passed to Lyra's change callbacks. It demonstrates that `newData` and `oldData` are frozen, preventing direct modification within the callback function to ensure data integrity and prevent unintended side effects. ```Lua local function callback(key, newData, oldData) -- ❌ This will error - data is frozen newData.coins = 100 end ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.