@ascnd-gg/client 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Ascnd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,314 @@
1
+ # @ascnd-gg/client
2
+
3
+ TypeScript/JavaScript client library for the [Ascnd](https://ascnd.gg) leaderboard API. Uses gRPC-Web for efficient, type-safe communication.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @ascnd-gg/client
9
+ # or
10
+ yarn add @ascnd-gg/client
11
+ # or
12
+ pnpm add @ascnd-gg/client
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```typescript
18
+ import { AscndClient, create, SubmitScoreRequestSchema } from "@ascnd-gg/client";
19
+
20
+ const client = new AscndClient({
21
+ baseUrl: "https://api.ascnd.gg",
22
+ apiKey: "your-api-key",
23
+ });
24
+
25
+ // Submit a score
26
+ const result = await client.submitScore(
27
+ create(SubmitScoreRequestSchema, {
28
+ leaderboardId: "high-scores",
29
+ playerId: "player-123",
30
+ score: 1000n,
31
+ })
32
+ );
33
+
34
+ console.log(`Rank: #${result.rank}`);
35
+ ```
36
+
37
+ ## Examples
38
+
39
+ Stand-alone example projects are available in the [`examples/`](./examples) directory:
40
+
41
+ | Example | Description |
42
+ |---------|-------------|
43
+ | [`submit-score`](./examples/submit-score) | Submit a score and display the resulting rank |
44
+ | [`leaderboard`](./examples/leaderboard) | Fetch and display the top 10 leaderboard entries |
45
+ | [`metadata-periods`](./examples/metadata-periods) | Submit scores with metadata and query by time period |
46
+
47
+ Each example is a self-contained project. To run:
48
+
49
+ ```bash
50
+ cd examples/submit-score
51
+ npm install
52
+ export ASCND_API_KEY=your_api_key
53
+ export LEADERBOARD_ID=your_leaderboard_id
54
+ npm start
55
+ ```
56
+
57
+ ## Usage
58
+
59
+ ### Creating a Client
60
+
61
+ ```typescript
62
+ import { AscndClient } from "@ascnd-gg/client";
63
+
64
+ const client = new AscndClient({
65
+ baseUrl: "https://api.ascnd.gg", // API base URL
66
+ apiKey: "your-api-key", // Your API key
67
+ timeout: 30000, // Optional: request timeout in ms (default: 30000)
68
+ });
69
+ ```
70
+
71
+ ### Submit a Score
72
+
73
+ Submit a player's score to a leaderboard:
74
+
75
+ ```typescript
76
+ import { create, SubmitScoreRequestSchema } from "@ascnd-gg/client";
77
+
78
+ const result = await client.submitScore(
79
+ create(SubmitScoreRequestSchema, {
80
+ leaderboardId: "high-scores",
81
+ playerId: "player-123",
82
+ score: 1000n,
83
+ metadata: new TextEncoder().encode(JSON.stringify({
84
+ level: 5,
85
+ character: "warrior",
86
+ })),
87
+ idempotencyKey: "unique-key", // Optional: prevent duplicate submissions
88
+ })
89
+ );
90
+
91
+ console.log(`Score ID: ${result.scoreId}`);
92
+ console.log(`Rank: #${result.rank}`);
93
+ console.log(`New personal best: ${result.isNewBest}`);
94
+ console.log(`Was deduplicated: ${result.wasDeduplicated}`);
95
+
96
+ // Check anticheat results (if enabled)
97
+ if (result.anticheat) {
98
+ console.log(`Anticheat passed: ${result.anticheat.passed}`);
99
+ if (!result.anticheat.passed) {
100
+ console.log(`Action taken: ${result.anticheat.action}`);
101
+ for (const violation of result.anticheat.violations) {
102
+ console.log(` - ${violation.flagType}: ${violation.reason}`);
103
+ }
104
+ }
105
+ }
106
+ ```
107
+
108
+ ### Get Leaderboard
109
+
110
+ Retrieve the top scores for a leaderboard:
111
+
112
+ ```typescript
113
+ import { create, GetLeaderboardRequestSchema } from "@ascnd-gg/client";
114
+
115
+ const leaderboard = await client.getLeaderboard(
116
+ create(GetLeaderboardRequestSchema, {
117
+ leaderboardId: "high-scores",
118
+ limit: 10, // Optional: max entries (default: 10, max: 100)
119
+ offset: 0, // Optional: pagination offset
120
+ period: "current", // Optional: "current", "previous", or timestamp
121
+ viewSlug: "platform-pc", // Optional: filter by metadata view
122
+ })
123
+ );
124
+
125
+ console.log(`Total players: ${leaderboard.totalEntries}`);
126
+ console.log(`Period: ${leaderboard.periodStart} - ${leaderboard.periodEnd}`);
127
+
128
+ for (const entry of leaderboard.entries) {
129
+ console.log(`#${entry.rank}: ${entry.playerId} - ${entry.score}`);
130
+ if (entry.bracket) {
131
+ console.log(` Bracket: ${entry.bracket.name} (${entry.bracket.color})`);
132
+ }
133
+ }
134
+
135
+ if (leaderboard.hasMore) {
136
+ // Fetch next page with offset: 10
137
+ }
138
+
139
+ // View info (if filtering by viewSlug)
140
+ if (leaderboard.view) {
141
+ console.log(`Viewing: ${leaderboard.view.name}`);
142
+ }
143
+ ```
144
+
145
+ ### Get Player Rank
146
+
147
+ Get a specific player's rank and score:
148
+
149
+ ```typescript
150
+ import { create, GetPlayerRankRequestSchema } from "@ascnd-gg/client";
151
+
152
+ const playerRank = await client.getPlayerRank(
153
+ create(GetPlayerRankRequestSchema, {
154
+ leaderboardId: "high-scores",
155
+ playerId: "player-123",
156
+ period: "current", // Optional
157
+ viewSlug: "platform-pc", // Optional: filter by metadata view
158
+ })
159
+ );
160
+
161
+ if (playerRank.rank !== undefined) {
162
+ console.log(`Rank: #${playerRank.rank}`);
163
+ console.log(`Score: ${playerRank.score}`);
164
+ console.log(`Best score: ${playerRank.bestScore}`);
165
+ console.log(`Percentile: ${playerRank.percentile}`);
166
+ console.log(`Total players: ${playerRank.totalEntries}`);
167
+
168
+ // Bracket info (if brackets are enabled)
169
+ if (playerRank.bracket) {
170
+ console.log(`Bracket: ${playerRank.bracket.name}`);
171
+ }
172
+
173
+ // Global rank when filtering by view
174
+ if (playerRank.globalRank !== undefined) {
175
+ console.log(`Global Rank: #${playerRank.globalRank}`);
176
+ }
177
+ } else {
178
+ console.log("Player has no score on this leaderboard");
179
+ }
180
+ ```
181
+
182
+ ## Features
183
+
184
+ ### Anticheat
185
+
186
+ When anticheat is enabled for a leaderboard, the `submitScore` response includes validation results:
187
+
188
+ ```typescript
189
+ const result = await client.submitScore(request);
190
+
191
+ if (result.anticheat && !result.anticheat.passed) {
192
+ console.log(`Score flagged: ${result.anticheat.action}`);
193
+ // Possible actions: "none", "flag", "shadow_ban", "reject"
194
+
195
+ for (const violation of result.anticheat.violations) {
196
+ console.log(` ${violation.flagType}: ${violation.reason}`);
197
+ // Flag types: "bounds_exceeded", "velocity_exceeded",
198
+ // "duplicate_idempotency", "missing_idempotency_key"
199
+ }
200
+ }
201
+ ```
202
+
203
+ ### Brackets
204
+
205
+ Leaderboards with brackets enabled include tier information for each entry:
206
+
207
+ ```typescript
208
+ const leaderboard = await client.getLeaderboard(request);
209
+
210
+ for (const entry of leaderboard.entries) {
211
+ if (entry.bracket) {
212
+ console.log(`${entry.playerId} is in ${entry.bracket.name}`);
213
+ // entry.bracket.color contains hex color code like "#FFD700"
214
+ }
215
+ }
216
+ ```
217
+
218
+ ### Metadata Views
219
+
220
+ Filter leaderboards by metadata views to create segmented rankings:
221
+
222
+ ```typescript
223
+ import { create, GetLeaderboardRequestSchema } from "@ascnd-gg/client";
224
+
225
+ // Get PC-only leaderboard
226
+ const pcLeaderboard = await client.getLeaderboard(
227
+ create(GetLeaderboardRequestSchema, {
228
+ leaderboardId: "high-scores",
229
+ viewSlug: "platform-pc",
230
+ })
231
+ );
232
+
233
+ // Rankings are within the view
234
+ // Use globalRank in GetPlayerRank to see overall position
235
+ ```
236
+
237
+ ## Error Handling
238
+
239
+ The client throws `AscndError` for API errors:
240
+
241
+ ```typescript
242
+ import { AscndClient, AscndError } from "@ascnd-gg/client";
243
+
244
+ try {
245
+ await client.submitScore(request);
246
+ } catch (error) {
247
+ if (error instanceof AscndError) {
248
+ console.error(`API Error: ${error.message}`);
249
+ console.error(`Code: ${error.code}`);
250
+ console.error(`Details:`, error.details);
251
+ } else {
252
+ throw error;
253
+ }
254
+ }
255
+ ```
256
+
257
+ ## API Reference
258
+
259
+ ### AscndClient
260
+
261
+ #### Constructor
262
+
263
+ ```typescript
264
+ new AscndClient(config: AscndClientConfig)
265
+ ```
266
+
267
+ | Option | Type | Required | Description |
268
+ |--------|------|----------|-------------|
269
+ | `baseUrl` | `string` | Yes | The base URL of the Ascnd API |
270
+ | `apiKey` | `string` | Yes | Your API key for authentication |
271
+ | `timeout` | `number` | No | Request timeout in milliseconds (default: 30000) |
272
+
273
+ #### Methods
274
+
275
+ ##### `submitScore(request: SubmitScoreRequest): Promise<SubmitScoreResponse>`
276
+
277
+ Submit a player's score to a leaderboard.
278
+
279
+ ##### `getLeaderboard(request: GetLeaderboardRequest): Promise<GetLeaderboardResponse>`
280
+
281
+ Retrieve the top scores for a leaderboard.
282
+
283
+ ##### `getPlayerRank(request: GetPlayerRankRequest): Promise<GetPlayerRankResponse>`
284
+
285
+ Get a specific player's rank and score.
286
+
287
+ ### Message Creation
288
+
289
+ Use `create()` with schemas to create properly typed request messages:
290
+
291
+ ```typescript
292
+ import { create, SubmitScoreRequestSchema } from "@ascnd-gg/client";
293
+
294
+ const request = create(SubmitScoreRequestSchema, {
295
+ leaderboardId: "high-scores",
296
+ playerId: "player-123",
297
+ score: 1000n, // Note: scores are bigint
298
+ });
299
+ ```
300
+
301
+ ## Requirements
302
+
303
+ - Node.js 18+ (for native fetch support)
304
+ - Or any runtime with `fetch` available (browsers, Deno, Bun, Cloudflare Workers, etc.)
305
+
306
+ ## Links
307
+
308
+ - [Documentation](https://ascnd.gg/docs/sdks/typescript)
309
+ - [GitHub](https://github.com/ascnd-gg/ascnd-client-js)
310
+ - [npm](https://www.npmjs.com/package/@ascnd-gg/client)
311
+
312
+ ## License
313
+
314
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,253 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AnticheatResultSchema: () => AnticheatResultSchema,
24
+ AnticheatViolationSchema: () => AnticheatViolationSchema,
25
+ AscndClient: () => AscndClient,
26
+ AscndError: () => AscndError,
27
+ AscndService: () => AscndService,
28
+ BracketInfoSchema: () => BracketInfoSchema,
29
+ GetLeaderboardRequestSchema: () => GetLeaderboardRequestSchema,
30
+ GetLeaderboardResponseSchema: () => GetLeaderboardResponseSchema,
31
+ GetPlayerRankRequestSchema: () => GetPlayerRankRequestSchema,
32
+ GetPlayerRankResponseSchema: () => GetPlayerRankResponseSchema,
33
+ LeaderboardEntrySchema: () => LeaderboardEntrySchema,
34
+ SubmitScoreRequestSchema: () => SubmitScoreRequestSchema,
35
+ SubmitScoreResponseSchema: () => SubmitScoreResponseSchema,
36
+ ViewInfoSchema: () => ViewInfoSchema,
37
+ create: () => import_protobuf.create
38
+ });
39
+ module.exports = __toCommonJS(index_exports);
40
+
41
+ // src/client.ts
42
+ var import_connect = require("@connectrpc/connect");
43
+ var import_connect_web = require("@connectrpc/connect-web");
44
+
45
+ // src/gen/ascnd_pb.ts
46
+ var import_codegenv2 = require("@bufbuild/protobuf/codegenv2");
47
+ var file_ascnd = /* @__PURE__ */ (0, import_codegenv2.fileDesc)("Cgthc2NuZC5wcm90bxIIYXNjbmQudjEipAEKElN1Ym1pdFNjb3JlUmVxdWVzdBIWCg5sZWFkZXJib2FyZF9pZBgBIAEoCRIRCglwbGF5ZXJfaWQYAiABKAkSDQoFc2NvcmUYAyABKAMSFQoIbWV0YWRhdGEYBCABKAxIAIgBARIcCg9pZGVtcG90ZW5jeV9rZXkYBSABKAlIAYgBAUILCglfbWV0YWRhdGFCEgoQX2lkZW1wb3RlbmN5X2tleSKlAQoTU3VibWl0U2NvcmVSZXNwb25zZRIQCghzY29yZV9pZBgBIAEoCRIMCgRyYW5rGAIgASgFEhMKC2lzX25ld19iZXN0GAMgASgIEhgKEHdhc19kZWR1cGxpY2F0ZWQYBCABKAgSMQoJYW50aWNoZWF0GAUgASgLMhkuYXNjbmQudjEuQW50aWNoZWF0UmVzdWx0SACIAQFCDAoKX2FudGljaGVhdCJjCg9BbnRpY2hlYXRSZXN1bHQSDgoGcGFzc2VkGAEgASgIEjAKCnZpb2xhdGlvbnMYAiADKAsyHC5hc2NuZC52MS5BbnRpY2hlYXRWaW9sYXRpb24SDgoGYWN0aW9uGAMgASgJIjcKEkFudGljaGVhdFZpb2xhdGlvbhIRCglmbGFnX3R5cGUYASABKAkSDgoGcmVhc29uGAIgASgJIrMBChVHZXRMZWFkZXJib2FyZFJlcXVlc3QSFgoObGVhZGVyYm9hcmRfaWQYASABKAkSEgoFbGltaXQYAiABKAVIAIgBARITCgZvZmZzZXQYAyABKAVIAYgBARITCgZwZXJpb2QYBCABKAlIAogBARIWCgl2aWV3X3NsdWcYBSABKAlIA4gBAUIICgZfbGltaXRCCQoHX29mZnNldEIJCgdfcGVyaW9kQgwKCl92aWV3X3NsdWci3AEKFkdldExlYWRlcmJvYXJkUmVzcG9uc2USKwoHZW50cmllcxgBIAMoCzIaLmFzY25kLnYxLkxlYWRlcmJvYXJkRW50cnkSFQoNdG90YWxfZW50cmllcxgCIAEoBRIQCghoYXNfbW9yZRgDIAEoCBIUCgxwZXJpb2Rfc3RhcnQYBCABKAkSFwoKcGVyaW9kX2VuZBgFIAEoCUgAiAEBEiUKBHZpZXcYBiABKAsyEi5hc2NuZC52MS5WaWV3SW5mb0gBiAEBQg0KC19wZXJpb2RfZW5kQgcKBV92aWV3IrUBChBMZWFkZXJib2FyZEVudHJ5EgwKBHJhbmsYASABKAUSEQoJcGxheWVyX2lkGAIgASgJEg0KBXNjb3JlGAMgASgDEhQKDHN1Ym1pdHRlZF9hdBgEIAEoCRIVCghtZXRhZGF0YRgFIAEoDEgAiAEBEisKB2JyYWNrZXQYBiABKAsyFS5hc2NuZC52MS5CcmFja2V0SW5mb0gBiAEBQgsKCV9tZXRhZGF0YUIKCghfYnJhY2tldCJFCgtCcmFja2V0SW5mbxIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJEhIKBWNvbG9yGAMgASgJSACIAQFCCAoGX2NvbG9yIiYKCFZpZXdJbmZvEgwKBHNsdWcYASABKAkSDAoEbmFtZRgCIAEoCSKHAQoUR2V0UGxheWVyUmFua1JlcXVlc3QSFgoObGVhZGVyYm9hcmRfaWQYASABKAkSEQoJcGxheWVyX2lkGAIgASgJEhMKBnBlcmlvZBgDIAEoCUgAiAEBEhYKCXZpZXdfc2x1ZxgEIAEoCUgBiAEBQgkKB19wZXJpb2RCDAoKX3ZpZXdfc2x1ZyLLAgoVR2V0UGxheWVyUmFua1Jlc3BvbnNlEhEKBHJhbmsYASABKAVIAIgBARISCgVzY29yZRgCIAEoA0gBiAEBEhcKCmJlc3Rfc2NvcmUYAyABKANIAogBARIVCg10b3RhbF9lbnRyaWVzGAQgASgFEhcKCnBlcmNlbnRpbGUYBSABKAlIA4gBARIrCgdicmFja2V0GAYgASgLMhUuYXNjbmQudjEuQnJhY2tldEluZm9IBIgBARIlCgR2aWV3GAcgASgLMhIuYXNjbmQudjEuVmlld0luZm9IBYgBARIYCgtnbG9iYWxfcmFuaxgIIAEoBUgGiAEBQgcKBV9yYW5rQggKBl9zY29yZUINCgtfYmVzdF9zY29yZUINCgtfcGVyY2VudGlsZUIKCghfYnJhY2tldEIHCgVfdmlld0IOCgxfZ2xvYmFsX3JhbmsygQIKDEFzY25kU2VydmljZRJKCgtTdWJtaXRTY29yZRIcLmFzY25kLnYxLlN1Ym1pdFNjb3JlUmVxdWVzdBodLmFzY25kLnYxLlN1Ym1pdFNjb3JlUmVzcG9uc2USUwoOR2V0TGVhZGVyYm9hcmQSHy5hc2NuZC52MS5HZXRMZWFkZXJib2FyZFJlcXVlc3QaIC5hc2NuZC52MS5HZXRMZWFkZXJib2FyZFJlc3BvbnNlElAKDUdldFBsYXllclJhbmsSHi5hc2NuZC52MS5HZXRQbGF5ZXJSYW5rUmVxdWVzdBofLmFzY25kLnYxLkdldFBsYXllclJhbmtSZXNwb25zZWIGcHJvdG8z");
48
+ var SubmitScoreRequestSchema = /* @__PURE__ */ (0, import_codegenv2.messageDesc)(file_ascnd, 0);
49
+ var SubmitScoreResponseSchema = /* @__PURE__ */ (0, import_codegenv2.messageDesc)(file_ascnd, 1);
50
+ var AnticheatResultSchema = /* @__PURE__ */ (0, import_codegenv2.messageDesc)(file_ascnd, 2);
51
+ var AnticheatViolationSchema = /* @__PURE__ */ (0, import_codegenv2.messageDesc)(file_ascnd, 3);
52
+ var GetLeaderboardRequestSchema = /* @__PURE__ */ (0, import_codegenv2.messageDesc)(file_ascnd, 4);
53
+ var GetLeaderboardResponseSchema = /* @__PURE__ */ (0, import_codegenv2.messageDesc)(file_ascnd, 5);
54
+ var LeaderboardEntrySchema = /* @__PURE__ */ (0, import_codegenv2.messageDesc)(file_ascnd, 6);
55
+ var BracketInfoSchema = /* @__PURE__ */ (0, import_codegenv2.messageDesc)(file_ascnd, 7);
56
+ var ViewInfoSchema = /* @__PURE__ */ (0, import_codegenv2.messageDesc)(file_ascnd, 8);
57
+ var GetPlayerRankRequestSchema = /* @__PURE__ */ (0, import_codegenv2.messageDesc)(file_ascnd, 9);
58
+ var GetPlayerRankResponseSchema = /* @__PURE__ */ (0, import_codegenv2.messageDesc)(file_ascnd, 10);
59
+ var AscndService = /* @__PURE__ */ (0, import_codegenv2.serviceDesc)(file_ascnd, 0);
60
+
61
+ // src/types.ts
62
+ var AscndError = class extends Error {
63
+ /** gRPC/Connect error code. */
64
+ code;
65
+ /** Additional error details. */
66
+ details;
67
+ constructor(message, code, details) {
68
+ super(message);
69
+ this.name = "AscndError";
70
+ this.code = code;
71
+ this.details = details;
72
+ }
73
+ };
74
+
75
+ // src/client.ts
76
+ var AscndClient = class {
77
+ client;
78
+ /**
79
+ * Creates a new Ascnd client.
80
+ *
81
+ * @param config - Client configuration options.
82
+ */
83
+ constructor(config) {
84
+ if (!config.baseUrl) {
85
+ throw new Error("baseUrl is required");
86
+ }
87
+ if (!config.apiKey) {
88
+ throw new Error("apiKey is required");
89
+ }
90
+ const authInterceptor = (next) => async (req) => {
91
+ req.header.set("x-api-key", config.apiKey);
92
+ return await next(req);
93
+ };
94
+ const transport = (0, import_connect_web.createGrpcWebTransport)({
95
+ baseUrl: config.baseUrl.replace(/\/$/, ""),
96
+ interceptors: [authInterceptor],
97
+ defaultTimeoutMs: config.timeout ?? 3e4
98
+ });
99
+ this.client = (0, import_connect.createClient)(AscndService, transport);
100
+ }
101
+ /**
102
+ * Submits a player's score to a leaderboard.
103
+ *
104
+ * @param request - The score submission request.
105
+ * @returns The submission result including the player's new rank.
106
+ * @throws {AscndError} If the API returns an error.
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * import { create } from "@bufbuild/protobuf";
111
+ * import { SubmitScoreRequestSchema } from "@ascnd-gg/client";
112
+ *
113
+ * const result = await client.submitScore(
114
+ * create(SubmitScoreRequestSchema, {
115
+ * leaderboardId: "high-scores",
116
+ * playerId: "player-123",
117
+ * score: 1000n,
118
+ * metadata: new TextEncoder().encode(JSON.stringify({ level: 5 })),
119
+ * })
120
+ * );
121
+ *
122
+ * console.log(`New rank: ${result.rank}`);
123
+ * if (result.isNewBest) {
124
+ * console.log("New personal best!");
125
+ * }
126
+ * if (result.anticheat && !result.anticheat.passed) {
127
+ * console.log("Anticheat flagged:", result.anticheat.violations);
128
+ * }
129
+ * ```
130
+ */
131
+ async submitScore(request) {
132
+ try {
133
+ return await this.client.submitScore(request);
134
+ } catch (error) {
135
+ throw this.convertError(error);
136
+ }
137
+ }
138
+ /**
139
+ * Retrieves the top scores for a leaderboard.
140
+ *
141
+ * @param request - The leaderboard request parameters.
142
+ * @returns The leaderboard entries and pagination info.
143
+ * @throws {AscndError} If the API returns an error.
144
+ *
145
+ * @example
146
+ * ```typescript
147
+ * import { create } from "@bufbuild/protobuf";
148
+ * import { GetLeaderboardRequestSchema } from "@ascnd-gg/client";
149
+ *
150
+ * const leaderboard = await client.getLeaderboard(
151
+ * create(GetLeaderboardRequestSchema, {
152
+ * leaderboardId: "high-scores",
153
+ * limit: 10,
154
+ * viewSlug: "platform-pc", // Filter by metadata view
155
+ * })
156
+ * );
157
+ *
158
+ * for (const entry of leaderboard.entries) {
159
+ * console.log(`#${entry.rank}: ${entry.playerId} - ${entry.score}`);
160
+ * if (entry.bracket) {
161
+ * console.log(` Bracket: ${entry.bracket.name}`);
162
+ * }
163
+ * }
164
+ * ```
165
+ */
166
+ async getLeaderboard(request) {
167
+ try {
168
+ return await this.client.getLeaderboard(request);
169
+ } catch (error) {
170
+ throw this.convertError(error);
171
+ }
172
+ }
173
+ /**
174
+ * Retrieves a specific player's rank and score information.
175
+ *
176
+ * @param request - The player rank request parameters.
177
+ * @returns The player's rank, score, and percentile information.
178
+ * @throws {AscndError} If the API returns an error.
179
+ *
180
+ * @example
181
+ * ```typescript
182
+ * import { create } from "@bufbuild/protobuf";
183
+ * import { GetPlayerRankRequestSchema } from "@ascnd-gg/client";
184
+ *
185
+ * const playerRank = await client.getPlayerRank(
186
+ * create(GetPlayerRankRequestSchema, {
187
+ * leaderboardId: "high-scores",
188
+ * playerId: "player-123",
189
+ * viewSlug: "platform-pc",
190
+ * })
191
+ * );
192
+ *
193
+ * if (playerRank.rank !== undefined) {
194
+ * console.log(`Rank: #${playerRank.rank}`);
195
+ * console.log(`Score: ${playerRank.score}`);
196
+ * console.log(`Percentile: ${playerRank.percentile}`);
197
+ * if (playerRank.bracket) {
198
+ * console.log(`Bracket: ${playerRank.bracket.name}`);
199
+ * }
200
+ * if (playerRank.globalRank !== undefined) {
201
+ * console.log(`Global Rank: #${playerRank.globalRank}`);
202
+ * }
203
+ * } else {
204
+ * console.log("Player not on leaderboard");
205
+ * }
206
+ * ```
207
+ */
208
+ async getPlayerRank(request) {
209
+ try {
210
+ return await this.client.getPlayerRank(request);
211
+ } catch (error) {
212
+ throw this.convertError(error);
213
+ }
214
+ }
215
+ /**
216
+ * Converts a Connect error to an AscndError.
217
+ */
218
+ convertError(error) {
219
+ if (error instanceof import_connect.ConnectError) {
220
+ return new AscndError(
221
+ error.message,
222
+ error.code.toString(),
223
+ error.details?.length ? { details: error.details } : void 0
224
+ );
225
+ }
226
+ if (error instanceof Error) {
227
+ return new AscndError(error.message, "UNKNOWN");
228
+ }
229
+ return new AscndError("Unknown error occurred", "UNKNOWN");
230
+ }
231
+ };
232
+
233
+ // src/index.ts
234
+ var import_protobuf = require("@bufbuild/protobuf");
235
+ // Annotate the CommonJS export names for ESM import in node:
236
+ 0 && (module.exports = {
237
+ AnticheatResultSchema,
238
+ AnticheatViolationSchema,
239
+ AscndClient,
240
+ AscndError,
241
+ AscndService,
242
+ BracketInfoSchema,
243
+ GetLeaderboardRequestSchema,
244
+ GetLeaderboardResponseSchema,
245
+ GetPlayerRankRequestSchema,
246
+ GetPlayerRankResponseSchema,
247
+ LeaderboardEntrySchema,
248
+ SubmitScoreRequestSchema,
249
+ SubmitScoreResponseSchema,
250
+ ViewInfoSchema,
251
+ create
252
+ });
253
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/gen/ascnd_pb.ts","../src/types.ts"],"sourcesContent":["/**\n * @ascnd-gg/client - TypeScript/JavaScript client for the Ascnd leaderboard API.\n *\n * @example\n * ```typescript\n * import { AscndClient, create, SubmitScoreRequestSchema } from \"@ascnd-gg/client\";\n *\n * const client = new AscndClient({\n * baseUrl: \"https://api.ascnd.gg\",\n * apiKey: \"your-api-key\",\n * });\n *\n * // Submit a score\n * const result = await client.submitScore(\n * create(SubmitScoreRequestSchema, {\n * leaderboardId: \"high-scores\",\n * playerId: \"player-123\",\n * score: 1000n,\n * })\n * );\n *\n * console.log(`New rank: ${result.rank}`);\n * ```\n *\n * @packageDocumentation\n */\n\n// Export the main client class\nexport { AscndClient } from \"./client.js\";\n\n// Re-export create helper from protobuf for convenience\nexport { create } from \"@bufbuild/protobuf\";\n\n// Export all types\nexport type {\n // Client configuration\n AscndClientConfig,\n\n // Submit Score\n SubmitScoreRequest,\n SubmitScoreResponse,\n\n // Get Leaderboard\n GetLeaderboardRequest,\n GetLeaderboardResponse,\n LeaderboardEntry,\n\n // Get Player Rank\n GetPlayerRankRequest,\n GetPlayerRankResponse,\n\n // Anticheat\n AnticheatResult,\n AnticheatViolation,\n\n // Bracket\n BracketInfo,\n\n // View\n ViewInfo,\n} from \"./types.js\";\n\n// Export error class\nexport { AscndError } from \"./types.js\";\n\n// Export the service definition\nexport { AscndService } from \"./types.js\";\n\n// Export schemas for creating messages\nexport {\n SubmitScoreRequestSchema,\n SubmitScoreResponseSchema,\n GetLeaderboardRequestSchema,\n GetLeaderboardResponseSchema,\n GetPlayerRankRequestSchema,\n GetPlayerRankResponseSchema,\n LeaderboardEntrySchema,\n AnticheatResultSchema,\n AnticheatViolationSchema,\n BracketInfoSchema,\n ViewInfoSchema,\n} from \"./types.js\";\n","import { createClient, type Client, type Interceptor, ConnectError } from \"@connectrpc/connect\";\nimport { createGrpcWebTransport } from \"@connectrpc/connect-web\";\nimport {\n AscndService,\n type SubmitScoreRequest,\n type SubmitScoreResponse,\n type GetLeaderboardRequest,\n type GetLeaderboardResponse,\n type GetPlayerRankRequest,\n type GetPlayerRankResponse,\n AscndClientConfig,\n AscndError,\n} from \"./types.js\";\n\n/**\n * Client for the Ascnd leaderboard API using gRPC-Web.\n *\n * @example\n * ```typescript\n * import { AscndClient, create } from \"@ascnd-gg/client\";\n * import { SubmitScoreRequestSchema } from \"@ascnd-gg/client\";\n *\n * const client = new AscndClient({\n * baseUrl: \"https://api.ascnd.gg\",\n * apiKey: \"your-api-key\",\n * });\n *\n * // Submit a score\n * const result = await client.submitScore(\n * create(SubmitScoreRequestSchema, {\n * leaderboardId: \"high-scores\",\n * playerId: \"player-123\",\n * score: 1000n,\n * })\n * );\n *\n * console.log(`Rank: #${result.rank}`);\n * ```\n */\nexport class AscndClient {\n private readonly client: Client<typeof AscndService>;\n\n /**\n * Creates a new Ascnd client.\n *\n * @param config - Client configuration options.\n */\n constructor(config: AscndClientConfig) {\n if (!config.baseUrl) {\n throw new Error(\"baseUrl is required\");\n }\n if (!config.apiKey) {\n throw new Error(\"apiKey is required\");\n }\n\n // Create an interceptor to add the API key header\n const authInterceptor: Interceptor = (next) => async (req) => {\n req.header.set(\"x-api-key\", config.apiKey);\n return await next(req);\n };\n\n // Create the gRPC-Web transport\n const transport = createGrpcWebTransport({\n baseUrl: config.baseUrl.replace(/\\/$/, \"\"),\n interceptors: [authInterceptor],\n defaultTimeoutMs: config.timeout ?? 30000,\n });\n\n this.client = createClient(AscndService, transport);\n }\n\n /**\n * Submits a player's score to a leaderboard.\n *\n * @param request - The score submission request.\n * @returns The submission result including the player's new rank.\n * @throws {AscndError} If the API returns an error.\n *\n * @example\n * ```typescript\n * import { create } from \"@bufbuild/protobuf\";\n * import { SubmitScoreRequestSchema } from \"@ascnd-gg/client\";\n *\n * const result = await client.submitScore(\n * create(SubmitScoreRequestSchema, {\n * leaderboardId: \"high-scores\",\n * playerId: \"player-123\",\n * score: 1000n,\n * metadata: new TextEncoder().encode(JSON.stringify({ level: 5 })),\n * })\n * );\n *\n * console.log(`New rank: ${result.rank}`);\n * if (result.isNewBest) {\n * console.log(\"New personal best!\");\n * }\n * if (result.anticheat && !result.anticheat.passed) {\n * console.log(\"Anticheat flagged:\", result.anticheat.violations);\n * }\n * ```\n */\n async submitScore(request: SubmitScoreRequest): Promise<SubmitScoreResponse> {\n try {\n return await this.client.submitScore(request);\n } catch (error) {\n throw this.convertError(error);\n }\n }\n\n /**\n * Retrieves the top scores for a leaderboard.\n *\n * @param request - The leaderboard request parameters.\n * @returns The leaderboard entries and pagination info.\n * @throws {AscndError} If the API returns an error.\n *\n * @example\n * ```typescript\n * import { create } from \"@bufbuild/protobuf\";\n * import { GetLeaderboardRequestSchema } from \"@ascnd-gg/client\";\n *\n * const leaderboard = await client.getLeaderboard(\n * create(GetLeaderboardRequestSchema, {\n * leaderboardId: \"high-scores\",\n * limit: 10,\n * viewSlug: \"platform-pc\", // Filter by metadata view\n * })\n * );\n *\n * for (const entry of leaderboard.entries) {\n * console.log(`#${entry.rank}: ${entry.playerId} - ${entry.score}`);\n * if (entry.bracket) {\n * console.log(` Bracket: ${entry.bracket.name}`);\n * }\n * }\n * ```\n */\n async getLeaderboard(\n request: GetLeaderboardRequest\n ): Promise<GetLeaderboardResponse> {\n try {\n return await this.client.getLeaderboard(request);\n } catch (error) {\n throw this.convertError(error);\n }\n }\n\n /**\n * Retrieves a specific player's rank and score information.\n *\n * @param request - The player rank request parameters.\n * @returns The player's rank, score, and percentile information.\n * @throws {AscndError} If the API returns an error.\n *\n * @example\n * ```typescript\n * import { create } from \"@bufbuild/protobuf\";\n * import { GetPlayerRankRequestSchema } from \"@ascnd-gg/client\";\n *\n * const playerRank = await client.getPlayerRank(\n * create(GetPlayerRankRequestSchema, {\n * leaderboardId: \"high-scores\",\n * playerId: \"player-123\",\n * viewSlug: \"platform-pc\",\n * })\n * );\n *\n * if (playerRank.rank !== undefined) {\n * console.log(`Rank: #${playerRank.rank}`);\n * console.log(`Score: ${playerRank.score}`);\n * console.log(`Percentile: ${playerRank.percentile}`);\n * if (playerRank.bracket) {\n * console.log(`Bracket: ${playerRank.bracket.name}`);\n * }\n * if (playerRank.globalRank !== undefined) {\n * console.log(`Global Rank: #${playerRank.globalRank}`);\n * }\n * } else {\n * console.log(\"Player not on leaderboard\");\n * }\n * ```\n */\n async getPlayerRank(\n request: GetPlayerRankRequest\n ): Promise<GetPlayerRankResponse> {\n try {\n return await this.client.getPlayerRank(request);\n } catch (error) {\n throw this.convertError(error);\n }\n }\n\n /**\n * Converts a Connect error to an AscndError.\n */\n private convertError(error: unknown): AscndError {\n if (error instanceof ConnectError) {\n return new AscndError(\n error.message,\n error.code.toString(),\n error.details?.length ? { details: error.details } : undefined\n );\n }\n if (error instanceof Error) {\n return new AscndError(error.message, \"UNKNOWN\");\n }\n return new AscndError(\"Unknown error occurred\", \"UNKNOWN\");\n }\n}\n","// @generated by protoc-gen-es v2.10.2 with parameter \"target=ts\"\n// @generated from file ascnd.proto (package ascnd.v1, syntax proto3)\n/* eslint-disable */\n\nimport type { GenFile, GenMessage, GenService } from \"@bufbuild/protobuf/codegenv2\";\nimport { fileDesc, messageDesc, serviceDesc } from \"@bufbuild/protobuf/codegenv2\";\nimport type { Message } from \"@bufbuild/protobuf\";\n\n/**\n * Describes the file ascnd.proto.\n */\nexport const file_ascnd: GenFile = /*@__PURE__*/\n fileDesc(\"Cgthc2NuZC5wcm90bxIIYXNjbmQudjEipAEKElN1Ym1pdFNjb3JlUmVxdWVzdBIWCg5sZWFkZXJib2FyZF9pZBgBIAEoCRIRCglwbGF5ZXJfaWQYAiABKAkSDQoFc2NvcmUYAyABKAMSFQoIbWV0YWRhdGEYBCABKAxIAIgBARIcCg9pZGVtcG90ZW5jeV9rZXkYBSABKAlIAYgBAUILCglfbWV0YWRhdGFCEgoQX2lkZW1wb3RlbmN5X2tleSKlAQoTU3VibWl0U2NvcmVSZXNwb25zZRIQCghzY29yZV9pZBgBIAEoCRIMCgRyYW5rGAIgASgFEhMKC2lzX25ld19iZXN0GAMgASgIEhgKEHdhc19kZWR1cGxpY2F0ZWQYBCABKAgSMQoJYW50aWNoZWF0GAUgASgLMhkuYXNjbmQudjEuQW50aWNoZWF0UmVzdWx0SACIAQFCDAoKX2FudGljaGVhdCJjCg9BbnRpY2hlYXRSZXN1bHQSDgoGcGFzc2VkGAEgASgIEjAKCnZpb2xhdGlvbnMYAiADKAsyHC5hc2NuZC52MS5BbnRpY2hlYXRWaW9sYXRpb24SDgoGYWN0aW9uGAMgASgJIjcKEkFudGljaGVhdFZpb2xhdGlvbhIRCglmbGFnX3R5cGUYASABKAkSDgoGcmVhc29uGAIgASgJIrMBChVHZXRMZWFkZXJib2FyZFJlcXVlc3QSFgoObGVhZGVyYm9hcmRfaWQYASABKAkSEgoFbGltaXQYAiABKAVIAIgBARITCgZvZmZzZXQYAyABKAVIAYgBARITCgZwZXJpb2QYBCABKAlIAogBARIWCgl2aWV3X3NsdWcYBSABKAlIA4gBAUIICgZfbGltaXRCCQoHX29mZnNldEIJCgdfcGVyaW9kQgwKCl92aWV3X3NsdWci3AEKFkdldExlYWRlcmJvYXJkUmVzcG9uc2USKwoHZW50cmllcxgBIAMoCzIaLmFzY25kLnYxLkxlYWRlcmJvYXJkRW50cnkSFQoNdG90YWxfZW50cmllcxgCIAEoBRIQCghoYXNfbW9yZRgDIAEoCBIUCgxwZXJpb2Rfc3RhcnQYBCABKAkSFwoKcGVyaW9kX2VuZBgFIAEoCUgAiAEBEiUKBHZpZXcYBiABKAsyEi5hc2NuZC52MS5WaWV3SW5mb0gBiAEBQg0KC19wZXJpb2RfZW5kQgcKBV92aWV3IrUBChBMZWFkZXJib2FyZEVudHJ5EgwKBHJhbmsYASABKAUSEQoJcGxheWVyX2lkGAIgASgJEg0KBXNjb3JlGAMgASgDEhQKDHN1Ym1pdHRlZF9hdBgEIAEoCRIVCghtZXRhZGF0YRgFIAEoDEgAiAEBEisKB2JyYWNrZXQYBiABKAsyFS5hc2NuZC52MS5CcmFja2V0SW5mb0gBiAEBQgsKCV9tZXRhZGF0YUIKCghfYnJhY2tldCJFCgtCcmFja2V0SW5mbxIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJEhIKBWNvbG9yGAMgASgJSACIAQFCCAoGX2NvbG9yIiYKCFZpZXdJbmZvEgwKBHNsdWcYASABKAkSDAoEbmFtZRgCIAEoCSKHAQoUR2V0UGxheWVyUmFua1JlcXVlc3QSFgoObGVhZGVyYm9hcmRfaWQYASABKAkSEQoJcGxheWVyX2lkGAIgASgJEhMKBnBlcmlvZBgDIAEoCUgAiAEBEhYKCXZpZXdfc2x1ZxgEIAEoCUgBiAEBQgkKB19wZXJpb2RCDAoKX3ZpZXdfc2x1ZyLLAgoVR2V0UGxheWVyUmFua1Jlc3BvbnNlEhEKBHJhbmsYASABKAVIAIgBARISCgVzY29yZRgCIAEoA0gBiAEBEhcKCmJlc3Rfc2NvcmUYAyABKANIAogBARIVCg10b3RhbF9lbnRyaWVzGAQgASgFEhcKCnBlcmNlbnRpbGUYBSABKAlIA4gBARIrCgdicmFja2V0GAYgASgLMhUuYXNjbmQudjEuQnJhY2tldEluZm9IBIgBARIlCgR2aWV3GAcgASgLMhIuYXNjbmQudjEuVmlld0luZm9IBYgBARIYCgtnbG9iYWxfcmFuaxgIIAEoBUgGiAEBQgcKBV9yYW5rQggKBl9zY29yZUINCgtfYmVzdF9zY29yZUINCgtfcGVyY2VudGlsZUIKCghfYnJhY2tldEIHCgVfdmlld0IOCgxfZ2xvYmFsX3JhbmsygQIKDEFzY25kU2VydmljZRJKCgtTdWJtaXRTY29yZRIcLmFzY25kLnYxLlN1Ym1pdFNjb3JlUmVxdWVzdBodLmFzY25kLnYxLlN1Ym1pdFNjb3JlUmVzcG9uc2USUwoOR2V0TGVhZGVyYm9hcmQSHy5hc2NuZC52MS5HZXRMZWFkZXJib2FyZFJlcXVlc3QaIC5hc2NuZC52MS5HZXRMZWFkZXJib2FyZFJlc3BvbnNlElAKDUdldFBsYXllclJhbmsSHi5hc2NuZC52MS5HZXRQbGF5ZXJSYW5rUmVxdWVzdBofLmFzY25kLnYxLkdldFBsYXllclJhbmtSZXNwb25zZWIGcHJvdG8z\");\n\n/**\n * SubmitScoreRequest contains the score submission details.\n *\n * @generated from message ascnd.v1.SubmitScoreRequest\n */\nexport type SubmitScoreRequest = Message<\"ascnd.v1.SubmitScoreRequest\"> & {\n /**\n * The leaderboard to submit the score to.\n *\n * @generated from field: string leaderboard_id = 1;\n */\n leaderboardId: string;\n\n /**\n * The player's unique identifier (provided by the game).\n *\n * @generated from field: string player_id = 2;\n */\n playerId: string;\n\n /**\n * The score value.\n *\n * @generated from field: int64 score = 3;\n */\n score: bigint;\n\n /**\n * Optional metadata (JSON-encoded game-specific data).\n *\n * @generated from field: optional bytes metadata = 4;\n */\n metadata?: Uint8Array;\n\n /**\n * Optional idempotency key to prevent duplicate submissions.\n *\n * @generated from field: optional string idempotency_key = 5;\n */\n idempotencyKey?: string;\n};\n\n/**\n * Describes the message ascnd.v1.SubmitScoreRequest.\n * Use `create(SubmitScoreRequestSchema)` to create a new message.\n */\nexport const SubmitScoreRequestSchema: GenMessage<SubmitScoreRequest> = /*@__PURE__*/\n messageDesc(file_ascnd, 0);\n\n/**\n * SubmitScoreResponse contains the result of the score submission.\n *\n * @generated from message ascnd.v1.SubmitScoreResponse\n */\nexport type SubmitScoreResponse = Message<\"ascnd.v1.SubmitScoreResponse\"> & {\n /**\n * The unique identifier of the submitted score.\n *\n * @generated from field: string score_id = 1;\n */\n scoreId: string;\n\n /**\n * The player's rank after this submission.\n *\n * @generated from field: int32 rank = 2;\n */\n rank: number;\n\n /**\n * Whether this is the player's new best score for this period.\n *\n * @generated from field: bool is_new_best = 3;\n */\n isNewBest: boolean;\n\n /**\n * Whether the score was deduplicated (already submitted recently).\n *\n * @generated from field: bool was_deduplicated = 4;\n */\n wasDeduplicated: boolean;\n\n /**\n * Anticheat validation result (if anticheat is enabled for this leaderboard).\n *\n * @generated from field: optional ascnd.v1.AnticheatResult anticheat = 5;\n */\n anticheat?: AnticheatResult;\n};\n\n/**\n * Describes the message ascnd.v1.SubmitScoreResponse.\n * Use `create(SubmitScoreResponseSchema)` to create a new message.\n */\nexport const SubmitScoreResponseSchema: GenMessage<SubmitScoreResponse> = /*@__PURE__*/\n messageDesc(file_ascnd, 1);\n\n/**\n * AnticheatResult contains the result of anticheat validation.\n *\n * @generated from message ascnd.v1.AnticheatResult\n */\nexport type AnticheatResult = Message<\"ascnd.v1.AnticheatResult\"> & {\n /**\n * Whether the score passed all anticheat checks.\n *\n * @generated from field: bool passed = 1;\n */\n passed: boolean;\n\n /**\n * List of violation flags that were triggered.\n *\n * @generated from field: repeated ascnd.v1.AnticheatViolation violations = 2;\n */\n violations: AnticheatViolation[];\n\n /**\n * The enforcement action taken: \"none\", \"flag\", \"shadow_ban\", or \"reject\".\n *\n * @generated from field: string action = 3;\n */\n action: string;\n};\n\n/**\n * Describes the message ascnd.v1.AnticheatResult.\n * Use `create(AnticheatResultSchema)` to create a new message.\n */\nexport const AnticheatResultSchema: GenMessage<AnticheatResult> = /*@__PURE__*/\n messageDesc(file_ascnd, 2);\n\n/**\n * AnticheatViolation describes a single anticheat rule violation.\n *\n * @generated from message ascnd.v1.AnticheatViolation\n */\nexport type AnticheatViolation = Message<\"ascnd.v1.AnticheatViolation\"> & {\n /**\n * The type of violation: \"bounds_exceeded\", \"velocity_exceeded\",\n * \"duplicate_idempotency\", \"missing_idempotency_key\".\n *\n * @generated from field: string flag_type = 1;\n */\n flagType: string;\n\n /**\n * Human-readable description of the violation.\n *\n * @generated from field: string reason = 2;\n */\n reason: string;\n};\n\n/**\n * Describes the message ascnd.v1.AnticheatViolation.\n * Use `create(AnticheatViolationSchema)` to create a new message.\n */\nexport const AnticheatViolationSchema: GenMessage<AnticheatViolation> = /*@__PURE__*/\n messageDesc(file_ascnd, 3);\n\n/**\n * GetLeaderboardRequest specifies which leaderboard and page to retrieve.\n *\n * @generated from message ascnd.v1.GetLeaderboardRequest\n */\nexport type GetLeaderboardRequest = Message<\"ascnd.v1.GetLeaderboardRequest\"> & {\n /**\n * The leaderboard to retrieve.\n *\n * @generated from field: string leaderboard_id = 1;\n */\n leaderboardId: string;\n\n /**\n * Maximum number of entries to return (default: 10, max: 100).\n *\n * @generated from field: optional int32 limit = 2;\n */\n limit?: number;\n\n /**\n * Number of entries to skip for pagination.\n *\n * @generated from field: optional int32 offset = 3;\n */\n offset?: number;\n\n /**\n * Which period to retrieve: \"current\", \"previous\", or a timestamp.\n *\n * @generated from field: optional string period = 4;\n */\n period?: string;\n\n /**\n * Optional view slug to filter by metadata criteria.\n * If provided, returns rankings within the view (not global rank).\n *\n * @generated from field: optional string view_slug = 5;\n */\n viewSlug?: string;\n};\n\n/**\n * Describes the message ascnd.v1.GetLeaderboardRequest.\n * Use `create(GetLeaderboardRequestSchema)` to create a new message.\n */\nexport const GetLeaderboardRequestSchema: GenMessage<GetLeaderboardRequest> = /*@__PURE__*/\n messageDesc(file_ascnd, 4);\n\n/**\n * GetLeaderboardResponse contains the leaderboard entries.\n *\n * @generated from message ascnd.v1.GetLeaderboardResponse\n */\nexport type GetLeaderboardResponse = Message<\"ascnd.v1.GetLeaderboardResponse\"> & {\n /**\n * The leaderboard entries.\n *\n * @generated from field: repeated ascnd.v1.LeaderboardEntry entries = 1;\n */\n entries: LeaderboardEntry[];\n\n /**\n * Approximate total number of entries.\n *\n * @generated from field: int32 total_entries = 2;\n */\n totalEntries: number;\n\n /**\n * Whether there are more entries after this page.\n *\n * @generated from field: bool has_more = 3;\n */\n hasMore: boolean;\n\n /**\n * The start of the current period (ISO 8601 timestamp).\n *\n * @generated from field: string period_start = 4;\n */\n periodStart: string;\n\n /**\n * The end of the current period, if applicable (ISO 8601 timestamp).\n *\n * @generated from field: optional string period_end = 5;\n */\n periodEnd?: string;\n\n /**\n * Active view info if filtering by view_slug.\n *\n * @generated from field: optional ascnd.v1.ViewInfo view = 6;\n */\n view?: ViewInfo;\n};\n\n/**\n * Describes the message ascnd.v1.GetLeaderboardResponse.\n * Use `create(GetLeaderboardResponseSchema)` to create a new message.\n */\nexport const GetLeaderboardResponseSchema: GenMessage<GetLeaderboardResponse> = /*@__PURE__*/\n messageDesc(file_ascnd, 5);\n\n/**\n * LeaderboardEntry represents a single entry on the leaderboard.\n *\n * @generated from message ascnd.v1.LeaderboardEntry\n */\nexport type LeaderboardEntry = Message<\"ascnd.v1.LeaderboardEntry\"> & {\n /**\n * The player's rank (1-indexed).\n *\n * @generated from field: int32 rank = 1;\n */\n rank: number;\n\n /**\n * The player's unique identifier.\n *\n * @generated from field: string player_id = 2;\n */\n playerId: string;\n\n /**\n * The player's score.\n *\n * @generated from field: int64 score = 3;\n */\n score: bigint;\n\n /**\n * When the score was submitted (ISO 8601 timestamp).\n *\n * @generated from field: string submitted_at = 4;\n */\n submittedAt: string;\n\n /**\n * Optional metadata associated with the score.\n *\n * @generated from field: optional bytes metadata = 5;\n */\n metadata?: Uint8Array;\n\n /**\n * Optional bracket assignment for this player.\n *\n * @generated from field: optional ascnd.v1.BracketInfo bracket = 6;\n */\n bracket?: BracketInfo;\n};\n\n/**\n * Describes the message ascnd.v1.LeaderboardEntry.\n * Use `create(LeaderboardEntrySchema)` to create a new message.\n */\nexport const LeaderboardEntrySchema: GenMessage<LeaderboardEntry> = /*@__PURE__*/\n messageDesc(file_ascnd, 6);\n\n/**\n * BracketInfo contains minimal bracket information.\n *\n * @generated from message ascnd.v1.BracketInfo\n */\nexport type BracketInfo = Message<\"ascnd.v1.BracketInfo\"> & {\n /**\n * The bracket's unique identifier.\n *\n * @generated from field: string id = 1;\n */\n id: string;\n\n /**\n * The bracket's name (e.g., \"Gold\", \"Silver\", \"Bronze\").\n *\n * @generated from field: string name = 2;\n */\n name: string;\n\n /**\n * Optional hex color code (e.g., \"#FF5500\") for the bracket badge.\n *\n * @generated from field: optional string color = 3;\n */\n color?: string;\n};\n\n/**\n * Describes the message ascnd.v1.BracketInfo.\n * Use `create(BracketInfoSchema)` to create a new message.\n */\nexport const BracketInfoSchema: GenMessage<BracketInfo> = /*@__PURE__*/\n messageDesc(file_ascnd, 7);\n\n/**\n * ViewInfo contains minimal metadata view information.\n *\n * @generated from message ascnd.v1.ViewInfo\n */\nexport type ViewInfo = Message<\"ascnd.v1.ViewInfo\"> & {\n /**\n * The view's slug identifier.\n *\n * @generated from field: string slug = 1;\n */\n slug: string;\n\n /**\n * The view's display name.\n *\n * @generated from field: string name = 2;\n */\n name: string;\n};\n\n/**\n * Describes the message ascnd.v1.ViewInfo.\n * Use `create(ViewInfoSchema)` to create a new message.\n */\nexport const ViewInfoSchema: GenMessage<ViewInfo> = /*@__PURE__*/\n messageDesc(file_ascnd, 8);\n\n/**\n * GetPlayerRankRequest specifies which player and leaderboard to query.\n *\n * @generated from message ascnd.v1.GetPlayerRankRequest\n */\nexport type GetPlayerRankRequest = Message<\"ascnd.v1.GetPlayerRankRequest\"> & {\n /**\n * The leaderboard to query.\n *\n * @generated from field: string leaderboard_id = 1;\n */\n leaderboardId: string;\n\n /**\n * The player's unique identifier.\n *\n * @generated from field: string player_id = 2;\n */\n playerId: string;\n\n /**\n * Which period to query: \"current\", \"previous\", or a timestamp.\n *\n * @generated from field: optional string period = 3;\n */\n period?: string;\n\n /**\n * Optional view slug to get rank within a filtered view.\n *\n * @generated from field: optional string view_slug = 4;\n */\n viewSlug?: string;\n};\n\n/**\n * Describes the message ascnd.v1.GetPlayerRankRequest.\n * Use `create(GetPlayerRankRequestSchema)` to create a new message.\n */\nexport const GetPlayerRankRequestSchema: GenMessage<GetPlayerRankRequest> = /*@__PURE__*/\n messageDesc(file_ascnd, 9);\n\n/**\n * GetPlayerRankResponse contains the player's rank information.\n *\n * @generated from message ascnd.v1.GetPlayerRankResponse\n */\nexport type GetPlayerRankResponse = Message<\"ascnd.v1.GetPlayerRankResponse\"> & {\n /**\n * The player's rank (null if not on leaderboard).\n * When querying a view, this is the rank within the view.\n *\n * @generated from field: optional int32 rank = 1;\n */\n rank?: number;\n\n /**\n * The player's current score (null if not on leaderboard).\n *\n * @generated from field: optional int64 score = 2;\n */\n score?: bigint;\n\n /**\n * The player's best score this period.\n *\n * @generated from field: optional int64 best_score = 3;\n */\n bestScore?: bigint;\n\n /**\n * Total number of entries on this leaderboard (or view if filtered).\n *\n * @generated from field: int32 total_entries = 4;\n */\n totalEntries: number;\n\n /**\n * The player's percentile (e.g., \"top 5%\").\n *\n * @generated from field: optional string percentile = 5;\n */\n percentile?: string;\n\n /**\n * Optional bracket assignment for this player.\n *\n * @generated from field: optional ascnd.v1.BracketInfo bracket = 6;\n */\n bracket?: BracketInfo;\n\n /**\n * Active view info if querying with view_slug.\n *\n * @generated from field: optional ascnd.v1.ViewInfo view = 7;\n */\n view?: ViewInfo;\n\n /**\n * Global rank when querying a view (shows overall position).\n *\n * @generated from field: optional int32 global_rank = 8;\n */\n globalRank?: number;\n};\n\n/**\n * Describes the message ascnd.v1.GetPlayerRankResponse.\n * Use `create(GetPlayerRankResponseSchema)` to create a new message.\n */\nexport const GetPlayerRankResponseSchema: GenMessage<GetPlayerRankResponse> = /*@__PURE__*/\n messageDesc(file_ascnd, 10);\n\n/**\n * AscndService provides leaderboard management for games.\n *\n * @generated from service ascnd.v1.AscndService\n */\nexport const AscndService: GenService<{\n /**\n * SubmitScore records a player's score on a leaderboard.\n *\n * @generated from rpc ascnd.v1.AscndService.SubmitScore\n */\n submitScore: {\n methodKind: \"unary\";\n input: typeof SubmitScoreRequestSchema;\n output: typeof SubmitScoreResponseSchema;\n },\n /**\n * GetLeaderboard retrieves the top scores for a leaderboard.\n *\n * @generated from rpc ascnd.v1.AscndService.GetLeaderboard\n */\n getLeaderboard: {\n methodKind: \"unary\";\n input: typeof GetLeaderboardRequestSchema;\n output: typeof GetLeaderboardResponseSchema;\n },\n /**\n * GetPlayerRank retrieves a specific player's rank and score.\n *\n * @generated from rpc ascnd.v1.AscndService.GetPlayerRank\n */\n getPlayerRank: {\n methodKind: \"unary\";\n input: typeof GetPlayerRankRequestSchema;\n output: typeof GetPlayerRankResponseSchema;\n },\n}> = /*@__PURE__*/\n serviceDesc(file_ascnd, 0);\n\n","/**\n * TypeScript types for the Ascnd gRPC API.\n *\n * Types are generated from the proto file and re-exported here.\n */\n\n// Re-export all generated types\nexport type {\n SubmitScoreRequest,\n SubmitScoreResponse,\n GetLeaderboardRequest,\n GetLeaderboardResponse,\n GetPlayerRankRequest,\n GetPlayerRankResponse,\n LeaderboardEntry,\n AnticheatResult,\n AnticheatViolation,\n BracketInfo,\n ViewInfo,\n} from \"./gen/ascnd_pb.js\";\n\n// Re-export the service definition\nexport { AscndService } from \"./gen/ascnd_pb.js\";\n\n// Re-export schemas for creating messages\nexport {\n SubmitScoreRequestSchema,\n SubmitScoreResponseSchema,\n GetLeaderboardRequestSchema,\n GetLeaderboardResponseSchema,\n GetPlayerRankRequestSchema,\n GetPlayerRankResponseSchema,\n LeaderboardEntrySchema,\n AnticheatResultSchema,\n AnticheatViolationSchema,\n BracketInfoSchema,\n ViewInfoSchema,\n} from \"./gen/ascnd_pb.js\";\n\n// ============================================================================\n// Client Configuration\n// ============================================================================\n\n/**\n * Configuration options for the Ascnd client.\n */\nexport interface AscndClientConfig {\n /** The base URL of the Ascnd API (e.g., \"https://api.ascnd.gg\"). */\n baseUrl: string;\n\n /** Your API key for authentication. */\n apiKey: string;\n\n /** Optional request timeout in milliseconds (default: 30000). */\n timeout?: number;\n}\n\n// ============================================================================\n// Error Types\n// ============================================================================\n\n/**\n * Custom error class for Ascnd API errors.\n */\nexport class AscndError extends Error {\n /** gRPC/Connect error code. */\n readonly code: string;\n\n /** Additional error details. */\n readonly details?: Record<string, unknown>;\n\n constructor(\n message: string,\n code: string,\n details?: Record<string, unknown>\n ) {\n super(message);\n this.name = \"AscndError\";\n this.code = code;\n this.details = details;\n }\n}\n"],"mappings":"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","names":[]}