Suphi's DataStore Module

Suphi's DataStore Module

Download | Video Tutorial | Discord Server

Features

Session locking            "Prevents another object from opening the same datastore key while its opened by another object"
Cross Server Communication "Easily use MemoryStoreQueue to send data to the session owner"
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"
Multiple Script Support    "Safe to interact with the same datastore object from multiple scripts"
Task Batching              "Tasks will be batched together when possible"
Direct Value Access        "Access the datastore value directly, module will never tamper with your data and will never leave any data in your datastore or memorystore"
Easy To Use                "Simple and elegant"
Lightweight                "No RunService events and no while true do loops 100% event based"

Support My Work

If you liked my work and want to donate to me you can do so here


SourceCode

You can get the sourcecode to this module here

Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.


Documentation

CONSTRUCTOR

new(name: string, scope: string, key: string)
"Returns previously created datastore session else a new session"
new(name: string, key: string)
"Returns previously created datastore session else a new session"
hidden(name: string, scope: string, key: string)
"Returns a new session that cannot be returned by new or find"
hidden(name: string, key: string)
"Returns a new session that cannot be returned by new or find"
find(name: string, scope: string, key: string)
"Returns previously created datastore session else nil"
find(name: string, key: string)
"Returns previously created datastore session else nil"
Response Success Saved Locked State Error
"List of responses that acts like a enum"

PROPERTIES

Value  any  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)"
SaveDelay  number  0
"Delay between saves"
LockInterval  number  60
"Interval in seconds the memorystore will update the session lock"
LockAttempts  number  5
"How many times the memorystore needs to fail before the session closes"
SaveOnClose  boolean  true
"Automatically save the data when the session is closed or destroyed"
Id  string  "Name/Scope/Key"  READ ONLY
"Identifying string"
UniqueId  string  "8-4-4-4-12"  READ ONLY
"Unique identifying string"
Key  string  "Key"  READ ONLY
"Key used for the datastore"
State  boolean?  false  READ ONLY
"Current state of the session [nil = Destroyed] [false = Closed] [true = Open]"
Hidden  boolean  false/true  READ ONLY
"Set to true if this session was created by the hidden constructor"
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 (not updated while session is open)"
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: boolean?, dataStore: DataStore)  Signal
"Fires when the State property has changed"
Saving(value: any, dataStore: DataStore)  Signal
"Fires just before the value is about to save"
Saved(response: string, responseData: any, dataStore: DataStore)  Signal
"Fires after a save attempt"
AttemptsChanged(attemptsRemaining: number, dataStore: DataStore)  Signal
"Fires when the AttemptsRemaining property has changed"
ProcessQueue(id: string, values: array, dataStore: DataStore)  Signal
"Fires when state = true and values detected inside the MemoryStoreQueue"

METHODS

Open(template: any)  string any
"Tries to open the session, optional template parameter will be reconciled onto the value"
Read(template: any)  string any
"Reads the datastore value without the need to open the session, optional template parameter will be reconciled onto the value"
Save()  string any
"Force save the current value to the datastore"
Close()  string any
"Closes the session"
Destroy()  string any
"Closes and destroys the session, destroyed sessions will be locked"
Queue(value: any, expiration: number?, priority: number?)  string any
"Adds a value to the MemoryStoreQueue expiration default (604800 seconds / 7 days), max (3888000 seconds / 45 days)"
Remove(id: string)  string any
"Remove values from the MemoryStoreQueue"
Clone()  any
"Clones the value property"
Reconcile(template: any) 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%]"

Examples

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 response, responseData = dataStore:Open()

-- If the session fails to open lets print why and return
if response ~= "Success" then print(dataStore.Id, response, responseData) return end

-- Set the datastore value
dataStore.Value = "Hello World!"

-- Save, close and destroy the session
dataStore:Destroy()

PLAYER DATA EXAMPLE

local DataStoreModule = require(11671168253)

local template = {
    Level = 0,
    Coins = 0,
    Inventory = {},
    DeveloperProducts = {},
}

local function StateChanged(state, dataStore)
    while dataStore.State == false do -- Keep trying to re-open if the state is closed
        if dataStore:Open(template) ~= "Success" then task.wait(6) end
    end
end

game.Players.PlayerAdded:Connect(function(player)
    local dataStore = DataStoreModule.new("Player", player.UserId)
    dataStore.StateChanged:Connect(StateChanged)
    StateChanged(dataStore.State, dataStore)
end)

game.Players.PlayerRemoving:Connect(function(player)
    local dataStore = DataStoreModule.find("Player", player.UserId)
    if dataStore ~= nil then dataStore:Destroy() end -- If the player leaves datastore object is destroyed allowing the retry loop to stop
end)

SET 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 productId = tostring(receiptInfo.ProductId)

    -- Add 1 to to the productId in the DeveloperProducts table
    dataStore.Value.DeveloperProducts[productId] = (dataStore.Value.DeveloperProducts[productId] or 0) + 1

    if dataStore:Save() == "Saved" then
        -- there was no errors lets grant the purchase
        return Enum.ProductPurchaseDecision.PurchaseGranted
    else
        -- the save failed lets make sure to remove the product or it might get saved in the next save interval
        dataStore.Value.DeveloperProducts[productId] -= 1
        return Enum.ProductPurchaseDecision.NotProcessedYet
    end
end

QUEUE EXAMPLE

-- Server A
local dataStore = DataStoreModule.new("Name", "Key")

if dataStore:Queue("Some random data")  ~= "Success" then print("Failed to queue") end
if dataStore:Queue({1, 6, 3, 8})  ~= "Success" then print("Failed to queue") end

dataStore:Destroy()
-- Server B
local dataStore = DataStoreModule.new("Name", "Key")

dataStore.ProcessQueue:Connect(function(id, values, dataStore)
    if dataStore:Remove(id) ~= "Success" then return end
    for index, value in values do print(value) end
end)

dataStore:Open() -- must be open to ProcessQueue

CUSTOM FUNCTIONS

local DataStoreModule = require(11671168253)

local function Set(dataStore, key, value)
    if dataStore.State ~= true then return false end 
    dataStore.Value[key] = value
    return true
end

local function Increment(dataStore, key, delta)
    if dataStore.State ~= true then return false end 
    dataStore.Value[key] += delta
    return true
end

game.Players.PlayerAdded:Connect(function(player)
    local dataStore = DataStoreModule.new("Player", player.UserId)
    dataStore.Set = Set
    dataStore.Increment = Increment
end)
local dataStore = DataStoreModule.find("Player", player.UserId)
if dataStore == nil then error("DataStore not found") end
local success = dataStore:Set("Key", 100)
local success = dataStore:Increment("Key", 2)

Other Projects

Infinite Terrain
Suphi’s DataStore Module
Infinite Scripter
Mesh Editor
Toggle Decomposition Geometry
Tag Explorer
Suphi’s RemoteFunction Module

187 Likes

do you know why would we use this over ProfileService/DataStore2?

5 Likes

I will have more videos coming soon that will go into detail between the differences on how each module works

but the main difference between Suphi's DataStore Module and ProfileService is that Suphi's DataStore Module uses the MemoryStore to session lock where ProfileService saves os.time() into the DataStore

and DataStore2 is different because its the only one that uses OrderedDataStore + DataStore to save

other then that they have all been designed differently so if you watch the video above you can see how to script using Suphi's DataStore Module and will give you a good idea of the workflow

22 Likes

does this datastore module use caching?

Edit: is this written in strict Luau? cant see on mobile without github repo

5 Likes

Yes you can set the SaveInterval property to adjust how often the cache is saved

While the module itself is not strict your scripts that require the module can be strict as all the types have been setup correctly

The module is also sandboxed meaning that it won’t let you make a mistake if you set a incorrect property it will give a error

So doing something like dataStore.SaveInterval = -1 will give a error but doing dataStore.SaveInterval = 10 won’t give you a error

4 Likes

Going to give it a try for my next project. Could you upload it to a repo? Thanks <3

2 Likes

There is someone in my discord that uploads it to GitHub but is currently out of date I personally will not be maintain a repo at this time so if you want to make sure you have the latest version you will need to get it from the toolbox

1 Like

what if you want to see the code on mobile or on a banned account???

3 Likes

since this uses memorystoreservice, can you make a datastore on the server or nope, meaning that lets say i wanna make a global shop that updates each time a player buys something, can i do that cross server communication with it, heres another example:

my game has a shop, its a global shop, and each time a player buys something it needs to update on all the existing servers and update it, simillar to blox fruits just that in my game it updates pretty frequently, and i need to make it save the shop even after the servers were shutdown, for example if the shop had a fruit called Apple, after the shutdown it will save the fruit there, note that it is not player specific datastore that i need, its a server type datastore that can communicate through all servers

2 Likes

Unfortunately that won’t be possible unless you download it on your computer then you can put it in your phone or upload it to your own github

5 Likes

Hey suphi i would use this module if my game didn’t release but now i cant because of data migration etc…Will use it in new project !

4 Likes

SDM only uses the memorystoreservice temporarily while the session is locked and when sending data from one server to the next

The module does not care if your using it for a player or for global shop it will except any name scope and key so your free to use the module for anything you like


but I would not recommend using SDM for a global shop

your best option is to simply use a datastore without caching and always use UpdateAsync to update the shops datastore when making changes you can batch changes to reduce requests

3 Likes

I hope you have fun working with it if you have any problems you can tell me and I’ll do my best to help you

That is also another benefit of SDM it does not tamper with your data so it’s very easy to migrate to or to migrate away

2 Likes

In my case I d’ont think it will works because of how ProfileService save data

2 Likes

Suphi’s DataStore Module 1.1 is now live

Bug fix
Saved response will now return dataStore.Value as responseData instead of nil
Added Saved event

This is a drop in replacement when updating from version 1.0 to 1.1 you don’t need to change anything in your scripts

4 Likes

Pretty cool. I suggest using gitbook, since it makes the documentation look so much better than this.

2 Likes

Thanks for sharing this resource! :slightly_smiling_face: I wonder could its data loss prevention be used for global leaderboards? For example, could I create a global top 100 players by coins leaderboard by storing coins using this module and when the stored coins change also store them in an OrderedDataStore? Is that a good idea? :sweat_smile:

1 Like

You can find documentation inside the discord channel that you might feel looks better

or if you like you can add the documentation to gitbook if you like

1 Like

So you would save the coins inside the players data using Suphi’s DataStore Module

and when the coin amount changes you will also save the coins in a OrderedDataStore

then you can use the OrderedDataStore to get the top 100

if your coins values changes to quickly you might be setting the OrderedDataStore to quickly. If that’s the case you can use the saving event to set the OrderedDataStore

3 Likes

about that, not everyone have discord, and i feel like using gitbook is better like what @Bloxxy213_DVL said

1 Like