Store
Coordinates Sessions and provides a safe, high-level API for interacting with persistent data stored in Roblox DataStores and MemoryStores.
Core Responsibilities:
- Manages the lifecycle of
Sessionobjects for individual data keys (e.g., player IDs). - Provides methods for loading, reading (
get), updating (update), and saving data. - Implements atomic multi-key transactions (
tx). - Handles data validation, migrations, and optional legacy data import.
-
Abstracts away underlying complexities like data sharding (
Files), distributed locking (Locks), and transaction coordination (Transactions). - Integrates with a configurable logging system (
Log).
Usage Example:
local store = Store.createStore({
name = "PlayerData",
template = {
coins = 0,
items = {},
},
schema = function(data)
return typeof(data.coins) == "number" and typeof(data.items) == "table",
"Invalid data format"
end,
-- ... other config options
})
-- Load data for a player
store:load("player_1"):andThen(function()
-- Get current data
return store:get("player_1")
end):andThen(function(data)
print(data.coins) -- 0
-- Update data
return store:update("player_1", function(data)
data.coins += 100
return true -- Must return true to commit changes
end)
end)
Types
StoreConfig
interface StoreConfig {name: string--
A unique name for this store (e.g., "PlayerDataProd"). Used for logging and deriving DataStore/MemoryStore keys.
template: T--
A deep copyable Luau table/value representing the default state for a new key.
schema: (value: any) → (boolean,string?)--
A validation function (e.g., created with t) that checks if data conforms to the expected structure. Returns true if valid, or false and an error message if invalid.
migrationSteps: {Types.MigrationStep}?--
An optional ordered list of migration steps to apply to data loaded from the DataStore if its schema is older than the current version. See Migrations.luau.
importLegacyData: ((key: string) → any?)?--
An optional function to load data from a different, legacy storage system when a key is accessed for the first time and doesn't exist in this store.
dataStoreService: DataStoreService?--
An optional override for the Roblox DataStoreService. Useful for testing or custom storage implementations. Defaults to game:GetService("DataStoreService").
memoryStoreService: MemoryStoreService?--
An optional override for the Roblox MemoryStoreService. Useful for testing. Defaults to game:GetService("MemoryStoreService").
useMock: boolean?--
If true (and running in Studio), uses mock in-memory implementations of DataStoreService and MemoryStoreService instead of the actual Roblox services. Useful for testing in a controlled environment. Defaults to false.
changedCallbacks: {(key: string,newData: T,oldData: T?) → ()}?--
An optional list of functions called after data for a key has been successfully updated. Provides the key, the new data state, and the previous data state (if available).
logCallback: ((logMessage: Log.LogMessage) → ())?--
A function to receive log messages generated by this Store instance and its components. If omitted, logs are discarded. See Log.
onLockLost: ((key: string) → ())?--
An optional callback function triggered if the distributed lock for a key's session is lost unexpectedly (e.g., due to expiration or external interference). This usually indicates the session is no longer safe to use.
}Configuration options for creating a new Store instance using createStore.
Functions
createStore
Factory function to create a new Store instance.
Initializes the store context, sets up DataStore and MemoryStore connections (real or mock), validates the template schema, and returns the configured Store object.
txInternal
Store.txInternal(keys: {string},--
An array of keys involved in the transaction.
transformFunction: ((state: {[string]: any}) → boolean) | ((state: {[string]: any}) → {[string]: any} | false),--
A function that receives the current state of all keys and, depending on immutable, either modifies it directly (mutable) and returns true to commit, or returns a new state (immutable) to commit. Must return false to abort the transaction.
immutable: boolean) → Promise--
Resolves with true if the transaction was successful, or false if it was aborted. Rejects on error.
Internal helper to perform transaction logic on multiple keys. Handles acquiring locks, validating state, and committing changes atomically via a two-phase commit process.
Supports both immutable and mutable transactions, where immutable controls
whether the data passed to the transformFunction is a deep copy (mutable)
or a reference to the canonical, frozen data (immutable).
Implementation Details:
-
Uses
PromiseQueue.multiQueueAddto ensure the transform function only runs when all involved session queues are ready. -
Uses a temporary
txLockPromiseon sessions to block concurrentupdatecalls while the transaction logic is executing. - Performs a two-phase commit using a transaction marker in the
txStore:- Write
falsetotxStoreunder a uniquetxId. - Update all primary
recordStoreentries with the new data andtxId. - If successful, remove the
txIdentry fromtxStore.
- Write
-
If any step fails, attempts to revert changes by rewriting records without the
txIdand removing thetxIdmarker. - Uses
JsonPatchto calculate differences for efficient storage inTxInfo.
Propagates DataStore errors encountered during the commit or revert phases.
Errors
| Type | Description |
|---|---|
| "Key not loaded" | If any key in the `keys` array has not been loaded. |
| "Key is already locked by another transaction" | If any key is already involved in an ongoing `tx`. |
| "Key is closed" | If any involved session has been closed (e.g., due to lock loss). |
| "Store is closed" | If the store instance has been closed. |
| "Schema validation failed" | If the data for any key after transformation fails the schema check. |
| "Keys changed in transaction" | If the `transformFunction` attempts to add or remove keys from the state table it receives. |
load
Store:load(key: string,--
The unique identifier for the data to load (e.g., "player_123").
userIds: {number}?--
Optional list of UserIDs for DataStore key tagging.
) → Promise--
Resolves when the data is successfully loaded and the session is ready, or rejects on error.
Acquires a distributed lock, loads data for the given key into memory,
and establishes a Session object to manage the key's state.
This must be called before performing operations like get, update, or save
on the key. It handles concurrent load attempts and waits for any ongoing unload
operations to complete first.
Propagates errors from Session.load (e.g., lock acquisition failure, DataStore errors).
Errors
| Type | Description |
|---|---|
| "Load already in progress" | If `load` is called again for the same key while a previous load is still running. |
| "Store is closed" | If the store instance has been closed via `close()`. |
loadAsync
This is a yielding function. When called, it will pause the Lua thread that called the function until a result is ready to be returned, without interrupting other scripts. YieldsStore:loadAsync(key: string,--
The unique identifier for the data to load.
userIds: {number}?--
Optional list of UserIDs for DataStore key tagging.
) → ()Synchronous wrapper for Store:load. Waits for the Promise to settle. Throws an error if the load fails or is cancelled.
Errors
| Type | Description |
|---|---|
| any | Throws any error encountered during the load process. |
unload
Store:unload(key: string--
The unique identifier for the data to unload.
) → Promise--
Resolves when the data is successfully unloaded, or rejects on error.
Unloads data for the given key from memory, saves any pending changes, releases the distributed lock, and ends the session.
Propagates errors from Session:unload (e.g., save failures).
Errors
| Type | Description |
|---|---|
| "Store is closed" | If the store instance has been closed. |
unloadAsync
This is a yielding function. When called, it will pause the Lua thread that called the function until a result is ready to be returned, without interrupting other scripts. YieldsStore:unloadAsync(key: string--
The unique identifier for the data to unload.
) → ()Synchronous wrapper for Store:unload. Waits for the Promise to settle. Throws an error if the unload fails.
Errors
| Type | Description |
|---|---|
| any | Throws any error encountered during the unload process. |
get
Store:get(key: string--
The key whose data to retrieve.
) → Promise<T>--
Resolves with the current data object (potentially a deep copy).
Gets the current, in-memory data state for the given key.
Requires the key to be loaded first via load().
If the key is still loading, this will wait for it to finish.
Errors
| Type | Description |
|---|---|
| "Key not loaded" | If `load()` has not been successfully called for this key. |
| "Store is closed" | If the store has been closed. |
getAsync
This is a yielding function. When called, it will pause the Lua thread that called the function until a result is ready to be returned, without interrupting other scripts. YieldsStore:getAsync(key: string--
The key whose data to retrieve.
) → T--
The current data object.
Synchronous wrapper for Store:get. Waits for the Promise to settle. Throws an error if getting the data fails.
Errors
| Type | Description |
|---|---|
| "Key not loaded" | If `load()` has not been successfully called for this key. |
| "Store is closed" | If the store has been closed. |
update
Store:update(key: string,--
The key whose data to update.
transformFunction: (data: T) → boolean--
A function that receives the current data and returns true to commit changes or false to abort.
) → Promise<boolean>--
Resolves with true if the transform function returned true and the update was successfully queued, or false if the transform function returned false. Rejects on errors like key not loaded, store closed, or schema validation failure after transformation.
Applies changes to the data for a given key using a transform function.
The transformFunction receives the current data and can modify it directly.
It must return true to indicate that changes were made and should be
saved, or false to abort the update without saving.
Changes are applied optimistically to the in-memory state first and then queued for saving to the DataStore.
Errors
| Type | Description |
|---|---|
| "Key not loaded" | If `load()` has not been successfully called for this key. |
| "Store is closed" | If the store instance has been closed. |
| "Schema validation failed" | If the data returned by `transformFunction` does not pass the store's schema check. |
updateAsync
This is a yielding function. When called, it will pause the Lua thread that called the function until a result is ready to be returned, without interrupting other scripts. YieldsStore:updateAsync(key: string,--
The key whose data to update.
transformFunction: (data: T) → boolean--
The transformation function.
) → boolean--
Returns the boolean value returned by the transformFunction.
Synchronous wrapper for Store:update. Waits for the Promise to settle. Throws an error if the update fails.
Errors
| Type | Description |
|---|---|
| "Key not loaded" | If `load()` has not been successfully called for this key. |
| "Store is closed" | If the store instance has been closed. |
| "Schema validation failed" | If the data returned by `transformFunction` does not pass the store's schema check. |
updateImmutable
Store:updateImmutable(key: string,--
The key whose data to update.
transformFunction: (data: T) → T | false--
A function that receives the current data and returns a new copy of the data with changes to commit changes, or false to abort.
) → Promise<boolean>--
Resolves with true if the transform function committed and the update was successfully queued, or false if the transform function returned false. Rejects on errors like key not loaded, store closed, or schema validation failure after transformation.
Applies changes to the data for a given key using a transform function, with immutable copy-on-write semantics.
The transformFunction receives the current data but frozen (immutable),
and cannot modify it directly. Instead, it should return new data that
reflects the desired changes. Otherwise it should return false to abort
the update without saving.
Changes are applied optimistically to the in-memory state first and then queued for saving to the DataStore.
Errors
| Type | Description |
|---|---|
| "Key not loaded" | If `load()` has not been successfully called for this key. |
| "Store is closed" | If the store instance has been closed. |
| "Schema validation failed" | If the data returned by `transformFunction` does not pass the store's schema check. |
updateImmutableAsync
This is a yielding function. When called, it will pause the Lua thread that called the function until a result is ready to be returned, without interrupting other scripts. YieldsStore:updateImmutableAsync(key: string,--
The key whose data to update.
transformFunction: (data: T) → T | false--
The transformation function.
) → boolean--
Returns true if the update was successful, or false if the transform function returned false.
Synchronous wrapper for Store:updateImmutable. Waits for the Promise to settle. Throws an error if the update fails.
Errors
| Type | Description |
|---|---|
| "Key not loaded" | If `load()` has not been successfully called for this key. |
| "Store is closed" | If the store instance has been closed. |
| "Schema validation failed" | If the data returned by `transformFunction` does not pass the store's schema check. |
tx
Store:tx(keys: {string},--
An array of keys involved in the transaction.
transformFunction: (state: {[string]: T}) → boolean--
The transformation function.
) → Promise<boolean>--
Resolves with true if the transaction was successful, or false if it was aborted. Rejects on error.
Performs an atomic transaction across multiple keys.
Requires the keys to be loaded first via load(). The transformFunction
is called with the current state of all involved keys and must return true
to commit changes or false to abort.
Propagates errors from the transaction process, including DataStore errors, schema validation failures, and key loading issues.
Errors
| Type | Description |
|---|---|
| "Key not loaded" | If any key in the `keys` array has not been loaded. |
| "Key is already locked by another transaction" | If any key is already involved in an ongoing `tx`. |
| "Key is closed" | If any involved session has been closed (e.g., due to lock loss). |
| "Store is closed" | If the store instance has been closed. |
| "Schema validation failed" | If the data for any key after transformation fails the schema check. |
| "Keys changed in transaction" | If the `transformFunction` attempts to add or remove keys from the state table it receives. |
txAsync
This is a yielding function. When called, it will pause the Lua thread that called the function until a result is ready to be returned, without interrupting other scripts. YieldsStore:txAsync(keys: {string},--
An array of keys involved in the transaction.
transformFunction: (state: {[string]: T}) → boolean--
The transformation function.
) → ()Synchronous wrapper for Store:tx. Waits for the Promise to settle. Throws an error if the transaction fails or is aborted.
Errors
| Type | Description |
|---|---|
| any | Throws any error encountered during the transaction. |
txImmutable
Store:txImmutable(keys: {string},--
An array of keys involved in the transaction.
transformFunction: (state: {[string]: T}) → {[string]: T} | false--
The transformation function.
) → Promise<boolean>--
Resolves with true if the transaction was successful, or false if it was aborted. Rejects on error.
Performs an atomic transaction across multiple keys with immutable, copy-on-write semantics.
The data passed to the function is frozen and cannot be modified directly. Instead, the function should return a new table with the desired changes.
Requires the keys to be loaded first via load(). The transformFunction
is called with the current state of all involved keys and must return the
new state to commit or false to abort.
Propagates errors from the transaction process, including DataStore errors, schema validation failures, and key loading issues.
Errors
| Type | Description |
|---|---|
| "Key not loaded" | If any key in the `keys` array has not been loaded. |
| "Key is already locked by another transaction" | If any key is already involved in an ongoing `tx`. |
| "Key is closed" | If any involved session has been closed (e.g., due to lock loss). |
| "Store is closed" | If the store instance has been closed. |
| "Schema validation failed" | If the data for any key after transformation fails the schema check. |
| "Keys changed in transaction" | If the `transformFunction` attempts to add or remove keys from the state table it receives. |
txImmutableAsync
This is a yielding function. When called, it will pause the Lua thread that called the function until a result is ready to be returned, without interrupting other scripts. YieldsStore:txImmutableAsync(keys: {string},--
An array of keys involved in the transaction.
transformFunction: (state: {[string]: T}) → {[string]: T} | false--
The transformation function.
) → booleanSynchronous wrapper for Store:txImmutable. Waits for the Promise to settle. Throws an error if the transaction fails or is aborted.
Errors
| Type | Description |
|---|---|
| any | Throws any error encountered during the transaction. |
save
Store:save(key: string--
The key whose data to save.
) → Promise--
Resolves when the save operation completes successfully, rejects on error.
Forces an immediate save of the given key's current in-memory data state to the DataStore.
NOTE
Data is automatically saved periodically by the Session's autosave mechanism.
Manual saves are typically only needed in specific scenarios like processing
developer product purchases (MarketplaceService.ProcessReceipt) where immediate
persistence is crucial before granting benefits.
Propagates errors from Session:save (e.g., DataStore write errors).
Errors
| Type | Description |
|---|---|
| "Key not loaded" | If `load()` has not been successfully called for this key. |
| "Store is closed" | If the store instance has been closed. |
saveAsync
This is a yielding function. When called, it will pause the Lua thread that called the function until a result is ready to be returned, without interrupting other scripts. YieldsStore:saveAsync(key: string--
The key whose data to save.
) → ()Synchronous wrapper for Store:save. Waits for the Promise to settle. Throws an error if the save fails.
Errors
| Type | Description |
|---|---|
| any | Throws any error encountered during the save operation. |
close
Store:close() → Promise--
Resolves when all sessions have attempted to unload, or rejects if any session encountered an error during its unload process (errors are aggregated).
Closes the store, gracefully unloading all active sessions.
Attempts to save any pending changes for all loaded keys before releasing locks and removing sessions from memory. The store instance becomes unusable as soon as this is called.
closeAsync
This is a yielding function. When called, it will pause the Lua thread that called the function until a result is ready to be returned, without interrupting other scripts. YieldsStore:closeAsync() → ()Synchronous wrapper for Store:close. Waits for the Promise to settle. Throws an error if closing fails (i.e., if any session failed to unload).
Errors
| Type | Description |
|---|---|
| any | Throws an error (potentially a table of errors) if closing fails. |
peek
Store:peek(key: string--
The key whose data to peek at.
) → Promise<T?>--
Resolves with the data object, or nil if the key doesn't exist. Rejects on DataStore errors.
Reads the current data for the given key directly from the DataStore, bypassing the session cache and locking mechanism.
This provides a snapshot of the last saved state but does not load the key into an active session. Useful for inspecting data without acquiring a lock. Handles potential sharding and transaction status automatically.
Propagates DataStore errors from underlying reads.
peekAsync
This is a yielding function. When called, it will pause the Lua thread that called the function until a result is ready to be returned, without interrupting other scripts. YieldsStore:peekAsync(key: string--
The key whose data to peek at.
) → T?--
The data object, or nil if the key doesn't exist.
Synchronous wrapper for Store:peek. Waits for the Promise to settle. Returns the data for the key if it exists. Throws on any errors from underlying DataStore operations.
Errors
| Type | Description |
|---|---|
| any | May throw errors from underlying DataStore operations. |
probeLockActive
Store:probeLockActive(key: string--
The key to check the lock status for.
) → Promise<boolean>--
Resolves with true if a lock is active, false otherwise. Rejects on MemoryStore errors.
Checks if a distributed lock is currently active for the given key in MemoryStore.
Propagates errors from Locks.probeLockActive.
probeLockActiveAsync
This is a yielding function. When called, it will pause the Lua thread that called the function until a result is ready to be returned, without interrupting other scripts. YieldsStore:probeLockActiveAsync(key: string--
The key to check the lock status for.
) → boolean--
true if a lock is active, false otherwise.
Synchronous wrapper for Store:probeLockActive. Waits for the Promise to settle. Throws an error if the check fails.
Errors
| Type | Description |
|---|---|
| any | Throws any error encountered during the probe operation. |
listVersions
Store:listVersions(params: ListVersionParams--
Parameters specifying the key, sorting, date range, and page size.
) → Promise<DataStoreVersionPages>--
Resolves with an iterator object (DataStoreVersionPages) that can be used to fetch pages of version history. Rejects on DataStore errors.
Lists historical versions of the data for a given key using DataStore versioning.
Propagates errors from DataStore:ListVersionsAsync.
listVersionsAsync
This is a yielding function. When called, it will pause the Lua thread that called the function until a result is ready to be returned, without interrupting other scripts. YieldsStore:listVersionsAsync(params: ListVersionParams--
Parameters for listing versions.
) → DataStoreVersionPages--
An iterator for version history.
Synchronous wrapper for Store:listVersions. Waits for the Promise to settle. Throws an error if listing versions fails.
Errors
| Type | Description |
|---|---|
| any | Throws any error encountered during the list operation. |
readVersion
Store:readVersion(key: string,--
The key whose version to read.
version: string--
The specific version ID (obtained from listVersions).
) → Promise<(T,)>--
Resolves with the data object (T) and the DataStoreKeyInfo for that version. Rejects if the version doesn't exist or on DataStore/read errors.
Reads the data content of a specific historical version for a given key.
Propagates DataStore errors from underlying reads.
Errors
| Type | Description |
|---|---|
| "Record not found" | If the specified version doesn't exist. |
readVersionAsync
This is a yielding function. When called, it will pause the Lua thread that called the function until a result is ready to be returned, without interrupting other scripts. YieldsStore:readVersionAsync(key: string,--
The key whose version to read.
version: string--
The specific version ID.
) → (T,--
The data object for the specified version.
DataStoreKeyInfo--
The key info for the specified version.
)Synchronous wrapper for Store:readVersion. Waits for the Promise to settle. Throws an error if reading the version fails.
Errors
| Type | Description |
|---|---|
| any | Throws any error encountered during the read operation. |