{"id":"ygharsallah/replication","name":"replication","scope":"ygharsallah","platform":"roblox","description":"Lightweight path-based state replication for Roblox with change listeners","version":"1.0.0","latest":"1.0.0","versions":["0.0.1","0.0.2","0.0.3","0.0.4","1.0.0"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":true,"dependencies":{},"integrity":"d181561903a7c0ad837c24ee7873c6a892e212b5d3be9fe2f2efb5aea238046d","likes":0,"downloads":0,"install":"forest install ygharsallah/replication","url":"https://forest.dev/p/roblox/ygharsallah/replication","files":"https://api.forest.dev/ai/package/roblox/ygharsallah/replication/files","readme":"# Replication\r\n\r\nSimple server-authored state replication with path-based updates and client listeners.\r\n\r\n## Installation\r\n\r\nAdd the package to your `wally.toml`:\r\n\r\n```toml\r\n[dependencies]\r\nReplication = \"ygharsallah/replication@VERSION\"\r\n```\r\n\r\nThen require it from your project:\r\n\r\n```lua\r\nlocal Replication = require(game.ReplicatedStorage.Packages.Replication)\r\n```\r\n\r\n## API\r\n\r\n## `Replication.ID(name: string) -> ID`\r\n\r\nCreates a unique identifier used to reference a replication object. The same name on server and client resolves to the same object.\r\n\r\n---\r\n\r\n## `Replication.New(config: { ID: ID, Mutable: boolean, Data: table, Targets: Player | { Player }? }) -> { Mutable: boolean, Data: table }`\r\n\r\n*(Server-only)* Creates a new replication object and replicates it to all clients, or only to `Targets` when provided.\r\n\r\n| Field | Type | Description |\r\n|---|---|---|\r\n| `ID` | `ID` | The identifier returned by `Replication.ID()` |\r\n| `Mutable` | `boolean` | Whether `Replication:Set()` can be called on this object |\r\n| `Data` | `table` | Initial data to replicate. The passed table reference is stored directly |\r\n| `Targets` | `Player \\| { Player }?` | Optional audience. If omitted, replicates to all clients |\r\n\r\n---\r\n\r\n## `Replication.NewForPlayer(player: Player, config: { ID: ID, Mutable: boolean, Data: table }) -> { Mutable: boolean, Data: table }`\r\n\r\n*(Server-only)* Convenience wrapper for single-player replication. Equivalent to calling `Replication.New()` with `Targets = player`.\r\n\r\n---\r\n\r\n## `Replication:Set(ID: string, path: string, value: any) -> void`\r\n\r\n*(Server-only)* Updates a value at dot-notation `path` (e.g. `\"Money\"`, `\"Stats.XP\"`, `\"Items.1\"`) and replicates the change to the object's audience (`Targets` if configured, otherwise all clients). Requires `Mutable = true`.\r\n\r\n---\r\n\r\n## `Replication.GetData(ID: string) -> table`\r\n\r\nReturns a deep-copied snapshot of the current replicated data for the given ID.\r\n\r\n`Replication:getData(...)` is still supported as a backwards-compatible alias.\r\n\r\n---\r\n\r\n## `Replication.OnChanged(ID: string, path: string, callback: (new_value: any, old_value: any?) -> void) -> Connection`\r\n\r\nRegisters a listener that fires whenever that exact `path` is updated via `Replication:Set()`. Supports dot-notation for nested tables (e.g. `\"Stats.XP\"`).\r\n\r\n`Replication:onChanged(...)` is still supported as a backwards-compatible alias.\r\n\r\n**Callback parameters:**\r\n\r\n| Parameter | Type | Description |\r\n|---|---|---|\r\n| `new_value` | `any` | The new value after the change |\r\n| `old_value` | `any?` | The previous value, nil if this is the first update |\r\n\r\nBoth callback values are deep-copied snapshots.\r\n\r\nReturns a `Connection` object.\r\n\r\n---\r\n\r\n## `Connection:disconnect() -> void`\r\n\r\nUnregisters the listener returned by `Replication.OnChanged()`.\r\n\r\n## Behavior Notes\r\n\r\n- `Replication.New()` and `Replication:Set()` are server-only.\r\n- `Replication.NewForPlayer()` is a convenience wrapper around `Replication.New(..., Targets = player)`.\r\n- When `Targets` is passed to `Replication.New()`, only those players receive `init` and future `set` updates for that object.\r\n- Mutating the original table passed to `Replication.New()` does not replicate by itself; call `Replication:Set()` to replicate and trigger listeners.\r\n- `Replication.OnChanged()` listeners are keyed by exact path. Updating `\"Stats.XP\"` does not fire a listener registered on `\"Stats\"`.\r\n\r\n## Basic example\r\n\r\n### Server-side\r\n\r\n``` lua\r\nlocal Players = game:GetService(\"Players\")\r\nlocal Replication = require(game.ReplicatedStorage.Packages.Replication)\r\n\r\nPlayers.PlayerAdded:Connect(function(player)\r\n    local ID = Replication.ID((\"PlayerData:%d\"):format(player.UserId))\r\n\r\n    local Data = {\r\n        Money = 0,\r\n        Invincible = true,\r\n        Stats = {\r\n            Level = 1,\r\n            XP = 0,\r\n        },\r\n        Items = {\r\n            \"Sword\",\r\n            \"Shield\",\r\n            \"Potion\",\r\n        },\r\n    }\r\n\r\n    -- Only this player receives this object.\r\n    Replication.NewForPlayer(player, {\r\n        ID = ID,\r\n        Mutable = true,\r\n        Data = Data,\r\n    })\r\n\r\n    task.spawn(function()\r\n        while player.Parent == Players do\r\n            Replication:Set(ID, \"Money\", Data.Money + 100)\r\n            Replication:Set(ID, \"Stats.XP\", Data.Stats.XP + 10)\r\n            task.wait(1)\r\n        end\r\n    end)\r\n\r\n    Replication:Set(ID, \"Invincible\", true)\r\nend)\r\n```\r\n\r\n### Client-side\r\n\r\n``` lua\r\nlocal Players = game:GetService(\"Players\")\r\nlocal Replication = require(game.ReplicatedStorage.Packages.Replication)\r\n\r\nlocal player = Players.LocalPlayer\r\nlocal ID = Replication.ID((\"PlayerData:%d\"):format(player.UserId))\r\n\r\n-- Fetch the current snapshot of the replicated data\r\nlocal remoteData = Replication.GetData(ID)\r\nlocal Money = remoteData.Money\r\nlocal Stats = remoteData.Stats\r\nlocal Items = remoteData.Items\r\n\r\n-- Fires whenever Money changes, prints old and new value\r\nlocal function printMoney(new_value, old_value)\r\n    if old_value then\r\n        print(`Money has changed from {old_value} to {new_value}!`)\r\n        return\r\n    end\r\n\r\n    print(`Money has changed to {new_value}!`)\r\nend\r\n\r\n-- Fires whenever Stats.XP changes, prints old and new value\r\nlocal function printXP(new_value, old_value)\r\n    if old_value then\r\n        print(`XP has changed from {old_value} to {new_value}!`)\r\n        return\r\n    end\r\n\r\n    print(`XP has changed to {new_value}!`)\r\nend\r\n\r\n-- Fires whenever the Items array changes, prints the new item count\r\nlocal function printItems(new_value, old_value)\r\n    print(`Items updated! Now has {#new_value} items.`)\r\nend\r\n\r\n-- Register listeners and store the connections so they can be disconnected later\r\nlocal moneyConnection = Replication.OnChanged(ID, \"Money\", printMoney)\r\nlocal xpConnection    = Replication.OnChanged(ID, \"Stats.XP\", printXP)\r\nlocal itemsConnection = Replication.OnChanged(ID, \"Items\", printItems)\r\n\r\n-- Disconnect all listeners after 10 seconds\r\ntask.delay(10, function()\r\n    moneyConnection:disconnect()\r\n    xpConnection:disconnect()\r\n    itemsConnection:disconnect()\r\nend)\r\n```\r\n","readmeTruncated":false}