Add live syncback functionality and optimize service encoding

- Introduced a new endpoint `/api/syncback` for one-click full sync from Roblox Studio to the filesystem, eliminating the need for an active WebSocket connection.
- Implemented `SyncbackRequest` and `ServiceChunk` structures to handle service-level properties and child instances during syncback.
- Enhanced `encodeService` to include ObjectValue carriers for accurate reference handling and prevent data loss.
- Updated the syncback process to utilize Luau-style relative paths for reference attributes, improving path management and consistency.
- Integrated session caching in the matching algorithm to optimize performance during syncback operations.

These changes significantly improve the live syncback experience, ensuring reliable data synchronization and enhanced performance during development.
This commit is contained in:
Joe 2026-02-18 22:34:52 -06:00
parent 7f4b5c4941
commit b47eef1173
165 changed files with 5205 additions and 206 deletions

View file

@ -221,7 +221,9 @@ encodeInstance = function(instance, parentId, _skipPathCheck)
-- Only encode properties that:
-- 1. Are readable (ReadWrite or Read scriptability)
-- 2. Actually serialize (not "DoesNotSerialize")
local isReadable = propertyMeta.scriptability == "ReadWrite" or propertyMeta.scriptability == "Read"
local isReadable = propertyMeta.scriptability == "ReadWrite"
or propertyMeta.scriptability == "Read"
or propertyMeta.scriptability == "Custom"
local doesSerialize = propertyMeta.serialization ~= "DoesNotSerialize"
if isReadable and doesSerialize then

View file

@ -34,7 +34,9 @@ return function(service: Instance)
continue
end
local isReadable = propertyMeta.scriptability == "ReadWrite" or propertyMeta.scriptability == "Read"
local isReadable = propertyMeta.scriptability == "ReadWrite"
or propertyMeta.scriptability == "Read"
or propertyMeta.scriptability == "Custom"
local doesSerialize = propertyMeta.serialization ~= "DoesNotSerialize"
if isReadable and doesSerialize then

View file

@ -4,6 +4,7 @@
]]
local HttpService = game:GetService("HttpService")
local ScriptEditorService = game:GetService("ScriptEditorService")
local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin
@ -367,6 +368,20 @@ function PatchTree.build(patch, instanceMap, changeListHeaders, gitMetadata)
break
end
end
if defaultSelection == nil then
local ok, draft = pcall(ScriptEditorService.GetEditorSource, ScriptEditorService, instance)
if ok and draft ~= source then
local draftBlob = "blob " .. tostring(#draft) .. "\0" .. draft
local draftHash = SHA1(buffer.fromstring(draftBlob))
for _, hash in hashes do
if draftHash == hash then
defaultSelection = "push"
break
end
end
end
end
end
end
end

View file

@ -10,6 +10,41 @@
local HttpService = game:GetService("HttpService")
local MockPromise = {}
MockPromise.__index = MockPromise
function MockPromise.resolve(value)
return setmetatable({ _value = value, _rejected = false }, MockPromise)
end
function MockPromise.reject(err)
return setmetatable({ _value = err, _rejected = true }, MockPromise)
end
function MockPromise:andThen(callback)
if not self._rejected then
local ok, result = pcall(callback, self._value)
if ok then
return MockPromise.resolve(result)
else
return MockPromise.reject(result)
end
end
return self
end
function MockPromise:catch(callback)
if self._rejected then
local ok, result = pcall(callback, self._value)
if ok then
return MockPromise.resolve(result)
else
return MockPromise.reject(result)
end
end
return self
end
local MockApiContext = {}
MockApiContext.__index = MockApiContext
@ -155,22 +190,15 @@ function MockApiContext:write(patch)
self:_maybeDelay()
if self.failOnWrite then
return false, "Mock write failure"
return MockPromise.reject("Mock write failure")
end
if self.onWrite then
local result = self.onWrite(self, patch)
if result ~= nil then
return result
end
self.onWrite(self, patch)
end
if self.writeResponse then
return true, self.writeResponse
end
-- Default success response
return true, { success = true }
local response = self.writeResponse or { success = true }
return MockPromise.resolve(response)
end
--[[