keeperboard 1.0.2 → 1.0.3

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # KeeperBoard SDK
2
2
 
3
- TypeScript client SDK for [KeeperBoard](https://github.com/YOUR_USERNAME/keeperboard) — a free, open-source leaderboard-as-a-service for indie game developers.
3
+ TypeScript client SDK for [KeeperBoard](https://github.com/clauderoy790/keeperboard) — a free, open-source leaderboard-as-a-service for indie game developers.
4
4
 
5
5
  Works with Phaser.js, vanilla JavaScript, and any TypeScript/JavaScript game running in the browser.
6
6
 
@@ -24,7 +24,8 @@ import { KeeperBoardClient, PlayerIdentity } from './keeperboard/index';
24
24
 
25
25
  1. Sign up at your KeeperBoard dashboard
26
26
  2. Create a game
27
- 3. Generate an API key for your environment (dev/prod)
27
+ 3. Create an environment (dev/prod)
28
+ 4. Generate an API key for that environment
28
29
 
29
30
  ### 2. Initialize the client
30
31
 
@@ -32,23 +33,22 @@ import { KeeperBoardClient, PlayerIdentity } from './keeperboard/index';
32
33
  import { KeeperBoardClient, PlayerIdentity } from 'keeperboard-sdk';
33
34
 
34
35
  // Create the API client
35
- const keeperboard = new KeeperBoardClient({
36
+ const client = new KeeperBoardClient({
36
37
  apiUrl: 'https://keeperboard.vercel.app',
37
- apiKey: 'kb_dev_your_api_key_here',
38
+ apiKey: 'kb_prod_your_api_key_here',
38
39
  });
39
40
 
40
41
  // Helper for persistent player identity
41
- const playerIdentity = new PlayerIdentity();
42
+ const identity = new PlayerIdentity();
42
43
  ```
43
44
 
44
45
  ### 3. Submit a score
45
46
 
46
47
  ```typescript
47
- // Get or create a persistent player GUID
48
- const playerGuid = playerIdentity.getOrCreatePlayerGuid();
48
+ const playerGuid = identity.getOrCreatePlayerGuid();
49
49
 
50
- // Submit a score (only updates if higher than existing)
51
- const result = await keeperboard.submitScore(playerGuid, 'PlayerName', 1500);
50
+ // Submit to default leaderboard
51
+ const result = await client.submitScore(playerGuid, 'PlayerName', 1500);
52
52
 
53
53
  console.log(`Rank: #${result.rank}`);
54
54
  console.log(`New high score: ${result.is_new_high_score}`);
@@ -57,13 +57,24 @@ console.log(`New high score: ${result.is_new_high_score}`);
57
57
  ### 4. Display the leaderboard
58
58
 
59
59
  ```typescript
60
- const leaderboard = await keeperboard.getLeaderboard(10);
60
+ // Get top 10
61
+ const leaderboard = await client.getLeaderboard();
61
62
 
62
63
  leaderboard.entries.forEach((entry) => {
63
64
  console.log(`#${entry.rank} ${entry.player_name}: ${entry.score}`);
64
65
  });
65
66
  ```
66
67
 
68
+ ### 5. Show player's rank (even if not in top 10)
69
+
70
+ ```typescript
71
+ const player = await client.getPlayerRank(playerGuid);
72
+
73
+ if (player && player.rank > 10) {
74
+ console.log(`You are ranked #${player.rank} with ${player.score} points`);
75
+ }
76
+ ```
77
+
67
78
  ## API Reference
68
79
 
69
80
  ### KeeperBoardClient
@@ -72,97 +83,159 @@ leaderboard.entries.forEach((entry) => {
72
83
 
73
84
  ```typescript
74
85
  const client = new KeeperBoardClient({
75
- apiUrl: string, // Your KeeperBoard API URL
76
- apiKey: string, // API key from dashboard (e.g., "kb_dev_...")
86
+ apiUrl: string, // Your KeeperBoard API URL
87
+ apiKey: string, // API key from dashboard
77
88
  });
78
89
  ```
79
90
 
80
- #### Methods
91
+ > **Note:** The API key determines which game and environment you're accessing. You don't need to pass environment or game IDs — they're implicit in the key.
92
+
93
+ ---
81
94
 
82
- ##### `submitScore(playerGuid, playerName, score, metadata?, leaderboardSlug?)`
95
+ ### Score Submission
83
96
 
84
- Submit a score. Only updates if the new score is higher than the existing one.
97
+ #### `submitScore(playerGuid, playerName, score)`
98
+
99
+ Submit a score to the default leaderboard. Only updates if higher than existing score.
85
100
 
86
101
  ```typescript
87
- const result = await client.submitScore(
88
- 'player-uuid-123',
102
+ const result = await client.submitScore('player-uuid', 'PlayerName', 2500);
103
+ // Returns: { id, player_guid, player_name, score, rank, is_new_high_score }
104
+ ```
105
+
106
+ #### `submitScore(playerGuid, playerName, score, leaderboard)`
107
+
108
+ Submit to a specific leaderboard by name.
109
+
110
+ ```typescript
111
+ await client.submitScore('player-uuid', 'PlayerName', 2500, 'Weekly Best');
112
+ ```
113
+
114
+ #### `submitScore(playerGuid, playerName, score, leaderboard, metadata)`
115
+
116
+ Submit with optional metadata.
117
+
118
+ ```typescript
119
+ await client.submitScore(
120
+ 'player-uuid',
89
121
  'PlayerName',
90
122
  2500,
91
- { level: 10, character: 'warrior' }, // optional metadata
92
- 'high-scores', // optional leaderboard slug
123
+ 'Weekly Best',
124
+ { level: 10, character: 'warrior' }
93
125
  );
126
+ ```
94
127
 
95
- // Returns:
96
- // {
97
- // id: string,
98
- // player_guid: string,
99
- // player_name: string,
100
- // score: number,
101
- // rank: number,
102
- // is_new_high_score: boolean
103
- // }
128
+ ---
129
+
130
+ ### Leaderboard
131
+
132
+ #### `getLeaderboard()`
133
+
134
+ Get the default leaderboard (top 10 entries).
135
+
136
+ ```typescript
137
+ const lb = await client.getLeaderboard();
138
+ // Returns: { entries, total_count, reset_schedule }
104
139
  ```
105
140
 
106
- ##### `getLeaderboard(limit?, offset?, leaderboardSlug?)`
141
+ #### `getLeaderboard(name)`
107
142
 
108
- Get leaderboard entries with pagination.
143
+ Get a specific leaderboard by name.
109
144
 
110
145
  ```typescript
111
- const result = await client.getLeaderboard(
112
- 25, // limit (max 100)
113
- 0, // offset
114
- 'high-scores', // optional leaderboard slug
115
- );
146
+ const lb = await client.getLeaderboard('Weekly Best');
147
+ ```
148
+
149
+ #### `getLeaderboard(name, limit)`
150
+
151
+ Get with a custom limit (max 100).
152
+
153
+ ```typescript
154
+ const lb = await client.getLeaderboard('Weekly Best', 50);
155
+ ```
156
+
157
+ #### `getLeaderboard(name, limit, offset)`
158
+
159
+ Get with pagination.
160
+
161
+ ```typescript
162
+ // Page 2 (entries 11-20)
163
+ const lb = await client.getLeaderboard('Weekly Best', 10, 10);
164
+ ```
165
+
166
+ ---
167
+
168
+ ### Leaderboard Versions (Time-Based)
116
169
 
117
- // Returns:
118
- // {
119
- // entries: [{ rank, player_guid, player_name, score }],
120
- // total_count: number
121
- // }
170
+ For leaderboards with reset schedules (daily/weekly/monthly), you can query historical versions.
171
+
172
+ #### `getLeaderboardVersion(name, version)`
173
+
174
+ Get a specific version of a time-based leaderboard.
175
+
176
+ ```typescript
177
+ // Get last week's scores (version 3)
178
+ const lastWeek = await client.getLeaderboardVersion('Weekly Best', 3);
122
179
  ```
123
180
 
124
- ##### `getPlayer(playerGuid, leaderboardSlug?)`
181
+ #### `getLeaderboardVersion(name, version, limit, offset)`
125
182
 
126
- Get a player's score and rank. Returns `null` if not found.
183
+ Get historical version with pagination.
127
184
 
128
185
  ```typescript
129
- const player = await client.getPlayer('player-uuid-123');
186
+ const lb = await client.getLeaderboardVersion('Weekly Best', 3, 25, 0);
187
+ ```
188
+
189
+ ---
190
+
191
+ ### Player
192
+
193
+ #### `getPlayerRank(playerGuid)`
194
+
195
+ Get a player's rank and score. Returns `null` if player has no score.
196
+
197
+ ```typescript
198
+ const player = await client.getPlayerRank('player-uuid');
130
199
 
131
200
  if (player) {
132
- console.log(`Score: ${player.score}, Rank: #${player.rank}`);
201
+ console.log(`Rank: #${player.rank}, Score: ${player.score}`);
133
202
  }
134
203
  ```
135
204
 
136
- ##### `updatePlayerName(playerGuid, newName, leaderboardSlug?)`
205
+ #### `getPlayerRank(playerGuid, leaderboard)`
206
+
207
+ Get player's rank on a specific leaderboard.
208
+
209
+ ```typescript
210
+ const player = await client.getPlayerRank('player-uuid', 'Weekly Best');
211
+ ```
212
+
213
+ #### `updatePlayerName(playerGuid, newName)`
137
214
 
138
215
  Update a player's display name.
139
216
 
140
217
  ```typescript
141
- const result = await client.updatePlayerName(
142
- 'player-uuid-123',
143
- 'NewPlayerName',
144
- );
218
+ await client.updatePlayerName('player-uuid', 'NewPlayerName');
145
219
  ```
146
220
 
147
- ##### `claimScore(playerGuid, playerName, leaderboardSlug?)`
221
+ ---
222
+
223
+ ### Claim (for migrated scores)
224
+
225
+ #### `claimScore(playerGuid, playerName)`
148
226
 
149
227
  Claim a migrated score by matching player name. Used when scores were imported without player GUIDs.
150
228
 
151
229
  ```typescript
152
- try {
153
- const result = await client.claimScore(
154
- 'new-player-guid',
155
- 'ImportedPlayerName',
156
- );
157
- console.log(`Claimed score: ${result.score}`);
158
- } catch (error) {
159
- if (error.code === 'NOT_FOUND') {
160
- console.log('No unclaimed score found');
161
- }
162
- }
230
+ const result = await client.claimScore('new-player-guid', 'ImportedPlayerName');
231
+ console.log(`Claimed score: ${result.score}, Rank: #${result.rank}`);
163
232
  ```
164
233
 
165
- ##### `healthCheck()`
234
+ ---
235
+
236
+ ### Health Check
237
+
238
+ #### `healthCheck()`
166
239
 
167
240
  Check if the API is healthy. Does not require an API key.
168
241
 
@@ -171,6 +244,8 @@ const health = await client.healthCheck();
171
244
  console.log(`API Version: ${health.version}`);
172
245
  ```
173
246
 
247
+ ---
248
+
174
249
  ### PlayerIdentity
175
250
 
176
251
  Helper for managing persistent player identity in localStorage.
@@ -196,6 +271,8 @@ if (identity.hasIdentity()) {
196
271
  identity.clear();
197
272
  ```
198
273
 
274
+ ---
275
+
199
276
  ### Error Handling
200
277
 
201
278
  All methods throw `KeeperBoardError` on failure:
@@ -219,25 +296,41 @@ try {
219
296
  case 'INVALID_REQUEST':
220
297
  // Check request parameters
221
298
  break;
222
- case 'ALREADY_CLAIMED':
223
- // Player already has a score
224
- break;
225
299
  }
226
300
  }
227
301
  }
228
302
  ```
229
303
 
304
+ ---
305
+
306
+ ## Multiple Leaderboards
307
+
308
+ If your game has multiple leaderboards (e.g., per-level or per-mode):
309
+
310
+ ```typescript
311
+ // Submit to different leaderboards
312
+ await client.submitScore(guid, name, score, 'Level 1');
313
+ await client.submitScore(guid, name, score, 'Endless Mode');
314
+ await client.submitScore(guid, name, score, 'Weekly Challenge');
315
+
316
+ // Get specific leaderboards
317
+ const level1 = await client.getLeaderboard('Level 1');
318
+ const endless = await client.getLeaderboard('Endless Mode', 50);
319
+ ```
320
+
321
+ ---
322
+
230
323
  ## Phaser.js Integration Example
231
324
 
232
325
  ```typescript
233
326
  import { KeeperBoardClient, PlayerIdentity } from 'keeperboard-sdk';
234
327
 
235
- const keeperboard = new KeeperBoardClient({
328
+ const client = new KeeperBoardClient({
236
329
  apiUrl: 'https://keeperboard.vercel.app',
237
330
  apiKey: 'kb_prod_your_api_key',
238
331
  });
239
332
 
240
- const playerIdentity = new PlayerIdentity();
333
+ const identity = new PlayerIdentity();
241
334
 
242
335
  class GameOverScene extends Phaser.Scene {
243
336
  private score: number = 0;
@@ -247,34 +340,25 @@ class GameOverScene extends Phaser.Scene {
247
340
  }
248
341
 
249
342
  async create() {
250
- const playerGuid = playerIdentity.getOrCreatePlayerGuid();
251
- const playerName = playerIdentity.getPlayerName() ?? 'Anonymous';
343
+ const playerGuid = identity.getOrCreatePlayerGuid();
344
+ const playerName = identity.getPlayerName() ?? 'Anonymous';
252
345
 
253
346
  // Submit score
254
- const result = await keeperboard.submitScore(
255
- playerGuid,
256
- playerName,
257
- this.score,
258
- );
347
+ const result = await client.submitScore(playerGuid, playerName, this.score);
259
348
 
260
- // Display result
349
+ // Display rank
261
350
  this.add
262
- .text(400, 200, `Your Rank: #${result.rank}`, {
263
- fontSize: '32px',
264
- })
351
+ .text(400, 200, `Your Rank: #${result.rank}`, { fontSize: '32px' })
265
352
  .setOrigin(0.5);
266
353
 
267
354
  if (result.is_new_high_score) {
268
355
  this.add
269
- .text(400, 250, 'NEW HIGH SCORE!', {
270
- fontSize: '24px',
271
- color: '#ffff00',
272
- })
356
+ .text(400, 250, 'NEW HIGH SCORE!', { fontSize: '24px', color: '#ffff00' })
273
357
  .setOrigin(0.5);
274
358
  }
275
359
 
276
- // Display leaderboard
277
- const leaderboard = await keeperboard.getLeaderboard(10);
360
+ // Display top 10
361
+ const leaderboard = await client.getLeaderboard();
278
362
 
279
363
  leaderboard.entries.forEach((entry, index) => {
280
364
  const isMe = entry.player_guid === playerGuid;
@@ -285,44 +369,26 @@ class GameOverScene extends Phaser.Scene {
285
369
  400,
286
370
  350 + index * 30,
287
371
  `#${entry.rank} ${entry.player_name}: ${entry.score}`,
288
- { fontSize: '18px', color },
372
+ { fontSize: '18px', color }
289
373
  )
290
374
  .setOrigin(0.5);
291
375
  });
376
+
377
+ // Show player's rank if not in top 10
378
+ const player = await client.getPlayerRank(playerGuid);
379
+ if (player && player.rank > 10) {
380
+ this.add
381
+ .text(400, 660, `... #${player.rank} ${playerName}: ${player.score}`, {
382
+ fontSize: '18px',
383
+ color: '#00ff00',
384
+ })
385
+ .setOrigin(0.5);
386
+ }
292
387
  }
293
388
  }
294
389
  ```
295
390
 
296
- ## Multiple Leaderboards
297
-
298
- If your game has multiple leaderboards (e.g., per-level or per-mode), use the `leaderboardSlug` parameter:
299
-
300
- ```typescript
301
- // Submit to a specific leaderboard
302
- await client.submitScore(guid, name, score, undefined, 'level-1-scores');
303
- await client.submitScore(guid, name, score, undefined, 'endless-mode');
304
-
305
- // Get a specific leaderboard
306
- const level1 = await client.getLeaderboard(10, 0, 'level-1-scores');
307
- const endless = await client.getLeaderboard(10, 0, 'endless-mode');
308
- ```
309
-
310
- ## TypeScript Types
311
-
312
- All types are exported for your convenience:
313
-
314
- ```typescript
315
- import type {
316
- KeeperBoardConfig,
317
- ScoreSubmission,
318
- ScoreResponse,
319
- LeaderboardEntry,
320
- LeaderboardResponse,
321
- PlayerResponse,
322
- ClaimResponse,
323
- HealthResponse,
324
- } from 'keeperboard-sdk';
325
- ```
391
+ ---
326
392
 
327
393
  ## Development
328
394
 
@@ -336,10 +402,29 @@ npm run typecheck
336
402
  # Build
337
403
  npm run build
338
404
 
405
+ # Run tests (requires .env with Supabase credentials)
406
+ npm test
407
+
339
408
  # Clean
340
409
  npm run clean
341
410
  ```
342
411
 
412
+ ### Running Tests
413
+
414
+ Copy `.env.example` to `.env` and fill in your Supabase credentials:
415
+
416
+ ```bash
417
+ cp .env.example .env
418
+ ```
419
+
420
+ Then run:
421
+
422
+ ```bash
423
+ npm test
424
+ ```
425
+
426
+ ---
427
+
343
428
  ## License
344
429
 
345
430
  MIT
package/dist/index.d.mts CHANGED
@@ -18,16 +18,8 @@ interface ScoreSubmission {
18
18
  /** Optional metadata to attach to the score */
19
19
  metadata?: Record<string, unknown>;
20
20
  }
21
- interface PlayerNameUpdate {
22
- /** New display name for the player */
23
- player_name: string;
24
- }
25
- interface ClaimRequest {
26
- /** Player GUID to assign to the migrated score */
27
- player_guid: string;
28
- /** Player name to match against migrated scores */
29
- player_name: string;
30
- }
21
+ /** Reset schedule options for leaderboards */
22
+ type ResetSchedule = 'none' | 'daily' | 'weekly' | 'monthly';
31
23
  interface ScoreResponse {
32
24
  /** Score ID in the database */
33
25
  id: string;
@@ -55,8 +47,16 @@ interface LeaderboardEntry {
55
47
  interface LeaderboardResponse {
56
48
  /** Array of leaderboard entries */
57
49
  entries: LeaderboardEntry[];
58
- /** Total number of scores on this leaderboard */
50
+ /** Total number of scores in this version/period */
59
51
  total_count: number;
52
+ /** The reset schedule of this leaderboard */
53
+ reset_schedule: ResetSchedule;
54
+ /** Current version number — only present when reset_schedule is not 'none' */
55
+ version?: number;
56
+ /** Oldest available version number — only present when reset_schedule is not 'none' */
57
+ oldest_version?: number;
58
+ /** ISO timestamp of when the next reset occurs — only present when reset_schedule is not 'none' */
59
+ next_reset?: string;
60
60
  }
61
61
  interface PlayerResponse {
62
62
  /** Score ID in the database */
@@ -88,16 +88,6 @@ interface HealthResponse {
88
88
  /** Server timestamp */
89
89
  timestamp: string;
90
90
  }
91
- interface ApiSuccessResponse<T> {
92
- success: true;
93
- data: T;
94
- }
95
- interface ApiErrorResponse {
96
- success: false;
97
- error: string;
98
- code: string;
99
- }
100
- type ApiResponse<T> = ApiSuccessResponse<T> | ApiErrorResponse;
101
91
  declare class KeeperBoardError extends Error {
102
92
  readonly code: string;
103
93
  readonly statusCode: number;
@@ -114,63 +104,102 @@ declare class KeeperBoardClient {
114
104
  private readonly apiKey;
115
105
  constructor(config: KeeperBoardConfig);
116
106
  /**
117
- * Submit a score to the leaderboard.
107
+ * Submit a score to the default leaderboard.
108
+ * Only updates if the new score is higher than the existing one.
109
+ */
110
+ submitScore(playerGuid: string, playerName: string, score: number): Promise<ScoreResponse>;
111
+ /**
112
+ * Submit a score to a specific leaderboard.
118
113
  * Only updates if the new score is higher than the existing one.
114
+ */
115
+ submitScore(playerGuid: string, playerName: string, score: number, leaderboard: string): Promise<ScoreResponse>;
116
+ /**
117
+ * Submit a score with metadata.
118
+ * Only updates if the new score is higher than the existing one.
119
+ */
120
+ submitScore(playerGuid: string, playerName: string, score: number, leaderboard: string, metadata: Record<string, unknown>): Promise<ScoreResponse>;
121
+ /**
122
+ * Get the default leaderboard (top 10 entries).
119
123
  *
120
- * @param playerGuid - Unique player identifier
121
- * @param playerName - Player display name
122
- * @param score - Score value
123
- * @param metadata - Optional metadata to attach
124
- * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
125
- * @returns Score response with rank and whether it's a new high score
124
+ * @example
125
+ * const lb = await client.getLeaderboard();
126
126
  */
127
- submitScore(playerGuid: string, playerName: string, score: number, metadata?: Record<string, unknown>, leaderboardSlug?: string): Promise<ScoreResponse>;
127
+ getLeaderboard(): Promise<LeaderboardResponse>;
128
128
  /**
129
- * Get the leaderboard entries with pagination.
129
+ * Get a specific leaderboard by name (top 10 entries).
130
130
  *
131
- * @param limit - Maximum number of entries to return (default: 10, max: 100)
132
- * @param offset - Pagination offset (default: 0)
133
- * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
134
- * @returns Leaderboard entries with total count
131
+ * @example
132
+ * const lb = await client.getLeaderboard('Weekly Best');
135
133
  */
136
- getLeaderboard(limit?: number, offset?: number, leaderboardSlug?: string): Promise<LeaderboardResponse>;
134
+ getLeaderboard(name: string): Promise<LeaderboardResponse>;
137
135
  /**
138
- * Get a specific player's score and rank.
136
+ * Get a leaderboard with custom limit.
139
137
  *
140
- * @param playerGuid - Player's unique identifier
141
- * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
142
- * @returns Player's score and rank, or null if not found
138
+ * @example
139
+ * const lb = await client.getLeaderboard('Weekly Best', 25);
143
140
  */
144
- getPlayer(playerGuid: string, leaderboardSlug?: string): Promise<PlayerResponse | null>;
141
+ getLeaderboard(name: string, limit: number): Promise<LeaderboardResponse>;
145
142
  /**
146
- * Update a player's display name.
143
+ * Get a leaderboard with pagination.
147
144
  *
148
- * @param playerGuid - Player's unique identifier
149
- * @param newName - New display name
150
- * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
151
- * @returns Updated player info
145
+ * @example
146
+ * const page2 = await client.getLeaderboard('Weekly Best', 10, 10);
152
147
  */
153
- updatePlayerName(playerGuid: string, newName: string, leaderboardSlug?: string): Promise<PlayerResponse>;
148
+ getLeaderboard(name: string, limit: number, offset: number): Promise<LeaderboardResponse>;
154
149
  /**
155
- * Claim a migrated score by matching player name.
156
- * Used when scores were imported (e.g., from CSV) without player GUIDs.
150
+ * Get a specific version of a time-based leaderboard.
157
151
  *
158
- * @param playerGuid - Player GUID to assign to the claimed score
159
- * @param playerName - Player name to match against migrated scores
160
- * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
161
- * @returns Claim result with score and rank
152
+ * @example
153
+ * // Get last week's leaderboard (version 3)
154
+ * const lastWeek = await client.getLeaderboardVersion('Weekly Best', 3);
162
155
  */
163
- claimScore(playerGuid: string, playerName: string, leaderboardSlug?: string): Promise<ClaimResponse>;
156
+ getLeaderboardVersion(name: string, version: number): Promise<LeaderboardResponse>;
164
157
  /**
165
- * Check if the API is healthy.
166
- * This endpoint does not require an API key.
158
+ * Get a specific version with custom limit.
159
+ */
160
+ getLeaderboardVersion(name: string, version: number, limit: number): Promise<LeaderboardResponse>;
161
+ /**
162
+ * Get a specific version with pagination.
163
+ */
164
+ getLeaderboardVersion(name: string, version: number, limit: number, offset: number): Promise<LeaderboardResponse>;
165
+ /**
166
+ * Get a player's rank and score on the default leaderboard.
167
+ * Returns null if the player has no score.
167
168
  *
168
- * @returns Health status with version and timestamp
169
+ * @example
170
+ * const player = await client.getPlayerRank(playerGuid);
171
+ * if (player) {
172
+ * console.log(`You are ranked #${player.rank}`);
173
+ * }
169
174
  */
170
- healthCheck(): Promise<HealthResponse>;
175
+ getPlayerRank(playerGuid: string): Promise<PlayerResponse | null>;
176
+ /**
177
+ * Get a player's rank and score on a specific leaderboard.
178
+ * Returns null if the player has no score.
179
+ */
180
+ getPlayerRank(playerGuid: string, leaderboard: string): Promise<PlayerResponse | null>;
181
+ /**
182
+ * Update a player's display name on the default leaderboard.
183
+ */
184
+ updatePlayerName(playerGuid: string, newName: string): Promise<PlayerResponse>;
185
+ /**
186
+ * Update a player's display name on a specific leaderboard.
187
+ */
188
+ updatePlayerName(playerGuid: string, newName: string, leaderboard: string): Promise<PlayerResponse>;
171
189
  /**
172
- * Internal request helper with auth and error handling.
190
+ * Claim a migrated score by matching player name.
191
+ * Used when scores were imported without player GUIDs.
173
192
  */
193
+ claimScore(playerGuid: string, playerName: string): Promise<ClaimResponse>;
194
+ /**
195
+ * Claim a migrated score on a specific leaderboard.
196
+ */
197
+ claimScore(playerGuid: string, playerName: string, leaderboard: string): Promise<ClaimResponse>;
198
+ /**
199
+ * Check if the API is healthy.
200
+ * This endpoint does not require an API key.
201
+ */
202
+ healthCheck(): Promise<HealthResponse>;
174
203
  private request;
175
204
  }
176
205
 
@@ -223,4 +252,4 @@ declare class PlayerIdentity {
223
252
  private generateUUID;
224
253
  }
225
254
 
226
- export { type ApiErrorResponse, type ApiResponse, type ApiSuccessResponse, type ClaimRequest, type ClaimResponse, type HealthResponse, KeeperBoardClient, type KeeperBoardConfig, KeeperBoardError, type LeaderboardEntry, type LeaderboardResponse, PlayerIdentity, type PlayerIdentityConfig, type PlayerNameUpdate, type PlayerResponse, type ScoreResponse, type ScoreSubmission };
255
+ export { type ClaimResponse, type HealthResponse, KeeperBoardClient, type KeeperBoardConfig, KeeperBoardError, type LeaderboardEntry, type LeaderboardResponse, PlayerIdentity, type PlayerIdentityConfig, type PlayerResponse, type ResetSchedule, type ScoreResponse, type ScoreSubmission };
package/dist/index.d.ts CHANGED
@@ -18,16 +18,8 @@ interface ScoreSubmission {
18
18
  /** Optional metadata to attach to the score */
19
19
  metadata?: Record<string, unknown>;
20
20
  }
21
- interface PlayerNameUpdate {
22
- /** New display name for the player */
23
- player_name: string;
24
- }
25
- interface ClaimRequest {
26
- /** Player GUID to assign to the migrated score */
27
- player_guid: string;
28
- /** Player name to match against migrated scores */
29
- player_name: string;
30
- }
21
+ /** Reset schedule options for leaderboards */
22
+ type ResetSchedule = 'none' | 'daily' | 'weekly' | 'monthly';
31
23
  interface ScoreResponse {
32
24
  /** Score ID in the database */
33
25
  id: string;
@@ -55,8 +47,16 @@ interface LeaderboardEntry {
55
47
  interface LeaderboardResponse {
56
48
  /** Array of leaderboard entries */
57
49
  entries: LeaderboardEntry[];
58
- /** Total number of scores on this leaderboard */
50
+ /** Total number of scores in this version/period */
59
51
  total_count: number;
52
+ /** The reset schedule of this leaderboard */
53
+ reset_schedule: ResetSchedule;
54
+ /** Current version number — only present when reset_schedule is not 'none' */
55
+ version?: number;
56
+ /** Oldest available version number — only present when reset_schedule is not 'none' */
57
+ oldest_version?: number;
58
+ /** ISO timestamp of when the next reset occurs — only present when reset_schedule is not 'none' */
59
+ next_reset?: string;
60
60
  }
61
61
  interface PlayerResponse {
62
62
  /** Score ID in the database */
@@ -88,16 +88,6 @@ interface HealthResponse {
88
88
  /** Server timestamp */
89
89
  timestamp: string;
90
90
  }
91
- interface ApiSuccessResponse<T> {
92
- success: true;
93
- data: T;
94
- }
95
- interface ApiErrorResponse {
96
- success: false;
97
- error: string;
98
- code: string;
99
- }
100
- type ApiResponse<T> = ApiSuccessResponse<T> | ApiErrorResponse;
101
91
  declare class KeeperBoardError extends Error {
102
92
  readonly code: string;
103
93
  readonly statusCode: number;
@@ -114,63 +104,102 @@ declare class KeeperBoardClient {
114
104
  private readonly apiKey;
115
105
  constructor(config: KeeperBoardConfig);
116
106
  /**
117
- * Submit a score to the leaderboard.
107
+ * Submit a score to the default leaderboard.
108
+ * Only updates if the new score is higher than the existing one.
109
+ */
110
+ submitScore(playerGuid: string, playerName: string, score: number): Promise<ScoreResponse>;
111
+ /**
112
+ * Submit a score to a specific leaderboard.
118
113
  * Only updates if the new score is higher than the existing one.
114
+ */
115
+ submitScore(playerGuid: string, playerName: string, score: number, leaderboard: string): Promise<ScoreResponse>;
116
+ /**
117
+ * Submit a score with metadata.
118
+ * Only updates if the new score is higher than the existing one.
119
+ */
120
+ submitScore(playerGuid: string, playerName: string, score: number, leaderboard: string, metadata: Record<string, unknown>): Promise<ScoreResponse>;
121
+ /**
122
+ * Get the default leaderboard (top 10 entries).
119
123
  *
120
- * @param playerGuid - Unique player identifier
121
- * @param playerName - Player display name
122
- * @param score - Score value
123
- * @param metadata - Optional metadata to attach
124
- * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
125
- * @returns Score response with rank and whether it's a new high score
124
+ * @example
125
+ * const lb = await client.getLeaderboard();
126
126
  */
127
- submitScore(playerGuid: string, playerName: string, score: number, metadata?: Record<string, unknown>, leaderboardSlug?: string): Promise<ScoreResponse>;
127
+ getLeaderboard(): Promise<LeaderboardResponse>;
128
128
  /**
129
- * Get the leaderboard entries with pagination.
129
+ * Get a specific leaderboard by name (top 10 entries).
130
130
  *
131
- * @param limit - Maximum number of entries to return (default: 10, max: 100)
132
- * @param offset - Pagination offset (default: 0)
133
- * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
134
- * @returns Leaderboard entries with total count
131
+ * @example
132
+ * const lb = await client.getLeaderboard('Weekly Best');
135
133
  */
136
- getLeaderboard(limit?: number, offset?: number, leaderboardSlug?: string): Promise<LeaderboardResponse>;
134
+ getLeaderboard(name: string): Promise<LeaderboardResponse>;
137
135
  /**
138
- * Get a specific player's score and rank.
136
+ * Get a leaderboard with custom limit.
139
137
  *
140
- * @param playerGuid - Player's unique identifier
141
- * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
142
- * @returns Player's score and rank, or null if not found
138
+ * @example
139
+ * const lb = await client.getLeaderboard('Weekly Best', 25);
143
140
  */
144
- getPlayer(playerGuid: string, leaderboardSlug?: string): Promise<PlayerResponse | null>;
141
+ getLeaderboard(name: string, limit: number): Promise<LeaderboardResponse>;
145
142
  /**
146
- * Update a player's display name.
143
+ * Get a leaderboard with pagination.
147
144
  *
148
- * @param playerGuid - Player's unique identifier
149
- * @param newName - New display name
150
- * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
151
- * @returns Updated player info
145
+ * @example
146
+ * const page2 = await client.getLeaderboard('Weekly Best', 10, 10);
152
147
  */
153
- updatePlayerName(playerGuid: string, newName: string, leaderboardSlug?: string): Promise<PlayerResponse>;
148
+ getLeaderboard(name: string, limit: number, offset: number): Promise<LeaderboardResponse>;
154
149
  /**
155
- * Claim a migrated score by matching player name.
156
- * Used when scores were imported (e.g., from CSV) without player GUIDs.
150
+ * Get a specific version of a time-based leaderboard.
157
151
  *
158
- * @param playerGuid - Player GUID to assign to the claimed score
159
- * @param playerName - Player name to match against migrated scores
160
- * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
161
- * @returns Claim result with score and rank
152
+ * @example
153
+ * // Get last week's leaderboard (version 3)
154
+ * const lastWeek = await client.getLeaderboardVersion('Weekly Best', 3);
162
155
  */
163
- claimScore(playerGuid: string, playerName: string, leaderboardSlug?: string): Promise<ClaimResponse>;
156
+ getLeaderboardVersion(name: string, version: number): Promise<LeaderboardResponse>;
164
157
  /**
165
- * Check if the API is healthy.
166
- * This endpoint does not require an API key.
158
+ * Get a specific version with custom limit.
159
+ */
160
+ getLeaderboardVersion(name: string, version: number, limit: number): Promise<LeaderboardResponse>;
161
+ /**
162
+ * Get a specific version with pagination.
163
+ */
164
+ getLeaderboardVersion(name: string, version: number, limit: number, offset: number): Promise<LeaderboardResponse>;
165
+ /**
166
+ * Get a player's rank and score on the default leaderboard.
167
+ * Returns null if the player has no score.
167
168
  *
168
- * @returns Health status with version and timestamp
169
+ * @example
170
+ * const player = await client.getPlayerRank(playerGuid);
171
+ * if (player) {
172
+ * console.log(`You are ranked #${player.rank}`);
173
+ * }
169
174
  */
170
- healthCheck(): Promise<HealthResponse>;
175
+ getPlayerRank(playerGuid: string): Promise<PlayerResponse | null>;
176
+ /**
177
+ * Get a player's rank and score on a specific leaderboard.
178
+ * Returns null if the player has no score.
179
+ */
180
+ getPlayerRank(playerGuid: string, leaderboard: string): Promise<PlayerResponse | null>;
181
+ /**
182
+ * Update a player's display name on the default leaderboard.
183
+ */
184
+ updatePlayerName(playerGuid: string, newName: string): Promise<PlayerResponse>;
185
+ /**
186
+ * Update a player's display name on a specific leaderboard.
187
+ */
188
+ updatePlayerName(playerGuid: string, newName: string, leaderboard: string): Promise<PlayerResponse>;
171
189
  /**
172
- * Internal request helper with auth and error handling.
190
+ * Claim a migrated score by matching player name.
191
+ * Used when scores were imported without player GUIDs.
173
192
  */
193
+ claimScore(playerGuid: string, playerName: string): Promise<ClaimResponse>;
194
+ /**
195
+ * Claim a migrated score on a specific leaderboard.
196
+ */
197
+ claimScore(playerGuid: string, playerName: string, leaderboard: string): Promise<ClaimResponse>;
198
+ /**
199
+ * Check if the API is healthy.
200
+ * This endpoint does not require an API key.
201
+ */
202
+ healthCheck(): Promise<HealthResponse>;
174
203
  private request;
175
204
  }
176
205
 
@@ -223,4 +252,4 @@ declare class PlayerIdentity {
223
252
  private generateUUID;
224
253
  }
225
254
 
226
- export { type ApiErrorResponse, type ApiResponse, type ApiSuccessResponse, type ClaimRequest, type ClaimResponse, type HealthResponse, KeeperBoardClient, type KeeperBoardConfig, KeeperBoardError, type LeaderboardEntry, type LeaderboardResponse, PlayerIdentity, type PlayerIdentityConfig, type PlayerNameUpdate, type PlayerResponse, type ScoreResponse, type ScoreSubmission };
255
+ export { type ClaimResponse, type HealthResponse, KeeperBoardClient, type KeeperBoardConfig, KeeperBoardError, type LeaderboardEntry, type LeaderboardResponse, PlayerIdentity, type PlayerIdentityConfig, type PlayerResponse, type ResetSchedule, type ScoreResponse, type ScoreSubmission };
package/dist/index.js CHANGED
@@ -42,21 +42,10 @@ var KeeperBoardClient = class {
42
42
  this.apiUrl = config.apiUrl.replace(/\/$/, "");
43
43
  this.apiKey = config.apiKey;
44
44
  }
45
- /**
46
- * Submit a score to the leaderboard.
47
- * Only updates if the new score is higher than the existing one.
48
- *
49
- * @param playerGuid - Unique player identifier
50
- * @param playerName - Player display name
51
- * @param score - Score value
52
- * @param metadata - Optional metadata to attach
53
- * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
54
- * @returns Score response with rank and whether it's a new high score
55
- */
56
- async submitScore(playerGuid, playerName, score, metadata, leaderboardSlug) {
45
+ async submitScore(playerGuid, playerName, score, leaderboard, metadata) {
57
46
  const params = new URLSearchParams();
58
- if (leaderboardSlug) {
59
- params.set("leaderboard", leaderboardSlug);
47
+ if (leaderboard) {
48
+ params.set("leaderboard", leaderboard);
60
49
  }
61
50
  const url = `${this.apiUrl}/api/v1/scores${params.toString() ? "?" + params.toString() : ""}`;
62
51
  const body = {
@@ -65,49 +54,38 @@ var KeeperBoardClient = class {
65
54
  score,
66
55
  ...metadata && { metadata }
67
56
  };
68
- const response = await this.request(url, {
57
+ return this.request(url, {
69
58
  method: "POST",
70
59
  body: JSON.stringify(body)
71
60
  });
72
- return response;
73
61
  }
74
- /**
75
- * Get the leaderboard entries with pagination.
76
- *
77
- * @param limit - Maximum number of entries to return (default: 10, max: 100)
78
- * @param offset - Pagination offset (default: 0)
79
- * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
80
- * @returns Leaderboard entries with total count
81
- */
82
- async getLeaderboard(limit = 10, offset = 0, leaderboardSlug) {
62
+ async getLeaderboard(name, limit = 10, offset = 0) {
83
63
  const params = new URLSearchParams();
84
64
  params.set("limit", String(Math.min(limit, 100)));
85
65
  params.set("offset", String(offset));
86
- if (leaderboardSlug) {
87
- params.set("leaderboard", leaderboardSlug);
66
+ if (name) {
67
+ params.set("leaderboard", name);
88
68
  }
89
69
  const url = `${this.apiUrl}/api/v1/leaderboard?${params.toString()}`;
90
- return this.request(url, {
91
- method: "GET"
92
- });
70
+ return this.request(url, { method: "GET" });
93
71
  }
94
- /**
95
- * Get a specific player's score and rank.
96
- *
97
- * @param playerGuid - Player's unique identifier
98
- * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
99
- * @returns Player's score and rank, or null if not found
100
- */
101
- async getPlayer(playerGuid, leaderboardSlug) {
72
+ async getLeaderboardVersion(name, version, limit = 10, offset = 0) {
73
+ const params = new URLSearchParams();
74
+ params.set("leaderboard", name);
75
+ params.set("version", String(version));
76
+ params.set("limit", String(Math.min(limit, 100)));
77
+ params.set("offset", String(offset));
78
+ const url = `${this.apiUrl}/api/v1/leaderboard?${params.toString()}`;
79
+ return this.request(url, { method: "GET" });
80
+ }
81
+ async getPlayerRank(playerGuid, leaderboard) {
102
82
  const params = new URLSearchParams();
103
- if (leaderboardSlug) {
104
- params.set("leaderboard", leaderboardSlug);
83
+ if (leaderboard) {
84
+ params.set("leaderboard", leaderboard);
105
85
  }
106
86
  const url = `${this.apiUrl}/api/v1/player/${encodeURIComponent(playerGuid)}${params.toString() ? "?" + params.toString() : ""}`;
107
87
  try {
108
- return await this.request(url, {
109
- method: "GET"
110
- });
88
+ return await this.request(url, { method: "GET" });
111
89
  } catch (error) {
112
90
  if (error instanceof KeeperBoardError && error.code === "NOT_FOUND") {
113
91
  return null;
@@ -115,18 +93,10 @@ var KeeperBoardClient = class {
115
93
  throw error;
116
94
  }
117
95
  }
118
- /**
119
- * Update a player's display name.
120
- *
121
- * @param playerGuid - Player's unique identifier
122
- * @param newName - New display name
123
- * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
124
- * @returns Updated player info
125
- */
126
- async updatePlayerName(playerGuid, newName, leaderboardSlug) {
96
+ async updatePlayerName(playerGuid, newName, leaderboard) {
127
97
  const params = new URLSearchParams();
128
- if (leaderboardSlug) {
129
- params.set("leaderboard", leaderboardSlug);
98
+ if (leaderboard) {
99
+ params.set("leaderboard", leaderboard);
130
100
  }
131
101
  const url = `${this.apiUrl}/api/v1/player/${encodeURIComponent(playerGuid)}${params.toString() ? "?" + params.toString() : ""}`;
132
102
  return this.request(url, {
@@ -134,19 +104,10 @@ var KeeperBoardClient = class {
134
104
  body: JSON.stringify({ player_name: newName })
135
105
  });
136
106
  }
137
- /**
138
- * Claim a migrated score by matching player name.
139
- * Used when scores were imported (e.g., from CSV) without player GUIDs.
140
- *
141
- * @param playerGuid - Player GUID to assign to the claimed score
142
- * @param playerName - Player name to match against migrated scores
143
- * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
144
- * @returns Claim result with score and rank
145
- */
146
- async claimScore(playerGuid, playerName, leaderboardSlug) {
107
+ async claimScore(playerGuid, playerName, leaderboard) {
147
108
  const params = new URLSearchParams();
148
- if (leaderboardSlug) {
149
- params.set("leaderboard", leaderboardSlug);
109
+ if (leaderboard) {
110
+ params.set("leaderboard", leaderboard);
150
111
  }
151
112
  const url = `${this.apiUrl}/api/v1/claim${params.toString() ? "?" + params.toString() : ""}`;
152
113
  return this.request(url, {
@@ -157,19 +118,18 @@ var KeeperBoardClient = class {
157
118
  })
158
119
  });
159
120
  }
121
+ // ============================================
122
+ // HEALTH CHECK
123
+ // ============================================
160
124
  /**
161
125
  * Check if the API is healthy.
162
126
  * This endpoint does not require an API key.
163
- *
164
- * @returns Health status with version and timestamp
165
127
  */
166
128
  async healthCheck() {
167
129
  const url = `${this.apiUrl}/api/v1/health`;
168
130
  const response = await fetch(url, {
169
131
  method: "GET",
170
- headers: {
171
- Accept: "application/json"
172
- }
132
+ headers: { Accept: "application/json" }
173
133
  });
174
134
  const json = await response.json();
175
135
  if (!json.success) {
@@ -177,9 +137,9 @@ var KeeperBoardClient = class {
177
137
  }
178
138
  return json.data;
179
139
  }
180
- /**
181
- * Internal request helper with auth and error handling.
182
- */
140
+ // ============================================
141
+ // INTERNAL
142
+ // ============================================
183
143
  async request(url, options) {
184
144
  const headers = {
185
145
  "Content-Type": "application/json",
@@ -188,10 +148,7 @@ var KeeperBoardClient = class {
188
148
  };
189
149
  const response = await fetch(url, {
190
150
  ...options,
191
- headers: {
192
- ...headers,
193
- ...options.headers || {}
194
- }
151
+ headers: { ...headers, ...options.headers || {} }
195
152
  });
196
153
  const json = await response.json();
197
154
  if (!json.success) {
package/dist/index.mjs CHANGED
@@ -14,21 +14,10 @@ var KeeperBoardClient = class {
14
14
  this.apiUrl = config.apiUrl.replace(/\/$/, "");
15
15
  this.apiKey = config.apiKey;
16
16
  }
17
- /**
18
- * Submit a score to the leaderboard.
19
- * Only updates if the new score is higher than the existing one.
20
- *
21
- * @param playerGuid - Unique player identifier
22
- * @param playerName - Player display name
23
- * @param score - Score value
24
- * @param metadata - Optional metadata to attach
25
- * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
26
- * @returns Score response with rank and whether it's a new high score
27
- */
28
- async submitScore(playerGuid, playerName, score, metadata, leaderboardSlug) {
17
+ async submitScore(playerGuid, playerName, score, leaderboard, metadata) {
29
18
  const params = new URLSearchParams();
30
- if (leaderboardSlug) {
31
- params.set("leaderboard", leaderboardSlug);
19
+ if (leaderboard) {
20
+ params.set("leaderboard", leaderboard);
32
21
  }
33
22
  const url = `${this.apiUrl}/api/v1/scores${params.toString() ? "?" + params.toString() : ""}`;
34
23
  const body = {
@@ -37,49 +26,38 @@ var KeeperBoardClient = class {
37
26
  score,
38
27
  ...metadata && { metadata }
39
28
  };
40
- const response = await this.request(url, {
29
+ return this.request(url, {
41
30
  method: "POST",
42
31
  body: JSON.stringify(body)
43
32
  });
44
- return response;
45
33
  }
46
- /**
47
- * Get the leaderboard entries with pagination.
48
- *
49
- * @param limit - Maximum number of entries to return (default: 10, max: 100)
50
- * @param offset - Pagination offset (default: 0)
51
- * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
52
- * @returns Leaderboard entries with total count
53
- */
54
- async getLeaderboard(limit = 10, offset = 0, leaderboardSlug) {
34
+ async getLeaderboard(name, limit = 10, offset = 0) {
55
35
  const params = new URLSearchParams();
56
36
  params.set("limit", String(Math.min(limit, 100)));
57
37
  params.set("offset", String(offset));
58
- if (leaderboardSlug) {
59
- params.set("leaderboard", leaderboardSlug);
38
+ if (name) {
39
+ params.set("leaderboard", name);
60
40
  }
61
41
  const url = `${this.apiUrl}/api/v1/leaderboard?${params.toString()}`;
62
- return this.request(url, {
63
- method: "GET"
64
- });
42
+ return this.request(url, { method: "GET" });
65
43
  }
66
- /**
67
- * Get a specific player's score and rank.
68
- *
69
- * @param playerGuid - Player's unique identifier
70
- * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
71
- * @returns Player's score and rank, or null if not found
72
- */
73
- async getPlayer(playerGuid, leaderboardSlug) {
44
+ async getLeaderboardVersion(name, version, limit = 10, offset = 0) {
45
+ const params = new URLSearchParams();
46
+ params.set("leaderboard", name);
47
+ params.set("version", String(version));
48
+ params.set("limit", String(Math.min(limit, 100)));
49
+ params.set("offset", String(offset));
50
+ const url = `${this.apiUrl}/api/v1/leaderboard?${params.toString()}`;
51
+ return this.request(url, { method: "GET" });
52
+ }
53
+ async getPlayerRank(playerGuid, leaderboard) {
74
54
  const params = new URLSearchParams();
75
- if (leaderboardSlug) {
76
- params.set("leaderboard", leaderboardSlug);
55
+ if (leaderboard) {
56
+ params.set("leaderboard", leaderboard);
77
57
  }
78
58
  const url = `${this.apiUrl}/api/v1/player/${encodeURIComponent(playerGuid)}${params.toString() ? "?" + params.toString() : ""}`;
79
59
  try {
80
- return await this.request(url, {
81
- method: "GET"
82
- });
60
+ return await this.request(url, { method: "GET" });
83
61
  } catch (error) {
84
62
  if (error instanceof KeeperBoardError && error.code === "NOT_FOUND") {
85
63
  return null;
@@ -87,18 +65,10 @@ var KeeperBoardClient = class {
87
65
  throw error;
88
66
  }
89
67
  }
90
- /**
91
- * Update a player's display name.
92
- *
93
- * @param playerGuid - Player's unique identifier
94
- * @param newName - New display name
95
- * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
96
- * @returns Updated player info
97
- */
98
- async updatePlayerName(playerGuid, newName, leaderboardSlug) {
68
+ async updatePlayerName(playerGuid, newName, leaderboard) {
99
69
  const params = new URLSearchParams();
100
- if (leaderboardSlug) {
101
- params.set("leaderboard", leaderboardSlug);
70
+ if (leaderboard) {
71
+ params.set("leaderboard", leaderboard);
102
72
  }
103
73
  const url = `${this.apiUrl}/api/v1/player/${encodeURIComponent(playerGuid)}${params.toString() ? "?" + params.toString() : ""}`;
104
74
  return this.request(url, {
@@ -106,19 +76,10 @@ var KeeperBoardClient = class {
106
76
  body: JSON.stringify({ player_name: newName })
107
77
  });
108
78
  }
109
- /**
110
- * Claim a migrated score by matching player name.
111
- * Used when scores were imported (e.g., from CSV) without player GUIDs.
112
- *
113
- * @param playerGuid - Player GUID to assign to the claimed score
114
- * @param playerName - Player name to match against migrated scores
115
- * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
116
- * @returns Claim result with score and rank
117
- */
118
- async claimScore(playerGuid, playerName, leaderboardSlug) {
79
+ async claimScore(playerGuid, playerName, leaderboard) {
119
80
  const params = new URLSearchParams();
120
- if (leaderboardSlug) {
121
- params.set("leaderboard", leaderboardSlug);
81
+ if (leaderboard) {
82
+ params.set("leaderboard", leaderboard);
122
83
  }
123
84
  const url = `${this.apiUrl}/api/v1/claim${params.toString() ? "?" + params.toString() : ""}`;
124
85
  return this.request(url, {
@@ -129,19 +90,18 @@ var KeeperBoardClient = class {
129
90
  })
130
91
  });
131
92
  }
93
+ // ============================================
94
+ // HEALTH CHECK
95
+ // ============================================
132
96
  /**
133
97
  * Check if the API is healthy.
134
98
  * This endpoint does not require an API key.
135
- *
136
- * @returns Health status with version and timestamp
137
99
  */
138
100
  async healthCheck() {
139
101
  const url = `${this.apiUrl}/api/v1/health`;
140
102
  const response = await fetch(url, {
141
103
  method: "GET",
142
- headers: {
143
- Accept: "application/json"
144
- }
104
+ headers: { Accept: "application/json" }
145
105
  });
146
106
  const json = await response.json();
147
107
  if (!json.success) {
@@ -149,9 +109,9 @@ var KeeperBoardClient = class {
149
109
  }
150
110
  return json.data;
151
111
  }
152
- /**
153
- * Internal request helper with auth and error handling.
154
- */
112
+ // ============================================
113
+ // INTERNAL
114
+ // ============================================
155
115
  async request(url, options) {
156
116
  const headers = {
157
117
  "Content-Type": "application/json",
@@ -160,10 +120,7 @@ var KeeperBoardClient = class {
160
120
  };
161
121
  const response = await fetch(url, {
162
122
  ...options,
163
- headers: {
164
- ...headers,
165
- ...options.headers || {}
166
- }
123
+ headers: { ...headers, ...options.headers || {} }
167
124
  });
168
125
  const json = await response.json();
169
126
  if (!json.success) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keeperboard",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "TypeScript client SDK for KeeperBoard leaderboard-as-a-service",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -19,6 +19,8 @@
19
19
  "build": "tsup src/index.ts --format cjs,esm --dts",
20
20
  "typecheck": "tsc --noEmit",
21
21
  "clean": "rm -rf dist",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest",
22
24
  "prepublishOnly": "npm run build"
23
25
  },
24
26
  "keywords": [
@@ -42,8 +44,11 @@
42
44
  "url": "https://github.com/clauderoy790/keeperboard/issues"
43
45
  },
44
46
  "devDependencies": {
47
+ "@supabase/supabase-js": "^2.95.3",
48
+ "dotenv": "^17.2.4",
45
49
  "tsup": "^8.0.0",
46
- "typescript": "^5.0.0"
50
+ "typescript": "^5.0.0",
51
+ "vitest": "^4.0.18"
47
52
  },
48
53
  "engines": {
49
54
  "node": ">=18"