{"id":"twrblxdevs/profile-manager","name":"profile-manager","scope":"twrblxdevs","platform":"roblox","description":"A session-locked profile data manager for Roblox DataStores.","version":"0.1.2","latest":"0.1.2","versions":["0.1.0","0.1.1","0.1.2"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":true,"dependencies":{},"integrity":"e14ee1d1ae697922b1db3da1744af4d363e861c95632e856d427b9e509db90e7","likes":0,"downloads":0,"install":"forest install twrblxdevs/profile-manager","url":"https://forest.dev/p/roblox/twrblxdevs/profile-manager","files":"https://api.forest.dev/ai/package/roblox/twrblxdevs/profile-manager/files","readme":"# ProfileManager\n\nProfileManager is a small Roblox DataStore wrapper for player-style profile data. It gives you session locking, auto-save, automatic template reconciliation, user id association, mock stores for Studio testing, and a Wally package layout.\n\nIt is inspired by the workflow of ProfileStore, but this is its own implementation and intentionally keeps the surface area focused.\n\n## Install\n\nAdd the package to your game's `wally.toml` after publishing it under your Wally scope:\n\n```toml\n[dependencies]\nProfileManager = \"therb/profile-manager@0.1.0\"\n```\n\nThen install packages:\n\n```sh\nwally install\n```\n\n## Basic Usage\n\n```luau\nlocal Players = game:GetService(\"Players\")\nlocal ServerScriptService = game:GetService(\"ServerScriptService\")\n\nlocal ProfileManager = require(ServerScriptService.Packages.ProfileManager)\n\nlocal TEMPLATE = {\n\tCoins = 0,\n\tInventory = {},\n\tSettings = {\n\t\tMusic = true,\n\t},\n}\n\nlocal PlayerStore = ProfileManager.New(\"PlayerData\", TEMPLATE)\nlocal Profiles = {}\n\nPlayers.PlayerAdded:Connect(function(player)\n\tlocal profile = PlayerStore:StartSessionAsync(\"Player_\" .. player.UserId, {\n\t\tCancel = function()\n\t\t\treturn player.Parent ~= Players\n\t\tend,\n\t})\n\n\tif profile == nil then\n\t\tplayer:Kick(\"Your data could not be loaded. Please rejoin.\")\n\t\treturn\n\tend\n\n\tprofile:AddUserId(player.UserId)\n\n\tprofile.OnSessionEnd:Connect(function()\n\t\tProfiles[player] = nil\n\t\tplayer:Kick(\"Your data session ended. Please rejoin.\")\n\tend)\n\n\tProfiles[player] = profile\nend)\n\nPlayers.PlayerRemoving:Connect(function(player)\n\tlocal profile = Profiles[player]\n\tif profile ~= nil then\n\t\tProfiles[player] = nil\n\t\tprofile:EndSession()\n\tend\nend)\n```\n\n## API\n\n### `ProfileManager.New(storeName, template?, options?)`\n\nCreates a profile store backed by `DataStoreService:GetDataStore(storeName)`.\n\nOptions:\n\n- `AutoSaveInterval`: seconds between auto-saves. Defaults to `60`.\n- `LockTimeout`: seconds before a stale lock can be claimed. Defaults to `180`.\n- `LoadRetryDelay`: seconds between load attempts while another server owns the lock. Defaults to `2`.\n- `LoadTimeout`: maximum seconds to wait for a session by default. Defaults to `30`.\n- `SaveRetries`: DataStore retry attempts for saving/removing. Defaults to `3`.\n- `RetryDelay`: base retry delay for failed DataStore calls. Defaults to `2`.\n- `DataStoreScope`: optional DataStore scope.\n- `DisableAutoSave`: set to `true` to require manual saves.\n\n### `store:StartSessionAsync(key, params?)`\n\nLoads a profile and claims its session lock. Returns a `Profile` or `nil` when loading is cancelled, times out, or cannot acquire the lock.\n\nParams:\n\n- `Cancel`: optional function checked between retries.\n- `Steal`: force-claims the lock. Use only for debugging or recovery tooling.\n- `Timeout`: overrides the store load timeout for this call.\n\nAlias: `store:LoadProfileAsync(key, params?)`.\n\n### `store:GetAsync(key)`\n\nReads a profile without claiming a session. The returned profile will not auto-save and should be treated as read-only unless you know exactly what you are doing.\n\n### `store:RemoveAsync(key)`\n\nRemoves the key from the DataStore. This fails if the same store currently has an active session for that key.\n\n### `store.Mock`\n\nUses the same API against in-memory storage that disappears when the server shuts down.\n\n```luau\nlocal Store = ProfileManager.New(\"PlayerData\", TEMPLATE)\n\nif game:GetService(\"RunService\"):IsStudio() then\n\tStore = Store.Mock\nend\n```\n\n## Profile API\n\n- `profile.Data`: your mutable profile data table.\n- `profile.LastSavedData`: deep copy of the last successfully saved data.\n- `profile.MetaData`: created/updated timestamps, save count, session count, and meta tags.\n- `profile.UserIds`: user ids associated with this profile.\n- `profile.RobloxMetaData`: table saved as Roblox DataStore metadata.\n- `profile:IsActive()`: returns whether this server still owns the session.\n- `profile:Reconcile()`: manually fills missing fields from the template. Profiles are also reconciled automatically when loaded.\n- `profile:Save()`: saves immediately while active.\n- `profile:EndSession()`: final save, clears the session lock, and stops auto-save.\n- `profile:Release()`: alias for `EndSession`.\n- `profile:AddUserId(userId)` / `profile:RemoveUserId(userId)`.\n- `profile:SetMetaTag(name, value)` / `profile:GetMetaTag(name)`.\n- `profile.OnSave`, `profile.OnAfterSave`, `profile.OnLastSave`, `profile.OnSessionEnd`: simple signal objects.\n\n## Notes\n\nProfileManager uses a simple stale-lock model instead of ProfileStore's full MessagingService-assisted handoff. Keep `LockTimeout` comfortably higher than `AutoSaveInterval`, and always call `profile:EndSession()` when a player leaves.\n","readmeTruncated":false}