@playcademy/sdk 0.2.2 → 0.2.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/dist/index.js CHANGED
@@ -241,6 +241,7 @@ var detectOutputFormat = () => {
241
241
  };
242
242
  var colors = {
243
243
  reset: "\x1B[0m",
244
+ bold: "\x1B[1m",
244
245
  dim: "\x1B[2m",
245
246
  red: "\x1B[31m",
246
247
  yellow: "\x1B[33m",
@@ -262,44 +263,48 @@ var getLevelColor = (level) => {
262
263
  return colors.reset;
263
264
  }
264
265
  };
265
- var formatBrowserOutput = (level, message, context) => {
266
+ var formatBrowserOutput = (level, message, context, scope) => {
266
267
  const timestamp = new Date().toISOString();
267
268
  const levelUpper = level.toUpperCase();
268
269
  const consoleMethod = getConsoleMethod(level);
270
+ const scopePrefix = scope ? `[${scope}] ` : "";
269
271
  if (context && Object.keys(context).length > 0) {
270
- consoleMethod(`[${timestamp}] ${levelUpper}`, message, context);
272
+ consoleMethod(`[${timestamp}] ${levelUpper}`, `${scopePrefix}${message}`, context);
271
273
  } else {
272
- consoleMethod(`[${timestamp}] ${levelUpper}`, message);
274
+ consoleMethod(`[${timestamp}] ${levelUpper}`, `${scopePrefix}${message}`);
273
275
  }
274
276
  };
275
- var formatColorTTY = (level, message, context) => {
277
+ var formatColorTTY = (level, message, context, scope) => {
276
278
  const timestamp = new Date().toISOString();
277
279
  const levelColor = getLevelColor(level);
278
280
  const levelUpper = level.toUpperCase().padEnd(5);
279
281
  const consoleMethod = getConsoleMethod(level);
280
282
  const coloredPrefix = `${colors.dim}[${timestamp}]${colors.reset} ${levelColor}${levelUpper}${colors.reset}`;
283
+ const scopePrefix = scope ? `${colors.bold}[${scope}]${colors.reset} ` : "";
281
284
  if (context && Object.keys(context).length > 0) {
282
- consoleMethod(`${coloredPrefix} ${message}`, context);
285
+ consoleMethod(`${coloredPrefix} ${scopePrefix}${message}`, context);
283
286
  } else {
284
- consoleMethod(`${coloredPrefix} ${message}`);
287
+ consoleMethod(`${coloredPrefix} ${scopePrefix}${message}`);
285
288
  }
286
289
  };
287
- var formatJSONSingleLine = (level, message, context) => {
290
+ var formatJSONSingleLine = (level, message, context, scope) => {
288
291
  const timestamp = new Date().toISOString();
289
292
  const logEntry = {
290
293
  timestamp,
291
294
  level: level.toUpperCase(),
295
+ ...scope && { scope },
292
296
  message,
293
297
  ...context && Object.keys(context).length > 0 && { context }
294
298
  };
295
299
  const consoleMethod = getConsoleMethod(level);
296
300
  consoleMethod(JSON.stringify(logEntry));
297
301
  };
298
- var formatJSONPretty = (level, message, context) => {
302
+ var formatJSONPretty = (level, message, context, scope) => {
299
303
  const timestamp = new Date().toISOString();
300
304
  const logEntry = {
301
305
  timestamp,
302
306
  level: level.toUpperCase(),
307
+ ...scope && { scope },
303
308
  message,
304
309
  ...context && Object.keys(context).length > 0 && { context }
305
310
  };
@@ -340,36 +345,37 @@ var shouldLog = (level) => {
340
345
  return levelPriority[level] >= levelPriority[minLevel];
341
346
  };
342
347
  var customHandler;
343
- var performLog = (level, message, context) => {
348
+ var performLog = (level, message, context, scope) => {
344
349
  if (!shouldLog(level))
345
350
  return;
346
351
  if (customHandler) {
347
- customHandler(level, message, context);
352
+ customHandler(level, message, context, scope);
348
353
  return;
349
354
  }
350
355
  const outputFormat = detectOutputFormat();
351
356
  switch (outputFormat) {
352
357
  case "browser":
353
- formatBrowserOutput(level, message, context);
358
+ formatBrowserOutput(level, message, context, scope);
354
359
  break;
355
360
  case "color-tty":
356
- formatColorTTY(level, message, context);
361
+ formatColorTTY(level, message, context, scope);
357
362
  break;
358
363
  case "json-single-line":
359
- formatJSONSingleLine(level, message, context);
364
+ formatJSONSingleLine(level, message, context, scope);
360
365
  break;
361
366
  case "json-pretty":
362
- formatJSONPretty(level, message, context);
367
+ formatJSONPretty(level, message, context, scope);
363
368
  break;
364
369
  }
365
370
  };
366
- var createLogger = () => {
371
+ var createLogger = (scopeName) => {
367
372
  return {
368
- debug: (message, context) => performLog("debug", message, context),
369
- info: (message, context) => performLog("info", message, context),
370
- warn: (message, context) => performLog("warn", message, context),
371
- error: (message, context) => performLog("error", message, context),
372
- log: performLog
373
+ debug: (message, context) => performLog("debug", message, context, scopeName),
374
+ info: (message, context) => performLog("info", message, context, scopeName),
375
+ warn: (message, context) => performLog("warn", message, context, scopeName),
376
+ error: (message, context) => performLog("error", message, context, scopeName),
377
+ log: (level, message, context) => performLog(level, message, context, scopeName),
378
+ scope: (name) => createLogger(scopeName ? `${scopeName}.${name}` : name)
373
379
  };
374
380
  };
375
381
  var log = createLogger();
@@ -384,26 +390,87 @@ class PlaycademyError extends Error {
384
390
 
385
391
  class ApiError extends Error {
386
392
  status;
393
+ code;
387
394
  details;
388
- constructor(status, message, details) {
389
- super(`${status} ${message}`);
395
+ rawBody;
396
+ constructor(status, code, message, details, rawBody) {
397
+ super(message);
390
398
  this.status = status;
399
+ this.name = "ApiError";
400
+ this.code = code;
391
401
  this.details = details;
402
+ this.rawBody = rawBody;
392
403
  Object.setPrototypeOf(this, ApiError.prototype);
393
404
  }
405
+ static fromResponse(status, statusText, body) {
406
+ if (body && typeof body === "object" && "error" in body) {
407
+ const errorBody = body;
408
+ const err = errorBody.error;
409
+ if (err && typeof err === "object") {
410
+ return new ApiError(status, err.code ?? statusCodeToErrorCode(status), err.message ?? statusText, err.details, body);
411
+ }
412
+ }
413
+ return new ApiError(status, statusCodeToErrorCode(status), statusText, undefined, body);
414
+ }
415
+ is(code) {
416
+ return this.code === code;
417
+ }
418
+ isClientError() {
419
+ return this.status >= 400 && this.status < 500;
420
+ }
421
+ isServerError() {
422
+ return this.status >= 500;
423
+ }
424
+ isRetryable() {
425
+ return this.isServerError() || this.code === "TOO_MANY_REQUESTS" || this.code === "RATE_LIMITED" || this.code === "TIMEOUT";
426
+ }
427
+ }
428
+ function statusCodeToErrorCode(status) {
429
+ switch (status) {
430
+ case 400:
431
+ return "BAD_REQUEST";
432
+ case 401:
433
+ return "UNAUTHORIZED";
434
+ case 403:
435
+ return "FORBIDDEN";
436
+ case 404:
437
+ return "NOT_FOUND";
438
+ case 405:
439
+ return "METHOD_NOT_ALLOWED";
440
+ case 409:
441
+ return "CONFLICT";
442
+ case 410:
443
+ return "GONE";
444
+ case 412:
445
+ return "PRECONDITION_FAILED";
446
+ case 413:
447
+ return "PAYLOAD_TOO_LARGE";
448
+ case 422:
449
+ return "VALIDATION_FAILED";
450
+ case 429:
451
+ return "TOO_MANY_REQUESTS";
452
+ case 500:
453
+ return "INTERNAL_ERROR";
454
+ case 501:
455
+ return "NOT_IMPLEMENTED";
456
+ case 503:
457
+ return "SERVICE_UNAVAILABLE";
458
+ case 504:
459
+ return "TIMEOUT";
460
+ default:
461
+ return status >= 500 ? "INTERNAL_ERROR" : "BAD_REQUEST";
462
+ }
394
463
  }
395
464
  function extractApiErrorInfo(error) {
396
465
  if (!(error instanceof ApiError)) {
397
466
  return null;
398
467
  }
399
- const info = {
468
+ return {
400
469
  status: error.status,
401
- statusText: error.message
470
+ code: error.code,
471
+ message: error.message,
472
+ ...error.details !== undefined && { details: error.details }
402
473
  };
403
- if (error.details) {
404
- info.details = error.details;
405
- }
406
- return info;
407
474
  }
408
475
 
409
476
  // src/core/static/login.ts
@@ -1030,6 +1097,96 @@ function createUsersNamespace(client) {
1030
1097
  }
1031
1098
  };
1032
1099
  }
1100
+ // ../constants/src/achievements.ts
1101
+ var ACHIEVEMENT_IDS = {
1102
+ PLAY_ANY_GAME_DAILY: "play_any_game_daily",
1103
+ DAILY_CHEST_OPEN: "daily_chest_open",
1104
+ LEADERBOARD_TOP3_DAILY: "leaderboard_top3_daily",
1105
+ LEADERBOARD_TOP3_WEEKLY: "leaderboard_top3_weekly",
1106
+ FIRST_SCORE_ANY_GAME: "first_score_any_game",
1107
+ PERSONAL_BEST_ANY_GAME: "personal_best_any_game"
1108
+ };
1109
+ var ACHIEVEMENT_DEFINITIONS = [
1110
+ {
1111
+ id: ACHIEVEMENT_IDS.PLAY_ANY_GAME_DAILY,
1112
+ title: "Play any game",
1113
+ description: "Play any arcade game for at least 60 seconds in a single session.",
1114
+ scope: "daily",
1115
+ rewardCredits: 10,
1116
+ limit: 1,
1117
+ completionType: "time_played_session",
1118
+ completionConfig: { minSeconds: 60 },
1119
+ target: { anyArcadeGame: true },
1120
+ active: true
1121
+ },
1122
+ {
1123
+ id: ACHIEVEMENT_IDS.DAILY_CHEST_OPEN,
1124
+ title: "Opened the daily chest",
1125
+ description: "Find the chest on the map and open it to claim your reward.",
1126
+ scope: "daily",
1127
+ rewardCredits: 10,
1128
+ limit: 1,
1129
+ completionType: "interaction",
1130
+ completionConfig: { triggerId: "bunny_chest" },
1131
+ target: { map: "arcade" },
1132
+ active: true
1133
+ },
1134
+ {
1135
+ id: ACHIEVEMENT_IDS.LEADERBOARD_TOP3_DAILY,
1136
+ title: "Daily Champion",
1137
+ description: "Finish in the top 3 of any game leaderboard for the day.",
1138
+ scope: "daily",
1139
+ rewardCredits: 25,
1140
+ limit: 1,
1141
+ completionType: "leaderboard_rank",
1142
+ completionConfig: {
1143
+ rankRewards: [50, 30, 15]
1144
+ },
1145
+ target: { anyArcadeGame: true },
1146
+ active: true
1147
+ },
1148
+ {
1149
+ id: ACHIEVEMENT_IDS.LEADERBOARD_TOP3_WEEKLY,
1150
+ title: "Weekly Legend",
1151
+ description: "Finish in the top 3 of any game leaderboard for the week.",
1152
+ scope: "weekly",
1153
+ rewardCredits: 50,
1154
+ limit: 1,
1155
+ completionType: "leaderboard_rank",
1156
+ completionConfig: {
1157
+ rankRewards: [100, 60, 30]
1158
+ },
1159
+ target: { anyArcadeGame: true },
1160
+ active: true
1161
+ },
1162
+ {
1163
+ id: ACHIEVEMENT_IDS.FIRST_SCORE_ANY_GAME,
1164
+ title: "First Score",
1165
+ description: "Submit your first score in any arcade game.",
1166
+ scope: "game",
1167
+ rewardCredits: 5,
1168
+ limit: 1,
1169
+ completionType: "first_score",
1170
+ completionConfig: {},
1171
+ target: { anyArcadeGame: true },
1172
+ active: true
1173
+ },
1174
+ {
1175
+ id: ACHIEVEMENT_IDS.PERSONAL_BEST_ANY_GAME,
1176
+ title: "Personal Best",
1177
+ description: "Beat your personal best score in any arcade game.",
1178
+ scope: "daily",
1179
+ rewardCredits: 5,
1180
+ limit: 3,
1181
+ completionType: "personal_best",
1182
+ completionConfig: {},
1183
+ target: { anyArcadeGame: true },
1184
+ active: true
1185
+ }
1186
+ ];
1187
+ // ../constants/src/typescript.ts
1188
+ var TSC_PACKAGE = "@typescript/native-preview";
1189
+ var USE_NATIVE_TSC = TSC_PACKAGE.includes("native-preview");
1033
1190
  // ../constants/src/overworld.ts
1034
1191
  var ITEM_SLUGS = {
1035
1192
  PLAYCADEMY_CREDITS: "PLAYCADEMY_CREDITS",
@@ -1702,7 +1859,7 @@ async function request({
1702
1859
  const errorBody = await clonedRes.json().catch(() => clonedRes.text().catch(() => {
1703
1860
  return;
1704
1861
  })) ?? undefined;
1705
- throw new ApiError(res.status, res.statusText, errorBody);
1862
+ throw ApiError.fromResponse(res.status, res.statusText, errorBody);
1706
1863
  }
1707
1864
  if (res.status === 204)
1708
1865
  return;