lightleaderboard 1.0.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.
@@ -0,0 +1,271 @@
1
+ interface LightLeaderboardConfig {
2
+ /** Your game's API key from the LightLeaderboard dashboard */
3
+ apiKey: string;
4
+ /** Your game's reference ID from the dashboard */
5
+ gameId: string;
6
+ /** Override the API base URL (defaults to https://leaderboard.goproso.com) */
7
+ baseUrl?: string;
8
+ /**
9
+ * Optional secret key to auto-sign score submissions (HMAC-SHA256).
10
+ * Set this if you enabled "Require signed scores" on your game.
11
+ * Uses the Web Crypto API — requires Node 18+ or a modern browser.
12
+ */
13
+ scoreSecret?: string;
14
+ }
15
+ interface LeaderboardEntry {
16
+ id: number;
17
+ /** 1-based position on the leaderboard */
18
+ rank: number;
19
+ playerRefId: string | null;
20
+ playerName: string | null;
21
+ score: number;
22
+ playTimeMs: number | null;
23
+ seasonId: string | null;
24
+ teamId: string | null;
25
+ /** Arbitrary metadata attached at submission time */
26
+ metadata: Record<string, unknown> | null;
27
+ createdAt: string;
28
+ }
29
+ interface GetLeaderboardOptions {
30
+ /** Number of entries to return (1–100, default 20) */
31
+ limit?: number;
32
+ /** Pagination offset (default 0) */
33
+ offset?: number;
34
+ /** Time window filter (default "all") */
35
+ period?: 'all' | 'weekly' | 'monthly';
36
+ /** Only return entries for this season ID */
37
+ season?: string;
38
+ /** Only return entries for this team ID */
39
+ team?: string;
40
+ /**
41
+ * Return every raw submission instead of best-per-player.
42
+ * Default is false — one entry per player (their personal best).
43
+ */
44
+ allEntries?: boolean;
45
+ }
46
+ interface GetLeaderboardResult {
47
+ entries: LeaderboardEntry[];
48
+ scoreOrder: 'asc' | 'desc';
49
+ period: string;
50
+ season: string | null;
51
+ team: string | null;
52
+ limit: number;
53
+ offset: number;
54
+ }
55
+ interface SubmitScoreOptions {
56
+ /** The numeric score to record */
57
+ score: number;
58
+ /** Your game's internal player identifier */
59
+ playerRefId?: string;
60
+ /** Display name shown on the leaderboard */
61
+ playerName?: string;
62
+ /** Duration of this run in milliseconds */
63
+ playTimeMs?: number;
64
+ /** Season bucket for seasonal leaderboards */
65
+ seasonId?: string;
66
+ /** Team bucket for team leaderboards */
67
+ teamId?: string;
68
+ /**
69
+ * Idempotency key — submitting the same ID twice returns the first
70
+ * entry without creating a duplicate.
71
+ */
72
+ submissionId?: string;
73
+ /** Arbitrary JSON metadata to attach to this submission */
74
+ metadata?: Record<string, unknown>;
75
+ /** Custom text stat slot 1 */
76
+ gameStatTxt1?: string;
77
+ /** Custom text stat slot 2 */
78
+ gameStatTxt2?: string;
79
+ /** Custom text stat slot 3 */
80
+ gameStatTxt3?: string;
81
+ /** Custom numeric stat slot 1 */
82
+ gameStatInt1?: number;
83
+ /** Custom numeric stat slot 2 */
84
+ gameStatInt2?: number;
85
+ /** Custom numeric stat slot 3 */
86
+ gameStatInt3?: number;
87
+ }
88
+ interface SubmitScoreResult {
89
+ /** Database ID of the created entry */
90
+ id: number;
91
+ /**
92
+ * Player's current global rank immediately after submission.
93
+ * null when no playerRefId was provided.
94
+ */
95
+ rank: number | null;
96
+ /** true if this score beats the player's previous personal best */
97
+ isPersonalBest: boolean;
98
+ /** Total unique players on this leaderboard */
99
+ totalPlayers: number | null;
100
+ /** true when this submissionId already existed (idempotent replay) */
101
+ deduped?: boolean;
102
+ }
103
+ interface GetRankOptions {
104
+ period?: 'all' | 'weekly' | 'monthly';
105
+ season?: string;
106
+ team?: string;
107
+ }
108
+ interface PlayerRankResult {
109
+ playerRefId: string;
110
+ playerName: string | null;
111
+ /** 1-based rank. null when the player has no entries in this filter. */
112
+ rank: number | null;
113
+ score: number | null;
114
+ totalPlayers: number | null;
115
+ /**
116
+ * How far from the bottom the player is, 0–100.
117
+ * Higher is better: 97.6 = top 2.4 %.
118
+ * null when the player has no entries.
119
+ */
120
+ percentile: number | null;
121
+ period: string;
122
+ season: string | null;
123
+ team: string | null;
124
+ scoreOrder: 'asc' | 'desc';
125
+ }
126
+ interface GetCentricOptions {
127
+ /** Entries to return centered around the player (default 10) */
128
+ limit?: number;
129
+ period?: 'all' | 'weekly' | 'monthly';
130
+ season?: string;
131
+ team?: string;
132
+ }
133
+ interface CentricLeaderboardResult {
134
+ entries: LeaderboardEntry[];
135
+ /** The target player's rank within the current filter */
136
+ playerRank: number | null;
137
+ period: string;
138
+ season: string | null;
139
+ team: string | null;
140
+ scoreOrder: 'asc' | 'desc';
141
+ }
142
+ interface PlayerProfile {
143
+ playerName: string | null;
144
+ avatarUrl: string | null;
145
+ teamId: string | null;
146
+ level: number | null;
147
+ country: string | null;
148
+ device: string | null;
149
+ createdAt: string;
150
+ updatedAt: string;
151
+ }
152
+ interface UpdatePlayerOptions {
153
+ playerName?: string;
154
+ avatarUrl?: string;
155
+ teamId?: string;
156
+ level?: number;
157
+ /** ISO 3166-1 alpha-2 country code, e.g. "US" */
158
+ country?: string;
159
+ device?: string;
160
+ }
161
+ interface PlayerScoreEntry {
162
+ id: number;
163
+ score: number;
164
+ playTimeMs: number | null;
165
+ seasonId: string | null;
166
+ teamId: string | null;
167
+ metadata: Record<string, unknown> | null;
168
+ createdAt: string;
169
+ gameStatTxt1: string | null;
170
+ gameStatTxt2: string | null;
171
+ gameStatTxt3: string | null;
172
+ gameStatInt1: number | null;
173
+ gameStatInt2: number | null;
174
+ gameStatInt3: number | null;
175
+ }
176
+ interface GetPlayerScoresOptions {
177
+ /** Max entries to return (1–200, default 50) */
178
+ limit?: number;
179
+ offset?: number;
180
+ season?: string;
181
+ team?: string;
182
+ }
183
+ interface GetPlayerScoresResult {
184
+ playerRefId: string;
185
+ entries: PlayerScoreEntry[];
186
+ /** Player's all-time best score */
187
+ bestScore: number | null;
188
+ /** Total number of submissions matching the filter */
189
+ total: number;
190
+ limit: number;
191
+ offset: number;
192
+ scoreOrder: 'asc' | 'desc';
193
+ }
194
+
195
+ declare class LightLeaderboard {
196
+ private readonly apiKey;
197
+ private readonly gameId;
198
+ private readonly baseUrl;
199
+ private readonly scoreSecret?;
200
+ constructor(config: LightLeaderboardConfig);
201
+ private gameUrl;
202
+ private fetch;
203
+ /**
204
+ * Submit a score to the leaderboard.
205
+ *
206
+ * Returns the player's new rank immediately — no second API call needed.
207
+ *
208
+ * @example
209
+ * const result = await lb.submitScore({ score: 9500, playerRefId: 'p1', playerName: 'Alice' });
210
+ * console.log(`Rank #${result.rank} of ${result.totalPlayers}`);
211
+ */
212
+ submitScore(options: SubmitScoreOptions): Promise<SubmitScoreResult>;
213
+ /**
214
+ * Fetch the leaderboard. Returns one entry per player (their best score) by default.
215
+ *
216
+ * @example
217
+ * const { entries } = await lb.getLeaderboard({ limit: 10, period: 'weekly' });
218
+ * entries.forEach(e => console.log(`#${e.rank} ${e.playerName}: ${e.score}`));
219
+ */
220
+ getLeaderboard(options?: GetLeaderboardOptions): Promise<GetLeaderboardResult>;
221
+ /**
222
+ * Get a player's current rank, score, and percentile.
223
+ *
224
+ * @example
225
+ * const { rank, percentile } = await lb.getPlayerRank('player-123');
226
+ * console.log(`Rank #${rank} — top ${(100 - percentile).toFixed(1)}%`);
227
+ */
228
+ getPlayerRank(playerRefId: string, options?: GetRankOptions): Promise<PlayerRankResult>;
229
+ /**
230
+ * Fetch the leaderboard centered on a specific player, showing the players
231
+ * immediately above and below them.
232
+ *
233
+ * @example
234
+ * const { entries, playerRank } = await lb.getCentricLeaderboard('player-123', { limit: 11 });
235
+ */
236
+ getCentricLeaderboard(playerRefId: string, options?: GetCentricOptions): Promise<CentricLeaderboardResult>;
237
+ /**
238
+ * Fetch a player's profile (name, avatar, country, level, etc.).
239
+ */
240
+ getPlayer(playerRefId: string): Promise<PlayerProfile>;
241
+ /**
242
+ * Create or update a player's profile. Fields are merged — omitted fields
243
+ * keep their existing values.
244
+ *
245
+ * @example
246
+ * await lb.updatePlayer('player-123', { playerName: 'Alice', country: 'US', level: 5 });
247
+ */
248
+ updatePlayer(playerRefId: string, options: UpdatePlayerOptions): Promise<void>;
249
+ /**
250
+ * Fetch all submissions a player has ever made, ordered newest first.
251
+ * Useful for progression charts and run history screens.
252
+ *
253
+ * @example
254
+ * const { entries, bestScore, total } = await lb.getPlayerScores('player-123');
255
+ */
256
+ getPlayerScores(playerRefId: string, options?: GetPlayerScoresOptions): Promise<GetPlayerScoresResult>;
257
+ }
258
+
259
+ declare class LightLeaderboardError extends Error {
260
+ /** HTTP status code returned by the API */
261
+ readonly status: number;
262
+ /** Raw response body from the API */
263
+ readonly response: unknown;
264
+ constructor(message: string, status: number, response: unknown);
265
+ get isAuthError(): boolean;
266
+ get isRateLimitError(): boolean;
267
+ get isBillingError(): boolean;
268
+ get isValidationError(): boolean;
269
+ }
270
+
271
+ export { type CentricLeaderboardResult, type GetCentricOptions, type GetLeaderboardOptions, type GetLeaderboardResult, type GetPlayerScoresOptions, type GetPlayerScoresResult, type GetRankOptions, type LeaderboardEntry, LightLeaderboard, type LightLeaderboardConfig, LightLeaderboardError, type PlayerProfile, type PlayerRankResult, type PlayerScoreEntry, type SubmitScoreOptions, type SubmitScoreResult, type UpdatePlayerOptions };
@@ -0,0 +1,271 @@
1
+ interface LightLeaderboardConfig {
2
+ /** Your game's API key from the LightLeaderboard dashboard */
3
+ apiKey: string;
4
+ /** Your game's reference ID from the dashboard */
5
+ gameId: string;
6
+ /** Override the API base URL (defaults to https://leaderboard.goproso.com) */
7
+ baseUrl?: string;
8
+ /**
9
+ * Optional secret key to auto-sign score submissions (HMAC-SHA256).
10
+ * Set this if you enabled "Require signed scores" on your game.
11
+ * Uses the Web Crypto API — requires Node 18+ or a modern browser.
12
+ */
13
+ scoreSecret?: string;
14
+ }
15
+ interface LeaderboardEntry {
16
+ id: number;
17
+ /** 1-based position on the leaderboard */
18
+ rank: number;
19
+ playerRefId: string | null;
20
+ playerName: string | null;
21
+ score: number;
22
+ playTimeMs: number | null;
23
+ seasonId: string | null;
24
+ teamId: string | null;
25
+ /** Arbitrary metadata attached at submission time */
26
+ metadata: Record<string, unknown> | null;
27
+ createdAt: string;
28
+ }
29
+ interface GetLeaderboardOptions {
30
+ /** Number of entries to return (1–100, default 20) */
31
+ limit?: number;
32
+ /** Pagination offset (default 0) */
33
+ offset?: number;
34
+ /** Time window filter (default "all") */
35
+ period?: 'all' | 'weekly' | 'monthly';
36
+ /** Only return entries for this season ID */
37
+ season?: string;
38
+ /** Only return entries for this team ID */
39
+ team?: string;
40
+ /**
41
+ * Return every raw submission instead of best-per-player.
42
+ * Default is false — one entry per player (their personal best).
43
+ */
44
+ allEntries?: boolean;
45
+ }
46
+ interface GetLeaderboardResult {
47
+ entries: LeaderboardEntry[];
48
+ scoreOrder: 'asc' | 'desc';
49
+ period: string;
50
+ season: string | null;
51
+ team: string | null;
52
+ limit: number;
53
+ offset: number;
54
+ }
55
+ interface SubmitScoreOptions {
56
+ /** The numeric score to record */
57
+ score: number;
58
+ /** Your game's internal player identifier */
59
+ playerRefId?: string;
60
+ /** Display name shown on the leaderboard */
61
+ playerName?: string;
62
+ /** Duration of this run in milliseconds */
63
+ playTimeMs?: number;
64
+ /** Season bucket for seasonal leaderboards */
65
+ seasonId?: string;
66
+ /** Team bucket for team leaderboards */
67
+ teamId?: string;
68
+ /**
69
+ * Idempotency key — submitting the same ID twice returns the first
70
+ * entry without creating a duplicate.
71
+ */
72
+ submissionId?: string;
73
+ /** Arbitrary JSON metadata to attach to this submission */
74
+ metadata?: Record<string, unknown>;
75
+ /** Custom text stat slot 1 */
76
+ gameStatTxt1?: string;
77
+ /** Custom text stat slot 2 */
78
+ gameStatTxt2?: string;
79
+ /** Custom text stat slot 3 */
80
+ gameStatTxt3?: string;
81
+ /** Custom numeric stat slot 1 */
82
+ gameStatInt1?: number;
83
+ /** Custom numeric stat slot 2 */
84
+ gameStatInt2?: number;
85
+ /** Custom numeric stat slot 3 */
86
+ gameStatInt3?: number;
87
+ }
88
+ interface SubmitScoreResult {
89
+ /** Database ID of the created entry */
90
+ id: number;
91
+ /**
92
+ * Player's current global rank immediately after submission.
93
+ * null when no playerRefId was provided.
94
+ */
95
+ rank: number | null;
96
+ /** true if this score beats the player's previous personal best */
97
+ isPersonalBest: boolean;
98
+ /** Total unique players on this leaderboard */
99
+ totalPlayers: number | null;
100
+ /** true when this submissionId already existed (idempotent replay) */
101
+ deduped?: boolean;
102
+ }
103
+ interface GetRankOptions {
104
+ period?: 'all' | 'weekly' | 'monthly';
105
+ season?: string;
106
+ team?: string;
107
+ }
108
+ interface PlayerRankResult {
109
+ playerRefId: string;
110
+ playerName: string | null;
111
+ /** 1-based rank. null when the player has no entries in this filter. */
112
+ rank: number | null;
113
+ score: number | null;
114
+ totalPlayers: number | null;
115
+ /**
116
+ * How far from the bottom the player is, 0–100.
117
+ * Higher is better: 97.6 = top 2.4 %.
118
+ * null when the player has no entries.
119
+ */
120
+ percentile: number | null;
121
+ period: string;
122
+ season: string | null;
123
+ team: string | null;
124
+ scoreOrder: 'asc' | 'desc';
125
+ }
126
+ interface GetCentricOptions {
127
+ /** Entries to return centered around the player (default 10) */
128
+ limit?: number;
129
+ period?: 'all' | 'weekly' | 'monthly';
130
+ season?: string;
131
+ team?: string;
132
+ }
133
+ interface CentricLeaderboardResult {
134
+ entries: LeaderboardEntry[];
135
+ /** The target player's rank within the current filter */
136
+ playerRank: number | null;
137
+ period: string;
138
+ season: string | null;
139
+ team: string | null;
140
+ scoreOrder: 'asc' | 'desc';
141
+ }
142
+ interface PlayerProfile {
143
+ playerName: string | null;
144
+ avatarUrl: string | null;
145
+ teamId: string | null;
146
+ level: number | null;
147
+ country: string | null;
148
+ device: string | null;
149
+ createdAt: string;
150
+ updatedAt: string;
151
+ }
152
+ interface UpdatePlayerOptions {
153
+ playerName?: string;
154
+ avatarUrl?: string;
155
+ teamId?: string;
156
+ level?: number;
157
+ /** ISO 3166-1 alpha-2 country code, e.g. "US" */
158
+ country?: string;
159
+ device?: string;
160
+ }
161
+ interface PlayerScoreEntry {
162
+ id: number;
163
+ score: number;
164
+ playTimeMs: number | null;
165
+ seasonId: string | null;
166
+ teamId: string | null;
167
+ metadata: Record<string, unknown> | null;
168
+ createdAt: string;
169
+ gameStatTxt1: string | null;
170
+ gameStatTxt2: string | null;
171
+ gameStatTxt3: string | null;
172
+ gameStatInt1: number | null;
173
+ gameStatInt2: number | null;
174
+ gameStatInt3: number | null;
175
+ }
176
+ interface GetPlayerScoresOptions {
177
+ /** Max entries to return (1–200, default 50) */
178
+ limit?: number;
179
+ offset?: number;
180
+ season?: string;
181
+ team?: string;
182
+ }
183
+ interface GetPlayerScoresResult {
184
+ playerRefId: string;
185
+ entries: PlayerScoreEntry[];
186
+ /** Player's all-time best score */
187
+ bestScore: number | null;
188
+ /** Total number of submissions matching the filter */
189
+ total: number;
190
+ limit: number;
191
+ offset: number;
192
+ scoreOrder: 'asc' | 'desc';
193
+ }
194
+
195
+ declare class LightLeaderboard {
196
+ private readonly apiKey;
197
+ private readonly gameId;
198
+ private readonly baseUrl;
199
+ private readonly scoreSecret?;
200
+ constructor(config: LightLeaderboardConfig);
201
+ private gameUrl;
202
+ private fetch;
203
+ /**
204
+ * Submit a score to the leaderboard.
205
+ *
206
+ * Returns the player's new rank immediately — no second API call needed.
207
+ *
208
+ * @example
209
+ * const result = await lb.submitScore({ score: 9500, playerRefId: 'p1', playerName: 'Alice' });
210
+ * console.log(`Rank #${result.rank} of ${result.totalPlayers}`);
211
+ */
212
+ submitScore(options: SubmitScoreOptions): Promise<SubmitScoreResult>;
213
+ /**
214
+ * Fetch the leaderboard. Returns one entry per player (their best score) by default.
215
+ *
216
+ * @example
217
+ * const { entries } = await lb.getLeaderboard({ limit: 10, period: 'weekly' });
218
+ * entries.forEach(e => console.log(`#${e.rank} ${e.playerName}: ${e.score}`));
219
+ */
220
+ getLeaderboard(options?: GetLeaderboardOptions): Promise<GetLeaderboardResult>;
221
+ /**
222
+ * Get a player's current rank, score, and percentile.
223
+ *
224
+ * @example
225
+ * const { rank, percentile } = await lb.getPlayerRank('player-123');
226
+ * console.log(`Rank #${rank} — top ${(100 - percentile).toFixed(1)}%`);
227
+ */
228
+ getPlayerRank(playerRefId: string, options?: GetRankOptions): Promise<PlayerRankResult>;
229
+ /**
230
+ * Fetch the leaderboard centered on a specific player, showing the players
231
+ * immediately above and below them.
232
+ *
233
+ * @example
234
+ * const { entries, playerRank } = await lb.getCentricLeaderboard('player-123', { limit: 11 });
235
+ */
236
+ getCentricLeaderboard(playerRefId: string, options?: GetCentricOptions): Promise<CentricLeaderboardResult>;
237
+ /**
238
+ * Fetch a player's profile (name, avatar, country, level, etc.).
239
+ */
240
+ getPlayer(playerRefId: string): Promise<PlayerProfile>;
241
+ /**
242
+ * Create or update a player's profile. Fields are merged — omitted fields
243
+ * keep their existing values.
244
+ *
245
+ * @example
246
+ * await lb.updatePlayer('player-123', { playerName: 'Alice', country: 'US', level: 5 });
247
+ */
248
+ updatePlayer(playerRefId: string, options: UpdatePlayerOptions): Promise<void>;
249
+ /**
250
+ * Fetch all submissions a player has ever made, ordered newest first.
251
+ * Useful for progression charts and run history screens.
252
+ *
253
+ * @example
254
+ * const { entries, bestScore, total } = await lb.getPlayerScores('player-123');
255
+ */
256
+ getPlayerScores(playerRefId: string, options?: GetPlayerScoresOptions): Promise<GetPlayerScoresResult>;
257
+ }
258
+
259
+ declare class LightLeaderboardError extends Error {
260
+ /** HTTP status code returned by the API */
261
+ readonly status: number;
262
+ /** Raw response body from the API */
263
+ readonly response: unknown;
264
+ constructor(message: string, status: number, response: unknown);
265
+ get isAuthError(): boolean;
266
+ get isRateLimitError(): boolean;
267
+ get isBillingError(): boolean;
268
+ get isValidationError(): boolean;
269
+ }
270
+
271
+ export { type CentricLeaderboardResult, type GetCentricOptions, type GetLeaderboardOptions, type GetLeaderboardResult, type GetPlayerScoresOptions, type GetPlayerScoresResult, type GetRankOptions, type LeaderboardEntry, LightLeaderboard, type LightLeaderboardConfig, LightLeaderboardError, type PlayerProfile, type PlayerRankResult, type PlayerScoreEntry, type SubmitScoreOptions, type SubmitScoreResult, type UpdatePlayerOptions };