local Y=require((game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")):WaitForChild("\078\101\118\101\114\109\111\114\101"))local w=Y("\068\097\116\097\083\116\111\114\101\068\101\108\101\116\101\084\111\107\101\110")local T=Y("\068\097\116\097\083\116\111\114\101\080\114\111\109\105\115\101\115")local t=Y("\068\097\116\097\083\116\111\114\101\083\116\097\103\101")local M=Y("\077\097\105\100")local p=Y("\080\114\111\109\105\115\101")local f=Y("\083\105\103\110\097\108")local y=false local u=300 local B=15 local Z=20 local U=setmetatable({},t)U.ClassName="\068\097\116\097\083\116\111\114\101"U.__index=U function U.new(Y,w)local T=setmetatable(t.new(),U)T._key=w or error("\078\111 \107\101\121")T._robloxDataStore=Y or error("\078\111 \114\111\098\108\111\120\068\097\116\097\083\116\111\114\101")T.Saving=f.new()spawn(function()while T.Destroy do for Y=1,B,1 do wait(u/B)if not T.Destroy then break end end if not T.Destroy then break end wait(math.random(1,Z))if not T.Destroy then break end T:Save()end end)return T end function U.DidLoadFail(Y)if not Y._loadPromise then return false end if Y._loadPromise:IsRejected()then return true end return false end function U.PromiseLoadSuccessful(Y)return(Y._maid:GivePromise(Y:_promiseLoad())):Then(function()return true end,function()return false end)end function U.Save(Y)if Y:DidLoadFail()then warn("\091\068\097\116\097\083\116\111\114\101\093 - \078\111\116 \115\097\118\105\110\103\044 \102\097\105\108\101\100 \116\111 \108\111\097\100")return p.rejected("\076\111\097\100 \110\111\116 \115\117\099\099\101\115\115\102\117\108\044 \110\111\116 \115\097\118\105\110\103")end if not Y:HasWritableData()then if y then print("\091\068\097\116\097\083\116\111\114\101\046\083\097\118\101\093 - \078\111\116 \115\097\118\105\110\103\044 \110\111\116\104\105\110\103 \115\116\097\103\101\100")end return p.resolved(nil)end return Y:_saveData(Y:GetNewWriter())end function U.Load(T,Y,w)return(T:_promiseLoad()):Then(function(T)if T[Y]==nil then return w else return T[Y]end end)end function U._saveData(t,Y)local f=M.new()local u=p.new()u:Resolve(f:GivePromise(T.UpdateAsync(t._robloxDataStore,t._key,function(T)if u:IsRejected()then return nil end T=Y:WriteMerge(T or{})assert(T~=w,"\067\097\110\110\111\116 \100\101\108\101\116\101 \102\114\111\109 \085\112\100\097\116\101\065\115\121\110\099")if y then print("\091\068\097\116\097\083\116\111\114\101\093 - \087\114\105\116\105\110\103",(game:GetService("\072\116\116\112\083\101\114\118\105\099\101")):JSONEncode(T))end return T end,function(Y)warn("\091\068\097\116\097\083\116\111\114\101\093 - \070\097\105\108\101\100 \116\111 \085\112\100\097\116\101\065\115\121\110\099 \100\097\116\097",Y)return p.rejected(Y)end)))t._maid._saveMaid=f t.Saving:Fire(u)return u end function U._promiseLoad(Y)if Y._loadPromise then return Y._loadPromise end Y._loadPromise=Y._maid:GivePromise((T.GetAsync(Y._robloxDataStore,Y._key)):Then(function(Y)if Y==nil then return{}elseif type(Y)=="\116\097\098\108\101"then return Y else return p.rejected("\070\097\105\108\101\100 \116\111 \108\111\097\100 \100\097\116\097\046 \087\114\111\110\103 \116\121\112\101 \'"..(type(Y).."\'"))end end,function(Y)warn("\091\068\097\116\097\083\116\111\114\101\093 - \070\097\105\108\101\100 \116\111 \071\101\116\065\115\121\110\099 \100\097\116\097",Y)return p.rejected(Y)end))return Y._loadPromise end return U
--- Wraps the datastore object to provide async cached loading and saving
-- @classmod DataStore

local require = require(game:GetService("ReplicatedStorage"):WaitForChild("Nevermore"))

local DataStoreDeleteToken = require("DataStoreDeleteToken")
local DataStorePromises = require("DataStorePromises")
local DataStoreStage = require("DataStoreStage")
local Maid = require("Maid")
local Promise = require("Promise")
local Signal = require("Signal")

local DEBUG_WRITING = false

local AUTO_SAVE_TIME = 60*5
local CHECK_DIVISION = 15
local JITTER = 20 -- Randomly assign jitter so if a ton of players join at once we don't hit the datastore at once

local DataStore = setmetatable({}, DataStoreStage)
DataStore.ClassName = "DataStore"
DataStore.__index = DataStore

function DataStore.new(robloxDataStore, key)
	local self = setmetatable(DataStoreStage.new(), DataStore)

	self._key = key or error("No key")
	self._robloxDataStore = robloxDataStore or error("No robloxDataStore")

	self.Saving = Signal.new() -- :Fire(promise)

	spawn(function()
		while self.Destroy do
			for _=1, CHECK_DIVISION do
				wait(AUTO_SAVE_TIME/CHECK_DIVISION)
				if not self.Destroy then
					break
				end
			end

			if not self.Destroy then
				break
			end

			-- Apply additional jitter on auto-save
			wait(math.random(1, JITTER))

			if not self.Destroy then
				break
			end

			self:Save()
		end
	end)

	return self
end

function DataStore:DidLoadFail()
	if not self._loadPromise then
		return false
	end

	if self._loadPromise:IsRejected() then
		return true
	end

	return false
end

function DataStore:PromiseLoadSuccessful()
	return self._maid:GivePromise(self:_promiseLoad()):Then(function()
		return true
	end, function()
		return false
	end)
end

-- Saves all stored data
function DataStore:Save()
	if self:DidLoadFail() then
		warn("[DataStore] - Not saving, failed to load")
		return Promise.rejected("Load not successful, not saving")
	end

	if not self:HasWritableData() then
		-- Nothing to save, don't update anything
		if DEBUG_WRITING then
			print("[DataStore.Save] - Not saving, nothing staged")
		end
		return Promise.resolved(nil)
	end

	return self:_saveData(self:GetNewWriter())
end

-- Loads data. This returns the originally loaded data.
function DataStore:Load(name, defaultValue)
	return self:_promiseLoad()
		:Then(function(data)
			if data[name] == nil then
				return defaultValue
			else
				return data[name]
			end
		end)
end

function DataStore:_saveData(writer)
	local maid = Maid.new()

	local promise = Promise.new()
	promise:Resolve(maid:GivePromise(DataStorePromises.UpdateAsync(self._robloxDataStore, self._key, function(data)
		if promise:IsRejected() then
			-- Cancel if we have another request
			return nil
		end

		data = writer:WriteMerge(data or {})
		assert(data ~= DataStoreDeleteToken, "Cannot delete from UpdateAsync")

		if DEBUG_WRITING then
			print("[DataStore] - Writing", game:GetService("HttpService"):JSONEncode(data))
		end

		return data
	end, function(err)
		-- Might be caused by Maid rejecting state
		warn("[DataStore] - Failed to UpdateAsync data", err)
		return Promise.rejected(err)
	end)))

	self._maid._saveMaid = maid

	self.Saving:Fire(promise)

	return promise
end

function DataStore:_promiseLoad()
	if self._loadPromise then
		return self._loadPromise
	end

	self._loadPromise = self._maid:GivePromise(DataStorePromises.GetAsync(self._robloxDataStore, self._key)
		:Then(function(data)
			if data == nil then
				return {}
			elseif type(data) == "table" then
				return data
			else
				return Promise.rejected("Failed to load data. Wrong type '" .. type(data) .. "'")
			end
		end, function(err)
			-- Log:
			warn("[DataStore] - Failed to GetAsync data", err)
			return Promise.rejected(err)
		end))

	return self._loadPromise
end

return DataStore