Skip to content
Engine

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.

ServiceServerClient

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:

NeedUse
Player progress, inventories, settingsProfile stores
Plain key-value dataStores
One entry bigger than a DataStore key can hold, such as the build on a plotExtended stores
Sorted numbers (leaderboards)Ordered stores
Fast, temporary, cross-server data (matchmaking, server lists)MemoryStore maps and queues
Messages between serversTopics
One shared settings object for the whole gameDocuments
Developer product purchasesReceipt processors
Small per-player client settings with no server codeLocal 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

Server: player data
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
end
Client: reading your own data
local 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 }? }?): ProfileStore

Creates 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

MemberDescription
: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): stringThe 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, DataVersionRead-only.
Mock, IsMockA mock version of the store that never touches real DataStores, for tests.

StartSessionParams

FieldDescription
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).

MethodDescription
: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): numberAdds to a number and returns the result.
:ArrayInsert(path, value, index?): numberInserts into an array.
:ArrayRemove(path, index): anyRemoves from an array.
:Remove(path): anyDeletes a value and returns it. Returns nil and changes nothing when there is nothing at the path.
:Update(path, transform): anyReplaces 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 table

Tables 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

MemberDescription
: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), SessionEndedreason 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, MetaTagsState.

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
end

Edits 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)
EditOptionsDescription
IdYour own idempotency id. The same id is never applied twice.
WaitForApplyWait for the owning server to confirm (up to EditAckTimeout) instead of returning "queued".
DurableWhen 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.ProductPurchaseDecision

Builds 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?): Store

A plain DataStore with retries and request budgeting. GetStore and FindStore look it up again.

MethodDescription
: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?): ExtendedStore

One 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.

Server: saving a plot
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.

MemberDescription
: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, IsMockRead-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

CallDataStore requests
GetAsync1 read.
GetSectionAsync, GetSectionsAsync1 read, plus 1 per key of the sections (about one per 4 MB).
CreateAsync or SetAsync without sections, UpdateAsync1 update.
CreateAsync or SetAsync with sections, SetSectionAsync, SetSectionsAsync2 updates of the master key, 1 write per new key, and removes for the keys they replace.
RemoveSectionAsync1 update, plus 1 remove per key.
RemoveAsync1 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?): OrderedStore

Numbers 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.

CreateMethods
: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): Topic

Cross-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 }?): Document

One 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.

MemberDescription
:LoadAsync()Loads the latest version.
:UpdateAsync(transform)Applies transform(data) (change the table in place) and saves.
:Get(path), Data, Revision, LoadedRead.
Changed: Signal<data>Fires when a newer revision is loaded or written.
:Destroy()

Service events and state

MemberDescription
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, LocalStoresEnabledRead-only properties.

Client

On the client, DataService gives read-only access to the player's own replicated profiles and to local stores.

MemberDescription
: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, SyncedSignals.

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.

Client
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)
MemberDescription
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

Defaults from DefaultConfiguration/Services/DataService39 settings
SettingDefaultWhat it does
AutoSavePeriod300Seconds between autosaves of one active profile.
FirstLoadRepeat5Seconds between the first and second read while another server still owns a session.
LoadRepeatPeriod10Seconds between later reads while waiting for another server to hand a session over.
SessionStealSeconds40Seconds of waiting before a session held by an unresponsive server is stolen outright.
AssumeDeadSeconds630Seconds without a save after which another server's session counts as crashed and is taken immediately.
StartSessionTimeout120Seconds :StartSessionAsync keeps retrying failing DataStore calls before its Promise rejects.
MaxMessageQueue1000Unprocessed messages/edits one profile key keeps queued before new ones are dropped.
EndSessionOnLeavetrueEnd a session started with a Player when that player leaves.
SessionMessageInterval6Minimum seconds between DataStore saves triggered by cross-server nudges to one profile.
EditAckTimeout15Seconds :EditAsync / :TransferAsync wait for the owning server to confirm before settling for "queued".
AppliedIdRetentionDays30Days a profile remembers applied edit/transfer ids to reject duplicates.
MaxAppliedIds2000Cap on remembered applied ids per profile; the oldest are forgotten first.
DurableSaveDelay1Seconds the owning server coalesces durable-save requests (edits, transfers) into one DataStore write.
MaxInvalidSaveAttempts3Saves of a profile whose Data fails validation before an ending session gives up on persisting it.
ValidateDataOnSavetrueCheck Profile.Data for unstorable values before every save and report the offending path.
CriticalStateErrorCount5DataStore errors inside CriticalStateErrorExpire seconds that flip IsCriticalState on.
CriticalStateErrorExpire120Seconds one error keeps counting toward the critical state.
CriticalStateExpire120Seconds the critical state stays on after it was entered.
WarnOnErrortrueAlso warn() every DataStore/MemoryStore/MessagingService error next to firing RequestFailed.
RetryAttempts4Attempts a Store/OrderedStore/MemoryStore/Topic call gets before its Promise rejects.
RetryBaseDelay1Seconds before the first retry; doubles per attempt with jitter.
RetryMaxDelay20Cap on the retry delay in seconds.
MinRequestBudget5DataStore request budget below which a call waits before being sent.
BudgetWaitSeconds1Seconds a budget-starved call waits between checks.
MessageMaxBytes1024JSON size a Topic message may have; MessagingService rejects bigger ones.
ExtendedStoreChunkSize4000000Characters one ExtendedStore section key holds before the section continues in another key.
ExtendedStorePendingTimeout900Seconds an unfinished ExtendedStore write may run before a later write removes its keys as leftovers.
ExtendedStoreParallelRequests4Section keys one ExtendedStore call reads, writes or removes at the same time.
ReplicationRate30Times per second queued replica patches are flushed to clients; 0 sends every patch immediately.
ReplicaTimeout10Seconds 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.
LocalStoreEnabledtrueLet 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.
LocalStoreMaxBytes16384JSON size cap of one player's combined local stores.
LocalStoreMaxKeys256Keys one local store may hold.
LocalStoreFlushDelay0.25Seconds the client coalesces rapid :Set calls on the same key before sending them.
LocalStoreRateLimitPerSec10Local-store writes per second one client may send.
LocalStoreRateLimitBurst30Burst 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"