Releases: AlternativeLua/SuphisDataStoreModule
Version: 1.3
Update 1.3
- added [any]: any to the datastore type
v1.2
Update 1.1 + 1.2
- bug fix
- bug fix -- fixed small edge case when calling ds:Close() would return nil instead of "Success"
- Saved response will now return dataStore.Value as responseData instead of nil
- Added Saved event
- improved proxy
- improved task manager
- you can now save custom values inside the object
- under the hood changes
Full Changelog: V1.0...V1.2
v1.0
Update 1.0
- bug fixes
- added hidden objects
- added Response enum
- added SaveDelay
- added Hidden property
- added Queue
- added Remove
- renamed Load to Read
- functions now respond differently
- Close and Destroy now tell you if the datastore saved
- improved proxy
- improved task manager
- you can now save custom values inside the object
- under the hood changes
Full Changelog: V0.15...V1.0
Version: 0.15 [BETA]
Update
Fixed a bug where sometimes closing or destroying a session would still keep the session locked until the server shutdown
Version: 0.14 [BETA]
Update
From 0.10 to 0.14
* 0.11
- SaveWhenClosing renamed to SaveOnClose
- FailsBeforeClose renamed to LockAttempts
- Added AttemptsChanged event
- Saving, StateChanged and AttemptsChanged events now return the datastore session object
- bug fix
* 0.12
- Lock loop accuracy improved this allow us to reduce the lock time from
- (LockInterval + 1) * LockAttempts + 30 seconds to LockInterval * LockAttempts + 30 seconds
* 0.13
- Added a internal save throttle to prevent "Datastore request was added to queue." warning
* 0.14
- Doing dataStoreModule.new("Name", "Key") after doing dataStore:Destroy() will now always give you a brand new session object even if the previous session has not yet saved
Version: 0.10 [BETA]
SuphisDataStoreModule BETA
Before I start, this is not mine but Suphi#3388 module, I got permission to put this up in github. Here is the discord: https://discord.gg/B3zmjPVBce
Features
- Session locking Prevents multiple servers from opening the same datastore key
- Auto save Automatically saves cached data to the datastore based on the saveinterval property
- Bind To Close Automatically saves, closes and destroys all sessions when server starts to close
- Reconcile Fills in missing values from the template into the value property
- Compression Compress data to reduce character count
- Save throttling Impossible for save requests dropping
- Multiple script support Safe to interact with the same datastore object from multiple scripts
- Task batching Tasks will be batched togever when possible
- Direct value access Access the datastore value directly, module will never tamper with your data
- Easy to use Simple and elegant
- Lightweight No RunService events and no while do loops 100% event based
Suphi's DataStore Module vs ProfileService
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.
So 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.
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
ProfileService does many DeepTableCopys where it loops your data and copys all values into new tables which can be an exspensive operation for very large tables Suphi's DataStore Module was designed in such a way that it never has to DeepTableCopy
ProfileService forces you to save your data as a table where Suphi's DataStore Module lets you set the datastore value directly where you can save numbers, strings, booleans or tables
Download
Go 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:
https://create.roblox.com/marketplace/asset/11671168253/
local dataStoreModule = require(11671168253)Current version: 0.10 [BETA]
Contructors
Id string "8-4-4-4-12" READ ONLYUnique identifying string
new(name: string, scope: string, key: string)Returns previously created session else a new session
new(name: string, key: string)Returns previously created session else a new session
find(name: string, scope: string, key: string)Returns previously created session else nil
find(name: string, key: string)Returns previously created session else nil
Properties
Value Variant nilValue of datastore
Metadata table {}Metadata associated with the key
UserIds table {}An array of UserIds associated with the key
SaveInterval number 60Interval in seconds the datastore will automatically save (set to 0 to disable automatic saving)
LockInterval number 60Interval in seconds the memorystore will update the session lock
FailsBeforeClose number 5How many times the memorystore needs to fail before the session closes
SaveWhenClosing boolean trueAutomatically save the data when the session is closed or destroyed
Id string "Name/Scope/Key" READ ONLYUnique identifying string
Key string "Key" READ ONLYKey used for the datastore
State string "Closed" READ ONLYCurrent state of the session
AttemptsRemaining number 0 READ ONLYHow many memorystore attempts remaining before the session closes
CreatedTime number 0 READ ONLYNumber of milliseconds from epoch to when the datastore was created
UpdatedTime number 0 READ ONLYNumber of milliseconds from epoch to when the datastore was updated
Version string "" READ ONLYUnique identifying string of the current datastore save
CompressedValue string "" READ ONLYCompressed string that is updated before every save if compression is enabled by setting dataStore.Metadata.Compress = {["Level"] = 2, ["Decimals"] = 3, ["Safety"] = true}
Level = 1 (allows mixed tables), Level = 2 (does not allow mixed tables but compresses arrays better), Decimals = amount of decimal places saved, Safety = replace delete character from strings
Events
StateChanged(state: string) RBXScriptSignalFires after state property has changed
Saving(value: Variant) SignalFires just before the data is about to save
Methods
Open(default: Variant) nil/string nil/stringTries to open the session, optional template parameter will be reconciled onto the value, returns errorType and errorMessage if fails
Load(default: Variant) nil/string nil/stringLoads the datastore value without the need to open the session, optional template parameter will be reconciled onto the value, returns errorType and errorMessage if fails
Save() nil/string nil/stringForce save the current value to the datastore, returns errorType and errorMessage if fails
Close(save: boolean) nil/string nil/stringCloses the session, returns errorType and errorMessage if session is destroyed
Destroy() nilCloses and destroys the session, destroyed sessions will be locked
Clone() VariantClones the value property
Reconcile(template: Variant) nilFills in missing values from the template into the value property
Usage() number numberHow much datastore has been used, returns the character count and the second number is a number scaled from 0 to 1 [0 = 0% , 0.5 = 50%, 1 = 100%, 1.5 = 150%]
Simple Example
-- Require the ModuleScript
local dataStoreModule = require(11671168253)
-- Find or create a datastore object
local dataStore = dataStoreModule.new("Name", "Key")
-- Connect a function to the StateChanged event and print to the output when the state changes
dataStore.StateChanged:Connect(function(state)
if state == nil then print("Destroyed", dataStore.Id) end
if state == false then print("Closed ", dataStore.Id) end
if state == true then print("Open ", dataStore.Id) end
end)
-- Open the datastore session
local errorType, errorMessage = dataStore:Open()
-- If the session fails to open lets print why and return
if errorType ~= nil then print(dataStore.Id, errorType, errorMessage) return end
-- Set the datastore value
dataStore.Value = "Hello world!"
-- Save, close and destroy the session
dataStore:Destroy()Load Example
local dataStoreModule = require(11671168253)
local dataStore = dataStoreModule.new("Name", "Key")
-- load the value from the datastore
local errorType, errorMessage = dataStore:Load()
if errorType ~= nil then print(dataStore.Id, errorType, errorMessage) return end
-- WARNING this value might be out of date use open instead if you need the latest value
print(dataStore.Value)
-- as we never opened the session it will instantly destroy without saving or closing
dataStore:Destroy()Setup Player Data Example
local dataStoreModule = require(11671168253)
local template = {
Level = 0,
Inventory = {},
DeveloperProducts = {},
}
game.Players.PlayerAdded:Connect(function(player)
local dataStore = dataStoreModule.new("Player", player.UserId)
local errorType = dataStore:Open(template) -- reconcile with template
if errorType ~= nil then print(player.Name, "failed to open") end
end)
game.Players.PlayerRemoving:Connect(function(player)
local dataStore = dataStoreModule.find("Player", player.UserId)
if dataStore == nil then return end
dataStore:Destroy()
end)Setup Player Data Example
local dataStore = dataStoreModule.find("Player", player.UserId)
if dataStore == nil then return end
if dataStore.State ~= true then return end -- make sure the session is open or the value will never get saved
dataStore.Value.Level += 1Developer Products Example
local marketplaceService = game:GetSer...Version: 0.9 [BETA]
SuphisDataStoreModule BETA
Before I start, this is not mine but Suphi#3388 module, I got permission to put this up in github. Here is the discord: https://discord.gg/B3zmjPVBce
Features
- Session locking Prevents multiple servers from opening the same datastore key
- Auto save Automatically saves cached data to the datastore based on the saveinterval property
- Bind To Close Automatically saves, closes and destroys all sessions when server starts to close
- Reconcile Fills in missing values from the template into the value property
- Compression Compress data to reduce character count
- Save throttling Impossible for save requests dropping
- Multiple script support Safe to interact with the same datastore object from multiple scripts
- Task batching Tasks will be batched togever when possible
- Direct value access Access the datastore value directly, module will never tamper with your data
- Easy to use Simple and elegant
- Lightweight No RunService events and no while do loops 100% event based
Suphi's DataStore Module vs ProfileService
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.
So 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.
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
ProfileService does many DeepTableCopys where it loops your data and copys all values into new tables which can be an exspensive operation for very large tables Suphi's DataStore Module was designed in such a way that it never has to DeepTableCopy
ProfileService forces you to save your data as a table where Suphi's DataStore Module lets you set the datastore value directly where you can save numbers, strings, booleans or tables
Download
Go 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:
https://create.roblox.com/marketplace/asset/11671168253/
local dataStoreModule = require(11671168253)Current version: 0.9 [BETA]
Contructors
Id string "8-4-4-4-12" READ ONLYUnique identifying string
new(name: string, scope: string, key: string)Returns previously created session else a new session
new(name: string, key: string)Returns previously created session else a new session
find(name: string, scope: string, key: string)Returns previously created session else nil
find(name: string, key: string)Returns previously created session else nil
Properties
Value Variant nilValue of datastore
Metadata table {}Metadata associated with the key
UserIds table {}An array of UserIds associated with the key
SaveInterval number 60Interval in seconds the datastore will automatically save (set to 0 to disable automatic saving)
LockInterval number 60Interval in seconds the memorystore will update the session lock
SaveWhenClosing boolean trueAutomatically save the data when the session is closed or destroyed
Id string "Name/Scope/Key" READ ONLYUnique identifying string
Key string "Key" READ ONLYKey used for the datastore
State string "Closed" READ ONLYCurrent state of the session
CreatedTime number 0 READ ONLYNumber of milliseconds from epoch to when the datastore was created
UpdatedTime number 0 READ ONLYNumber of milliseconds from epoch to when the datastore was updated
Version string "" READ ONLYUnique identifying string of the current datastore save
CompressedValue string "" READ ONLYCompressed string that is updated before every save if compression is enabled by setting dataStore.Metadata.Compress = {["Level"] = 2, ["Decimals"] = 3, ["Safety"] = true}
Level = 1 (allows mixed tables), Level = 2 (does not allow mixed tables but compresses arrays better), Decimals = amount of decimal places saved, Safety = replace delete character from strings
Events
StateChanged(state: string) RBXScriptSignalFires after state property has changed
Saving(value: Variant) SignalFires just before the data is about to save
Methods
Open(default: Variant) nil/string nil/stringTries to open the session, optional template parameter will be reconciled onto the value, returns errorType and errorMessage if fails
Load(default: Variant) nil/string nil/stringLoads the datastore value without the need to open the session, optional template parameter will be reconciled onto the value, returns errorType and errorMessage if fails
Save() nil/string nil/stringForce save the current value to the datastore, returns errorType and errorMessage if fails
Close(save: boolean) nil/string nil/stringCloses the session, returns errorType and errorMessage if session is destroyed
Destroy() nilCloses and destroys the session, destroyed sessions will be locked
Clone() VariantClones the value property
Reconcile(template: Variant) nilFills in missing values from the template into the value property
Usage() number numberHow much datastore has been used, returns the character count and the second number is a number scaled from 0 to 1 [0 = 0% , 0.5 = 50%, 1 = 100%, 1.5 = 150%]
Simple Example
-- Require the ModuleScript
local dataStoreModule = require(11671168253)
-- Find or create a datastore object
local dataStore = dataStoreModule.new("Name", "Key")
-- Connect a function to the StateChanged event and print to the output when the state changes
dataStore.StateChanged:Connect(function(state)
if state == nil then print("Destroyed", dataStore.Id) end
if state == false then print("Closed ", dataStore.Id) end
if state == true then print("Open ", dataStore.Id) end
end)
-- Open the datastore session
local errorType, errorMessage = dataStore:Open()
-- If the session fails to open lets print why and return
if errorType ~= nil then print(dataStore.Id, errorType, errorMessage) return end
-- Set the datastore value
dataStore.Value = "Hello world!"
-- Save, close and destroy the session
dataStore:Destroy()Load Example
local dataStoreModule = require(11671168253)
local dataStore = dataStoreModule.new("Name", "Key")
-- load the value from the datastore
local errorType, errorMessage = dataStore:Load()
if errorType ~= nil then print(dataStore.Id, errorType, errorMessage) return end
-- WARNING this value might be out of date use open instead if you need the latest value
print(dataStore.Value)
-- as we never opened the session it will instantly destroy without saving or closing
dataStore:Destroy()Setup Player Data Example
local dataStoreModule = require(11671168253)
local template = {
Level = 0,
Inventory = {},
DeveloperProducts = {},
}
game.Players.PlayerAdded:Connect(function(player)
local dataStore = dataStoreModule.new("Player", player.UserId)
local errorType = dataStore:Open(template) -- reconcile with template
if errorType ~= nil then print(player.Name, "failed to open") end
end)
game.Players.PlayerRemoving:Connect(function(player)
local dataStore = dataStoreModule.find("Player", player.UserId)
if dataStore == nil then return end
dataStore:Destroy()
end)Setup Player Data Example
local dataStore = dataStoreModule.find("Player", player.UserId)
if dataStore == nil then return end
if dataStore.State ~= true then return end -- make sure the session is open or the value will never get saved
dataStore.Value.Level += 1Developer Products Example
local marketplaceService = game:GetService("MarketplaceService")
local dataStoreModule = require(11671168253)
marketplaceService.ProcessReceipt = function(receiptInfo)
local dataStore = dataStoreModule.find("Player", receiptInfo.PlayerId)
if dataStore == nil then ...Version: 0.8 [BETA]
SuphisDataStoreModule BETA
Before I start, this is not mine but Suphi#3388 module, I got permission to put this up in github. Here is the discord: https://discord.gg/B3zmjPVBce
Features
- Session locking Prevents multiple servers from opening the same datastore key
- Auto save Automatically saves cached data to the datastore based on the saveinterval property
- Bind To Close Automatically saves, closes and destroys all sessions when server starts to close
- Reconcile Fills in missing values from the template into the value property
- Compression Compress data to reduce character count
- Save throttling Impossible for save requests dropping
- Multiple script support Safe to interact with the same datastore object from multiple scripts
- Task batching Tasks will be batched togever when possible
- Direct value access Access the datastore value directly, module will never tamper with your data
- Easy to use Simple and elegant
- Lightweight No RunService events and no while do loops 100% event based
Suphi's DataStore Module vs ProfileService
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.
So 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.
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
ProfileService does many DeepTableCopys where it loops your data and copys all values into new tables which can be an exspensive operation for very large tables Suphi's DataStore Module was designed in such a way that it never has to DeepTableCopy
ProfileService forces you to save your data as a table where Suphi's DataStore Module lets you set the datastore value directly where you can save numbers, strings, booleans or tables
Download
Go to releases and download the version(latest stable version prefered), or copy the code in the repo. Alternatively without downloading you can do:
local dataStoreModule = require(11671168253)Current version: 0.8 [BETA]
Contructors
new(name: string, scope: string, key: string)Returns previously created session else a new session
new(name: string, key: string)Returns previously created session else a new session
find(name: string, scope: string, key: string)Returns previously created session else nil
find(name: string, key: string)Returns previously created session else nil
Properties
Value Variant nilValue of datastore
Metadata table {}Metadata associated with the key
UserIds table {}An array of UserIds associated with the key
SaveInterval number 60Interval in seconds the datastore will automatically save (set to 0 to disable automatic saving)
LockInterval number 60Interval in seconds the memorystore will update the session lock
SaveWhenClosing boolean trueAutomatically save the data when the session is closed or destroyed
Id string "Name/Scope/Key" READ ONLYUnique identifying string
Key string "Key" READ ONLYKey used for the datastore
State string "Closed" READ ONLYCurrent state of the session
CreatedTime number 0 READ ONLYNumber of milliseconds from epoch to when the datastore was created
UpdatedTime number 0 READ ONLYNumber of milliseconds from epoch to when the datastore was updated
Version string "" READ ONLYUnique identifying string of the current datastore save
CompressedValue string "" READ ONLYCompressed string that is updated before every save if compression is enabled by setting dataStore.Metadata.Compress = {["Level"] = 2, ["Decimals"] = 3, ["Safety"] = true}
Level = 1 (allows mixed tables), Level = 2 (does not allow mixed tables but compresses arrays better), Decimals = amount of decimal places saved, Safety = replace delete character from strings
Events
StateChanged(state: string) RBXScriptSignalFires after state property has changed
Saving(value: Variant) SignalFires just before the data is about to save
Methods
Open(default: Variant) nil/stringTries to open the session, optional template parameter will be reconciled onto the value, returns errorType and errorMessage if fails
Load(default: Variant) nil/stringLoads the datastore value without the need to open the session, optional template parameter will be reconciled onto the value, returns errorType and errorMessage if fails
Save() nil/stringForce save the current value to the datastore, returns errorType and errorMessage if fails
Close(save: boolean) nil/stringCloses the session, returns errorType and errorMessage if session is destroyed
Destroy() nilCloses and destroys the session, destroyed sessions will be locked
Clone() VariantClones the value property
Reconcile(template: Variant) nilFills in missing values from the template into the value property
Usage() number numberHow much datastore has been used, returns the character count and the second number is a number scaled from 0 to 1 [0 = 0% , 0.5 = 50%, 1 = 100%, 1.5 = 150%]
Simple Example
-- Require the ModuleScript
local dataStoreModule = require(11671168253)
-- Find or create a datastore object
local dataStore = dataStoreModule.new("Name", "Key")
-- Connect a function to the StateChanged event and print to the output when the state changes
dataStore.StateChanged:Connect(function(state)
if state == nil then print("Destroyed", dataStore.Id) end
if state == false then print("Closed ", dataStore.Id) end
if state == true then print("Open ", dataStore.Id) end
end)
-- Open the datastore session
local errorType, errorMessage = dataStore:Open()
-- If the session fails to open lets print why and return
if errorType ~= nil then print(dataStore.Id, errorType, errorMessage) return end
-- Set the datastore value
dataStore.Value = "Hello world!"
-- Save, close and destroy the session
dataStore:Destroy()Load Example
local dataStoreModule = require(11671168253)
local dataStore = dataStoreModule.new("Name", "Key")
-- load the value from the datastore
local errorType, errorMessage = dataStore:Load()
if errorType ~= nil then print(dataStore.Id, errorType, errorMessage) return end
-- WARNING this value might be out of date use open instead if you need the latest value
print(dataStore.Value)
-- as we never opened the session it will instantly destroy without saving or closing
dataStore:Destroy()Setup Player Data Example
local dataStoreModule = require(11671168253)
local template = {
Level = 0,
Inventory = {},
DeveloperProducts = {},
}
game.Players.PlayerAdded:Connect(function(player)
local dataStore = dataStoreModule.new("Player", player.UserId)
local errorType = dataStore:Open(template) -- reconcile with template
if errorType ~= nil then print(player.Name, "failed to open") end
end)
game.Players.PlayerRemoving:Connect(function(player)
local dataStore = dataStoreModule.find("Player", player.UserId)
if dataStore == nil then return end
dataStore:Destroy()
end)Setup Player Data Example
local dataStore = dataStoreModule.find("Player", player.UserId)
if dataStore == nil then return end
if dataStore.State ~= true then return end -- make sure the session is open or the value will never get saved
dataStore.Value.Level += 1Developer Products Example
local marketplaceService = game:GetService("MarketplaceService")
local dataStoreModule = require(11671168253)
marketplaceService.ProcessReceipt = function(receiptInfo)
local dataStore = dataStoreModule.find("Player", receiptInfo.PlayerId)
if dataStore == nil then return Enum.ProductPurchaseDecision.NotProcessedYet end
if dataStore.State ~= true then return Enum.ProductPurchaseDecision.NotProcessedYet end
-- convert the ProductId to a string as we are not allowed empty slots for numeric indexes
local produ...Version: 0.7 [BETA]
- version 0.7 is now out
- very small update
- SaveInterval and LockInterval set to 60 seconds by default
- SaveBeforeClose can no longer be set higher then 3
- Saving event now returns the datastore value
SuphisDataStoreModule BETA
Before I start, this is not mine but Suphi#3388 module, I got permission to put this up in github. Here is the discord: https://discord.gg/B3zmjPVBce
Features
- Session locking Prevents multiple servers from opening the same datastore key
- Auto save Automatically saves cached data to the datastore based on the saveinterval property
- Bind To Close Automatically saves, closes and destroys all sessions when server starts to close
- Reconcile Fills in missing values from the template into the value property
- Compression Compress data to reduce character count
- Save throttling Impossible for save requests dropping
- Multiple script support Safe to interact with the same datastore object from multiple scripts
- Task batching Tasks will be batched togever when possible
- Direct value access Access the datastore value directly, module will never tamper with your data
- Easy to use Simple and elegant
- Lightweight No RunService events and no while do loops 100% event based
Suphi's DataStore Module vs ProfileService
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.
So 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.
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
ProfileService does many DeepTableCopys where it loops your data and copys all values into new tables which can be an exspensive operation for very large tables Suphi's DataStore Module was designed in such a way that it never has to DeepTableCopy
ProfileService forces you to save your data as a table where Suphi's DataStore Module lets you set the datastore value directly where you can save numbers, strings, booleans or tables
Download
Go to releases and download the version(latest stable version prefered), or copy the code in the repo. Alternatively without downloading you can do:
local dataStoreModule = require(11671168253)Current version: 0.7 [BETA]
Contructors
new(name: string, scope: string, key: string)Returns previously created session else a new session
new(name: string, key: string)Returns previously created session else a new session
find(name: string, scope: string, key: string)Returns previously created session else nil
find(name: string, key: string)Returns previously created session else nil
Properties
Value Variant nilValue of datastore
Metadata table {}Metadata associated with the key
UserIds table {}An array of UserIds associated with the key
SaveInterval number 30Interval in seconds the datastore will automatically save (set to 0 to disable automatic saving)
LockInterval number 30Interval in seconds the memorystore will update the session lock
SaveBeforeClose boolean trueAutomatically save the data when the session is closed or destroyed
Id string "Name/Scope/Key" READ ONLYUnique identifying string
Key string "Key" READ ONLYKey used for the datastore
State string "Closed" READ ONLYCurrent state of the session
CreatedTime number 0 READ ONLYNumber of milliseconds from epoch to when the datastore was created
UpdatedTime number 0 READ ONLYNumber of milliseconds from epoch to when the datastore was updated
Version string "" READ ONLYUnique identifying string of the current datastore save
CompressedValue string "" READ ONLYCompressed string that is updated before every save if compression is enabled by setting dataStore.Metadata.Compress = {["Level"] = 2, ["Decimals"] = 3, ["Safety"] = true}
Level = 1 (allows mixed tables), Level = 2 (does not allow mixed tables but compresses arrays better), Decimals = amount of decimal places saved, Safety = replace delete character from strings
Events
StateChanged(state: string) RBXScriptSignalFires after state property has changed
Saving(value: Variant) SignalFires just before the data is about to save
Methods
Open(default: Variant) nil/stringTries to open the session, optional template parameter will be reconciled onto the value, returns errorType and errorMessage if fails
Load(default: Variant) nil/stringLoads the datastore value without the need to open the session, optional template parameter will be reconciled onto the value, returns errorType and errorMessage if fails
Save() nil/stringForce save the current value to the datastore, returns errorType and errorMessage if fails
Close(save: boolean) nil/stringCloses the session, returns errorType and errorMessage if session is destroyed
Destroy(save: boolean) nilCloses and destroys the session, destroyed sessions will be locked
Clone() VariantClones the value property
Reconcile(template: Variant) nilFills in missing values from the template into the value property
Usage() number numberHow much datastore has been used, returns the character count and the second number is a number scaled from 0 to 1 [0 = 0% , 0.5 = 50%, 1 = 100%, 1.5 = 150%]
Simple Example
-- Require the ModuleScript
local dataStoreModule = require(11671168253)
-- Find or create a datastore object
local dataStore = dataStoreModule.new("Name", "Key")
-- Connect a function to the StateChanged event and print to the output when the state changes
dataStore.StateChanged:Connect(function(state)
if state == nil then print("Destroyed", dataStore.Id) end
if state == false then print("Closed ", dataStore.Id) end
if state == true then print("Open ", dataStore.Id) end
end)
-- Open the datastore session
local errorType, errorMessage = dataStore:Open()
-- If the session fails to open lets print why and return
if errorType ~= nil then print(dataStore.Id, errorType, errorMessage) return end
-- Set the datastore value
dataStore.Value = "Hello world!"
-- Save, close and destroy the session
dataStore:Destroy()Load Example
local dataStoreModule = require(11671168253)
local dataStore = dataStoreModule.new("Name", "Key")
-- load the value from the datastore
local errorType, errorMessage = dataStore:Load()
if errorType ~= nil then print(dataStore.Id, errorType, errorMessage) return end
-- WARNING this value might be out of date use open instead if you need the latest value
print(dataStore.Value)
-- as we never opened the session it will instantly destroy without saving or closing
dataStore:Destroy()Setup Player Data Example
local dataStoreModule = require(11671168253)
local template = {
Level = 0,
Inventory = {},
DeveloperProducts = {},
}
game.Players.PlayerAdded:Connect(function(player)
local dataStore = dataStoreModule.new("Player", player.UserId)
local errorType = dataStore:Open(template) -- reconcile with template
if errorType ~= nil then print(player.Name, "failed to open") end
end)
game.Players.PlayerRemoving:Connect(function(player)
local dataStore = dataStoreModule.find("Player", player.UserId)
if dataStore == nil then return end
dataStore:Destroy()
end)Setup Player Data Example
local dataStore = dataStoreModule.find("Player", player.UserId)
if dataStore == nil then return end
if dataStore.State ~= true then return end -- make sure the session is open or the value will never get saved
dataStore.Value.Level += 1Developer Products Example
local marketplaceService = game:GetService("MarketplaceService")
local dataStoreModule = require(11671168253)
marketplaceService.ProcessReceipt = function(receiptInfo)
local dataStore = dataStoreModule.find("Player", receiptInfo.PlayerId)
if dataStore == nil then return Enum.ProductPurcha...Version: 0.6 [BETA]
SuphisDataStoreModule BETA
Before I start, this is not mine but Suphi#3388 module, I got permission to put this up in github. Here is the discord: https://discord.gg/B3zmjPVBce
Features
- Session locking Prevents multiple servers from opening the same datastore key
- Auto save Automatically saves cached data to the datastore based on the saveinterval property
- Bind To Close Automatically saves, closes and destroys all sessions when server starts to close
- Reconcile Fills in missing values from the template into the value property
- Compression Compress data to reduce character count
- Save throttling Impossible for save requests dropping
- Multiple script support Safe to interact with the same datastore object from multiple scripts
- Task batching Tasks will be batched togever when possible
- Direct value access Access the datastore value directly, module will never tamper with your data
- Easy to use Simple and elegant
- Lightweight No RunService events and no while do loops 100% event based
Suphi's DataStore Module vs ProfileService
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.
So 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.
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
ProfileService does many DeepTableCopys where it loops your data and copys all values into new tables which can be an exspensive operation for very large tables Suphi's DataStore Module was designed in such a way that it never has to DeepTableCopy
ProfileService forces you to save your data as a table where Suphi's DataStore Module lets you set the datastore value directly where you can save numbers, strings, booleans or tables
Download
Go to releases and download the version(latest stable version prefered), or copy the code in the repo. Alternatively without downloading you can do:
local dataStoreModule = require(11671168253)Current version: 0.6 [BETA]
Contructors
new(name: string, scope: string, key: string)Returns previously created session else a new session
new(name: string, key: string)Returns previously created session else a new session
find(name: string, scope: string, key: string)Returns previously created session else nil
find(name: string, key: string)Returns previously created session else nil
Properties
Value Variant nilValue of datastore
Metadata table {}Metadata associated with the key
UserIds table {}An array of UserIds associated with the key
SaveInterval number 30Interval in seconds the datastore will automatically save (set to 0 to disable automatic saving)
LockInterval number 30Interval in seconds the memorystore will update the session lock
SaveBeforeClose boolean trueAutomatically save the data when the session is closed or destroyed
Id string "Name/Scope/Key" READ ONLYUnique identifying string
Key string "Key" READ ONLYKey used for the datastore
State string "Closed" READ ONLYCurrent state of the session
CreatedTime number 0 READ ONLYNumber of milliseconds from epoch to when the datastore was created
UpdatedTime number 0 READ ONLYNumber of milliseconds from epoch to when the datastore was updated
SavedTime number os.clock() READ ONLYCPU time of when the datastore was last saved
Version string "" READ ONLYUnique identifying string of the current datastore save
CompressedValue string "" READ ONLYCompressed string that is updated before every save if compression is enabled by setting dataStore.Metadata.Compress = {["Level"] = 2, ["Decimals"] = 3, ["Safety"] = true}
Level = 1 (allows mixed tables), Level = 2 (does not allow mixed tables but compresses arrays better), Decimals = amount of decimal places saved, Safety = replace delete character from strings
Events
StateChanged(state: string) RBXScriptSignalFires after state property has changed
Saving() SignalFires just before the data is about to save
Methods
Open(default: Variant) nil/stringTries to open the session, optional template parameter will be reconciled onto the value, returns errorType and errorMessage if fails
Load(default: Variant) nil/stringLoads the datastore value without the need to open the session, optional template parameter will be reconciled onto the value, returns errorType and errorMessage if fails
Save() nil/stringForce save the current value to the datastore, returns errorType and errorMessage if fails
Close(save: boolean) nil/stringCloses the session, returns errorType and errorMessage if session is destroyed
Destroy(save: boolean) nilCloses and destroys the session, destroyed sessions will be locked
Clone() VariantClones the value property
Reconcile(template: Variant) nilFills in missing values from the template into the value property
Usage() number numberHow much datastore has been used, returns the character count and the second number is a number scaled from 0 to 1 [0 = 0% , 0.5 = 50%, 1 = 100%, 1.5 = 150%]
Simple Example
-- Require the ModuleScript
local dataStoreModule = require(11671168253)
-- Find or create a datastore object
local dataStore = dataStoreModule.new("Name", "Key")
-- Connect a function to the StateChanged event and print to the output when the state changes
dataStore.StateChanged:Connect(function(state)
if state == nil then print("Destroyed", dataStore.Id) end
if state == false then print("Closed ", dataStore.Id) end
if state == true then print("Open ", dataStore.Id) end
end)
-- Open the datastore session
local errorType, errorMessage = dataStore:Open()
-- If the session fails to open lets print why and return
if errorType ~= nil then print(dataStore.Id, errorType, errorMessage) return end
-- Set the datastore value
dataStore.Value = "Hello world!"
-- Save, close and destroy the session
dataStore:Destroy()Load Example
local dataStoreModule = require(11671168253)
local dataStore = dataStoreModule.new("Name", "Key")
-- load the value from the datastore
local errorType, errorMessage = dataStore:Load()
if errorType ~= nil then print(dataStore.Id, errorType, errorMessage) return end
-- WARNING this value might be out of date use open instead if you need the latest value
print(dataStore.Value)
-- as we never opened the session it will instantly destroy without saving or closing
dataStore:Destroy()Setup Player Data Example
local dataStoreModule = require(11671168253)
local template = {
Level = 0,
Inventory = {},
DeveloperProducts = {},
}
game.Players.PlayerAdded:Connect(function(player)
local dataStore = dataStoreModule.new("Player", player.UserId)
local errorType = dataStore:Open(template) -- reconcile with template
if errorType ~= nil then print(player.Name, "failed to open") end
end)
game.Players.PlayerRemoving:Connect(function(player)
local dataStore = dataStoreModule.find("Player", player.UserId)
if dataStore == nil then return end
dataStore:Destroy()
end)Setup Player Data Example
local dataStore = dataStoreModule.find("Player", player.UserId)
if dataStore == nil then return end
if dataStore.State ~= true then return end -- make sure the session is open or the value will never get saved
dataStore.Value.Level += 1Developer Products Example
local marketplaceService = game:GetService("MarketplaceService")
local dataStoreModule = require(11671168253)
marketplaceService.ProcessReceipt = function(receiptInfo)
local dataStore = dataStoreModule.find("Player", receiptInfo.PlayerId)
if dataStore == nil then return Enum.ProductPurchaseDecision.NotProcessedYet end
if dataStore.State ~= true then return Enum.ProductPurchaseDecision.NotProcessedYet end
--...