{"id":19104185,"url":"https://github.com/NameTakenBonk/SuphisDataStoreModule","last_synced_at":"2025-04-18T19:33:04.094Z","repository":{"id":119042063,"uuid":"563490226","full_name":"NameTakenBonk/SuphisDataStoreModule","owner":"NameTakenBonk","description":"Beta","archived":false,"fork":false,"pushed_at":"2024-06-09T12:30:14.000Z","size":1386,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-11-09T04:03:20.550Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"0bsd","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/NameTakenBonk.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":null,"code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":null,"patreon":"Suphi","open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"lfx_crowdfunding":null,"custom":null}},"created_at":"2022-11-08T18:12:22.000Z","updated_at":"2024-07-29T16:58:01.000Z","dependencies_parsed_at":"2024-11-09T04:02:54.992Z","dependency_job_id":"075c5129-074c-40e5-a98e-5ec92ea3fea2","html_url":"https://github.com/NameTakenBonk/SuphisDataStoreModule","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NameTakenBonk%2FSuphisDataStoreModule","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NameTakenBonk%2FSuphisDataStoreModule/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NameTakenBonk%2FSuphisDataStoreModule/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NameTakenBonk%2FSuphisDataStoreModule/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/NameTakenBonk","download_url":"https://codeload.github.com/NameTakenBonk/SuphisDataStoreModule/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249536320,"owners_count":21287578,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-09T04:00:57.596Z","updated_at":"2025-04-18T19:33:03.804Z","avatar_url":"https://github.com/NameTakenBonk.png","language":"Lua","readme":"![💻Suphi's_Data_Store_Module](https://github.com/NameTakenBonk/SuphisDataStoreModule/assets/83465599/997eb561-56a2-4b68-ae6e-0ce0a646c7cd)\n\n# Suphis DataStore Module \n\n![carbon (2)](https://github.com/NameTakenBonk/SuphisDataStoreModule/assets/83465599/d8e6a7a2-4134-4134-bcff-7153f840de7e)\n\n\nBefore I start, this is not mine but 5uphi module, I got permission to put this up in github. Here is the discord: https://discord.gg/B3zmjPVBce\n\n![GitHub all releases](https://img.shields.io/github/downloads/NameTakenBonk/SuphisDataStoreModule/total?color=%231fb594) ![GitHub Repo stars](https://img.shields.io/github/stars/NameTakenBonk/SuphisDataStoreModule?color=%23fff200)\n\n# Features \n\n* Session locking            Prevents multiple servers from opening the same datastore key\n* Cross Server Communication Easily use MemoryStoreQueue to send data to the session owner\n* Auto save                  Automatically saves cached data to the datastore based on the saveinterval property\n* Bind To Close              Automatically saves, closes and destroys all sessions when server starts to close\n* Reconcile                  Fills in missing values from the template into the value property\n* Compression                Compress data to reduce character count\n* Multiple script support    Safe to interact with the same datastore object from multiple scripts\n* Task batching              Tasks will be batched togever when possible\n* Direct value access        Access the datastore value directly, module will never tamper with your data and will never leave any data in your datastore or memorystore\n* Easy to use                Simple and elegant\n* Lightweight                No RunService events and no while do loops 100% event based\n\n# Suphi's DataStore Module vs ProfileService\n\n**ProfileService** relies on `os.time()` to lock the session. The problem with this is that each servers `os.time()` may not be 100% in sync. So to combat this problem **ProfileService** has a session timeout of 30 minutes, but if the servers have a `os.time()` delta greater then 30 minutes, then the server will be able to bypass the session lock and you will lose data. Another problem is because sessions are locked for 30 minutes, if roblox servers go down and quickly come back up and the server was not able to unlock the sessions. Then players will not be able to enter your game for 30 minutes until the sessions timeout. but will be able to enter other games that dont use **ProfileService**.\n\nSo the way **Suphi's DataStore Module** works is that it uses the `MemoryStore` to save the session lock and because memorystores have a built in expiration time. The memorystore will get automatically removed for all servers at the exact same time and because of this it will be imposible for a server to bypass the session lock. This also allows us to have a very low session timeout of **[interval] + 30 seconds**. Another benefit of using the `MemoryStore` is that instead of using `UpdateAsync` on the `DataStore`. We only use ``UpdateAsync`` for the ``MemoryStore``. Which allows us to not waste any ``Get`` and ``Set`` requests for the ``DataStore``. The ``MemoryStore`` has a request limit of ``1000 + 100 ⨉ [number of player]`` **per minute** while the DataStore only has a request limit of ``60 + 10 ⨉ [number of player]`` **per minute**.\n\n**ProfileService** relays on ``RunService.Heartbeat`` and has a few ``while true do task.wait()`` end. on the other hand **Suphi's DataStore Module** is 100% event driven making it super lightweight\n\n**ProfileService** saves data along side your data and forces you to save your data as a table where **Suphi's DataStore Module** gives you full access to your datastores value and lets you set the datastore value directly with numbers, strings, booleans, tables or nil **Suphi's DataStore Module** will not save any data inside your datastore\n\n# Download\n\nGo to releases and download the version(latest stable version prefered), or copy the code(Both signal and datastoremodule, make signal the child of the main module.) in the repo. Alternatively without downloading you can do:\n\n### Roblox\nhttps://create.roblox.com/marketplace/asset/11671168253/\n```lua\nlocal dataStoreModule = require(11671168253)\n```\n\n### Wally\n```toml\nsuphisdatastoremodule = \"nametakenbonk/suphisdatastoremodule@1.3.0\"\n```\n\nCurrent version: `1.3`\n\n# Docs/API\n\nDo you want check the docs or the api you can do so here: https://nametakenbonk.github.io/SuphisDataStoreModule/\n\n# Events\n\n```lua\nStateChanged(state: boolean?, object: DataStore)  Signal\n```\nFires after state property has changed\n\n```lua\nSaving(value: Variant, object: DataStore)  Signal\n```\nFires just before the value is about to save\n\n```lua\nSaved(response: string, responseData: any, dataStore: DataStore)  Signal\n```\nFires after a save attempt\n\n```lua\nAttemptsChanged(AttemptsRemaining: number, object: DataStore)  Signal\n```\nFires when the AttemptsRemaining property has changed\n\n```lua\nProcessQueue(id: string, values: array, dataStore: DataStore)  Signal\n```\nFires when state = true and values detected inside the MemoryStoreQueue\n\n# Simple Example\n\n```lua\n-- Require the ModuleScript\nlocal DataStoreModule = require(11671168253)\n\n-- Find or create a datastore object\nlocal dataStore = DataStoreModule.new(\"Name\", \"Key\")\n\n-- Connect a function to the StateChanged event and print to the output when the state changes\ndataStore.StateChanged:Connect(function(state)\n    if state == nil then print(\"Destroyed\", dataStore.Id) end\n    if state == false then print(\"Closed   \", dataStore.Id) end\n    if state == true then print(\"Open     \", dataStore.Id) end\nend)\n\n-- Open the datastore session\nlocal response, responseData = dataStore:Open()\n\n-- If the session fails to open lets print why and return\nif response ~= \"Success\" then print(dataStore.Id, response, responseData) return end\n\n-- Set the datastore value\ndataStore.Value = \"Hello world!\"\n\n-- Save, close and destroy the session\ndataStore:Destroy()\n```\n\n\n# Load Example\n\n```lua\nlocal DataStoreModule = require(11671168253)\nlocal dataStore = DataStoreModule.new(\"Name\", \"Key\")\n\n-- read the value from the datastore\nif dataStore:Read() ~= \"Success\" then return end\n\n-- WARNING this value might be out of date use open instead if you need the latest value\nprint(dataStore.Value)\n\n-- as we never opened the session it will instantly destroy without saving or closing\ndataStore:Destroy()\n```\n\n# Setup Player Data Example\n\n```lua\nlocal DataStoreModule = require(11671168253)\n\nlocal template = {\n    Level = 0,\n    Coins = 0,\n    Inventory = {},\n    DeveloperProducts = {},\n}\n\ngame.Players.PlayerAdded:Connect(function(player)\n    local dataStore = DataStoreModule.new(\"Player\", player.UserId)\n    if dataStore:Open(template) ~= \"Success\" then print(player.Name, \"failed to open\") end\nend)\n\ngame.Players.PlayerRemoving:Connect(function(player)\n    local dataStore = DataStoreModule.find(\"Player\", player.UserId)\n    if dataStore == nil then return end\n    dataStore:Destroy()\nend)\n```\n\n# Setup Player Data Example\n\n```lua\nlocal dataStore = DataStoreModule.find(\"Player\", player.UserId)\nif dataStore == nil then return end\nif dataStore.State ~= true then return end -- make sure the session is open or the value will never get saved\ndataStore.Value.Level += 1\n```\n\n# Developer Products Example\n\n```lua \nlocal marketplaceService = game:GetService(\"MarketplaceService\")\nlocal DataStoreModule = require(11671168253)\n\nmarketplaceService.ProcessReceipt = function(receiptInfo)\n    local dataStore = DataStoreModule.find(\"Player\", receiptInfo.PlayerId)\n    if dataStore == nil then return Enum.ProductPurchaseDecision.NotProcessedYet end\n    if dataStore.State ~= true then return Enum.ProductPurchaseDecision.NotProcessedYet end\n\n    -- convert the ProductId to a string as we are not allowed empty slots for numeric indexes\n    local productId = tostring(receiptInfo.ProductId)\n\n    -- Add 1 to to the productId in the DeveloperProducts table\n    dataStore.Value.DeveloperProducts[productId] = (dataStore.Value.DeveloperProducts[productId] or 0) + 1\n\n    if dataStore:Save() == \"Saved\" then\n        -- there was no errors lets grant the purchase\n        return Enum.ProductPurchaseDecision.PurchaseGranted\n    else\n        -- the save failed lets make sure to remove the product or it might get saved in the next save interval\n        dataStore.Value.DeveloperProducts[productId] -= 1\n        return Enum.ProductPurchaseDecision.NotProcessedYet\n    end\nend\n```\n\n# Setup Player Data Automatic Retry Example\n\n```lua\nlocal DataStoreModule = require(11671168253)\n\nlocal template = {\n    Level = 0,\n    Coins = 0,\n    Inventory = {},\n    DeveloperProducts = {},\n}\n\nlocal function StateChanged(state, dataStore)\n    while dataStore.State == false do -- Keep trying to re-open if the state is closed\n        if dataStore:Open(template) ~= \"Success\" then task.wait(6) end\n    end\nend\n\ngame.Players.PlayerAdded:Connect(function(player)\n    local dataStore = DataStoreModule.new(\"Player\", player.UserId)\n    dataStore.StateChanged:Connect(StateChanged)\n    StateChanged(dataStore.State, dataStore)\nend)\n\ngame.Players.PlayerRemoving:Connect(function(player)\n    local dataStore = DataStoreModule.find(\"Player\", player.UserId)\n    if dataStore ~= nil then dataStore:Destroy() end -- If the player leaves datastore object is destroyed allowing the retry loop to stop\nend)\n```\n\n# Leaderstats Example\n```lua\nlocal DataStoreModule = require(11671168253)\n\nlocal keys = {\"Level\", \"Coins\"}\n\nlocal function StateChanged(state, dataStore)\n    if state ~= true then return end\n    for index, name in keys do\n        dataStore.Leaderstats[name].Value = dataStore.Value[name]\n    end\nend\n\nlocal function Add(player, key, amount)\n    local dataStore = DataStoreModule.find(\"Player\", player.UserId)\n    if dataStore == nil then return end\n    if dataStore.State ~= true then return end\n    dataStore.Value[key] += amount\n    dataStore.Leaderstats[key].Value = dataStore.Value[key]\nend\n\ngame.Players.PlayerAdded:Connect(function(player)\n    local leaderstats = Instance.new(\"Folder\")\n    leaderstats.Name = \"leaderstats\"\n    leaderstats.Parent = player\n    \n    for index, name in keys do\n        local intValue = Instance.new(\"IntValue\")\n        intValue.Name = name\n        intValue.Parent = leaderstats\n    end\n\n    local dataStore = DataStoreModule.new(\"Player\", player.UserId)\n    dataStore.Leaderstats = leaderstats -- save the leaderstats folder into the datastore object\n    dataStore.StateChanged:Connect(StateChanged)\nend)\n\n-- give somePlayer 10 coins\nAdd(somePlayer, \"Coins\", 10)\n```\n\n# Compression Example\n```lua\nlocal httpService = game:GetService(\"HttpService\")\n\nlocal DataStoreModule = require(11671168253)\nlocal dataStore = DataStoreModule.new(\"name\", \"key\")\nif dataStore:Open() ~= \"Success\" then return end\n\n-- Enable compression\ndataStore.Metadata.Compress = {[\"Level\"] = 2, [\"Decimals\"] = 3, [\"Safety\"] = true}\n-- Level can be set to 1 or 2 (1 will allow mixed tables / 2 will not allow mixed tables but will compress arrays better)\n-- Decimals will set the maximum number of decimals saved for numbers more decimals will use more data\n-- Safety will scan your strings for the delete character [] and replace them with space [ ]\n-- Setting to false will save faster but you could break the datastore if you have the delete character in any of your keys/strings\n-- Recommended to set safty to true if you save strings sent from the client\n\ndataStore.Value = {\n    [\"Number\"] = 1234567891234.987,\n    [\"String\"] = \"Hello World!\",\n    [\"Array\"] = {1234567891234567, 2345678912345678, 3456789123456789, 4567891234567891, 5678912345678912}\n}\n\n-- save datastore to force the CompressedValue to update\ndataStore:Save()\n\nprint(dataStore.Value)\n-- print the datastore value\nprint(httpService:JSONEncode(dataStore.Value)) \n-- print the compressed value\nprint(httpService:JSONEncode(dataStore.CompressedValue))\n```\n\n# Queue Example\n```lua\nlocal DataStoreModule = require(11671168253)\n\nlocal template = {\n    Level = 0,\n    Coins = 0,\n    Inventory = {},\n    DeveloperProducts = {},\n}\n\nlocal function ProcessQueue(id, values, dataStore)\n    -- this function will only get called if the datastore is open\n    if dataStore:Remove(id) ~= \"Success\" then return end\n    for index, value in values do dataStore.Value.Coins += value end\n    -- if the datastore fails to save after we changed the coins, coins will be lost\nend\n\nlocal function GiveCoins(userId, amount)\n    -- try to find a datastore or create a hidden one\n    local dataStore = DataStoreModule.find(\"Player\", userId) or DataStoreModule.hidden(\"Player\", userId)\n    local response = dataStore:Open(template)\n    if response == \"Success\" then\n        dataStore.Value.Coins += amount -- datastore is open set the coins directly\n    elseif response == \"Locked\" then\n        -- another server has the datastore open add the amount to the queue so they can process it\n        -- it's posible that the other server might miss the amount added to the queue\n        -- if so amount will stay in the queue for upto (3888000 seconds / 45 days)\n        return dataStore:Queue(amount, 3888000) == \"Success\"\n    else\n        return false -- roblox servers are down\n    end\n    return dataStore.Hidden == false or dataStore:Destroy() == \"Saved\" -- if this is a hidden datastore destroy it\nend\n\ngame.Players.PlayerAdded:Connect(function(player)\n    local dataStore = DataStoreModule.new(\"Player\", player.UserId)\n    if dataStore:Open(template) ~= \"Success\" then print(player.Name, \"failed to open\") return end\n    dataStore.ProcessQueue:Connect(ProcessQueue)\nend)\n\ngame.Players.PlayerRemoving:Connect(function(player)\n    local dataStore = DataStoreModule.find(\"Player\", player.UserId)\n    if dataStore ~= nil then dataStore:Destroy() end\nend)\n\n-- try to give 5uphi 10 coins after 5 seconds\ntask.wait(5)\nlocal success = GiveCoins(\"456056545\", 10)\n```\n\n # SAFER PROCESS QUEUE EXAMPLE 1\n```lua\nlocal function ProcessQueue(id, values, dataStore)\n    -- try to remove values from the queue if not success then return\n    if dataStore:Remove(id) ~= \"Success\" then return end\n\n    -- add coins for each value in the queue\n    for index, value in values do dataStore.Value.Coins += value end\n\n    -- try to save the datastore if saved then return\n    if dataStore:Save() == \"Saved\" then return end\n\n    -- try to adding the values back into the queue so we can process them again later\n    -- if we succeed in adding the value back into the queue remove the coins so they dont get saved in the next saving intervals\n    for index, value in values do\n        if dataStore:Queue(value, 3888000) == \"Success\" then dataStore.Value.Coins -= value end\n    end\n\n    -- any values that we could not add back into the queue will stay inside datastore.Value.Coins and hopefully they will get saved in the next saving intervals\n    -- if the next saving intervals also fail then the coins will be lost\nend\n```\n\n```lua\nlocal function ProcessQueue(id, values, dataStore)\n    -- add coins for each value in the queue\n    for index, value in values do dataStore.Value.Coins += value end\n\n    -- try to save the datastore if not saved then remove coins and return\n    if dataStore:Save() ~= \"Saved\" then\n        for index, value in values do dataStore.Value.Coins -= value end\n        return\n    end\n\n    -- try to remove values from the queue if success then return\n    if dataStore:Remove(id) == \"Success\" then return end\n\n    -- because remove was not success remove coins\n    for index, value in values do dataStore.Value.Coins -= value end\n\n    -- try to save the datastore\n    dataStore:Save()\n\n    -- if the datastore fails to save hopefully it will get saved in the next saving intervals\n    -- if the next saving intervals also fail then when the queue gets processed again and they will get the coins again\nend\n```\n\n# Responses\n\n```lua\nlocal response, responseData = dataStore:Open()\n-- Success, nil\n-- Locked, UniqueId\n-- State, Destroying/Destroyed\n-- Error, ErrorMessage\n\nlocal response, responseData = dataStore:Read()\n-- Success, nil\n-- State, Open\n-- Error, ErrorMessage\n\nlocal response, responseData = dataStore:Save()\n-- Saved, nil\n-- State, Closing/Closed/Destroying/Destroyed\n-- Error, ErrorMessage\n\nlocal response, responseData = dataStore:Close()\n-- Success, nil\n-- Saved, nil\n\nlocal response, responseData = dataStore:Destroy()\n-- Success, nil\n-- Saved, nil\n\nlocal response, responseData = dataStore:Queue()\n-- Success, nil\n-- Error, ErrorMessage\n\nlocal response, responseData = dataStore:Remove()\n-- Success, nil\n-- Error, ErrorMessage\n```\n\n# Update 1.3\n* added [any]: any to the datastore type\n\n\n\u003c!-- markdownlint-enable --\u003e\n","funding_links":["https://patreon.com/Suphi"],"categories":["Libraries"],"sub_categories":["DataStore"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FNameTakenBonk%2FSuphisDataStoreModule","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FNameTakenBonk%2FSuphisDataStoreModule","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FNameTakenBonk%2FSuphisDataStoreModule/lists"}