Data and Security
DataService
Safe player data with session locking, autosave and replication to the client, plus DataStores, extended stores for entries bigger than one key, ordered stores, MemoryStores, cross-server topics, shared documents, purchase receipts and client local stores.
Getting it
local DataService = Engine:GetService("DataService")Overview
DataService is Engine's storage layer. Its core is the profile: a table of data for one key (usually one player) that is loaded once, owned by exactly one server at a time, kept in memory while the player plays, autosaved, and saved one last time when the session ends or the server shuts down. Because only one server can own a profile, item duplication through server hopping is not possible.
Changes you make through the profile's methods (Set, Increment, ArrayInsert...) are also replicated to the player who owns the profile, so the client always has a live, read-only copy of its own data without any remotes of your own.
Around profiles DataService wraps every other Roblox storage service with retries, request budgeting and error reporting:
| Need | Use |
|---|---|
| Player progress, inventories, settings | Profile stores |
| Plain key-value data | Stores |
| One entry bigger than a DataStore key can hold, such as the build on a plot | Extended stores |
| Sorted numbers (leaderboards) | Ordered stores |
| Fast, temporary, cross-server data (matchmaking, server lists) | MemoryStore maps and queues |
| Messages between servers | Topics |
| One shared settings object for the whole game | Documents |
| Developer product purchases | Receipt processors |
| Small per-player client settings with no server code | Local stores |
Note
In Studio without "Enable Studio Access to API Services", or without an internet connection, DataService falls back to a temporary in-memory store: everything works, but nothing is kept after the test. Turn on API access in Game Settings to test real saving.
Player data in 20 lines
local Players = game:GetService("Players")
local Engine = require(game:GetService("ServerScriptService"):WaitForChild("Engine"))
local DataService = Engine:GetService("DataService")
local PlayerData = DataService:CreateProfileStore("PlayerData", {
Coins = 0,
Level = 1,
Inventory = {},
})
Players.PlayerAdded:Connect(function(player)
PlayerData:StartSessionAsync(PlayerData:PlayerKey(player), {
Player = player,
Create = true, -- create the profile for new players
}):andThen(function(profile)
profile:Reconcile() -- add fields that were added to the template later
print(player.Name, "has", profile:Get("Coins"), "coins")
end):catch(function(err)
player:Kick("Your data could not be loaded. Please rejoin.")
warn(err)
end)
end)
local function addCoins(player: Player, amount: number)
local profile = PlayerData:GetSession(PlayerData:PlayerKey(player))
if profile then
profile:Increment("Coins", amount)
end
endlocal DataService = Engine:GetService("DataService")
DataService:GetProfileAsync("PlayerData"):andThen(function(profile)
coinsLabel.Text = tostring(profile:Get("Coins"))
profile:OnChange("Coins", function(coins)
coinsLabel.Text = tostring(coins)
end)
end)Important
StartSessionAsync only creates a missing profile when you pass Create = true. Without it, a key that has never been saved rejects with "the profile does not exist". This prevents typos in keys from silently creating empty profiles.
The session ends by itself when the player leaves (EndSessionOnLeave), and every active profile is saved when the server shuts down.
Profile stores
CreateProfileStore
DataService:CreateProfileStore(name: string, template: { [string]: any }?, options: { Migrations: { Migration }? }?): ProfileStoreCreates the store (a DataStore with that name). New profiles start as a deep copy of template. Migrations is a list of functions that upgrade old data in place: when a profile with an older data version loads, each missing migration runs once, in order.
local PlayerData = DataService:CreateProfileStore("PlayerData", template, {
Migrations = {
function(data) -- version 1 -> 2: coins became a table of currencies
data.Currencies = { Coins = data.Coins or 0 }
data.Coins = nil
end,
},
}):GetProfileStore(name) returns an existing store (errors if missing), :FindProfileStore(name) returns nil instead, :GetProfileStores() lists them.
ProfileStore
| Member | Description |
|---|---|
:StartSessionAsync(key, params?): Promise<Profile> | Loads and locks a profile. Params below. |
:GetSession(key): Profile? | The active profile for a key on this server. |
:PlayerKey(player or userId): string | The key for a player, Player_<UserId> by default (PlayerKeyFormat). |
:CreateAsync(key, data?) | Creates a profile without starting a session. |
:ExistsAsync(key): Promise<boolean> | |
:GetAsync(key, version?): Promise<Profile?> | A read-only copy (view mode), optionally of an older version. Useful for viewing offline players. |
:VersionQuery(key, direction?, minDate?, maxDate?) | Walk through saved versions with :NextAsync(). |
:RestoreVersionAsync(key, version) | Rolls a profile back to a saved version. |
:RemoveAsync(key) | Deletes a profile. |
:MessageAsync(key, message) | Sends a message to a profile, even if it is loaded on another server or offline (see Messages). |
:DefineEdit(name, handler), :EditAsync(...) | Safe changes to profiles owned elsewhere, see Edits. |
:DefineTransfer(name, handlers), :TransferAsync(...) | Moving something from one profile to another, exactly once. |
Name, Template, DataVersion | Read-only. |
Mock, IsMock | A mock version of the store that never touches real DataStores, for tests. |
StartSessionParams
| Field | Description |
|---|---|
Player: Player? | Attach the profile to this player: their UserId is recorded for GDPR, the data replicates to them, and the session ends when they leave. |
Create: boolean? | Create the profile if it does not exist. |
Steal: boolean? | Take the profile from another server immediately instead of waiting for it to hand over. |
Cancel: (() -> boolean)? | Checked while waiting; return true to give up (for example when the player left). |
If another server still owns the profile, DataService asks it to save and release, and waits (FirstLoadRepeat, LoadRepeatPeriod). An unresponsive server's session is taken over after SessionStealSeconds, and a crashed server's session (no save for AssumeDeadSeconds) is taken immediately.
Profile
Reading and writing data
Use the profile's methods to change data. They validate the change, mark the profile dirty and replicate the change to the player. Paths are dot strings such as "Inventory.Weapons" or key arrays (see Patch).
| Method | Description |
|---|---|
:Get(path) | Reads a value. |
:Set(path, value) | Writes a value. Every table on the path must already exist; the last key may be new. |
:Increment(path, delta): number | Adds to a number and returns the result. |
:ArrayInsert(path, value, index?): number | Inserts into an array. |
:ArrayRemove(path, index): any | Removes from an array. |
:Remove(path): any | Deletes a value and returns it. Returns nil and changes nothing when there is nothing at the path. |
:Update(path, transform): any | Replaces a value with transform(current). The path follows the same rule as Set. |
:Reconcile() | Adds keys from the template that the data is missing (never overwrites). |
Set and Update never create tables along the path, and never replace a value on the path that is not a table. They error with the part of the path that is the problem, and change nothing:
profile:Set("Stats.Kills", 0) -- errors: Engine: DataService Profile:Set - Stats does not exist
profile:Set("Stats", {}) -- create the table first
profile:Set("Stats.Kills", 0) -- works: the last key may be new
profile:Set("Coins.Gold", 5) -- errors when Coins holds a number: Coins is not a tableTables your data always needs belong in the template, so :Reconcile() adds them to older profiles.
You can also change profile.Data directly; call :Sync() afterwards to replicate the whole table to the player.
Saving and sessions
| Member | Description |
|---|---|
:Save(), :SaveAsync() | Save now instead of waiting for the next autosave. |
:EndSession(), :EndSessionAsync() | Save and release the profile. |
:OverwriteAsync() | Write this data even if the profile is not active (for admin tools on view-mode copies). |
:EraseAsync() | Delete the profile's data. |
:IsActive() | Whether this server still owns the session. |
:AddUserId(id), :RemoveUserId(id) | UserIds associated with the data (for GDPR requests). |
:SetMetaTag(name, value), :GetMetaTag(name) | Small values stored next to the data. |
:HasApplied(id) | Whether an edit or receipt with this id was already applied. |
Saving, Saved(data) | Signals around every save. |
SessionEnding(reason), SessionEnded | reason is "Manual", "External" (another server took it) or "Shutdown". |
DataInvalid(reason, path) | Fires when a save finds data that cannot be stored, such as an Instance or NaN, with the exact path. |
Data, LastSavedData, Key, Player, Active, ViewMode, Version, DataVersion, FirstSessionTime, SessionLoadCount, LastSavedAt, UserIds, MetaTags | State. |
Before every save the data is checked with Patch.Validate (ValidateDataOnSave). Invalid data is reported and the save is skipped, so one bad value never corrupts the stored profile.
Messages
ProfileStore:MessageAsync(key, message) queues a message in the profile itself. The server that owns the profile receives it in profile.OnMessage; if nobody owns it, the next server to load it does.
profile.OnMessage = function(message, processed)
if message.Type == "Gift" then
profile:Increment("Coins", message.Amount)
end
processed() -- remove the message from the queue
endEdits and transfers
Messages are fire-and-forget. Edits are named, validated changes that you can await, with a guaranteed single application even when retried:
PlayerData:DefineEdit("GrantItem", function(profile, payload, meta)
if #profile:Get("Inventory") >= 100 then
return false -- reject: inventory full
end
profile:ArrayInsert("Inventory", payload.ItemId)
end)
-- from any server, for an online or offline player
PlayerData:EditAsync(PlayerData:PlayerKey(userId), "GrantItem", { ItemId = "Sword" }, { WaitForApply = true })
:andThen(function(result, reason)
print(result) -- "applied", "queued", "rejected" or "missing"
end)| EditOptions | Description |
|---|---|
Id | Your own idempotency id. The same id is never applied twice. |
WaitForApply | Wait for the owning server to confirm (up to EditAckTimeout) instead of returning "queued". |
Durable | When applied locally, also save before resolving. |
A handler that errors or returns false rejects the edit. Payloads must be storable values.
Transfers move something between two profiles, for example a trade or a gift, so that it is never lost or duplicated: the Debit handler runs on the source profile, then Credit on the target. If the credit fails, Refund gives it back to the source.
PlayerData:DefineTransfer("GiftCoins", {
Debit = function(profile, payload)
if profile:Get("Coins") < payload.Amount then
return false
end
profile:Increment("Coins", -payload.Amount)
end,
Credit = function(profile, payload)
profile:Increment("Coins", payload.Amount)
end,
Refund = function(profile, payload)
profile:Increment("Coins", payload.Amount)
end,
})
PlayerData:TransferAsync(fromKey, toKey, "GiftCoins", { Amount = 100 })TransferOptions: { Id?, ToStore?, WaitForApply? }. ToStore moves between two profile stores. Applied ids are remembered for AppliedIdRetentionDays.
Receipt processors
CreateReceiptProcessor
DataService:CreateReceiptProcessor(store: ProfileStore, handlers: { [productId]: (profile, receiptInfo) -> any }, options: { OnUnknownProduct? }?): (receiptInfo) -> Enum.ProductPurchaseDecisionBuilds a ProcessReceipt callback that grants developer products exactly once: it finds the buyer's active profile, skips purchases it already applied, runs your handler, records the purchase id and saves before telling Roblox the purchase was granted. A handler that errors or returns false leaves the purchase pending, so Roblox tries again later.
MarketplaceService.ProcessReceipt = DataService:CreateReceiptProcessor(PlayerData, {
[1234567] = function(profile)
profile:Increment("Coins", 500)
end,
})MonetizationService offers a complete purchase system on top of this.
Stores
DataService:CreateStore(name: string, scope: string?): StoreA plain DataStore with retries and request budgeting. GetStore and FindStore look it up again.
| Method | Description |
|---|---|
:GetAsync(key) | |
:SetAsync(key, value, userIds?, options?) | |
:UpdateAsync(key, transform) | transform(current, keyInfo). |
:IncrementAsync(key, delta?) | |
:RemoveAsync(key) |
All return Promises. Calls are retried RetryAttempts times with backoff, and wait for request budget when the server is close to its DataStore limits.
Extended stores
DataService:CreateExtendedStore(name: string, scope: string?): ExtendedStoreOne DataStore key holds at most 4 MB. An ExtendedStore keeps entries that need more room, such as the build on a plot. Every entry has one master key with Data, the small essential data you choose (author, name, ...), and named sections for the big data. Each section is saved in keys of its own, and a section bigger than one key is split over several. GetExtendedStore and FindExtendedStore look a store up again.
Note
Every call costs more DataStore requests than a Store, profile store or Document (see Requests per call). Use an ExtendedStore only when one entry, such as one player's profile or one plot, must hold more than a single key can store. Anything that fits in one key belongs in those.
local Plots = DataService:CreateExtendedStore("Plots")
-- When the plot is claimed
Plots:CreateAsync(plotKey, { Owner = player.UserId, Name = "My base" })
-- Every save
Plots:SetSectionAsync(plotKey, "Build", serializeBuild(plot))
-- Loading
Plots:GetAsync(plotKey):andThen(function(data, info)
if data then
print(data.Name, "was last saved at", info.UpdatedAt)
end
end)
Plots:GetSectionAsync(plotKey, "Build"):andThen(function(build)
if build then
loadBuild(plot, build)
end
end)ExtendedStore
Every method returns a Promise.
| Member | Description |
|---|---|
:CreateAsync(key, data, sections?) | Creates a new entry, with its sections in the same step, and resolves with info. Rejects when the entry already exists. |
:GetAsync(key) | Resolves data, info, or nil when the entry does not exist. |
:SetAsync(key, data, sections?) | Replaces the data and resolves with info. With sections, it saves the data and those sections in one step; sections you leave out stay as they are. |
:UpdateAsync(key, transform) | transform(data, info) returns the new data, or nil to cancel. Resolves data, info. |
:RemoveAsync(key) | Removes the entry and every key it uses, and resolves with the removed data (nil when there was no entry). |
:GetSectionAsync(key, name) | Resolves the section's value, or nil. |
:GetSectionsAsync(key, names?) | Resolves { [name] = value } with the sections that exist, every section when names is nil. |
:SetSectionAsync(key, name, value) | Saves one section and resolves with info. |
:SetSectionsAsync(key, sections) | Saves several sections in one step and resolves with info. |
:RemoveSectionAsync(key, name) | Removes a section and its keys, and resolves with info (nil when the entry does not exist). |
Name, Scope, IsMock | Read-only. |
CreateAsync is the only method that creates an entry. SetAsync, UpdateAsync, SetSectionAsync and SetSectionsAsync on an entry that does not exist reject with "the entry does not exist" and write nothing. Reading or removing something that does not exist changes nothing, and a read resolves nil.
info is read-only: { CreatedAt, UpdatedAt, Revision, Sections }, where Sections is { [name] = { Size, KeyCount, UpdatedAt } }. Times are Unix seconds and Size is in characters.
data must be a table that a DataStore can store (see Patch.Validate). A section can hold any storable value; remove one with RemoveSectionAsync, not by saving nil. Keys and section names are at most 50 characters. The master key holds Data and the list of sections, so it must stay under 4 MB itself: keep big values in sections.
Safe saving
- A section is written to new keys first, and the master key switches to them in one atomic update. A failed or interrupted save never leaves half-written data: the entry keeps its previous version, and a create that fails leaves nothing behind.
- Replaced keys are removed afterwards. Keys that a crashed server left behind are removed by a later write, once they are older than
ExtendedStorePendingTimeout. - Every section is checked (size and CRC32) when it is read. A read that overlaps a save tries again on the new version, and real damage is reported as an error instead of returned.
- Writes to one key on one server run in the order they were called. A save that Roblox reports as failed although it went through is not applied twice.
- A key that holds something other than an ExtendedStore entry is never overwritten.
- The server waits for running writes when it shuts down.
- Data and sections are copied when you call the method, so changing your table afterwards does not change what is saved.
Without DataStore access it works in memory, like a Store, and IsMock is true.
Warning
A Store with the same name and scope uses the same DataStore. Give an ExtendedStore a name of its own, or keep the keys of the two apart.
Requests per call
| Call | DataStore requests |
|---|---|
GetAsync | 1 read. |
GetSectionAsync, GetSectionsAsync | 1 read, plus 1 per key of the sections (about one per 4 MB). |
CreateAsync or SetAsync without sections, UpdateAsync | 1 update. |
CreateAsync or SetAsync with sections, SetSectionAsync, SetSectionsAsync | 2 updates of the master key, 1 write per new key, and removes for the keys they replace. |
RemoveSectionAsync | 1 update, plus 1 remove per key. |
RemoveAsync | 1 update, 1 remove per key, 1 read and 1 remove. |
ExtendedStoreChunkSize sets how many characters one section key holds, and ExtendedStoreParallelRequests how many section keys one call reads, writes or removes at the same time.
Ordered stores
DataService:CreateOrderedStore(name: string, scope: string?): OrderedStoreNumbers sorted by value, for all-time leaderboards: GetAsync, SetAsync, IncrementAsync, RemoveAsync, and :GetTopAsync(count, ascending?, minValue?, maxValue?), which resolves with { { Key, Value } }. For complete leaderboards with periods and boards in the world, see LeaderboardSystem.
Memory stores
Fast, temporary storage shared by all servers of the experience. Every value needs an expiration in seconds.
| Create | Methods |
|---|---|
:CreateSortedMap(name) | GetAsync, SetAsync(key, value, expiration, sortKey?), UpdateAsync(key, transform, expiration), RemoveAsync, GetRangeAsync(count, ascending?, lower?, upper?) |
:CreateHashMap(name) | GetAsync, SetAsync(key, value, expiration), UpdateAsync, RemoveAsync, ListItemsAsync(count) |
:CreateQueue(name, invisibilityTimeout?) | AddAsync(value, expiration, priority?), ReadAsync(count, allOrNothing?, waitTimeout?), RemoveAsync(id) |
Each has Get... and Find... lookups.
Topics
DataService:CreateTopic(name: string): TopicCross-server messages through MessagingService. topic:PublishAsync(data) sends (JSON up to MessageMaxBytes), and topic.Received: Signal<(data, sentAt)> fires on every server that created the topic. :Destroy() unsubscribes.
local announcements = DataService:CreateTopic("Announcements")
announcements.Received:Connect(function(data)
showBanner(data.Text)
end)
announcements:PublishAsync({ Text = "Double coins weekend!" })Documents
DataService:CreateDocument(name: string, template: { [string]: any }?): DocumentOne shared, versioned table for the whole game, such as live event settings or a ban list. Updates are applied safely even when several servers write at once.
| Member | Description |
|---|---|
:LoadAsync() | Loads the latest version. |
:UpdateAsync(transform) | Applies transform(data) (change the table in place) and saves. |
:Get(path), Data, Revision, Loaded | Read. |
Changed: Signal<data> | Fires when a newer revision is loaded or written. |
:Destroy() |
Service events and state
| Member | Description |
|---|---|
SessionStarted: Signal<profile>, SessionEnded: Signal<profile> | Any profile session on this server. |
DataInvalid: Signal<(profile, reason, path)> | Unstorable data found while saving. |
RequestFailed: Signal<(message, store, key)> | A DataStore, MemoryStore or MessagingService call failed. |
ProfileOverwritten: Signal<(store, key)> | A profile held data that was not a profile and was overwritten. |
CriticalStateChanged: Signal<boolean> | Too many errors in a short time (Roblox outage). Consider pausing trading while critical. |
DataStoreStateChanged: Signal<state> | "NotReady", "NoInternet", "NoAccess" or "Access". |
:WaitForDataStoreAsync() | Resolves with the state once it is known. |
:GetActiveSessions() | Every active profile on this server. |
:EraseUserAsync(userId) | Deletes that user's profile in every profile store. Use it for Roblox "right to erasure" requests. |
DataStoreState, IsClosing, IsCriticalState, ActiveSessionCount, LocalStoresEnabled | Read-only properties. |
Client
On the client, DataService gives read-only access to the player's own replicated profiles and to local stores.
| Member | Description |
|---|---|
:GetProfile(storeName): Profile? | The replicated profile, if it has arrived. |
:GetProfileAsync(storeName, timeout?): Promise<Profile> | Waits for it. |
:GetProfiles() | All replicated profiles. |
:RefreshProfileAsync(storeName) | Asks the server for a fresh copy. |
ProfileLoaded, ProfileUnloaded, Synced | Signals. |
A client profile has Data, Version, Active, StoreName, :Get(path), :OnChange(path, fn(new, old)), Changed: Signal<(keys, new, old)> and SessionEnded. It is a copy: changing it does not change the server's data.
Local stores (client)
Local stores let the client save small per-player values, such as UI preferences or keybinds, without any server code. They are backed by a profile store on the server, size-limited and rate-limited.
local settings = DataService:CreateLocalStore("Settings")
settings:WaitForLoadAsync():andThen(function()
musicToggle.Value = settings:Get("Music") ~= false
end)
musicToggle.Changed:Connect(function(value)
settings:Set("Music", value)
end)| Member | Description |
|---|---|
DataService:CreateLocalStore(name), :GetLocalStore(name), :FindLocalStore(name) | Names may not start with $. |
:Get(key), :GetAll(), :Set(key, value), :Remove(key) | Rapid Set calls on one key are combined before sending. |
:WaitForLoadAsync(timeout?), IsLoaded, Loaded | |
Changed: Signal<(key, new, old)> |
Never put anything in a local store that matters for gameplay: the client decides what goes in. The server can read a player's local stores with DataService:GetLocalProfileAsync(player).
Configuration
DefaultConfiguration/Services/DataService39 settings| Setting | Default | What it does |
|---|---|---|
| AutoSavePeriod | 300 | Seconds between autosaves of one active profile. |
| FirstLoadRepeat | 5 | Seconds between the first and second read while another server still owns a session. |
| LoadRepeatPeriod | 10 | Seconds between later reads while waiting for another server to hand a session over. |
| SessionStealSeconds | 40 | Seconds of waiting before a session held by an unresponsive server is stolen outright. |
| AssumeDeadSeconds | 630 | Seconds without a save after which another server's session counts as crashed and is taken immediately. |
| StartSessionTimeout | 120 | Seconds :StartSessionAsync keeps retrying failing DataStore calls before its Promise rejects. |
| MaxMessageQueue | 1000 | Unprocessed messages/edits one profile key keeps queued before new ones are dropped. |
| EndSessionOnLeave | true | End a session started with a Player when that player leaves. |
| SessionMessageInterval | 6 | Minimum seconds between DataStore saves triggered by cross-server nudges to one profile. |
| EditAckTimeout | 15 | Seconds :EditAsync / :TransferAsync wait for the owning server to confirm before settling for "queued". |
| AppliedIdRetentionDays | 30 | Days a profile remembers applied edit/transfer ids to reject duplicates. |
| MaxAppliedIds | 2000 | Cap on remembered applied ids per profile; the oldest are forgotten first. |
| DurableSaveDelay | 1 | Seconds the owning server coalesces durable-save requests (edits, transfers) into one DataStore write. |
| MaxInvalidSaveAttempts | 3 | Saves of a profile whose Data fails validation before an ending session gives up on persisting it. |
| ValidateDataOnSave | true | Check Profile.Data for unstorable values before every save and report the offending path. |
| CriticalStateErrorCount | 5 | DataStore errors inside CriticalStateErrorExpire seconds that flip IsCriticalState on. |
| CriticalStateErrorExpire | 120 | Seconds one error keeps counting toward the critical state. |
| CriticalStateExpire | 120 | Seconds the critical state stays on after it was entered. |
| WarnOnError | true | Also warn() every DataStore/MemoryStore/MessagingService error next to firing RequestFailed. |
| RetryAttempts | 4 | Attempts a Store/OrderedStore/MemoryStore/Topic call gets before its Promise rejects. |
| RetryBaseDelay | 1 | Seconds before the first retry; doubles per attempt with jitter. |
| RetryMaxDelay | 20 | Cap on the retry delay in seconds. |
| MinRequestBudget | 5 | DataStore request budget below which a call waits before being sent. |
| BudgetWaitSeconds | 1 | Seconds a budget-starved call waits between checks. |
| MessageMaxBytes | 1024 | JSON size a Topic message may have; MessagingService rejects bigger ones. |
| ExtendedStoreChunkSize | 4000000 | Characters one ExtendedStore section key holds before the section continues in another key. |
| ExtendedStorePendingTimeout | 900 | Seconds an unfinished ExtendedStore write may run before a later write removes its keys as leftovers. |
| ExtendedStoreParallelRequests | 4 | Section keys one ExtendedStore call reads, writes or removes at the same time. |
| ReplicationRate | 30 | Times per second queued replica patches are flushed to clients; 0 sends every patch immediately. |
| ReplicaTimeout | 10 | Seconds the client's :GetReplicaAsync waits for a replica before rejecting. |
| PlayerKeyFormat | "Player_%d" | string.format pattern ProfileStore:PlayerKey turns a UserId into a profile key with. |
| DocumentStoreName | "Engine.Documents" | DataStore every Document lives in, one key per document name. |
| LocalStoreEnabled | true | Let clients persist small per-player stores through :GetLocalStore without any server code. |
| LocalStoreName | "Engine.LocalStore" | DataStore name the engine keeps every player's local stores in. |
| LocalStoreMaxBytes | 16384 | JSON size cap of one player's combined local stores. |
| LocalStoreMaxKeys | 256 | Keys one local store may hold. |
| LocalStoreFlushDelay | 0.25 | Seconds the client coalesces rapid :Set calls on the same key before sending them. |
| LocalStoreRateLimitPerSec | 10 | Local-store writes per second one client may send. |
| LocalStoreRateLimitBurst | 30 | Burst the local-store rate limiter allows. |
Types
export type Path
export type Profile
export type ProfileStore
export type StartSessionParams
export type ProfileStoreOptions
export type Migration = (data: { [string]: any }) -> ()
export type LastSaveReason = "Manual" | "External" | "Shutdown"
export type EditHandler
export type EditResult = "applied" | "queued" | "rejected" | "missing"
export type EditOptions
export type TransferHandlers
export type TransferOptions
export type Store
export type ExtendedStore
export type ExtendedKeyInfo
export type SectionInfo
export type OrderedStore
export type SortedMap
export type HashMap
export type Queue
export type Topic
export type Document
export type ReceiptHandlers
export type ReceiptProcessor
export type DataStoreState = "NotReady" | "NoInternet" | "NoAccess" | "Access"