@playcademy/sdk 0.11.0 → 0.11.1-beta.2

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.d.ts CHANGED
@@ -1028,9 +1028,15 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
1028
1028
  pauseActivity: () => void;
1029
1029
  resumeActivity: () => void;
1030
1030
  endActivity: (data: _playcademy_types.EndActivityScoreData) => Promise<_playcademy_types.EndActivityResponse>;
1031
- advanceCourse: (options?: {
1032
- subject?: _playcademy_types.TimebackSubject;
1033
- }) => Promise<_playcademy_types.AdvanceCourseResponse>;
1031
+ course: {
1032
+ advance: (options?: {
1033
+ subject?: _playcademy_types.TimebackSubject;
1034
+ }) => Promise<_playcademy_types.AdvanceCourseResponse>;
1035
+ unenroll: (options?: {
1036
+ subject?: _playcademy_types.TimebackSubject;
1037
+ force?: boolean;
1038
+ }) => Promise<_playcademy_types.UnenrollCourseResponse>;
1039
+ };
1034
1040
  };
1035
1041
  /**
1036
1042
  * Game score submission and leaderboards.
@@ -1256,6 +1262,12 @@ interface TimebackUser extends TimebackUserContext {
1256
1262
  * Call `mastery.fetch()` to get mastery progress from the server.
1257
1263
  */
1258
1264
  mastery: TimebackUserMastery;
1265
+ /**
1266
+ * Highest-grade-mastered data for the current user.
1267
+ * Call `highestGradeMastered.fetch({ subject })` to get the student's
1268
+ * highest mastered grade for a subject.
1269
+ */
1270
+ highestGradeMastered: TimebackUserHighestGradeMastered;
1259
1271
  }
1260
1272
  /**
1261
1273
  * Mastery data access for the current user.
@@ -1296,6 +1308,17 @@ interface TimebackUserMastery {
1296
1308
  */
1297
1309
  fetch(options?: GetMasteryOptions): Promise<MasteryResponse>;
1298
1310
  }
1311
+ /**
1312
+ * Highest-grade-mastered data access for the current user.
1313
+ * Results are cached for 5 seconds to avoid redundant network requests.
1314
+ */
1315
+ interface TimebackUserHighestGradeMastered {
1316
+ /**
1317
+ * Fetch the highest grade the current student has mastered for a subject.
1318
+ * Results are cached for 5 seconds (use `force: true` to bypass).
1319
+ */
1320
+ fetch(options: GetHighestGradeMasteredOptions): Promise<HighestGradeMasteredResponse>;
1321
+ }
1299
1322
  /**
1300
1323
  * Options for querying student XP.
1301
1324
  */
@@ -1322,6 +1345,15 @@ interface GetMasteryOptions {
1322
1345
  /** Bypass cache and fetch fresh data (default: false) */
1323
1346
  force?: boolean;
1324
1347
  }
1348
+ /**
1349
+ * Options for querying highest grade mastered.
1350
+ */
1351
+ interface GetHighestGradeMasteredOptions {
1352
+ /** Subject to query */
1353
+ subject: TimebackSubject;
1354
+ /** Bypass cache and fetch fresh data (default: false) */
1355
+ force?: boolean;
1356
+ }
1325
1357
  /**
1326
1358
  * XP data for a single course.
1327
1359
  */
@@ -1360,6 +1392,13 @@ interface MasteryResponse {
1360
1392
  totalMasterableUnits: number;
1361
1393
  courses?: CourseMastery[];
1362
1394
  }
1395
+ /**
1396
+ * Response from highest-grade-mastered query.
1397
+ */
1398
+ interface HighestGradeMasteredResponse {
1399
+ subject: TimebackSubject;
1400
+ highestGradeMastered: TimebackGrade | null;
1401
+ }
1363
1402
 
1364
1403
  /**
1365
1404
  * Core client configuration and lifecycle types
package/dist/index.js CHANGED
@@ -1146,8 +1146,10 @@ var TIMEBACK_ROUTES = {
1146
1146
  END_ACTIVITY: "/integrations/timeback/end-activity",
1147
1147
  GET_XP: "/integrations/timeback/xp",
1148
1148
  GET_MASTERY: "/integrations/timeback/mastery",
1149
+ GET_HIGHEST_GRADE_MASTERED: "/integrations/timeback/highest-grade-mastered",
1149
1150
  HEARTBEAT: "/integrations/timeback/heartbeat",
1150
- ADVANCE_COURSE: "/integrations/timeback/advance-course"
1151
+ ADVANCE_COURSE: "/integrations/timeback/advance-course",
1152
+ UNENROLL_COURSE: "/integrations/timeback/unenroll-course"
1151
1153
  };
1152
1154
  var TIMEBACK_GAME_METRIC_DECIMAL_PLACES = {
1153
1155
  xp: 1,
@@ -1800,20 +1802,29 @@ function createTimebackEngine(client) {
1800
1802
  ttl: 5000,
1801
1803
  keyPrefix: "game.timeback.mastery"
1802
1804
  });
1805
+ const highestGradeMasteredCache = createTTLCache({
1806
+ ttl: 5000,
1807
+ keyPrefix: "game.timeback.highestGradeMastered"
1808
+ });
1803
1809
  const enrollmentsCache = createTTLCache({
1804
1810
  ttl: 5 * 60 * 1000,
1805
1811
  keyPrefix: "game.timeback.enrollments"
1806
1812
  });
1807
- async function applyPromotion(promotion) {
1808
- if (promotion.status !== "promoted" && promotion.status !== "already-promoted") {
1809
- return;
1810
- }
1813
+ async function refreshAfterCourseMutation() {
1811
1814
  userCache.clear("current");
1812
1815
  enrollmentsCache.clear("current");
1816
+ xpCache.clear();
1817
+ highestGradeMasteredCache.clear();
1813
1818
  try {
1814
1819
  await userStore.refresh();
1815
1820
  } catch {}
1816
1821
  }
1822
+ async function applyPromotion(promotion) {
1823
+ if (promotion.status !== "promoted" && promotion.status !== "already-promoted") {
1824
+ return;
1825
+ }
1826
+ await refreshAfterCourseMutation();
1827
+ }
1817
1828
  const activityTracker = createTimebackActivityTracker(client);
1818
1829
  async function refreshUserContext() {
1819
1830
  const context = await userStore.refresh();
@@ -1891,6 +1902,14 @@ function createTimebackEngine(client) {
1891
1902
  return client["requestGameBackend"](endpoint, "GET");
1892
1903
  }, { force: options.force });
1893
1904
  }
1905
+ },
1906
+ highestGradeMastered: {
1907
+ fetch: (options) => highestGradeMasteredCache.get(options.subject, async () => {
1908
+ const params = new URLSearchParams;
1909
+ params.set("subject", options.subject);
1910
+ const endpoint = `${TIMEBACK_ROUTES.GET_HIGHEST_GRADE_MASTERED}?${params.toString()}`;
1911
+ return client["requestGameBackend"](endpoint, "GET");
1912
+ }, { force: options.force })
1894
1913
  }
1895
1914
  },
1896
1915
  activity: {
@@ -1900,10 +1919,22 @@ function createTimebackEngine(client) {
1900
1919
  resume: activityTracker.resumeActivity,
1901
1920
  end: activityTracker.endActivity
1902
1921
  },
1903
- async advanceCourse(params) {
1904
- const response = await client["requestGameBackend"](TIMEBACK_ROUTES.ADVANCE_COURSE, "POST", params?.subject !== undefined ? { subject: params.subject } : {});
1905
- await applyPromotion(response.promotion);
1906
- return response;
1922
+ course: {
1923
+ async advance(params) {
1924
+ const response = await client["requestGameBackend"](TIMEBACK_ROUTES.ADVANCE_COURSE, "POST", params?.subject !== undefined ? { subject: params.subject } : {});
1925
+ await applyPromotion(response.promotion);
1926
+ return response;
1927
+ },
1928
+ async unenroll(params) {
1929
+ const response = await client["requestGameBackend"](TIMEBACK_ROUTES.UNENROLL_COURSE, "POST", {
1930
+ ...params?.subject !== undefined ? { subject: params.subject } : {},
1931
+ ...params?.force !== undefined ? { force: params.force } : {}
1932
+ });
1933
+ if (response.unenrollment.status === "unenrolled") {
1934
+ await refreshAfterCourseMutation();
1935
+ }
1936
+ return response;
1937
+ }
1907
1938
  }
1908
1939
  };
1909
1940
  }
@@ -1976,6 +2007,17 @@ function createTimebackNamespace(client) {
1976
2007
  }
1977
2008
  return engine.user.mastery.fetch(options);
1978
2009
  }
2010
+ },
2011
+ highestGradeMastered: {
2012
+ fetch: async (options) => {
2013
+ if (!options?.subject) {
2014
+ throw new Error("subject is required");
2015
+ }
2016
+ if (!isValidSubject(options.subject)) {
2017
+ throw new Error(`Invalid subject: ${options.subject}. Valid subjects: ${VALID_SUBJECTS.join(", ")}`);
2018
+ }
2019
+ return engine.user.highestGradeMastered.fetch(options);
2020
+ }
1979
2021
  }
1980
2022
  };
1981
2023
  },
@@ -1999,12 +2041,24 @@ function createTimebackNamespace(client) {
1999
2041
  assertPlatformMode(client, "timeback.endActivity()");
2000
2042
  return engine.activity.end(data);
2001
2043
  },
2002
- advanceCourse: async (options) => {
2003
- assertPlatformMode(client, "timeback.advanceCourse()");
2004
- if (options?.subject !== undefined && !isValidSubject(options.subject)) {
2005
- throw new Error(`Invalid subject: ${options.subject}. Valid subjects: ${VALID_SUBJECTS.join(", ")}`);
2044
+ course: {
2045
+ advance: async (options) => {
2046
+ assertPlatformMode(client, "timeback.course.advance()");
2047
+ if (options?.subject !== undefined && !isValidSubject(options.subject)) {
2048
+ throw new Error(`Invalid subject: ${options.subject}. Valid subjects: ${VALID_SUBJECTS.join(", ")}`);
2049
+ }
2050
+ return engine.course.advance(options);
2051
+ },
2052
+ unenroll: async (options) => {
2053
+ assertPlatformMode(client, "timeback.course.unenroll()");
2054
+ if (options?.subject !== undefined && !isValidSubject(options.subject)) {
2055
+ throw new Error(`Invalid subject: ${options.subject}. Valid subjects: ${VALID_SUBJECTS.join(", ")}`);
2056
+ }
2057
+ if (options?.force !== undefined && typeof options.force !== "boolean") {
2058
+ throw new Error("Invalid force: must be a boolean");
2059
+ }
2060
+ return engine.course.unenroll(options);
2006
2061
  }
2007
- return engine.advanceCourse(options);
2008
2062
  }
2009
2063
  };
2010
2064
  }
@@ -1110,6 +1110,12 @@ interface TimebackUser extends TimebackUserContext {
1110
1110
  * Call `mastery.fetch()` to get mastery progress from the server.
1111
1111
  */
1112
1112
  mastery: TimebackUserMastery;
1113
+ /**
1114
+ * Highest-grade-mastered data for the current user.
1115
+ * Call `highestGradeMastered.fetch({ subject })` to get the student's
1116
+ * highest mastered grade for a subject.
1117
+ */
1118
+ highestGradeMastered: TimebackUserHighestGradeMastered;
1113
1119
  }
1114
1120
  /**
1115
1121
  * Mastery data access for the current user.
@@ -1150,6 +1156,17 @@ interface TimebackUserMastery {
1150
1156
  */
1151
1157
  fetch(options?: GetMasteryOptions): Promise<MasteryResponse>;
1152
1158
  }
1159
+ /**
1160
+ * Highest-grade-mastered data access for the current user.
1161
+ * Results are cached for 5 seconds to avoid redundant network requests.
1162
+ */
1163
+ interface TimebackUserHighestGradeMastered {
1164
+ /**
1165
+ * Fetch the highest grade the current student has mastered for a subject.
1166
+ * Results are cached for 5 seconds (use `force: true` to bypass).
1167
+ */
1168
+ fetch(options: GetHighestGradeMasteredOptions): Promise<HighestGradeMasteredResponse>;
1169
+ }
1153
1170
  /**
1154
1171
  * Options for querying student XP.
1155
1172
  */
@@ -1176,6 +1193,15 @@ interface GetMasteryOptions {
1176
1193
  /** Bypass cache and fetch fresh data (default: false) */
1177
1194
  force?: boolean;
1178
1195
  }
1196
+ /**
1197
+ * Options for querying highest grade mastered.
1198
+ */
1199
+ interface GetHighestGradeMasteredOptions {
1200
+ /** Subject to query */
1201
+ subject: TimebackSubject;
1202
+ /** Bypass cache and fetch fresh data (default: false) */
1203
+ force?: boolean;
1204
+ }
1179
1205
  /**
1180
1206
  * XP data for a single course.
1181
1207
  */
@@ -1214,6 +1240,13 @@ interface MasteryResponse {
1214
1240
  totalMasterableUnits: number;
1215
1241
  courses?: CourseMastery[];
1216
1242
  }
1243
+ /**
1244
+ * Response from highest-grade-mastered query.
1245
+ */
1246
+ interface HighestGradeMasteredResponse {
1247
+ subject: TimebackSubject;
1248
+ highestGradeMastered: TimebackGrade | null;
1249
+ }
1217
1250
 
1218
1251
  /**
1219
1252
  * Core client configuration and lifecycle types
@@ -3104,4 +3137,4 @@ declare class PlaycademyInternalClient extends PlaycademyBaseClient {
3104
3137
  }
3105
3138
 
3106
3139
  export { ApiError, MessageEvents, PlaycademyInternalClient as PlaycademyClient, PlaycademyError, PlaycademyInternalClient, extractApiErrorInfo, messaging };
3107
- export type { ApiErrorCode, ApiErrorInfo, AssessmentBankStatus, AssessmentRow, AssessmentSummary, AuthCallbackPayload, AuthOptions, AuthProviderType, AuthResult, AuthServerMessage, AuthStateChangePayload, AuthStateUpdate, BetterAuthApiKey, BetterAuthApiKeyResponse, BetterAuthSignInResponse, BucketFile, ClientConfig, ClientEvents, CourseMastery, CourseXp, DemoEndOptions, DemoEndPayload, DevUploadEvent, DevUploadHooks, ErrorResponseBody, EventListeners, ExternalGame, FetchedGame, Game, GameContextPayload, GameCustomHostname, GameInitUser, GameRow as GameRecord, GameTokenResponse, GetMasteryOptions, GetXpOptions, HostedGame, InitErrorPayload, InitPayload, KVKeyEntry, KVKeyMetadata, KVSeedEntry, KVStatsResponse, KeyEventPayload, LoginResponse, MasteryResponse, MessageEventMap, PlatformTimebackUser, PlatformTimebackUserContext, PlaycademyMode, PlaycademyServerClientConfig, PlaycademyServerClientState, ScoreSubmission, StartActivityOptions, StartActivityResult, TelemetryPayload, TimebackEnrollment, TimebackInitContext, TimebackOrganization, TimebackUser, TimebackUserContext, TimebackUserMastery, TimebackUserRefreshField, TimebackUserRefreshOptions, TimebackUserXp, TokenRefreshPayload, TokenType, UpsertGameMetadataInput, UserRow as User, XpResponse };
3140
+ export type { ApiErrorCode, ApiErrorInfo, AssessmentBankStatus, AssessmentRow, AssessmentSummary, AuthCallbackPayload, AuthOptions, AuthProviderType, AuthResult, AuthServerMessage, AuthStateChangePayload, AuthStateUpdate, BetterAuthApiKey, BetterAuthApiKeyResponse, BetterAuthSignInResponse, BucketFile, ClientConfig, ClientEvents, CourseMastery, CourseXp, DemoEndOptions, DemoEndPayload, DevUploadEvent, DevUploadHooks, ErrorResponseBody, EventListeners, ExternalGame, FetchedGame, Game, GameContextPayload, GameCustomHostname, GameInitUser, GameRow as GameRecord, GameTokenResponse, GetHighestGradeMasteredOptions, GetMasteryOptions, GetXpOptions, HighestGradeMasteredResponse, HostedGame, InitErrorPayload, InitPayload, KVKeyEntry, KVKeyMetadata, KVSeedEntry, KVStatsResponse, KeyEventPayload, LoginResponse, MasteryResponse, MessageEventMap, PlatformTimebackUser, PlatformTimebackUserContext, PlaycademyMode, PlaycademyServerClientConfig, PlaycademyServerClientState, ScoreSubmission, StartActivityOptions, StartActivityResult, TelemetryPayload, TimebackEnrollment, TimebackInitContext, TimebackOrganization, TimebackUser, TimebackUserContext, TimebackUserHighestGradeMastered, TimebackUserMastery, TimebackUserRefreshField, TimebackUserRefreshOptions, TimebackUserXp, TokenRefreshPayload, TokenType, UpsertGameMetadataInput, UserRow as User, XpResponse };
package/dist/internal.js CHANGED
@@ -1146,8 +1146,10 @@ var TIMEBACK_ROUTES = {
1146
1146
  END_ACTIVITY: "/integrations/timeback/end-activity",
1147
1147
  GET_XP: "/integrations/timeback/xp",
1148
1148
  GET_MASTERY: "/integrations/timeback/mastery",
1149
+ GET_HIGHEST_GRADE_MASTERED: "/integrations/timeback/highest-grade-mastered",
1149
1150
  HEARTBEAT: "/integrations/timeback/heartbeat",
1150
- ADVANCE_COURSE: "/integrations/timeback/advance-course"
1151
+ ADVANCE_COURSE: "/integrations/timeback/advance-course",
1152
+ UNENROLL_COURSE: "/integrations/timeback/unenroll-course"
1151
1153
  };
1152
1154
  var TIMEBACK_GAME_METRIC_DECIMAL_PLACES = {
1153
1155
  xp: 1,
@@ -1800,20 +1802,29 @@ function createTimebackEngine(client) {
1800
1802
  ttl: 5000,
1801
1803
  keyPrefix: "game.timeback.mastery"
1802
1804
  });
1805
+ const highestGradeMasteredCache = createTTLCache({
1806
+ ttl: 5000,
1807
+ keyPrefix: "game.timeback.highestGradeMastered"
1808
+ });
1803
1809
  const enrollmentsCache = createTTLCache({
1804
1810
  ttl: 5 * 60 * 1000,
1805
1811
  keyPrefix: "game.timeback.enrollments"
1806
1812
  });
1807
- async function applyPromotion(promotion) {
1808
- if (promotion.status !== "promoted" && promotion.status !== "already-promoted") {
1809
- return;
1810
- }
1813
+ async function refreshAfterCourseMutation() {
1811
1814
  userCache.clear("current");
1812
1815
  enrollmentsCache.clear("current");
1816
+ xpCache.clear();
1817
+ highestGradeMasteredCache.clear();
1813
1818
  try {
1814
1819
  await userStore.refresh();
1815
1820
  } catch {}
1816
1821
  }
1822
+ async function applyPromotion(promotion) {
1823
+ if (promotion.status !== "promoted" && promotion.status !== "already-promoted") {
1824
+ return;
1825
+ }
1826
+ await refreshAfterCourseMutation();
1827
+ }
1817
1828
  const activityTracker = createTimebackActivityTracker(client);
1818
1829
  async function refreshUserContext() {
1819
1830
  const context = await userStore.refresh();
@@ -1891,6 +1902,14 @@ function createTimebackEngine(client) {
1891
1902
  return client["requestGameBackend"](endpoint, "GET");
1892
1903
  }, { force: options.force });
1893
1904
  }
1905
+ },
1906
+ highestGradeMastered: {
1907
+ fetch: (options) => highestGradeMasteredCache.get(options.subject, async () => {
1908
+ const params = new URLSearchParams;
1909
+ params.set("subject", options.subject);
1910
+ const endpoint = `${TIMEBACK_ROUTES.GET_HIGHEST_GRADE_MASTERED}?${params.toString()}`;
1911
+ return client["requestGameBackend"](endpoint, "GET");
1912
+ }, { force: options.force })
1894
1913
  }
1895
1914
  },
1896
1915
  activity: {
@@ -1900,10 +1919,22 @@ function createTimebackEngine(client) {
1900
1919
  resume: activityTracker.resumeActivity,
1901
1920
  end: activityTracker.endActivity
1902
1921
  },
1903
- async advanceCourse(params) {
1904
- const response = await client["requestGameBackend"](TIMEBACK_ROUTES.ADVANCE_COURSE, "POST", params?.subject !== undefined ? { subject: params.subject } : {});
1905
- await applyPromotion(response.promotion);
1906
- return response;
1922
+ course: {
1923
+ async advance(params) {
1924
+ const response = await client["requestGameBackend"](TIMEBACK_ROUTES.ADVANCE_COURSE, "POST", params?.subject !== undefined ? { subject: params.subject } : {});
1925
+ await applyPromotion(response.promotion);
1926
+ return response;
1927
+ },
1928
+ async unenroll(params) {
1929
+ const response = await client["requestGameBackend"](TIMEBACK_ROUTES.UNENROLL_COURSE, "POST", {
1930
+ ...params?.subject !== undefined ? { subject: params.subject } : {},
1931
+ ...params?.force !== undefined ? { force: params.force } : {}
1932
+ });
1933
+ if (response.unenrollment.status === "unenrolled") {
1934
+ await refreshAfterCourseMutation();
1935
+ }
1936
+ return response;
1937
+ }
1907
1938
  }
1908
1939
  };
1909
1940
  }
@@ -1976,6 +2007,17 @@ function createTimebackNamespace(client) {
1976
2007
  }
1977
2008
  return engine.user.mastery.fetch(options);
1978
2009
  }
2010
+ },
2011
+ highestGradeMastered: {
2012
+ fetch: async (options) => {
2013
+ if (!options?.subject) {
2014
+ throw new Error("subject is required");
2015
+ }
2016
+ if (!isValidSubject(options.subject)) {
2017
+ throw new Error(`Invalid subject: ${options.subject}. Valid subjects: ${VALID_SUBJECTS.join(", ")}`);
2018
+ }
2019
+ return engine.user.highestGradeMastered.fetch(options);
2020
+ }
1979
2021
  }
1980
2022
  };
1981
2023
  },
@@ -1999,12 +2041,24 @@ function createTimebackNamespace(client) {
1999
2041
  assertPlatformMode(client, "timeback.endActivity()");
2000
2042
  return engine.activity.end(data);
2001
2043
  },
2002
- advanceCourse: async (options) => {
2003
- assertPlatformMode(client, "timeback.advanceCourse()");
2004
- if (options?.subject !== undefined && !isValidSubject(options.subject)) {
2005
- throw new Error(`Invalid subject: ${options.subject}. Valid subjects: ${VALID_SUBJECTS.join(", ")}`);
2044
+ course: {
2045
+ advance: async (options) => {
2046
+ assertPlatformMode(client, "timeback.course.advance()");
2047
+ if (options?.subject !== undefined && !isValidSubject(options.subject)) {
2048
+ throw new Error(`Invalid subject: ${options.subject}. Valid subjects: ${VALID_SUBJECTS.join(", ")}`);
2049
+ }
2050
+ return engine.course.advance(options);
2051
+ },
2052
+ unenroll: async (options) => {
2053
+ assertPlatformMode(client, "timeback.course.unenroll()");
2054
+ if (options?.subject !== undefined && !isValidSubject(options.subject)) {
2055
+ throw new Error(`Invalid subject: ${options.subject}. Valid subjects: ${VALID_SUBJECTS.join(", ")}`);
2056
+ }
2057
+ if (options?.force !== undefined && typeof options.force !== "boolean") {
2058
+ throw new Error("Invalid force: must be a boolean");
2059
+ }
2060
+ return engine.course.unenroll(options);
2006
2061
  }
2007
- return engine.advanceCourse(options);
2008
2062
  }
2009
2063
  };
2010
2064
  }
@@ -303,6 +303,9 @@ declare class PlaycademyClient {
303
303
  subject?: string;
304
304
  include?: 'perCourse'[];
305
305
  }) => Promise<_playcademy_types.StudentMasteryResponse>;
306
+ getStudentHighestGradeMastered: (studentId: string, options: {
307
+ subject: string;
308
+ }) => Promise<_playcademy_types.StudentHighestGradeMasteredResponse>;
306
309
  };
307
310
  }
308
311
 
@@ -110,6 +110,16 @@ function createTimebackNamespace(client) {
110
110
  const queryString = params.toString();
111
111
  const endpoint = `/api/timeback/student-mastery/${studentId}?${queryString}`;
112
112
  return client["request"](endpoint, "GET");
113
+ },
114
+ getStudentHighestGradeMastered: async (studentId, options) => {
115
+ if (!isValidSubject(options.subject)) {
116
+ throw new Error(`Invalid subject: ${options.subject}. Valid subjects: ${VALID_SUBJECTS.join(", ")}`);
117
+ }
118
+ const params = new URLSearchParams;
119
+ params.set("gameId", client.gameId);
120
+ params.set("subject", options.subject);
121
+ const endpoint = `/api/timeback/student-highest-grade-mastered/${encodeURIComponent(studentId)}?${params.toString()}`;
122
+ return client["request"](endpoint, "GET");
113
123
  }
114
124
  };
115
125
  }
package/dist/server.d.ts CHANGED
@@ -303,6 +303,9 @@ declare class PlaycademyClient$1 {
303
303
  subject?: string;
304
304
  include?: 'perCourse'[];
305
305
  }) => Promise<_playcademy_types.StudentMasteryResponse>;
306
+ getStudentHighestGradeMastered: (studentId: string, options: {
307
+ subject: string;
308
+ }) => Promise<_playcademy_types.StudentHighestGradeMasteredResponse>;
306
309
  };
307
310
  }
308
311
 
package/dist/server.js CHANGED
@@ -299,6 +299,16 @@ function createTimebackNamespace(client) {
299
299
  const queryString = params.toString();
300
300
  const endpoint = `/api/timeback/student-mastery/${studentId}?${queryString}`;
301
301
  return client["request"](endpoint, "GET");
302
+ },
303
+ getStudentHighestGradeMastered: async (studentId, options) => {
304
+ if (!isValidSubject(options.subject)) {
305
+ throw new Error(`Invalid subject: ${options.subject}. Valid subjects: ${VALID_SUBJECTS.join(", ")}`);
306
+ }
307
+ const params = new URLSearchParams;
308
+ params.set("gameId", client.gameId);
309
+ params.set("subject", options.subject);
310
+ const endpoint = `/api/timeback/student-highest-grade-mastered/${encodeURIComponent(studentId)}?${params.toString()}`;
311
+ return client["request"](endpoint, "GET");
302
312
  }
303
313
  };
304
314
  }
package/dist/types.d.ts CHANGED
@@ -1385,9 +1385,15 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
1385
1385
  pauseActivity: () => void;
1386
1386
  resumeActivity: () => void;
1387
1387
  endActivity: (data: _playcademy_types.EndActivityScoreData) => Promise<_playcademy_types.EndActivityResponse>;
1388
- advanceCourse: (options?: {
1389
- subject?: _playcademy_types.TimebackSubject;
1390
- }) => Promise<_playcademy_types.AdvanceCourseResponse>;
1388
+ course: {
1389
+ advance: (options?: {
1390
+ subject?: _playcademy_types.TimebackSubject;
1391
+ }) => Promise<_playcademy_types.AdvanceCourseResponse>;
1392
+ unenroll: (options?: {
1393
+ subject?: _playcademy_types.TimebackSubject;
1394
+ force?: boolean;
1395
+ }) => Promise<_playcademy_types.UnenrollCourseResponse>;
1396
+ };
1391
1397
  };
1392
1398
  /**
1393
1399
  * Game score submission and leaderboards.
@@ -1613,6 +1619,12 @@ interface TimebackUser extends TimebackUserContext {
1613
1619
  * Call `mastery.fetch()` to get mastery progress from the server.
1614
1620
  */
1615
1621
  mastery: TimebackUserMastery;
1622
+ /**
1623
+ * Highest-grade-mastered data for the current user.
1624
+ * Call `highestGradeMastered.fetch({ subject })` to get the student's
1625
+ * highest mastered grade for a subject.
1626
+ */
1627
+ highestGradeMastered: TimebackUserHighestGradeMastered;
1616
1628
  }
1617
1629
  /**
1618
1630
  * Mastery data access for the current user.
@@ -1653,6 +1665,17 @@ interface TimebackUserMastery {
1653
1665
  */
1654
1666
  fetch(options?: GetMasteryOptions): Promise<MasteryResponse>;
1655
1667
  }
1668
+ /**
1669
+ * Highest-grade-mastered data access for the current user.
1670
+ * Results are cached for 5 seconds to avoid redundant network requests.
1671
+ */
1672
+ interface TimebackUserHighestGradeMastered {
1673
+ /**
1674
+ * Fetch the highest grade the current student has mastered for a subject.
1675
+ * Results are cached for 5 seconds (use `force: true` to bypass).
1676
+ */
1677
+ fetch(options: GetHighestGradeMasteredOptions): Promise<HighestGradeMasteredResponse>;
1678
+ }
1656
1679
  /**
1657
1680
  * Options for querying student XP.
1658
1681
  */
@@ -1679,6 +1702,15 @@ interface GetMasteryOptions {
1679
1702
  /** Bypass cache and fetch fresh data (default: false) */
1680
1703
  force?: boolean;
1681
1704
  }
1705
+ /**
1706
+ * Options for querying highest grade mastered.
1707
+ */
1708
+ interface GetHighestGradeMasteredOptions {
1709
+ /** Subject to query */
1710
+ subject: TimebackSubject;
1711
+ /** Bypass cache and fetch fresh data (default: false) */
1712
+ force?: boolean;
1713
+ }
1682
1714
  /**
1683
1715
  * XP data for a single course.
1684
1716
  */
@@ -1717,6 +1749,13 @@ interface MasteryResponse {
1717
1749
  totalMasterableUnits: number;
1718
1750
  courses?: CourseMastery[];
1719
1751
  }
1752
+ /**
1753
+ * Response from highest-grade-mastered query.
1754
+ */
1755
+ interface HighestGradeMasteredResponse {
1756
+ subject: TimebackSubject;
1757
+ highestGradeMastered: TimebackGrade | null;
1758
+ }
1720
1759
 
1721
1760
  /**
1722
1761
  * Core client configuration and lifecycle types
@@ -2142,4 +2181,4 @@ interface AssessmentBankStatus {
2142
2181
  }
2143
2182
 
2144
2183
  export { PlaycademyClient };
2145
- export type { AssessmentBankStatus, AssessmentRow, AssessmentSummary, AuthCallbackPayload, AuthOptions, AuthProviderType, AuthResult, AuthServerMessage, AuthStateChangePayload, AuthStateUpdate, BetterAuthApiKey, BetterAuthApiKeyResponse, BetterAuthSignInResponse, BucketFile, ClientConfig, ClientEvents, CourseMastery, CourseXp, DemoEndOptions, DemoEndPayload, DevUploadEvent, DevUploadHooks, EventListeners, ExternalGame, FetchedGame, Game, GameContextPayload, GameCustomHostname, GameInitUser, GameRow as GameRecord, GameTokenResponse, GetMasteryOptions, GetXpOptions, HostedGame, InitErrorPayload, InitPayload, KVKeyEntry, KVKeyMetadata, KVSeedEntry, KVStatsResponse, KeyEventPayload, LoginResponse, MasteryResponse, PlatformTimebackUser, PlatformTimebackUserContext, PlaycademyMode, PlaycademyServerClientConfig, PlaycademyServerClientState, ScoreSubmission, StartActivityOptions, StartActivityResult, TelemetryPayload, TimebackEnrollment, TimebackInitContext, TimebackOrganization, TimebackUser, TimebackUserContext, TimebackUserMastery, TimebackUserRefreshField, TimebackUserRefreshOptions, TimebackUserXp, TokenRefreshPayload, TokenType, UpsertGameMetadataInput, UserRow as User, XpResponse };
2184
+ export type { AssessmentBankStatus, AssessmentRow, AssessmentSummary, AuthCallbackPayload, AuthOptions, AuthProviderType, AuthResult, AuthServerMessage, AuthStateChangePayload, AuthStateUpdate, BetterAuthApiKey, BetterAuthApiKeyResponse, BetterAuthSignInResponse, BucketFile, ClientConfig, ClientEvents, CourseMastery, CourseXp, DemoEndOptions, DemoEndPayload, DevUploadEvent, DevUploadHooks, EventListeners, ExternalGame, FetchedGame, Game, GameContextPayload, GameCustomHostname, GameInitUser, GameRow as GameRecord, GameTokenResponse, GetHighestGradeMasteredOptions, GetMasteryOptions, GetXpOptions, HighestGradeMasteredResponse, HostedGame, InitErrorPayload, InitPayload, KVKeyEntry, KVKeyMetadata, KVSeedEntry, KVStatsResponse, KeyEventPayload, LoginResponse, MasteryResponse, PlatformTimebackUser, PlatformTimebackUserContext, PlaycademyMode, PlaycademyServerClientConfig, PlaycademyServerClientState, ScoreSubmission, StartActivityOptions, StartActivityResult, TelemetryPayload, TimebackEnrollment, TimebackInitContext, TimebackOrganization, TimebackUser, TimebackUserContext, TimebackUserHighestGradeMastered, TimebackUserMastery, TimebackUserRefreshField, TimebackUserRefreshOptions, TimebackUserXp, TokenRefreshPayload, TokenType, UpsertGameMetadataInput, UserRow as User, XpResponse };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/sdk",
3
- "version": "0.11.0",
3
+ "version": "0.11.1-beta.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {