refactor: update charm sync api

This commit is contained in:
littensy 2025-10-31 21:56:02 -07:00
parent 1e0b10aef6
commit 32788b19e1
13 changed files with 359 additions and 335 deletions

View file

@ -2,8 +2,6 @@ local Charm = require("../../Charm")
local client = require("../client")
local suite = require("../../../../test/suite")
local atom = Charm.atom
local subscribe = Charm.subscribe
local test = suite.test
local afterEach = suite.afterEach
@ -11,29 +9,29 @@ afterEach(function()
client.reset()
end)
test("should sync state payload", function()
local a = atom(1)
test("should hydrate atom", function()
local a = Charm.atom(1)
client.syncAtoms({ a = a })
client.applyPayloads({
client.register({ a = a })
client.hydrate({
{ type = "init", data = { a = 2 } },
})
assert(a() == 2, "should set a to the initial value")
end)
test("should sync state patch", function()
local a = atom({ b = 1, c = 2 })
test("should merge patch", function()
local a = Charm.atom({ b = 1, c = 2 })
local previous = a()
client.syncAtoms({ a = a })
client.applyPayloads({
client.register({ a = a })
client.hydrate({
{ type = "init", data = { a = a() } },
})
assert(a() == previous, "should set a to the initial value")
client.applyPayloads({
client.hydrate({
{ type = "patch", data = { a = { b = 3 } } },
})
@ -43,17 +41,17 @@ test("should sync state patch", function()
end)
test("should fix sparse array patches", function()
local a = atom({ 1 })
local a = Charm.atom({ 1 })
client.syncAtoms({ a = a })
client.applyPayloads({
client.register({ a = a })
client.hydrate({
{ type = "init", data = { a = a() } },
})
-- This patch would be the result of a diff between { 1 } and { 1, 2 } that
-- was JSON-serialized and deserialized. The client should be able to catch
-- this and convert the string key back to a number.
client.applyPayloads({
client.hydrate({
{ type = "patch", data = { a = { ["2"] = 2 } } },
})
@ -61,21 +59,21 @@ test("should fix sparse array patches", function()
assert(a()[2] == 2, "should convert serialized arrays to use numbers")
end)
test("should apply multiple patches", function()
local a = atom(1)
test("should merge multiple patches", function()
local a = Charm.atom(1)
local calls = {}
client.syncAtoms({ a = a })
client.register({ a = a })
subscribe(a, function(value)
Charm.subscribe(a, function(value)
table.insert(calls, value)
end)
client.applyPayloads({
client.hydrate({
{ type = "init", data = { a = a() } },
})
client.applyPayloads({
client.hydrate({
{ type = "patch", data = { a = 2 } },
{ type = "patch", data = { a = 3 } },
})
@ -85,25 +83,25 @@ test("should apply multiple patches", function()
end)
test("should batch patches", function()
local a = atom(0)
local b = atom(0)
local a = Charm.atom(0)
local b = Charm.atom(0)
local states = {}
client.syncAtoms({ a = a, b = b })
client.register({ a = a, b = b })
subscribe(a, function()
Charm.subscribe(a, function()
states.b = b()
end)
subscribe(b, function()
Charm.subscribe(b, function()
states.a = a()
end)
client.applyPayloads({
client.hydrate({
{ type = "init", data = { a = a(), b = b() } },
})
client.applyPayloads({
client.hydrate({
{ type = "patch", data = { a = 1, b = 2 } },
})

View file

@ -20,11 +20,11 @@ test("should replicate server state to client", function()
local clientA = Charm.atom({ count = -1 })
local clientB = Charm.atom({ count = -1 })
client.syncAtoms({ a = clientA, b = clientB })
server.syncAtomsToClient(target, { a = serverA, b = serverB })
server.startSync(function(syncTarget, payloads)
client.register({ a = clientA, b = clientB })
server.subscribeClient(target, { a = serverA, b = serverB })
server.connect(function(syncTarget, payloads)
if syncTarget == target then
client.applyPayloads(payloads)
client.hydrate(payloads)
end
end)
@ -54,11 +54,11 @@ test("should replicate historical server state to client", function()
config.preserveHistory = true
client.syncAtoms({ a = clientA, b = clientB })
server.syncAtomsToClient(target, { a = serverA, b = serverB })
server.startSync(function(syncTarget, payloads)
client.register({ a = clientA, b = clientB })
server.subscribeClient(target, { a = serverA, b = serverB })
server.connect(function(syncTarget, payloads)
if syncTarget == target then
client.applyPayloads(payloads)
client.hydrate(payloads)
end
end)

View file

@ -20,8 +20,8 @@ test("should replicate state changes", function()
local c = Charm.atom(0)
local target, payloads
server.syncAtomsToClient(client, { a = a, b = b, c = c })
server.startSync(function(...)
server.subscribeClient(client, { a = a, b = b, c = c })
server.connect(function(...)
target, payloads = ...
end)
@ -47,8 +47,8 @@ test("should replicate partial state patches", function()
local b = Charm.atom(0 :: number?)
local target, payloads
server.syncAtomsToClient(client, { a = a, b = b })
server.startSync(function(...)
server.subscribeClient(client, { a = a, b = b })
server.connect(function(...)
target, payloads = ...
end)
@ -75,8 +75,8 @@ test("should init new clients", function()
local a = Charm.atom(0)
local payloadsA, payloadsB
server.syncAtomsToClient(clientA, { a = a })
server.startSync(function(target, payloads)
server.subscribeClient(clientA, { a = a })
server.connect(function(target, payloads)
if target == clientA then
payloadsA = payloads
elseif target == clientB then
@ -89,7 +89,7 @@ test("should init new clients", function()
assert(payloadsA[1].type == "init", "Expected first payload to be init for client A")
assert(payloadsA[1].data.a == a(), "Expected init payload to contain atom 'a' for client A")
server.syncAtomsToClient(clientB, { a = a })
server.subscribeClient(clientB, { a = a })
a(2)
server.flush()
assert(payloadsA[1].type == "patch", "Expected first payload to be patch for client A")
@ -104,13 +104,13 @@ test("should init new atoms", function()
local b = Charm.atom(0)
local target, payloads
server.syncAtomsToClient(client, { a = a })
server.startSync(function(...)
server.subscribeClient(client, { a = a })
server.connect(function(...)
target, payloads = ...
end)
server.flush()
server.syncAtomsToClient(client, { b = b })
server.subscribeClient(client, { b = b })
a(1)
b(1)
server.flush()
@ -129,9 +129,9 @@ test("should only replicate to synced clients", function()
local b = Charm.atom(0)
local payloadsA, payloadsB
server.syncAtomsToClient(clientA, { a = a })
server.syncAtomsToClient(clientB, { b = b })
server.startSync(function(target, payloads)
server.subscribeClient(clientA, { a = a })
server.subscribeClient(clientB, { b = b })
server.connect(function(target, payloads)
if target == clientA then
payloadsA = payloads
elseif target == clientB then
@ -170,8 +170,8 @@ test("should validate state", function()
local client = {}
local a = Charm.atom({})
server.syncAtomsToClient(client, { a = a })
server.startSync(function() end)
server.subscribeClient(client, { a = a })
server.connect(function() end)
a({ [0.5] = true })
assert(not pcall(server.flush), "Expected server.flush to fail")
@ -185,8 +185,8 @@ test("should serialize state", function()
local a = Charm.atom({ 1, 2, 3, 4, 5 })
local payloads
server.syncAtomsToClient(client, { a = a })
server.startSync(function(_, p)
server.subscribeClient(client, { a = a })
server.connect(function(_, p)
payloads = p
end)
server.flush()
@ -213,8 +213,8 @@ describe("historical", function()
local c = Charm.atom(0)
local target, payloads
server.syncAtomsToClient(client, { a = a, b = b, c = c })
server.startSync(function(...)
server.subscribeClient(client, { a = a, b = b, c = c })
server.connect(function(...)
target, payloads = ...
end)
@ -242,8 +242,8 @@ describe("historical", function()
local a = Charm.atom(0)
local payloadsA, payloadsB
server.syncAtomsToClient(clientA, { a = a })
server.startSync(function(target, payloads)
server.subscribeClient(clientA, { a = a })
server.connect(function(target, payloads)
if target == clientA then
payloadsA = payloads
elseif target == clientB then
@ -259,7 +259,7 @@ describe("historical", function()
assert(payloadsA[2].type == "patch", "Expected second payload to be patch for client A")
assert(payloadsA[2].data.a == 2, "Expected second patch payload to contain atom 'a' with value 1 for client A")
server.syncAtomsToClient(clientB, { a = a })
server.subscribeClient(clientB, { a = a })
a(3)
a(4)
server.flush()
@ -278,8 +278,8 @@ describe("historical", function()
local b = Charm.atom(0)
local payloads
server.syncAtomsToClient(client, { a = a })
server.startSync(function(_, ...)
server.subscribeClient(client, { a = a })
server.connect(function(_, ...)
payloads = ...
end)
@ -287,7 +287,7 @@ describe("historical", function()
assert(payloads[1].type == "init", "Expected first payload to be init")
assert(payloads[1].data.a == 0, "Expected init payload to contain atom 'a'")
server.syncAtomsToClient(client, { b = b })
server.subscribeClient(client, { b = b })
a(1)
a(2)
b(1)

View file

@ -8,8 +8,8 @@ suite.describe("e2e", function()
require("./e2e.spec")
end)
suite.describe("flatten", function()
require("./flatten.spec")
suite.describe("utils", function()
require("./utils.spec")
end)
suite.describe("patch", function()

View file

@ -1,17 +1,18 @@
local Charm = require("../../Charm")
local flatten = require("../flatten")
local suite = require("../../../../test/suite")
local utils = require("../utils")
local atom = Charm.atom
local test = suite.test
test("should flatten atoms", function()
local getF, setF = Charm.signal(0)
local input = {
a = atom(0),
b = { c = atom(0) },
d = { e = { f = atom(0) } },
d = { e = { f = utils.signalToAtom(getF, setF) } },
}
local result = flatten(input)
local result = utils.flattenState(input)
assert(result.a == input.a, "should flatten the first level")
assert(result["b/c"] == input.b.c, "should flatten the second level")