Is this data module I made good and would work well for bigger use?

I made a small data store module and I wanted to know if this would be a good way of saving a players data efficiently

--[[

DOCUMENTATION:

LightData.SetDataStore(Name, Scope) | Line 166
	Sets the datastore name and scope for LightData.
	Name: string
	Scope: string

LightData.SetDataTemplate(Template) | Line 170
	Sets the data template for LightData.
	Template: table
	
LightData:GetData(player) | Line 176
	Gets the player's data.
	player: Player
	returns: table
	
LightData:SaveData(player, remove) | Line 186
	Saves the player's data.
	player: Player
	remove: boolean

]]--

-- # Services
local Players = game:GetService('Players')
local DataStoreService = game:GetService('DataStoreService')
local ReplicatedStorage = game:GetService('ReplicatedStorage')
local RunService = game:GetService('RunService')
local HttpService = game:GetService('HttpService')

-- # Light Data
local LightData = {
	DataStore = nil;
	PlayerData = {};
	DataTemplate = {};
}

-- # Cache
local Cache = {
	Cache = {}
}

-- # Cache Functions
function Cache:Add(player: Player, Data: {})
	Cache.Cache[player.UserId] = Data
	
	warn(`{player.UserId}s Data Has Been Added To Cache.`)
end

function Cache:Clear()
	local function doSave(UserId, Data)
		while true do
			local succ, err = pcall(function()
				local EncodedData = HttpService:JSONEncode(Data)
				LightData.DataStore:SetAsync(tostring(UserId), EncodedData)
			end)

			if succ then
				break
			end

			task.wait(3)
		end

		Cache.Cache[UserId] = nil
	end
	
	for UserId, Data in pairs(Cache.Cache) do
		task.spawn(doSave(UserId, Data))
	end
end

function Cache:ClearOne(UserId: number)
	local function doSave(UserId, Data)
		while true do
			local succ, err = pcall(function()
				local EncodedData = HttpService:JSONEncode(Data)
				LightData.DataStore:SetAsync(tostring(UserId), EncodedData)
			end)

			if succ then
				break
			end

			task.wait(3)
		end

		Cache.Cache[UserId] = nil
	end
	
	if Cache.Cache[UserId] then
		doSave(UserId, Cache.Cache[UserId])
	end
end

-- # Utils
local function DeepCopyTable(t)
	local copy = {}
	for key, value in pairs(t) do
		if type(value) == "table" then
			copy[key] = DeepCopyTable(value)
		else
			copy[key] = value
		end
	end
	return copy
end

local function ReconcileTable(target, template)
	for key, value in pairs(template) do
		if type(key) == "string" then
			if target[key] == nil then
				if type(value) == "table" then
					target[key] = DeepCopyTable(value)
				else
					target[key] = value
				end
			elseif type(target[key]) == "table" and type(value) == "table" then
				ReconcileTable(target[key], value)
			end
		end
	end
end

-- # Private Functions
local function LoadData(player: Player): {}
	assert(LightData.DataStore, 'You must create a data store to load player data.')
	
	local Succ, Data = pcall(function()
		return LightData.DataStore:GetAsync(tostring(player.UserId))
	end)

	if Succ then
		Data = HttpService:JSONDecode(Data)
		
		ReconcileTable(Data, LightData.DataTemplate)
		return Data
	end
	
	return LightData.DataTemplate
end

local function SaveData(player: Player, Data: {}, Remove: boolean)
	assert(LightData.DataStore, 'You must create a data store to save player data.')
	assert(typeof(Data) == 'table', 'Data must be a table to save.')
	
	local Succ, err = pcall(function()
		local EncodedData = HttpService:JSONEncode(Data)
		LightData.DataStore:SetAsync(tostring(player.UserId), EncodedData)
	end)
	
	if not Succ then
		Cache:Add(player, Data)
	end
	
	if Remove ~= nil and Remove == true then
		if LightData.PlayerData[player] ~= nil then
			LightData.PlayerData[player] = nil
		end
	end
end

-- # Global Functions
function LightData.SetDataStore(Name: string, Scope: string)
	LightData.DataStore = DataStoreService:GetDataStore(Name, Scope)
end

function LightData.SetDataTemplate(Template: {})
	assert(typeof(Template) == 'table', 'Given template must be a table.')
	
	LightData.DataTemplate = Template
end

function LightData.GetData(player: Player)
	local PlayerData = LightData.PlayerData[player] or LoadData(player)
	
	if typeof(PlayerData) == 'table' then
		return PlayerData
	end
	
	return nil
end

function LightData.SaveData(player: Player, Remove: boolean)
	local PlayerData = LightData.GetData(player)
	
	if PlayerData ~= nil then
		SaveData(player, PlayerData, Remove)
	end
end

-- # Misc
game:BindToClose(function()
	Cache:Clear() -- > Make sure all data stored in the cache gets saved.
end)

-- # return
return LightData

Looks pretty good. From a quick glance, there’s a couple of things I wanted to ask/check:

  1. Why are you using a colon for some of the module functions?
    If you aren’t needing the item itself, there’s no point in doing it.
    You’re just doing Cache.Cache, etc. where you just index the cache object.
    I’d recommend either changing it to self or just using a dot.
  2. Use UpdateAsync. It allows for comparison code, and has other safety features that SetAsync does not. SetAsync should be used with caution and only when you are making a safer save, e.g. a leaderboard, which should just mimic data.