### Lapis.setConfig Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/api-reference.md Example of setting global configuration options for Lapis. ```lua local Lapis = require(game.ReplicatedStorage.Packages.Lapis) Lapis.setConfig({ saveAttempts = 10, loadAttempts = 30, showRetryWarnings = false, }) ``` -------------------------------- ### Collection:load Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/api-reference.md Example of loading a document with a key and handling potential errors. ```lua local collection = Lapis.createCollection("PlayerData", { defaultData = { coins = 100 }, }) collection:load("Player123", { 123 }) :andThen(function(document) print("Loaded document with data:", document:read()) end) :catch(function(error) warn("Failed to load document:", error) end) ``` -------------------------------- ### Player Data Loading and Closing Example Source: https://github.com/nezuo/lapis/blob/master/docs/Example.md This code demonstrates loading player data when a player joins and closing it when they leave, including error handling and GDPR considerations. ```lua local DEFAULT_DATA = { coins = 100 } local collection = Lapis.createCollection("PlayerData", { defaultData = DEFAULT_DATA, -- You can use t by osyrisrblx to type check your data at runtime. validate = t.strictInterface({ coins = t.integer }), }) local documents = {} local function onPlayerAdded(player: Player) -- The second argument associates the document with the player's UserId which is useful -- for GDPR compliance. collection :load(`Player{player.UserId}`, { player.UserId }) :andThen(function(document) if player.Parent == nil then -- The player might have left before the document finished loading. -- The document needs to be closed because PlayerRemoving won't fire at this point. document:close():catch(warn) return end documents[player] = document end) :catch(function(message) warn(`Player {player.Name}'s data failed to load: {message}`) -- Optionally, you can kick the player when their data fails to load: player:Kick("Data failed to load.") end) end local function onPlayerRemoving(player: Player) local document = documents[player] -- The document won't be added to the dictionary if PlayerRemoving fires bofore it finishes loading. if document ~= nil then documents[player] = nil document:close():catch(warn) end end Players.PlayerAdded:Connect(onPlayerAdded) Players.PlayerRemoving:Connect(onPlayerRemoving) for _, player in Players:GetPlayers() do onPlayerAdded(player) end ``` -------------------------------- ### Lapis.createCollection Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/api-reference.md Example of creating a new collection for managing player data. ```lua local Lapis = require(game.ReplicatedStorage.Packages.Lapis) local playerDataCollection = Lapis.createCollection("PlayerData", { defaultData = { coins = 100, level = 1 }, validate = function(data) return type(data) == "table" and type(data.coins) == "number" and type(data.level) == "number" end, }) ``` -------------------------------- ### Main Entry Point Source: https://github.com/nezuo/lapis/blob/master/_autodocs/README.md Example of setting global configuration and creating a collection. ```lua local Lapis = require(game.ReplicatedStorage.Packages.Lapis) -- Set global configuration Lapis.setConfig({ saveAttempts = 10, loadAttempts = 30, }) -- Create a collection local playerData = Lapis.createCollection("PlayerData", { defaultData = { coins = 100, level = 1 }, validate = function(data) return type(data) == "table" and type(data.coins) == "number" end, }) ``` -------------------------------- ### Save Batching Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/throttling.md This example demonstrates how multiple save operations for the same key can be batched for efficiency. ```lua document:write({ coins = 100 }) document:save() -- Request 1 document:write({ coins = 200 }) document:save() -- Request 2 (pending) document:write({ coins = 300 }) document:save() -- Request 3 (pending, updates Request 2) ``` -------------------------------- ### Backwards Compatible Migrations Example Source: https://github.com/nezuo/lapis/blob/master/docs/Migrations.md Illustrates how to define migrations with explicit backwards compatibility settings. ```lua local function v1() -- v1 removes a key which causes an error on servers with version 0. old.playTime = nil return old end local function v2() -- v2 adds a new value to the player's data which won't result in an error on servers with version 1. old.items = {} return old end local function v3() -- v3 removes a key which causes an error on servers with version 0, 1, or 2. old.coins = nil return old end local MIGRATIONS = { { migrate = v1, backwardsCompatible = false, -- Version 1 isn't backwards compatible with version 0. }, { migrate = v2, backwardsCompatible = true, -- Version 2 is backwards compatible with version 1. }, v3, -- Migrations aren't backwards compatible by default. } ``` -------------------------------- ### Global Configuration Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/configuration.md Disables warnings for DataStore operation retries. ```lua Lapis.setConfig({ showRetryWarnings = false }) ``` -------------------------------- ### Example Migrations Source: https://github.com/nezuo/lapis/blob/master/docs/Migrations.md Demonstrates adding a new key and removing an existing key during data migration. ```lua local MIGRATIONS = { -- Migrate from version 0 to 1. function(old) return Dictionary.merge(old, { coins = 0, -- Add a key called coins to the data. }) end, -- Migrate from version 1 to 2. function(old) -- We no longer need the playTime key, so we remove it. -- Note: Migrations can update the data mutably but you still need to return the value. old.playTime = nil return old end, } local collection = Lapis.createCollection("collection", { migrations = MIGRATIONS, validate = validate, defaultData = DEFAULT_DATA, }) ``` -------------------------------- ### Global Configuration Example (Testing) Source: https://github.com/nezuo/lapis/blob/master/_autodocs/configuration.md Configures Lapis to use a mock DataStoreService for testing. ```lua local MockDataStoreService = require(script.Parent.MockDataStoreService) Lapis.setConfig({ dataStoreService = MockDataStoreService.new() }) ``` -------------------------------- ### Example Document Value Source: https://github.com/nezuo/lapis/blob/master/_autodocs/throttling.md An example of a document's data structure. ```lua { migrationVersion = 3, lastCompatibleVersion = 1, lockId = "550e8400-e29b-41d4-a716-446655440000", data = { coins = 1000, level = 50, experience = 50000, } } ``` -------------------------------- ### Collection:read Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/api-reference.md Example of reading a document's data without a session lock. ```lua local collection = Lapis.createCollection("PlayerData", { defaultData = { coins = 100 }, }) collection:read("Player123") :andThen(function(data) if data then print("Player coins:", data.coins) else print("Document does not exist") end end) :catch(function(error) warn("Failed to read document:", error) end) ``` -------------------------------- ### Install Lapis via Wally Source: https://github.com/nezuo/lapis/blob/master/_autodocs/README.md Instructions for installing Lapis using the Wally package manager. ```toml [dependencies] Lapis = "nezuo/lapis@0.3.4" ``` -------------------------------- ### Example with t Library (Recommended) Source: https://github.com/nezuo/lapis/blob/master/_autodocs/configuration.md Shows how to use the 't' library for more robust and recommended data validation. ```lua local t = require(game.ReplicatedStorage.Packages.t) local collection = Lapis.createCollection("PlayerData", { defaultData = { coins = 100, level = 1 }, validate = t.strictInterface({ coins = t.integer, level = t.integer, }) }) ``` -------------------------------- ### Per-Operation Retry Customization Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/throttling.md Example of how UpdateAsync is called with retry configuration parameters. ```lua -- From src/Data/init.luau return self.throttle:updateAsync( dataStore, key, transform, true, -- cancelOnGameClose attempts, -- From config retryDelay -- From config ) ``` -------------------------------- ### Migration Sequencing Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/migrations.md Demonstrates migration sequencing with multiple functions to add/modify fields over versions. ```lua local MIGRATIONS = { -- Version 0->1: Add coins function(data, key) print("Migration 1: Adding coins to", key) return { coins = 0, } end, -- Version 1->2: Add level function(data, key) print("Migration 2: Adding level") return { coins = data.coins, level = 1, } end, -- Version 2->3: Rename field function(data, key) print("Migration 3: Adding experience") return { coins = data.coins, level = data.level, experience = 0, } end, } local collection = Lapis.createCollection("PlayerData", { defaultData = { coins = 100, level = 1, experience = 0 }, migrations = MIGRATIONS, }) ``` -------------------------------- ### Compatible Migration Example (Adds Field) Source: https://github.com/nezuo/lapis/blob/master/_autodocs/migrations.md This example demonstrates a backwards compatible migration where a new field is added. Older servers can ignore this new field. ```lua { migrate = function(data, key) -- Adding a field is safe if old servers can ignore it data.items = data.items or {} return data end, backwardsCompatible = true } ``` -------------------------------- ### Global Configuration Example (Production) Source: https://github.com/nezuo/lapis/blob/master/_autodocs/configuration.md Configures Lapis to use the default DataStoreService instance. ```lua Lapis.setConfig({ dataStoreService = game:GetService("DataStoreService") }) ``` -------------------------------- ### Using Backwards Compatibility Source: https://github.com/nezuo/lapis/blob/master/_autodocs/configuration.md An example showing how to use backwards compatibility flags within migrations. ```lua local collection = Lapis.createCollection("PlayerData", { defaultData = { coins = 100 }, migrations = { { migrate = function(data, key) return { coins = data.coins, level = 1, -- Add new field (backwards compatible) } end, backwardsCompatible = true }, { migrate = function(data, key) data.playTime = nil -- Remove field (not backwards compatible) return data end, backwardsCompatible = false }, } }) ``` -------------------------------- ### Example with Simple Validation Source: https://github.com/nezuo/lapis/blob/master/_autodocs/configuration.md Demonstrates a basic validation function that checks data types and values. ```lua local collection = Lapis.createCollection("PlayerData", { defaultData = { coins = 100 }, validate = function(data) if type(data) ~= "table" then return false, "Data must be a table" end if type(data.coins) ~= "number" then return false, "coins must be a number" end if data.coins < 0 then return false, "coins cannot be negative" end return true end }) ``` -------------------------------- ### Create a Collection Source: https://github.com/nezuo/lapis/blob/master/_autodocs/README.md Example of how to create a new Lapis collection with default data. ```lua local Lapis = require(game.ReplicatedStorage.Packages.Lapis) local collection = Lapis.createCollection("PlayerData", { defaultData = { coins = 100 } }) ``` -------------------------------- ### Document:beforeClose() Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/api-reference.md Registers a callback to log the number of coins before a document is closed. ```lua collection:load("Player123") :andThen(function(document) document:beforeClose(function() local data = document:read() print("Document is about to close with coins:", data.coins) end) document:close() end) ``` -------------------------------- ### DefaultDataThrew Example Scenario Source: https://github.com/nezuo/lapis/blob/master/_autodocs/errors.md Example of a defaultData function that throws an error if the key format is unexpected. ```lua local collection = Lapis.createCollection("PlayerData", { defaultData = function(key) -- This function throws if key format is unexpected local playerId = tonumber(key:match("Player(%d+)")) if not playerId then error("Invalid key format") end return { playerId = playerId } end }) ``` -------------------------------- ### MigrationError Example Scenario Source: https://github.com/nezuo/lapis/blob/master/_autodocs/errors.md Example of a migration function that might throw an error if the 'coins' field is missing. ```lua local collection = Lapis.createCollection("PlayerData", { defaultData = { coins = 100 }, migrations = { function(data, key) -- This throws if data doesn't have coins field return { coins = data.coins + 50 } end } }) ``` -------------------------------- ### Incompatible Migration Example (Removes Field) Source: https://github.com/nezuo/lapis/blob/master/_autodocs/migrations.md This example demonstrates a migration that is not backwards compatible because it removes a field that older servers might expect. ```lua { migrate = function(data, key) -- Removing a field breaks old servers that expect it data.playTime = nil return data end, backwardsCompatible = false } ``` -------------------------------- ### Document:read() Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/api-reference.md Shows how to load a document and then read its data, accessing specific fields like 'coins' and 'level'. ```lua collection:load("Player123") :andThen(function(document) local data = document:read() print("Coins:", data.coins) print("Level:", data.level) end) ``` -------------------------------- ### Global Configuration Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/configuration.md Sets the delay between load operation retry attempts to 2 seconds. ```lua Lapis.setConfig({ loadRetryDelay = 2 }) ``` -------------------------------- ### Document:beforeSave() Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/api-reference.md Registers a callback to log the number of coins before a document is saved. ```lua collection:load("Player123") :andThen(function(document) document:beforeSave(function() local data = document:read() print("About to save data with coins:", data.coins) end) document:save() end) ``` -------------------------------- ### Simple Migration Array Source: https://github.com/nezuo/lapis/blob/master/_autodocs/configuration.md An example demonstrating how to define a simple array of migration functions to transform document data. ```lua local collection = Lapis.createCollection("PlayerData", { defaultData = { coins = 100 }, migrations = { -- Migration 1: Add new field function(data, key) return { coins = data.coins, level = 1, } end, -- Migration 2: Rename field function(data, key) return { coins = data.coins, level = data.level, experience = 0, } end, } }) ``` -------------------------------- ### Transitive Backwards Compatibility Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/migrations.md Illustrates how backwards compatibility is transitive across multiple migration versions. ```lua local MIGRATIONS = { { migrate = function(data, key) ... end, backwardsCompatible = false, -- v1 not compatible with v0 }, { migrate = function(data, key) ... end, backwardsCompatible = true, -- v2 compatible with v1 }, { migrate = function(data, key) ... end, backwardsCompatible = true, -- v3 compatible with v2 }, } ``` -------------------------------- ### Using the Key Parameter Source: https://github.com/nezuo/lapis/blob/master/_autodocs/migrations.md Example of using the 'key' parameter to extract a user ID from a document key. ```lua function(data, key) -- Extract user ID from key (e.g., "Player123" -> 123) local userId = tonumber(key:match("Player(%d+)")) or 0 return { coins = data.coins, level = data.level, userId = userId, } end ``` -------------------------------- ### Migrations Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/README.md Illustrates how to define migrations for schema evolution, including adding a new field and renaming a field with backwards compatibility considerations. ```lua migrations = { -- Add new field function(data, key) data.newField = "default" return data end, -- Rename field with backwards compatibility { migrate = function(data, key) data.newName = data.oldName data.oldName = nil return data end, backwardsCompatible = false, } } ``` -------------------------------- ### Collection Operations Source: https://github.com/nezuo/lapis/blob/master/_autodocs/README.md Examples of loading, reading, and removing documents from a collection. ```lua -- Load a document (session locked) playerData:load("Player123", { 123 }) :andThen(function(document) -- Use document end) -- Read without locking playerData:read("Player123") :andThen(function(data) if data then print("Player coins:", data.coins) end end) -- Remove a document playerData:remove("Player123") :andThen(function() print("Removed") end) ``` -------------------------------- ### Customizing Lapis Configuration Source: https://github.com/nezuo/lapis/blob/master/_autodocs/throttling.md Example of how to set custom retry configurations for load operations. ```lua Lapis.setConfig({ loadAttempts = 40, loadRetryDelay = 2, }) collection:load("Player123") -- Uses custom retry config ``` -------------------------------- ### Collection-Specific Configuration Example (Static Default Data) Source: https://github.com/nezuo/lapis/blob/master/_autodocs/configuration.md Creates a collection named 'PlayerData' with static default data for new documents. ```lua local collection = Lapis.createCollection("PlayerData", { defaultData = { coins = 100, level = 1, inventory = {}, } }) ``` -------------------------------- ### Document:write() Example (Immutable Update) Source: https://github.com/nezuo/lapis/blob/master/_autodocs/api-reference.md Illustrates an immutable update using Document:write(), where a new data object is provided. ```lua collection:load("Player123") :andThen(function(document) local currentData = document:read() document:write({ coins = currentData.coins + 100, level = currentData.level, }) end) ``` -------------------------------- ### Global Configuration Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/configuration.md Sets the maximum number of retry attempts for load operations to 30. ```lua Lapis.setConfig({ loadAttempts = 30 }) ``` -------------------------------- ### Document:write() Example (Mutable Update) Source: https://github.com/nezuo/lapis/blob/master/_autodocs/api-reference.md Demonstrates a mutable update by directly modifying the data object obtained from Document:read(). ```lua collection:load("Player123") :andThen(function(document) local data = document:read() data.coins = data.coins + 100 end) ``` -------------------------------- ### Document:save() Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/api-reference.md Loads a document, modifies its data (increments coins), saves the changes, and handles success or failure. ```lua collection:load("Player123") :andThen(function(document) local data = document:read() data.coins = data.coins + 100 return document:save() end) :andThen(function() print("Save successful") end) :catch(function(error) warn("Save failed:", error) end) ``` -------------------------------- ### Handling Errors Source: https://github.com/nezuo/lapis/blob/master/_autodocs/errors.md Example of how to handle errors using the :catch() method on a promise. ```lua collection:load("Player123") :andThen(function(document) -- Success path end) :catch(function(error) -- Error path if type(error) == "string" then print("Error message:", error) end end) ``` -------------------------------- ### Document:addUserId() Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/api-reference.md Shows how to associate an additional user ID with a document and then save the changes. ```lua collection:load("Player123", { 123 }) :andThen(function(document) -- Associate an additional user ID document:addUserId(456) document:save() end) ``` -------------------------------- ### RobloxApiError Recovery Strategy Source: https://github.com/nezuo/lapis/blob/master/_autodocs/errors.md Example of how to handle a RobloxApiError, including checking for 'DataStoreFailure'. ```lua collection:load("Player123") :catch(function(error) if string.find(error, "DataStoreFailure") then warn("DataStore error, will retry:", error) -- Lapis already retries, so this indicates all retries failed -- You may want to: -- 1. Kick the player and have them rejoin -- 2. Use a fallback/cached version of the data -- 3. Log the error for investigation end end) ``` -------------------------------- ### Error Handling Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/README.md Demonstrates how to handle potential errors when loading a document using promises, with specific checks for 'SessionLocked' and 'DataStoreFailure'. ```lua collection:load("Player123") :andThen(function(document) -- Success path end) :catch(function(error) -- Error path if string.find(error, "SessionLocked") then warn("Document locked by another server") elseif string.find(error, "DataStoreFailure") then warn("DataStore error:", error) end end) ``` -------------------------------- ### Document:close() Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/api-reference.md Demonstrates closing a document when a player leaves, handling the save process and potential errors. ```lua Players.PlayerRemoving:Connect(function(player) local document = documents[player] if document then documents[player] = nil document:close() :andThen(function() print("Document closed successfully") end) :catch(function(error) warn("Failed to close document:", error) end) end end) ``` -------------------------------- ### Player Data Management Source: https://github.com/nezuo/lapis/blob/master/_autodocs/README.md Example pattern for managing player data using Lapis. ```lua local documents = {} local function onPlayerAdded(player) playerData:load(`Player{player.UserId}`, { player.UserId }) :andThen(function(document) if player.Parent == nil then document:close():catch(warn) return end documents[player] = document end) :catch(function(error) warn(`Data failed to load: {error}`) player:Kick("Data failed to load.") end) end local function onPlayerRemoving(player) local document = documents[player] if document then documents[player] = nil document:close():catch(warn) end end Players.PlayerAdded:Connect(onPlayerAdded) Players.PlayerRemoving:Connect(onPlayerRemoving) ``` -------------------------------- ### Collection:remove() Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/api-reference.md Demonstrates how to remove a document from a collection and handle the promise resolution or rejection. ```lua local collection = Lapis.createCollection("PlayerData", { defaultData = { coins = 100 }, }) collection:remove("Player123") :andThen(function() print("Document removed successfully") end) :catch(function(error) warn("Failed to remove document:", error) end) ``` -------------------------------- ### Global Configuration Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/configuration.md Sets the maximum number of retry attempts for save and close operations to 10. ```lua Lapis.setConfig({ saveAttempts = 10 }) ``` -------------------------------- ### Defensive Migration Code Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/migrations.md Demonstrates writing migration functions defensively by handling missing fields and potential data type issues gracefully. ```lua function(data, key) -- Handle missing fields gracefully local coins = data.coins or 100 local level = data.level or 1 -- Handle data type issues if type(coins) ~= "number" then coins = tonumber(coins) or 100 end return { coins = coins, level = level, } end ``` -------------------------------- ### Adding Timeouts to Load Operations Source: https://github.com/nezuo/lapis/blob/master/_autodocs/throttling.md Example of how to add a promise timeout to load operations. ```lua collection:load("Player123") :timeout(30) -- Promise timeout after 30 seconds :catch(function(error) warn("Load timed out:", error) end) ``` -------------------------------- ### Load Player Data Source: https://github.com/nezuo/lapis/blob/master/_autodocs/README.md Example of loading player data from a collection using the Promise API. ```lua collection:load("Player123"):andThen(function(document) print("Loaded:", document:read()) end) ``` -------------------------------- ### Document:keyInfo() Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/api-reference.md Retrieves and prints the last updated time and associated user IDs from a document's key information. ```lua collection:load("Player123") :andThen(function(document) local keyInfo = document:keyInfo() print("Last updated at:", keyInfo:GetUpdatedTime()) print("Associated users:", keyInfo:GetUserIds()) end) ``` -------------------------------- ### Unit Test Example for Migration Source: https://github.com/nezuo/lapis/blob/master/_autodocs/migrations.md A basic unit test written in Lua using a describe/it structure to verify a migration function's output. ```lua -- PlayerData.test.lua describe("Migrations", function() it("should add experience field", function() local data = { coins = 100, level = 5, } local function migration(d, key) return { coins = d.coins, level = d.level, experience = 0, } end local result = migration(data, "Player1") expect(result.coins).to.equal(100) expect(result.level).to.equal(5) expect(result.experience).to.equal(0) end) end) ``` -------------------------------- ### Converting Data Structure Source: https://github.com/nezuo/lapis/blob/master/_autodocs/migrations.md Example of converting a simple array of item names to an array of item objects. ```lua function(data, key) -- Old: items = { "sword", "shield" } -- New: items = { { name = "sword" }, { name = "shield" } } local newItems = {} for _, itemName in data.items or {} do table.insert(newItems, { name = itemName }) end return { coins = data.coins, items = newItems, } end ``` -------------------------------- ### MigrationError Recovery Strategy Source: https://github.com/nezuo/lapis/blob/master/_autodocs/errors.md Example of how to catch and handle a MigrationError, with potential causes and recovery steps. ```lua collection:load("Player123") :catch(function(error) if string.find(error, "Migration") then warn("Migration failed:", error) -- Possible causes: -- 1. Data structure is corrupted in DataStore -- 2. Migration function has a bug -- 3. Downgraded to an older server version -- Consider removing the document and creating with default data end end) ``` -------------------------------- ### RobloxApiError Example Message Source: https://github.com/nezuo/lapis/blob/master/_autodocs/errors.md Example message format for a RobloxApiError. ```lua DataStoreFailure(RobloxApiError(...)) ``` -------------------------------- ### Developer Product Purchase Source: https://github.com/nezuo/lapis/blob/master/_autodocs/README.md Example of implementing a `ProcessReceipt` function to handle developer product purchases, granting coins to a player and saving their data. ```lua local function processReceipt(receiptInfo) local player = Players:GetPlayerByUserId(receiptInfo.PlayerId) if not player or not documents[player] then return Enum.ProductPurchaseDecision.NotProcessedYet end local document = documents[player] local data = document:read() -- Apply product effect data.coins = data.coins + 100 document:write(data) -- Save immediately for critical transaction local ok = document:save():await() return ok and Enum.ProductPurchaseDecision.PurchaseGranted or Enum.ProductPurchaseDecision.NotProcessedYet end MarketplaceService.ProcessReceipt = processReceipt ``` -------------------------------- ### UpdateAsync Retry Parameters Source: https://github.com/nezuo/lapis/blob/master/_autodocs/throttling.md Example of how retry attempts and delay can be passed as parameters to the updateAsync function. ```lua self.throttle:updateAsync(dataStore, key, transform, cancelOnGameClose, attempts, retryDelay) ``` -------------------------------- ### UpdateAsync Transform Function Source: https://github.com/nezuo/lapis/blob/master/_autodocs/throttling.md Example of an UpdateAsync transform function, showing its parameters and possible return values. ```lua function(value, keyInfo) -- value: Current value in DataStore (or nil if new) -- keyInfo: DataStoreKeyInfo object -- Return one of: -- 1. ("succeed", newValue, userIds, metadata) -- 2. ("retry", error) -- 3. ("fail", error) -- 4. ("cancelled") -- Only for game-close operations -- Modify and return return "succeed", newValue, userIds, metadata end ``` -------------------------------- ### Migration Object with Backwards Compatibility Source: https://github.com/nezuo/lapis/blob/master/_autodocs/migrations.md Example of a migration defined as an object with 'migrate' function and 'backwardsCompatible' flag. ```lua local collection = Lapis.createCollection("PlayerData", { defaultData = { coins = 100 }, migrations = { { migrate = function(data, key) return { coins = data.coins, level = 1, } end, backwardsCompatible = true } } }) ``` -------------------------------- ### Simple Function Migration Source: https://github.com/nezuo/lapis/blob/master/_autodocs/migrations.md Example of a basic migration using a simple Lua function to add a new field 'level'. ```lua local collection = Lapis.createCollection("PlayerData", { defaultData = { coins = 100 }, migrations = { function(data, key) return { coins = data.coins, level = 1, -- Add new field } end } }) ``` -------------------------------- ### BeforeSaveCloseCallbackThrew Example Scenario Source: https://github.com/nezuo/lapis/blob/master/_autodocs/errors.md An example scenario demonstrating a 'BeforeSaveCloseCallbackThrew' error where a callback throws an error if document data is nil. ```lua collection:load("Player123") :andThen(function(document) document:beforeSave(function() local data = document:read() -- This throws if data is nil print(data.coins) end) end) ``` -------------------------------- ### ValidateFailed Example Scenario Source: https://github.com/nezuo/lapis/blob/master/_autodocs/errors.md Example of a validation function that checks data types and constraints, returning false for invalid data. ```lua local collection = Lapis.createCollection("PlayerData", { defaultData = { coins = 100, level = 1 }, validate = function(data) if type(data) ~= "table" then return false, "data must be a table" end if type(data.coins) ~= "number" then return false, "coins must be a number" end if data.coins < 0 then return false, "coins cannot be negative" end return true end }) ``` -------------------------------- ### ValidateThrew Example Scenario Source: https://github.com/nezuo/lapis/blob/master/_autodocs/errors.md Example of a validation function that might throw an error due to an unhandled condition, like accessing a nil field. ```lua local collection = Lapis.createCollection("PlayerData", { defaultData = { coins = 100 }, validate = function(data) -- This throws if data.level doesn't exist if data.level > 100 then -- Could be nil return false, "level too high" end return true end }) ``` -------------------------------- ### Scenario 3: Safe Refactoring (Rename Field) Migration Source: https://github.com/nezuo/lapis/blob/master/_autodocs/migrations.md Example of renaming a field while attempting to maintain backwards compatibility through a migration process. Note: This specific example marks the migration as breaking. ```lua local collection = Lapis.createCollection("PlayerData", { defaultData = { coins = 100, experience = 0, }, migrations = { { migrate = function(data, key) -- Map old field to new field data.experience = data.exp or 0 data.exp = nil -- Remove old field return data end, backwardsCompatible = false, -- Breaking change } } }) ``` -------------------------------- ### Save Data Source: https://github.com/nezuo/lapis/blob/master/_autodocs/README.md Example of saving a document after modifications. ```lua document:save():andThen(function() print("Saved!") end) ``` -------------------------------- ### DefaultDataThrew Recovery Strategy Source: https://github.com/nezuo/lapis/blob/master/_autodocs/errors.md Example of how to handle a DefaultDataThrew error. ```lua collection:load("InvalidKey") :catch(function(error) if string.find(error, "defaultData") then warn("Failed to generate default data:", error) -- Ensure key format is correct -- Check the defaultData function for bugs end end) ``` -------------------------------- ### Removing a Field Source: https://github.com/nezuo/lapis/blob/master/_autodocs/migrations.md Example of removing a field by setting its value to nil. ```lua function(data, key) data.oldField = nil return data end ``` -------------------------------- ### Migration Error Handling Examples Source: https://github.com/nezuo/lapis/blob/master/_autodocs/migrations.md Illustrates scenarios where a migration function might fail (throwing an error or returning nil) and how to catch these errors when loading a document. ```lua function(data, key) if data.coins == nil then error("Missing coins field") -- Throws error end if data.level > 100 then return nil -- Invalid return end return data end ``` ```lua collection:load("Player123") :catch(function(error) if string.find(error, "Migration") then warn("Failed to load due to migration error:", error) -- Optionally remove the corrupted document -- collection:remove("Player123"):await() -- Then create fresh with default data end end) ``` -------------------------------- ### Transforming a Field Source: https://github.com/nezuo/lapis/blob/master/_autodocs/migrations.md Example of transforming a field's value, such as doubling the 'coins'. ```lua function(data, key) return { coins = data.coins * 2, -- Double coins level = data.level, } end ``` -------------------------------- ### SessionLocked Recovery Strategy Source: https://github.com/nezuo/lapis/blob/master/_autodocs/errors.md Example of handling a SessionLocked error after all retries have been exhausted. ```lua collection:load("Player123") :andThen(function(document) -- If we reach here, the lock was acquired -- (may have retried internally) end) :catch(function(error) if string.find(error, "SessionLocked") then warn("Session locked after all retries:", error) -- All retry attempts exhausted -- Consider: -- 1. Increasing loadAttempts in config -- 2. Increasing loadRetryDelay to give other servers more time -- 3. Kicking the player if they need immediate access end end) ``` -------------------------------- ### Document Operations Source: https://github.com/nezuo/lapis/blob/master/_autodocs/README.md Examples of reading, writing, modifying, and saving document data. ```lua collection:load("Player123"):andThen(function(document) -- Read data local data = document:read() -- Write data (immutable) document:write({ coins = data.coins + 100, level = data.level, }) -- Or modify directly (if freezeData = false) data.coins = data.coins + 100 -- Add/remove user IDs document:addUserId(456) document:removeUserId(789) -- Register callbacks document:beforeSave(function() print("About to save") end) document:beforeClose(function() print("About to close") end) -- Save the document document:save() -- Save and close document:close() end) ``` -------------------------------- ### SessionLockStolen Error Recovery Source: https://github.com/nezuo/lapis/blob/master/_autodocs/errors.md Code example for recovering from a 'SessionLockStolen' error during a save operation. ```lua document:save() :catch(function(error) if string.find(error, "SessionLockStolen") then warn("Session lock was overridden by another server:", error) -- Changes cannot be saved -- This typically means: -- 1. Server took too long (>30 min) without closing -- 2. Another server loaded the document -- Consider: -- 1. Closing the document -- 2. Investigating why the lock was held so long end end) ``` -------------------------------- ### Collection-Specific Configuration Example (Function Default Data) Source: https://github.com/nezuo/lapis/blob/master/_autodocs/configuration.md Creates a collection named 'PlayerData' with a function to generate unique default data per document based on the key. ```lua local collection = Lapis.createCollection("PlayerData", { defaultData = function(key) -- key will be something like "Player123" return { coins = 100, level = 1, playerId = tonumber(key:match("Player(%d+)")), inventory = {}, } end }) ``` -------------------------------- ### DocumentRemoved Error Recovery Source: https://github.com/nezuo/lapis/blob/master/_autodocs/errors.md Code example for recovering from a 'DocumentRemoved' error during a save operation. ```lua document:save() :catch(function(error) if string.find(error, "DocumentRemoved") then warn("Document was removed from DataStore:", error) -- The document can no longer be saved -- Consider: -- 1. Closing the document -- 2. Re-loading the document (will create new with default data) -- 3. Notifying the player end end) ``` -------------------------------- ### Developer Product Handling Source: https://github.com/nezuo/lapis/blob/master/_autodocs/usage-patterns.md Example of handling developer product purchases using Lapis collections, including applying product logic and tracking recent purchases. ```lua local MarketplaceService = game:GetService("MarketplaceService") local Players = game:GetService("Players") local ReplicatedStorage = game:GetService("ReplicatedStorage") local Lapis = require(ReplicatedStorage.Packages.Lapis) local collection = Lapis.createCollection("PlayerData", { defaultData = { coins = 100, recentPurchases = {} }, }) local documents = {} -- Product handlers local PRODUCTS = { [12345] = function(data) return { coins = data.coins + 100, recentPurchases = data.recentPurchases, } end, [12346] = function(data) return { coins = data.coins + 500, recentPurchases = data.recentPurchases, } end, } local function processReceipt(receiptInfo) local player = Players:GetPlayerByUserId(receiptInfo.PlayerId) if not player then return Enum.ProductPurchaseDecision.NotProcessedYet end -- Wait for document to load while not documents[player] and player.Parent do task.wait() end local document = documents[player] if not document then return Enum.ProductPurchaseDecision.NotProcessedYet end local data = document:read() -- Check if already processed if table.find(data.recentPurchases, receiptInfo.PurchaseId) then local ok = document:save():await() return ok and Enum.ProductPurchaseDecision.PurchaseGranted or Enum.ProductPurchaseDecision.NotProcessedYet end -- Apply product local productHandler = PRODUCTS[receiptInfo.ProductId] if not productHandler then return Enum.ProductPurchaseDecision.NotProcessedYet end local ok, newData = pcall(productHandler, data) if not ok then return Enum.ProductPurchaseDecision.NotProcessedYet end -- Add to recent purchases local newPurchases = table.clone(data.recentPurchases) table.insert(newPurchases, receiptInfo.PurchaseId) -- Limit recent purchases to 100 if #newPurchases > 100 then table.remove(newPurchases, 1) end newData.recentPurchases = newPurchases document:write(newData) local saveOk = document:save():await() return saveOk and Enum.ProductPurchaseDecision.PurchaseGranted or Enum.ProductPurchaseDecision.NotProcessedYet end MarketplaceService.ProcessReceipt = processReceipt ``` -------------------------------- ### BeforeSaveCloseCallbackThrew Error Recovery Source: https://github.com/nezuo/lapis/blob/master/_autodocs/errors.md Code example for recovering from a 'BeforeSaveCloseCallbackThrew' error during document close. ```lua document:close() :catch(function(error) if string.find(error, "CallbackThrew") then warn("Callback error during close:", error) -- Fix the callback function -- Consider retrying the close operation end end) ``` -------------------------------- ### Kick Player on Error Pattern Source: https://github.com/nezuo/lapis/blob/master/_autodocs/errors.md Example of catching a load error and kicking the player with an explanation. ```lua collection:load(tostring(player.UserId)) :andThen(function(document) documents[player] = document end) :catch(function(error) warn(`Failed to load data for {player.Name}:`, error) player:Kick("Data failed to load. Please rejoin.") end) ``` -------------------------------- ### Mistake 2: Returning Nil Source: https://github.com/nezuo/lapis/blob/master/_autodocs/migrations.md Illustrates the error of returning nil from a migration function and shows the correct approach of always returning data. ```lua -- BAD: Migration returns nil function(data, key) if not data.valid then return nil -- Error! end return data end -- GOOD: Always return data function(data, key) if not data.valid then return data -- Return even if invalid end return data end ``` -------------------------------- ### Graceful Degradation Pattern Source: https://github.com/nezuo/lapis/blob/master/_autodocs/errors.md Example of handling a failed document load by returning a dummy document. ```lua collection:load("Player123") :andThen(function(document) return document end) :catch(function(error) warn("Could not load player data:", error) -- Return a dummy document or cached data return { read = function() return { coins = 0 } end, write = function() end, save = function() return Promise.resolve() end, close = function() return Promise.resolve() end, } end) ``` -------------------------------- ### Session-Based Data with Checkpoints Source: https://github.com/nezuo/lapis/blob/master/_autodocs/usage-patterns.md Demonstrates how to create a collection for session-based data that automatically saves progress to checkpoints. ```lua local collection = Lapis.createCollection("SessionData", { defaultData = function(key) return { startedAt = os.time(), checkpoint = 0, progress = 0, } end, }) collection:load(sessionKey) :andThen(function(document) -- Restore to last checkpoint local data = document:read() restoreCheckpoint(data.checkpoint) -- Register checkpoint callback document:beforeSave(function() local data = document:read() data.checkpoint = currentCheckpoint() data.progress = currentProgress() end) end) ``` -------------------------------- ### ValidateThrew Recovery Strategy Source: https://github.com/nezuo/lapis/blob/master/_autodocs/errors.md Example of catching a ValidateThrew error and suggesting fixes for the validation function. ```lua collection:load("Player123") :catch(function(error) if string.find(error, "ValidateThrew") then warn("Validation error (bug in validate function):", error) -- Fix the validate function -- Ensure it handles all edge cases end end) ``` -------------------------------- ### Document:removeUserId() Example Source: https://github.com/nezuo/lapis/blob/master/_autodocs/api-reference.md Illustrates how to remove a user ID association from a document and then save the changes. ```lua collection:load("Player123", { 123, 456 }) :andThen(function(document) -- Remove a user ID association document:removeUserId(456) document:save() end) ``` -------------------------------- ### Renaming a Field Source: https://github.com/nezuo/lapis/blob/master/_autodocs/migrations.md Example of renaming a field by creating a new structure and excluding the old field. ```lua function(data, key) return { coins = data.coins, level = data.level, -- Renamed from 'playerLevel' -- oldLevel = nil, -- Remove old field } end ```