diff --git a/README.md b/README.md index 8ca0d97..7af12a7 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,15 @@ # Lapis A Roblox DataStore abstraction that offers: -- Caching -- Session locking -- Validation (rejects incorrect data) -- Migrations -- Retries -- Throttling -- Promise based API -- Immutability +- **Session Locking** - Documents can only be accessed from one server at a time. This prevents some bugs and duping methods. +- **Validation** - Ensure your data is correct before saving it. +- **Migrations** - Update the structure of your data over time. +- **Retries** - Failed DataStore requests will be retried. +- **Throttling** - DataStore requests will never exceed their budget and throw an error. +- **Promise-based API** - Promises are used instead of yielding. +- **Immutability** - Documents must be updated immutably. +- **Save Batching** - Pending `Document:save()` and `Document:close()` calls are combined into one DataStore request when possible. +- **Auto Save** - Documents are automatically saved every 5 minutes. +- **BindToClose** - All documents are automatically closed when the game shuts down. This library was inspired by [Quicksave](https://github.com/evaera/Quicksave). diff --git a/docs/Example.md b/docs/Example.md new file mode 100644 index 0000000..f142498 --- /dev/null +++ b/docs/Example.md @@ -0,0 +1,58 @@ +--- +sidebar_position: 2 +--- + +# Example Usage +The following code is an example of how you would load and close player data: +```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) + -- 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) + else + documents[player] = document + end + 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) + 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 +``` +:::info +You do not need to handle `game:BindToClose` or auto saving. Lapis automatically does both of those. +::: \ No newline at end of file diff --git a/docs/Migrations.md b/docs/Migrations.md new file mode 100644 index 0000000..795a5a2 --- /dev/null +++ b/docs/Migrations.md @@ -0,0 +1,29 @@ +--- +sidebar_position: 3 +--- + +# Writing Migrations +Migrations allow you to update the structure of your data over time. You can add new keys, remove ones you no longer need, or change the way you store something. + +Here is an example of a few migrations: +```lua +local MIGRATIONS = { + -- Migrate from version 1 to 2. + function(old) + return Dictionary.merge(old, { + coins = 0, -- Add a key called coins to the data. + }) + end, + -- Migrate from version 2 to 3. + function(old) + -- We no longer need the playTime key, so we remove it. + return Dictionary.removeKey(old, "playTime") + end, +} + +local collection = Lapis.createCollection("collection", { + migrations = MIGRATIONS, + validate = validate, + defaultData = DEFAULT_DATA, +}) +``` \ No newline at end of file diff --git a/docs/intro.md b/docs/intro.md new file mode 100644 index 0000000..c7ef8e3 --- /dev/null +++ b/docs/intro.md @@ -0,0 +1,17 @@ +--- +sidebar_position: 1 +--- + +# Introduction + +## Features +- **Session Locking** - Documents can only be accessed from one server at a time. This prevents some bugs and duping methods. +- **Validation** - Ensure your data is correct before saving it. +- **Migrations** - Update the structure of your data over time. +- **Retries** - Failed DataStore requests will be retried. +- **Throttling** - DataStore requests will never exceed their budget and throw an error. +- **Promise-based API** - Promises are used instead of yielding. +- **Immutability** - Documents must be updated immutably. +- **Save Batching** - Pending `Document:save()` and `Document:close()` calls are combined into one DataStore request when possible. +- **Auto Save** - Documents are automatically saved every 5 minutes. +- **BindToClose** - All documents are automatically closed when the game shuts down. \ No newline at end of file diff --git a/src/init.lua b/src/init.lua index c6a6027..d2a28bc 100644 --- a/src/init.lua +++ b/src/init.lua @@ -84,7 +84,7 @@ end @within Lapis .validate (any) -> true | (false, string) -- Takes a document's data and returns true on success or false and an error on fail. .defaultData any - .migrations { (any) -> any } -- Migrations take old data and return new data. Order is first to last. + .migrations { (any) -> any }? -- Migrations take old data and return new data. Order is first to last. ]=] --[=[