krawlet-js 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/dist/index.js ADDED
@@ -0,0 +1,579 @@
1
+ // src/error.ts
2
+ var KrawletError = class _KrawletError extends Error {
3
+ constructor(message, code, statusCode, requestId, details, response) {
4
+ super(message);
5
+ this.name = "KrawletError";
6
+ this.code = code;
7
+ this.statusCode = statusCode;
8
+ this.requestId = requestId;
9
+ this.details = details;
10
+ this.response = response;
11
+ if (Error.captureStackTrace) {
12
+ Error.captureStackTrace(this, _KrawletError);
13
+ }
14
+ }
15
+ /**
16
+ * Check if this is a client error (4xx)
17
+ */
18
+ isClientError() {
19
+ return this.statusCode >= 400 && this.statusCode < 500;
20
+ }
21
+ /**
22
+ * Check if this is a server error (5xx)
23
+ */
24
+ isServerError() {
25
+ return this.statusCode >= 500;
26
+ }
27
+ /**
28
+ * Check if this is a rate limit error
29
+ */
30
+ isRateLimitError() {
31
+ return this.statusCode === 429 || this.code === "RATE_LIMIT_EXCEEDED";
32
+ }
33
+ };
34
+
35
+ // src/http-client.ts
36
+ var HttpClient = class {
37
+ constructor(config) {
38
+ this.config = {
39
+ baseUrl: config.baseUrl,
40
+ apiKey: config.apiKey || "",
41
+ timeout: config.timeout || 3e4,
42
+ headers: config.headers || {},
43
+ enableRetry: config.enableRetry !== false,
44
+ maxRetries: config.maxRetries || 3,
45
+ retryDelay: config.retryDelay || 1e3
46
+ };
47
+ }
48
+ /**
49
+ * Get the last known rate limit information
50
+ */
51
+ getRateLimit() {
52
+ return this.lastRateLimit;
53
+ }
54
+ /**
55
+ * Make an HTTP request
56
+ */
57
+ async request(path, options = {}) {
58
+ const url = this.buildUrl(path, options.params);
59
+ const headers = this.buildHeaders(options.headers, options.apiKey);
60
+ let lastError;
61
+ let attempt = 0;
62
+ const maxAttempts = this.config.enableRetry ? this.config.maxRetries + 1 : 1;
63
+ while (attempt < maxAttempts) {
64
+ try {
65
+ const controller = new AbortController();
66
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
67
+ const response = await fetch(url, {
68
+ method: options.method || "GET",
69
+ headers,
70
+ body: options.body ? JSON.stringify(options.body) : void 0,
71
+ signal: controller.signal
72
+ });
73
+ clearTimeout(timeoutId);
74
+ this.extractRateLimit(response);
75
+ if (!response.ok) {
76
+ await this.handleErrorResponse(response);
77
+ }
78
+ const data = await response.json();
79
+ return data;
80
+ } catch (error) {
81
+ lastError = error;
82
+ if (error instanceof KrawletError && error.isClientError() && !error.isRateLimitError()) {
83
+ throw error;
84
+ }
85
+ if (attempt < maxAttempts - 1 && this.shouldRetry(error)) {
86
+ const delay = this.calculateRetryDelay(attempt);
87
+ await this.sleep(delay);
88
+ attempt++;
89
+ continue;
90
+ }
91
+ throw error;
92
+ }
93
+ }
94
+ throw lastError || new Error("Request failed after retries");
95
+ }
96
+ /**
97
+ * Build the full URL with query parameters
98
+ */
99
+ buildUrl(path, params) {
100
+ const url = new URL(path, this.config.baseUrl);
101
+ if (params) {
102
+ Object.entries(params).forEach(([key, value]) => {
103
+ if (value !== void 0) {
104
+ url.searchParams.append(key, String(value));
105
+ }
106
+ });
107
+ }
108
+ return url.toString();
109
+ }
110
+ /**
111
+ * Build request headers
112
+ */
113
+ buildHeaders(customHeaders, apiKey) {
114
+ const headers = {
115
+ "Content-Type": "application/json",
116
+ ...this.config.headers,
117
+ ...customHeaders
118
+ };
119
+ const key = apiKey || this.config.apiKey;
120
+ if (key) {
121
+ headers["Authorization"] = `Bearer ${key}`;
122
+ }
123
+ return headers;
124
+ }
125
+ /**
126
+ * Extract rate limit information from response headers
127
+ */
128
+ extractRateLimit(response) {
129
+ const limit = response.headers.get("X-RateLimit-Limit");
130
+ const remaining = response.headers.get("X-RateLimit-Remaining");
131
+ const reset = response.headers.get("X-RateLimit-Reset");
132
+ if (limit && remaining && reset) {
133
+ this.lastRateLimit = {
134
+ limit: parseInt(limit, 10),
135
+ remaining: parseInt(remaining, 10),
136
+ reset: parseInt(reset, 10)
137
+ };
138
+ }
139
+ }
140
+ /**
141
+ * Handle error responses
142
+ */
143
+ async handleErrorResponse(response) {
144
+ let errorData;
145
+ try {
146
+ errorData = await response.json();
147
+ } catch {
148
+ throw new KrawletError(
149
+ `HTTP ${response.status}: ${response.statusText}`,
150
+ "UNKNOWN_ERROR",
151
+ response.status
152
+ );
153
+ }
154
+ throw new KrawletError(
155
+ errorData.error.message,
156
+ errorData.error.code,
157
+ response.status,
158
+ errorData.meta.requestId,
159
+ errorData.error.details,
160
+ errorData
161
+ );
162
+ }
163
+ /**
164
+ * Determine if a request should be retried
165
+ */
166
+ shouldRetry(error) {
167
+ if (error instanceof KrawletError) {
168
+ return error.isServerError() || error.isRateLimitError();
169
+ }
170
+ if (error instanceof Error) {
171
+ return error.name === "AbortError" || error.message.includes("fetch");
172
+ }
173
+ return false;
174
+ }
175
+ /**
176
+ * Calculate retry delay with exponential backoff
177
+ */
178
+ calculateRetryDelay(attempt) {
179
+ return this.config.retryDelay * Math.pow(2, attempt);
180
+ }
181
+ /**
182
+ * Sleep for a given duration
183
+ */
184
+ sleep(ms) {
185
+ return new Promise((resolve) => setTimeout(resolve, ms));
186
+ }
187
+ };
188
+
189
+ // src/resources/health.ts
190
+ var HealthResource = class {
191
+ constructor(client) {
192
+ this.client = client;
193
+ }
194
+ /**
195
+ * Basic health check to verify API is operational
196
+ * @returns Health status information
197
+ */
198
+ async check() {
199
+ const response = await this.client.request("/v1/health");
200
+ return response.data;
201
+ }
202
+ /**
203
+ * Detailed health check with system information
204
+ * @returns Detailed health status with system metrics
205
+ */
206
+ async detailed() {
207
+ const response = await this.client.request("/v1/health/detailed");
208
+ return response.data;
209
+ }
210
+ };
211
+
212
+ // src/resources/players.ts
213
+ var PlayersResource = class {
214
+ constructor(client) {
215
+ this.client = client;
216
+ }
217
+ /**
218
+ * Retrieve all players
219
+ * @returns Array of all players
220
+ */
221
+ async getAll() {
222
+ const response = await this.client.request("/v1/players");
223
+ return response.data;
224
+ }
225
+ /**
226
+ * Retrieve players by Kromer addresses
227
+ * @param addresses - Array of Kromer addresses
228
+ * @returns Array of matching players
229
+ */
230
+ async getByAddresses(addresses) {
231
+ const response = await this.client.request("/v1/players", {
232
+ params: {
233
+ addresses: addresses.join(",")
234
+ }
235
+ });
236
+ return response.data;
237
+ }
238
+ /**
239
+ * Retrieve players by Minecraft names
240
+ * @param names - Array of player names (case-insensitive)
241
+ * @returns Array of matching players
242
+ */
243
+ async getByNames(names) {
244
+ const response = await this.client.request("/v1/players", {
245
+ params: {
246
+ names: names.join(",")
247
+ }
248
+ });
249
+ return response.data;
250
+ }
251
+ /**
252
+ * Retrieve players by Minecraft UUIDs
253
+ * @param uuids - Array of player UUIDs
254
+ * @returns Array of matching players
255
+ */
256
+ async getByUuids(uuids) {
257
+ const response = await this.client.request("/v1/players", {
258
+ params: {
259
+ uuids: uuids.join(",")
260
+ }
261
+ });
262
+ return response.data;
263
+ }
264
+ };
265
+
266
+ // src/resources/shops.ts
267
+ var ShopsResource = class {
268
+ constructor(client) {
269
+ this.client = client;
270
+ }
271
+ /**
272
+ * Retrieve all shops with their items and addresses
273
+ * @returns Array of all shops
274
+ */
275
+ async getAll() {
276
+ const response = await this.client.request("/v1/shops");
277
+ return response.data;
278
+ }
279
+ /**
280
+ * Retrieve a specific shop by ID
281
+ * @param id - Shop ID (computer ID)
282
+ * @returns Shop data
283
+ * @throws KrawletError with SHOP_NOT_FOUND if shop doesn't exist
284
+ */
285
+ async get(id) {
286
+ const response = await this.client.request(`/v1/shops/${id}`);
287
+ return response.data;
288
+ }
289
+ /**
290
+ * Retrieve all items for a specific shop
291
+ * @param id - Shop ID
292
+ * @returns Array of items in the shop
293
+ */
294
+ async getItems(id) {
295
+ const response = await this.client.request(`/v1/shops/${id}/items`);
296
+ return response.data;
297
+ }
298
+ /**
299
+ * Create or update a shop (requires ShopSync authentication)
300
+ * @param data - Shop sync data
301
+ * @param token - ShopSync API token
302
+ * @returns Success message
303
+ * @throws KrawletError with VALIDATION_ERROR if data is invalid
304
+ * @throws KrawletError with UNAUTHORIZED if not authenticated
305
+ */
306
+ async update(data, token) {
307
+ const response = await this.client.request("/v1/shops", {
308
+ method: "POST",
309
+ body: data,
310
+ apiKey: token
311
+ });
312
+ return response.data;
313
+ }
314
+ };
315
+
316
+ // src/resources/items.ts
317
+ var ItemsResource = class {
318
+ constructor(client) {
319
+ this.client = client;
320
+ }
321
+ /**
322
+ * Retrieve all item listings with prices across all shops
323
+ * @returns Array of all items
324
+ */
325
+ async getAll() {
326
+ const response = await this.client.request("/v1/items");
327
+ return response.data;
328
+ }
329
+ };
330
+
331
+ // src/resources/addresses.ts
332
+ var AddressesResource = class {
333
+ constructor(client) {
334
+ this.client = client;
335
+ }
336
+ /**
337
+ * Retrieve all known Kromer addresses
338
+ * @returns Array of all known addresses
339
+ */
340
+ async getAll() {
341
+ const response = await this.client.request("/v1/addresses");
342
+ return response.data;
343
+ }
344
+ };
345
+
346
+ // src/resources/storage.ts
347
+ var StorageResource = class {
348
+ constructor(client) {
349
+ this.client = client;
350
+ }
351
+ /**
352
+ * Retrieve the last stored ender storage data
353
+ * @returns Storage data and retrieval timestamp
354
+ * @throws KrawletError with NOT_FOUND if no data has been stored yet
355
+ */
356
+ async get() {
357
+ const response = await this.client.request("/v1/storage");
358
+ return response.data;
359
+ }
360
+ /**
361
+ * Store ender storage data (requires authentication)
362
+ * @param data - Any JSON object to store
363
+ * @param token - Ender Storage API token
364
+ * @returns Success message and timestamp
365
+ * @throws KrawletError with BAD_REQUEST if data is invalid
366
+ * @throws KrawletError with UNAUTHORIZED if not authenticated
367
+ */
368
+ async set(data, token) {
369
+ const response = await this.client.request(
370
+ "/v1/storage",
371
+ {
372
+ method: "POST",
373
+ body: data,
374
+ apiKey: token
375
+ }
376
+ );
377
+ return response.data;
378
+ }
379
+ };
380
+
381
+ // src/resources/reports.ts
382
+ var ReportsResource = class {
383
+ constructor(client) {
384
+ this.client = client;
385
+ }
386
+ /**
387
+ * Retrieve overall statistics for ShopSync reports
388
+ * @returns Statistics object
389
+ */
390
+ async getStats() {
391
+ const response = await this.client.request("/v1/reports/stats");
392
+ return response.data;
393
+ }
394
+ /**
395
+ * Retrieve recent validation failures
396
+ * @param options - Query options
397
+ * @param options.limit - Maximum records to return (default: 50)
398
+ * @returns Validation failure records
399
+ */
400
+ async getValidationFailures(options) {
401
+ const response = await this.client.request("/v1/reports/validation-failures", {
402
+ params: {
403
+ limit: options?.limit || 50
404
+ }
405
+ });
406
+ return response.data;
407
+ }
408
+ /**
409
+ * Retrieve recent successful ShopSync posts
410
+ * @param options - Query options
411
+ * @param options.limit - Maximum records to return (default: 50)
412
+ * @returns Successful post records
413
+ */
414
+ async getSuccessfulPosts(options) {
415
+ const response = await this.client.request("/v1/reports/successful-posts", {
416
+ params: {
417
+ limit: options?.limit || 50
418
+ }
419
+ });
420
+ return response.data;
421
+ }
422
+ /**
423
+ * Retrieve recent shop change events
424
+ * @param options - Query options
425
+ * @param options.limit - Maximum records to return (default: 50)
426
+ * @param options.shopId - Filter by shop ID
427
+ * @returns Shop change event records
428
+ */
429
+ async getShopChanges(options) {
430
+ const response = await this.client.request("/v1/reports/shop-changes", {
431
+ params: {
432
+ limit: options?.limit || 50,
433
+ shopId: options?.shopId
434
+ }
435
+ });
436
+ return response.data;
437
+ }
438
+ /**
439
+ * Retrieve recent item change events
440
+ * @param options - Query options
441
+ * @param options.limit - Maximum records to return (default: 50)
442
+ * @param options.shopId - Filter by shop ID
443
+ * @returns Item change event records
444
+ */
445
+ async getItemChanges(options) {
446
+ const response = await this.client.request("/v1/reports/item-changes", {
447
+ params: {
448
+ limit: options?.limit || 50,
449
+ shopId: options?.shopId
450
+ }
451
+ });
452
+ return response.data;
453
+ }
454
+ /**
455
+ * Retrieve shop change logs from the database
456
+ * @param options - Query options including pagination and filtering
457
+ * @returns Shop change logs
458
+ */
459
+ async getShopChangeLogs(options) {
460
+ const response = await this.client.request("/v1/reports/shop-change-logs", {
461
+ params: {
462
+ limit: options?.limit,
463
+ offset: options?.offset,
464
+ shopId: options?.shopId,
465
+ since: options?.since,
466
+ until: options?.until
467
+ }
468
+ });
469
+ return response.data;
470
+ }
471
+ /**
472
+ * Retrieve item change logs from the database
473
+ * @param options - Query options including pagination and filtering
474
+ * @returns Item change logs
475
+ */
476
+ async getItemChangeLogs(options) {
477
+ const response = await this.client.request("/v1/reports/item-change-logs", {
478
+ params: {
479
+ limit: options?.limit,
480
+ offset: options?.offset,
481
+ shopId: options?.shopId,
482
+ since: options?.since,
483
+ until: options?.until
484
+ }
485
+ });
486
+ return response.data;
487
+ }
488
+ /**
489
+ * Retrieve price change logs from the database
490
+ * @param options - Query options including pagination and filtering
491
+ * @returns Price change logs
492
+ */
493
+ async getPriceChangeLogs(options) {
494
+ const response = await this.client.request("/v1/reports/price-change-logs", {
495
+ params: {
496
+ limit: options?.limit,
497
+ offset: options?.offset,
498
+ shopId: options?.shopId,
499
+ since: options?.since,
500
+ until: options?.until
501
+ }
502
+ });
503
+ return response.data;
504
+ }
505
+ /**
506
+ * Retrieve a specific report record by ID
507
+ * @param id - Report record ID
508
+ * @returns Report record
509
+ * @throws KrawletError with NOT_FOUND if record doesn't exist
510
+ */
511
+ async get(id) {
512
+ const response = await this.client.request(`/v1/reports/${id}`);
513
+ return response.data;
514
+ }
515
+ };
516
+
517
+ // src/client.ts
518
+ var KrawletClient = class {
519
+ /**
520
+ * Create a new Krawlet API client
521
+ * @param config - Client configuration options
522
+ */
523
+ constructor(config = {}) {
524
+ const httpConfig = {
525
+ baseUrl: config.baseUrl || "https://api.krawlet.cc",
526
+ apiKey: config.apiKey,
527
+ timeout: config.timeout,
528
+ headers: config.headers,
529
+ enableRetry: config.enableRetry,
530
+ maxRetries: config.maxRetries,
531
+ retryDelay: config.retryDelay
532
+ };
533
+ this.httpClient = new HttpClient(httpConfig);
534
+ this.health = new HealthResource(this.httpClient);
535
+ this.players = new PlayersResource(this.httpClient);
536
+ this.shops = new ShopsResource(this.httpClient);
537
+ this.items = new ItemsResource(this.httpClient);
538
+ this.addresses = new AddressesResource(this.httpClient);
539
+ this.storage = new StorageResource(this.httpClient);
540
+ this.reports = new ReportsResource(this.httpClient);
541
+ }
542
+ /**
543
+ * Get the last known rate limit information
544
+ * @returns Rate limit data or undefined if no requests have been made
545
+ */
546
+ getRateLimit() {
547
+ return this.httpClient.getRateLimit();
548
+ }
549
+ };
550
+
551
+ // src/types.ts
552
+ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
553
+ ErrorCode2["BAD_REQUEST"] = "BAD_REQUEST";
554
+ ErrorCode2["UNAUTHORIZED"] = "UNAUTHORIZED";
555
+ ErrorCode2["FORBIDDEN"] = "FORBIDDEN";
556
+ ErrorCode2["NOT_FOUND"] = "NOT_FOUND";
557
+ ErrorCode2["VALIDATION_ERROR"] = "VALIDATION_ERROR";
558
+ ErrorCode2["RATE_LIMIT_EXCEEDED"] = "RATE_LIMIT_EXCEEDED";
559
+ ErrorCode2["PLAYER_NOT_FOUND"] = "PLAYER_NOT_FOUND";
560
+ ErrorCode2["SHOP_NOT_FOUND"] = "SHOP_NOT_FOUND";
561
+ ErrorCode2["TURTLE_NOT_FOUND"] = "TURTLE_NOT_FOUND";
562
+ ErrorCode2["INVALID_API_KEY"] = "INVALID_API_KEY";
563
+ ErrorCode2["INTERNAL_ERROR"] = "INTERNAL_ERROR";
564
+ ErrorCode2["DATABASE_ERROR"] = "DATABASE_ERROR";
565
+ ErrorCode2["HEALTH_CHECK_FAILED"] = "HEALTH_CHECK_FAILED";
566
+ return ErrorCode2;
567
+ })(ErrorCode || {});
568
+ export {
569
+ AddressesResource,
570
+ ErrorCode,
571
+ HealthResource,
572
+ ItemsResource,
573
+ KrawletClient,
574
+ KrawletError,
575
+ PlayersResource,
576
+ ReportsResource,
577
+ ShopsResource,
578
+ StorageResource
579
+ };
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "krawlet-js",
3
+ "version": "1.0.0",
4
+ "description": "TypeScript/JavaScript client library for the Krawlet Minecraft economy tracking API",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.mjs",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "require": "./dist/index.js",
13
+ "import": "./dist/index.mjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
21
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
22
+ "test": "vitest",
23
+ "test:coverage": "vverage",
24
+ "lint": "eslint src --ext .ts",
25
+ "format": "prettier --write \"src/**/*.ts\"",
26
+ "prepublishOnly": "pnpm run build"
27
+ },
28
+ "keywords": [
29
+ "krawlet",
30
+ "minecraft",
31
+ "economy",
32
+ "api",
33
+ "client",
34
+ "typescript"
35
+ ],
36
+ "author": "",
37
+ "license": "ISC",
38
+ "packageManager": "pnpm@10.28.1",
39
+ "engines": {
40
+ "node": ">=18.0.0"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^22.10.5",
44
+ "@typescript-eslint/eslint-plugin": "^8.20.0",
45
+ "@typescript-eslint/parser": "^8.20.0",
46
+ "@vitest/coverage-v8": "^2.1.8",
47
+ "eslint": "^9.18.0",
48
+ "prettier": "^3.4.2",
49
+ "tsup": "^8.3.5",
50
+ "typescript": "^5.7.2",
51
+ "vitest": "^2.1.8"
52
+ }
53
+ }