@playcademy/sdk 0.10.0 → 0.10.1-beta.1

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
@@ -1145,9 +1145,7 @@ interface StartActivityResult {
1145
1145
 
1146
1146
  /**
1147
1147
  * A TimeBack enrollment for the current game session.
1148
- * Alias for UserEnrollment without the optional gameId. Active enrollment IDs
1149
- * are available at `enrollment.enrollmentIds?.active` when supplied by the
1150
- * platform.
1148
+ * Alias for UserEnrollment without the optional gameId.
1151
1149
  */
1152
1150
  type TimebackEnrollment = Omit<UserEnrollment, 'gameId'>;
1153
1151
  /**
@@ -1253,6 +1251,50 @@ interface TimebackUser extends TimebackUserContext {
1253
1251
  * Call `xp.fetch()` to get XP from the server.
1254
1252
  */
1255
1253
  xp: TimebackUserXp;
1254
+ /**
1255
+ * Mastery data for the current user.
1256
+ * Call `mastery.fetch()` to get mastery progress from the server.
1257
+ */
1258
+ mastery: TimebackUserMastery;
1259
+ }
1260
+ /**
1261
+ * Mastery data access for the current user.
1262
+ * Results are cached for 5 seconds to avoid redundant network requests.
1263
+ */
1264
+ interface TimebackUserMastery {
1265
+ /**
1266
+ * Fetch mastery data from the server.
1267
+ * Returns mastery for all courses in this game, or filter by grade/subject.
1268
+ * Results are cached for 5 seconds (use `force: true` to bypass).
1269
+ *
1270
+ * @param options - Query options
1271
+ * @param options.grade - Grade level to filter (must be used with subject)
1272
+ * @param options.subject - Subject to filter (must be used with grade)
1273
+ * @param options.include - Additional data to include: 'perCourse'
1274
+ * @param options.force - Bypass cache and fetch fresh data (default: false)
1275
+ * @returns Promise resolving to mastery data
1276
+ *
1277
+ * @example
1278
+ * ```typescript
1279
+ * // Get total mastery for all game courses
1280
+ * const mastery = await client.timeback.user.mastery.fetch()
1281
+ *
1282
+ * // Get mastery for a specific grade/subject
1283
+ * const mastery = await client.timeback.user.mastery.fetch({
1284
+ * grade: 3,
1285
+ * subject: 'Math'
1286
+ * })
1287
+ *
1288
+ * // Get mastery with per-course breakdown
1289
+ * const mastery = await client.timeback.user.mastery.fetch({
1290
+ * include: ['perCourse']
1291
+ * })
1292
+ *
1293
+ * // Force fresh data
1294
+ * const mastery = await client.timeback.user.mastery.fetch({ force: true })
1295
+ * ```
1296
+ */
1297
+ fetch(options?: GetMasteryOptions): Promise<MasteryResponse>;
1256
1298
  }
1257
1299
  /**
1258
1300
  * Options for querying student XP.
@@ -1267,6 +1309,19 @@ interface GetXpOptions {
1267
1309
  /** Bypass cache and fetch fresh data (default: false) */
1268
1310
  force?: boolean;
1269
1311
  }
1312
+ /**
1313
+ * Options for querying student mastery.
1314
+ */
1315
+ interface GetMasteryOptions {
1316
+ /** Grade level to filter (must be used with subject) */
1317
+ grade?: TimebackGrade;
1318
+ /** Subject to filter (must be used with grade) */
1319
+ subject?: TimebackSubject;
1320
+ /** Additional data to include: 'perCourse' */
1321
+ include?: 'perCourse'[];
1322
+ /** Bypass cache and fetch fresh data (default: false) */
1323
+ force?: boolean;
1324
+ }
1270
1325
  /**
1271
1326
  * XP data for a single course.
1272
1327
  */
@@ -1285,6 +1340,26 @@ interface XpResponse {
1285
1340
  todayXp?: number;
1286
1341
  courses?: CourseXp[];
1287
1342
  }
1343
+ /**
1344
+ * Mastery data for a single course.
1345
+ */
1346
+ interface CourseMastery {
1347
+ grade: TimebackGrade;
1348
+ subject: TimebackSubject;
1349
+ title: string;
1350
+ masteredUnits: number;
1351
+ masterableUnits: number;
1352
+ pctComplete: number;
1353
+ isComplete: boolean;
1354
+ }
1355
+ /**
1356
+ * Response from mastery query.
1357
+ */
1358
+ interface MasteryResponse {
1359
+ totalMasteredUnits: number;
1360
+ totalMasterableUnits: number;
1361
+ courses?: CourseMastery[];
1362
+ }
1288
1363
 
1289
1364
  /**
1290
1365
  * Core client configuration and lifecycle types
package/dist/index.js CHANGED
@@ -1145,6 +1145,7 @@ function isValidSubject(value) {
1145
1145
  var TIMEBACK_ROUTES = {
1146
1146
  END_ACTIVITY: "/integrations/timeback/end-activity",
1147
1147
  GET_XP: "/integrations/timeback/xp",
1148
+ GET_MASTERY: "/integrations/timeback/mastery",
1148
1149
  HEARTBEAT: "/integrations/timeback/heartbeat",
1149
1150
  ADVANCE_COURSE: "/integrations/timeback/advance-course"
1150
1151
  };
@@ -1705,6 +1706,9 @@ function createTimebackActivityTracker(client) {
1705
1706
  const unreportedActiveMs = Math.max(0, activeTime - activity.totalPersistedActiveMs);
1706
1707
  const unreportedPausedMs = Math.max(0, activity.pausedTime - activity.totalPersistedPausedMs);
1707
1708
  const { correctQuestions, totalQuestions } = data;
1709
+ if (data.masteredUnits !== undefined && data.masteredUnitsAbsolute !== undefined) {
1710
+ throw new Error("Cannot provide both masteredUnits and masteredUnitsAbsolute — use one or the other");
1711
+ }
1708
1712
  const request = {
1709
1713
  runId: activity.runId,
1710
1714
  resumeId: activity.resumeId,
@@ -1722,6 +1726,7 @@ function createTimebackActivityTracker(client) {
1722
1726
  },
1723
1727
  xpEarned: data.xpAwarded,
1724
1728
  masteredUnits: data.masteredUnits,
1729
+ masteredUnitsAbsolute: data.masteredUnitsAbsolute,
1725
1730
  extensions: data.extensions
1726
1731
  };
1727
1732
  try {
@@ -1780,6 +1785,10 @@ function createTimebackEngine(client) {
1780
1785
  ttl: 5000,
1781
1786
  keyPrefix: "game.timeback.xp"
1782
1787
  });
1788
+ const masteryCache = createTTLCache({
1789
+ ttl: 5000,
1790
+ keyPrefix: "game.timeback.mastery"
1791
+ });
1783
1792
  const enrollmentsCache = createTTLCache({
1784
1793
  ttl: 5 * 60 * 1000,
1785
1794
  keyPrefix: "game.timeback.enrollments"
@@ -1847,6 +1856,30 @@ function createTimebackEngine(client) {
1847
1856
  return client["requestGameBackend"](endpoint, "GET");
1848
1857
  }, { force: options.force });
1849
1858
  }
1859
+ },
1860
+ mastery: {
1861
+ fetch: (options) => {
1862
+ const cacheKey = [
1863
+ options.grade ?? "",
1864
+ options.subject ?? "",
1865
+ options.include?.toSorted().join(",") ?? ""
1866
+ ].join(":");
1867
+ return masteryCache.get(cacheKey, async () => {
1868
+ const params = new URLSearchParams;
1869
+ if (options.grade !== undefined) {
1870
+ params.set("grade", String(options.grade));
1871
+ }
1872
+ if (options.subject !== undefined) {
1873
+ params.set("subject", options.subject);
1874
+ }
1875
+ if (options.include?.length) {
1876
+ params.set("include", options.include.join(","));
1877
+ }
1878
+ const queryString = params.toString();
1879
+ const endpoint = `${TIMEBACK_ROUTES.GET_MASTERY}${queryString ? `?${queryString}` : ""}`;
1880
+ return client["requestGameBackend"](endpoint, "GET");
1881
+ }, { force: options.force });
1882
+ }
1850
1883
  }
1851
1884
  },
1852
1885
  activity: {
@@ -1865,7 +1898,8 @@ function createTimebackEngine(client) {
1865
1898
  }
1866
1899
 
1867
1900
  // src/namespaces/game/timeback.ts
1868
- var VALID_INCLUDE_OPTIONS = ["perCourse", "today"];
1901
+ var VALID_XP_INCLUDE_OPTIONS = ["perCourse", "today"];
1902
+ var VALID_MASTERY_INCLUDE_OPTIONS = ["perCourse"];
1869
1903
  function createTimebackNamespace(client) {
1870
1904
  const engine = createTimebackEngine(client);
1871
1905
  return {
@@ -1901,13 +1935,36 @@ function createTimebackNamespace(client) {
1901
1935
  }
1902
1936
  if (options.include?.length) {
1903
1937
  for (const opt of options.include) {
1904
- if (!VALID_INCLUDE_OPTIONS.includes(opt)) {
1905
- throw new Error(`Invalid include option: ${opt}. Valid options: ${VALID_INCLUDE_OPTIONS.join(", ")}`);
1938
+ if (!VALID_XP_INCLUDE_OPTIONS.includes(opt)) {
1939
+ throw new Error(`Invalid include option: ${opt}. Valid options: ${VALID_XP_INCLUDE_OPTIONS.join(", ")}`);
1906
1940
  }
1907
1941
  }
1908
1942
  }
1909
1943
  return engine.user.xp.fetch(options);
1910
1944
  }
1945
+ },
1946
+ mastery: {
1947
+ fetch: async (options = {}) => {
1948
+ const hasGrade = options.grade !== undefined;
1949
+ const hasSubject = options.subject !== undefined;
1950
+ if (hasGrade !== hasSubject) {
1951
+ throw new Error("Both grade and subject must be provided together");
1952
+ }
1953
+ if (hasGrade && !isValidGrade(options.grade)) {
1954
+ throw new Error(`Invalid grade: ${options.grade}. Valid grades: ${VALID_GRADES.join(", ")}`);
1955
+ }
1956
+ if (hasSubject && !isValidSubject(options.subject)) {
1957
+ throw new Error(`Invalid subject: ${options.subject}. Valid subjects: ${VALID_SUBJECTS.join(", ")}`);
1958
+ }
1959
+ if (options.include?.length) {
1960
+ for (const opt of options.include) {
1961
+ if (!VALID_MASTERY_INCLUDE_OPTIONS.includes(opt)) {
1962
+ throw new Error(`Invalid include option: ${opt}. Valid options: ${VALID_MASTERY_INCLUDE_OPTIONS.join(", ")}`);
1963
+ }
1964
+ }
1965
+ }
1966
+ return engine.user.mastery.fetch(options);
1967
+ }
1911
1968
  }
1912
1969
  };
1913
1970
  },
@@ -999,9 +999,7 @@ interface StartActivityResult {
999
999
 
1000
1000
  /**
1001
1001
  * A TimeBack enrollment for the current game session.
1002
- * Alias for UserEnrollment without the optional gameId. Active enrollment IDs
1003
- * are available at `enrollment.enrollmentIds?.active` when supplied by the
1004
- * platform.
1002
+ * Alias for UserEnrollment without the optional gameId.
1005
1003
  */
1006
1004
  type TimebackEnrollment = Omit<UserEnrollment, 'gameId'>;
1007
1005
  /**
@@ -1107,6 +1105,50 @@ interface TimebackUser extends TimebackUserContext {
1107
1105
  * Call `xp.fetch()` to get XP from the server.
1108
1106
  */
1109
1107
  xp: TimebackUserXp;
1108
+ /**
1109
+ * Mastery data for the current user.
1110
+ * Call `mastery.fetch()` to get mastery progress from the server.
1111
+ */
1112
+ mastery: TimebackUserMastery;
1113
+ }
1114
+ /**
1115
+ * Mastery data access for the current user.
1116
+ * Results are cached for 5 seconds to avoid redundant network requests.
1117
+ */
1118
+ interface TimebackUserMastery {
1119
+ /**
1120
+ * Fetch mastery data from the server.
1121
+ * Returns mastery for all courses in this game, or filter by grade/subject.
1122
+ * Results are cached for 5 seconds (use `force: true` to bypass).
1123
+ *
1124
+ * @param options - Query options
1125
+ * @param options.grade - Grade level to filter (must be used with subject)
1126
+ * @param options.subject - Subject to filter (must be used with grade)
1127
+ * @param options.include - Additional data to include: 'perCourse'
1128
+ * @param options.force - Bypass cache and fetch fresh data (default: false)
1129
+ * @returns Promise resolving to mastery data
1130
+ *
1131
+ * @example
1132
+ * ```typescript
1133
+ * // Get total mastery for all game courses
1134
+ * const mastery = await client.timeback.user.mastery.fetch()
1135
+ *
1136
+ * // Get mastery for a specific grade/subject
1137
+ * const mastery = await client.timeback.user.mastery.fetch({
1138
+ * grade: 3,
1139
+ * subject: 'Math'
1140
+ * })
1141
+ *
1142
+ * // Get mastery with per-course breakdown
1143
+ * const mastery = await client.timeback.user.mastery.fetch({
1144
+ * include: ['perCourse']
1145
+ * })
1146
+ *
1147
+ * // Force fresh data
1148
+ * const mastery = await client.timeback.user.mastery.fetch({ force: true })
1149
+ * ```
1150
+ */
1151
+ fetch(options?: GetMasteryOptions): Promise<MasteryResponse>;
1110
1152
  }
1111
1153
  /**
1112
1154
  * Options for querying student XP.
@@ -1121,6 +1163,19 @@ interface GetXpOptions {
1121
1163
  /** Bypass cache and fetch fresh data (default: false) */
1122
1164
  force?: boolean;
1123
1165
  }
1166
+ /**
1167
+ * Options for querying student mastery.
1168
+ */
1169
+ interface GetMasteryOptions {
1170
+ /** Grade level to filter (must be used with subject) */
1171
+ grade?: TimebackGrade;
1172
+ /** Subject to filter (must be used with grade) */
1173
+ subject?: TimebackSubject;
1174
+ /** Additional data to include: 'perCourse' */
1175
+ include?: 'perCourse'[];
1176
+ /** Bypass cache and fetch fresh data (default: false) */
1177
+ force?: boolean;
1178
+ }
1124
1179
  /**
1125
1180
  * XP data for a single course.
1126
1181
  */
@@ -1139,6 +1194,26 @@ interface XpResponse {
1139
1194
  todayXp?: number;
1140
1195
  courses?: CourseXp[];
1141
1196
  }
1197
+ /**
1198
+ * Mastery data for a single course.
1199
+ */
1200
+ interface CourseMastery {
1201
+ grade: TimebackGrade;
1202
+ subject: TimebackSubject;
1203
+ title: string;
1204
+ masteredUnits: number;
1205
+ masterableUnits: number;
1206
+ pctComplete: number;
1207
+ isComplete: boolean;
1208
+ }
1209
+ /**
1210
+ * Response from mastery query.
1211
+ */
1212
+ interface MasteryResponse {
1213
+ totalMasteredUnits: number;
1214
+ totalMasterableUnits: number;
1215
+ courses?: CourseMastery[];
1216
+ }
1142
1217
 
1143
1218
  /**
1144
1219
  * Core client configuration and lifecycle types
@@ -3025,4 +3100,4 @@ declare class PlaycademyInternalClient extends PlaycademyBaseClient {
3025
3100
  }
3026
3101
 
3027
3102
  export { ApiError, MessageEvents, PlaycademyInternalClient as PlaycademyClient, PlaycademyError, PlaycademyInternalClient, extractApiErrorInfo, messaging };
3028
- export type { ApiErrorCode, ApiErrorInfo, AssessmentBankStatus, AssessmentRow, AssessmentSummary, AuthCallbackPayload, AuthOptions, AuthProviderType, AuthResult, AuthServerMessage, AuthStateChangePayload, AuthStateUpdate, BetterAuthApiKey, BetterAuthApiKeyResponse, BetterAuthSignInResponse, BucketFile, ClientConfig, ClientEvents, CourseXp, DemoEndOptions, DemoEndPayload, DevUploadEvent, DevUploadHooks, ErrorResponseBody, EventListeners, ExternalGame, FetchedGame, Game, GameContextPayload, GameCustomHostname, GameInitUser, GameRow as GameRecord, GameTokenResponse, GetXpOptions, HostedGame, InitErrorPayload, InitPayload, KVKeyEntry, KVKeyMetadata, KVSeedEntry, KVStatsResponse, KeyEventPayload, LoginResponse, MessageEventMap, PlatformTimebackUser, PlatformTimebackUserContext, PlaycademyMode, PlaycademyServerClientConfig, PlaycademyServerClientState, ScoreSubmission, StartActivityOptions, StartActivityResult, TelemetryPayload, TimebackEnrollment, TimebackInitContext, TimebackOrganization, TimebackUser, TimebackUserContext, TimebackUserRefreshField, TimebackUserRefreshOptions, TimebackUserXp, TokenRefreshPayload, TokenType, UpsertGameMetadataInput, UserRow as User, XpResponse };
3103
+ 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 };
package/dist/internal.js CHANGED
@@ -1145,6 +1145,7 @@ function isValidSubject(value) {
1145
1145
  var TIMEBACK_ROUTES = {
1146
1146
  END_ACTIVITY: "/integrations/timeback/end-activity",
1147
1147
  GET_XP: "/integrations/timeback/xp",
1148
+ GET_MASTERY: "/integrations/timeback/mastery",
1148
1149
  HEARTBEAT: "/integrations/timeback/heartbeat",
1149
1150
  ADVANCE_COURSE: "/integrations/timeback/advance-course"
1150
1151
  };
@@ -1705,6 +1706,9 @@ function createTimebackActivityTracker(client) {
1705
1706
  const unreportedActiveMs = Math.max(0, activeTime - activity.totalPersistedActiveMs);
1706
1707
  const unreportedPausedMs = Math.max(0, activity.pausedTime - activity.totalPersistedPausedMs);
1707
1708
  const { correctQuestions, totalQuestions } = data;
1709
+ if (data.masteredUnits !== undefined && data.masteredUnitsAbsolute !== undefined) {
1710
+ throw new Error("Cannot provide both masteredUnits and masteredUnitsAbsolute — use one or the other");
1711
+ }
1708
1712
  const request = {
1709
1713
  runId: activity.runId,
1710
1714
  resumeId: activity.resumeId,
@@ -1722,6 +1726,7 @@ function createTimebackActivityTracker(client) {
1722
1726
  },
1723
1727
  xpEarned: data.xpAwarded,
1724
1728
  masteredUnits: data.masteredUnits,
1729
+ masteredUnitsAbsolute: data.masteredUnitsAbsolute,
1725
1730
  extensions: data.extensions
1726
1731
  };
1727
1732
  try {
@@ -1780,6 +1785,10 @@ function createTimebackEngine(client) {
1780
1785
  ttl: 5000,
1781
1786
  keyPrefix: "game.timeback.xp"
1782
1787
  });
1788
+ const masteryCache = createTTLCache({
1789
+ ttl: 5000,
1790
+ keyPrefix: "game.timeback.mastery"
1791
+ });
1783
1792
  const enrollmentsCache = createTTLCache({
1784
1793
  ttl: 5 * 60 * 1000,
1785
1794
  keyPrefix: "game.timeback.enrollments"
@@ -1847,6 +1856,30 @@ function createTimebackEngine(client) {
1847
1856
  return client["requestGameBackend"](endpoint, "GET");
1848
1857
  }, { force: options.force });
1849
1858
  }
1859
+ },
1860
+ mastery: {
1861
+ fetch: (options) => {
1862
+ const cacheKey = [
1863
+ options.grade ?? "",
1864
+ options.subject ?? "",
1865
+ options.include?.toSorted().join(",") ?? ""
1866
+ ].join(":");
1867
+ return masteryCache.get(cacheKey, async () => {
1868
+ const params = new URLSearchParams;
1869
+ if (options.grade !== undefined) {
1870
+ params.set("grade", String(options.grade));
1871
+ }
1872
+ if (options.subject !== undefined) {
1873
+ params.set("subject", options.subject);
1874
+ }
1875
+ if (options.include?.length) {
1876
+ params.set("include", options.include.join(","));
1877
+ }
1878
+ const queryString = params.toString();
1879
+ const endpoint = `${TIMEBACK_ROUTES.GET_MASTERY}${queryString ? `?${queryString}` : ""}`;
1880
+ return client["requestGameBackend"](endpoint, "GET");
1881
+ }, { force: options.force });
1882
+ }
1850
1883
  }
1851
1884
  },
1852
1885
  activity: {
@@ -1865,7 +1898,8 @@ function createTimebackEngine(client) {
1865
1898
  }
1866
1899
 
1867
1900
  // src/namespaces/game/timeback.ts
1868
- var VALID_INCLUDE_OPTIONS = ["perCourse", "today"];
1901
+ var VALID_XP_INCLUDE_OPTIONS = ["perCourse", "today"];
1902
+ var VALID_MASTERY_INCLUDE_OPTIONS = ["perCourse"];
1869
1903
  function createTimebackNamespace(client) {
1870
1904
  const engine = createTimebackEngine(client);
1871
1905
  return {
@@ -1901,13 +1935,36 @@ function createTimebackNamespace(client) {
1901
1935
  }
1902
1936
  if (options.include?.length) {
1903
1937
  for (const opt of options.include) {
1904
- if (!VALID_INCLUDE_OPTIONS.includes(opt)) {
1905
- throw new Error(`Invalid include option: ${opt}. Valid options: ${VALID_INCLUDE_OPTIONS.join(", ")}`);
1938
+ if (!VALID_XP_INCLUDE_OPTIONS.includes(opt)) {
1939
+ throw new Error(`Invalid include option: ${opt}. Valid options: ${VALID_XP_INCLUDE_OPTIONS.join(", ")}`);
1906
1940
  }
1907
1941
  }
1908
1942
  }
1909
1943
  return engine.user.xp.fetch(options);
1910
1944
  }
1945
+ },
1946
+ mastery: {
1947
+ fetch: async (options = {}) => {
1948
+ const hasGrade = options.grade !== undefined;
1949
+ const hasSubject = options.subject !== undefined;
1950
+ if (hasGrade !== hasSubject) {
1951
+ throw new Error("Both grade and subject must be provided together");
1952
+ }
1953
+ if (hasGrade && !isValidGrade(options.grade)) {
1954
+ throw new Error(`Invalid grade: ${options.grade}. Valid grades: ${VALID_GRADES.join(", ")}`);
1955
+ }
1956
+ if (hasSubject && !isValidSubject(options.subject)) {
1957
+ throw new Error(`Invalid subject: ${options.subject}. Valid subjects: ${VALID_SUBJECTS.join(", ")}`);
1958
+ }
1959
+ if (options.include?.length) {
1960
+ for (const opt of options.include) {
1961
+ if (!VALID_MASTERY_INCLUDE_OPTIONS.includes(opt)) {
1962
+ throw new Error(`Invalid include option: ${opt}. Valid options: ${VALID_MASTERY_INCLUDE_OPTIONS.join(", ")}`);
1963
+ }
1964
+ }
1965
+ }
1966
+ return engine.user.mastery.fetch(options);
1967
+ }
1911
1968
  }
1912
1969
  };
1913
1970
  },
@@ -298,6 +298,11 @@ declare class PlaycademyClient {
298
298
  subject?: string;
299
299
  include?: ('perCourse' | 'today')[];
300
300
  }) => Promise<_playcademy_types.StudentXpResponse>;
301
+ getStudentMastery: (studentId: string, options?: {
302
+ grade?: number;
303
+ subject?: string;
304
+ include?: 'perCourse'[];
305
+ }) => Promise<_playcademy_types.StudentMasteryResponse>;
301
306
  };
302
307
  }
303
308
 
@@ -83,6 +83,33 @@ function createTimebackNamespace(client) {
83
83
  const queryString = params.toString();
84
84
  const endpoint = `/api/timeback/student-xp/${studentId}?${queryString}`;
85
85
  return client["request"](endpoint, "GET");
86
+ },
87
+ getStudentMastery: async (studentId, options) => {
88
+ const hasGrade = options?.grade !== undefined;
89
+ const hasSubject = options?.subject !== undefined;
90
+ if (hasGrade !== hasSubject) {
91
+ throw new Error("Both grade and subject must be provided together");
92
+ }
93
+ if (hasGrade && !isValidGrade(options.grade)) {
94
+ throw new Error(`Invalid grade: ${options.grade}. Valid grades: ${VALID_GRADES.join(", ")}`);
95
+ }
96
+ if (hasSubject && !isValidSubject(options.subject)) {
97
+ throw new Error(`Invalid subject: ${options.subject}. Valid subjects: ${VALID_SUBJECTS.join(", ")}`);
98
+ }
99
+ const params = new URLSearchParams;
100
+ params.set("gameId", client.gameId);
101
+ if (options?.grade !== undefined) {
102
+ params.set("grade", String(options.grade));
103
+ }
104
+ if (options?.subject) {
105
+ params.set("subject", options.subject);
106
+ }
107
+ if (options?.include?.length) {
108
+ params.set("include", options.include.join(","));
109
+ }
110
+ const queryString = params.toString();
111
+ const endpoint = `/api/timeback/student-mastery/${studentId}?${queryString}`;
112
+ return client["request"](endpoint, "GET");
86
113
  }
87
114
  };
88
115
  }
package/dist/server.d.ts CHANGED
@@ -298,6 +298,11 @@ declare class PlaycademyClient$1 {
298
298
  subject?: string;
299
299
  include?: ('perCourse' | 'today')[];
300
300
  }) => Promise<_playcademy_types.StudentXpResponse>;
301
+ getStudentMastery: (studentId: string, options?: {
302
+ grade?: number;
303
+ subject?: string;
304
+ include?: 'perCourse'[];
305
+ }) => Promise<_playcademy_types.StudentMasteryResponse>;
301
306
  };
302
307
  }
303
308
 
package/dist/server.js CHANGED
@@ -272,6 +272,33 @@ function createTimebackNamespace(client) {
272
272
  const queryString = params.toString();
273
273
  const endpoint = `/api/timeback/student-xp/${studentId}?${queryString}`;
274
274
  return client["request"](endpoint, "GET");
275
+ },
276
+ getStudentMastery: async (studentId, options) => {
277
+ const hasGrade = options?.grade !== undefined;
278
+ const hasSubject = options?.subject !== undefined;
279
+ if (hasGrade !== hasSubject) {
280
+ throw new Error("Both grade and subject must be provided together");
281
+ }
282
+ if (hasGrade && !isValidGrade(options.grade)) {
283
+ throw new Error(`Invalid grade: ${options.grade}. Valid grades: ${VALID_GRADES.join(", ")}`);
284
+ }
285
+ if (hasSubject && !isValidSubject(options.subject)) {
286
+ throw new Error(`Invalid subject: ${options.subject}. Valid subjects: ${VALID_SUBJECTS.join(", ")}`);
287
+ }
288
+ const params = new URLSearchParams;
289
+ params.set("gameId", client.gameId);
290
+ if (options?.grade !== undefined) {
291
+ params.set("grade", String(options.grade));
292
+ }
293
+ if (options?.subject) {
294
+ params.set("subject", options.subject);
295
+ }
296
+ if (options?.include?.length) {
297
+ params.set("include", options.include.join(","));
298
+ }
299
+ const queryString = params.toString();
300
+ const endpoint = `/api/timeback/student-mastery/${studentId}?${queryString}`;
301
+ return client["request"](endpoint, "GET");
275
302
  }
276
303
  };
277
304
  }
package/dist/types.d.ts CHANGED
@@ -1502,9 +1502,7 @@ interface StartActivityResult {
1502
1502
 
1503
1503
  /**
1504
1504
  * A TimeBack enrollment for the current game session.
1505
- * Alias for UserEnrollment without the optional gameId. Active enrollment IDs
1506
- * are available at `enrollment.enrollmentIds?.active` when supplied by the
1507
- * platform.
1505
+ * Alias for UserEnrollment without the optional gameId.
1508
1506
  */
1509
1507
  type TimebackEnrollment = Omit<UserEnrollment, 'gameId'>;
1510
1508
  /**
@@ -1610,6 +1608,50 @@ interface TimebackUser extends TimebackUserContext {
1610
1608
  * Call `xp.fetch()` to get XP from the server.
1611
1609
  */
1612
1610
  xp: TimebackUserXp;
1611
+ /**
1612
+ * Mastery data for the current user.
1613
+ * Call `mastery.fetch()` to get mastery progress from the server.
1614
+ */
1615
+ mastery: TimebackUserMastery;
1616
+ }
1617
+ /**
1618
+ * Mastery data access for the current user.
1619
+ * Results are cached for 5 seconds to avoid redundant network requests.
1620
+ */
1621
+ interface TimebackUserMastery {
1622
+ /**
1623
+ * Fetch mastery data from the server.
1624
+ * Returns mastery for all courses in this game, or filter by grade/subject.
1625
+ * Results are cached for 5 seconds (use `force: true` to bypass).
1626
+ *
1627
+ * @param options - Query options
1628
+ * @param options.grade - Grade level to filter (must be used with subject)
1629
+ * @param options.subject - Subject to filter (must be used with grade)
1630
+ * @param options.include - Additional data to include: 'perCourse'
1631
+ * @param options.force - Bypass cache and fetch fresh data (default: false)
1632
+ * @returns Promise resolving to mastery data
1633
+ *
1634
+ * @example
1635
+ * ```typescript
1636
+ * // Get total mastery for all game courses
1637
+ * const mastery = await client.timeback.user.mastery.fetch()
1638
+ *
1639
+ * // Get mastery for a specific grade/subject
1640
+ * const mastery = await client.timeback.user.mastery.fetch({
1641
+ * grade: 3,
1642
+ * subject: 'Math'
1643
+ * })
1644
+ *
1645
+ * // Get mastery with per-course breakdown
1646
+ * const mastery = await client.timeback.user.mastery.fetch({
1647
+ * include: ['perCourse']
1648
+ * })
1649
+ *
1650
+ * // Force fresh data
1651
+ * const mastery = await client.timeback.user.mastery.fetch({ force: true })
1652
+ * ```
1653
+ */
1654
+ fetch(options?: GetMasteryOptions): Promise<MasteryResponse>;
1613
1655
  }
1614
1656
  /**
1615
1657
  * Options for querying student XP.
@@ -1624,6 +1666,19 @@ interface GetXpOptions {
1624
1666
  /** Bypass cache and fetch fresh data (default: false) */
1625
1667
  force?: boolean;
1626
1668
  }
1669
+ /**
1670
+ * Options for querying student mastery.
1671
+ */
1672
+ interface GetMasteryOptions {
1673
+ /** Grade level to filter (must be used with subject) */
1674
+ grade?: TimebackGrade;
1675
+ /** Subject to filter (must be used with grade) */
1676
+ subject?: TimebackSubject;
1677
+ /** Additional data to include: 'perCourse' */
1678
+ include?: 'perCourse'[];
1679
+ /** Bypass cache and fetch fresh data (default: false) */
1680
+ force?: boolean;
1681
+ }
1627
1682
  /**
1628
1683
  * XP data for a single course.
1629
1684
  */
@@ -1642,6 +1697,26 @@ interface XpResponse {
1642
1697
  todayXp?: number;
1643
1698
  courses?: CourseXp[];
1644
1699
  }
1700
+ /**
1701
+ * Mastery data for a single course.
1702
+ */
1703
+ interface CourseMastery {
1704
+ grade: TimebackGrade;
1705
+ subject: TimebackSubject;
1706
+ title: string;
1707
+ masteredUnits: number;
1708
+ masterableUnits: number;
1709
+ pctComplete: number;
1710
+ isComplete: boolean;
1711
+ }
1712
+ /**
1713
+ * Response from mastery query.
1714
+ */
1715
+ interface MasteryResponse {
1716
+ totalMasteredUnits: number;
1717
+ totalMasterableUnits: number;
1718
+ courses?: CourseMastery[];
1719
+ }
1645
1720
 
1646
1721
  /**
1647
1722
  * Core client configuration and lifecycle types
@@ -2067,4 +2142,4 @@ interface AssessmentBankStatus {
2067
2142
  }
2068
2143
 
2069
2144
  export { PlaycademyClient };
2070
- export type { AssessmentBankStatus, AssessmentRow, AssessmentSummary, AuthCallbackPayload, AuthOptions, AuthProviderType, AuthResult, AuthServerMessage, AuthStateChangePayload, AuthStateUpdate, BetterAuthApiKey, BetterAuthApiKeyResponse, BetterAuthSignInResponse, BucketFile, ClientConfig, ClientEvents, CourseXp, DemoEndOptions, DemoEndPayload, DevUploadEvent, DevUploadHooks, EventListeners, ExternalGame, FetchedGame, Game, GameContextPayload, GameCustomHostname, GameInitUser, GameRow as GameRecord, GameTokenResponse, GetXpOptions, HostedGame, InitErrorPayload, InitPayload, KVKeyEntry, KVKeyMetadata, KVSeedEntry, KVStatsResponse, KeyEventPayload, LoginResponse, PlatformTimebackUser, PlatformTimebackUserContext, PlaycademyMode, PlaycademyServerClientConfig, PlaycademyServerClientState, ScoreSubmission, StartActivityOptions, StartActivityResult, TelemetryPayload, TimebackEnrollment, TimebackInitContext, TimebackOrganization, TimebackUser, TimebackUserContext, TimebackUserRefreshField, TimebackUserRefreshOptions, TimebackUserXp, TokenRefreshPayload, TokenType, UpsertGameMetadataInput, UserRow as User, XpResponse };
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/sdk",
3
- "version": "0.10.0",
3
+ "version": "0.10.1-beta.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {