Skip to content

Repository files navigation

Scribe

Persistent, fully-typed, automatically-replicated player data for Roblox Luau, built on ProfileStore.

📖 Full documentation → ericplane.github.io/Scribe 🔌 Studio plugin → Scribe Studio

# wally.toml
[dependencies]
Scribe = "ericplane/scribe@1.3.2"
  • Fully typed. A type-solver-generated accessor tree types your data end to end (data.Coins.Increment(50), nested containers, arrays, and datatype fields), checked at compile time.
  • Schemas all the way down.Scribe.ArrayOf and Scribe.DictOf give array and dictionary entries a schema, so data.Plots[1].Origin is a typed CFrame that packs to 13 bytes, with per-element bounds and size caps.
  • Replication for free. Schema-compressed batched diffs stream to clients over a pluggable transport, and you read the same data on the client with the same API, with no RemoteEvents to wire up.
  • Production-grade. Migrations, a wipe guard, version history, GDPR export and erase, leaderboards, gifting and perks, and fail-closed monetization all sit on top of ProfileStore's session locking.

Important

The typed API needs the new Luau type solver (Studio: select Workspace and set UseNewLuauTypeSolver to Enabled; or enable it in your Luau LSP settings). Scribe runs correctly without it. Data.Raw is the untyped escape hatch.

Quick start

One shared module declares the template and options and returns { Server, Client }:

-- ReplicatedStorage/Shared/Data.luaulocalScribe=require(game:GetService("ReplicatedStorage").Packages.Scribe)
returnScribe({
Template= { Coins=0, Settings= { Music=true } },
ProfileStoreIndex="PlayerData", -- required: your DataStore nameProfileKeyPrefix="PLAYER_", -- required: per-player key prefix
})
-- Server: wait for the profile to load, then use the typed accessorlocalData=require(game:GetService("ReplicatedStorage").Shared.Data).Servergame:GetService("Players").PlayerAdded:Connect(function(player)
localdata=Data.WaitForData(player) -- yields until Ready (default 60s timeout)ifdatathendata.Coins.Increment(50)
endend)
-- Client: read the same data reactively (writes are local-only; server wins)localData=require(game:GetService("ReplicatedStorage").Shared.Data).ClientData.Coins.Observe(function(coins)
coinsLabel.Text=tostring(coins)
end)

For declarators, replication + visibility, monetization, leaderboards, migrations, diagnostics, and the full API, see the documentation.

Development

rokit install # wally + rojo + selene + luau-lsp + lune + stylua toolchain
wally install # dependencies
selene src test lune # lint
stylua --check src test lune # formatting (drop --check to apply)
lune run lune/run-tests # run the test suite (headless, ~2s)

The same lint, format, test, and type-check (luau-lsp) checks run in CI on every pull request (.github/workflows/ci.yml), and releases are gated on a green run. Mark the test, lint, format, analyze, and version-check checks as required in the repository's branch-protection settings to enforce them on merge.

Docs are built with Material for MkDocs from the doc-comments in src/ and the guides in docgen/guides/. See docgen/README.md for details.

License

MIT

About

Persistent, fully-typed, automatically-replicated player data for Roblox Luau, built on ProfileStore.

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages