@playcademy/sdk 0.16.0 → 0.16.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/README.md CHANGED
@@ -116,6 +116,44 @@ await client.timeback.startActivity(
116
116
  )
117
117
  ```
118
118
 
119
+ Hosted assessments use the same methods locally and when deployed:
120
+
121
+ ```typescript
122
+ const attempt = await client.timeback.assessments.start({
123
+ activityId: 'math-diagnostic',
124
+ purpose: 'diagnostic',
125
+ subject: 'Math',
126
+ grade: 5,
127
+ })
128
+
129
+ const saved = await client.timeback.assessments.save(attempt.attemptId, {
130
+ expectedResponseVersion: attempt.responseVersion,
131
+ responses: {
132
+ [attempt.assessment.items[0].identifier]: { RESPONSE: 'A' },
133
+ },
134
+ })
135
+
136
+ const completed = await client.timeback.assessments.submit(attempt.attemptId, {
137
+ expectedResponseVersion: saved.responseVersion,
138
+ submissionId: crypto.randomUUID(),
139
+ })
140
+ ```
141
+
142
+ `start()` resumes an unfinished attempt or selects the next live test. Save payloads are
143
+ question deltas, but each included question replaces its complete response state; use `null` to
144
+ clear an answer. Keep the returned `responseVersion` and send it with the next mutation.
145
+
146
+ For local development, import the current ordered catalog before starting the dev host:
147
+
148
+ ```bash
149
+ playcademy timeback assessments import
150
+ playcademy dev
151
+ ```
152
+
153
+ The host reads `.playcademy/timeback/assessment-fixtures.json` and keeps local attempts in memory.
154
+ Restarting the dev host resets mutable attempt state. Deployed games use TimeBack automatically;
155
+ game code never selects the provider or reads fixture content.
156
+
119
157
  ### `client.backend`
120
158
 
121
159
  HTTP client for game-specific backend functions deployed via `playcademy deploy`.
package/dist/index.d.ts CHANGED
@@ -1037,6 +1037,13 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
1037
1037
  * - `endActivity(scoreData)` - Submit activity results to TimeBack
1038
1038
  */
1039
1039
  timeback: {
1040
+ assessments: {
1041
+ start: (input: _playcademy_types.StartAssessmentInput) => Promise<_playcademy_types.AssessmentAttemptSnapshot>;
1042
+ latest: (options: _playcademy_types.GetLatestAssessmentOptions) => Promise<_playcademy_types.LatestAssessmentResult | null>;
1043
+ get: (attemptId: string) => Promise<_playcademy_types.AssessmentAttemptSnapshot>;
1044
+ save: (attemptId: string, input: _playcademy_types.SaveAssessmentInput) => Promise<_playcademy_types.AssessmentSaveResult>;
1045
+ submit: (attemptId: string, input: _playcademy_types.SubmitAssessmentInput) => Promise<_playcademy_types.AssessmentSubmitResult>;
1046
+ };
1040
1047
  readonly user: TimebackUser;
1041
1048
  readonly currentRunId: string | undefined;
1042
1049
  startActivity: (metadata: _playcademy_types.ActivityData, options?: StartActivityOptions) => StartActivityResult;
package/dist/index.js CHANGED
@@ -1118,25 +1118,12 @@ function createScoresNamespace(client) {
1118
1118
  }
1119
1119
  };
1120
1120
  }
1121
- // src/core/guards.ts
1122
- var VALID_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
1123
- var VALID_SUBJECTS = [
1124
- "Reading",
1125
- "Language",
1126
- "Vocabulary",
1127
- "Social Studies",
1128
- "Writing",
1129
- "Science",
1130
- "FastMath",
1131
- "Math",
1132
- "None"
1133
- ];
1134
- function isValidGrade(value) {
1135
- return typeof value === "number" && Number.isInteger(value) && VALID_GRADES.includes(value);
1136
- }
1137
- function isValidSubject(value) {
1138
- return typeof value === "string" && VALID_SUBJECTS.includes(value);
1139
- }
1121
+ // ../constants/src/auth.ts
1122
+ var DEFAULT_PERSONAL_API_KEY_PERMISSIONS = {
1123
+ games: ["read", "write", "delete"],
1124
+ users: ["read:self", "write:self"],
1125
+ dev: ["read", "write"]
1126
+ };
1140
1127
  // ../constants/src/platform.ts
1141
1128
  var PLAYCADEMY_BROWSER_TIME_ZONE_HEADER = "x-playcademy-browser-time-zone";
1142
1129
  // ../constants/src/timeback.ts
@@ -1147,8 +1134,22 @@ var TIMEBACK_ROUTES = {
1147
1134
  GET_HIGHEST_GRADE_MASTERED: "/integrations/timeback/highest-grade-mastered",
1148
1135
  HEARTBEAT: "/integrations/timeback/heartbeat",
1149
1136
  ADVANCE_COURSE: "/integrations/timeback/advance-course",
1150
- UNENROLL_COURSE: "/integrations/timeback/unenroll-course"
1137
+ UNENROLL_COURSE: "/integrations/timeback/unenroll-course",
1138
+ ASSESSMENTS: "/integrations/timeback/assessments"
1151
1139
  };
1140
+ var TIMEBACK_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
1141
+ var TIMEBACK_SUBJECTS = [
1142
+ "Reading",
1143
+ "Language",
1144
+ "Vocabulary",
1145
+ "Social Studies",
1146
+ "Writing",
1147
+ "Science",
1148
+ "FastMath",
1149
+ "Math",
1150
+ "None"
1151
+ ];
1152
+ var ASSESSMENT_PURPOSES = ["end_of_course", "diagnostic"];
1152
1153
  var TIMEBACK_GAME_METRIC_DECIMAL_PLACES = {
1153
1154
  xp: 1,
1154
1155
  mastery: 0,
@@ -1160,6 +1161,19 @@ var TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE = {
1160
1161
  time: 60,
1161
1162
  score: 0.5 / 10 ** TIMEBACK_GAME_METRIC_DECIMAL_PLACES.score
1162
1163
  };
1164
+ // src/core/guards.ts
1165
+ var VALID_GRADES = TIMEBACK_GRADES;
1166
+ var VALID_SUBJECTS = TIMEBACK_SUBJECTS;
1167
+ function isValidGrade(value) {
1168
+ return typeof value === "number" && Number.isInteger(value) && VALID_GRADES.includes(value);
1169
+ }
1170
+ function isValidSubject(value) {
1171
+ return typeof value === "string" && VALID_SUBJECTS.includes(value);
1172
+ }
1173
+ function isAssessmentPurpose(value) {
1174
+ return typeof value === "string" && ASSESSMENT_PURPOSES.includes(value);
1175
+ }
1176
+
1163
1177
  // src/core/cache/ttl-cache.ts
1164
1178
  function createTTLCache(options) {
1165
1179
  const cache = new Map;
@@ -2003,9 +2017,73 @@ function createTimebackEngine(client) {
2003
2017
  // src/namespaces/game/timeback.ts
2004
2018
  var VALID_XP_INCLUDE_OPTIONS = ["perCourse", "today"];
2005
2019
  var VALID_MASTERY_INCLUDE_OPTIONS = ["perCourse"];
2020
+ var ASSESSMENTS_ROUTE = TIMEBACK_ROUTES.ASSESSMENTS;
2021
+ function validateAssessmentFilters(options) {
2022
+ if (!isAssessmentPurpose(options?.purpose)) {
2023
+ throw new Error("purpose must be end_of_course or diagnostic");
2024
+ }
2025
+ if (options.grade !== undefined && !isValidGrade(options.grade)) {
2026
+ throw new Error(`Invalid grade: ${options.grade}. Valid grades: ${VALID_GRADES.join(", ")}`);
2027
+ }
2028
+ if (options.subject !== undefined && !isValidSubject(options.subject)) {
2029
+ throw new Error(`Invalid subject: ${options.subject}. Valid subjects: ${VALID_SUBJECTS.join(", ")}`);
2030
+ }
2031
+ }
2006
2032
  function createTimebackNamespace(client) {
2007
2033
  const engine = createTimebackEngine(client);
2008
2034
  return {
2035
+ assessments: {
2036
+ start: async (input) => {
2037
+ assertPlatformMode(client, "timeback.assessments.start()");
2038
+ if (!input.activityId?.trim()) {
2039
+ throw new Error("activityId is required");
2040
+ }
2041
+ validateAssessmentFilters(input);
2042
+ return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/start`, "POST", input);
2043
+ },
2044
+ latest: async (options) => {
2045
+ assertPlatformMode(client, "timeback.assessments.latest()");
2046
+ validateAssessmentFilters(options);
2047
+ const params = new URLSearchParams({ purpose: options.purpose });
2048
+ if (options.subject !== undefined) {
2049
+ params.set("subject", options.subject);
2050
+ }
2051
+ if (options.grade !== undefined) {
2052
+ params.set("grade", String(options.grade));
2053
+ }
2054
+ return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/latest?${params.toString()}`, "GET");
2055
+ },
2056
+ get: async (attemptId) => {
2057
+ assertPlatformMode(client, "timeback.assessments.get()");
2058
+ if (!attemptId?.trim()) {
2059
+ throw new Error("attemptId is required");
2060
+ }
2061
+ return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/${encodeURIComponent(attemptId)}`, "GET");
2062
+ },
2063
+ save: async (attemptId, input) => {
2064
+ assertPlatformMode(client, "timeback.assessments.save()");
2065
+ if (!attemptId?.trim()) {
2066
+ throw new Error("attemptId is required");
2067
+ }
2068
+ if (!Number.isInteger(input.expectedResponseVersion) || input.expectedResponseVersion < 0) {
2069
+ throw new Error("expectedResponseVersion must be a non-negative integer");
2070
+ }
2071
+ return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/${encodeURIComponent(attemptId)}/save`, "POST", input);
2072
+ },
2073
+ submit: async (attemptId, input) => {
2074
+ assertPlatformMode(client, "timeback.assessments.submit()");
2075
+ if (!attemptId?.trim()) {
2076
+ throw new Error("attemptId is required");
2077
+ }
2078
+ if (!Number.isInteger(input.expectedResponseVersion) || input.expectedResponseVersion < 0) {
2079
+ throw new Error("expectedResponseVersion must be a non-negative integer");
2080
+ }
2081
+ if (!input.submissionId?.trim()) {
2082
+ throw new Error("submissionId is required");
2083
+ }
2084
+ return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/${encodeURIComponent(attemptId)}/submit`, "POST", input);
2085
+ }
2086
+ },
2009
2087
  get user() {
2010
2088
  assertPlatformMode(client, "timeback.user");
2011
2089
  return {
@@ -2391,7 +2469,7 @@ async function request({
2391
2469
  return rawText && rawText.length > 0 ? rawText : undefined;
2392
2470
  }
2393
2471
  // src/version.ts
2394
- var SDK_VERSION = "0.16.0";
2472
+ var SDK_VERSION = "0.16.1-beta.2";
2395
2473
 
2396
2474
  // src/clients/base.ts
2397
2475
  class PlaycademyBaseClient {
@@ -3483,6 +3483,7 @@ declare class PlaycademyInternalClient extends PlaycademyBaseClient {
3483
3483
  reactivateCourse: (gameId: string, courseId: string) => Promise<_playcademy_types.GameTimebackIntegration>;
3484
3484
  getIntegrationConfig: (gameId: string, courseId: string) => Promise<_playcademy_types.GameTimebackIntegrationConfig>;
3485
3485
  getConfig: (gameId: string) => Promise<_playcademy_types.TimebackSetupRequest['config']>;
3486
+ exportAssessmentFixtures: (gameId: string) => Promise<_playcademy_types.AssessmentRuntimeFixtureBundle>;
3486
3487
  };
3487
3488
  students: {
3488
3489
  get: (timebackId: string, options?: TTLCacheConfig) => Promise<PlatformTimebackUserContext>;
package/dist/internal.js CHANGED
@@ -1118,25 +1118,12 @@ function createScoresNamespace(client) {
1118
1118
  }
1119
1119
  };
1120
1120
  }
1121
- // src/core/guards.ts
1122
- var VALID_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
1123
- var VALID_SUBJECTS = [
1124
- "Reading",
1125
- "Language",
1126
- "Vocabulary",
1127
- "Social Studies",
1128
- "Writing",
1129
- "Science",
1130
- "FastMath",
1131
- "Math",
1132
- "None"
1133
- ];
1134
- function isValidGrade(value) {
1135
- return typeof value === "number" && Number.isInteger(value) && VALID_GRADES.includes(value);
1136
- }
1137
- function isValidSubject(value) {
1138
- return typeof value === "string" && VALID_SUBJECTS.includes(value);
1139
- }
1121
+ // ../constants/src/auth.ts
1122
+ var DEFAULT_PERSONAL_API_KEY_PERMISSIONS = {
1123
+ games: ["read", "write", "delete"],
1124
+ users: ["read:self", "write:self"],
1125
+ dev: ["read", "write"]
1126
+ };
1140
1127
  // ../constants/src/platform.ts
1141
1128
  var PLAYCADEMY_BROWSER_TIME_ZONE_HEADER = "x-playcademy-browser-time-zone";
1142
1129
  // ../constants/src/timeback.ts
@@ -1147,8 +1134,22 @@ var TIMEBACK_ROUTES = {
1147
1134
  GET_HIGHEST_GRADE_MASTERED: "/integrations/timeback/highest-grade-mastered",
1148
1135
  HEARTBEAT: "/integrations/timeback/heartbeat",
1149
1136
  ADVANCE_COURSE: "/integrations/timeback/advance-course",
1150
- UNENROLL_COURSE: "/integrations/timeback/unenroll-course"
1137
+ UNENROLL_COURSE: "/integrations/timeback/unenroll-course",
1138
+ ASSESSMENTS: "/integrations/timeback/assessments"
1151
1139
  };
1140
+ var TIMEBACK_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
1141
+ var TIMEBACK_SUBJECTS = [
1142
+ "Reading",
1143
+ "Language",
1144
+ "Vocabulary",
1145
+ "Social Studies",
1146
+ "Writing",
1147
+ "Science",
1148
+ "FastMath",
1149
+ "Math",
1150
+ "None"
1151
+ ];
1152
+ var ASSESSMENT_PURPOSES = ["end_of_course", "diagnostic"];
1152
1153
  var TIMEBACK_GAME_METRIC_DECIMAL_PLACES = {
1153
1154
  xp: 1,
1154
1155
  mastery: 0,
@@ -1160,6 +1161,19 @@ var TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE = {
1160
1161
  time: 60,
1161
1162
  score: 0.5 / 10 ** TIMEBACK_GAME_METRIC_DECIMAL_PLACES.score
1162
1163
  };
1164
+ // src/core/guards.ts
1165
+ var VALID_GRADES = TIMEBACK_GRADES;
1166
+ var VALID_SUBJECTS = TIMEBACK_SUBJECTS;
1167
+ function isValidGrade(value) {
1168
+ return typeof value === "number" && Number.isInteger(value) && VALID_GRADES.includes(value);
1169
+ }
1170
+ function isValidSubject(value) {
1171
+ return typeof value === "string" && VALID_SUBJECTS.includes(value);
1172
+ }
1173
+ function isAssessmentPurpose(value) {
1174
+ return typeof value === "string" && ASSESSMENT_PURPOSES.includes(value);
1175
+ }
1176
+
1163
1177
  // src/core/cache/ttl-cache.ts
1164
1178
  function createTTLCache(options) {
1165
1179
  const cache = new Map;
@@ -2003,9 +2017,73 @@ function createTimebackEngine(client) {
2003
2017
  // src/namespaces/game/timeback.ts
2004
2018
  var VALID_XP_INCLUDE_OPTIONS = ["perCourse", "today"];
2005
2019
  var VALID_MASTERY_INCLUDE_OPTIONS = ["perCourse"];
2020
+ var ASSESSMENTS_ROUTE = TIMEBACK_ROUTES.ASSESSMENTS;
2021
+ function validateAssessmentFilters(options) {
2022
+ if (!isAssessmentPurpose(options?.purpose)) {
2023
+ throw new Error("purpose must be end_of_course or diagnostic");
2024
+ }
2025
+ if (options.grade !== undefined && !isValidGrade(options.grade)) {
2026
+ throw new Error(`Invalid grade: ${options.grade}. Valid grades: ${VALID_GRADES.join(", ")}`);
2027
+ }
2028
+ if (options.subject !== undefined && !isValidSubject(options.subject)) {
2029
+ throw new Error(`Invalid subject: ${options.subject}. Valid subjects: ${VALID_SUBJECTS.join(", ")}`);
2030
+ }
2031
+ }
2006
2032
  function createTimebackNamespace(client) {
2007
2033
  const engine = createTimebackEngine(client);
2008
2034
  return {
2035
+ assessments: {
2036
+ start: async (input) => {
2037
+ assertPlatformMode(client, "timeback.assessments.start()");
2038
+ if (!input.activityId?.trim()) {
2039
+ throw new Error("activityId is required");
2040
+ }
2041
+ validateAssessmentFilters(input);
2042
+ return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/start`, "POST", input);
2043
+ },
2044
+ latest: async (options) => {
2045
+ assertPlatformMode(client, "timeback.assessments.latest()");
2046
+ validateAssessmentFilters(options);
2047
+ const params = new URLSearchParams({ purpose: options.purpose });
2048
+ if (options.subject !== undefined) {
2049
+ params.set("subject", options.subject);
2050
+ }
2051
+ if (options.grade !== undefined) {
2052
+ params.set("grade", String(options.grade));
2053
+ }
2054
+ return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/latest?${params.toString()}`, "GET");
2055
+ },
2056
+ get: async (attemptId) => {
2057
+ assertPlatformMode(client, "timeback.assessments.get()");
2058
+ if (!attemptId?.trim()) {
2059
+ throw new Error("attemptId is required");
2060
+ }
2061
+ return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/${encodeURIComponent(attemptId)}`, "GET");
2062
+ },
2063
+ save: async (attemptId, input) => {
2064
+ assertPlatformMode(client, "timeback.assessments.save()");
2065
+ if (!attemptId?.trim()) {
2066
+ throw new Error("attemptId is required");
2067
+ }
2068
+ if (!Number.isInteger(input.expectedResponseVersion) || input.expectedResponseVersion < 0) {
2069
+ throw new Error("expectedResponseVersion must be a non-negative integer");
2070
+ }
2071
+ return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/${encodeURIComponent(attemptId)}/save`, "POST", input);
2072
+ },
2073
+ submit: async (attemptId, input) => {
2074
+ assertPlatformMode(client, "timeback.assessments.submit()");
2075
+ if (!attemptId?.trim()) {
2076
+ throw new Error("attemptId is required");
2077
+ }
2078
+ if (!Number.isInteger(input.expectedResponseVersion) || input.expectedResponseVersion < 0) {
2079
+ throw new Error("expectedResponseVersion must be a non-negative integer");
2080
+ }
2081
+ if (!input.submissionId?.trim()) {
2082
+ throw new Error("submissionId is required");
2083
+ }
2084
+ return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/${encodeURIComponent(attemptId)}/submit`, "POST", input);
2085
+ }
2086
+ },
2009
2087
  get user() {
2010
2088
  assertPlatformMode(client, "timeback.user");
2011
2089
  return {
@@ -2146,6 +2224,11 @@ function createAdminNamespace(client) {
2146
2224
  }
2147
2225
  };
2148
2226
  }
2227
+ // ../utils/src/error.ts
2228
+ function errorMessage(err) {
2229
+ return err instanceof Error ? err.message : String(err);
2230
+ }
2231
+
2149
2232
  // src/namespaces/platform/auth.ts
2150
2233
  function createAuthNamespace(client) {
2151
2234
  return {
@@ -2162,7 +2245,7 @@ function createAuthNamespace(client) {
2162
2245
  } catch (error) {
2163
2246
  return {
2164
2247
  success: false,
2165
- error: error instanceof Error ? error.message : "Authentication failed"
2248
+ error: errorMessage(error)
2166
2249
  };
2167
2250
  }
2168
2251
  },
@@ -2177,11 +2260,7 @@ function createAuthNamespace(client) {
2177
2260
  body: {
2178
2261
  name: options?.name || `SDK Key - ${new Date().toISOString()}`,
2179
2262
  expiresIn: options?.expiresIn !== undefined ? options.expiresIn : null,
2180
- permissions: options?.permissions || {
2181
- games: ["read", "write", "delete"],
2182
- users: ["read:self", "write:self"],
2183
- dev: ["read", "write"]
2184
- }
2263
+ permissions: options?.permissions || DEFAULT_PERSONAL_API_KEY_PERMISSIONS
2185
2264
  }
2186
2265
  }),
2187
2266
  list: async () => client["request"]("/auth/api-key/list", "GET"),
@@ -2914,7 +2993,8 @@ function createTimebackNamespace2(client) {
2914
2993
  deactivateCourse: (gameId, courseId) => client["request"](`/timeback/integrations/${gameId}/${courseId}`, "DELETE"),
2915
2994
  reactivateCourse: (gameId, courseId) => client["request"](`/timeback/integrations/${gameId}/${courseId}/reactivate`, "POST"),
2916
2995
  getIntegrationConfig: (gameId, courseId) => client["request"](`/timeback/integrations/${gameId}/${courseId}`, "GET"),
2917
- getConfig: (gameId) => client["request"](`/timeback/config/${gameId}`, "GET")
2996
+ getConfig: (gameId) => client["request"](`/timeback/config/${gameId}`, "GET"),
2997
+ exportAssessmentFixtures: (gameId) => client["request"](`/timeback/assessments/fixtures/${gameId}`, "GET")
2918
2998
  },
2919
2999
  students: {
2920
3000
  get: async (timebackId, options) => studentCache.get(timebackId, async () => client["request"](`/timeback/user/${timebackId}`, "GET"), options),
@@ -3239,7 +3319,7 @@ async function request({
3239
3319
  return rawText && rawText.length > 0 ? rawText : undefined;
3240
3320
  }
3241
3321
  // src/version.ts
3242
- var SDK_VERSION = "0.16.0";
3322
+ var SDK_VERSION = "0.16.1-beta.2";
3243
3323
 
3244
3324
  // src/clients/base.ts
3245
3325
  class PlaycademyBaseClient {
@@ -346,6 +346,15 @@ declare class PlaycademyClient {
346
346
  get config(): PlaycademyServerClientState['config'];
347
347
  /** TimeBack integration methods (endActivity) */
348
348
  timeback: {
349
+ assessments: {
350
+ start: (studentId: string, input: _playcademy_types.StartAssessmentInput) => Promise<_playcademy_types.AssessmentAttemptSnapshot>;
351
+ latest: (studentId: string, options: _playcademy_types.GetLatestAssessmentOptions) => Promise<_playcademy_types.LatestAssessmentResult | null>;
352
+ get: (studentId: string, attemptId: string) => Promise<_playcademy_types.AssessmentAttemptSnapshot>;
353
+ save: (studentId: string, attemptId: string, input: _playcademy_types.SaveAssessmentInput) => Promise<_playcademy_types.AssessmentSaveResult>;
354
+ submit: (studentId: string, attemptId: string, input: _playcademy_types.SubmitAssessmentInput, context: {
355
+ sensorUrl: string;
356
+ }) => Promise<_playcademy_types.AssessmentSubmitResult>;
357
+ };
349
358
  endActivity: (studentId: string, payload: _playcademy_types.EndActivityPayload) => Promise<_playcademy_types.EndActivityResponse>;
350
359
  getStudentXp: (studentId: string, options?: {
351
360
  grade?: number;
@@ -10,9 +10,27 @@ var __export = (target, all) => {
10
10
  };
11
11
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
12
12
 
13
- // src/core/guards.ts
14
- var VALID_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
15
- var VALID_SUBJECTS = [
13
+ // ../constants/src/auth.ts
14
+ var DEFAULT_PERSONAL_API_KEY_PERMISSIONS = {
15
+ games: ["read", "write", "delete"],
16
+ users: ["read:self", "write:self"],
17
+ dev: ["read", "write"]
18
+ };
19
+ // ../constants/src/platform.ts
20
+ var PLAYCADEMY_BROWSER_TIME_ZONE_HEADER = "x-playcademy-browser-time-zone";
21
+ // ../constants/src/timeback.ts
22
+ var TIMEBACK_ROUTES = {
23
+ END_ACTIVITY: "/integrations/timeback/end-activity",
24
+ GET_XP: "/integrations/timeback/xp",
25
+ GET_MASTERY: "/integrations/timeback/mastery",
26
+ GET_HIGHEST_GRADE_MASTERED: "/integrations/timeback/highest-grade-mastered",
27
+ HEARTBEAT: "/integrations/timeback/heartbeat",
28
+ ADVANCE_COURSE: "/integrations/timeback/advance-course",
29
+ UNENROLL_COURSE: "/integrations/timeback/unenroll-course",
30
+ ASSESSMENTS: "/integrations/timeback/assessments"
31
+ };
32
+ var TIMEBACK_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
33
+ var TIMEBACK_SUBJECTS = [
16
34
  "Reading",
17
35
  "Language",
18
36
  "Vocabulary",
@@ -23,12 +41,30 @@ var VALID_SUBJECTS = [
23
41
  "Math",
24
42
  "None"
25
43
  ];
44
+ var ASSESSMENT_PURPOSES = ["end_of_course", "diagnostic"];
45
+ var TIMEBACK_GAME_METRIC_DECIMAL_PLACES = {
46
+ xp: 1,
47
+ mastery: 0,
48
+ score: 2
49
+ };
50
+ var TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE = {
51
+ xp: 0.5 / 10 ** TIMEBACK_GAME_METRIC_DECIMAL_PLACES.xp,
52
+ mastery: 0,
53
+ time: 60,
54
+ score: 0.5 / 10 ** TIMEBACK_GAME_METRIC_DECIMAL_PLACES.score
55
+ };
56
+ // src/core/guards.ts
57
+ var VALID_GRADES = TIMEBACK_GRADES;
58
+ var VALID_SUBJECTS = TIMEBACK_SUBJECTS;
26
59
  function isValidGrade(value) {
27
60
  return typeof value === "number" && Number.isInteger(value) && VALID_GRADES.includes(value);
28
61
  }
29
62
  function isValidSubject(value) {
30
63
  return typeof value === "string" && VALID_SUBJECTS.includes(value);
31
64
  }
65
+ function isAssessmentPurpose(value) {
66
+ return typeof value === "string" && ASSESSMENT_PURPOSES.includes(value);
67
+ }
32
68
  // src/server/namespaces/timeback.ts
33
69
  function createTimebackNamespace(client) {
34
70
  function enrichActivityData(data) {
@@ -38,6 +74,35 @@ function createTimebackNamespace(client) {
38
74
  };
39
75
  }
40
76
  return {
77
+ assessments: {
78
+ start: (studentId, input) => client["request"]("/api/timeback/assessments/start", "POST", { ...input, gameId: client.gameId, studentId }),
79
+ latest: (studentId, options) => {
80
+ const params = new URLSearchParams({
81
+ gameId: client.gameId,
82
+ studentId,
83
+ purpose: options.purpose
84
+ });
85
+ if (options.subject !== undefined) {
86
+ params.set("subject", options.subject);
87
+ }
88
+ if (options.grade !== undefined) {
89
+ params.set("grade", String(options.grade));
90
+ }
91
+ return client["request"](`/api/timeback/assessments/latest?${params.toString()}`, "GET");
92
+ },
93
+ get: (studentId, attemptId) => {
94
+ const params = new URLSearchParams({ gameId: client.gameId, studentId });
95
+ return client["request"](`/api/timeback/assessments/${encodeURIComponent(attemptId)}?${params.toString()}`, "GET");
96
+ },
97
+ save: (studentId, attemptId, input) => client["request"](`/api/timeback/assessments/${encodeURIComponent(attemptId)}/save`, "POST", { ...input, gameId: client.gameId, studentId }),
98
+ submit: (studentId, attemptId, input, context) => client["request"](`/api/timeback/assessments/${encodeURIComponent(attemptId)}/submit`, "POST", {
99
+ ...input,
100
+ gameId: client.gameId,
101
+ studentId,
102
+ appName: client.config.name,
103
+ sensorUrl: context.sensorUrl
104
+ })
105
+ },
41
106
  endActivity: async (studentId, payload) => {
42
107
  if (!isValidGrade(payload.activityData.grade)) {
43
108
  throw new Error("activityData.grade must be a valid grade level (-1 to 13)");
@@ -234,7 +299,7 @@ function extractApiErrorInfo(error) {
234
299
  }
235
300
 
236
301
  // src/version.ts
237
- var SDK_VERSION = "0.16.0";
302
+ var SDK_VERSION = "0.16.1-beta.2";
238
303
 
239
304
  // src/server/request.ts
240
305
  async function makeApiRequest(opts) {
package/dist/server.d.ts CHANGED
@@ -346,6 +346,15 @@ declare class PlaycademyClient$1 {
346
346
  get config(): PlaycademyServerClientState['config'];
347
347
  /** TimeBack integration methods (endActivity) */
348
348
  timeback: {
349
+ assessments: {
350
+ start: (studentId: string, input: _playcademy_types.StartAssessmentInput) => Promise<_playcademy_types.AssessmentAttemptSnapshot>;
351
+ latest: (studentId: string, options: _playcademy_types.GetLatestAssessmentOptions) => Promise<_playcademy_types.LatestAssessmentResult | null>;
352
+ get: (studentId: string, attemptId: string) => Promise<_playcademy_types.AssessmentAttemptSnapshot>;
353
+ save: (studentId: string, attemptId: string, input: _playcademy_types.SaveAssessmentInput) => Promise<_playcademy_types.AssessmentSaveResult>;
354
+ submit: (studentId: string, attemptId: string, input: _playcademy_types.SubmitAssessmentInput, context: {
355
+ sensorUrl: string;
356
+ }) => Promise<_playcademy_types.AssessmentSubmitResult>;
357
+ };
349
358
  endActivity: (studentId: string, payload: _playcademy_types.EndActivityPayload) => Promise<_playcademy_types.EndActivityResponse>;
350
359
  getStudentXp: (studentId: string, options?: {
351
360
  grade?: number;
package/dist/server.js CHANGED
@@ -199,9 +199,27 @@ var init_config_loader = __esm(() => {
199
199
  init_file_loader();
200
200
  });
201
201
 
202
- // src/core/guards.ts
203
- var VALID_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
204
- var VALID_SUBJECTS = [
202
+ // ../constants/src/auth.ts
203
+ var DEFAULT_PERSONAL_API_KEY_PERMISSIONS = {
204
+ games: ["read", "write", "delete"],
205
+ users: ["read:self", "write:self"],
206
+ dev: ["read", "write"]
207
+ };
208
+ // ../constants/src/platform.ts
209
+ var PLAYCADEMY_BROWSER_TIME_ZONE_HEADER = "x-playcademy-browser-time-zone";
210
+ // ../constants/src/timeback.ts
211
+ var TIMEBACK_ROUTES = {
212
+ END_ACTIVITY: "/integrations/timeback/end-activity",
213
+ GET_XP: "/integrations/timeback/xp",
214
+ GET_MASTERY: "/integrations/timeback/mastery",
215
+ GET_HIGHEST_GRADE_MASTERED: "/integrations/timeback/highest-grade-mastered",
216
+ HEARTBEAT: "/integrations/timeback/heartbeat",
217
+ ADVANCE_COURSE: "/integrations/timeback/advance-course",
218
+ UNENROLL_COURSE: "/integrations/timeback/unenroll-course",
219
+ ASSESSMENTS: "/integrations/timeback/assessments"
220
+ };
221
+ var TIMEBACK_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
222
+ var TIMEBACK_SUBJECTS = [
205
223
  "Reading",
206
224
  "Language",
207
225
  "Vocabulary",
@@ -212,12 +230,30 @@ var VALID_SUBJECTS = [
212
230
  "Math",
213
231
  "None"
214
232
  ];
233
+ var ASSESSMENT_PURPOSES = ["end_of_course", "diagnostic"];
234
+ var TIMEBACK_GAME_METRIC_DECIMAL_PLACES = {
235
+ xp: 1,
236
+ mastery: 0,
237
+ score: 2
238
+ };
239
+ var TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE = {
240
+ xp: 0.5 / 10 ** TIMEBACK_GAME_METRIC_DECIMAL_PLACES.xp,
241
+ mastery: 0,
242
+ time: 60,
243
+ score: 0.5 / 10 ** TIMEBACK_GAME_METRIC_DECIMAL_PLACES.score
244
+ };
245
+ // src/core/guards.ts
246
+ var VALID_GRADES = TIMEBACK_GRADES;
247
+ var VALID_SUBJECTS = TIMEBACK_SUBJECTS;
215
248
  function isValidGrade(value) {
216
249
  return typeof value === "number" && Number.isInteger(value) && VALID_GRADES.includes(value);
217
250
  }
218
251
  function isValidSubject(value) {
219
252
  return typeof value === "string" && VALID_SUBJECTS.includes(value);
220
253
  }
254
+ function isAssessmentPurpose(value) {
255
+ return typeof value === "string" && ASSESSMENT_PURPOSES.includes(value);
256
+ }
221
257
  // src/server/namespaces/timeback.ts
222
258
  function createTimebackNamespace(client) {
223
259
  function enrichActivityData(data) {
@@ -227,6 +263,35 @@ function createTimebackNamespace(client) {
227
263
  };
228
264
  }
229
265
  return {
266
+ assessments: {
267
+ start: (studentId, input) => client["request"]("/api/timeback/assessments/start", "POST", { ...input, gameId: client.gameId, studentId }),
268
+ latest: (studentId, options) => {
269
+ const params = new URLSearchParams({
270
+ gameId: client.gameId,
271
+ studentId,
272
+ purpose: options.purpose
273
+ });
274
+ if (options.subject !== undefined) {
275
+ params.set("subject", options.subject);
276
+ }
277
+ if (options.grade !== undefined) {
278
+ params.set("grade", String(options.grade));
279
+ }
280
+ return client["request"](`/api/timeback/assessments/latest?${params.toString()}`, "GET");
281
+ },
282
+ get: (studentId, attemptId) => {
283
+ const params = new URLSearchParams({ gameId: client.gameId, studentId });
284
+ return client["request"](`/api/timeback/assessments/${encodeURIComponent(attemptId)}?${params.toString()}`, "GET");
285
+ },
286
+ save: (studentId, attemptId, input) => client["request"](`/api/timeback/assessments/${encodeURIComponent(attemptId)}/save`, "POST", { ...input, gameId: client.gameId, studentId }),
287
+ submit: (studentId, attemptId, input, context) => client["request"](`/api/timeback/assessments/${encodeURIComponent(attemptId)}/submit`, "POST", {
288
+ ...input,
289
+ gameId: client.gameId,
290
+ studentId,
291
+ appName: client.config.name,
292
+ sensorUrl: context.sensorUrl
293
+ })
294
+ },
230
295
  endActivity: async (studentId, payload) => {
231
296
  if (!isValidGrade(payload.activityData.grade)) {
232
297
  throw new Error("activityData.grade must be a valid grade level (-1 to 13)");
@@ -423,7 +488,7 @@ function extractApiErrorInfo(error) {
423
488
  }
424
489
 
425
490
  // src/version.ts
426
- var SDK_VERSION = "0.16.0";
491
+ var SDK_VERSION = "0.16.1-beta.2";
427
492
 
428
493
  // src/server/request.ts
429
494
  async function makeApiRequest(opts) {
package/dist/types.d.ts CHANGED
@@ -1465,6 +1465,13 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
1465
1465
  * - `endActivity(scoreData)` - Submit activity results to TimeBack
1466
1466
  */
1467
1467
  timeback: {
1468
+ assessments: {
1469
+ start: (input: _playcademy_types.StartAssessmentInput) => Promise<_playcademy_types.AssessmentAttemptSnapshot>;
1470
+ latest: (options: _playcademy_types.GetLatestAssessmentOptions) => Promise<_playcademy_types.LatestAssessmentResult | null>;
1471
+ get: (attemptId: string) => Promise<_playcademy_types.AssessmentAttemptSnapshot>;
1472
+ save: (attemptId: string, input: _playcademy_types.SaveAssessmentInput) => Promise<_playcademy_types.AssessmentSaveResult>;
1473
+ submit: (attemptId: string, input: _playcademy_types.SubmitAssessmentInput) => Promise<_playcademy_types.AssessmentSubmitResult>;
1474
+ };
1468
1475
  readonly user: TimebackUser;
1469
1476
  readonly currentRunId: string | undefined;
1470
1477
  startActivity: (metadata: _playcademy_types.ActivityData, options?: StartActivityOptions) => StartActivityResult;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/sdk",
3
- "version": "0.16.0",
3
+ "version": "0.16.1-beta.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {