Skip to content

Releases: AlternativeLua/SuphisDataStoreModule

Version: 1.3

11 Nov 12:47
6364395

Choose a tag to compare

Update 1.3

  • added [any]: any to the datastore type

v1.2

24 Jun 14:53
5ae8187

Choose a tag to compare

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

14 Jun 20:07

Choose a tag to compare

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]

02 May 18:18
89474d1

Choose a tag to compare

Version: 0.15 [BETA] Pre-release
Pre-release

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]

29 Apr 10:23
96c4ef3

Choose a tag to compare

Version: 0.14 [BETA] Pre-release
Pre-release

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]

06 Mar 19:53
1e5230d

Choose a tag to compare

Version: 0.10 [BETA] Pre-release
Pre-release

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 ONLY

Unique 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  nil

Value of datastore

Metadata  table  {}

Metadata associated with the key

UserIds  table  {}

An array of UserIds associated with the key

SaveInterval number  60

Interval in seconds the datastore will automatically save (set to 0 to disable automatic saving)

LockInterval  number  60

Interval in seconds the memorystore will update the session lock

FailsBeforeClose  number  5

How many times the memorystore needs to fail before the session closes

SaveWhenClosing boolean  true

Automatically save the data when the session is closed or destroyed

Id  string  "Name/Scope/Key"  READ ONLY

Unique identifying string

Key  string  "Key"  READ ONLY

Key used for the datastore

State  string  "Closed"  READ ONLY

Current state of the session

AttemptsRemaining  number  0  READ ONLY

How many memorystore attempts remaining before the session closes

CreatedTime  number  0  READ ONLY

Number of milliseconds from epoch to when the datastore was created

UpdatedTime  number  0  READ ONLY

Number of milliseconds from epoch to when the datastore was updated

Version  string  ""  READ ONLY

Unique identifying string of the current datastore save

CompressedValue  string  ""  READ ONLY

Compressed 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)  RBXScriptSignal

Fires after state property has changed

Saving(value: Variant)  Signal

Fires just before the data is about to save

Methods

Open(default: Variant)  nil/string  nil/string

Tries 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/string

Loads 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/string

Force save the current value to the datastore, returns errorType and errorMessage if fails

Close(save: boolean)  nil/string  nil/string

Closes the session, returns errorType and errorMessage if session is destroyed

Destroy()  nil

Closes and destroys the session, destroyed sessions will be locked

Clone()  Variant

Clones the value property

Reconcile(template: Variant)  nil

Fills in missing values from the template into the value property

Usage()  number  number

How 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 += 1

Developer Products Example

local marketplaceService = game:GetSer...
Read more

Version: 0.9 [BETA]

08 Jan 16:20
2efab65

Choose a tag to compare

Version: 0.9 [BETA] Pre-release
Pre-release

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 ONLY

Unique 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  nil

Value of datastore

Metadata  table  {}

Metadata associated with the key

UserIds  table  {}

An array of UserIds associated with the key

SaveInterval number  60

Interval in seconds the datastore will automatically save (set to 0 to disable automatic saving)

LockInterval  number  60

Interval in seconds the memorystore will update the session lock

SaveWhenClosing boolean  true

Automatically save the data when the session is closed or destroyed

Id  string  "Name/Scope/Key"  READ ONLY

Unique identifying string

Key  string  "Key"  READ ONLY

Key used for the datastore

State  string  "Closed"  READ ONLY

Current state of the session

CreatedTime  number  0  READ ONLY

Number of milliseconds from epoch to when the datastore was created

UpdatedTime  number  0  READ ONLY

Number of milliseconds from epoch to when the datastore was updated

Version  string  ""  READ ONLY

Unique identifying string of the current datastore save

CompressedValue  string  ""  READ ONLY

Compressed 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)  RBXScriptSignal

Fires after state property has changed

Saving(value: Variant)  Signal

Fires just before the data is about to save

Methods

Open(default: Variant)  nil/string  nil/string

Tries 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/string

Loads 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/string

Force save the current value to the datastore, returns errorType and errorMessage if fails

Close(save: boolean)  nil/string  nil/string

Closes the session, returns errorType and errorMessage if session is destroyed

Destroy()  nil

Closes and destroys the session, destroyed sessions will be locked

Clone()  Variant

Clones the value property

Reconcile(template: Variant)  nil

Fills in missing values from the template into the value property

Usage()  number  number

How 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 += 1

Developer 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 ...
Read more

Version: 0.8 [BETA]

16 Dec 18:44
5db730d

Choose a tag to compare

Version: 0.8 [BETA] Pre-release
Pre-release

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  nil

Value of datastore

Metadata  table  {}

Metadata associated with the key

UserIds  table  {}

An array of UserIds associated with the key

SaveInterval number  60

Interval in seconds the datastore will automatically save (set to 0 to disable automatic saving)

LockInterval  number  60

Interval in seconds the memorystore will update the session lock

SaveWhenClosing boolean  true

Automatically save the data when the session is closed or destroyed

Id  string  "Name/Scope/Key"  READ ONLY

Unique identifying string

Key  string  "Key"  READ ONLY

Key used for the datastore

State  string  "Closed"  READ ONLY

Current state of the session

CreatedTime  number  0  READ ONLY

Number of milliseconds from epoch to when the datastore was created

UpdatedTime  number  0  READ ONLY

Number of milliseconds from epoch to when the datastore was updated

Version  string  ""  READ ONLY

Unique identifying string of the current datastore save

CompressedValue  string  ""  READ ONLY

Compressed 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)  RBXScriptSignal

Fires after state property has changed

Saving(value: Variant)  Signal

Fires just before the data is about to save

Methods

Open(default: Variant)  nil/string

Tries to open the session, optional template parameter will be reconciled onto the value, returns errorType and errorMessage if fails

Load(default: Variant)  nil/string

Loads 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

Force save the current value to the datastore, returns errorType and errorMessage if fails

Close(save: boolean)  nil/string

Closes the session, returns errorType and errorMessage if session is destroyed

Destroy()  nil

Closes and destroys the session, destroyed sessions will be locked

Clone()  Variant

Clones the value property

Reconcile(template: Variant)  nil

Fills in missing values from the template into the value property

Usage()  number  number

How 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 += 1

Developer 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...
Read more

Version: 0.7 [BETA]

06 Dec 19:26
63c300e

Choose a tag to compare

Version: 0.7 [BETA] Pre-release
Pre-release
  • 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  nil

Value of datastore

Metadata  table  {}

Metadata associated with the key

UserIds  table  {}

An array of UserIds associated with the key

SaveInterval  number  30

Interval in seconds the datastore will automatically save (set to 0 to disable automatic saving)

LockInterval  number  30

Interval in seconds the memorystore will update the session lock

SaveBeforeClose  boolean  true

Automatically save the data when the session is closed or destroyed

Id  string  "Name/Scope/Key"  READ ONLY

Unique identifying string

Key  string  "Key"  READ ONLY

Key used for the datastore

State  string  "Closed"  READ ONLY

Current state of the session

CreatedTime  number  0  READ ONLY

Number of milliseconds from epoch to when the datastore was created

UpdatedTime  number  0  READ ONLY

Number of milliseconds from epoch to when the datastore was updated

Version  string  ""  READ ONLY

Unique identifying string of the current datastore save

CompressedValue  string  ""  READ ONLY

Compressed 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)  RBXScriptSignal

Fires after state property has changed

Saving(value: Variant)  Signal

Fires just before the data is about to save

Methods

Open(default: Variant)  nil/string

Tries to open the session, optional template parameter will be reconciled onto the value, returns errorType and errorMessage if fails

Load(default: Variant)  nil/string

Loads 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

Force save the current value to the datastore, returns errorType and errorMessage if fails

Close(save: boolean)  nil/string

Closes the session, returns errorType and errorMessage if session is destroyed

Destroy(save: boolean)  nil

Closes and destroys the session, destroyed sessions will be locked

Clone()  Variant

Clones the value property

Reconcile(template: Variant)  nil

Fills in missing values from the template into the value property

Usage()  number  number

How 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 += 1

Developer 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...
Read more

Version: 0.6 [BETA]

30 Nov 17:43
8552761

Choose a tag to compare

Version: 0.6 [BETA] Pre-release
Pre-release

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  nil

Value of datastore

Metadata  table  {}

Metadata associated with the key

UserIds  table  {}

An array of UserIds associated with the key

SaveInterval  number  30

Interval in seconds the datastore will automatically save (set to 0 to disable automatic saving)

LockInterval  number  30

Interval in seconds the memorystore will update the session lock

SaveBeforeClose  boolean  true

Automatically save the data when the session is closed or destroyed

Id  string  "Name/Scope/Key"  READ ONLY

Unique identifying string

Key  string  "Key"  READ ONLY

Key used for the datastore

State  string  "Closed"  READ ONLY

Current state of the session

CreatedTime  number  0  READ ONLY

Number of milliseconds from epoch to when the datastore was created

UpdatedTime  number  0  READ ONLY

Number of milliseconds from epoch to when the datastore was updated

SavedTime  number  os.clock()  READ ONLY

CPU time of when the datastore was last saved

Version  string  ""  READ ONLY

Unique identifying string of the current datastore save

CompressedValue  string  ""  READ ONLY

Compressed 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)  RBXScriptSignal

Fires after state property has changed

Saving()  Signal

Fires just before the data is about to save

Methods

Open(default: Variant)  nil/string

Tries to open the session, optional template parameter will be reconciled onto the value, returns errorType and errorMessage if fails

Load(default: Variant)  nil/string

Loads 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

Force save the current value to the datastore, returns errorType and errorMessage if fails

Close(save: boolean)  nil/string

Closes the session, returns errorType and errorMessage if session is destroyed

Destroy(save: boolean)  nil

Closes and destroys the session, destroyed sessions will be locked

Clone()  Variant

Clones the value property

Reconcile(template: Variant)  nil

Fills in missing values from the template into the value property

Usage()  number  number

How 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 += 1

Developer 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

    --...
Read more