{"id":"elentium/edstest","name":"edstest","scope":"elentium","platform":"roblox","description":"Mirrored from the Wally registry.","version":"1.6.1","latest":"1.6.1","versions":["1.6.0","1.6.1"],"license":"Apache-2.0","licenseRating":"safe","licenseCaveats":["Modified files must carry a notice of changes. If the package ships a NOTICE file, its attributions must be preserved.","License identified from the packaged LICENSE file; the manifest declared none."],"licenseVerified":true,"dependencies":{},"integrity":"ec1df7449ae55dabc570989da23db550b3a116a2ac1055264a6ebc5ebb83fb1a","likes":0,"downloads":0,"install":"forest install elentium/edstest","url":"https://forest.dev/p/roblox/elentium/edstest","files":"https://api.forest.dev/ai/package/roblox/elentium/edstest/files","readme":"# EliteDataStoreService V1.6.0 [by iamnotultra3 a.k.a Elite]\n\t\n**[APACHE 2.0 LICENSE]**\n\n*A powerful and efficient DataStoreService wrapper that handles most pain points for you, leaving you with big, yet safe control of data stores*\n\n\n# What this module does\n- *Handles DataStore request limits for you (no more dropped calls)*\n- *Strong argument checks for safer use*\n- *Clean IntelliSense support*\n- *Lightweight and efficient, minimal overhead*\n- *Exposes the same methods as DataStoreService (and more), but with built-in safety and better reliability*\n- *minimal overhead*\n- *rich logging*\n\n\n# Why not just use DataStoreService?\n**DataStoreService has a few big issues:**\n- Easy to hit request limits and lose calls\n- Errors can happen even if your code is correct\n- API surface is bloated and not always dev-friendly\n\n**This module solves those problems by queueing requests, validating inputs, and surfacing errors without sandboxing any functionality.**\n\n\n# Notes\n- Roblox still enforces the 4MB per key size limit\n- The module is battle tested, you do not have to worry about bugs! (if there is any issue(typo, small inconsistency) pls tell me)\n- A basic understanding of DataStoreService is recommended:\n\"https://create.roblox.com/docs/reference/engine/classes/DataStoreService\"\n- The module keeps being enhanced in performance and features, there is also an upcoming CloudService module that is an all-in-one datastore solution, which uses EliteDataStoreService as middleware between the module and DataStores\n- After some benchmark tests, this module showed almost no difference in performance compared to DataStoreService\n- This module is never meant to be a full wrapper like ProfileStore, its just a layer of protection against most annoying downsides of DataStoreService, almost all control is given to you\n\n# Best Practices\n- Prioritize Player Saves on Shutdown: During the game:BindToClose() event, set a flag in your saving logic to ensure all remaining player data saves use the prioritize = true argument. This allows player data saves to jump ahead of any lower-priority background tasks in the queue.\n- Check the success Flag: Always capture and check the first return value (success). If it's false, it means the underlying Roblox API call failed (e.g., internal service error, 500 error, etc.). You should log this and potentially revert any game-state changes associated with the failed operation.\n\n\n# Github\n• \"https://github.com/Elentium/EliteDataStoreService-REWORK-\"\n\n# Wally\n• \"elentium/elitedatastoreservice@1.5.2\"\n\n# Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md) for version history and update notes.\n\n**-- New Key Locking System (V1.5.0) --**\nThe key locking system allows concurrent reads when no write is happening or pending, while writes stay linear. This ensures reads waiting during writes get fresh data, and writes waiting during reads preserve data consistency.\n\n**Useful resources to learn from:**\n- *linked lists: \"https://www.youtube.com/watch?v=DTEraIOfoS0\"*,\n- *data stores:* [\n    *beginner: \"https://youtu.be/H-cDbjd5-bs?si=UZ1IJfiFhw1_EI6n\"*,\n    *intermediate: \"https://youtu.be/B446FyN1xtc?si=JoK9dPGpF1qva7HG\"*,\n    *advanced: \"https://create.roblox.com/docs/reference/engine/classes/DataStoreService\"*\n],\n- *metatables: \"https://youtu.be/bk8UVm-gxBs?si=Kjga1m_VFFPrbeWo\"*\n\n# Code examples\n\n**1. Average code**\n```luau\n--!strict\nlocal Players = game:GetService(\"Players\")\n\nlocal EliteDataStoreService = require(path.to.EliteDataStoreService)\n\nlocal EliteDataStore = EliteDataStoreService:GetGlobalDataStore()\n\nlocal PlayersData: { [number]: number } = {}\n\nlocal function OnPlayerAdded(Player: Player): ()\n    local DataSuccess, DataResult = EliteDataStore:Get(Player.UserId)\n    if not DataSuccess then\n        warn(`Failed to load data for player {Player.Name}, error: {DataResult}`)\n        Player:Kick(\"Failed to load your data, please rejoin\")\n    else\n        print(`Loaded data for player {Player.Name}, data: {DataResult}`)\n    end\n\n    DataResult = DataResult or 0\n    PlayersData[Player.UserId] = DataResult\n\n    local leaderstats: Folder = Instance.new(\"Folder\")\n    leaderstats.Name = \"leaderstats\"\n    leaderstats.Parent = Player\n\n    local Coins: IntValue = Instance.new(\"IntValue\")\n    Coins.Name = \"Coins\"\n    Coins.Value = DataResult\n    Coins.Parent = leaderstats\n\n    Coins:GetPropertyChangedSignal(\"Value\"):Connect(function()\n        PlayersData[Player.UserId] = Coins.Value\n    end)\nend\n\nlocal function OnPlayerRemoving(Player: Player): ()\n    local Data = PlayersData[Player.UserId]\n    if not Data then\n        warn(`Data for player {Player.Name} not found`)\n        return\n    end\n\n    PlayersData[Player.UserId] = nil\n    local success, result = EliteDataStore:Set(Player.UserId, Data, { Player.UserId })\n    if not success then\n        warn(`Failed to save data for player {Player.Name}, error: {result}`)\n    else\n        print(`Saved data for player {Player.Name}, saved data: {result}`)\n    end\nend\n\nPlayers.PlayerAdded:Connect(OnPlayerAdded)\n\nPlayers.PlayerRemoving:Connect(OnPlayerRemoving)\n\n\ngame:BindToClose(function()\n    for _, Player in Players:GetPlayers() do\n        task.spawn(OnPlayerRemoving, Player)\n    end\nend)\n```\n\n\n**2. Leaderboard example**\n```luau\n--!strict\nlocal Players = game:GetService(\"Players\")\n\nlocal EliteDataStoreService = require(path.to.EliteDataStoreService)\n\nlocal EliteDataStore = EliteDataStoreService:GetGlobalDataStore()\nlocal LeaderboardStore = EliteDataStoreService:GetOrderedDataStore(\"GlobalLB\")\n\nlocal PlayersData: { [number]: number } = {}\n\nlocal function OnPlayerAdded(Player: Player): ()\n    local DataSuccess, DataResult = EliteDataStore:Get(Player.UserId)\n    if not DataSuccess then\n        warn(`Failed to load data for player {Player.Name}, error: {DataResult}`)\n        Player:Kick(\"Failed to load your data, please rejoin\")\n    else\n        print(`Loaded data for player {Player.Name}, data: {DataResult}`)\n    end\n\n    DataResult = DataResult or 0\n    PlayersData[Player.UserId] = DataResult\n\n    local leaderstats: Folder = Instance.new(\"Folder\")\n    leaderstats.Name = \"leaderstats\"\n    leaderstats.Parent = Player\n\n    local Coins: IntValue = Instance.new(\"IntValue\")\n    Coins.Name = \"Coins\"\n    Coins.Value = DataResult\n    Coins.Parent = leaderstats\n\n    Coins:GetPropertyChangedSignal(\"Value\"):Connect(function()\n        PlayersData[Player.UserId] = Coins.Value\n    end)\nend\n\nlocal function OnPlayerRemoving(Player: Player): ()\n    local Data = PlayersData[Player.UserId]\n    if not Data then\n        warn(`Data for player {Player.Name} not found`)\n        return\n    end\n\n    PlayersData[Player.UserId] = nil\n    local success, result = EliteDataStore:Set(Player.UserId, Data, { Player.UserId })\n    if not success then\n        warn(`Failed to save data for player {Player.Name}, error: {result}`)\n    else\n        print(`Saved data for player {Player.Name}, saved data: {result}`)\n    end\n\n    local successLB, resultLB = LeaderboardStore:Set(Player.UserId, Data, { Player.UserId })\n    if not successLB then\n        warn(`Failed to save leaderboard data for player {Player.Name}, error: {result}`)\n    else\n        print(`Saved leaderboard data for player {Player.Name}, saved data: {result}`)\n    end\nend\n\nPlayers.PlayerAdded:Connect(OnPlayerAdded)\n\nPlayers.PlayerRemoving:Connect(OnPlayerRemoving)\n\n\ngame:BindToClose(function()\n    for _, Player in Players:GetPlayers() do\n        task.spawn(OnPlayerRemoving, Player)\n    end\nend)\n\nwhile task.wait(300) do\n    local LeaderboardPages = LeaderboardStore:GetSorted(false, 50)\n    local page = LeaderboardPages:GetCurrentPage()\n    while page and #page > 0 do\n        for rank, entry in page do\n            print(`[{rank}] : {entry.key} : {entry.value}`)\n        end\n\n        local success\n        success, page = LeaderboardPages:AdvanceToNextPage()\n        if not success then\n            warn(`Failed to advance to next page, error: {page}`)\n            break\n        end\n    end\nend\n```\n\n# API\n\n- *EliteDataStoreService:GetDataStore(DataStoreName: string, Scope: string?, Options: DataStoreOptions?) -> EliteDataStore*\n{\n    Status: \"Non-Yielding\",\n\t\n    Description: \"Creates an EliteDataStore object based on the given arguments\",\n\t\n    Arguments: (\n\t\n        DataStoreName: string - \"How the data store should be called\",\n\t\t\n        Scope: string? - \"The branch of the DataStore, does not share any data with other scopes, default scope: 'default'\",\n\t\t\n        Options: DataStoreOptions? - \"An instance used to enable/disable experimental features and v2 features\"\n\t\t\n    ),\n\t\n    Returns: EliteDataStore - \"The elite data store object\"\n\t\n},\n\n\n- *EliteDataStoreService:GetGlobalDataStore() -> EliteDataStore* {\n\n    Status: \"Non-Yielding\",\n\t\n    Description: \"Creates an EliteDataStore object that uses roblox Global DataStore\",\n\t\n    Arguments: (),\n\t\n    Returns: EliteDataStore - \"The elite data store object\"\n\t\n},\n\n\n- *EliteDataStoreService:GetOrderedDataStore(DataStoreName: string, Scope: string?) -> EliteOrderedDataStore* {\n\n    Status: \"Non-Yielding\",\n\t\n    Description: \"Creates an EliteOrderedDataStore object based on the given arguments\",\n\t\n    Arguments: (\n\t\n        DataStoreName: string - \"How the data store should be called\",\n\t\t\n        Scope: string? - \"The branch of the DataStore, does not share any data with other scopes, default scope: 'default'\"\n    ),\n\t\n    Returns: EliteOrderedDataStore - \"The elite ordered data store object\"\n\t\n},\n\n\n- *EliteDataStoreService:GetRequestBudgetForRequestType(RequestType: Enum.DataStoreRequestType) -> number* {\n\n    Status: \"Non-Yielding\",\n\t\n    Description: \"Works the same as roblox DataStoreService:GetRequestBudgetForRequestType\",\n\t\n    Arguments: (\n\t\n        RequestType: Enum.DataStoreRequestType - \"The type of request to get the remaining budget for\"\n\t\t\n    ),\n\t\n    Returns: number - \"The remaining budget for the given request type\"\n\t\n},\n\n\n- *EliteDataStoreService:ListDataStores(Prefix: string?, PageSize: number?, Cursor: string?, Prioritize: boolean?) -> (boolean, EliteDataStorePages<DataStoreListingPagesEntry>)* {\n\n    Status: \"Yielding\",\n\t\n    Description: \"Creates a query of game DataStores based on the given arguments\",\n\t\n    Arguments: (\n\t\n        Prefix: string? - \"Prefix to enumerate data stores that start with the given prefix\",\n\t\t\n        PageSize: number? - \"Number of items to be returned in each page. If no value is given, the engine sends a default value of 0 to the data store web service, which in turn defaults to 32 items per page\",\n\t\t\n        Cursor: string? - \"Cursor to continue iteration\",\n\t\t\n        Prioritize: boolean? - \"Whether to prioritize this request in the processing queue\"\n\t\t\n    ),\n\t\n    Returns: (boolean, EliteDataStorePages<DataStoreListingPagesEntry>) - \"Success flag and the elite pages object that is based on DataStoreListingPages, or error message if failed\"\n\t\n},\n\n\n- *EliteDataStoreService:SetIterationCycle(seconds: number) -> ()* {\n\n    Status: \"Non-Yielding\",\n\t\n    Description: \"Sets how often the processor iterates through the queues in seconds\",\n\t\n    Arguments: (\n\t\n        seconds: number - \"The iteration cycle duration\"\n\t\t\n    ),\n\t\n    Returns: () - \"Nothing\"\n\t\n},\n\n\n- *EliteDataStoreService:WaitForAllRequests() -> ()* {\n\n    Status: \"Yielding\",\n\t\n    Description: \"Yields until all pending requests in the queues are processed\",\n\t\n    Arguments: (),\n\t\n    Returns: () - \"Nothing\"\n\t\n},\n\n\n- *EliteDataStoreService:GetQueueSize() -> number* {\n\n    Status: \"Non-Yielding\",\n\t\n    Description: \"Returns the current size of the main queue\",\n\t\n    Arguments: (),\n\t\n    Returns: number - \"The number of requests in the main queue\"\n\t\n},\n\n\n- *EliteDataStoreService:GetPriorityQueueSize() -> number* {\n\n    Status: \"Non-Yielding\",\n\t\n    Description: \"Returns the current size of the priority queue\",\n\t\n    Arguments: (),\n\t\n    Returns: number - \"The number of requests in the priority queue\"\n\t\n},\n\n\n- *EliteDataStoreService:CheckDataStoreAccess() -> 'Access' | 'NoAccess' | 'NoInternet'* {\n\n    Status: \"Non-Yielding\",\n\t\n    Description: \"Checks the current access status to DataStoreService, primarily for Studio environments\",\n  \n    Arguments: (),\n  \n    Returns: 'Access' | 'NoAccess' | 'NoInternet' - \"The access status\"\n  \n},\n\n\n- *EliteDataStoreService:ReplaceDataStoreServiceWithCustomHandler(Handler: typeof(DataStoreService)) -> ()* {\n\n    Status: \"Non-Yielding\",\n\t\n    Description: \"Replaces the internal DataStoreService reference with a custom handler\",\n\t\n    Arguments: (\n\t\n        Handler: typeof(DataStoreService) - \"The custom DataStoreService-like object\"\n\t\t\n    ),\n\t\n    Returns: () - \"Nothing\"\n  \n},\n\n\n- *EliteDataStoreService:GuardCall(Method: (...any) -> (boolean, any), MaxRetries: number?, RetriesIntermission: number?, ExponentialBackoff: boolean?, ...: any) -> (boolean, any)* {\n\n    Status: \"Yielding\",\n\t\n    Description: \"Safely calls a method with retry logic on failure\",\n\t\n    Arguments: (\n\t\n        Method: (...any) -> (boolean, any) - \"The method to call, expected to return success and result\",\n\t\t\n        MaxRetries: number? - \"Maximum retry attempts, default: 5\",\n\t\t\n        RetriesIntermission: number? - \"Base delay between retries in seconds, default: 1\",\n\t\t\n        ExponentialBackoff: boolean? - \"Whether to use exponential backoff for delays, default: true\",\n\t\t\n        ...: any - \"Arguments to pass to the method\"\n\t\t\n    ),\n\t\n    Returns: (boolean, any) - \"Success flag and result from the method, or error if all retries fail\"\n\t\n},\n\n\n- *EliteDataStoreService:SetConstant(Constant: string, Value: any) -> ()* {\n\n    Status: \"Non-Yielding\",\n\t\n    Description: \"Sets a runtime constant. Valid constants: LOG_LEVEL, SHUTDOWN_YIELD, DEFAULT_MAX_RETRIES, DEFAULT_RETRIES_INTERMISSION, DEFAULT_EXPONENTIAL_BACKOFF\",\n\t\n    Arguments: (\n\t\n        Constant: string - \"The constant name to set\",\n\t\t\n        Value: any - \"The new value\"\n\t\t\n    ),\n\t\n    Returns: () - \"Nothing\"\n\t\n},\n\n\n- *EliteDataStore:CanRead(Key: string | number) -> boolean* {\n\n    Status: \"Non-Yielding\",\n\t\n    Description: \"Checks if the key is currently available for read operations\",\n\t\n    Arguments: (\n\t\n        Key: string | number - \"The key to check\"\n\t\t\n    ),\n\t\n    Returns: boolean - \"Whether the key can be read\"\n\t\n},\n\n- *EliteDataStore:CanWrite(Key: string | number) -> boolean* {\n\n    Status: \"Non-Yielding\",\n\t\n    Description: \"Checks if the key is currently available for write operations\",\n\t\n    Arguments: (\n\t\n        Key: string | number - \"The key to check\"\n\t\t\n    ),\n\n    Returns: boolean - \"Whether the key can be written to\"\n\t\n},\n\n- *EliteDataStore:Get(Key: string | number, Options: DataStoreGetOptions?, Prioritize: boolean?) -> (boolean, any)* {\n\n    Status: \"Yielding\",\n\t\n    Description: \"Retrieves the value associated with the key\",\n\t\n    Arguments: (\n  \n        Key: string | number - \"The key to retrieve\",\n\t\t\n        Options: DataStoreGetOptions? - \"Optional get options\",\n\t\t\n        Prioritize: boolean? - \"Whether to prioritize this request\"\n\t\t\n    ),\n\t\n    Returns: (boolean, any) - \"Success flag and the value, or error message if failed\"\n\t\n},\n\n- *EliteDataStore:Set(Key: string | number, Value: any, UserIds: {number}?, Options: DataStoreSetOptions?, Prioritize: boolean?) -> (boolean, any)* {\n\n    Status: \"Yielding\",\n\t\n    Description: \"Sets the value for the key\",\n\t\n    Arguments: (\n\t\n        Key: string | number - \"The key to set\",\n\t\t\n        Value: any - \"The value to store\",\n\t\t\n        UserIds: {number}? - \"Optional user IDs for attribution\",\n\t\t\n        Options: DataStoreSetOptions? - \"Optional set options\",\n\t\t\n        Prioritize: boolean? - \"Whether to prioritize this request\"\n\t\t\n    ),\n\t\n    Returns: (boolean, any) - \"Success flag and the version ID, or error message if failed\"\n\t\n},\n\n- *EliteDataStore:Increment(Key: string | number, Delta: number?, UserIds: {number}?, Options: DataStoreIncrementOptions?, Prioritize: boolean?) -> (boolean, number)* {\n\n    Status: \"Yielding\",\n\t\n    Description: \"Increments the numeric val","readmeTruncated":true,"readmeFull":"https://api.forest.dev/v1/package/elentium/roblox/edstest/1.6.1/readme"}