@quenty/datastore 13.35.0 → 13.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [13.36.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/datastore@13.35.0...@quenty/datastore@13.36.0) (2026-04-15)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * wait for data cleanup before attempting load ([#678](https://github.com/Quenty/NevermoreEngine/issues/678)) ([80adc48](https://github.com/Quenty/NevermoreEngine/commit/80adc48669603d9a30f1bb4aa8c45d09e995ada6))
12
+
13
+
14
+
15
+
16
+
6
17
  # [13.35.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/datastore@13.34.0...@quenty/datastore@13.35.0) (2026-04-14)
7
18
 
8
19
  **Note:** Version bump only for package @quenty/datastore
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quenty/datastore",
3
- "version": "13.35.0",
3
+ "version": "13.36.0",
4
4
  "description": "Quenty's Datastore implementation for Roblox",
5
5
  "keywords": [
6
6
  "Roblox",
@@ -50,5 +50,5 @@
50
50
  "publishConfig": {
51
51
  "access": "public"
52
52
  },
53
- "gitHead": "f8273b1b201921976953174e30958440e44e64cb"
53
+ "gitHead": "da500f4af908cef350ee386cf472a102e8e53437"
54
54
  }
@@ -78,6 +78,7 @@ export type PlayerDataStoreManager =
78
78
  _keyGenerator: KeyGenerator,
79
79
  _datastores: { [PlayerUserId]: DataStore.DataStore },
80
80
  _removing: { [PlayerUserId]: boolean },
81
+ _removingPromises: { [PlayerUserId]: Promise.Promise<any> },
81
82
  _pendingSaves: PendingPromiseTracker.PendingPromiseTracker<any>,
82
83
  _removingCallbacks: { RemovingCallback },
83
84
  _disableSavingInStudio: boolean?,
@@ -112,6 +113,7 @@ function PlayerDataStoreManager.new(
112
113
 
113
114
  self._datastores = {} -- [userId] = datastore
114
115
  self._removing = {} -- [player] = true
116
+ self._removingPromises = {} -- [player] = removal promise
115
117
  self._pendingSaves = PendingPromiseTracker.new()
116
118
  self._removingCallbacks = {} -- [func, ...]
117
119
 
@@ -194,6 +196,67 @@ function PlayerDataStoreManager.GetDataStore(
194
196
  return self:_createDataStore(userId)
195
197
  end
196
198
 
199
+ --[=[
200
+ Gets the datastore for a player, waiting for any in-progress removal/save first.
201
+ Use this in async flows to safely support fast leave/rejoin behavior.
202
+
203
+ @return Promise<DataStore>
204
+ ]=]
205
+ function PlayerDataStoreManager.PromiseDataStore(
206
+ self: PlayerDataStoreManager,
207
+ playerOrUserId: Player | PlayerUserId
208
+ ): Promise.Promise<DataStore.DataStore>
209
+ local userId = self:_toPlayerUserIdOrError(playerOrUserId)
210
+
211
+ return self:_promiseDataStoreByUserId(userId)
212
+ end
213
+
214
+ function PlayerDataStoreManager._promiseDataStoreByUserId(
215
+ self: PlayerDataStoreManager,
216
+ userId: PlayerUserId
217
+ ): Promise.Promise<DataStore.DataStore>
218
+ local dataStore = self:GetDataStore(userId)
219
+ if dataStore then
220
+ return Promise.resolved(dataStore)
221
+ end
222
+
223
+ return self:_promiseWaitForRemoving(userId):Then(function()
224
+ return self:_promiseDataStoreByUserId(userId)
225
+ end)
226
+ end
227
+
228
+ function PlayerDataStoreManager._promiseWaitForRemoving(
229
+ self: PlayerDataStoreManager,
230
+ userId: PlayerUserId
231
+ ): Promise.Promise<()>
232
+ local removingPromise = self._removingPromises[userId]
233
+ if removingPromise then
234
+ return removingPromise:Then(function()
235
+ return nil
236
+ end)
237
+ end
238
+
239
+ if self._removing[userId] then
240
+ return Promise.defer(function(resolve, reject)
241
+ local elapsed = 0
242
+ while self._removing[userId] do
243
+ elapsed += task.wait()
244
+
245
+ if elapsed >= 15 then
246
+ warn(
247
+ `[PlayerDataStoreManager] - Last session cleanup for {userId} taking longer than 15 seconds. Rejecting.`
248
+ )
249
+ reject()
250
+ break
251
+ end
252
+ end
253
+ resolve()
254
+ end)
255
+ end
256
+
257
+ return Promise.resolved()
258
+ end
259
+
197
260
  function PlayerDataStoreManager:_toPlayerUserIdOrError(playerOrUserId: Player | PlayerUserId): PlayerUserId
198
261
  if typeof(playerOrUserId) == "Instance" and playerOrUserId:IsA("Player") then
199
262
  return playerOrUserId.UserId
@@ -286,7 +349,7 @@ function PlayerDataStoreManager._removePlayerDataStore(self: PlayerDataStoreMana
286
349
  end
287
350
  end
288
351
 
289
- PromiseUtils.all(removingPromises)
352
+ local removalPromise = PromiseUtils.all(removingPromises)
290
353
  :Then(function()
291
354
  return datastore:SaveAndCloseSession()
292
355
  end)
@@ -295,6 +358,13 @@ function PlayerDataStoreManager._removePlayerDataStore(self: PlayerDataStoreMana
295
358
  self._removing[userId] = nil
296
359
  end)
297
360
 
361
+ self._removingPromises[userId] = removalPromise
362
+ removalPromise:Finally(function()
363
+ if self._removingPromises[userId] == removalPromise then
364
+ self._removingPromises[userId] = nil
365
+ end
366
+ end)
367
+
298
368
  -- Prevent double removal or additional issues
299
369
  self._datastores[userId] = nil
300
370
  self._maid._savingConns[userId] = nil
@@ -107,7 +107,7 @@ function PlayerDataStoreService.PromiseDataStore(
107
107
  player: Player | number
108
108
  ): Promise.Promise<DataStore.DataStore>
109
109
  return self:PromiseManager():Then(function(manager)
110
- return manager:GetDataStore(player)
110
+ return manager:PromiseDataStore(player)
111
111
  end)
112
112
  end
113
113