๐ Migrations
Migrations allow you to evolve your data schema over time without losing existing player data.
๐ What Are Migrations?โ
When you change your dataTemplate, existing saved data won't automatically have the new structure. Migrations are functions that transform old data into the new format.
flowchart LR
A["๐ฆ Old Data<br/>v1"] --> B["๐ Migration 2"]
B --> C["๐ Migration 3"]
C --> D["โ
Current Data<br/>v3"]
๐ ๏ธ Creating Migrationsโ
Add migration functions to your schema's migrations array:
local PlayerSchema = Coppermind.registerSchema({
name = "PlayerData",
dataTemplate = {
coins = 0,
gems = 0,
diamonds = 0, -- Added in v2
settings = { -- Added in v3
musicEnabled = true,
},
},
migrations = {
-- Migration 1: Initial version (often empty)
function(data)
-- Nothing to do for v1
end,
-- Migration 2: Add diamonds field
function(data)
data.diamonds = 0
end,
-- Migration 3: Add settings
function(data)
data.settings = {
musicEnabled = true,
}
end,
},
})
โ๏ธ How Migrations Workโ
| Step | Description |
|---|---|
| 1๏ธโฃ | Each saved data entry has a version number |
| 2๏ธโฃ | When loading, Coppermind compares saved version to migration count |
| 3๏ธโฃ | All migrations from savedVersion + 1 to currentVersion run |
| 4๏ธโฃ | Data is reconciled with the template |
Example Flowโ
๐ฆ Saved data (version 1):
{ coins = 500 }
๐ Migrations 2 and 3 run:
{ coins = 500, diamonds = 0, settings = { musicEnabled = true } }
โ
Reconciliation with template:
{ coins = 500, gems = 0, diamonds = 0, settings = { musicEnabled = true } }
โ Migration Best Practicesโ
- ๐ซ Never Remove
- ๐ก๏ธ Handle Missing
- ๐พ Preserve Data
- ๐ Transform
Keep Forever
Once deployed, migrations should never be removed!
migrations = {
function(data) end, -- v1 - keep forever
function(data)
data.newField = 0
end, -- v2 - keep forever
function(data)
data.anotherField = ""
end, -- v3 - keep forever
}
function(data)
-- Check before accessing nested tables
if not data.stats then
data.stats = {}
end
data.stats.newStat = 0
end
-- โ
Good - preserves existing value or sets default
function(data)
data.gems = data.gems or 0
end
-- โ Bad - overwrites existing data
function(data)
data.gems = 0 -- Would reset player's gems!
end
-- Renaming a field
function(data)
data.currency = data.coins or 0
data.coins = nil -- Remove old field
end
-- Restructuring nested data
function(data)
if data.musicVolume then
data.settings = data.settings or {}
data.settings.musicVolume = data.musicVolume
data.musicVolume = nil
end
end
๐งฉ Complex Migration Examplesโ
๐ Restructuring Dataโ
-- Before: { level = 5, xp = 100, ... }
-- After: { stats = { level = 5, xp = 100 }, ... }
function(data)
data.stats = {
level = data.level or 1,
xp = data.xp or 0,
}
data.level = nil
data.xp = nil
end
๐ Converting Array Formatsโ
-- Before: { inventory = { "sword", "shield" } }
-- After: { inventory = { { id = "sword", count = 1 }, { id = "shield", count = 1 } } }
function(data)
if data.inventory and type(data.inventory[1]) == "string" then
local newInventory = {}
for _, itemId in data.inventory do
table.insert(newInventory, {
id = itemId,
count = 1,
})
end
data.inventory = newInventory
end
end
๐งฎ Adding Computed Defaultsโ
-- Set initial value based on existing data
function(data)
-- Veterans get a bonus
if data.playtime and data.playtime > 3600 then
data.veteranBonus = 100
else
data.veteranBonus = 0
end
end
๐งช Testing Migrationsโ
Use Mock Mode
Test migrations without affecting real player data!
Coppermind.setMockMode(true)
Coppermind.clearMockData()
-- Simulate old data
local store = Coppermind.loadStore(PlayerSchema, "test_key", {})
task.wait(0.2)
-- Verify migrations ran correctly
local data = Coppermind.getData(PlayerSchema, "test_key")
assert(data.newField ~= nil, "Migration should add newField")
Coppermind.unloadStore(PlayerSchema, "test_key")
Coppermind.setMockMode(false)
๐ Version Trackingโ
The store's version property tracks which migrations have been applied:
local store = Coppermind.loadStore(schema, key, {})
store.onReady:Connect(function()
print("Data version:", store.version)
-- This equals the number of migrations that have been applied
end)
๐ Debugging Migrationsโ
Development Only
Remove debug logging before deploying to production!
migrations = {
function(data)
print("[Migration 1] Running...")
data.newField = data.newField or "default"
print("[Migration 1] Complete")
end,
function(data)
print("[Migration 2] Running...")
print("[Migration 2] Current data:", data)
data.anotherField = true
print("[Migration 2] Complete")
end,
}