### Install DocumentService via Wally Source: https://context7.com/anthony0br/documentservice/llms.txt Add DocumentService to your project's dependencies by including it in your wally.toml file. ```toml [dependencies] DocumentService = "anthony0br/documentservice@1.2.2" ``` -------------------------------- ### Get Required Services and Libraries Source: https://github.com/anthony0br/documentservice/blob/main/docs/tutorial.md Obtain the necessary services and libraries for managing player data. Ensure DocumentService and Guard are correctly required. ```lua local DataStoreService = game:GetService("DataStoreService") local DocumentService = require(path.to.DocumentService) local Guard = require(path.to.Guard) ``` -------------------------------- ### Get and Set Document Cache Source: https://context7.com/anthony0br/documentservice/llms.txt Use GetCache to retrieve the current in-memory snapshot and SetCache to replace it. Ensure you clone nested tables before mutation as cache tables are frozen. This is only available on session-locked documents. ```lua -- Award coins to a player without hitting the DataStore local function awardCoins(player: Player, amount: number) local document = PlayerStore:GetDocument(tostring(player.UserId)) if not document:IsOpen() then warn("Document not open for", player.Name) return end -- Clone before mutating — cache tables are frozen local data = table.clone(document:GetCache()) data.Coins += amount document:SetCache(data) print(`{player.Name} now has {data.Coins} coins (cached, not yet saved to DataStore)`) end -- For Robux transactions, force an immediate DataStore write: local function awardRobuxPurchase(player: Player, coinsToGrant: number) local document = PlayerStore:GetDocument(tostring(player.UserId)) local data = table.clone(document:GetCache()) data.Coins += coinsToGrant document:SetCache(data) local saveResult = document:SaveAsync() if not saveResult.success then warn("Save failed for Robux purchase:", saveResult.reason) end end ``` -------------------------------- ### Open a Document and handle session locks Source: https://context7.com/anthony0br/documentservice/llms.txt Opens a document, applying migrations and validation. For session-locked documents, it acquires the lock and starts autosaves. Includes logic to steal the lock if necessary and handle potential Roblox API or compatibility errors. ```lua local function onPlayerAdded(player: Player) local document = PlayerStore:GetDocument(tostring(player.UserId)) local result = document:OpenAsync() -- After 5 retries the lock should have expired (10 min timeout), -- so it is safe to steal at this point. if not result.success and result.reason == "SessionLockedError" then document:Steal() result = document:OpenAsync() end if not result.success then if result.reason == "BackwardsCompatibilityError" then player:Kick("Your data requires a newer server. Please rejoin.") elseif result.reason == "RobloxAPIError" then player:Kick("Failed to load data due to a Roblox outage. Please try again.") else player:Kick(`Data load failed: {result.reason}. Please report this.`) end return end -- result.data holds the validated, migrated player data print(`Opened data for {player.Name}:`, result.data) end ``` -------------------------------- ### Document:OpenAsync Source: https://context7.com/anthony0br/documentservice/llms.txt Opens the document, running all migrations, the check function, and (if session-locked) acquiring the session lock. Automatically starts periodic autosaves every 150 seconds for session-locked documents. Returns a `Result` — always check `result.success` before proceeding. Retries up to 5 times over ~16 seconds if the session is locked by another server. ```APIDOC ## Document:OpenAsync ### Description Opens the document, running all migrations, the check function, and (if session-locked) acquiring the session lock. Automatically starts periodic autosaves every 150 seconds for session-locked documents. Returns a `Result` — always check `result.success` before proceeding. Retries up to 5 times over ~16 seconds if the session is locked by another server. ### Returns - **Result** - An object with a `success` boolean property and potentially `data` or `reason` properties. ### Example ```lua local function onPlayerAdded(player: Player) local document = PlayerStore:GetDocument(tostring(player.UserId)) local result = document:OpenAsync() -- After 5 retries the lock should have expired (10 min timeout), -- so it is safe to steal at this point. if not result.success and result.reason == "SessionLockedError" then document:Steal() result = document:OpenAsync() end if not result.success then if result.reason == "BackwardsCompatibilityError" then player:Kick("Your data requires a newer server. Please rejoin.") elseif result.reason == "RobloxAPIError" then player:Kick("Failed to load data due to a Roblox outage. Please try again.") else player:Kick(`Data load failed: {result.reason}. Please report this.`) end return end -- result.data holds the validated, migrated player data print(`Opened data for {player.Name}:`, result.data) end ``` ``` -------------------------------- ### Get a Document by key Source: https://context7.com/anthony0br/documentservice/llms.txt Retrieve a Document object for a given key. Documents are cached and become eligible for garbage collection when closed and no longer referenced. This method does not open the document. ```lua -- Called on Players.PlayerAdded local function onPlayerAdded(player: Player) local document, isNew = PlayerStore:GetDocument(tostring(player.UserId)) -- isNew is true the first time this key is retrieved this session print(`Got document for {player.Name}, isNew={isNew}`) -- document must now be opened before reading/writing end game:GetService("Players").PlayerAdded:Connect(onPlayerAdded) ``` -------------------------------- ### DocumentStore:GetDocument Source: https://context7.com/anthony0br/documentservice/llms.txt Gets (or lazily creates) a `Document` object for the given key. Documents are cached in a weak table — once closed and all references released they become eligible for garbage collection. Does not open the document; you must call `OpenAsync` separately. ```APIDOC ## DocumentStore:GetDocument ### Description Gets (or lazily creates) a `Document` object for the given key. Documents are cached in a weak table — once closed and all references released they become eligible for garbage collection. Does not open the document; you must call `OpenAsync` separately. ### Parameters - **key** (string) - Required - The unique identifier for the document. ### Returns - **Document** - The Document object. - **boolean** - True if the document was newly created this session, false otherwise. ### Example ```lua -- Called on Players.PlayerAdded local function onPlayerAdded(player: Player) local document, isNew = PlayerStore:GetDocument(tostring(player.UserId)) -- isNew is true the first time this key is retrieved this session print(`Got document for {player.Name}, isNew={isNew}`) -- document must now be opened before reading/writing end game:GetService("Players").PlayerAdded:Connect(onPlayerAdded) ``` ``` -------------------------------- ### Create a new DocumentStore Source: https://context7.com/anthony0br/documentservice/llms.txt Initialize a DocumentStore with a DataStore, schema validation, default data, and session-lock configuration. This should be done once per server per DataStore. ```lua local DataStoreService = game:GetService("DataStoreService") local DocumentService = require(path.to.DocumentService) local Guard = require(path.to.Guard) -- https://util.redblox.dev/guard.html type PlayerData = { Coins: number, XP: number, Level: number, } local DataInterface = { Coins = Guard.Integer, XP = Guard.Integer, Level = Guard.Integer, } local function dataCheck(value: unknown): PlayerData assert(type(value) == "table", "Data must be a table") local v: any = value return { Coins = DataInterface.Coins(v.Coins), XP = DataInterface.XP(v.XP), Level = DataInterface.Level(v.Level), } end local PlayerStore = DocumentService.DocumentStore.new({ dataStore = DataStoreService:GetDataStore("PlayerData") :: any, check = Guard.Check(dataCheck), default = { Coins = 0, XP = 0, Level = 1 }, migrations = {}, -- see Migrations section lockSessions = true, -- enables caching; required for player data bindToClose = true, -- default: true; set false in Studio to speed up playtests }) ``` -------------------------------- ### Initialize DocumentStore for Player Data Source: https://github.com/anthony0br/documentservice/blob/main/docs/tutorial.md Set up the DocumentStore for player data, configuring data store, check function, default values, and migration settings. Session locking is enabled to prevent concurrent editing. ```lua local PlayerDocumentStore = DocumentService.DocumentStore.new({ dataStore = DataStoreService:GetDataStore("PlayerData"), check = Guard.Check(dataCheck), default = { Coins = 0, XP = 0, }, migrations = { { backwardsCompatible = false, migrate = function() -- Put a migration here! end }, }, -- This is an important feature of player data. It locks editing to one server -- at a time, allowing us to safely cache player data and save batches of updates. -- We do this through additional methods that session locking unlocks, such as -- `SetCache` and `GetCache`. lockSessions = true, }) ``` -------------------------------- ### Create a DocumentStore Source: https://github.com/anthony0br/documentservice/blob/main/docs/creating.md Initialize a DocumentStore with a DataStore, a data validation schema, default values, and migration configurations. Ensure to cast DataStore to 'any' due to Luau type limitations. Avoid creating duplicate DocumentStores to prevent separate session management. ```lua type DataSchema = { coins: number, } local DataInterface = { coins = Guard.Integer, } local function dataCheck(value: unknown): DataSchema assert(type(value) == "table", "Data must be a table") local Value: any = value return { coins = DataInterface.coins(Value.coins), } end local store = DocumentStore.new({ dataStore = DataStoreService:GetDataStore("Test") :: any, -- For mockDataStores use below! --dataStore = MockDataStore:GetDataStore("Mock"), check = Guard.Check(dataCheck), default = { coins = 100, }, migrations = {}, lockSessions = true, }) ``` -------------------------------- ### Define DocumentStore with Migrations Source: https://context7.com/anthony0br/documentservice/llms.txt Initializes a DocumentStore with data validation and a series of migrations to transform data between schema versions. Set 'backwardsCompatible' to false for breaking changes. ```lua local PlayerStore = DocumentService.DocumentStore.new({ dataStore = DataStoreService:GetDataStore("PlayerData") :: any, check = Guard.Check(dataCheck), default = { Coins = 0, XP = 0, Level = 1, Badges = {} }, lockSessions = true, migrations = { -- Migration 1: add XP field (backwards compatible — old servers can still read) { backwardsCompatible = true, migrate = function(data) data.XP = data.XP or 0 return data end, }, -- Migration 2: add Level derived from XP (backwards compatible) { backwardsCompatible = true, migrate = function(data) data.Level = data.Level or math.floor(data.XP / 100) + 1 return data end, }, -- Migration 3: rename Coins to Gold — BREAKING change { backwardsCompatible = false, migrate = function(data) return { Gold = data.Coins, -- rename field XP = data.XP, Level = data.Level, Badges = data.Badges or {}, } end, }, }, }) ``` -------------------------------- ### DocumentStore.new Source: https://context7.com/anthony0br/documentservice/llms.txt Creates a new DocumentStore, binding it to a Roblox DataStore with a schema, default data, optional migrations, and session-lock configuration. Should be created once per server per DataStore and stored in a module to avoid duplicate sessions. Automatically registers a `BindToClose` handler to save and close all open documents on server shutdown. ```APIDOC ## DocumentStore.new ### Description Creates a new DocumentStore, binding it to a Roblox DataStore with a schema, default data, optional migrations, and session-lock configuration. Should be created once per server per DataStore and stored in a module to avoid duplicate sessions. Automatically registers a `BindToClose` handler to save and close all open documents on server shutdown. ### Parameters - **dataStore** (DataStore) - Required - The Roblox DataStore to bind to. - **check** (function) - Required - A function that validates and transforms data. - **default** (table) - Required - The default data to use if no data exists. - **migrations** (table) - Optional - A list of migration functions to apply to the data. - **lockSessions** (boolean) - Optional - Enables session locking for the document store. - **bindToClose** (boolean) - Optional - Automatically registers a `BindToClose` handler for saving on server shutdown. ### Example ```lua local DataStoreService = game:GetService("DataStoreService") local DocumentService = require(path.to.DocumentService) local Guard = require(path.to.Guard) type PlayerData = { Coins: number, XP: number, Level: number, } local DataInterface = { Coins = Guard.Integer, XP = Guard.Integer, Level = Guard.Integer, } local function dataCheck(value: unknown): PlayerData assert(type(value) == "table", "Data must be a table") local v: any = value return { Coins = DataInterface.Coins(v.Coins), XP = DataInterface.XP(v.XP), Level = DataInterface.Level(v.Level), } end local PlayerStore = DocumentService.DocumentStore.new({ dataStore = DataStoreService:GetDataStore("PlayerData") :: any, check = Guard.Check(dataCheck), default = { Coins = 0, XP = 0, Level = 1 }, migrations = {}, lockSessions = true, bindToClose = true, }) ``` ``` -------------------------------- ### Open Player Document with Error Handling Source: https://github.com/anthony0br/documentservice/blob/main/docs/opening.md This code snippet, intended for `Players.PlayerAdded`, opens a player document. It includes logic to handle session locks by attempting to 'steal' the document and provides specific player kick messages for different error reasons like `BackwardsCompatibilityError` and `RobloxAPIError`. ```lua local document = store:GetDocument(tostring(player.UserId)) local result = document:OpenAsync() -- DocumentService retries 5 times over 16 seconds, so it is safe to steal -- after a failed :OpenAsync! if not result.success and result.reason == "SessionLockedError" then document:Steal() result = document:OpenAsync() end if not result.success then if result.reason == "BackwardsCompatibilityError" then player:Kick( "You joined an old server which does not support your saved data." .. "Please try joining another server. If this persists, contact a developer." ) end if result.reason == "RobloxAPIError" then player:Kick("Failed to load data due to a Roblox service issue. Try again later.") end player:Kick( `Failed to load data: {result.reason}. Please screenshot this message and report it to a developer.` ) return false end ``` -------------------------------- ### Document:OpenAndUpdateAsync Source: https://context7.com/anthony0br/documentservice/llms.txt Opens a document and applies a transform in a single atomic DataStore operation. This is designed for non-session-locked documents where a one-off update is needed without keeping the document open. It fires both Open and Update hooks/signals. ```APIDOC ## Document:OpenAndUpdateAsync Opens a document and applies a transform in a single atomic DataStore operation. Designed for non-session-locked documents where you need a one-off update without keeping the document open. Fires both Open and Update hooks/signals. ``` -------------------------------- ### Attach Synchronous Hook Before Document Operation Source: https://context7.com/anthony0br/documentservice/llms.txt Attaches a callback that runs synchronously before an operation like 'Open', 'Close', 'Update', or 'Read'. Returns a cleanup function to remove the hook. Use 'OnceBefore' for single-use hooks. ```lua -- Log every Open attempt for this document local cancelHook = document:HookBefore("Open", function() print(`[DocumentService] Opening document: {tostring(player.UserId)}`) end) -- Remove the hook when it is no longer needed cancelHook() -- Single-use hook with OnceBefore document:OnceBefore("Close", function() print("Document is about to close for the first time") end) ``` -------------------------------- ### Read Document Asynchronously Source: https://context7.com/anthony0br/documentservice/llms.txt Reads the latest data directly from the DataStore, running migrations and the check function without saving changes. Does not require the document to be open. Returns a ReadResult; a SchemaError is returned if the key has never been opened. ```lua -- Read a player's data from an admin script without opening it local adminDoc = PlayerStore:GetDocument("12345678") local result = adminDoc:ReadAsync() if result.success then print("Player coins:", result.data.Coins) elseif result.reason == "SchemaError" then print("Key has no DocumentService data yet") elseif result.reason == "RobloxAPIError" then warn("DataStore unavailable") end ``` -------------------------------- ### Check Document Availability Asynchronously Source: https://context7.com/anthony0br/documentservice/llms.txt Checks if the document is available (not session-locked by another server). For non-session-locked stores, this always returns true. Useful for checking player activity on other servers before cross-server edits. ```lua -- Check before cross-server data edit local targetDoc = PlayerStore:GetDocument("87654321") local result = targetDoc:IsAvailableAsync() if result.success then if result.data then print("Document is available — safe to open") else print("Document is locked by another session") end else warn("DataStore check failed:", result.reason) end ``` -------------------------------- ### Type-Guard Helpers for DocumentStore and Document Source: https://context7.com/anthony0br/documentservice/llms.txt Provides helper functions 'isDocumentStore' and 'isDocument' to confirm if a variable holds an instance of DocumentService.DocumentStore or Document, respectively. Useful for defensive programming. ```lua local function printStoreInfo(store: any) if DocumentService.DocumentStore.isDocumentStore(store) then print("Valid DocumentStore") else warn("Not a DocumentStore!") end end local function printDocInfo(doc: any) if DocumentService.Document.isDocument(doc) then print("Valid Document, open =", doc:IsOpen()) else warn("Not a Document!") end end ``` -------------------------------- ### Open and Update Document Atomically Source: https://context7.com/anthony0br/documentservice/llms.txt OpenAndUpdateAsync is designed for one-off atomic updates on non-session-locked documents without keeping them open. It fires both Open and Update hooks/signals. ```lua -- One-off atomic update on a non-session-locked house document local HouseStore = DocumentService.DocumentStore.new({ dataStore = DataStoreService:GetDataStore("Houses") :: any, check = Guard.Check(houseCheck), default = { Owner = "", Furniture = {} }, lockSessions = false, }) local houseDoc = HouseStore:GetDocument("house_42") local result = houseDoc:OpenAndUpdateAsync(function(data: HouseData): HouseData return { Owner = data.Owner, Furniture = { table.unpack(data.Furniture), { type = "Chair", x = 5, y = 3 } }, } end) if result.success then print("House updated, furniture count:", #result.data.Furniture) end ``` -------------------------------- ### Create Player Data Check Function with Guard Source: https://github.com/anthony0br/documentservice/blob/main/docs/tutorial.md Implement a function to validate player data against a defined schema using Guard. This ensures data integrity before it's processed or saved. ```lua local DataInterface = { Coins = Guard.Integer, XP = Guard.Integer, } local function dataCheck(value: unknown): DataSchema assert(type(value) == "table", "Data must be a table") local Value: any = value return { Coins = DataInterface.Coins(Value.Coins), XP = DataInterface.XP(Value.XP), } end ``` -------------------------------- ### Handle Document Signals Source: https://context7.com/anthony0br/documentservice/llms.txt Listens for document lifecycle events like opening, closing, updating, reading, and cache changes. Signals fire on success and failure. Listeners run immediately until the first yield. ```lua -- Wait for a document to finish opening before accessing it local document = PlayerStore:GetDocument(tostring(player.UserId)) -- Non-blocking: attach a one-shot listener document:GetOpenedSignal():Once(function(result) if result.success then print("Document opened! Coins:", result.data.Coins) else warn("Open failed:", result.reason) end end) -- Or block the current thread until it opens local openResult = document:GetOpenedSignal():Wait() -- React to every cache update (e.g. replicate to client) document:GetCacheChangedSignal():Connect(function(newData) -- Fire a RemoteEvent to sync the client DataReplication:FireClient(player, newData) end) -- Disconnect a signal listener when no longer needed local connection = document:GetUpdatedSignal():Connect(function(result) print("Saved to DataStore:", result.success) end) connection:Disconnect() ``` -------------------------------- ### Wait for Document Open Signal Source: https://github.com/anthony0br/documentservice/blob/main/docs/waiting.md Use this method to block execution until the document is opened. Ensure the document object is valid before calling. ```lua document:GetOpenedSignal():Wait() ``` -------------------------------- ### Gracefully Shut Down DocumentStore Source: https://context7.com/anthony0br/documentservice/llms.txt Manually triggers a graceful shutdown of all open documents in the store, ensuring all data is saved and closed. This is automatically handled by the 'BindToClose' handler but can be called explicitly. ```lua -- Manually trigger a graceful shutdown (e.g. during a planned maintenance restart) local function gracefulShutdown() print("Starting graceful shutdown...") PlayerStore:CloseAllDocumentsAsync() print("All player documents saved and closed.") end ``` -------------------------------- ### Edit Player Data using SetCache Source: https://github.com/anthony0br/documentservice/blob/main/docs/tutorial.md Modify player data by cloning the current cache, updating the desired fields, and then setting the modified clone back to the cache. This ensures immutable updates and prevents bugs. ```lua local document = YourPlayerDataModule:GetDocument(player) if not document:IsOpen() then -- See "Waiting for a Document to Open" page if you need to wait for the document to be open end -- We need to clone the table and any sub-tables we intend to edit, since -- DocumentService freezes tables on SetCache. -- This forces immutable updates and helps you avoid creating bugs! local documentClone = table.clone(document:GetCache()) documentClone.Coins = 99 documentClone.XP = 99 document:SetCache(documentClone) ``` -------------------------------- ### Document:ReadAsync Source: https://context7.com/anthony0br/documentservice/llms.txt Reads the latest data directly from the DataStore, running migrations and the check function, without saving any changes. This operation does not require the document to be open and returns a `ReadResult`. ```APIDOC ## Document:ReadAsync Reads the latest data directly from the DataStore, running migrations and the check function, without saving any changes. Does not require the document to be open. Returns a `ReadResult` — a `SchemaError` is returned if the key has never been opened with DocumentService. ```lua -- Read a player's data from an admin script without opening it local adminDoc = PlayerStore:GetDocument("12345678") local result = adminDoc:ReadAsync() if result.success then print("Player coins:", result.data.Coins) elseif result.reason == "SchemaError" then print("Key has no DocumentService data yet") elseif result.reason == "RobloxAPIError" then warn("DataStore unavailable") end ``` ``` -------------------------------- ### Erase Document Asynchronously Source: https://context7.com/anthony0br/documentservice/llms.txt Permanently removes all data for the document's key from the DataStore. The document must not be open. Use this to comply with GDPR right-of-erasure requests. ```lua -- GDPR erasure endpoint (document must be closed first) local function erasePlayerData(userId: number) local document = PlayerStore:GetDocument(tostring(userId)) assert(not document:IsOpen(), "Close the document before erasing") local result = document:EraseAsync() if result.success then print(`Erased all data for userId {userId}`) else warn(`Erasure failed: {result.reason}`) end end ``` -------------------------------- ### Update Player Data in Cache Source: https://github.com/anthony0br/documentservice/blob/main/docs/cache.md Clone the existing cache, modify the data, and then set the updated cache. For Robux transactions, ensure `SaveAsync` is called to persist changes. ```lua local data = table.clone(document:GetCache()) data.Points += points document:SetCache(newData) ``` -------------------------------- ### Document:HookBefore Source: https://context7.com/anthony0br/documentservice/llms.txt Attaches a synchronous callback that executes before a specified document operation (Open, Close, Update, Read). It returns a cleanup function to remove the hook. Deprecated `HookAfter` and `HookFail` are superseded by signals. ```APIDOC ## Document:HookBefore ### Description Attaches a callback that runs synchronously before an operation (`"Open"`, `"Close"`, `"Update"`, or `"Read"`). Useful for logging and debugging. Returns a cleanup function to remove the hook. `HookAfter` and `HookFail` follow the same pattern but are deprecated in favour of signals in v1.2.0+. ### Usage ```lua -- Log every Open attempt for this document local cancelHook = document:HookBefore("Open", function() print(`[DocumentService] Opening document: {tostring(player.UserId)}`) end) -- Remove the hook when it is no longer needed cancelHook() -- Single-use hook with OnceBefore document:OnceBefore("Close", function() print("Document is about to close for the first time") end) ``` ``` -------------------------------- ### Define Player Data Schema Type Source: https://github.com/anthony0br/documentservice/blob/main/docs/tutorial.md Define the structure of your player data using a Lua type. This helps in organizing and understanding the data fields. ```lua type DataSchema = { Coins: number, XP: number, } ``` -------------------------------- ### Document Signals Source: https://context7.com/anthony0br/documentservice/llms.txt Documents emit five signals: `GetOpenedSignal`, `GetClosedSignal`, `GetUpdatedSignal`, `GetReadSignal`, and `GetCacheChangedSignal`. These signals fire on both success and failure, and listeners are executed immediately. ```APIDOC ## Document Signals Each Document exposes five signals: `GetOpenedSignal`, `GetClosedSignal`, `GetUpdatedSignal`, `GetReadSignal`, and `GetCacheChangedSignal`. Signals fire on both success and failure outcomes. Listeners are wrapped in `task.spawn` and run immediately until the first yield. ```lua -- Wait for a document to finish opening before accessing it local document = PlayerStore:GetDocument(tostring(player.UserId)) -- Non-blocking: attach a one-shot listener document:GetOpenedSignal():Once(function(result) if result.success then print("Document opened! Coins:", result.data.Coins) else warn("Open failed:", result.reason) end end) -- Or block the current thread until it opens local openResult = document:GetOpenedSignal():Wait() -- React to every cache update (e.g. replicate to client) document:GetCacheChangedSignal():Connect(function(newData) -- Fire a RemoteEvent to sync the client DataReplication:FireClient(player, newData) end) -- Disconnect a signal listener when no longer needed local connection = document:GetUpdatedSignal():Connect(function(result) print("Saved to DataStore:", result.success) end) connection:Disconnect() ``` ``` -------------------------------- ### Document:IsAvailableAsync Source: https://context7.com/anthony0br/documentservice/llms.txt Checks if the document is available, meaning it is not currently session-locked by another server. For documents not using session locking, this method always returns `true`. It is useful for determining if a player is active on another server before attempting a cross-server data edit. ```APIDOC ## Document:IsAvailableAsync Checks whether the document is available (not session-locked by another server). For non-session-locked stores this always returns `true`. Useful for checking if a player is active on another server before attempting a cross-server edit. ```lua -- Check before cross-server data edit local targetDoc = PlayerStore:GetDocument("87654321") local result = targetDoc:IsAvailableAsync() if result.success then if result.data then print("Document is available — safe to open") else print("Document is locked by another session") end else warn("DataStore check failed:", result.reason) end ``` ``` -------------------------------- ### Document:Steal Source: https://context7.com/anthony0br/documentservice/llms.txt Marks the document to forcibly take over an existing session lock on the next `:OpenAsync` call. This is only applicable to session-locked documents and should be used cautiously after confirming the previous session is likely inactive. ```APIDOC ## Document:Steal Marks the document so that the next `:OpenAsync` call will forcibly take over any existing session lock. Only applicable to session-locked documents. Use only after confirming the previous session is dead (DocumentService retries for ~16 seconds, so after 5 failed attempts the old session is very likely gone). ```lua local result = document:OpenAsync() if not result.success and result.reason == "SessionLockedError" then -- After 5 retries (~16 s) the old lock has almost certainly expired document:Steal() result = document:OpenAsync() if not result.success then warn("Still could not open after stealing lock:", result.reason) end end ``` ``` -------------------------------- ### Document:GetCache and Document:SetCache Source: https://context7.com/anthony0br/documentservice/llms.txt Retrieves an immutable snapshot of the document's in-memory cache or replaces the cache with a new value. These operations are only available on session-locked documents and should be used in pairs without yielding in between. Always use immutable operations on nested tables. ```APIDOC ## Document:GetCache / Document:SetCache `GetCache` returns the current in-memory snapshot of the document data (immutable — frozen by `table.freeze`). `SetCache` replaces the cache with a new value and fires the `CacheChangedSignal`. Only available on session-locked documents. Never yield between a `GetCache` and `SetCache` pair. Always use immutable (table.clone) operations on nested tables. ``` -------------------------------- ### DocumentStore.isDocumentStore / Document.isDocument Source: https://context7.com/anthony0br/documentservice/llms.txt Provides type-guard helper functions to verify if a given value is an instance of `DocumentStore` or `Document`. These are useful for defensive programming within utility functions. ```APIDOC ## DocumentStore.isDocumentStore / Document.isDocument ### Description Type-guard helpers to confirm that an arbitrary value is a `DocumentStore` or `Document` instance, respectively. Useful for defensive checks in utility functions. ### Usage ```lua local function printStoreInfo(store: any) if DocumentService.DocumentStore.isDocumentStore(store) then print("Valid DocumentStore") else warn("Not a DocumentStore!") end end local function printDocInfo(doc: any) if DocumentService.Document.isDocument(doc) then print("Valid Document, open =", doc:IsOpen()) else warn("Not a Document!") end end ``` ``` -------------------------------- ### Document:UpdateAsync Source: https://context7.com/anthony0br/documentservice/llms.txt Performs an atomic `UpdateAsync` transaction against the DataStore. For session-locked documents, it updates the cache and then persists it. For non-session-locked documents, it receives the current DataStore value and must return the updated value. Returns a `WriteResult`. ```APIDOC ## Document:UpdateAsync Performs an atomic `UpdateAsync` transaction against the DataStore. For session-locked documents, the transform runs against the current cache (which is updated immediately), then the cache value is persisted. For non-session-locked documents, the transform receives the current DataStore value and must return the updated value. Returns a `WriteResult`. ``` -------------------------------- ### Close Document Asynchronously Source: https://context7.com/anthony0br/documentservice/llms.txt Saves the document, removes the session lock, and cancels autosave. Call this when done with a document, typically on PlayerRemoving. If saving fails, the document remains open. ```lua game:GetService("Players").PlayerRemoving:Connect(function(player: Player) local document = PlayerStore:GetDocument(tostring(player.UserId)) if not document:IsOpen() then return end local result = document:CloseAsync() if not result.success then -- Data not saved — log the error for investigation warn(`Failed to close document for {player.Name}: {result.reason}`) else print(`Document closed and saved for {player.Name}`) end end) ``` -------------------------------- ### Steal Session Lock Source: https://context7.com/anthony0br/documentservice/llms.txt Marks the document for forceful takeover on the next :OpenAsync call. Only applicable to session-locked documents. Use only after confirming the previous session is likely dead (after ~16 seconds or 5 failed attempts). ```lua local result = document:OpenAsync() if not result.success and result.reason == "SessionLockedError" then -- After 5 retries (~16 s) the old lock has almost certainly expired document:Steal() result = document:OpenAsync() if not result.success then warn("Still could not open after stealing lock:", result.reason) end end ``` -------------------------------- ### Persist Cache to DataStore Source: https://context7.com/anthony0br/documentservice/llms.txt SaveAsync persists the current cache to the DataStore without transforming data. It's equivalent to UpdateAsync with an identity transform and only available on session-locked documents. The cache is always updated before the DataStore write. ```lua -- Force an immediate save (e.g. after a Robux purchase) local result = document:SaveAsync() if not result.success then warn(`Save failed for key {document._key}: {result.reason}`) -- Optionally retry, notify the player, or log to an analytics service end ``` -------------------------------- ### Validate Storable Data with SaveUtil.assertStorable Source: https://context7.com/anthony0br/documentservice/llms.txt Recursively validates if a value can be stored in a Roblox DataStore by checking for unsupported types like NaN, cyclic tables, functions, or userdata. Useful for pre-flight validation before creating a DocumentStore. ```lua local SaveUtil = DocumentService.SaveUtil -- Pre-flight check before constructing a DocumentStore local myDefault = { Name = "Player", Scores = { 10, 20, 30 }, Active = true, } local ok, err = pcall(SaveUtil.assertStorable, myDefault) if not ok then error("Default data is not storable: " .. err) end -- Example of values that would fail: -- SaveUtil.assertStorable(math.huge) -- NaN / inf -- SaveUtil.assertStorable({ [1]="a", x="b" }) -- mixed index types -- SaveUtil.assertStorable(Vector3.new(1,0,0)) -- userdata ``` -------------------------------- ### Document:SaveAsync Source: https://context7.com/anthony0br/documentservice/llms.txt Persists the current cache to the DataStore without transforming the data. This is equivalent to `UpdateAsync` with an identity transform and is only available on session-locked documents. The cache is always updated before the DataStore write, ensuring data is never lost even if this call fails. ```APIDOC ## Document:SaveAsync Persists the current cache to the DataStore without transforming the data. Equivalent to `UpdateAsync` with an identity transform. Only available on session-locked documents. The cache is always updated before the DataStore write, so data is never lost even if this call fails. ``` -------------------------------- ### Atomic UpdateAsync on Non-Session-Locked Document Source: https://context7.com/anthony0br/documentservice/llms.txt Use UpdateAsync for atomic transactions on non-session-locked documents. The transform runs against the current cache, which is updated immediately, and then persisted. Handles potential errors like SessionLockedError or RobloxAPIError. ```lua -- Non-session-locked shared document (e.g. a guild leaderboard) local GuildStore = DocumentService.DocumentStore.new({ dataStore = DataStoreService:GetDataStore("Guilds") :: any, check = Guard.Check(guildCheck), default = { Members = {}, TotalPoints = 0 }, lockSessions = false, -- allows multi-server editing }) local guildDoc = GuildStore:GetDocument("guild_abc") guildDoc:OpenAsync() -- Atomically add points from this server's contribution local result = guildDoc:UpdateAsync(function(data: GuildData): GuildData return { Members = data.Members, TotalPoints = data.TotalPoints + 500, } end) if result.success then print("Guild now has", result.data.TotalPoints, "total points") elseif result.reason == "SessionLockedError" then warn("Lock stolen by another server") elseif result.reason == "RobloxAPIError" then warn("DataStore API failure") end ``` -------------------------------- ### Document:CloseAsync Source: https://context7.com/anthony0br/documentservice/llms.txt Saves the document, removes the session lock, cancels autosave, and marks the document as closed. This should be called when finished with a document, typically on `Players.PlayerRemoving`. ```APIDOC ## Document:CloseAsync Saves the document (if session-locked), removes the session lock, cancels the autosave loop, and marks the document as closed. Must be called when you are done with a document — typically on `Players.PlayerRemoving`. If the save fails, the document remains open. `DocumentStore:CloseAllDocumentsAsync` is called automatically via `BindToClose`. ```lua game:GetService("Players").PlayerRemoving:Connect(function(player: Player) local document = PlayerStore:GetDocument(tostring(player.UserId)) if not document:IsOpen() then return end local result = document:CloseAsync() if not result.success then -- Data not saved — log the error for investigation warn(`Failed to close document for {player.Name}: {result.reason}`) else print(`Document closed and saved for {player.Name}`) end end) ``` ``` -------------------------------- ### SaveUtil.assertStorable Source: https://context7.com/anthony0br/documentservice/llms.txt Recursively validates if a value can be stored in a Roblox DataStore by checking for JSON serializability. It throws an error for values containing NaN, cyclic tables, metatables, functions, threads, userdata, mixed-index tables, or non-sequential numeric arrays. This function is called internally on every write but can also be used for pre-flight validation. ```APIDOC ## SaveUtil.assertStorable ### Description Recursively validates that a value can be stored in a Roblox DataStore (JSON-serialisable). Throws an error if the value contains NaN, cyclic tables, metatables, functions, threads, userdata, mixed-index tables, or non-sequential numeric arrays. Called internally on every write, but also useful for pre-flight validation. ### Usage ```lua local SaveUtil = DocumentService.SaveUtil -- Pre-flight check before constructing a DocumentStore local myDefault = { Name = "Player", Scores = { 10, 20, 30 }, Active = true, } local ok, err = pcall(SaveUtil.assertStorable, myDefault) if not ok then error("Default data is not storable: " .. err) end -- Example of values that would fail: -- SaveUtil.assertStorable(math.huge) -- NaN / inf -- SaveUtil.assertStorable({ [1]="a", x="b" }) -- mixed index types -- SaveUtil.assertStorable(Vector3.new(1,0,0)) -- userdata ``` ``` -------------------------------- ### Document:EraseAsync Source: https://context7.com/anthony0br/documentservice/llms.txt Permanently removes all data associated with the document's key from the DataStore. This action cannot be undone and requires the document to be closed prior to execution. It does not trigger any hooks or signals. ```APIDOC ## Document:EraseAsync Permanently removes all data for the document's key from the DataStore. The document must not be open. Does not run any hooks or signals. Use this to comply with GDPR right-of-erasure requests. ```lua -- GDPR erasure endpoint (document must be closed first) local function erasePlayerData(userId: number) local document = PlayerStore:GetDocument(tostring(userId)) assert(not document:IsOpen(), "Close the document before erasing") local result = document:EraseAsync() if result.success then print(`Erased all data for userId {userId}`) else warn(`Erasure failed: {result.reason}`) end end ``` ``` -------------------------------- ### DocumentStore:CloseAllDocumentsAsync Source: https://context7.com/anthony0br/documentservice/llms.txt Closes all currently open documents in the store efficiently, respecting DataStore request limits. It also ensures any documents in the process of opening are closed. The function yields until all documents are fully closed. ```APIDOC ## DocumentStore:CloseAllDocumentsAsync ### Description Closes every open document in the store as quickly as the DataStore request budget allows. Automatically called by the `BindToClose` handler registered in `DocumentStore.new`. Also waits for any documents that are mid-open before closing them. Yields until all documents are fully closed. ### Usage ```lua -- Manually trigger a graceful shutdown (e.g. during a planned maintenance restart) local function gracefulShutdown() print("Starting graceful shutdown...") PlayerStore:CloseAllDocumentsAsync() print("All player documents saved and closed.") end ``` ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.