Custom Knit Service Using Lapis?

Hey! Well trying to find an alternative to ProfileService, I stumbled across Lapis, and decided to use it because of its promise implementation, I’m trying to create a “custom service” for lapis, using Knit, but I can’t figure it out.

This (probably) isn’t the best way to organize things, nor use Knit (please point out any bad-practices I did, and how I could fix them)

Anyway, here’s my current script:

-- Services
local Players = game:GetService("Players")
local Knit = require(game:GetService("ReplicatedStorage").Packages.Knit)
local Lapis = require(Knit.Util.Lapis)
local Signal = require(Knit.Util.Signal)
local t = require(Knit.Util.t)

-- Variables
local dataTemplate = {
	coins = 100,
}

-- Data Service
local DataService = Knit.CreateService {
	Name = "DataService",
	Client = {}
}

-- Public Functions
function DataService:Set(player: Player, key: string, value)
	local document = self.Documents[player]
	if document then
		local data = document:read()
		data[key] = value
		document:write(data)
	else
		warn("No document found for player " .. player.UserId)
	end
end

function DataService:Update(player: Player, key: string, callback)
	assert(type(callback) == "function", "Callback must be a function; got " .. type(callback))
	local document = self.Documents[player]
	if document then
		local data = document:read()
		data[key] = callback(data[key])
		document:write(data)
	else
		warn("No document found for player " .. player.UserId)
	end
end

function DataService:Get(player, key)
	local document = self.Documents[player]
	if document then
		local data = document:read()
		print(document)
		return data[key]
	else
		warn("No document found for player " .. player.UserId)
		return nil
	end
end

-- Client Exposed Functions
function DataService.Client:Get(player, key)
	return self.Server:Get(player, key)
end

-- Initialize
function DataService:KnitStart()
	self.DataChanged = Signal.new()
	self.Collection = Lapis.createCollection("PlayerData", {
		defaultData = dataTemplate,
		validate = t.strictInterface({coins = t.integer}),
	})
	self.Documents = {}

	function self:PlayerAdded(player)
		self.Collection
			:load("Player" .. player.UserId, { player.UserId })
			:andThen(function(document)
				if player.Parent == nil then
					document:close():catch(warn)
					return
				end
				self.Documents[player] = document
			end)
			:catch(function(message)
				warn("Player " .. player.Name .. "'s data failed to load: " .. message)
				player:Kick("Data failed to load.")
			end)
	end

	function self:PlayerRemoved(player)
		local document = self.Documents[player]
		if document then
			document:close():catch(warn)
		end
		self.Documents[player] = nil
	end

	for _, quickJoiner in Players:GetPlayers() do
		self:PlayerAdded(quickJoiner)
	end

	Players.PlayerAdded:Connect(function(player)
		self:PlayerAdded(player)
	end)
	Players.PlayerRemoving:Connect(function(player)
		self:PlayerRemoved(player)
	end)
end

return DataService

Right now, when a client tried to use the :Get() method, like this:

Knit.GetService("DataService"):Get(game.Players.LocalPlayer, "coins")

It prints

Promise(Started)

and I can’t figure out how to get the actual value from there.

Is there a better way to handle the documents or properties?

Any help is appreciated! :sparkling_heart:

See documentation for Promises. Knit service methods by default return Promises when called by the client. That design allows you to chain onto the call for handling results including extracting the results. You’re looking to use expect to return the value the Promise resolved with.

local coins = DataService:Get("coins"):expect()

Promise#expect

Also it is unnecessary (and wrong) for you to pass LocalPlayer. Service methods are essentially just remote calls, the player who called the event is passed. See Service Methods in the Knit documentation. The server knows who called, the client doesn’t need to pass itself - hence no player in the above example.

1 Like