keeperboard 2.0.0 → 2.0.1
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/dist/index.d.mts +12 -2
- package/dist/index.d.ts +12 -2
- package/dist/index.js +33 -6
- package/dist/index.mjs +33 -6
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -306,6 +306,7 @@ declare class KeeperBoardSession {
|
|
|
306
306
|
private readonly defaultPlayerName;
|
|
307
307
|
private readonly cache;
|
|
308
308
|
private readonly retryQueue;
|
|
309
|
+
private cachedLimit;
|
|
309
310
|
private isSubmitting;
|
|
310
311
|
constructor(config: SessionConfig);
|
|
311
312
|
/** Get or create a persistent player GUID. */
|
|
@@ -328,7 +329,8 @@ declare class KeeperBoardSession {
|
|
|
328
329
|
* Get a combined snapshot: leaderboard entries (with `isCurrentPlayer` flag)
|
|
329
330
|
* plus the current player's rank if they're outside the top N.
|
|
330
331
|
*
|
|
331
|
-
* Uses cache if enabled and fresh.
|
|
332
|
+
* Uses cache if enabled and fresh. If a larger limit is requested than
|
|
333
|
+
* what's cached, the cache is invalidated and fresh data is fetched.
|
|
332
334
|
*/
|
|
333
335
|
getSnapshot(options?: {
|
|
334
336
|
limit?: number;
|
|
@@ -427,11 +429,17 @@ declare function validateName(input: string, options?: NameValidationOptions): s
|
|
|
427
429
|
|
|
428
430
|
/**
|
|
429
431
|
* Generic TTL cache with in-flight deduplication and background refresh.
|
|
432
|
+
*
|
|
433
|
+
* Features:
|
|
434
|
+
* - TTL-based expiration
|
|
435
|
+
* - In-flight request deduplication
|
|
436
|
+
* - Background refresh scheduling (handles concurrent refresh requests)
|
|
430
437
|
*/
|
|
431
438
|
declare class Cache<T> {
|
|
432
439
|
private data;
|
|
433
440
|
private fetchedAt;
|
|
434
441
|
private inflight;
|
|
442
|
+
private pendingRefresh;
|
|
435
443
|
private readonly ttlMs;
|
|
436
444
|
constructor(ttlMs: number);
|
|
437
445
|
/**
|
|
@@ -441,9 +449,11 @@ declare class Cache<T> {
|
|
|
441
449
|
getOrFetch(fetchFn: () => Promise<T>): Promise<T>;
|
|
442
450
|
/**
|
|
443
451
|
* Trigger a background refresh without awaiting the result.
|
|
444
|
-
* Returns immediately. If a fetch is already in flight,
|
|
452
|
+
* Returns immediately. If a fetch is already in flight, schedules
|
|
453
|
+
* the refresh to run after the current one completes.
|
|
445
454
|
*/
|
|
446
455
|
refreshInBackground(fetchFn: () => Promise<T>): void;
|
|
456
|
+
private startBackgroundFetch;
|
|
447
457
|
/** Invalidate the cache, forcing the next getOrFetch to re-fetch. */
|
|
448
458
|
invalidate(): void;
|
|
449
459
|
/** Get the cached value without fetching. Returns undefined if empty or stale. */
|
package/dist/index.d.ts
CHANGED
|
@@ -306,6 +306,7 @@ declare class KeeperBoardSession {
|
|
|
306
306
|
private readonly defaultPlayerName;
|
|
307
307
|
private readonly cache;
|
|
308
308
|
private readonly retryQueue;
|
|
309
|
+
private cachedLimit;
|
|
309
310
|
private isSubmitting;
|
|
310
311
|
constructor(config: SessionConfig);
|
|
311
312
|
/** Get or create a persistent player GUID. */
|
|
@@ -328,7 +329,8 @@ declare class KeeperBoardSession {
|
|
|
328
329
|
* Get a combined snapshot: leaderboard entries (with `isCurrentPlayer` flag)
|
|
329
330
|
* plus the current player's rank if they're outside the top N.
|
|
330
331
|
*
|
|
331
|
-
* Uses cache if enabled and fresh.
|
|
332
|
+
* Uses cache if enabled and fresh. If a larger limit is requested than
|
|
333
|
+
* what's cached, the cache is invalidated and fresh data is fetched.
|
|
332
334
|
*/
|
|
333
335
|
getSnapshot(options?: {
|
|
334
336
|
limit?: number;
|
|
@@ -427,11 +429,17 @@ declare function validateName(input: string, options?: NameValidationOptions): s
|
|
|
427
429
|
|
|
428
430
|
/**
|
|
429
431
|
* Generic TTL cache with in-flight deduplication and background refresh.
|
|
432
|
+
*
|
|
433
|
+
* Features:
|
|
434
|
+
* - TTL-based expiration
|
|
435
|
+
* - In-flight request deduplication
|
|
436
|
+
* - Background refresh scheduling (handles concurrent refresh requests)
|
|
430
437
|
*/
|
|
431
438
|
declare class Cache<T> {
|
|
432
439
|
private data;
|
|
433
440
|
private fetchedAt;
|
|
434
441
|
private inflight;
|
|
442
|
+
private pendingRefresh;
|
|
435
443
|
private readonly ttlMs;
|
|
436
444
|
constructor(ttlMs: number);
|
|
437
445
|
/**
|
|
@@ -441,9 +449,11 @@ declare class Cache<T> {
|
|
|
441
449
|
getOrFetch(fetchFn: () => Promise<T>): Promise<T>;
|
|
442
450
|
/**
|
|
443
451
|
* Trigger a background refresh without awaiting the result.
|
|
444
|
-
* Returns immediately. If a fetch is already in flight,
|
|
452
|
+
* Returns immediately. If a fetch is already in flight, schedules
|
|
453
|
+
* the refresh to run after the current one completes.
|
|
445
454
|
*/
|
|
446
455
|
refreshInBackground(fetchFn: () => Promise<T>): void;
|
|
456
|
+
private startBackgroundFetch;
|
|
447
457
|
/** Invalidate the cache, forcing the next getOrFetch to re-fetch. */
|
|
448
458
|
invalidate(): void;
|
|
449
459
|
/** Get the cached value without fetching. Returns undefined if empty or stale. */
|
package/dist/index.js
CHANGED
|
@@ -354,6 +354,7 @@ var Cache = class {
|
|
|
354
354
|
constructor(ttlMs) {
|
|
355
355
|
this.fetchedAt = 0;
|
|
356
356
|
this.inflight = null;
|
|
357
|
+
this.pendingRefresh = null;
|
|
357
358
|
this.ttlMs = ttlMs;
|
|
358
359
|
}
|
|
359
360
|
/**
|
|
@@ -380,17 +381,30 @@ var Cache = class {
|
|
|
380
381
|
}
|
|
381
382
|
/**
|
|
382
383
|
* Trigger a background refresh without awaiting the result.
|
|
383
|
-
* Returns immediately. If a fetch is already in flight,
|
|
384
|
+
* Returns immediately. If a fetch is already in flight, schedules
|
|
385
|
+
* the refresh to run after the current one completes.
|
|
384
386
|
*/
|
|
385
387
|
refreshInBackground(fetchFn) {
|
|
386
|
-
if (this.inflight)
|
|
388
|
+
if (this.inflight) {
|
|
389
|
+
this.pendingRefresh = fetchFn;
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
this.startBackgroundFetch(fetchFn);
|
|
393
|
+
}
|
|
394
|
+
startBackgroundFetch(fetchFn) {
|
|
387
395
|
this.inflight = fetchFn().then((result) => {
|
|
388
396
|
this.data = result;
|
|
389
397
|
this.fetchedAt = Date.now();
|
|
390
398
|
this.inflight = null;
|
|
399
|
+
if (this.pendingRefresh) {
|
|
400
|
+
const pending = this.pendingRefresh;
|
|
401
|
+
this.pendingRefresh = null;
|
|
402
|
+
this.startBackgroundFetch(pending);
|
|
403
|
+
}
|
|
391
404
|
return result;
|
|
392
405
|
}).catch((err) => {
|
|
393
406
|
this.inflight = null;
|
|
407
|
+
this.pendingRefresh = null;
|
|
394
408
|
throw err;
|
|
395
409
|
});
|
|
396
410
|
this.inflight.catch(() => {
|
|
@@ -473,7 +487,8 @@ function validateName(input, options) {
|
|
|
473
487
|
if (opts.uppercase) {
|
|
474
488
|
name = name.toUpperCase();
|
|
475
489
|
}
|
|
476
|
-
|
|
490
|
+
const pattern = options?.allowedPattern ?? (opts.uppercase ? /[^A-Z0-9_]/g : /[^A-Za-z0-9_]/g);
|
|
491
|
+
name = name.replace(pattern, "");
|
|
477
492
|
name = name.substring(0, opts.maxLength);
|
|
478
493
|
if (name.length < opts.minLength) {
|
|
479
494
|
return null;
|
|
@@ -484,6 +499,8 @@ function validateName(input, options) {
|
|
|
484
499
|
// src/KeeperBoardSession.ts
|
|
485
500
|
var KeeperBoardSession = class {
|
|
486
501
|
constructor(config) {
|
|
502
|
+
this.cachedLimit = 0;
|
|
503
|
+
// Track the limit used for cached data
|
|
487
504
|
this.isSubmitting = false;
|
|
488
505
|
this.client = new KeeperBoardClient({
|
|
489
506
|
apiKey: config.apiKey,
|
|
@@ -543,6 +560,7 @@ var KeeperBoardSession = class {
|
|
|
543
560
|
this.retryQueue?.clear();
|
|
544
561
|
if (this.cache) {
|
|
545
562
|
this.cache.invalidate();
|
|
563
|
+
this.cachedLimit = 0;
|
|
546
564
|
this.cache.refreshInBackground(() => this.fetchSnapshot());
|
|
547
565
|
}
|
|
548
566
|
return {
|
|
@@ -564,12 +582,18 @@ var KeeperBoardSession = class {
|
|
|
564
582
|
* Get a combined snapshot: leaderboard entries (with `isCurrentPlayer` flag)
|
|
565
583
|
* plus the current player's rank if they're outside the top N.
|
|
566
584
|
*
|
|
567
|
-
* Uses cache if enabled and fresh.
|
|
585
|
+
* Uses cache if enabled and fresh. If a larger limit is requested than
|
|
586
|
+
* what's cached, the cache is invalidated and fresh data is fetched.
|
|
568
587
|
*/
|
|
569
588
|
async getSnapshot(options) {
|
|
570
589
|
const limit = options?.limit ?? 10;
|
|
571
590
|
if (this.cache) {
|
|
572
|
-
|
|
591
|
+
if (limit > this.cachedLimit) {
|
|
592
|
+
this.cache.invalidate();
|
|
593
|
+
}
|
|
594
|
+
const result = await this.cache.getOrFetch(() => this.fetchSnapshot(limit));
|
|
595
|
+
this.cachedLimit = limit;
|
|
596
|
+
return result;
|
|
573
597
|
}
|
|
574
598
|
return this.fetchSnapshot(limit);
|
|
575
599
|
}
|
|
@@ -584,7 +608,10 @@ var KeeperBoardSession = class {
|
|
|
584
608
|
newName
|
|
585
609
|
});
|
|
586
610
|
this.identity.setPlayerName(newName);
|
|
587
|
-
this.cache
|
|
611
|
+
if (this.cache) {
|
|
612
|
+
this.cache.invalidate();
|
|
613
|
+
this.cachedLimit = 0;
|
|
614
|
+
}
|
|
588
615
|
return true;
|
|
589
616
|
} catch {
|
|
590
617
|
return false;
|
package/dist/index.mjs
CHANGED
|
@@ -322,6 +322,7 @@ var Cache = class {
|
|
|
322
322
|
constructor(ttlMs) {
|
|
323
323
|
this.fetchedAt = 0;
|
|
324
324
|
this.inflight = null;
|
|
325
|
+
this.pendingRefresh = null;
|
|
325
326
|
this.ttlMs = ttlMs;
|
|
326
327
|
}
|
|
327
328
|
/**
|
|
@@ -348,17 +349,30 @@ var Cache = class {
|
|
|
348
349
|
}
|
|
349
350
|
/**
|
|
350
351
|
* Trigger a background refresh without awaiting the result.
|
|
351
|
-
* Returns immediately. If a fetch is already in flight,
|
|
352
|
+
* Returns immediately. If a fetch is already in flight, schedules
|
|
353
|
+
* the refresh to run after the current one completes.
|
|
352
354
|
*/
|
|
353
355
|
refreshInBackground(fetchFn) {
|
|
354
|
-
if (this.inflight)
|
|
356
|
+
if (this.inflight) {
|
|
357
|
+
this.pendingRefresh = fetchFn;
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
this.startBackgroundFetch(fetchFn);
|
|
361
|
+
}
|
|
362
|
+
startBackgroundFetch(fetchFn) {
|
|
355
363
|
this.inflight = fetchFn().then((result) => {
|
|
356
364
|
this.data = result;
|
|
357
365
|
this.fetchedAt = Date.now();
|
|
358
366
|
this.inflight = null;
|
|
367
|
+
if (this.pendingRefresh) {
|
|
368
|
+
const pending = this.pendingRefresh;
|
|
369
|
+
this.pendingRefresh = null;
|
|
370
|
+
this.startBackgroundFetch(pending);
|
|
371
|
+
}
|
|
359
372
|
return result;
|
|
360
373
|
}).catch((err) => {
|
|
361
374
|
this.inflight = null;
|
|
375
|
+
this.pendingRefresh = null;
|
|
362
376
|
throw err;
|
|
363
377
|
});
|
|
364
378
|
this.inflight.catch(() => {
|
|
@@ -441,7 +455,8 @@ function validateName(input, options) {
|
|
|
441
455
|
if (opts.uppercase) {
|
|
442
456
|
name = name.toUpperCase();
|
|
443
457
|
}
|
|
444
|
-
|
|
458
|
+
const pattern = options?.allowedPattern ?? (opts.uppercase ? /[^A-Z0-9_]/g : /[^A-Za-z0-9_]/g);
|
|
459
|
+
name = name.replace(pattern, "");
|
|
445
460
|
name = name.substring(0, opts.maxLength);
|
|
446
461
|
if (name.length < opts.minLength) {
|
|
447
462
|
return null;
|
|
@@ -452,6 +467,8 @@ function validateName(input, options) {
|
|
|
452
467
|
// src/KeeperBoardSession.ts
|
|
453
468
|
var KeeperBoardSession = class {
|
|
454
469
|
constructor(config) {
|
|
470
|
+
this.cachedLimit = 0;
|
|
471
|
+
// Track the limit used for cached data
|
|
455
472
|
this.isSubmitting = false;
|
|
456
473
|
this.client = new KeeperBoardClient({
|
|
457
474
|
apiKey: config.apiKey,
|
|
@@ -511,6 +528,7 @@ var KeeperBoardSession = class {
|
|
|
511
528
|
this.retryQueue?.clear();
|
|
512
529
|
if (this.cache) {
|
|
513
530
|
this.cache.invalidate();
|
|
531
|
+
this.cachedLimit = 0;
|
|
514
532
|
this.cache.refreshInBackground(() => this.fetchSnapshot());
|
|
515
533
|
}
|
|
516
534
|
return {
|
|
@@ -532,12 +550,18 @@ var KeeperBoardSession = class {
|
|
|
532
550
|
* Get a combined snapshot: leaderboard entries (with `isCurrentPlayer` flag)
|
|
533
551
|
* plus the current player's rank if they're outside the top N.
|
|
534
552
|
*
|
|
535
|
-
* Uses cache if enabled and fresh.
|
|
553
|
+
* Uses cache if enabled and fresh. If a larger limit is requested than
|
|
554
|
+
* what's cached, the cache is invalidated and fresh data is fetched.
|
|
536
555
|
*/
|
|
537
556
|
async getSnapshot(options) {
|
|
538
557
|
const limit = options?.limit ?? 10;
|
|
539
558
|
if (this.cache) {
|
|
540
|
-
|
|
559
|
+
if (limit > this.cachedLimit) {
|
|
560
|
+
this.cache.invalidate();
|
|
561
|
+
}
|
|
562
|
+
const result = await this.cache.getOrFetch(() => this.fetchSnapshot(limit));
|
|
563
|
+
this.cachedLimit = limit;
|
|
564
|
+
return result;
|
|
541
565
|
}
|
|
542
566
|
return this.fetchSnapshot(limit);
|
|
543
567
|
}
|
|
@@ -552,7 +576,10 @@ var KeeperBoardSession = class {
|
|
|
552
576
|
newName
|
|
553
577
|
});
|
|
554
578
|
this.identity.setPlayerName(newName);
|
|
555
|
-
this.cache
|
|
579
|
+
if (this.cache) {
|
|
580
|
+
this.cache.invalidate();
|
|
581
|
+
this.cachedLimit = 0;
|
|
582
|
+
}
|
|
556
583
|
return true;
|
|
557
584
|
} catch {
|
|
558
585
|
return false;
|