@playcademy/sandbox 0.3.17-beta.24 → 0.3.17-beta.26

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.
Files changed (3) hide show
  1. package/dist/cli.js +140 -110
  2. package/dist/server.js +140 -110
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1330,7 +1330,7 @@ var package_default;
1330
1330
  var init_package = __esm(() => {
1331
1331
  package_default = {
1332
1332
  name: "@playcademy/sandbox",
1333
- version: "0.3.17-beta.24",
1333
+ version: "0.3.17-beta.26",
1334
1334
  description: "Local development server for Playcademy game development",
1335
1335
  type: "module",
1336
1336
  exports: {
@@ -30268,8 +30268,17 @@ function kebabToTitleCase(kebabStr) {
30268
30268
  return kebabStr.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
30269
30269
  }
30270
30270
  // ../utils/src/timezone.ts
30271
- function formatDateYMD(date3 = new Date) {
30272
- return date3.toISOString().split("T")[0];
30271
+ function formatDateYMDInTimezone(timeZone, date3 = new Date) {
30272
+ const parts2 = new Intl.DateTimeFormat("en-US", {
30273
+ timeZone,
30274
+ year: "numeric",
30275
+ month: "2-digit",
30276
+ day: "2-digit"
30277
+ }).formatToParts(date3);
30278
+ const y = parts2.find((p) => p.type === "year").value;
30279
+ const m = parts2.find((p) => p.type === "month").value;
30280
+ const d = parts2.find((p) => p.type === "day").value;
30281
+ return `${y}-${m}-${d}`;
30273
30282
  }
30274
30283
  function getUtcInstantForMidnight(date3, timeZone) {
30275
30284
  const parts2 = new Intl.DateTimeFormat("en-US", {
@@ -30340,6 +30349,19 @@ function getDayBoundariesInTimezone(date3, timezone) {
30340
30349
  const endOfDay = getUtcInstantForMidnight(nextDayNoon, timezone);
30341
30350
  return { startOfDay, endOfDay };
30342
30351
  }
30352
+ // ../utils/src/url.ts
30353
+ function buildPath(path, params) {
30354
+ const url = new URL(path, "http://n");
30355
+ if (params) {
30356
+ for (const [key, value] of Object.entries(params)) {
30357
+ if (value != null) {
30358
+ url.searchParams.set(key, value);
30359
+ }
30360
+ }
30361
+ }
30362
+ return `${url.pathname}${url.search}`;
30363
+ }
30364
+
30343
30365
  // ../utils/src/pure/index.ts
30344
30366
  var init_pure = __esm(() => {
30345
30367
  init_uuid2();
@@ -30392,11 +30414,11 @@ function isRecord2(value) {
30392
30414
  function getStringValue(value) {
30393
30415
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
30394
30416
  }
30395
- function parseSourcedIdFromUrl(url) {
30396
- if (!url) {
30417
+ function parseSourcedIdFromUrl(url2) {
30418
+ if (!url2) {
30397
30419
  return;
30398
30420
  }
30399
- const trimmed = url.trim().replace(/\/$/, "");
30421
+ const trimmed = url2.trim().replace(/\/$/, "");
30400
30422
  if (!trimmed) {
30401
30423
  return;
30402
30424
  }
@@ -30785,7 +30807,7 @@ class TimebackAdminService {
30785
30807
  history: []
30786
30808
  };
30787
30809
  }
30788
- const today = formatDateYMD();
30810
+ const today = formatDateYMDInTimezone(PLATFORM_TIMEZONE);
30789
30811
  const history = [];
30790
30812
  let totalXpRaw = 0;
30791
30813
  let todayXpRaw = 0;
@@ -30951,7 +30973,9 @@ class TimebackAdminService {
30951
30973
  const uniqueEnrollmentIds = [...new Set(enrollmentIds)];
30952
30974
  const results = await TimebackAdminService.runWithConcurrency(uniqueEnrollmentIds, TimebackAdminService.ANALYTICS_CONCURRENCY, async (enrollmentId) => {
30953
30975
  try {
30954
- const analytics = await client.edubridge.analytics.getEnrollmentFacts(enrollmentId);
30976
+ const analytics = await client.edubridge.analytics.getEnrollmentFacts(enrollmentId, {
30977
+ timezone: PLATFORM_TIMEZONE
30978
+ });
30955
30979
  return [enrollmentId, this.summarizeAnalyticsFacts(analytics.facts)];
30956
30980
  } catch (error) {
30957
30981
  logger16.warn("Failed to load enrollment analytics summary", {
@@ -31225,7 +31249,7 @@ class TimebackAdminService {
31225
31249
  const enrollment = await this.assertStudentEnrolledInCourse(client, data.studentId, data.courseId);
31226
31250
  let currentMastered = 0;
31227
31251
  try {
31228
- const analytics = await client.edubridge.analytics.getEnrollmentFacts(enrollment.id);
31252
+ const analytics = await client.edubridge.analytics.getEnrollmentFacts(enrollment.id, { timezone: PLATFORM_TIMEZONE });
31229
31253
  const summary = this.summarizeAnalyticsFacts(analytics.facts);
31230
31254
  currentMastered = summary.masteredUnits;
31231
31255
  } catch {
@@ -31415,6 +31439,7 @@ class TimebackAdminService {
31415
31439
  var logger16;
31416
31440
  var init_timeback_admin_service = __esm(() => {
31417
31441
  init_drizzle_orm();
31442
+ init_src();
31418
31443
  init_tables_index();
31419
31444
  init_src2();
31420
31445
  init_constants4();
@@ -33372,13 +33397,13 @@ var init_events = () => {};
33372
33397
 
33373
33398
  // ../api-core/src/services/notification.service.ts
33374
33399
  function convertWebSocketUrlToHttp(wsUrl) {
33375
- return wsUrl.replace(/^wss?:\/\//, (url) => url === "wss://" ? "https://" : "http://");
33400
+ return wsUrl.replace(/^wss?:\/\//, (url2) => url2 === "wss://" ? "https://" : "http://");
33376
33401
  }
33377
33402
  async function publishToUser(baseUrl, secret, userId, type, payload) {
33378
- const url = new URL(baseUrl);
33379
- url.pathname = "/publish";
33403
+ const url2 = new URL(baseUrl);
33404
+ url2.pathname = "/publish";
33380
33405
  try {
33381
- const res = await fetch(url.toString(), {
33406
+ const res = await fetch(url2.toString(), {
33382
33407
  method: "POST",
33383
33408
  headers: { "content-type": "application/json" },
33384
33409
  body: JSON.stringify({ userId, type, payload, secret })
@@ -35230,14 +35255,14 @@ function debugSensitive(label, value, showChars = 4) {
35230
35255
  const masked = value.length > showChars * 2 ? `${value.slice(0, showChars)}...${value.slice(-showChars)}` : `${value.slice(0, showChars)}...`;
35231
35256
  console.log(`[DEBUG] ${label}: ${masked}`);
35232
35257
  }
35233
- function debugRequest(method, url, headers, body2) {
35258
+ function debugRequest(method, url2, headers, body2) {
35234
35259
  if (!isDebugEnabled()) {
35235
35260
  return;
35236
35261
  }
35237
35262
  console.log(`
35238
35263
  [DEBUG] HTTP Request:`);
35239
35264
  console.log(` Method: ${method}`);
35240
- console.log(` URL: ${url}`);
35265
+ console.log(` URL: ${url2}`);
35241
35266
  if (headers) {
35242
35267
  console.log(` Headers:`);
35243
35268
  Object.entries(headers).forEach(([key, value]) => {
@@ -35310,7 +35335,7 @@ async function getTimebackTokenResponse(config2) {
35310
35335
  function getAuthUrl(environment = "production") {
35311
35336
  return TIMEBACK_AUTH_URLS4[environment];
35312
35337
  }
35313
- function handleHttpError(res, errorBody, attempt, retries, url) {
35338
+ function handleHttpError(res, errorBody, attempt, retries, url2) {
35314
35339
  const error = new TimebackApiError(res.status, res.statusText, errorBody);
35315
35340
  if (res.status >= HTTP_STATUS4.CLIENT_ERROR_MIN && res.status < HTTP_STATUS4.CLIENT_ERROR_MAX) {
35316
35341
  throw error;
@@ -35321,7 +35346,7 @@ function handleHttpError(res, errorBody, attempt, retries, url) {
35321
35346
  attempt: attempt + 1,
35322
35347
  maxRetries: retries,
35323
35348
  status: res.status,
35324
- url
35349
+ url: url2
35325
35350
  });
35326
35351
  return { retry: true, error };
35327
35352
  }
@@ -35352,7 +35377,7 @@ async function request({
35352
35377
  retries = 3,
35353
35378
  timeout = 1e4
35354
35379
  }) {
35355
- const url = baseUrl.replace(/\/$/, "") + (path.startsWith("/") ? path : `/${path}`);
35380
+ const url2 = baseUrl.replace(/\/$/, "") + (path.startsWith("/") ? path : `/${path}`);
35356
35381
  const headers = { ...extraHeaders };
35357
35382
  let payload;
35358
35383
  if (body2 instanceof FormData) {
@@ -35369,8 +35394,8 @@ async function request({
35369
35394
  try {
35370
35395
  const controller = new AbortController;
35371
35396
  const timeoutId = setTimeout(() => controller.abort(), timeout);
35372
- debugRequest(method, url, headers, body2);
35373
- const res = await fetch(url, {
35397
+ debugRequest(method, url2, headers, body2);
35398
+ const res = await fetch(url2, {
35374
35399
  method,
35375
35400
  headers,
35376
35401
  body: payload,
@@ -35386,7 +35411,7 @@ async function request({
35386
35411
  if (errorBody) {
35387
35412
  debugResponse(res.status, res.statusText, errorBody);
35388
35413
  }
35389
- const result = handleHttpError(res, errorBody, attempt, retries, url);
35414
+ const result = handleHttpError(res, errorBody, attempt, retries, url2);
35390
35415
  lastError = result.error;
35391
35416
  if (result.retry) {
35392
35417
  const delay = HTTP_DEFAULTS4.retryBackoffBase ** attempt * 1000;
@@ -35406,7 +35431,7 @@ async function request({
35406
35431
  attempt: attempt + 1,
35407
35432
  maxRetries: retries,
35408
35433
  error: lastError.message,
35409
- url
35434
+ url: url2
35410
35435
  });
35411
35436
  await new Promise((resolve) => setTimeout(resolve, delay));
35412
35437
  }
@@ -35645,7 +35670,9 @@ function createEduBridgeNamespace(client) {
35645
35670
  }
35646
35671
  };
35647
35672
  const analytics = {
35648
- getEnrollmentFacts: async (enrollmentId) => client["request"](`/edubridge/analytics/enrollment/${enrollmentId}`, "GET")
35673
+ getEnrollmentFacts: async (enrollmentId, options) => client["request"](buildPath(`/edubridge/analytics/enrollment/${enrollmentId}`, {
35674
+ timezone: options?.timezone
35675
+ }), "GET")
35649
35676
  };
35650
35677
  return {
35651
35678
  enrollments,
@@ -35736,8 +35763,8 @@ function createOneRosterNamespace(client) {
35736
35763
  queryParams.set("offset", String(options.offset));
35737
35764
  }
35738
35765
  const endpoint = `${ONEROSTER_ENDPOINTS4.users}/${userSourcedId}/classes`;
35739
- const url = queryParams.toString() ? `${endpoint}?${queryParams}` : endpoint;
35740
- const res = await client["request"](url, "GET");
35766
+ const url2 = queryParams.toString() ? `${endpoint}?${queryParams}` : endpoint;
35767
+ const res = await client["request"](url2, "GET");
35741
35768
  return res.classes || [];
35742
35769
  }
35743
35770
  },
@@ -35755,9 +35782,9 @@ function createOneRosterNamespace(client) {
35755
35782
  if (options?.offset) {
35756
35783
  queryParams.set("offset", String(options.offset));
35757
35784
  }
35758
- const url = `${ONEROSTER_ENDPOINTS4.enrollments}?${queryParams}`;
35785
+ const url2 = `${ONEROSTER_ENDPOINTS4.enrollments}?${queryParams}`;
35759
35786
  try {
35760
- const response = await client["request"](url, "GET");
35787
+ const response = await client["request"](url2, "GET");
35761
35788
  return response.enrollments || [];
35762
35789
  } catch (error) {
35763
35790
  logTimebackError("list enrollments for class", error, { classSourcedId });
@@ -35788,8 +35815,8 @@ function createOneRosterNamespace(client) {
35788
35815
  }
35789
35816
  queryParams.set("filter", filters.join(" AND "));
35790
35817
  queryParams.set("limit", "3000");
35791
- const url = `${ONEROSTER_ENDPOINTS4.enrollments}?${queryParams}`;
35792
- const response = await client["request"](url, "GET");
35818
+ const url2 = `${ONEROSTER_ENDPOINTS4.enrollments}?${queryParams}`;
35819
+ const response = await client["request"](url2, "GET");
35793
35820
  return response.enrollments || [];
35794
35821
  }));
35795
35822
  const enrollments = enrollmentGroups.flat();
@@ -35954,8 +35981,8 @@ function createOneRosterNamespace(client) {
35954
35981
  getAttemptStats: async (studentId, lineItemId) => {
35955
35982
  try {
35956
35983
  const filter = `student.sourcedId='${studentId}' AND assessmentLineItem.sourcedId='${lineItemId}'`;
35957
- const url = `${ONEROSTER_ENDPOINTS4.assessmentResults}?filter=${encodeURIComponent(filter)}`;
35958
- const response = await client["request"](url, "GET");
35984
+ const url2 = `${ONEROSTER_ENDPOINTS4.assessmentResults}?filter=${encodeURIComponent(filter)}`;
35985
+ const response = await client["request"](url2, "GET");
35959
35986
  const results = response.assessmentResults || [];
35960
35987
  const firstResult = results[0];
35961
35988
  if (!firstResult) {
@@ -36561,7 +36588,7 @@ class MasteryTracker {
36561
36588
  });
36562
36589
  return;
36563
36590
  }
36564
- const analytics = await this.edubridgeNamespace.analytics.getEnrollmentFacts(enrollment.id);
36591
+ const analytics = await this.edubridgeNamespace.analytics.getEnrollmentFacts(enrollment.id, { timezone: PLATFORM_TIMEZONE });
36565
36592
  return analytics.facts;
36566
36593
  } catch (error) {
36567
36594
  log.error("[MasteryTracker] Failed to load enrollment analytics facts", {
@@ -37284,7 +37311,7 @@ class TimebackClient {
37284
37311
  }
37285
37312
  const analyticsResults = await Promise.all(filteredEnrollments.map(async (enrollment) => {
37286
37313
  try {
37287
- const analytics = await this.edubridge.analytics.getEnrollmentFacts(enrollment.id);
37314
+ const analytics = await this.edubridge.analytics.getEnrollmentFacts(enrollment.id, { timezone: PLATFORM_TIMEZONE });
37288
37315
  return { enrollment, analytics };
37289
37316
  } catch (error) {
37290
37317
  log.warn("[TimebackClient] Failed to fetch analytics for enrollment", {
@@ -37294,7 +37321,7 @@ class TimebackClient {
37294
37321
  return { enrollment, analytics: null };
37295
37322
  }
37296
37323
  }));
37297
- const today = formatDateYMD();
37324
+ const today = formatDateYMDInTimezone(PLATFORM_TIMEZONE);
37298
37325
  let totalXp = 0;
37299
37326
  let todayXp = 0;
37300
37327
  const courses = [];
@@ -37393,6 +37420,7 @@ var __defProp2, __export2 = (target, all) => {
37393
37420
  });
37394
37421
  }, __esm5 = (fn, res) => () => (fn && (res = fn(fn = 0)), res), TIMEBACK_API_URLS4, TIMEBACK_AUTH_URLS4, CALIPER_API_URLS4, ONEROSTER_ENDPOINTS4, CALIPER_ENDPOINTS4, CALIPER_CONSTANTS4, TIMEBACK_EVENT_TYPES4, TIMEBACK_ACTIONS4, TIMEBACK_TYPES4, ACTIVITY_METRIC_TYPES4, TIME_METRIC_TYPES4, TIMEBACK_SUBJECTS4, TIMEBACK_GRADE_LEVELS4, TIMEBACK_GRADE_LEVEL_LABELS4, CALIPER_SUBJECTS4, ONEROSTER_STATUS4, SCORE_STATUS4, ENV_VARS4, HTTP_DEFAULTS4, AUTH_DEFAULTS4, CACHE_DEFAULTS4, CONFIG_DEFAULTS4, PLAYCADEMY_DEFAULTS4, RESOURCE_DEFAULTS4, HTTP_STATUS4, ERROR_NAMES4, init_constants8, exports_verify, init_verify, TimebackError, TimebackApiError, TimebackAuthenticationError, StudentNotFoundError, ConfigurationError, ResourceNotFoundError, SUBJECT_VALUES2, GRADE_VALUES2, TimebackAuthError, PERFECT_ACCURACY_THRESHOLD = 0.999999, EmailSchema, StudentSourcedIdSchema, StudentIdentifierSchema;
37395
37422
  var init_dist3 = __esm(() => {
37423
+ init_src();
37396
37424
  init_src();
37397
37425
  init_src2();
37398
37426
  init_src4();
@@ -37407,9 +37435,11 @@ var init_dist3 = __esm(() => {
37407
37435
  init_src2();
37408
37436
  init_src2();
37409
37437
  init_src2();
37438
+ init_src4();
37410
37439
  init_src2();
37411
37440
  init_src2();
37412
37441
  init_src2();
37442
+ init_src();
37413
37443
  init_src2();
37414
37444
  init_src2();
37415
37445
  init_src2();
@@ -38262,20 +38292,20 @@ var splitPath = (path) => {
38262
38292
  });
38263
38293
  }
38264
38294
  }, tryDecodeURI = (str) => tryDecode(str, decodeURI), getPath = (request2) => {
38265
- const url = request2.url;
38266
- const start2 = url.indexOf("/", url.indexOf(":") + 4);
38295
+ const url2 = request2.url;
38296
+ const start2 = url2.indexOf("/", url2.indexOf(":") + 4);
38267
38297
  let i2 = start2;
38268
- for (;i2 < url.length; i2++) {
38269
- const charCode = url.charCodeAt(i2);
38298
+ for (;i2 < url2.length; i2++) {
38299
+ const charCode = url2.charCodeAt(i2);
38270
38300
  if (charCode === 37) {
38271
- const queryIndex = url.indexOf("?", i2);
38272
- const path = url.slice(start2, queryIndex === -1 ? undefined : queryIndex);
38301
+ const queryIndex = url2.indexOf("?", i2);
38302
+ const path = url2.slice(start2, queryIndex === -1 ? undefined : queryIndex);
38273
38303
  return tryDecodeURI(path.includes("%25") ? path.replace(/%25/g, "%2525") : path);
38274
38304
  } else if (charCode === 63) {
38275
38305
  break;
38276
38306
  }
38277
38307
  }
38278
- return url.slice(start2, i2);
38308
+ return url2.slice(start2, i2);
38279
38309
  }, getPathNoStrict = (request2) => {
38280
38310
  const result = getPath(request2);
38281
38311
  return result.length > 1 && result.at(-1) === "/" ? result.slice(0, -1) : result;
@@ -38318,42 +38348,42 @@ var splitPath = (path) => {
38318
38348
  value = value.replace(/\+/g, " ");
38319
38349
  }
38320
38350
  return value.indexOf("%") !== -1 ? tryDecode(value, decodeURIComponent_) : value;
38321
- }, _getQueryParam = (url, key, multiple) => {
38351
+ }, _getQueryParam = (url2, key, multiple) => {
38322
38352
  let encoded;
38323
38353
  if (!multiple && key && !/[%+]/.test(key)) {
38324
- let keyIndex2 = url.indexOf("?", 8);
38354
+ let keyIndex2 = url2.indexOf("?", 8);
38325
38355
  if (keyIndex2 === -1) {
38326
38356
  return;
38327
38357
  }
38328
- if (!url.startsWith(key, keyIndex2 + 1)) {
38329
- keyIndex2 = url.indexOf(`&${key}`, keyIndex2 + 1);
38358
+ if (!url2.startsWith(key, keyIndex2 + 1)) {
38359
+ keyIndex2 = url2.indexOf(`&${key}`, keyIndex2 + 1);
38330
38360
  }
38331
38361
  while (keyIndex2 !== -1) {
38332
- const trailingKeyCode = url.charCodeAt(keyIndex2 + key.length + 1);
38362
+ const trailingKeyCode = url2.charCodeAt(keyIndex2 + key.length + 1);
38333
38363
  if (trailingKeyCode === 61) {
38334
38364
  const valueIndex = keyIndex2 + key.length + 2;
38335
- const endIndex = url.indexOf("&", valueIndex);
38336
- return _decodeURI(url.slice(valueIndex, endIndex === -1 ? undefined : endIndex));
38365
+ const endIndex = url2.indexOf("&", valueIndex);
38366
+ return _decodeURI(url2.slice(valueIndex, endIndex === -1 ? undefined : endIndex));
38337
38367
  } else if (trailingKeyCode == 38 || isNaN(trailingKeyCode)) {
38338
38368
  return "";
38339
38369
  }
38340
- keyIndex2 = url.indexOf(`&${key}`, keyIndex2 + 1);
38370
+ keyIndex2 = url2.indexOf(`&${key}`, keyIndex2 + 1);
38341
38371
  }
38342
- encoded = /[%+]/.test(url);
38372
+ encoded = /[%+]/.test(url2);
38343
38373
  if (!encoded) {
38344
38374
  return;
38345
38375
  }
38346
38376
  }
38347
38377
  const results = {};
38348
- encoded ??= /[%+]/.test(url);
38349
- let keyIndex = url.indexOf("?", 8);
38378
+ encoded ??= /[%+]/.test(url2);
38379
+ let keyIndex = url2.indexOf("?", 8);
38350
38380
  while (keyIndex !== -1) {
38351
- const nextKeyIndex = url.indexOf("&", keyIndex + 1);
38352
- let valueIndex = url.indexOf("=", keyIndex);
38381
+ const nextKeyIndex = url2.indexOf("&", keyIndex + 1);
38382
+ let valueIndex = url2.indexOf("=", keyIndex);
38353
38383
  if (valueIndex > nextKeyIndex && nextKeyIndex !== -1) {
38354
38384
  valueIndex = -1;
38355
38385
  }
38356
- let name3 = url.slice(keyIndex + 1, valueIndex === -1 ? nextKeyIndex === -1 ? undefined : nextKeyIndex : valueIndex);
38386
+ let name3 = url2.slice(keyIndex + 1, valueIndex === -1 ? nextKeyIndex === -1 ? undefined : nextKeyIndex : valueIndex);
38357
38387
  if (encoded) {
38358
38388
  name3 = _decodeURI(name3);
38359
38389
  }
@@ -38365,7 +38395,7 @@ var splitPath = (path) => {
38365
38395
  if (valueIndex === -1) {
38366
38396
  value = "";
38367
38397
  } else {
38368
- value = url.slice(valueIndex + 1, nextKeyIndex === -1 ? undefined : nextKeyIndex);
38398
+ value = url2.slice(valueIndex + 1, nextKeyIndex === -1 ? undefined : nextKeyIndex);
38369
38399
  if (encoded) {
38370
38400
  value = _decodeURI(value);
38371
38401
  }
@@ -38380,8 +38410,8 @@ var splitPath = (path) => {
38380
38410
  }
38381
38411
  }
38382
38412
  return key ? results[key] : results;
38383
- }, getQueryParam, getQueryParams = (url, key) => {
38384
- return _getQueryParam(url, key, true);
38413
+ }, getQueryParam, getQueryParams = (url2, key) => {
38414
+ return _getQueryParam(url2, key, true);
38385
38415
  }, decodeURIComponent_;
38386
38416
  var init_url = __esm(() => {
38387
38417
  patternCache = {};
@@ -38860,9 +38890,9 @@ var notFoundHandler = (c) => {
38860
38890
  const mergedPath = mergePath(this._basePath, path);
38861
38891
  const pathPrefixLength = mergedPath === "/" ? 0 : mergedPath.length;
38862
38892
  return (request2) => {
38863
- const url = new URL(request2.url);
38864
- url.pathname = url.pathname.slice(pathPrefixLength) || "/";
38865
- return new Request(url, request2);
38893
+ const url2 = new URL(request2.url);
38894
+ url2.pathname = url2.pathname.slice(pathPrefixLength) || "/";
38895
+ return new Request(url2, request2);
38866
38896
  };
38867
38897
  })();
38868
38898
  const handler = async (c, next) => {
@@ -39800,8 +39830,8 @@ var humanize = (times) => {
39800
39830
  return `${status}`;
39801
39831
  }, logger35 = (fn = console.log) => {
39802
39832
  return async function logger2(c, next) {
39803
- const { method, url } = c.req;
39804
- const path = url.slice(url.indexOf("/", 8));
39833
+ const { method, url: url2 } = c.req;
39834
+ const path = url2.slice(url2.indexOf("/", 8));
39805
39835
  await log4(fn, "<--", method, path);
39806
39836
  const start2 = Date.now();
39807
39837
  await next();
@@ -47559,34 +47589,34 @@ var require_node3 = __commonJS((exports) => {
47559
47589
  }
47560
47590
  exports2.urlParse = urlParse;
47561
47591
  function urlGenerate(aParsedUrl) {
47562
- var url = "";
47592
+ var url2 = "";
47563
47593
  if (aParsedUrl.scheme) {
47564
- url += aParsedUrl.scheme + ":";
47594
+ url2 += aParsedUrl.scheme + ":";
47565
47595
  }
47566
- url += "//";
47596
+ url2 += "//";
47567
47597
  if (aParsedUrl.auth) {
47568
- url += aParsedUrl.auth + "@";
47598
+ url2 += aParsedUrl.auth + "@";
47569
47599
  }
47570
47600
  if (aParsedUrl.host) {
47571
- url += aParsedUrl.host;
47601
+ url2 += aParsedUrl.host;
47572
47602
  }
47573
47603
  if (aParsedUrl.port) {
47574
- url += ":" + aParsedUrl.port;
47604
+ url2 += ":" + aParsedUrl.port;
47575
47605
  }
47576
47606
  if (aParsedUrl.path) {
47577
- url += aParsedUrl.path;
47607
+ url2 += aParsedUrl.path;
47578
47608
  }
47579
- return url;
47609
+ return url2;
47580
47610
  }
47581
47611
  exports2.urlGenerate = urlGenerate;
47582
47612
  function normalize(aPath) {
47583
47613
  var path = aPath;
47584
- var url = urlParse(aPath);
47585
- if (url) {
47586
- if (!url.path) {
47614
+ var url2 = urlParse(aPath);
47615
+ if (url2) {
47616
+ if (!url2.path) {
47587
47617
  return aPath;
47588
47618
  }
47589
- path = url.path;
47619
+ path = url2.path;
47590
47620
  }
47591
47621
  var isAbsolute2 = exports2.isAbsolute(path);
47592
47622
  var parts2 = path.split(/\/+/);
@@ -47610,9 +47640,9 @@ var require_node3 = __commonJS((exports) => {
47610
47640
  if (path === "") {
47611
47641
  path = isAbsolute2 ? "/" : ".";
47612
47642
  }
47613
- if (url) {
47614
- url.path = path;
47615
- return urlGenerate(url);
47643
+ if (url2) {
47644
+ url2.path = path;
47645
+ return urlGenerate(url2);
47616
47646
  }
47617
47647
  return path;
47618
47648
  }
@@ -48638,13 +48668,13 @@ var require_node3 = __commonJS((exports) => {
48638
48668
  if (this.sourceRoot != null) {
48639
48669
  relativeSource = util3.relative(this.sourceRoot, relativeSource);
48640
48670
  }
48641
- var url;
48642
- if (this.sourceRoot != null && (url = util3.urlParse(this.sourceRoot))) {
48671
+ var url2;
48672
+ if (this.sourceRoot != null && (url2 = util3.urlParse(this.sourceRoot))) {
48643
48673
  var fileUriAbsPath = relativeSource.replace(/^file:\/\//, "");
48644
- if (url.scheme == "file" && this._sources.has(fileUriAbsPath)) {
48674
+ if (url2.scheme == "file" && this._sources.has(fileUriAbsPath)) {
48645
48675
  return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)];
48646
48676
  }
48647
- if ((!url.path || url.path == "/") && this._sources.has("/" + relativeSource)) {
48677
+ if ((!url2.path || url2.path == "/") && this._sources.has("/" + relativeSource)) {
48648
48678
  return this.sourcesContent[this._sources.indexOf("/" + relativeSource)];
48649
48679
  }
48650
48680
  }
@@ -49212,18 +49242,18 @@ var require_node3 = __commonJS((exports) => {
49212
49242
  } catch (er2) {}
49213
49243
  return fileContentsCache[path2] = contents;
49214
49244
  });
49215
- function supportRelativeURL(file, url) {
49245
+ function supportRelativeURL(file, url2) {
49216
49246
  if (!file)
49217
- return url;
49247
+ return url2;
49218
49248
  var dir = path.dirname(file);
49219
49249
  var match2 = /^\w+:\/\/[^\/]*/.exec(dir);
49220
49250
  var protocol = match2 ? match2[0] : "";
49221
49251
  var startPath = dir.slice(protocol.length);
49222
49252
  if (protocol && /^\/\w\:/.test(startPath)) {
49223
49253
  protocol += "/";
49224
- return protocol + path.resolve(dir.slice(protocol.length), url).replace(/\\/g, "/");
49254
+ return protocol + path.resolve(dir.slice(protocol.length), url2).replace(/\\/g, "/");
49225
49255
  }
49226
- return protocol + path.resolve(dir.slice(protocol.length), url);
49256
+ return protocol + path.resolve(dir.slice(protocol.length), url2);
49227
49257
  }
49228
49258
  function retrieveSourceMapURL(source) {
49229
49259
  var fileData;
@@ -49283,8 +49313,8 @@ var require_node3 = __commonJS((exports) => {
49283
49313
  sourceMap.map.sources.forEach(function(source, i3) {
49284
49314
  var contents = sourceMap.map.sourcesContent[i3];
49285
49315
  if (contents) {
49286
- var url = supportRelativeURL(sourceMap.url, source);
49287
- fileContentsCache[url] = contents;
49316
+ var url2 = supportRelativeURL(sourceMap.url, source);
49317
+ fileContentsCache[url2] = contents;
49288
49318
  }
49289
49319
  });
49290
49320
  }
@@ -94831,8 +94861,8 @@ var init_bucket_controller = __esm(() => {
94831
94861
  if (!slug2) {
94832
94862
  throw ApiError.badRequest("Missing game slug");
94833
94863
  }
94834
- const url = ctx.url;
94835
- const prefix2 = url.searchParams.get("prefix") || undefined;
94864
+ const url2 = ctx.url;
94865
+ const prefix2 = url2.searchParams.get("prefix") || undefined;
94836
94866
  logger40.debug("Listing files", { userId: ctx.user.id, slug: slug2, prefix: prefix2 });
94837
94867
  const files = await ctx.services.bucket.listFiles(slug2, ctx.user, prefix2);
94838
94868
  return { files };
@@ -95680,8 +95710,8 @@ var init_kv_controller = __esm(() => {
95680
95710
  if (!slug2) {
95681
95711
  throw ApiError.badRequest("Missing game slug");
95682
95712
  }
95683
- const url = ctx.url;
95684
- const prefix2 = url.searchParams.get("prefix") || undefined;
95713
+ const url2 = ctx.url;
95714
+ const prefix2 = url2.searchParams.get("prefix") || undefined;
95685
95715
  logger50.debug("Listing keys", { userId: ctx.user.id, slug: slug2, prefix: prefix2 });
95686
95716
  const keys = await ctx.services.kv.listKeys(slug2, ctx.user, prefix2);
95687
95717
  return { keys };
@@ -95804,17 +95834,17 @@ var init_leaderboard_controller = __esm(() => {
95804
95834
  return ctx.services.leaderboard.submitScore(gameId, ctx.user.id, { score: body2.score, metadata: body2.metadata }, ctx.user.isAnonymous, ctx.params.sessionId);
95805
95835
  });
95806
95836
  getGlobalLeaderboard = requireAuth(async (ctx) => {
95807
- const url = ctx.url;
95808
- const gameId = url.searchParams.get("gameId");
95837
+ const url2 = ctx.url;
95838
+ const gameId = url2.searchParams.get("gameId");
95809
95839
  if (!gameId) {
95810
95840
  throw ApiError.badRequest("Game ID is required");
95811
95841
  }
95812
95842
  let query;
95813
95843
  try {
95814
95844
  query = LeaderboardQuerySchema.parse({
95815
- timeframe: url.searchParams.get("timeframe") || "all_time",
95816
- limit: Number(url.searchParams.get("limit") || "10"),
95817
- offset: Number(url.searchParams.get("offset") || "0")
95845
+ timeframe: url2.searchParams.get("timeframe") || "all_time",
95846
+ limit: Number(url2.searchParams.get("limit") || "10"),
95847
+ offset: Number(url2.searchParams.get("offset") || "0")
95818
95848
  });
95819
95849
  } catch (error2) {
95820
95850
  if (error2 instanceof exports_external.ZodError) {
@@ -95836,13 +95866,13 @@ var init_leaderboard_controller = __esm(() => {
95836
95866
  if (!gameId) {
95837
95867
  throw ApiError.badRequest("Game ID is required");
95838
95868
  }
95839
- const url = ctx.url;
95869
+ const url2 = ctx.url;
95840
95870
  let query;
95841
95871
  try {
95842
95872
  query = LeaderboardQuerySchema.parse({
95843
- timeframe: url.searchParams.get("timeframe") || "all_time",
95844
- limit: Number(url.searchParams.get("limit") || "10"),
95845
- offset: Number(url.searchParams.get("offset") || "0")
95873
+ timeframe: url2.searchParams.get("timeframe") || "all_time",
95874
+ limit: Number(url2.searchParams.get("limit") || "10"),
95875
+ offset: Number(url2.searchParams.get("offset") || "0")
95846
95876
  });
95847
95877
  } catch (error2) {
95848
95878
  if (error2 instanceof exports_external.ZodError) {
@@ -95876,9 +95906,9 @@ var init_leaderboard_controller = __esm(() => {
95876
95906
  if (!userId) {
95877
95907
  throw ApiError.badRequest("User ID is required");
95878
95908
  }
95879
- const url = ctx.url;
95880
- const limit = Math.min(Number(url.searchParams.get("limit") || "50"), 100);
95881
- const gameId = url.searchParams.get("gameId") || undefined;
95909
+ const url2 = ctx.url;
95910
+ const limit = Math.min(Number(url2.searchParams.get("limit") || "50"), 100);
95911
+ const gameId = url2.searchParams.get("gameId") || undefined;
95882
95912
  logger51.debug("Getting user all scores", {
95883
95913
  requesterId: ctx.user.id,
95884
95914
  targetUserId: userId,
@@ -95892,8 +95922,8 @@ var init_leaderboard_controller = __esm(() => {
95892
95922
  if (!gameId || !userId) {
95893
95923
  throw ApiError.badRequest("Game ID and User ID are required");
95894
95924
  }
95895
- const url = ctx.url;
95896
- const limit = Math.min(Number(url.searchParams.get("limit") || "10"), 100);
95925
+ const url2 = ctx.url;
95926
+ const limit = Math.min(Number(url2.searchParams.get("limit") || "10"), 100);
95897
95927
  logger51.debug("Getting user scores", {
95898
95928
  requesterId: ctx.user.id,
95899
95929
  gameId,
@@ -98193,10 +98223,10 @@ var init_timeback6 = __esm(() => {
98193
98223
  return c2.json(createErrorResponse(error2), error2.status);
98194
98224
  }
98195
98225
  if (shouldMockTimeback()) {
98196
- const url = new URL(c2.req.url);
98197
- const gradeParam = url.searchParams.get("grade");
98198
- const subjectParam = url.searchParams.get("subject");
98199
- const includeParam = url.searchParams.get("include") || "";
98226
+ const url2 = new URL(c2.req.url);
98227
+ const gradeParam = url2.searchParams.get("grade");
98228
+ const subjectParam = url2.searchParams.get("subject");
98229
+ const includeParam = url2.searchParams.get("include") || "";
98200
98230
  const includeOptions = includeParam.split(",").map((opt) => opt.trim().toLowerCase());
98201
98231
  const includePerCourse = includeOptions.includes("percourse");
98202
98232
  const includeToday = includeOptions.includes("today");
package/dist/server.js CHANGED
@@ -1329,7 +1329,7 @@ var package_default;
1329
1329
  var init_package = __esm(() => {
1330
1330
  package_default = {
1331
1331
  name: "@playcademy/sandbox",
1332
- version: "0.3.17-beta.24",
1332
+ version: "0.3.17-beta.26",
1333
1333
  description: "Local development server for Playcademy game development",
1334
1334
  type: "module",
1335
1335
  exports: {
@@ -30267,8 +30267,17 @@ function kebabToTitleCase(kebabStr) {
30267
30267
  return kebabStr.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
30268
30268
  }
30269
30269
  // ../utils/src/timezone.ts
30270
- function formatDateYMD(date3 = new Date) {
30271
- return date3.toISOString().split("T")[0];
30270
+ function formatDateYMDInTimezone(timeZone, date3 = new Date) {
30271
+ const parts2 = new Intl.DateTimeFormat("en-US", {
30272
+ timeZone,
30273
+ year: "numeric",
30274
+ month: "2-digit",
30275
+ day: "2-digit"
30276
+ }).formatToParts(date3);
30277
+ const y = parts2.find((p) => p.type === "year").value;
30278
+ const m = parts2.find((p) => p.type === "month").value;
30279
+ const d = parts2.find((p) => p.type === "day").value;
30280
+ return `${y}-${m}-${d}`;
30272
30281
  }
30273
30282
  function getUtcInstantForMidnight(date3, timeZone) {
30274
30283
  const parts2 = new Intl.DateTimeFormat("en-US", {
@@ -30339,6 +30348,19 @@ function getDayBoundariesInTimezone(date3, timezone) {
30339
30348
  const endOfDay = getUtcInstantForMidnight(nextDayNoon, timezone);
30340
30349
  return { startOfDay, endOfDay };
30341
30350
  }
30351
+ // ../utils/src/url.ts
30352
+ function buildPath(path, params) {
30353
+ const url = new URL(path, "http://n");
30354
+ if (params) {
30355
+ for (const [key, value] of Object.entries(params)) {
30356
+ if (value != null) {
30357
+ url.searchParams.set(key, value);
30358
+ }
30359
+ }
30360
+ }
30361
+ return `${url.pathname}${url.search}`;
30362
+ }
30363
+
30342
30364
  // ../utils/src/pure/index.ts
30343
30365
  var init_pure = __esm(() => {
30344
30366
  init_uuid2();
@@ -30391,11 +30413,11 @@ function isRecord2(value) {
30391
30413
  function getStringValue(value) {
30392
30414
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
30393
30415
  }
30394
- function parseSourcedIdFromUrl(url) {
30395
- if (!url) {
30416
+ function parseSourcedIdFromUrl(url2) {
30417
+ if (!url2) {
30396
30418
  return;
30397
30419
  }
30398
- const trimmed = url.trim().replace(/\/$/, "");
30420
+ const trimmed = url2.trim().replace(/\/$/, "");
30399
30421
  if (!trimmed) {
30400
30422
  return;
30401
30423
  }
@@ -30784,7 +30806,7 @@ class TimebackAdminService {
30784
30806
  history: []
30785
30807
  };
30786
30808
  }
30787
- const today = formatDateYMD();
30809
+ const today = formatDateYMDInTimezone(PLATFORM_TIMEZONE);
30788
30810
  const history = [];
30789
30811
  let totalXpRaw = 0;
30790
30812
  let todayXpRaw = 0;
@@ -30950,7 +30972,9 @@ class TimebackAdminService {
30950
30972
  const uniqueEnrollmentIds = [...new Set(enrollmentIds)];
30951
30973
  const results = await TimebackAdminService.runWithConcurrency(uniqueEnrollmentIds, TimebackAdminService.ANALYTICS_CONCURRENCY, async (enrollmentId) => {
30952
30974
  try {
30953
- const analytics = await client.edubridge.analytics.getEnrollmentFacts(enrollmentId);
30975
+ const analytics = await client.edubridge.analytics.getEnrollmentFacts(enrollmentId, {
30976
+ timezone: PLATFORM_TIMEZONE
30977
+ });
30954
30978
  return [enrollmentId, this.summarizeAnalyticsFacts(analytics.facts)];
30955
30979
  } catch (error) {
30956
30980
  logger16.warn("Failed to load enrollment analytics summary", {
@@ -31224,7 +31248,7 @@ class TimebackAdminService {
31224
31248
  const enrollment = await this.assertStudentEnrolledInCourse(client, data.studentId, data.courseId);
31225
31249
  let currentMastered = 0;
31226
31250
  try {
31227
- const analytics = await client.edubridge.analytics.getEnrollmentFacts(enrollment.id);
31251
+ const analytics = await client.edubridge.analytics.getEnrollmentFacts(enrollment.id, { timezone: PLATFORM_TIMEZONE });
31228
31252
  const summary = this.summarizeAnalyticsFacts(analytics.facts);
31229
31253
  currentMastered = summary.masteredUnits;
31230
31254
  } catch {
@@ -31414,6 +31438,7 @@ class TimebackAdminService {
31414
31438
  var logger16;
31415
31439
  var init_timeback_admin_service = __esm(() => {
31416
31440
  init_drizzle_orm();
31441
+ init_src();
31417
31442
  init_tables_index();
31418
31443
  init_src2();
31419
31444
  init_constants4();
@@ -33371,13 +33396,13 @@ var init_events = () => {};
33371
33396
 
33372
33397
  // ../api-core/src/services/notification.service.ts
33373
33398
  function convertWebSocketUrlToHttp(wsUrl) {
33374
- return wsUrl.replace(/^wss?:\/\//, (url) => url === "wss://" ? "https://" : "http://");
33399
+ return wsUrl.replace(/^wss?:\/\//, (url2) => url2 === "wss://" ? "https://" : "http://");
33375
33400
  }
33376
33401
  async function publishToUser(baseUrl, secret, userId, type, payload) {
33377
- const url = new URL(baseUrl);
33378
- url.pathname = "/publish";
33402
+ const url2 = new URL(baseUrl);
33403
+ url2.pathname = "/publish";
33379
33404
  try {
33380
- const res = await fetch(url.toString(), {
33405
+ const res = await fetch(url2.toString(), {
33381
33406
  method: "POST",
33382
33407
  headers: { "content-type": "application/json" },
33383
33408
  body: JSON.stringify({ userId, type, payload, secret })
@@ -35229,14 +35254,14 @@ function debugSensitive(label, value, showChars = 4) {
35229
35254
  const masked = value.length > showChars * 2 ? `${value.slice(0, showChars)}...${value.slice(-showChars)}` : `${value.slice(0, showChars)}...`;
35230
35255
  console.log(`[DEBUG] ${label}: ${masked}`);
35231
35256
  }
35232
- function debugRequest(method, url, headers, body2) {
35257
+ function debugRequest(method, url2, headers, body2) {
35233
35258
  if (!isDebugEnabled()) {
35234
35259
  return;
35235
35260
  }
35236
35261
  console.log(`
35237
35262
  [DEBUG] HTTP Request:`);
35238
35263
  console.log(` Method: ${method}`);
35239
- console.log(` URL: ${url}`);
35264
+ console.log(` URL: ${url2}`);
35240
35265
  if (headers) {
35241
35266
  console.log(` Headers:`);
35242
35267
  Object.entries(headers).forEach(([key, value]) => {
@@ -35309,7 +35334,7 @@ async function getTimebackTokenResponse(config2) {
35309
35334
  function getAuthUrl(environment = "production") {
35310
35335
  return TIMEBACK_AUTH_URLS4[environment];
35311
35336
  }
35312
- function handleHttpError(res, errorBody, attempt, retries, url) {
35337
+ function handleHttpError(res, errorBody, attempt, retries, url2) {
35313
35338
  const error = new TimebackApiError(res.status, res.statusText, errorBody);
35314
35339
  if (res.status >= HTTP_STATUS4.CLIENT_ERROR_MIN && res.status < HTTP_STATUS4.CLIENT_ERROR_MAX) {
35315
35340
  throw error;
@@ -35320,7 +35345,7 @@ function handleHttpError(res, errorBody, attempt, retries, url) {
35320
35345
  attempt: attempt + 1,
35321
35346
  maxRetries: retries,
35322
35347
  status: res.status,
35323
- url
35348
+ url: url2
35324
35349
  });
35325
35350
  return { retry: true, error };
35326
35351
  }
@@ -35351,7 +35376,7 @@ async function request({
35351
35376
  retries = 3,
35352
35377
  timeout = 1e4
35353
35378
  }) {
35354
- const url = baseUrl.replace(/\/$/, "") + (path.startsWith("/") ? path : `/${path}`);
35379
+ const url2 = baseUrl.replace(/\/$/, "") + (path.startsWith("/") ? path : `/${path}`);
35355
35380
  const headers = { ...extraHeaders };
35356
35381
  let payload;
35357
35382
  if (body2 instanceof FormData) {
@@ -35368,8 +35393,8 @@ async function request({
35368
35393
  try {
35369
35394
  const controller = new AbortController;
35370
35395
  const timeoutId = setTimeout(() => controller.abort(), timeout);
35371
- debugRequest(method, url, headers, body2);
35372
- const res = await fetch(url, {
35396
+ debugRequest(method, url2, headers, body2);
35397
+ const res = await fetch(url2, {
35373
35398
  method,
35374
35399
  headers,
35375
35400
  body: payload,
@@ -35385,7 +35410,7 @@ async function request({
35385
35410
  if (errorBody) {
35386
35411
  debugResponse(res.status, res.statusText, errorBody);
35387
35412
  }
35388
- const result = handleHttpError(res, errorBody, attempt, retries, url);
35413
+ const result = handleHttpError(res, errorBody, attempt, retries, url2);
35389
35414
  lastError = result.error;
35390
35415
  if (result.retry) {
35391
35416
  const delay = HTTP_DEFAULTS4.retryBackoffBase ** attempt * 1000;
@@ -35405,7 +35430,7 @@ async function request({
35405
35430
  attempt: attempt + 1,
35406
35431
  maxRetries: retries,
35407
35432
  error: lastError.message,
35408
- url
35433
+ url: url2
35409
35434
  });
35410
35435
  await new Promise((resolve) => setTimeout(resolve, delay));
35411
35436
  }
@@ -35644,7 +35669,9 @@ function createEduBridgeNamespace(client) {
35644
35669
  }
35645
35670
  };
35646
35671
  const analytics = {
35647
- getEnrollmentFacts: async (enrollmentId) => client["request"](`/edubridge/analytics/enrollment/${enrollmentId}`, "GET")
35672
+ getEnrollmentFacts: async (enrollmentId, options) => client["request"](buildPath(`/edubridge/analytics/enrollment/${enrollmentId}`, {
35673
+ timezone: options?.timezone
35674
+ }), "GET")
35648
35675
  };
35649
35676
  return {
35650
35677
  enrollments,
@@ -35735,8 +35762,8 @@ function createOneRosterNamespace(client) {
35735
35762
  queryParams.set("offset", String(options.offset));
35736
35763
  }
35737
35764
  const endpoint = `${ONEROSTER_ENDPOINTS4.users}/${userSourcedId}/classes`;
35738
- const url = queryParams.toString() ? `${endpoint}?${queryParams}` : endpoint;
35739
- const res = await client["request"](url, "GET");
35765
+ const url2 = queryParams.toString() ? `${endpoint}?${queryParams}` : endpoint;
35766
+ const res = await client["request"](url2, "GET");
35740
35767
  return res.classes || [];
35741
35768
  }
35742
35769
  },
@@ -35754,9 +35781,9 @@ function createOneRosterNamespace(client) {
35754
35781
  if (options?.offset) {
35755
35782
  queryParams.set("offset", String(options.offset));
35756
35783
  }
35757
- const url = `${ONEROSTER_ENDPOINTS4.enrollments}?${queryParams}`;
35784
+ const url2 = `${ONEROSTER_ENDPOINTS4.enrollments}?${queryParams}`;
35758
35785
  try {
35759
- const response = await client["request"](url, "GET");
35786
+ const response = await client["request"](url2, "GET");
35760
35787
  return response.enrollments || [];
35761
35788
  } catch (error) {
35762
35789
  logTimebackError("list enrollments for class", error, { classSourcedId });
@@ -35787,8 +35814,8 @@ function createOneRosterNamespace(client) {
35787
35814
  }
35788
35815
  queryParams.set("filter", filters.join(" AND "));
35789
35816
  queryParams.set("limit", "3000");
35790
- const url = `${ONEROSTER_ENDPOINTS4.enrollments}?${queryParams}`;
35791
- const response = await client["request"](url, "GET");
35817
+ const url2 = `${ONEROSTER_ENDPOINTS4.enrollments}?${queryParams}`;
35818
+ const response = await client["request"](url2, "GET");
35792
35819
  return response.enrollments || [];
35793
35820
  }));
35794
35821
  const enrollments = enrollmentGroups.flat();
@@ -35953,8 +35980,8 @@ function createOneRosterNamespace(client) {
35953
35980
  getAttemptStats: async (studentId, lineItemId) => {
35954
35981
  try {
35955
35982
  const filter = `student.sourcedId='${studentId}' AND assessmentLineItem.sourcedId='${lineItemId}'`;
35956
- const url = `${ONEROSTER_ENDPOINTS4.assessmentResults}?filter=${encodeURIComponent(filter)}`;
35957
- const response = await client["request"](url, "GET");
35983
+ const url2 = `${ONEROSTER_ENDPOINTS4.assessmentResults}?filter=${encodeURIComponent(filter)}`;
35984
+ const response = await client["request"](url2, "GET");
35958
35985
  const results = response.assessmentResults || [];
35959
35986
  const firstResult = results[0];
35960
35987
  if (!firstResult) {
@@ -36560,7 +36587,7 @@ class MasteryTracker {
36560
36587
  });
36561
36588
  return;
36562
36589
  }
36563
- const analytics = await this.edubridgeNamespace.analytics.getEnrollmentFacts(enrollment.id);
36590
+ const analytics = await this.edubridgeNamespace.analytics.getEnrollmentFacts(enrollment.id, { timezone: PLATFORM_TIMEZONE });
36564
36591
  return analytics.facts;
36565
36592
  } catch (error) {
36566
36593
  log.error("[MasteryTracker] Failed to load enrollment analytics facts", {
@@ -37283,7 +37310,7 @@ class TimebackClient {
37283
37310
  }
37284
37311
  const analyticsResults = await Promise.all(filteredEnrollments.map(async (enrollment) => {
37285
37312
  try {
37286
- const analytics = await this.edubridge.analytics.getEnrollmentFacts(enrollment.id);
37313
+ const analytics = await this.edubridge.analytics.getEnrollmentFacts(enrollment.id, { timezone: PLATFORM_TIMEZONE });
37287
37314
  return { enrollment, analytics };
37288
37315
  } catch (error) {
37289
37316
  log.warn("[TimebackClient] Failed to fetch analytics for enrollment", {
@@ -37293,7 +37320,7 @@ class TimebackClient {
37293
37320
  return { enrollment, analytics: null };
37294
37321
  }
37295
37322
  }));
37296
- const today = formatDateYMD();
37323
+ const today = formatDateYMDInTimezone(PLATFORM_TIMEZONE);
37297
37324
  let totalXp = 0;
37298
37325
  let todayXp = 0;
37299
37326
  const courses = [];
@@ -37392,6 +37419,7 @@ var __defProp2, __export2 = (target, all) => {
37392
37419
  });
37393
37420
  }, __esm5 = (fn, res) => () => (fn && (res = fn(fn = 0)), res), TIMEBACK_API_URLS4, TIMEBACK_AUTH_URLS4, CALIPER_API_URLS4, ONEROSTER_ENDPOINTS4, CALIPER_ENDPOINTS4, CALIPER_CONSTANTS4, TIMEBACK_EVENT_TYPES4, TIMEBACK_ACTIONS4, TIMEBACK_TYPES4, ACTIVITY_METRIC_TYPES4, TIME_METRIC_TYPES4, TIMEBACK_SUBJECTS4, TIMEBACK_GRADE_LEVELS4, TIMEBACK_GRADE_LEVEL_LABELS4, CALIPER_SUBJECTS4, ONEROSTER_STATUS4, SCORE_STATUS4, ENV_VARS4, HTTP_DEFAULTS4, AUTH_DEFAULTS4, CACHE_DEFAULTS4, CONFIG_DEFAULTS4, PLAYCADEMY_DEFAULTS4, RESOURCE_DEFAULTS4, HTTP_STATUS4, ERROR_NAMES4, init_constants8, exports_verify, init_verify, TimebackError, TimebackApiError, TimebackAuthenticationError, StudentNotFoundError, ConfigurationError, ResourceNotFoundError, SUBJECT_VALUES2, GRADE_VALUES2, TimebackAuthError, PERFECT_ACCURACY_THRESHOLD = 0.999999, EmailSchema, StudentSourcedIdSchema, StudentIdentifierSchema;
37394
37421
  var init_dist3 = __esm(() => {
37422
+ init_src();
37395
37423
  init_src();
37396
37424
  init_src2();
37397
37425
  init_src4();
@@ -37406,9 +37434,11 @@ var init_dist3 = __esm(() => {
37406
37434
  init_src2();
37407
37435
  init_src2();
37408
37436
  init_src2();
37437
+ init_src4();
37409
37438
  init_src2();
37410
37439
  init_src2();
37411
37440
  init_src2();
37441
+ init_src();
37412
37442
  init_src2();
37413
37443
  init_src2();
37414
37444
  init_src2();
@@ -38261,20 +38291,20 @@ var splitPath = (path) => {
38261
38291
  });
38262
38292
  }
38263
38293
  }, tryDecodeURI = (str) => tryDecode(str, decodeURI), getPath = (request2) => {
38264
- const url = request2.url;
38265
- const start2 = url.indexOf("/", url.indexOf(":") + 4);
38294
+ const url2 = request2.url;
38295
+ const start2 = url2.indexOf("/", url2.indexOf(":") + 4);
38266
38296
  let i2 = start2;
38267
- for (;i2 < url.length; i2++) {
38268
- const charCode = url.charCodeAt(i2);
38297
+ for (;i2 < url2.length; i2++) {
38298
+ const charCode = url2.charCodeAt(i2);
38269
38299
  if (charCode === 37) {
38270
- const queryIndex = url.indexOf("?", i2);
38271
- const path = url.slice(start2, queryIndex === -1 ? undefined : queryIndex);
38300
+ const queryIndex = url2.indexOf("?", i2);
38301
+ const path = url2.slice(start2, queryIndex === -1 ? undefined : queryIndex);
38272
38302
  return tryDecodeURI(path.includes("%25") ? path.replace(/%25/g, "%2525") : path);
38273
38303
  } else if (charCode === 63) {
38274
38304
  break;
38275
38305
  }
38276
38306
  }
38277
- return url.slice(start2, i2);
38307
+ return url2.slice(start2, i2);
38278
38308
  }, getPathNoStrict = (request2) => {
38279
38309
  const result = getPath(request2);
38280
38310
  return result.length > 1 && result.at(-1) === "/" ? result.slice(0, -1) : result;
@@ -38317,42 +38347,42 @@ var splitPath = (path) => {
38317
38347
  value = value.replace(/\+/g, " ");
38318
38348
  }
38319
38349
  return value.indexOf("%") !== -1 ? tryDecode(value, decodeURIComponent_) : value;
38320
- }, _getQueryParam = (url, key, multiple) => {
38350
+ }, _getQueryParam = (url2, key, multiple) => {
38321
38351
  let encoded;
38322
38352
  if (!multiple && key && !/[%+]/.test(key)) {
38323
- let keyIndex2 = url.indexOf("?", 8);
38353
+ let keyIndex2 = url2.indexOf("?", 8);
38324
38354
  if (keyIndex2 === -1) {
38325
38355
  return;
38326
38356
  }
38327
- if (!url.startsWith(key, keyIndex2 + 1)) {
38328
- keyIndex2 = url.indexOf(`&${key}`, keyIndex2 + 1);
38357
+ if (!url2.startsWith(key, keyIndex2 + 1)) {
38358
+ keyIndex2 = url2.indexOf(`&${key}`, keyIndex2 + 1);
38329
38359
  }
38330
38360
  while (keyIndex2 !== -1) {
38331
- const trailingKeyCode = url.charCodeAt(keyIndex2 + key.length + 1);
38361
+ const trailingKeyCode = url2.charCodeAt(keyIndex2 + key.length + 1);
38332
38362
  if (trailingKeyCode === 61) {
38333
38363
  const valueIndex = keyIndex2 + key.length + 2;
38334
- const endIndex = url.indexOf("&", valueIndex);
38335
- return _decodeURI(url.slice(valueIndex, endIndex === -1 ? undefined : endIndex));
38364
+ const endIndex = url2.indexOf("&", valueIndex);
38365
+ return _decodeURI(url2.slice(valueIndex, endIndex === -1 ? undefined : endIndex));
38336
38366
  } else if (trailingKeyCode == 38 || isNaN(trailingKeyCode)) {
38337
38367
  return "";
38338
38368
  }
38339
- keyIndex2 = url.indexOf(`&${key}`, keyIndex2 + 1);
38369
+ keyIndex2 = url2.indexOf(`&${key}`, keyIndex2 + 1);
38340
38370
  }
38341
- encoded = /[%+]/.test(url);
38371
+ encoded = /[%+]/.test(url2);
38342
38372
  if (!encoded) {
38343
38373
  return;
38344
38374
  }
38345
38375
  }
38346
38376
  const results = {};
38347
- encoded ??= /[%+]/.test(url);
38348
- let keyIndex = url.indexOf("?", 8);
38377
+ encoded ??= /[%+]/.test(url2);
38378
+ let keyIndex = url2.indexOf("?", 8);
38349
38379
  while (keyIndex !== -1) {
38350
- const nextKeyIndex = url.indexOf("&", keyIndex + 1);
38351
- let valueIndex = url.indexOf("=", keyIndex);
38380
+ const nextKeyIndex = url2.indexOf("&", keyIndex + 1);
38381
+ let valueIndex = url2.indexOf("=", keyIndex);
38352
38382
  if (valueIndex > nextKeyIndex && nextKeyIndex !== -1) {
38353
38383
  valueIndex = -1;
38354
38384
  }
38355
- let name3 = url.slice(keyIndex + 1, valueIndex === -1 ? nextKeyIndex === -1 ? undefined : nextKeyIndex : valueIndex);
38385
+ let name3 = url2.slice(keyIndex + 1, valueIndex === -1 ? nextKeyIndex === -1 ? undefined : nextKeyIndex : valueIndex);
38356
38386
  if (encoded) {
38357
38387
  name3 = _decodeURI(name3);
38358
38388
  }
@@ -38364,7 +38394,7 @@ var splitPath = (path) => {
38364
38394
  if (valueIndex === -1) {
38365
38395
  value = "";
38366
38396
  } else {
38367
- value = url.slice(valueIndex + 1, nextKeyIndex === -1 ? undefined : nextKeyIndex);
38397
+ value = url2.slice(valueIndex + 1, nextKeyIndex === -1 ? undefined : nextKeyIndex);
38368
38398
  if (encoded) {
38369
38399
  value = _decodeURI(value);
38370
38400
  }
@@ -38379,8 +38409,8 @@ var splitPath = (path) => {
38379
38409
  }
38380
38410
  }
38381
38411
  return key ? results[key] : results;
38382
- }, getQueryParam, getQueryParams = (url, key) => {
38383
- return _getQueryParam(url, key, true);
38412
+ }, getQueryParam, getQueryParams = (url2, key) => {
38413
+ return _getQueryParam(url2, key, true);
38384
38414
  }, decodeURIComponent_;
38385
38415
  var init_url = __esm(() => {
38386
38416
  patternCache = {};
@@ -38859,9 +38889,9 @@ var notFoundHandler = (c) => {
38859
38889
  const mergedPath = mergePath(this._basePath, path);
38860
38890
  const pathPrefixLength = mergedPath === "/" ? 0 : mergedPath.length;
38861
38891
  return (request2) => {
38862
- const url = new URL(request2.url);
38863
- url.pathname = url.pathname.slice(pathPrefixLength) || "/";
38864
- return new Request(url, request2);
38892
+ const url2 = new URL(request2.url);
38893
+ url2.pathname = url2.pathname.slice(pathPrefixLength) || "/";
38894
+ return new Request(url2, request2);
38865
38895
  };
38866
38896
  })();
38867
38897
  const handler = async (c, next) => {
@@ -39799,8 +39829,8 @@ var humanize = (times) => {
39799
39829
  return `${status}`;
39800
39830
  }, logger35 = (fn = console.log) => {
39801
39831
  return async function logger2(c, next) {
39802
- const { method, url } = c.req;
39803
- const path = url.slice(url.indexOf("/", 8));
39832
+ const { method, url: url2 } = c.req;
39833
+ const path = url2.slice(url2.indexOf("/", 8));
39804
39834
  await log4(fn, "<--", method, path);
39805
39835
  const start2 = Date.now();
39806
39836
  await next();
@@ -47558,34 +47588,34 @@ var require_node3 = __commonJS((exports) => {
47558
47588
  }
47559
47589
  exports2.urlParse = urlParse;
47560
47590
  function urlGenerate(aParsedUrl) {
47561
- var url = "";
47591
+ var url2 = "";
47562
47592
  if (aParsedUrl.scheme) {
47563
- url += aParsedUrl.scheme + ":";
47593
+ url2 += aParsedUrl.scheme + ":";
47564
47594
  }
47565
- url += "//";
47595
+ url2 += "//";
47566
47596
  if (aParsedUrl.auth) {
47567
- url += aParsedUrl.auth + "@";
47597
+ url2 += aParsedUrl.auth + "@";
47568
47598
  }
47569
47599
  if (aParsedUrl.host) {
47570
- url += aParsedUrl.host;
47600
+ url2 += aParsedUrl.host;
47571
47601
  }
47572
47602
  if (aParsedUrl.port) {
47573
- url += ":" + aParsedUrl.port;
47603
+ url2 += ":" + aParsedUrl.port;
47574
47604
  }
47575
47605
  if (aParsedUrl.path) {
47576
- url += aParsedUrl.path;
47606
+ url2 += aParsedUrl.path;
47577
47607
  }
47578
- return url;
47608
+ return url2;
47579
47609
  }
47580
47610
  exports2.urlGenerate = urlGenerate;
47581
47611
  function normalize(aPath) {
47582
47612
  var path = aPath;
47583
- var url = urlParse(aPath);
47584
- if (url) {
47585
- if (!url.path) {
47613
+ var url2 = urlParse(aPath);
47614
+ if (url2) {
47615
+ if (!url2.path) {
47586
47616
  return aPath;
47587
47617
  }
47588
- path = url.path;
47618
+ path = url2.path;
47589
47619
  }
47590
47620
  var isAbsolute2 = exports2.isAbsolute(path);
47591
47621
  var parts2 = path.split(/\/+/);
@@ -47609,9 +47639,9 @@ var require_node3 = __commonJS((exports) => {
47609
47639
  if (path === "") {
47610
47640
  path = isAbsolute2 ? "/" : ".";
47611
47641
  }
47612
- if (url) {
47613
- url.path = path;
47614
- return urlGenerate(url);
47642
+ if (url2) {
47643
+ url2.path = path;
47644
+ return urlGenerate(url2);
47615
47645
  }
47616
47646
  return path;
47617
47647
  }
@@ -48637,13 +48667,13 @@ var require_node3 = __commonJS((exports) => {
48637
48667
  if (this.sourceRoot != null) {
48638
48668
  relativeSource = util3.relative(this.sourceRoot, relativeSource);
48639
48669
  }
48640
- var url;
48641
- if (this.sourceRoot != null && (url = util3.urlParse(this.sourceRoot))) {
48670
+ var url2;
48671
+ if (this.sourceRoot != null && (url2 = util3.urlParse(this.sourceRoot))) {
48642
48672
  var fileUriAbsPath = relativeSource.replace(/^file:\/\//, "");
48643
- if (url.scheme == "file" && this._sources.has(fileUriAbsPath)) {
48673
+ if (url2.scheme == "file" && this._sources.has(fileUriAbsPath)) {
48644
48674
  return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)];
48645
48675
  }
48646
- if ((!url.path || url.path == "/") && this._sources.has("/" + relativeSource)) {
48676
+ if ((!url2.path || url2.path == "/") && this._sources.has("/" + relativeSource)) {
48647
48677
  return this.sourcesContent[this._sources.indexOf("/" + relativeSource)];
48648
48678
  }
48649
48679
  }
@@ -49211,18 +49241,18 @@ var require_node3 = __commonJS((exports) => {
49211
49241
  } catch (er2) {}
49212
49242
  return fileContentsCache[path2] = contents;
49213
49243
  });
49214
- function supportRelativeURL(file, url) {
49244
+ function supportRelativeURL(file, url2) {
49215
49245
  if (!file)
49216
- return url;
49246
+ return url2;
49217
49247
  var dir = path.dirname(file);
49218
49248
  var match2 = /^\w+:\/\/[^\/]*/.exec(dir);
49219
49249
  var protocol = match2 ? match2[0] : "";
49220
49250
  var startPath = dir.slice(protocol.length);
49221
49251
  if (protocol && /^\/\w\:/.test(startPath)) {
49222
49252
  protocol += "/";
49223
- return protocol + path.resolve(dir.slice(protocol.length), url).replace(/\\/g, "/");
49253
+ return protocol + path.resolve(dir.slice(protocol.length), url2).replace(/\\/g, "/");
49224
49254
  }
49225
- return protocol + path.resolve(dir.slice(protocol.length), url);
49255
+ return protocol + path.resolve(dir.slice(protocol.length), url2);
49226
49256
  }
49227
49257
  function retrieveSourceMapURL(source) {
49228
49258
  var fileData;
@@ -49282,8 +49312,8 @@ var require_node3 = __commonJS((exports) => {
49282
49312
  sourceMap.map.sources.forEach(function(source, i3) {
49283
49313
  var contents = sourceMap.map.sourcesContent[i3];
49284
49314
  if (contents) {
49285
- var url = supportRelativeURL(sourceMap.url, source);
49286
- fileContentsCache[url] = contents;
49315
+ var url2 = supportRelativeURL(sourceMap.url, source);
49316
+ fileContentsCache[url2] = contents;
49287
49317
  }
49288
49318
  });
49289
49319
  }
@@ -94830,8 +94860,8 @@ var init_bucket_controller = __esm(() => {
94830
94860
  if (!slug2) {
94831
94861
  throw ApiError.badRequest("Missing game slug");
94832
94862
  }
94833
- const url = ctx.url;
94834
- const prefix2 = url.searchParams.get("prefix") || undefined;
94863
+ const url2 = ctx.url;
94864
+ const prefix2 = url2.searchParams.get("prefix") || undefined;
94835
94865
  logger40.debug("Listing files", { userId: ctx.user.id, slug: slug2, prefix: prefix2 });
94836
94866
  const files = await ctx.services.bucket.listFiles(slug2, ctx.user, prefix2);
94837
94867
  return { files };
@@ -95679,8 +95709,8 @@ var init_kv_controller = __esm(() => {
95679
95709
  if (!slug2) {
95680
95710
  throw ApiError.badRequest("Missing game slug");
95681
95711
  }
95682
- const url = ctx.url;
95683
- const prefix2 = url.searchParams.get("prefix") || undefined;
95712
+ const url2 = ctx.url;
95713
+ const prefix2 = url2.searchParams.get("prefix") || undefined;
95684
95714
  logger50.debug("Listing keys", { userId: ctx.user.id, slug: slug2, prefix: prefix2 });
95685
95715
  const keys = await ctx.services.kv.listKeys(slug2, ctx.user, prefix2);
95686
95716
  return { keys };
@@ -95803,17 +95833,17 @@ var init_leaderboard_controller = __esm(() => {
95803
95833
  return ctx.services.leaderboard.submitScore(gameId, ctx.user.id, { score: body2.score, metadata: body2.metadata }, ctx.user.isAnonymous, ctx.params.sessionId);
95804
95834
  });
95805
95835
  getGlobalLeaderboard = requireAuth(async (ctx) => {
95806
- const url = ctx.url;
95807
- const gameId = url.searchParams.get("gameId");
95836
+ const url2 = ctx.url;
95837
+ const gameId = url2.searchParams.get("gameId");
95808
95838
  if (!gameId) {
95809
95839
  throw ApiError.badRequest("Game ID is required");
95810
95840
  }
95811
95841
  let query;
95812
95842
  try {
95813
95843
  query = LeaderboardQuerySchema.parse({
95814
- timeframe: url.searchParams.get("timeframe") || "all_time",
95815
- limit: Number(url.searchParams.get("limit") || "10"),
95816
- offset: Number(url.searchParams.get("offset") || "0")
95844
+ timeframe: url2.searchParams.get("timeframe") || "all_time",
95845
+ limit: Number(url2.searchParams.get("limit") || "10"),
95846
+ offset: Number(url2.searchParams.get("offset") || "0")
95817
95847
  });
95818
95848
  } catch (error2) {
95819
95849
  if (error2 instanceof exports_external.ZodError) {
@@ -95835,13 +95865,13 @@ var init_leaderboard_controller = __esm(() => {
95835
95865
  if (!gameId) {
95836
95866
  throw ApiError.badRequest("Game ID is required");
95837
95867
  }
95838
- const url = ctx.url;
95868
+ const url2 = ctx.url;
95839
95869
  let query;
95840
95870
  try {
95841
95871
  query = LeaderboardQuerySchema.parse({
95842
- timeframe: url.searchParams.get("timeframe") || "all_time",
95843
- limit: Number(url.searchParams.get("limit") || "10"),
95844
- offset: Number(url.searchParams.get("offset") || "0")
95872
+ timeframe: url2.searchParams.get("timeframe") || "all_time",
95873
+ limit: Number(url2.searchParams.get("limit") || "10"),
95874
+ offset: Number(url2.searchParams.get("offset") || "0")
95845
95875
  });
95846
95876
  } catch (error2) {
95847
95877
  if (error2 instanceof exports_external.ZodError) {
@@ -95875,9 +95905,9 @@ var init_leaderboard_controller = __esm(() => {
95875
95905
  if (!userId) {
95876
95906
  throw ApiError.badRequest("User ID is required");
95877
95907
  }
95878
- const url = ctx.url;
95879
- const limit = Math.min(Number(url.searchParams.get("limit") || "50"), 100);
95880
- const gameId = url.searchParams.get("gameId") || undefined;
95908
+ const url2 = ctx.url;
95909
+ const limit = Math.min(Number(url2.searchParams.get("limit") || "50"), 100);
95910
+ const gameId = url2.searchParams.get("gameId") || undefined;
95881
95911
  logger51.debug("Getting user all scores", {
95882
95912
  requesterId: ctx.user.id,
95883
95913
  targetUserId: userId,
@@ -95891,8 +95921,8 @@ var init_leaderboard_controller = __esm(() => {
95891
95921
  if (!gameId || !userId) {
95892
95922
  throw ApiError.badRequest("Game ID and User ID are required");
95893
95923
  }
95894
- const url = ctx.url;
95895
- const limit = Math.min(Number(url.searchParams.get("limit") || "10"), 100);
95924
+ const url2 = ctx.url;
95925
+ const limit = Math.min(Number(url2.searchParams.get("limit") || "10"), 100);
95896
95926
  logger51.debug("Getting user scores", {
95897
95927
  requesterId: ctx.user.id,
95898
95928
  gameId,
@@ -98192,10 +98222,10 @@ var init_timeback6 = __esm(() => {
98192
98222
  return c2.json(createErrorResponse(error2), error2.status);
98193
98223
  }
98194
98224
  if (shouldMockTimeback()) {
98195
- const url = new URL(c2.req.url);
98196
- const gradeParam = url.searchParams.get("grade");
98197
- const subjectParam = url.searchParams.get("subject");
98198
- const includeParam = url.searchParams.get("include") || "";
98225
+ const url2 = new URL(c2.req.url);
98226
+ const gradeParam = url2.searchParams.get("grade");
98227
+ const subjectParam = url2.searchParams.get("subject");
98228
+ const includeParam = url2.searchParams.get("include") || "";
98199
98229
  const includeOptions = includeParam.split(",").map((opt) => opt.trim().toLowerCase());
98200
98230
  const includePerCourse = includeOptions.includes("percourse");
98201
98231
  const includeToday = includeOptions.includes("today");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/sandbox",
3
- "version": "0.3.17-beta.24",
3
+ "version": "0.3.17-beta.26",
4
4
  "description": "Local development server for Playcademy game development",
5
5
  "type": "module",
6
6
  "exports": {