@playcademy/sandbox 0.1.10 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -60742,7 +60742,7 @@ var require_event_target_shim = __commonJS((exports, module2) => {
60742
60742
  var CAPTURE = 1;
60743
60743
  var BUBBLE = 2;
60744
60744
  var ATTRIBUTE = 3;
60745
- function isObject2(x6) {
60745
+ function isObject4(x6) {
60746
60746
  return x6 !== null && typeof x6 === "object";
60747
60747
  }
60748
60748
  function getListeners(eventTarget) {
@@ -60766,7 +60766,7 @@ var require_event_target_shim = __commonJS((exports, module2) => {
60766
60766
  return null;
60767
60767
  },
60768
60768
  set(listener) {
60769
- if (typeof listener !== "function" && !isObject2(listener)) {
60769
+ if (typeof listener !== "function" && !isObject4(listener)) {
60770
60770
  listener = null;
60771
60771
  }
60772
60772
  const listeners = getListeners(this);
@@ -60846,11 +60846,11 @@ var require_event_target_shim = __commonJS((exports, module2) => {
60846
60846
  if (listener == null) {
60847
60847
  return;
60848
60848
  }
60849
- if (typeof listener !== "function" && !isObject2(listener)) {
60849
+ if (typeof listener !== "function" && !isObject4(listener)) {
60850
60850
  throw new TypeError("'listener' should be a function or an object.");
60851
60851
  }
60852
60852
  const listeners = getListeners(this);
60853
- const optionsIsObj = isObject2(options);
60853
+ const optionsIsObj = isObject4(options);
60854
60854
  const capture = optionsIsObj ? Boolean(options.capture) : Boolean(options);
60855
60855
  const listenerType = capture ? CAPTURE : BUBBLE;
60856
60856
  const newNode = {
@@ -60880,7 +60880,7 @@ var require_event_target_shim = __commonJS((exports, module2) => {
60880
60880
  return;
60881
60881
  }
60882
60882
  const listeners = getListeners(this);
60883
- const capture = isObject2(options) ? Boolean(options.capture) : Boolean(options);
60883
+ const capture = isObject4(options) ? Boolean(options.capture) : Boolean(options);
60884
60884
  const listenerType = capture ? CAPTURE : BUBBLE;
60885
60885
  let prev = null;
60886
60886
  let node = listeners.get(eventName);
@@ -66890,7 +66890,7 @@ var init_block_senders = __esm(() => {
66890
66890
 
66891
66891
  // ../../node_modules/cloudflare/resources/email-security/settings/domains.mjs
66892
66892
  var Domains, DomainListResponsesV4PagePaginationArray, DomainBulkDeleteResponsesSinglePage;
66893
- var init_domains = __esm(() => {
66893
+ var init_domains3 = __esm(() => {
66894
66894
  init_pagination();
66895
66895
  Domains = class Domains extends APIResource {
66896
66896
  list(params, options) {
@@ -67000,8 +67000,8 @@ var init_settings4 = __esm(() => {
67000
67000
  init_allow_policies();
67001
67001
  init_block_senders();
67002
67002
  init_block_senders();
67003
- init_domains();
67004
- init_domains();
67003
+ init_domains3();
67004
+ init_domains3();
67005
67005
  init_impersonation_registry();
67006
67006
  init_impersonation_registry();
67007
67007
  init_trusted_domains();
@@ -68291,7 +68291,7 @@ var init_bulks = __esm(() => {
68291
68291
 
68292
68292
  // ../../node_modules/cloudflare/resources/intel/domains/domains.mjs
68293
68293
  var Domains2;
68294
- var init_domains2 = __esm(() => {
68294
+ var init_domains4 = __esm(() => {
68295
68295
  init_bulks();
68296
68296
  init_bulks();
68297
68297
  Domains2 = class Domains2 extends APIResource {
@@ -68426,8 +68426,8 @@ var init_intel = __esm(() => {
68426
68426
  init_asn3();
68427
68427
  init_attack_surface_report();
68428
68428
  init_attack_surface_report();
68429
- init_domains2();
68430
- init_domains2();
68429
+ init_domains4();
68430
+ init_domains4();
68431
68431
  init_indicator_feeds();
68432
68432
  init_indicator_feeds();
68433
68433
  Intel = class Intel extends APIResource {
@@ -71305,7 +71305,7 @@ var init_page_shield = __esm(() => {
71305
71305
 
71306
71306
  // ../../node_modules/cloudflare/resources/pages/projects/domains.mjs
71307
71307
  var Domains3, DomainListResponsesSinglePage;
71308
- var init_domains3 = __esm(() => {
71308
+ var init_domains5 = __esm(() => {
71309
71309
  init_pagination();
71310
71310
  Domains3 = class Domains3 extends APIResource {
71311
71311
  create(projectName, params, options) {
@@ -71408,8 +71408,8 @@ var init_deployments = __esm(() => {
71408
71408
  // ../../node_modules/cloudflare/resources/pages/projects/projects.mjs
71409
71409
  var Projects, DeploymentsSinglePage;
71410
71410
  var init_projects = __esm(() => {
71411
- init_domains3();
71412
- init_domains3();
71411
+ init_domains5();
71412
+ init_domains5();
71413
71413
  init_deployments();
71414
71414
  init_deployments();
71415
71415
  init_pagination();
@@ -71976,7 +71976,7 @@ var init_managed = __esm(() => {
71976
71976
 
71977
71977
  // ../../node_modules/cloudflare/resources/r2/buckets/domains/domains.mjs
71978
71978
  var Domains4;
71979
- var init_domains4 = __esm(() => {
71979
+ var init_domains6 = __esm(() => {
71980
71980
  init_custom5();
71981
71981
  init_custom5();
71982
71982
  init_managed();
@@ -72007,8 +72007,8 @@ var init_buckets = __esm(() => {
72007
72007
  init_metrics();
72008
72008
  init_sippy();
72009
72009
  init_sippy();
72010
- init_domains4();
72011
- init_domains4();
72010
+ init_domains6();
72011
+ init_domains6();
72012
72012
  Buckets = class Buckets extends APIResource {
72013
72013
  constructor() {
72014
72014
  super(...arguments);
@@ -75360,7 +75360,7 @@ var init_rate_limits = __esm(() => {
75360
75360
 
75361
75361
  // ../../node_modules/cloudflare/resources/registrar/domains.mjs
75362
75362
  var Domains5, DomainsSinglePage;
75363
- var init_domains5 = __esm(() => {
75363
+ var init_domains7 = __esm(() => {
75364
75364
  init_pagination();
75365
75365
  Domains5 = class Domains5 extends APIResource {
75366
75366
  update(domainName, params, options) {
@@ -75387,8 +75387,8 @@ var init_domains5 = __esm(() => {
75387
75387
  // ../../node_modules/cloudflare/resources/registrar/registrar.mjs
75388
75388
  var Registrar;
75389
75389
  var init_registrar = __esm(() => {
75390
- init_domains5();
75391
- init_domains5();
75390
+ init_domains7();
75391
+ init_domains7();
75392
75392
  Registrar = class Registrar extends APIResource {
75393
75393
  constructor() {
75394
75394
  super(...arguments);
@@ -78354,7 +78354,7 @@ var init_account_settings = __esm(() => {
78354
78354
 
78355
78355
  // ../../node_modules/cloudflare/resources/workers/domains.mjs
78356
78356
  var Domains6, DomainsSinglePage2;
78357
- var init_domains6 = __esm(() => {
78357
+ var init_domains8 = __esm(() => {
78358
78358
  init_pagination();
78359
78359
  Domains6 = class Domains6 extends APIResource {
78360
78360
  update(params, options) {
@@ -78496,7 +78496,7 @@ var init_versions3 = __esm(() => {
78496
78496
 
78497
78497
  // ../../node_modules/cloudflare/resources/workers/beta/workers/workers.mjs
78498
78498
  var Workers, WorkersV4PagePaginationArray;
78499
- var init_workers = __esm(() => {
78499
+ var init_workers3 = __esm(() => {
78500
78500
  init_versions3();
78501
78501
  init_versions3();
78502
78502
  init_pagination();
@@ -78550,8 +78550,8 @@ var init_workers = __esm(() => {
78550
78550
  // ../../node_modules/cloudflare/resources/workers/beta/beta.mjs
78551
78551
  var Beta;
78552
78552
  var init_beta = __esm(() => {
78553
- init_workers();
78554
- init_workers();
78553
+ init_workers3();
78554
+ init_workers3();
78555
78555
  Beta = class Beta extends APIResource {
78556
78556
  constructor() {
78557
78557
  super(...arguments);
@@ -78943,11 +78943,11 @@ var init_scripts2 = __esm(() => {
78943
78943
 
78944
78944
  // ../../node_modules/cloudflare/resources/workers/workers.mjs
78945
78945
  var Workers2;
78946
- var init_workers2 = __esm(() => {
78946
+ var init_workers4 = __esm(() => {
78947
78947
  init_account_settings();
78948
78948
  init_account_settings();
78949
- init_domains6();
78950
- init_domains6();
78949
+ init_domains8();
78950
+ init_domains8();
78951
78951
  init_routes3();
78952
78952
  init_routes3();
78953
78953
  init_subdomains();
@@ -83972,7 +83972,7 @@ var init_resources3 = __esm(() => {
83972
83972
  init_vectorize();
83973
83973
  init_waiting_rooms();
83974
83974
  init_web3();
83975
- init_workers2();
83975
+ init_workers4();
83976
83976
  init_workers_for_platforms();
83977
83977
  init_workflows();
83978
83978
  init_zaraz();
@@ -84079,7 +84079,7 @@ var init_cloudflare = __esm(() => {
84079
84079
  init_waiting_rooms();
84080
84080
  init_web3();
84081
84081
  init_workers_for_platforms();
84082
- init_workers2();
84082
+ init_workers4();
84083
84083
  init_workflows();
84084
84084
  init_zaraz();
84085
84085
  init_zero_trust();
@@ -84548,7 +84548,7 @@ var require_dist_cjs2 = __commonJS((exports, module2) => {
84548
84548
  Fields: () => Fields3,
84549
84549
  HttpRequest: () => HttpRequest,
84550
84550
  HttpResponse: () => HttpResponse,
84551
- IHttpRequest: () => import_types4.HttpRequest,
84551
+ IHttpRequest: () => import_types6.HttpRequest,
84552
84552
  getHttpHandlerExtensionConfiguration: () => getHttpHandlerExtensionConfiguration,
84553
84553
  isValidHostname: () => isValidHostname,
84554
84554
  resolveHttpHandlerRuntimeConfig: () => resolveHttpHandlerRuntimeConfig
@@ -84575,12 +84575,12 @@ var require_dist_cjs2 = __commonJS((exports, module2) => {
84575
84575
  httpHandler: httpHandlerExtensionConfiguration.httpHandler()
84576
84576
  };
84577
84577
  }, "resolveHttpHandlerRuntimeConfig");
84578
- var import_types4 = require_dist_cjs();
84578
+ var import_types6 = require_dist_cjs();
84579
84579
  var Field = class {
84580
84580
  static {
84581
84581
  __name(this, "Field");
84582
84582
  }
84583
- constructor({ name: name4, kind: kind2 = import_types4.FieldPosition.HEADER, values = [] }) {
84583
+ constructor({ name: name4, kind: kind2 = import_types6.FieldPosition.HEADER, values = [] }) {
84584
84584
  this.name = name4;
84585
84585
  this.kind = kind2;
84586
84586
  this.values = values;
@@ -85449,8 +85449,8 @@ var require_dist_cjs4 = __commonJS((exports, module2) => {
85449
85449
  normalizeProvider: () => normalizeProvider
85450
85450
  });
85451
85451
  module2.exports = __toCommonJS(src_exports);
85452
- var import_types4 = require_dist_cjs();
85453
- var getSmithyContext = /* @__PURE__ */ __name((context) => context[import_types4.SMITHY_CONTEXT_KEY] || (context[import_types4.SMITHY_CONTEXT_KEY] = {}), "getSmithyContext");
85452
+ var import_types6 = require_dist_cjs();
85453
+ var getSmithyContext = /* @__PURE__ */ __name((context) => context[import_types6.SMITHY_CONTEXT_KEY] || (context[import_types6.SMITHY_CONTEXT_KEY] = {}), "getSmithyContext");
85454
85454
  var normalizeProvider = /* @__PURE__ */ __name((input) => {
85455
85455
  if (typeof input === "function")
85456
85456
  return input;
@@ -89530,8 +89530,8 @@ var require_dist_cjs16 = __commonJS((exports, module2) => {
89530
89530
  setFeature: () => setFeature
89531
89531
  });
89532
89532
  module2.exports = __toCommonJS(src_exports);
89533
- var import_types4 = require_dist_cjs();
89534
- var getSmithyContext = /* @__PURE__ */ __name((context) => context[import_types4.SMITHY_CONTEXT_KEY] || (context[import_types4.SMITHY_CONTEXT_KEY] = {}), "getSmithyContext");
89533
+ var import_types6 = require_dist_cjs();
89534
+ var getSmithyContext = /* @__PURE__ */ __name((context) => context[import_types6.SMITHY_CONTEXT_KEY] || (context[import_types6.SMITHY_CONTEXT_KEY] = {}), "getSmithyContext");
89535
89535
  var import_util_middleware = require_dist_cjs4();
89536
89536
  var resolveAuthOptions = /* @__PURE__ */ __name((candidateAuthOptions, authSchemePreference) => {
89537
89537
  if (!authSchemePreference || authSchemePreference.length === 0) {
@@ -89767,9 +89767,9 @@ var require_dist_cjs16 = __commonJS((exports, module2) => {
89767
89767
  throw new Error("request could not be signed with `apiKey` since the `apiKey` is not defined");
89768
89768
  }
89769
89769
  const clonedRequest = import_protocol_http.HttpRequest.clone(httpRequest);
89770
- if (signingProperties.in === import_types4.HttpApiKeyAuthLocation.QUERY) {
89770
+ if (signingProperties.in === import_types6.HttpApiKeyAuthLocation.QUERY) {
89771
89771
  clonedRequest.query[signingProperties.name] = identity.apiKey;
89772
- } else if (signingProperties.in === import_types4.HttpApiKeyAuthLocation.HEADER) {
89772
+ } else if (signingProperties.in === import_types6.HttpApiKeyAuthLocation.HEADER) {
89773
89773
  clonedRequest.headers[signingProperties.name] = signingProperties.scheme ? `${signingProperties.scheme} ${identity.apiKey}` : identity.apiKey;
89774
89774
  } else {
89775
89775
  throw new Error("request can only be signed with `apiKey` locations `query` or `header`, but found: `" + signingProperties.in + "`");
@@ -90852,7 +90852,7 @@ var require_httpAuthSchemes = __commonJS((exports, module2) => {
90852
90852
  },
90853
90853
  default: undefined
90854
90854
  };
90855
- var import_client = require_client();
90855
+ var import_client2 = require_client();
90856
90856
  var import_core210 = require_dist_cjs16();
90857
90857
  var import_signature_v4 = require_dist_cjs20();
90858
90858
  var resolveAwsSdkSigV4Config = /* @__PURE__ */ __name((config2) => {
@@ -90871,7 +90871,7 @@ var require_httpAuthSchemes = __commonJS((exports, module2) => {
90871
90871
  });
90872
90872
  const boundProvider = bindCallerConfig(config2, memoizedProvider);
90873
90873
  if (isUserSupplied && !boundProvider.attributed) {
90874
- resolvedCredentials = /* @__PURE__ */ __name(async (options) => boundProvider(options).then((creds) => (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_CODE", "e")), "resolvedCredentials");
90874
+ resolvedCredentials = /* @__PURE__ */ __name(async (options) => boundProvider(options).then((creds) => (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_CODE", "e")), "resolvedCredentials");
90875
90875
  resolvedCredentials.memoized = boundProvider.memoized;
90876
90876
  resolvedCredentials.configBound = boundProvider.configBound;
90877
90877
  resolvedCredentials.attributed = true;
@@ -91419,7 +91419,7 @@ var require_dist_cjs23 = __commonJS((exports, module2) => {
91419
91419
  }
91420
91420
  };
91421
91421
  var import_protocols = require_protocols();
91422
- var import_types4 = require_dist_cjs();
91422
+ var import_types6 = require_dist_cjs();
91423
91423
  var Command = class {
91424
91424
  constructor() {
91425
91425
  this.middlewareStack = (0, import_middleware_stack.constructStack)();
@@ -91451,7 +91451,7 @@ var require_dist_cjs23 = __commonJS((exports, module2) => {
91451
91451
  commandName,
91452
91452
  inputFilterSensitiveLog,
91453
91453
  outputFilterSensitiveLog,
91454
- [import_types4.SMITHY_CONTEXT_KEY]: {
91454
+ [import_types6.SMITHY_CONTEXT_KEY]: {
91455
91455
  commandInstance: this,
91456
91456
  ...smithyContext
91457
91457
  },
@@ -91676,8 +91676,8 @@ var require_dist_cjs23 = __commonJS((exports, module2) => {
91676
91676
  }, "emitWarningIfUnsupportedVersion");
91677
91677
  var getChecksumConfiguration = /* @__PURE__ */ __name((runtimeConfig) => {
91678
91678
  const checksumAlgorithms = [];
91679
- for (const id in import_types4.AlgorithmId) {
91680
- const algorithmId = import_types4.AlgorithmId[id];
91679
+ for (const id in import_types6.AlgorithmId) {
91680
+ const algorithmId = import_types6.AlgorithmId[id];
91681
91681
  if (runtimeConfig[algorithmId] === undefined) {
91682
91682
  continue;
91683
91683
  }
@@ -93861,7 +93861,7 @@ var require_protocols2 = __commonJS((exports, module2) => {
93861
93861
  return this._read(schema4, data);
93862
93862
  }
93863
93863
  _read(schema4, value) {
93864
- const isObject2 = value !== null && typeof value === "object";
93864
+ const isObject4 = value !== null && typeof value === "object";
93865
93865
  const ns = import_schema2.NormalizedSchema.of(schema4);
93866
93866
  if (ns.isListSchema() && Array.isArray(value)) {
93867
93867
  const listMember = ns.getValueSchema();
@@ -93873,7 +93873,7 @@ var require_protocols2 = __commonJS((exports, module2) => {
93873
93873
  }
93874
93874
  }
93875
93875
  return out2;
93876
- } else if (ns.isMapSchema() && isObject2) {
93876
+ } else if (ns.isMapSchema() && isObject4) {
93877
93877
  const mapMember = ns.getValueSchema();
93878
93878
  const out2 = {};
93879
93879
  const sparse = !!ns.getMergedTraits().sparse;
@@ -93883,7 +93883,7 @@ var require_protocols2 = __commonJS((exports, module2) => {
93883
93883
  }
93884
93884
  }
93885
93885
  return out2;
93886
- } else if (ns.isStructSchema() && isObject2) {
93886
+ } else if (ns.isStructSchema() && isObject4) {
93887
93887
  const out2 = {};
93888
93888
  for (const [memberName, memberSchema] of ns.structIterator()) {
93889
93889
  const fromKey = this.settings.jsonName ? memberSchema.getMergedTraits().jsonName ?? memberName : memberName;
@@ -94015,7 +94015,7 @@ var require_protocols2 = __commonJS((exports, module2) => {
94015
94015
  return this.buffer;
94016
94016
  }
94017
94017
  _write(schema4, value, container) {
94018
- const isObject2 = value !== null && typeof value === "object";
94018
+ const isObject4 = value !== null && typeof value === "object";
94019
94019
  const ns = import_schema22.NormalizedSchema.of(schema4);
94020
94020
  if (ns.isListSchema() && Array.isArray(value)) {
94021
94021
  const listMember = ns.getValueSchema();
@@ -94027,7 +94027,7 @@ var require_protocols2 = __commonJS((exports, module2) => {
94027
94027
  }
94028
94028
  }
94029
94029
  return out2;
94030
- } else if (ns.isMapSchema() && isObject2) {
94030
+ } else if (ns.isMapSchema() && isObject4) {
94031
94031
  const mapMember = ns.getValueSchema();
94032
94032
  const out2 = {};
94033
94033
  const sparse = !!ns.getMergedTraits().sparse;
@@ -94037,7 +94037,7 @@ var require_protocols2 = __commonJS((exports, module2) => {
94037
94037
  }
94038
94038
  }
94039
94039
  return out2;
94040
- } else if (ns.isStructSchema() && isObject2) {
94040
+ } else if (ns.isStructSchema() && isObject4) {
94041
94041
  const out2 = {};
94042
94042
  for (const [memberName, memberSchema] of ns.structIterator()) {
94043
94043
  const targetKey = this.settings.jsonName ? memberSchema.getMergedTraits().jsonName ?? memberName : memberName;
@@ -101587,16 +101587,16 @@ var require_dist_cjs52 = __commonJS((exports, module2) => {
101587
101587
  var getProfileName = /* @__PURE__ */ __name((init3) => init3.profile || process.env[ENV_PROFILE] || DEFAULT_PROFILE, "getProfileName");
101588
101588
  __reExport(src_exports, require_getSSOTokenFilepath(), module2.exports);
101589
101589
  __reExport(src_exports, require_getSSOTokenFromFile(), module2.exports);
101590
- var import_types4 = require_dist_cjs();
101590
+ var import_types6 = require_dist_cjs();
101591
101591
  var getConfigData = /* @__PURE__ */ __name((data) => Object.entries(data).filter(([key]) => {
101592
101592
  const indexOfSeparator = key.indexOf(CONFIG_PREFIX_SEPARATOR);
101593
101593
  if (indexOfSeparator === -1) {
101594
101594
  return false;
101595
101595
  }
101596
- return Object.values(import_types4.IniSectionType).includes(key.substring(0, indexOfSeparator));
101596
+ return Object.values(import_types6.IniSectionType).includes(key.substring(0, indexOfSeparator));
101597
101597
  }).reduce((acc, [key, value]) => {
101598
101598
  const indexOfSeparator = key.indexOf(CONFIG_PREFIX_SEPARATOR);
101599
- const updatedKey = key.substring(0, indexOfSeparator) === import_types4.IniSectionType.PROFILE ? key.substring(indexOfSeparator + 1) : key;
101599
+ const updatedKey = key.substring(0, indexOfSeparator) === import_types6.IniSectionType.PROFILE ? key.substring(indexOfSeparator + 1) : key;
101600
101600
  acc[updatedKey] = value;
101601
101601
  return acc;
101602
101602
  }, {
@@ -101626,7 +101626,7 @@ var require_dist_cjs52 = __commonJS((exports, module2) => {
101626
101626
  const matches = prefixKeyRegex.exec(sectionName);
101627
101627
  if (matches) {
101628
101628
  const [, prefix2, , name4] = matches;
101629
- if (Object.values(import_types4.IniSectionType).includes(prefix2)) {
101629
+ if (Object.values(import_types6.IniSectionType).includes(prefix2)) {
101630
101630
  currentSection = [prefix2, name4].join(CONFIG_PREFIX_SEPARATOR);
101631
101631
  }
101632
101632
  } else {
@@ -101685,7 +101685,7 @@ var require_dist_cjs52 = __commonJS((exports, module2) => {
101685
101685
  credentialsFile: parsedFiles[1]
101686
101686
  };
101687
101687
  }, "loadSharedConfigFiles");
101688
- var getSsoSessionData = /* @__PURE__ */ __name((data) => Object.entries(data).filter(([key]) => key.startsWith(import_types4.IniSectionType.SSO_SESSION + CONFIG_PREFIX_SEPARATOR)).reduce((acc, [key, value]) => ({ ...acc, [key.substring(key.indexOf(CONFIG_PREFIX_SEPARATOR) + 1)]: value }), {}), "getSsoSessionData");
101688
+ var getSsoSessionData = /* @__PURE__ */ __name((data) => Object.entries(data).filter(([key]) => key.startsWith(import_types6.IniSectionType.SSO_SESSION + CONFIG_PREFIX_SEPARATOR)).reduce((acc, [key, value]) => ({ ...acc, [key.substring(key.indexOf(CONFIG_PREFIX_SEPARATOR) + 1)]: value }), {}), "getSsoSessionData");
101689
101689
  var import_slurpFile2 = require_slurpFile();
101690
101690
  var swallowError2 = /* @__PURE__ */ __name(() => ({}), "swallowError");
101691
101691
  var loadSsoSessionData = /* @__PURE__ */ __name(async (init3 = {}) => (0, import_slurpFile2.slurpFile)(init3.configFilepath ?? getConfigFilepath()).then(parseIni).then(getSsoSessionData).catch(swallowError2), "loadSsoSessionData");
@@ -102684,7 +102684,7 @@ var require_dist_cjs57 = __commonJS((exports, module2) => {
102684
102684
  fromEnv: () => fromEnv
102685
102685
  });
102686
102686
  module2.exports = __toCommonJS(index_exports);
102687
- var import_client = require_client();
102687
+ var import_client2 = require_client();
102688
102688
  var import_property_provider = require_dist_cjs17();
102689
102689
  var ENV_KEY = "AWS_ACCESS_KEY_ID";
102690
102690
  var ENV_SECRET = "AWS_SECRET_ACCESS_KEY";
@@ -102709,7 +102709,7 @@ var require_dist_cjs57 = __commonJS((exports, module2) => {
102709
102709
  ...credentialScope && { credentialScope },
102710
102710
  ...accountId && { accountId }
102711
102711
  };
102712
- (0, import_client.setCredentialFeature)(credentials2, "CREDENTIALS_ENV_VARS", "g");
102712
+ (0, import_client2.setCredentialFeature)(credentials2, "CREDENTIALS_ENV_VARS", "g");
102713
102713
  return credentials2;
102714
102714
  }
102715
102715
  throw new import_property_provider.CredentialsProviderError("Unable to find environment variable credentials.", { logger: init3?.logger });
@@ -105630,7 +105630,7 @@ var require_dist_cjs67 = __commonJS((exports, module2) => {
105630
105630
  var __getOwnPropNames3 = Object.getOwnPropertyNames;
105631
105631
  var __hasOwnProp3 = Object.prototype.hasOwnProperty;
105632
105632
  var __name = (target, value) => __defProp4(target, "name", { value, configurable: true });
105633
- var __esm3 = (fn2, res) => function __init() {
105633
+ var __esm5 = (fn2, res) => function __init() {
105634
105634
  return fn2 && (res = (0, fn2[__getOwnPropNames3(fn2)[0]])(fn2 = 0)), res;
105635
105635
  };
105636
105636
  var __export4 = (target, all) => {
@@ -105652,7 +105652,7 @@ var require_dist_cjs67 = __commonJS((exports, module2) => {
105652
105652
  SSOClient: () => import_client_sso.SSOClient
105653
105653
  });
105654
105654
  var import_client_sso;
105655
- var init_loadSso = __esm3({
105655
+ var init_loadSso = __esm5({
105656
105656
  "src/loadSso.ts"() {
105657
105657
  import_client_sso = require_dist_cjs65();
105658
105658
  }
@@ -105665,7 +105665,7 @@ var require_dist_cjs67 = __commonJS((exports, module2) => {
105665
105665
  });
105666
105666
  module2.exports = __toCommonJS(index_exports);
105667
105667
  var isSsoProfile = /* @__PURE__ */ __name((arg) => arg && (typeof arg.sso_start_url === "string" || typeof arg.sso_account_id === "string" || typeof arg.sso_session === "string" || typeof arg.sso_region === "string" || typeof arg.sso_role_name === "string"), "isSsoProfile");
105668
- var import_client = require_client();
105668
+ var import_client2 = require_client();
105669
105669
  var import_token_providers = require_dist_cjs66();
105670
105670
  var import_property_provider = require_dist_cjs17();
105671
105671
  var import_shared_ini_file_loader = require_dist_cjs52();
@@ -105750,9 +105750,9 @@ var require_dist_cjs67 = __commonJS((exports, module2) => {
105750
105750
  ...accountId && { accountId }
105751
105751
  };
105752
105752
  if (ssoSession) {
105753
- (0, import_client.setCredentialFeature)(credentials2, "CREDENTIALS_SSO", "s");
105753
+ (0, import_client2.setCredentialFeature)(credentials2, "CREDENTIALS_SSO", "s");
105754
105754
  } else {
105755
- (0, import_client.setCredentialFeature)(credentials2, "CREDENTIALS_SSO_LEGACY", "u");
105755
+ (0, import_client2.setCredentialFeature)(credentials2, "CREDENTIALS_SSO_LEGACY", "u");
105756
105756
  }
105757
105757
  return credentials2;
105758
105758
  }, "resolveSSOCredentials");
@@ -106952,7 +106952,7 @@ var require_sts = __commonJS((exports, module2) => {
106952
106952
  };
106953
106953
  (0, import_smithy_client6.createAggregatedClient)(commands, STS);
106954
106954
  var import_EndpointParameters3 = require_EndpointParameters();
106955
- var import_client = require_client();
106955
+ var import_client2 = require_client();
106956
106956
  var ASSUME_ROLE_DEFAULT_REGION = "us-east-1";
106957
106957
  var getAccountIdFromAssumedRoleUser = /* @__PURE__ */ __name((assumedRoleUser) => {
106958
106958
  if (typeof assumedRoleUser?.Arn === "string") {
@@ -107004,7 +107004,7 @@ var require_sts = __commonJS((exports, module2) => {
107004
107004
  ...Credentials2.CredentialScope && { credentialScope: Credentials2.CredentialScope },
107005
107005
  ...accountId && { accountId }
107006
107006
  };
107007
- (0, import_client.setCredentialFeature)(credentials2, "CREDENTIALS_STS_ASSUME_ROLE", "i");
107007
+ (0, import_client2.setCredentialFeature)(credentials2, "CREDENTIALS_STS_ASSUME_ROLE", "i");
107008
107008
  return credentials2;
107009
107009
  };
107010
107010
  }, "getDefaultRoleAssumer");
@@ -107041,9 +107041,9 @@ var require_sts = __commonJS((exports, module2) => {
107041
107041
  ...accountId && { accountId }
107042
107042
  };
107043
107043
  if (accountId) {
107044
- (0, import_client.setCredentialFeature)(credentials2, "RESOLVED_ACCOUNT_ID", "T");
107044
+ (0, import_client2.setCredentialFeature)(credentials2, "RESOLVED_ACCOUNT_ID", "T");
107045
107045
  }
107046
- (0, import_client.setCredentialFeature)(credentials2, "CREDENTIALS_STS_ASSUME_ROLE_WEB_ID", "k");
107046
+ (0, import_client2.setCredentialFeature)(credentials2, "CREDENTIALS_STS_ASSUME_ROLE_WEB_ID", "k");
107047
107047
  return credentials2;
107048
107048
  };
107049
107049
  }, "getDefaultRoleAssumerWithWebIdentity");
@@ -107105,7 +107105,7 @@ var require_dist_cjs68 = __commonJS((exports, module2) => {
107105
107105
  var import_property_provider = require_dist_cjs17();
107106
107106
  var import_child_process = __require("child_process");
107107
107107
  var import_util3 = __require("util");
107108
- var import_client = require_client();
107108
+ var import_client2 = require_client();
107109
107109
  var getValidatedProcessCredentials = /* @__PURE__ */ __name((profileName, data, profiles) => {
107110
107110
  if (data.Version !== 1) {
107111
107111
  throw Error(`Profile ${profileName} credential_process did not return Version 1.`);
@@ -107132,7 +107132,7 @@ var require_dist_cjs68 = __commonJS((exports, module2) => {
107132
107132
  ...data.CredentialScope && { credentialScope: data.CredentialScope },
107133
107133
  ...accountId && { accountId }
107134
107134
  };
107135
- (0, import_client.setCredentialFeature)(credentials2, "CREDENTIALS_PROCESS", "w");
107135
+ (0, import_client2.setCredentialFeature)(credentials2, "CREDENTIALS_PROCESS", "w");
107136
107136
  return credentials2;
107137
107137
  }, "getValidatedProcessCredentials");
107138
107138
  var resolveProcessCredentials = /* @__PURE__ */ __name(async (profileName, profiles, logger3) => {
@@ -107333,7 +107333,7 @@ var require_dist_cjs70 = __commonJS((exports, module2) => {
107333
107333
  });
107334
107334
  module2.exports = __toCommonJS(index_exports);
107335
107335
  var import_shared_ini_file_loader = require_dist_cjs52();
107336
- var import_client = require_client();
107336
+ var import_client2 = require_client();
107337
107337
  var import_property_provider = require_dist_cjs17();
107338
107338
  var resolveCredentialSource = /* @__PURE__ */ __name((credentialSource, profileName, logger3) => {
107339
107339
  const sourceProvidersMap = {
@@ -107360,7 +107360,7 @@ var require_dist_cjs70 = __commonJS((exports, module2) => {
107360
107360
  throw new import_property_provider.CredentialsProviderError(`Unsupported credential source in profile ${profileName}. Got ${credentialSource}, expected EcsContainer or Ec2InstanceMetadata or Environment.`, { logger: logger3 });
107361
107361
  }
107362
107362
  }, "resolveCredentialSource");
107363
- var setNamedProvider = /* @__PURE__ */ __name((creds) => (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_NAMED_PROVIDER", "p"), "setNamedProvider");
107363
+ var setNamedProvider = /* @__PURE__ */ __name((creds) => (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_NAMED_PROVIDER", "p"), "setNamedProvider");
107364
107364
  var isAssumeRoleProfile = /* @__PURE__ */ __name((arg, { profile = "default", logger: logger3 } = {}) => {
107365
107365
  return Boolean(arg) && typeof arg === "object" && typeof arg.role_arn === "string" && ["undefined", "string"].indexOf(typeof arg.role_session_name) > -1 && ["undefined", "string"].indexOf(typeof arg.external_id) > -1 && ["undefined", "string"].indexOf(typeof arg.mfa_serial) > -1 && (isAssumeRoleWithSourceProfile(arg, { profile, logger: logger3 }) || isCredentialSourceProfile(arg, { profile, logger: logger3 }));
107366
107366
  }, "isAssumeRoleProfile");
@@ -107402,7 +107402,7 @@ var require_dist_cjs70 = __commonJS((exports, module2) => {
107402
107402
  [source_profile]: true
107403
107403
  }, isCredentialSourceWithoutRoleArn(profiles[source_profile] ?? {})) : (await resolveCredentialSource(profileData.credential_source, profileName, options.logger)(options))();
107404
107404
  if (isCredentialSourceWithoutRoleArn(profileData)) {
107405
- return sourceCredsProvider.then((creds) => (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SOURCE_PROFILE", "o"));
107405
+ return sourceCredsProvider.then((creds) => (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SOURCE_PROFILE", "o"));
107406
107406
  } else {
107407
107407
  const params = {
107408
107408
  RoleArn: profileData.role_arn,
@@ -107419,7 +107419,7 @@ var require_dist_cjs70 = __commonJS((exports, module2) => {
107419
107419
  params.TokenCode = await options.mfaCodeProvider(mfa_serial);
107420
107420
  }
107421
107421
  const sourceCreds = await sourceCredsProvider;
107422
- return options.roleAssumer(sourceCreds, params).then((creds) => (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SOURCE_PROFILE", "o"));
107422
+ return options.roleAssumer(sourceCreds, params).then((creds) => (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SOURCE_PROFILE", "o"));
107423
107423
  }
107424
107424
  }, "resolveAssumeRoleCredentials");
107425
107425
  var isCredentialSourceWithoutRoleArn = /* @__PURE__ */ __name((section) => {
@@ -107429,7 +107429,7 @@ var require_dist_cjs70 = __commonJS((exports, module2) => {
107429
107429
  var resolveProcessCredentials = /* @__PURE__ */ __name(async (options, profile) => Promise.resolve().then(() => __toESM3(require_dist_cjs68())).then(({ fromProcess }) => fromProcess({
107430
107430
  ...options,
107431
107431
  profile
107432
- })().then((creds) => (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_PROCESS", "v"))), "resolveProcessCredentials");
107432
+ })().then((creds) => (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_PROCESS", "v"))), "resolveProcessCredentials");
107433
107433
  var resolveSsoCredentials = /* @__PURE__ */ __name(async (profile, profileData, options = {}) => {
107434
107434
  const { fromSSO } = await Promise.resolve().then(() => __toESM3(require_dist_cjs67()));
107435
107435
  return fromSSO({
@@ -107439,9 +107439,9 @@ var require_dist_cjs70 = __commonJS((exports, module2) => {
107439
107439
  clientConfig: options.clientConfig
107440
107440
  })().then((creds) => {
107441
107441
  if (profileData.sso_session) {
107442
- return (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SSO", "r");
107442
+ return (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SSO", "r");
107443
107443
  } else {
107444
- return (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SSO_LEGACY", "t");
107444
+ return (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SSO_LEGACY", "t");
107445
107445
  }
107446
107446
  });
107447
107447
  }, "resolveSsoCredentials");
@@ -107456,7 +107456,7 @@ var require_dist_cjs70 = __commonJS((exports, module2) => {
107456
107456
  ...profile.aws_credential_scope && { credentialScope: profile.aws_credential_scope },
107457
107457
  ...profile.aws_account_id && { accountId: profile.aws_account_id }
107458
107458
  };
107459
- return (0, import_client.setCredentialFeature)(credentials2, "CREDENTIALS_PROFILE", "n");
107459
+ return (0, import_client2.setCredentialFeature)(credentials2, "CREDENTIALS_PROFILE", "n");
107460
107460
  }, "resolveStaticCredentials");
107461
107461
  var isWebIdentityProfile = /* @__PURE__ */ __name((arg) => Boolean(arg) && typeof arg === "object" && typeof arg.web_identity_token_file === "string" && typeof arg.role_arn === "string" && ["undefined", "string"].indexOf(typeof arg.role_session_name) > -1, "isWebIdentityProfile");
107462
107462
  var resolveWebIdentityCredentials = /* @__PURE__ */ __name(async (profile, options) => Promise.resolve().then(() => __toESM3(require_dist_cjs69())).then(({ fromTokenFile: fromTokenFile2 }) => fromTokenFile2({
@@ -107466,7 +107466,7 @@ var require_dist_cjs70 = __commonJS((exports, module2) => {
107466
107466
  roleAssumerWithWebIdentity: options.roleAssumerWithWebIdentity,
107467
107467
  logger: options.logger,
107468
107468
  parentClientConfig: options.parentClientConfig
107469
- })().then((creds) => (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_STS_WEB_ID_TOKEN", "q"))), "resolveWebIdentityCredentials");
107469
+ })().then((creds) => (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_STS_WEB_ID_TOKEN", "q"))), "resolveWebIdentityCredentials");
107470
107470
  var resolveProfileData = /* @__PURE__ */ __name(async (profileName, profiles, options, visitedProfiles = {}, isAssumeRoleRecursiveCall = false) => {
107471
107471
  const data = profiles[profileName];
107472
107472
  if (Object.keys(visitedProfiles).length > 0 && isStaticCredsProfile(data)) {
@@ -126308,7 +126308,7 @@ var require_lodash2 = __commonJS((exports, module2) => {
126308
126308
  }
126309
126309
  }
126310
126310
  function baseKeysIn(object) {
126311
- if (!isObject2(object)) {
126311
+ if (!isObject4(object)) {
126312
126312
  return nativeKeysIn(object);
126313
126313
  }
126314
126314
  var isProto = isPrototype(object), result = [];
@@ -126368,7 +126368,7 @@ var require_lodash2 = __commonJS((exports, module2) => {
126368
126368
  return !!length && (typeof value == "number" || reIsUint.test(value)) && (value > -1 && value % 1 == 0 && value < length);
126369
126369
  }
126370
126370
  function isIterateeCall(value, index6, object) {
126371
- if (!isObject2(object)) {
126371
+ if (!isObject4(object)) {
126372
126372
  return false;
126373
126373
  }
126374
126374
  var type = typeof index6;
@@ -126404,13 +126404,13 @@ var require_lodash2 = __commonJS((exports, module2) => {
126404
126404
  return isObjectLike2(value) && isArrayLike(value);
126405
126405
  }
126406
126406
  function isFunction3(value) {
126407
- var tag2 = isObject2(value) ? objectToString.call(value) : "";
126407
+ var tag2 = isObject4(value) ? objectToString.call(value) : "";
126408
126408
  return tag2 == funcTag || tag2 == genTag;
126409
126409
  }
126410
126410
  function isLength(value) {
126411
126411
  return typeof value == "number" && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
126412
126412
  }
126413
- function isObject2(value) {
126413
+ function isObject4(value) {
126414
126414
  var type = typeof value;
126415
126415
  return !!value && (type == "object" || type == "function");
126416
126416
  }
@@ -126450,13 +126450,13 @@ var require_lodash3 = __commonJS((exports, module2) => {
126450
126450
  return isObjectLike2(value) && isArrayLike(value);
126451
126451
  }
126452
126452
  function isFunction3(value) {
126453
- var tag2 = isObject2(value) ? objectToString.call(value) : "";
126453
+ var tag2 = isObject4(value) ? objectToString.call(value) : "";
126454
126454
  return tag2 == funcTag || tag2 == genTag;
126455
126455
  }
126456
126456
  function isLength(value) {
126457
126457
  return typeof value == "number" && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
126458
126458
  }
126459
- function isObject2(value) {
126459
+ function isObject4(value) {
126460
126460
  var type = typeof value;
126461
126461
  return !!value && (type == "object" || type == "function");
126462
126462
  }
@@ -131607,7 +131607,7 @@ var require_built3 = __commonJS((exports, module2) => {
131607
131607
 
131608
131608
  // ../realtime/src/server/domain/constants.ts
131609
131609
  var HUB_PREFIX, MAP_PREFIX, PRESENCE_CHANNEL_NAME, DEFAULT_GAME_CHANNEL, WebSocketCloseCode, WebSocketCloseReason, REDIS_PRESENCE_TTL = 300, REDIS_RECONNECTION_ATTEMPTS = 10, REDIS_MAX_RECONNECTION_DELAY = 3000, REDIS_HASH_TAG_PRESENCE = "{presence}", REDIS_HASH_TAG_POSITION = "{position}", REDIS_PRESENCE_KEY_PREFIX, REDIS_ONLINE_USERS_KEY, REDIS_POSITION_KEY_PREFIX, REDIS_MAP_PLAYERS_KEY_PREFIX;
131610
- var init_constants = __esm(() => {
131610
+ var init_constants3 = __esm(() => {
131611
131611
  HUB_PREFIX = process.env.HUB_PREFIX ?? "hub:";
131612
131612
  MAP_PREFIX = process.env.MAP_PREFIX ?? "map_";
131613
131613
  PRESENCE_CHANNEL_NAME = process.env.PRESENCE_CHANNEL_NAME ?? "presence";
@@ -131756,7 +131756,7 @@ function isRedisConnected() {
131756
131756
  var import_ioredis, client = null;
131757
131757
  var init_client = __esm(() => {
131758
131758
  init_src();
131759
- init_constants();
131759
+ init_constants3();
131760
131760
  import_ioredis = __toESM(require_built3(), 1);
131761
131761
  });
131762
131762
 
@@ -131869,7 +131869,7 @@ async function updateUserNameplateBg(userId, color) {
131869
131869
  }
131870
131870
  var init_presence = __esm(() => {
131871
131871
  init_src();
131872
- init_constants();
131872
+ init_constants3();
131873
131873
  init_client();
131874
131874
  });
131875
131875
 
@@ -131920,7 +131920,7 @@ function extractTokenFromUrl(url) {
131920
131920
  return null;
131921
131921
  }
131922
131922
  }
131923
- var init_auth = __esm(() => {
131923
+ var init_auth3 = __esm(() => {
131924
131924
  init_esm2();
131925
131925
  init_src();
131926
131926
  });
@@ -132056,7 +132056,7 @@ var init_sandbox = __esm(() => {
132056
132056
  init_wrapper();
132057
132057
  init_src();
132058
132058
  init_infrastructure2();
132059
- init_auth();
132059
+ init_auth3();
132060
132060
  init_config4();
132061
132061
  });
132062
132062
  // ../realtime/src/server/application/services/presence.ts
@@ -132143,7 +132143,7 @@ var init_presence2 = __esm(() => {
132143
132143
  var playerPositions, PlayerPositionService;
132144
132144
  var init_player_position = __esm(() => {
132145
132145
  init_src();
132146
- init_constants();
132146
+ init_constants3();
132147
132147
  init_infrastructure2();
132148
132148
  playerPositions = new Map;
132149
132149
  PlayerPositionService = {
@@ -132275,7 +132275,7 @@ function computeInitialChannelKey(gameId, requested) {
132275
132275
  return `${HUB_PREFIX}${channel}`;
132276
132276
  }
132277
132277
  var init_channel_keys = __esm(() => {
132278
- init_constants();
132278
+ init_constants3();
132279
132279
  });
132280
132280
 
132281
132281
  // ../realtime/src/server/shared/utils/websocket.ts
@@ -132296,7 +132296,7 @@ function checkMatchingPlayerId(ws, payload) {
132296
132296
  }
132297
132297
  var init_websocket = __esm(() => {
132298
132298
  init_src();
132299
- init_constants();
132299
+ init_constants3();
132300
132300
  });
132301
132301
 
132302
132302
  // ../realtime/src/server/shared/utils/route.ts
@@ -132651,7 +132651,7 @@ async function handleWebSocketUpgrade(req, server, config2) {
132651
132651
  }
132652
132652
  var init_websocket2 = __esm(() => {
132653
132653
  init_src();
132654
- init_auth();
132654
+ init_auth3();
132655
132655
  init_response();
132656
132656
  });
132657
132657
 
@@ -132838,7 +132838,7 @@ var init_open = __esm(() => {
132838
132838
  init_src();
132839
132839
  init_presence4();
132840
132840
  init_services2();
132841
- init_constants();
132841
+ init_constants3();
132842
132842
  init_events();
132843
132843
  init_infrastructure2();
132844
132844
  init_utils13();
@@ -133247,6 +133247,7 @@ var config = {
133247
133247
  };
133248
133248
  process.env.BETTER_AUTH_SECRET = config.auth.betterAuthSecret;
133249
133249
  process.env.GAME_JWT_SECRET = config.auth.gameJwtSecret;
133250
+ process.env.PUBLIC_IS_LOCAL = "true";
133250
133251
 
133251
133252
  // ../../node_modules/@hono/node-server/dist/index.mjs
133252
133253
  import { createServer as createServerHTTP } from "http";
@@ -133792,7 +133793,7 @@ var serve = (options, listeningListener) => {
133792
133793
  // package.json
133793
133794
  var package_default = {
133794
133795
  name: "@playcademy/sandbox",
133795
- version: "0.1.9",
133796
+ version: "0.1.11",
133796
133797
  description: "Local development server for Playcademy game development",
133797
133798
  type: "module",
133798
133799
  exports: {
@@ -140872,12 +140873,17 @@ var timebackXpEvents = pgTable("timeback_xp_event", {
140872
140873
  }, (table) => [uniqueIndex("timeback_xp_events_source_id_idx").on(table.source, table.sourceId)]);
140873
140874
  var gameTimebackIntegrations = pgTable("game_timeback_integrations", {
140874
140875
  id: uuid("id").primaryKey().defaultRandom(),
140875
- gameId: uuid("game_id").notNull().unique().references(() => games.id, { onDelete: "cascade" }),
140876
+ gameId: uuid("game_id").notNull().references(() => games.id, { onDelete: "cascade" }),
140876
140877
  courseId: text("course_id").notNull(),
140878
+ grade: integer("grade").notNull(),
140879
+ subject: text("subject").notNull(),
140880
+ totalXp: integer("total_xp"),
140877
140881
  lastVerifiedAt: timestamp("last_verified_at", { withTimezone: true }),
140878
140882
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
140879
140883
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
140880
- });
140884
+ }, (table) => [
140885
+ uniqueIndex("game_timeback_integrations_game_grade_subject_idx").on(table.gameId, table.grade, table.subject)
140886
+ ]);
140881
140887
  // ../data/src/domains/achievement/table.ts
140882
140888
  var achievementScopeEnum = pgEnum("achievement_scope", [
140883
140889
  "daily",
@@ -147460,6 +147466,292 @@ var __export3 = (target, all) => {
147460
147466
  set: (newValue) => all[name3] = () => newValue
147461
147467
  });
147462
147468
  };
147469
+ var __esm3 = (fn2, res) => () => (fn2 && (res = fn2(fn2 = 0)), res);
147470
+ var init_auth = () => {};
147471
+ var PLAYCADEMY_BASE_URLS;
147472
+ var init_domains = __esm3(() => {
147473
+ PLAYCADEMY_BASE_URLS = {
147474
+ production: "https://hub.playcademy.net",
147475
+ staging: "https://hub.dev.playcademy.net"
147476
+ };
147477
+ });
147478
+ var init_env_vars = () => {};
147479
+ var ITEM_SLUGS2;
147480
+ var CURRENCIES2;
147481
+ var BADGES2;
147482
+ var init_overworld = __esm3(() => {
147483
+ ITEM_SLUGS2 = {
147484
+ PLAYCADEMY_CREDITS: "PLAYCADEMY_CREDITS",
147485
+ PLAYCADEMY_XP: "PLAYCADEMY_XP",
147486
+ FOUNDING_MEMBER_BADGE: "FOUNDING_MEMBER_BADGE",
147487
+ EARLY_ADOPTER_BADGE: "EARLY_ADOPTER_BADGE",
147488
+ FIRST_GAME_BADGE: "FIRST_GAME_BADGE",
147489
+ COMMON_SWORD: "COMMON_SWORD",
147490
+ SMALL_HEALTH_POTION: "SMALL_HEALTH_POTION",
147491
+ SMALL_BACKPACK: "SMALL_BACKPACK",
147492
+ LAVA_LAMP: "LAVA_LAMP",
147493
+ BOOMBOX: "BOOMBOX",
147494
+ CABIN_BED: "CABIN_BED"
147495
+ };
147496
+ CURRENCIES2 = {
147497
+ PRIMARY: ITEM_SLUGS2.PLAYCADEMY_CREDITS,
147498
+ XP: ITEM_SLUGS2.PLAYCADEMY_XP
147499
+ };
147500
+ BADGES2 = {
147501
+ FOUNDING_MEMBER: ITEM_SLUGS2.FOUNDING_MEMBER_BADGE,
147502
+ EARLY_ADOPTER: ITEM_SLUGS2.EARLY_ADOPTER_BADGE,
147503
+ FIRST_GAME: ITEM_SLUGS2.FIRST_GAME_BADGE
147504
+ };
147505
+ });
147506
+ var init_timeback = () => {};
147507
+ var init_workers = () => {};
147508
+ var init_src2 = __esm3(() => {
147509
+ init_auth();
147510
+ init_domains();
147511
+ init_env_vars();
147512
+ init_overworld();
147513
+ init_timeback();
147514
+ init_workers();
147515
+ });
147516
+ var TIMEBACK_API_URLS;
147517
+ var TIMEBACK_AUTH_URLS;
147518
+ var CALIPER_API_URLS;
147519
+ var ONEROSTER_ENDPOINTS;
147520
+ var CALIPER_ENDPOINTS;
147521
+ var createOneRosterUrls = (baseUrl) => {
147522
+ const effective = baseUrl || TIMEBACK_API_URLS.production;
147523
+ const base = effective.replace(/\/$/, "");
147524
+ return {
147525
+ user: (userId) => `${base}${ONEROSTER_ENDPOINTS.users}/${userId}`,
147526
+ course: (courseId) => `${base}${ONEROSTER_ENDPOINTS.courses}/${courseId}`,
147527
+ componentResource: (resourceId) => `${base}${ONEROSTER_ENDPOINTS.componentResources}/${resourceId}`
147528
+ };
147529
+ };
147530
+ var CALIPER_CONSTANTS;
147531
+ var TIMEBACK_EVENT_TYPES;
147532
+ var TIMEBACK_ACTIONS;
147533
+ var TIMEBACK_TYPES;
147534
+ var ACTIVITY_METRIC_TYPES;
147535
+ var TIME_METRIC_TYPES;
147536
+ var TIMEBACK_SUBJECTS;
147537
+ var TIMEBACK_GRADE_LEVELS;
147538
+ var TIMEBACK_GRADE_LEVEL_LABELS;
147539
+ var CALIPER_SUBJECTS;
147540
+ var ONEROSTER_STATUS;
147541
+ var SCORE_STATUS;
147542
+ var ENV_VARS;
147543
+ var HTTP_DEFAULTS;
147544
+ var AUTH_DEFAULTS;
147545
+ var CACHE_DEFAULTS;
147546
+ var CONFIG_DEFAULTS;
147547
+ var DEFAULT_PLAYCADEMY_ORGANIZATION_ID;
147548
+ var PLAYCADEMY_DEFAULTS;
147549
+ var RESOURCE_DEFAULTS;
147550
+ var HTTP_STATUS;
147551
+ var ERROR_NAMES;
147552
+ var init_constants = __esm3(() => {
147553
+ init_src2();
147554
+ TIMEBACK_API_URLS = {
147555
+ production: "https://api.alpha-1edtech.ai",
147556
+ staging: "https://api.staging.alpha-1edtech.com"
147557
+ };
147558
+ TIMEBACK_AUTH_URLS = {
147559
+ production: "https://prod-beyond-timeback-api-2-idp.auth.us-east-1.amazoncognito.com",
147560
+ staging: "https://alpha-auth-development-idp.auth.us-west-2.amazoncognito.com"
147561
+ };
147562
+ CALIPER_API_URLS = {
147563
+ production: "https://caliper.alpha-1edtech.ai",
147564
+ staging: "https://caliper-staging.alpha-1edtech.com"
147565
+ };
147566
+ ONEROSTER_ENDPOINTS = {
147567
+ organizations: "/ims/oneroster/rostering/v1p2/orgs",
147568
+ courses: "/ims/oneroster/rostering/v1p2/courses",
147569
+ courseComponents: "/ims/oneroster/rostering/v1p2/courses/components",
147570
+ resources: "/ims/oneroster/resources/v1p2/resources",
147571
+ componentResources: "/ims/oneroster/rostering/v1p2/courses/component-resources",
147572
+ classes: "/ims/oneroster/rostering/v1p2/classes",
147573
+ enrollments: "/ims/oneroster/rostering/v1p2/enrollments",
147574
+ assessmentLineItems: "/ims/oneroster/gradebook/v1p2/assessmentLineItems",
147575
+ assessmentResults: "/ims/oneroster/gradebook/v1p2/assessmentResults",
147576
+ users: "/ims/oneroster/rostering/v1p2/users"
147577
+ };
147578
+ CALIPER_ENDPOINTS = {
147579
+ events: "/caliper/event",
147580
+ validate: "/caliper/event/validate"
147581
+ };
147582
+ CALIPER_CONSTANTS = {
147583
+ context: "http://purl.imsglobal.org/ctx/caliper/v1p2",
147584
+ profile: "TimebackProfile",
147585
+ dataVersion: "http://purl.imsglobal.org/ctx/caliper/v1p2"
147586
+ };
147587
+ TIMEBACK_EVENT_TYPES = {
147588
+ activityEvent: "ActivityEvent",
147589
+ timeSpentEvent: "TimeSpentEvent"
147590
+ };
147591
+ TIMEBACK_ACTIONS = {
147592
+ completed: "Completed",
147593
+ spentTime: "SpentTime"
147594
+ };
147595
+ TIMEBACK_TYPES = {
147596
+ user: "TimebackUser",
147597
+ activityContext: "TimebackActivityContext",
147598
+ activityMetricsCollection: "TimebackActivityMetricsCollection",
147599
+ timeSpentMetricsCollection: "TimebackTimeSpentMetricsCollection"
147600
+ };
147601
+ ACTIVITY_METRIC_TYPES = {
147602
+ totalQuestions: "totalQuestions",
147603
+ correctQuestions: "correctQuestions",
147604
+ xpEarned: "xpEarned",
147605
+ masteredUnits: "masteredUnits"
147606
+ };
147607
+ TIME_METRIC_TYPES = {
147608
+ active: "active",
147609
+ inactive: "inactive",
147610
+ waste: "waste",
147611
+ unknown: "unknown",
147612
+ antiPattern: "anti-pattern"
147613
+ };
147614
+ TIMEBACK_SUBJECTS = [
147615
+ "Math",
147616
+ "FastMath",
147617
+ "Science",
147618
+ "Social Studies",
147619
+ "Language",
147620
+ "Reading",
147621
+ "Vocabulary",
147622
+ "Writing"
147623
+ ];
147624
+ TIMEBACK_GRADE_LEVELS = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
147625
+ TIMEBACK_GRADE_LEVEL_LABELS = {
147626
+ "-1": "pre-k",
147627
+ "0": "kindergarten",
147628
+ "1": "1st grade",
147629
+ "2": "2nd grade",
147630
+ "3": "3rd grade",
147631
+ "4": "4th grade",
147632
+ "5": "5th grade",
147633
+ "6": "6th grade",
147634
+ "7": "7th grade",
147635
+ "8": "8th grade",
147636
+ "9": "9th grade",
147637
+ "10": "10th grade",
147638
+ "11": "11th grade",
147639
+ "12": "12th grade",
147640
+ "13": "AP"
147641
+ };
147642
+ CALIPER_SUBJECTS = {
147643
+ Reading: "Reading",
147644
+ Language: "Language",
147645
+ Vocabulary: "Vocabulary",
147646
+ SocialStudies: "Social Studies",
147647
+ Writing: "Writing",
147648
+ Science: "Science",
147649
+ FastMath: "FastMath",
147650
+ Math: "Math",
147651
+ None: "None"
147652
+ };
147653
+ ONEROSTER_STATUS = {
147654
+ active: "active",
147655
+ toBeDeleted: "tobedeleted"
147656
+ };
147657
+ SCORE_STATUS = {
147658
+ exempt: "exempt",
147659
+ fullyGraded: "fully graded",
147660
+ notSubmitted: "not submitted",
147661
+ partiallyGraded: "partially graded",
147662
+ submitted: "submitted"
147663
+ };
147664
+ ENV_VARS = {
147665
+ clientId: "TIMEBACK_CLIENT_ID",
147666
+ clientSecret: "TIMEBACK_CLIENT_SECRET",
147667
+ baseUrl: "TIMEBACK_BASE_URL",
147668
+ environment: "TIMEBACK_ENVIRONMENT",
147669
+ vendorResourceId: "TIMEBACK_VENDOR_RESOURCE_ID",
147670
+ launchBaseUrl: "GAME_URL"
147671
+ };
147672
+ HTTP_DEFAULTS = {
147673
+ timeout: 30000,
147674
+ retries: 3,
147675
+ retryBackoffBase: 2
147676
+ };
147677
+ AUTH_DEFAULTS = {
147678
+ tokenCacheDuration: 50000
147679
+ };
147680
+ CACHE_DEFAULTS = {
147681
+ defaultTTL: 10 * 60 * 1000,
147682
+ defaultMaxSize: 500,
147683
+ defaultName: "TimebackCache",
147684
+ studentTTL: 10 * 60 * 1000,
147685
+ studentMaxSize: 500,
147686
+ assessmentTTL: 30 * 60 * 1000,
147687
+ assessmentMaxSize: 200,
147688
+ enrollmentTTL: 5 * 1000,
147689
+ enrollmentMaxSize: 100
147690
+ };
147691
+ CONFIG_DEFAULTS = {
147692
+ fileNames: ["timeback.config.js", "timeback.config.json"]
147693
+ };
147694
+ DEFAULT_PLAYCADEMY_ORGANIZATION_ID = process.env.TIMEBACK_ORG_SOURCE_ID || "PLAYCADEMY";
147695
+ PLAYCADEMY_DEFAULTS = {
147696
+ organization: DEFAULT_PLAYCADEMY_ORGANIZATION_ID,
147697
+ launchBaseUrls: PLAYCADEMY_BASE_URLS
147698
+ };
147699
+ RESOURCE_DEFAULTS = {
147700
+ organization: {
147701
+ name: "Playcademy Studios",
147702
+ type: "department"
147703
+ },
147704
+ course: {
147705
+ gradingScheme: "STANDARD",
147706
+ level: {
147707
+ elementary: "Elementary",
147708
+ middle: "Middle",
147709
+ high: "High",
147710
+ ap: "AP"
147711
+ },
147712
+ metadata: {
147713
+ goals: {
147714
+ dailyXp: 50,
147715
+ dailyLessons: 3
147716
+ },
147717
+ metrics: {
147718
+ totalXp: 1000,
147719
+ totalLessons: 50
147720
+ }
147721
+ }
147722
+ },
147723
+ component: {
147724
+ sortOrder: 1,
147725
+ prerequisiteCriteria: "ALL"
147726
+ },
147727
+ resource: {
147728
+ vendorId: "playcademy",
147729
+ roles: ["primary"],
147730
+ importance: "primary",
147731
+ metadata: {
147732
+ type: "interactive",
147733
+ toolProvider: "Playcademy",
147734
+ instructionalMethod: "exploratory",
147735
+ language: "en-US"
147736
+ }
147737
+ },
147738
+ componentResource: {
147739
+ sortOrder: 1,
147740
+ lessonType: "quiz"
147741
+ }
147742
+ };
147743
+ HTTP_STATUS = {
147744
+ CLIENT_ERROR_MIN: 400,
147745
+ CLIENT_ERROR_MAX: 500,
147746
+ SERVER_ERROR_MIN: 500
147747
+ };
147748
+ ERROR_NAMES = {
147749
+ timebackAuth: "TimebackAuthError",
147750
+ timebackApi: "TimebackApiError",
147751
+ timebackConfig: "TimebackConfigError",
147752
+ timebackSdk: "TimebackSDKError"
147753
+ };
147754
+ });
147463
147755
  function deriveSourcedIds(courseId) {
147464
147756
  return {
147465
147757
  course: courseId,
@@ -147475,7 +147767,8 @@ __export3(exports_verify, {
147475
147767
  });
147476
147768
  async function fetchTimebackConfig(client, courseId) {
147477
147769
  const sourcedIds = deriveSourcedIds(courseId);
147478
- const [course, component, resource, componentResource] = await Promise.all([
147770
+ const [org, course, component, resource, componentResource] = await Promise.all([
147771
+ client.oneroster.organizations.get(PLAYCADEMY_DEFAULTS.organization),
147479
147772
  client.oneroster.courses.get(sourcedIds.course),
147480
147773
  client.oneroster.courseComponents.get(sourcedIds.component),
147481
147774
  client.oneroster.resources.get(sourcedIds.resource),
@@ -147483,9 +147776,9 @@ async function fetchTimebackConfig(client, courseId) {
147483
147776
  ]);
147484
147777
  return {
147485
147778
  organization: {
147486
- name: "Playcademy Studios",
147487
- type: "department",
147488
- identifier: "PLAYCADEMY"
147779
+ name: org.name,
147780
+ type: org.type,
147781
+ identifier: org.identifier || PLAYCADEMY_DEFAULTS.organization
147489
147782
  },
147490
147783
  course: {
147491
147784
  title: course.title || "",
@@ -147545,145 +147838,10 @@ async function verifyTimebackResources(client, courseId) {
147545
147838
  }
147546
147839
  };
147547
147840
  }
147548
- var init_verify5 = () => {};
147549
- var ITEM_SLUGS2 = {
147550
- PLAYCADEMY_CREDITS: "PLAYCADEMY_CREDITS",
147551
- PLAYCADEMY_XP: "PLAYCADEMY_XP",
147552
- FOUNDING_MEMBER_BADGE: "FOUNDING_MEMBER_BADGE",
147553
- EARLY_ADOPTER_BADGE: "EARLY_ADOPTER_BADGE",
147554
- FIRST_GAME_BADGE: "FIRST_GAME_BADGE",
147555
- COMMON_SWORD: "COMMON_SWORD",
147556
- SMALL_HEALTH_POTION: "SMALL_HEALTH_POTION",
147557
- SMALL_BACKPACK: "SMALL_BACKPACK",
147558
- LAVA_LAMP: "LAVA_LAMP",
147559
- BOOMBOX: "BOOMBOX",
147560
- CABIN_BED: "CABIN_BED"
147561
- };
147562
- var CURRENCIES2 = {
147563
- PRIMARY: ITEM_SLUGS2.PLAYCADEMY_CREDITS,
147564
- XP: ITEM_SLUGS2.PLAYCADEMY_XP
147565
- };
147566
- var BADGES2 = {
147567
- FOUNDING_MEMBER: ITEM_SLUGS2.FOUNDING_MEMBER_BADGE,
147568
- EARLY_ADOPTER: ITEM_SLUGS2.EARLY_ADOPTER_BADGE,
147569
- FIRST_GAME: ITEM_SLUGS2.FIRST_GAME_BADGE
147570
- };
147571
- var TIMEBACK_API_URLS = {
147572
- production: "https://api.alpha-1edtech.ai",
147573
- staging: "https://api.staging.alpha-1edtech.com"
147574
- };
147575
- var TIMEBACK_AUTH_URLS = {
147576
- production: "https://prod-beyond-timeback-api-2-idp.auth.us-east-1.amazoncognito.com",
147577
- staging: "https://alpha-auth-development-idp.auth.us-west-2.amazoncognito.com"
147578
- };
147579
- var CALIPER_API_URLS = {
147580
- production: "https://caliper.alpha-1edtech.ai",
147581
- staging: "https://caliper-staging.alpha-1edtech.com"
147582
- };
147583
- var ONEROSTER_ENDPOINTS = {
147584
- organizations: "/ims/oneroster/rostering/v1p2/orgs",
147585
- courses: "/ims/oneroster/rostering/v1p2/courses",
147586
- courseComponents: "/ims/oneroster/rostering/v1p2/courses/components",
147587
- resources: "/ims/oneroster/resources/v1p2/resources",
147588
- componentResources: "/ims/oneroster/rostering/v1p2/courses/component-resources",
147589
- classes: "/ims/oneroster/rostering/v1p2/classes",
147590
- enrollments: "/ims/oneroster/rostering/v1p2/enrollments",
147591
- assessmentLineItems: "/ims/oneroster/gradebook/v1p2/assessmentLineItems",
147592
- assessmentResults: "/ims/oneroster/gradebook/v1p2/assessmentResults",
147593
- users: "/ims/oneroster/rostering/v1p2/users"
147594
- };
147595
- var CALIPER_ENDPOINTS = {
147596
- events: "/caliper/event",
147597
- validate: "/caliper/event/validate"
147598
- };
147599
- var createOneRosterUrls = (baseUrl) => {
147600
- const effective = baseUrl || TIMEBACK_API_URLS.production;
147601
- const base = effective.replace(/\/$/, "");
147602
- return {
147603
- user: (userId) => `${base}${ONEROSTER_ENDPOINTS.users}/${userId}`,
147604
- course: (courseId) => `${base}${ONEROSTER_ENDPOINTS.courses}/${courseId}`,
147605
- componentResource: (resourceId) => `${base}${ONEROSTER_ENDPOINTS.componentResources}/${resourceId}`
147606
- };
147607
- };
147608
- var CALIPER_CONSTANTS = {
147609
- context: "http://purl.imsglobal.org/ctx/caliper/v1p2",
147610
- profile: "TimebackProfile",
147611
- dataVersion: "http://purl.imsglobal.org/ctx/caliper/v1p2"
147612
- };
147613
- var TIMEBACK_EVENT_TYPES = {
147614
- activityEvent: "ActivityEvent",
147615
- timeSpentEvent: "TimeSpentEvent"
147616
- };
147617
- var TIMEBACK_ACTIONS = {
147618
- completed: "Completed",
147619
- spentTime: "SpentTime"
147620
- };
147621
- var TIMEBACK_TYPES = {
147622
- user: "TimebackUser",
147623
- activityContext: "TimebackActivityContext",
147624
- activityMetricsCollection: "TimebackActivityMetricsCollection",
147625
- timeSpentMetricsCollection: "TimebackTimeSpentMetricsCollection"
147626
- };
147627
- var ACTIVITY_METRIC_TYPES = {
147628
- totalQuestions: "totalQuestions",
147629
- correctQuestions: "correctQuestions",
147630
- xpEarned: "xpEarned",
147631
- masteredUnits: "masteredUnits"
147632
- };
147633
- var TIME_METRIC_TYPES = {
147634
- active: "active",
147635
- inactive: "inactive",
147636
- waste: "waste",
147637
- unknown: "unknown",
147638
- antiPattern: "anti-pattern"
147639
- };
147640
- var ONEROSTER_STATUS = {
147641
- active: "active",
147642
- toBeDeleted: "tobedeleted"
147643
- };
147644
- var SCORE_STATUS = {
147645
- exempt: "exempt",
147646
- fullyGraded: "fully graded",
147647
- notSubmitted: "not submitted",
147648
- partiallyGraded: "partially graded",
147649
- submitted: "submitted"
147650
- };
147651
- var ENV_VARS = {
147652
- clientId: "TIMEBACK_CLIENT_ID",
147653
- clientSecret: "TIMEBACK_CLIENT_SECRET",
147654
- baseUrl: "TIMEBACK_BASE_URL",
147655
- environment: "TIMEBACK_ENVIRONMENT",
147656
- vendorResourceId: "TIMEBACK_VENDOR_RESOURCE_ID",
147657
- launchBaseUrl: "GAME_URL"
147658
- };
147659
- var HTTP_DEFAULTS = {
147660
- timeout: 30000,
147661
- retries: 3,
147662
- retryBackoffBase: 2
147663
- };
147664
- var AUTH_DEFAULTS = {
147665
- tokenCacheDuration: 50000
147666
- };
147667
- var CACHE_DEFAULTS = {
147668
- defaultTTL: 10 * 60 * 1000,
147669
- defaultMaxSize: 500,
147670
- defaultName: "TimebackCache",
147671
- studentTTL: 10 * 60 * 1000,
147672
- studentMaxSize: 500,
147673
- assessmentTTL: 30 * 60 * 1000,
147674
- assessmentMaxSize: 200
147675
- };
147676
- var HTTP_STATUS = {
147677
- CLIENT_ERROR_MIN: 400,
147678
- CLIENT_ERROR_MAX: 500,
147679
- SERVER_ERROR_MIN: 500
147680
- };
147681
- var ERROR_NAMES = {
147682
- timebackAuth: "TimebackAuthError",
147683
- timebackApi: "TimebackApiError",
147684
- timebackConfig: "TimebackConfigError",
147685
- timebackSdk: "TimebackSDKError"
147686
- };
147841
+ var init_verify5 = __esm3(() => {
147842
+ init_constants();
147843
+ });
147844
+ init_constants();
147687
147845
 
147688
147846
  class TimebackError extends Error {
147689
147847
  constructor(message2) {
@@ -147745,6 +147903,25 @@ class ResourceNotFoundError extends TimebackError {
147745
147903
  Object.setPrototypeOf(this, ResourceNotFoundError.prototype);
147746
147904
  }
147747
147905
  }
147906
+ init_constants();
147907
+ var isObject2 = (value) => typeof value === "object" && value !== null;
147908
+ var SUBJECT_VALUES = TIMEBACK_SUBJECTS;
147909
+ var GRADE_VALUES = TIMEBACK_GRADE_LEVELS;
147910
+ function isPlaycademyResourceMetadata(value) {
147911
+ if (!isObject2(value)) {
147912
+ return false;
147913
+ }
147914
+ if (!("mastery" in value) || value.mastery === undefined) {
147915
+ return true;
147916
+ }
147917
+ return isObject2(value.mastery);
147918
+ }
147919
+ function isTimebackSubject(value) {
147920
+ return typeof value === "string" && SUBJECT_VALUES.includes(value);
147921
+ }
147922
+ function isTimebackGrade(value) {
147923
+ return typeof value === "number" && Number.isInteger(value) && GRADE_VALUES.includes(value);
147924
+ }
147748
147925
  var isBrowser2 = () => {
147749
147926
  const g5 = globalThis;
147750
147927
  return typeof g5.window !== "undefined" && typeof g5.document !== "undefined";
@@ -148193,6 +148370,8 @@ async function updateTimebackResources(client, courseId, config2) {
148193
148370
  updateComponentResourceLink(client, config2, sourcedIds)
148194
148371
  ]);
148195
148372
  }
148373
+ init_constants();
148374
+ init_constants();
148196
148375
  if (process.env.DEBUG === "true") {
148197
148376
  process.env.TERM = "dumb";
148198
148377
  }
@@ -148292,6 +148471,7 @@ async function getTimebackTokenResponse(config2) {
148292
148471
  function getAuthUrl(environment = "production") {
148293
148472
  return TIMEBACK_AUTH_URLS[environment];
148294
148473
  }
148474
+ init_constants();
148295
148475
  async function request({
148296
148476
  path: path2,
148297
148477
  baseUrl,
@@ -148397,6 +148577,154 @@ async function requestCaliper(options) {
148397
148577
  baseUrl: caliperBase
148398
148578
  });
148399
148579
  }
148580
+ init_constants();
148581
+ function createCaliperNamespace(client) {
148582
+ const urls = createOneRosterUrls(client.getBaseUrl());
148583
+ const caliper = {
148584
+ emit: async (event, sensorUrl) => {
148585
+ const envelope = {
148586
+ sensor: sensorUrl,
148587
+ sendTime: new Date().toISOString(),
148588
+ dataVersion: CALIPER_CONSTANTS.dataVersion,
148589
+ data: [event]
148590
+ };
148591
+ return client["requestCaliper"](CALIPER_ENDPOINTS.events, "POST", envelope);
148592
+ },
148593
+ emitBatch: async (events, sensorUrl) => {
148594
+ if (events.length === 0)
148595
+ return;
148596
+ const envelope = {
148597
+ sensor: sensorUrl,
148598
+ sendTime: new Date().toISOString(),
148599
+ dataVersion: CALIPER_CONSTANTS.dataVersion,
148600
+ data: events
148601
+ };
148602
+ return client["requestCaliper"](CALIPER_ENDPOINTS.events, "POST", envelope);
148603
+ },
148604
+ emitActivityEvent: async (data) => {
148605
+ const event = {
148606
+ "@context": CALIPER_CONSTANTS.context,
148607
+ id: `urn:uuid:${crypto.randomUUID()}`,
148608
+ type: TIMEBACK_EVENT_TYPES.activityEvent,
148609
+ eventTime: new Date().toISOString(),
148610
+ profile: CALIPER_CONSTANTS.profile,
148611
+ actor: {
148612
+ id: urls.user(data.studentId),
148613
+ type: TIMEBACK_TYPES.user,
148614
+ email: data.studentEmail
148615
+ },
148616
+ action: TIMEBACK_ACTIONS.completed,
148617
+ object: {
148618
+ id: caliper.buildActivityUrl(data),
148619
+ type: TIMEBACK_TYPES.activityContext,
148620
+ subject: data.subject,
148621
+ app: {
148622
+ name: data.appName
148623
+ },
148624
+ activity: {
148625
+ name: data.activityName
148626
+ },
148627
+ course: { id: urls.course(data.courseId), name: data.activityName },
148628
+ process: false
148629
+ },
148630
+ generated: {
148631
+ id: `urn:timeback:metrics:activity-completion-${crypto.randomUUID()}`,
148632
+ type: TIMEBACK_TYPES.activityMetricsCollection,
148633
+ attempt: data.attemptNumber || 1,
148634
+ items: [
148635
+ ...data.totalQuestions !== undefined ? [
148636
+ {
148637
+ type: ACTIVITY_METRIC_TYPES.totalQuestions,
148638
+ value: data.totalQuestions
148639
+ }
148640
+ ] : [],
148641
+ ...data.correctQuestions !== undefined ? [
148642
+ {
148643
+ type: ACTIVITY_METRIC_TYPES.correctQuestions,
148644
+ value: data.correctQuestions
148645
+ }
148646
+ ] : [],
148647
+ ...data.xpEarned !== undefined ? [{ type: ACTIVITY_METRIC_TYPES.xpEarned, value: data.xpEarned }] : [],
148648
+ ...data.masteredUnits !== undefined ? [
148649
+ {
148650
+ type: ACTIVITY_METRIC_TYPES.masteredUnits,
148651
+ value: data.masteredUnits
148652
+ }
148653
+ ] : []
148654
+ ],
148655
+ ...data.extensions ? { extensions: data.extensions } : {}
148656
+ }
148657
+ };
148658
+ return caliper.emit(event, data.sensorUrl);
148659
+ },
148660
+ emitTimeSpentEvent: async (data) => {
148661
+ const event = {
148662
+ "@context": CALIPER_CONSTANTS.context,
148663
+ id: `urn:uuid:${crypto.randomUUID()}`,
148664
+ type: TIMEBACK_EVENT_TYPES.timeSpentEvent,
148665
+ eventTime: new Date().toISOString(),
148666
+ profile: CALIPER_CONSTANTS.profile,
148667
+ actor: {
148668
+ id: urls.user(data.studentId),
148669
+ type: TIMEBACK_TYPES.user,
148670
+ email: data.studentEmail
148671
+ },
148672
+ action: TIMEBACK_ACTIONS.spentTime,
148673
+ object: {
148674
+ id: caliper.buildActivityUrl(data),
148675
+ type: TIMEBACK_TYPES.activityContext,
148676
+ subject: data.subject,
148677
+ app: {
148678
+ name: data.appName
148679
+ },
148680
+ activity: {
148681
+ name: data.activityName
148682
+ },
148683
+ course: { id: urls.course(data.courseId), name: data.activityName },
148684
+ process: false
148685
+ },
148686
+ generated: {
148687
+ id: `urn:timeback:metrics:time-spent-${crypto.randomUUID()}`,
148688
+ type: TIMEBACK_TYPES.timeSpentMetricsCollection,
148689
+ items: [
148690
+ { type: TIME_METRIC_TYPES.active, value: data.activeTimeSeconds },
148691
+ ...data.inactiveTimeSeconds !== undefined ? [
148692
+ {
148693
+ type: TIME_METRIC_TYPES.inactive,
148694
+ value: data.inactiveTimeSeconds
148695
+ }
148696
+ ] : [],
148697
+ ...data.wasteTimeSeconds !== undefined ? [{ type: TIME_METRIC_TYPES.waste, value: data.wasteTimeSeconds }] : []
148698
+ ]
148699
+ }
148700
+ };
148701
+ return caliper.emit(event, data.sensorUrl);
148702
+ },
148703
+ buildActivityUrl: (data) => {
148704
+ const base = data.sensorUrl.replace(/\/$/, "");
148705
+ return `${base}/activities/${data.courseId}/${data.activityId}/${crypto.randomUUID()}`;
148706
+ }
148707
+ };
148708
+ return caliper;
148709
+ }
148710
+ function createEduBridgeNamespace(client) {
148711
+ const enrollments = {
148712
+ listByUser: async (userId) => {
148713
+ const response = await client["request"](`/edubridge/enrollments/user/${userId}`, "GET");
148714
+ return response.data;
148715
+ }
148716
+ };
148717
+ const analytics = {
148718
+ getEnrollmentFacts: async (enrollmentId) => {
148719
+ return client["request"](`/edubridge/analytics/enrollment/${enrollmentId}`, "GET");
148720
+ }
148721
+ };
148722
+ return {
148723
+ enrollments,
148724
+ analytics
148725
+ };
148726
+ }
148727
+ init_constants();
148400
148728
  function logTimebackError(operation, error2, context) {
148401
148729
  const errorMessage = error2 instanceof Error ? error2.message : String(error2);
148402
148730
  if (error2 instanceof TimebackApiError) {
@@ -148432,6 +148760,17 @@ function createOneRosterNamespace(client) {
148432
148760
  listByCourse: async (courseSourcedId) => {
148433
148761
  const res = await client["request"](`${ONEROSTER_ENDPOINTS.courses}/${courseSourcedId}/classes`, "GET");
148434
148762
  return res.classes;
148763
+ },
148764
+ listByStudent: async (userSourcedId, options) => {
148765
+ const queryParams = new URLSearchParams;
148766
+ if (options?.limit)
148767
+ queryParams.set("limit", String(options.limit));
148768
+ if (options?.offset)
148769
+ queryParams.set("offset", String(options.offset));
148770
+ const endpoint = `${ONEROSTER_ENDPOINTS.users}/${userSourcedId}/classes`;
148771
+ const url = queryParams.toString() ? `${endpoint}?${queryParams}` : endpoint;
148772
+ const res = await client["request"](url, "GET");
148773
+ return res.classes || [];
148435
148774
  }
148436
148775
  },
148437
148776
  organizations: {
@@ -148439,7 +148778,7 @@ function createOneRosterNamespace(client) {
148439
148778
  return client["request"](ONEROSTER_ENDPOINTS.organizations, "POST", data);
148440
148779
  },
148441
148780
  get: async (sourcedId) => {
148442
- return client["request"](`${ONEROSTER_ENDPOINTS.organizations}/${sourcedId}`, "GET");
148781
+ return client["request"](`${ONEROSTER_ENDPOINTS.organizations}/${sourcedId}`, "GET").then((res) => res.org);
148443
148782
  },
148444
148783
  update: async (sourcedId, data) => {
148445
148784
  return client["request"](`${ONEROSTER_ENDPOINTS.organizations}/${sourcedId}`, "PUT", data);
@@ -148547,14 +148886,30 @@ function createOneRosterNamespace(client) {
148547
148886
  update: async (sourcedId, data) => {
148548
148887
  return client["request"](`${ONEROSTER_ENDPOINTS.assessmentResults}/${sourcedId}`, "PUT", data);
148549
148888
  },
148550
- getLatestForStudent: async (studentId, lineItemId) => {
148889
+ getAttemptStats: async (studentId, lineItemId) => {
148551
148890
  try {
148552
148891
  const filter2 = `student.sourcedId='${studentId}' AND assessmentLineItem.sourcedId='${lineItemId}'`;
148553
- const url = `${ONEROSTER_ENDPOINTS.assessmentResults}?filter=${encodeURIComponent(filter2)}&sort=scoreDate&orderBy=desc&limit=1`;
148892
+ const url = `${ONEROSTER_ENDPOINTS.assessmentResults}?filter=${encodeURIComponent(filter2)}`;
148554
148893
  const response = await client["request"](url, "GET");
148555
- return response.assessmentResults[0] || null;
148894
+ const results = response.assessmentResults || [];
148895
+ if (results.length === 0)
148896
+ return null;
148897
+ let maxAttemptResult = results[0];
148898
+ let maxAttemptNumber = maxAttemptResult.metadata?.attemptNumber || 0;
148899
+ let activeAttemptCount = 0;
148900
+ for (const result of results) {
148901
+ const attemptNumber = result.metadata?.attemptNumber || 0;
148902
+ if (attemptNumber > maxAttemptNumber) {
148903
+ maxAttemptNumber = attemptNumber;
148904
+ maxAttemptResult = result;
148905
+ }
148906
+ if (result.status === "active") {
148907
+ activeAttemptCount++;
148908
+ }
148909
+ }
148910
+ return { maxAttemptNumber, activeAttemptCount, maxAttemptResult };
148556
148911
  } catch (error2) {
148557
- logTimebackError("query latest assessment result", error2, {
148912
+ logTimebackError("query attempt stats", error2, {
148558
148913
  studentId,
148559
148914
  lineItemId
148560
148915
  });
@@ -148567,7 +148922,7 @@ function createOneRosterNamespace(client) {
148567
148922
  return client["request"](ONEROSTER_ENDPOINTS.users, "POST", { user: data });
148568
148923
  },
148569
148924
  get: async (sourcedId) => {
148570
- return client["request"](`${ONEROSTER_ENDPOINTS.users}/${sourcedId}`, "GET");
148925
+ return client["request"](`${ONEROSTER_ENDPOINTS.users}/${sourcedId}`, "GET").then((res) => res.user);
148571
148926
  },
148572
148927
  findByEmail: async (email) => {
148573
148928
  const params = new URLSearchParams({ filter: `email='${email}'` });
@@ -148583,130 +148938,8 @@ function createOneRosterNamespace(client) {
148583
148938
  }
148584
148939
  };
148585
148940
  }
148586
- function createCaliperNamespace(client) {
148587
- const urls = createOneRosterUrls(client.getBaseUrl());
148588
- const caliper = {
148589
- emit: async (event, sensorUrl) => {
148590
- const envelope = {
148591
- sensor: sensorUrl,
148592
- sendTime: new Date().toISOString(),
148593
- dataVersion: CALIPER_CONSTANTS.dataVersion,
148594
- data: [event]
148595
- };
148596
- return client["requestCaliper"](CALIPER_ENDPOINTS.events, "POST", envelope);
148597
- },
148598
- emitBatch: async (events, sensorUrl) => {
148599
- if (events.length === 0)
148600
- return;
148601
- const envelope = {
148602
- sensor: sensorUrl,
148603
- sendTime: new Date().toISOString(),
148604
- dataVersion: CALIPER_CONSTANTS.dataVersion,
148605
- data: events
148606
- };
148607
- return client["requestCaliper"](CALIPER_ENDPOINTS.events, "POST", envelope);
148608
- },
148609
- emitActivityEvent: async (data) => {
148610
- const event = {
148611
- "@context": CALIPER_CONSTANTS.context,
148612
- id: `urn:uuid:${crypto.randomUUID()}`,
148613
- type: TIMEBACK_EVENT_TYPES.activityEvent,
148614
- eventTime: new Date().toISOString(),
148615
- profile: CALIPER_CONSTANTS.profile,
148616
- actor: {
148617
- id: urls.user(data.studentId),
148618
- type: TIMEBACK_TYPES.user,
148619
- email: data.studentEmail
148620
- },
148621
- action: TIMEBACK_ACTIONS.completed,
148622
- object: {
148623
- id: urls.componentResource(data.activityId),
148624
- type: TIMEBACK_TYPES.activityContext,
148625
- subject: data.subject,
148626
- app: {
148627
- name: data.appName
148628
- },
148629
- activity: {
148630
- name: data.activityName
148631
- },
148632
- course: { id: urls.course(data.courseId), name: data.activityName },
148633
- process: true
148634
- },
148635
- generated: {
148636
- id: `urn:timeback:metrics:activity-completion-${crypto.randomUUID()}`,
148637
- type: TIMEBACK_TYPES.activityMetricsCollection,
148638
- attempt: data.attemptNumber || 1,
148639
- items: [
148640
- ...data.totalQuestions !== undefined ? [
148641
- {
148642
- type: ACTIVITY_METRIC_TYPES.totalQuestions,
148643
- value: data.totalQuestions
148644
- }
148645
- ] : [],
148646
- ...data.correctQuestions !== undefined ? [
148647
- {
148648
- type: ACTIVITY_METRIC_TYPES.correctQuestions,
148649
- value: data.correctQuestions
148650
- }
148651
- ] : [],
148652
- ...data.xpEarned !== undefined ? [{ type: ACTIVITY_METRIC_TYPES.xpEarned, value: data.xpEarned }] : [],
148653
- ...data.masteredUnits !== undefined ? [
148654
- {
148655
- type: ACTIVITY_METRIC_TYPES.masteredUnits,
148656
- value: data.masteredUnits
148657
- }
148658
- ] : []
148659
- ]
148660
- }
148661
- };
148662
- return caliper.emit(event, data.sensorUrl);
148663
- },
148664
- emitTimeSpentEvent: async (data) => {
148665
- const event = {
148666
- "@context": CALIPER_CONSTANTS.context,
148667
- id: `urn:uuid:${crypto.randomUUID()}`,
148668
- type: TIMEBACK_EVENT_TYPES.timeSpentEvent,
148669
- eventTime: new Date().toISOString(),
148670
- profile: CALIPER_CONSTANTS.profile,
148671
- actor: {
148672
- id: urls.user(data.studentId),
148673
- type: TIMEBACK_TYPES.user,
148674
- email: data.studentEmail
148675
- },
148676
- action: TIMEBACK_ACTIONS.spentTime,
148677
- object: {
148678
- id: urls.componentResource(data.activityId),
148679
- type: TIMEBACK_TYPES.activityContext,
148680
- subject: data.subject,
148681
- app: {
148682
- name: data.appName
148683
- },
148684
- activity: {
148685
- name: data.activityName
148686
- },
148687
- course: { id: urls.course(data.courseId), name: data.activityName },
148688
- process: false
148689
- },
148690
- generated: {
148691
- id: `urn:timeback:metrics:time-spent-${crypto.randomUUID()}`,
148692
- type: TIMEBACK_TYPES.timeSpentMetricsCollection,
148693
- items: [
148694
- { type: TIME_METRIC_TYPES.active, value: data.activeTimeSeconds },
148695
- ...data.inactiveTimeSeconds !== undefined ? [
148696
- {
148697
- type: TIME_METRIC_TYPES.inactive,
148698
- value: data.inactiveTimeSeconds
148699
- }
148700
- ] : [],
148701
- ...data.wasteTimeSeconds !== undefined ? [{ type: TIME_METRIC_TYPES.waste, value: data.wasteTimeSeconds }] : []
148702
- ]
148703
- }
148704
- };
148705
- return caliper.emit(event, data.sensorUrl);
148706
- }
148707
- };
148708
- return caliper;
148709
- }
148941
+ init_constants();
148942
+ init_constants();
148710
148943
 
148711
148944
  class TimebackCache {
148712
148945
  cache = new Map;
@@ -148812,6 +149045,8 @@ class TimebackCache {
148812
149045
  class TimebackCacheManager {
148813
149046
  studentCache;
148814
149047
  assessmentLineItemCache;
149048
+ resourceMasteryCache;
149049
+ enrollmentCache;
148815
149050
  constructor() {
148816
149051
  this.studentCache = new TimebackCache({
148817
149052
  defaultTTL: CACHE_DEFAULTS.studentTTL,
@@ -148823,6 +149058,16 @@ class TimebackCacheManager {
148823
149058
  maxSize: CACHE_DEFAULTS.assessmentMaxSize,
148824
149059
  name: "AssessmentLineItemCache"
148825
149060
  });
149061
+ this.resourceMasteryCache = new TimebackCache({
149062
+ defaultTTL: CACHE_DEFAULTS.assessmentTTL,
149063
+ maxSize: CACHE_DEFAULTS.assessmentMaxSize,
149064
+ name: "ResourceMasteryCache"
149065
+ });
149066
+ this.enrollmentCache = new TimebackCache({
149067
+ defaultTTL: CACHE_DEFAULTS.enrollmentTTL,
149068
+ maxSize: CACHE_DEFAULTS.enrollmentMaxSize,
149069
+ name: "EnrollmentCache"
149070
+ });
148826
149071
  }
148827
149072
  getStudent(key) {
148828
149073
  return this.studentCache.get(key);
@@ -148836,26 +149081,188 @@ class TimebackCacheManager {
148836
149081
  setAssessmentLineItem(key, lineItemId) {
148837
149082
  this.assessmentLineItemCache.set(key, lineItemId);
148838
149083
  }
149084
+ getResourceMasterableUnits(key) {
149085
+ return this.resourceMasteryCache.get(key);
149086
+ }
149087
+ setResourceMasterableUnits(key, value) {
149088
+ this.resourceMasteryCache.set(key, value);
149089
+ }
149090
+ getEnrollments(studentId) {
149091
+ return this.enrollmentCache.get(studentId);
149092
+ }
149093
+ setEnrollments(studentId, enrollments) {
149094
+ this.enrollmentCache.set(studentId, enrollments);
149095
+ }
148839
149096
  clearAll() {
148840
149097
  this.studentCache.clear();
148841
149098
  this.assessmentLineItemCache.clear();
149099
+ this.resourceMasteryCache.clear();
149100
+ this.enrollmentCache.clear();
148842
149101
  log3.info("[TimebackCacheManager] All caches cleared");
148843
149102
  }
148844
149103
  getStats() {
148845
149104
  return {
148846
149105
  studentCache: this.studentCache.stats(),
148847
- assessmentLineItemCache: this.assessmentLineItemCache.stats()
149106
+ assessmentLineItemCache: this.assessmentLineItemCache.stats(),
149107
+ resourceMasteryCache: this.resourceMasteryCache.stats(),
149108
+ enrollmentCache: this.enrollmentCache.stats()
148848
149109
  };
148849
149110
  }
148850
149111
  cleanup() {
148851
149112
  this.studentCache.cleanup();
148852
149113
  this.assessmentLineItemCache.cleanup();
149114
+ this.resourceMasteryCache.cleanup();
149115
+ this.enrollmentCache.cleanup();
148853
149116
  log3.debug("[TimebackCacheManager] Cache cleanup completed");
148854
149117
  }
148855
149118
  }
149119
+ init_constants();
149120
+
149121
+ class MasteryTracker {
149122
+ cacheManager;
149123
+ onerosterNamespace;
149124
+ edubridgeNamespace;
149125
+ constructor(cacheManager, onerosterNamespace, edubridgeNamespace) {
149126
+ this.cacheManager = cacheManager;
149127
+ this.onerosterNamespace = onerosterNamespace;
149128
+ this.edubridgeNamespace = edubridgeNamespace;
149129
+ }
149130
+ async checkProgress(input) {
149131
+ const { studentId, courseId, resourceId, masteredUnits } = input;
149132
+ if (typeof masteredUnits !== "number" || masteredUnits <= 0) {
149133
+ return;
149134
+ }
149135
+ const masterableUnits = await this.resolveMasterableUnits(resourceId);
149136
+ if (!masterableUnits || masterableUnits <= 0) {
149137
+ log3.warn("[MasteryTracker] No masterableUnits configured for course", {
149138
+ courseId,
149139
+ resourceId
149140
+ });
149141
+ return;
149142
+ }
149143
+ const facts = await this.fetchEnrollmentAnalyticsFacts(studentId, courseId);
149144
+ if (!facts) {
149145
+ log3.warn("[MasteryTracker] Unable to retrieve analytics for mastery-based completion", {
149146
+ studentId,
149147
+ courseId
149148
+ });
149149
+ return;
149150
+ }
149151
+ const historicalMasteredUnits = this.sumAnalyticsMetric(facts, "masteredUnits");
149152
+ const totalMastered = historicalMasteredUnits + masteredUnits;
149153
+ const rawPct = totalMastered / masterableUnits * 100;
149154
+ const pctCompleteApp = Math.min(100, Math.max(0, Math.round(rawPct)));
149155
+ const masteryAchieved = totalMastered >= masterableUnits;
149156
+ return { pctCompleteApp, masteryAchieved };
149157
+ }
149158
+ async createCompletionEntry(studentId, courseId, classId, appName) {
149159
+ const ids = deriveSourcedIds(courseId);
149160
+ const lineItemId = `${ids.course}-mastery-completion-assessment`;
149161
+ const resultId = `${lineItemId}:${studentId}:completion`;
149162
+ try {
149163
+ await this.onerosterNamespace.assessmentLineItems.findOrCreate(lineItemId, {
149164
+ sourcedId: lineItemId,
149165
+ title: "Mastery Completion",
149166
+ status: ONEROSTER_STATUS.active,
149167
+ ...classId ? { class: { sourcedId: classId } } : { course: { sourcedId: ids.course } },
149168
+ ...ids.componentResource ? { componentResource: { sourcedId: ids.componentResource } } : {}
149169
+ });
149170
+ await this.onerosterNamespace.assessmentResults.upsert(resultId, {
149171
+ sourcedId: resultId,
149172
+ status: ONEROSTER_STATUS.active,
149173
+ assessmentLineItem: { sourcedId: lineItemId },
149174
+ student: { sourcedId: studentId },
149175
+ score: 100,
149176
+ scoreDate: new Date().toISOString(),
149177
+ scoreStatus: SCORE_STATUS.fullyGraded,
149178
+ inProgress: "false",
149179
+ metadata: {
149180
+ isMasteryCompletion: true,
149181
+ completedAt: new Date().toISOString(),
149182
+ appName
149183
+ }
149184
+ });
149185
+ log3.info("[MasteryTracker] Created mastery completion entry", {
149186
+ studentId,
149187
+ lineItemId,
149188
+ resultId
149189
+ });
149190
+ } catch (error2) {
149191
+ log3.error("[MasteryTracker] Failed to create mastery completion entry", {
149192
+ studentId,
149193
+ lineItemId,
149194
+ error: error2
149195
+ });
149196
+ }
149197
+ }
149198
+ async resolveMasterableUnits(resourceId) {
149199
+ if (!resourceId) {
149200
+ return;
149201
+ }
149202
+ const cached = this.cacheManager.getResourceMasterableUnits(resourceId);
149203
+ if (cached !== undefined) {
149204
+ return cached === null ? undefined : cached;
149205
+ }
149206
+ try {
149207
+ const resource = await this.onerosterNamespace.resources.get(resourceId);
149208
+ const playcademyMetadata = resource.metadata?.playcademy;
149209
+ if (!playcademyMetadata) {
149210
+ return;
149211
+ }
149212
+ const masterableUnits = isPlaycademyResourceMetadata(playcademyMetadata) ? playcademyMetadata.mastery?.masterableUnits : undefined;
149213
+ this.cacheManager.setResourceMasterableUnits(resourceId, masterableUnits ?? null);
149214
+ return masterableUnits;
149215
+ } catch (error2) {
149216
+ log3.error("[MasteryTracker] Failed to fetch resource metadata for mastery config", {
149217
+ resourceId,
149218
+ error: error2
149219
+ });
149220
+ this.cacheManager.setResourceMasterableUnits(resourceId, null);
149221
+ return;
149222
+ }
149223
+ }
149224
+ async fetchEnrollmentAnalyticsFacts(studentId, courseId) {
149225
+ try {
149226
+ const enrollments = await this.edubridgeNamespace.enrollments.listByUser(studentId);
149227
+ const enrollment = enrollments.find((e) => e.course.id === courseId);
149228
+ if (!enrollment) {
149229
+ log3.warn("[MasteryTracker] Enrollment not found for student/course", {
149230
+ studentId,
149231
+ courseId
149232
+ });
149233
+ return;
149234
+ }
149235
+ const analytics = await this.edubridgeNamespace.analytics.getEnrollmentFacts(enrollment.id);
149236
+ return analytics.facts;
149237
+ } catch (error2) {
149238
+ log3.error("[MasteryTracker] Failed to load enrollment analytics facts", {
149239
+ studentId,
149240
+ courseId,
149241
+ error: error2
149242
+ });
149243
+ return;
149244
+ }
149245
+ }
149246
+ sumAnalyticsMetric(facts, metric) {
149247
+ if (!facts) {
149248
+ return 0;
149249
+ }
149250
+ let total = 0;
149251
+ Object.values(facts).forEach((dateFacts) => {
149252
+ Object.values(dateFacts).forEach((subjectFacts) => {
149253
+ const metrics = subjectFacts.activityMetrics;
149254
+ if (metrics && typeof metrics[metric] === "number") {
149255
+ total += metrics[metric];
149256
+ }
149257
+ });
149258
+ });
149259
+ return total;
149260
+ }
149261
+ }
148856
149262
  function kebabToTitleCase(kebabStr) {
148857
149263
  return kebabStr.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
148858
149264
  }
149265
+ init_constants();
148859
149266
  function validateProgressData(progressData) {
148860
149267
  if (!progressData.subject) {
148861
149268
  throw new ConfigurationError("subject", "Subject is required for Caliper events. Provide it in progressData.subject");
@@ -148909,36 +149316,47 @@ class ProgressRecorder {
148909
149316
  cacheManager;
148910
149317
  onerosterNamespace;
148911
149318
  caliperNamespace;
148912
- constructor(studentResolver, cacheManager, onerosterNamespace, caliperNamespace) {
149319
+ masteryTracker;
149320
+ constructor(studentResolver, cacheManager, onerosterNamespace, caliperNamespace, masteryTracker) {
148913
149321
  this.studentResolver = studentResolver;
148914
149322
  this.cacheManager = cacheManager;
148915
149323
  this.onerosterNamespace = onerosterNamespace;
148916
149324
  this.caliperNamespace = caliperNamespace;
149325
+ this.masteryTracker = masteryTracker;
148917
149326
  }
148918
149327
  async record(courseId, studentIdentifier, progressData) {
148919
149328
  validateProgressData(progressData);
148920
- const ids = deriveSourcedIds(courseId);
148921
- const activityId = progressData.activityId || ids.componentResource || ids.resource || "unknown-activity";
148922
- const activityName = progressData.activityName || kebabToTitleCase(activityId);
148923
- const classId = progressData.classId;
148924
- const courseName = progressData.courseName || "Game Course";
148925
- const student = await this.studentResolver.resolve(studentIdentifier, progressData.studentEmail);
149329
+ const { ids, activityId, activityName, courseName, student } = await this.resolveContext(courseId, studentIdentifier, progressData);
148926
149330
  const { id: studentId, email: studentEmail } = student;
148927
149331
  const { score, totalQuestions, correctQuestions, xpEarned, masteredUnits, attemptNumber } = progressData;
148928
- const lineItemId = `${activityId}-assessment`;
148929
- let actualLineItemId = this.cacheManager.getAssessmentLineItem(lineItemId);
148930
- if (!actualLineItemId) {
148931
- actualLineItemId = await this.getOrCreateLineItem(lineItemId, activityName, classId, ids);
148932
- this.cacheManager.setAssessmentLineItem(lineItemId, actualLineItemId);
148933
- }
148934
- let currentAttemptNumber = attemptNumber || 1;
148935
- const isFirstAttempt = currentAttemptNumber === 1;
148936
- if (!attemptNumber && score !== undefined) {
148937
- currentAttemptNumber = await this.determineAttemptNumber(studentId, actualLineItemId);
149332
+ const actualLineItemId = await this.resolveAssessmentLineItem(activityId, activityName, progressData.classId, ids);
149333
+ const currentAttemptNumber = await this.resolveAttemptNumber(attemptNumber, score, studentId, actualLineItemId);
149334
+ const isFirstActiveAttempt = currentAttemptNumber === 1;
149335
+ const calculatedXp = this.calculateXpForProgress(progressData, totalQuestions, correctQuestions, xpEarned, isFirstActiveAttempt);
149336
+ let extensions = progressData.extensions;
149337
+ const masteryProgress = await this.masteryTracker.checkProgress({
149338
+ studentId,
149339
+ courseId,
149340
+ resourceId: ids.resource,
149341
+ masteredUnits: progressData.masteredUnits ?? 0
149342
+ });
149343
+ let pctCompleteApp;
149344
+ let masteryAchieved = false;
149345
+ let scoreStatus = SCORE_STATUS.fullyGraded;
149346
+ const inProgress = "false";
149347
+ if (masteryProgress) {
149348
+ masteryAchieved = masteryProgress.masteryAchieved;
149349
+ pctCompleteApp = masteryProgress.pctCompleteApp;
149350
+ extensions = {
149351
+ ...extensions || {},
149352
+ ...pctCompleteApp !== undefined ? { pctCompleteApp } : {}
149353
+ };
149354
+ if (masteryAchieved) {
149355
+ scoreStatus = SCORE_STATUS.fullyGraded;
149356
+ }
148938
149357
  }
148939
- const calculatedXp = this.calculateXpForProgress(progressData, totalQuestions, correctQuestions, xpEarned, isFirstAttempt);
148940
149358
  if (score !== undefined) {
148941
- await this.createGradebookEntry(actualLineItemId, studentId, currentAttemptNumber, score, totalQuestions, correctQuestions, calculatedXp);
149359
+ await this.createGradebookEntry(actualLineItemId, studentId, currentAttemptNumber, score, totalQuestions, correctQuestions, calculatedXp, masteredUnits, scoreStatus, inProgress, progressData.appName);
148942
149360
  } else {
148943
149361
  log3.warn("[ProgressRecorder] Score not provided, skipping gradebook entry", {
148944
149362
  studentId,
@@ -148946,20 +149364,70 @@ class ProgressRecorder {
148946
149364
  attemptNumber: currentAttemptNumber
148947
149365
  });
148948
149366
  }
148949
- await this.emitCaliperEvent(studentId, studentEmail, activityId, activityName, ids.course, courseName, totalQuestions, correctQuestions, calculatedXp, masteredUnits, currentAttemptNumber, progressData);
149367
+ if (masteryAchieved) {
149368
+ await this.masteryTracker.createCompletionEntry(studentId, courseId, progressData.classId, progressData.appName);
149369
+ }
149370
+ await this.emitCaliperEvent(studentId, studentEmail, activityId, activityName, ids.course, courseName, totalQuestions, correctQuestions, calculatedXp, masteredUnits, currentAttemptNumber, progressData, extensions);
148950
149371
  return {
148951
149372
  xpAwarded: calculatedXp,
148952
- attemptNumber: currentAttemptNumber
149373
+ attemptNumber: currentAttemptNumber,
149374
+ masteredUnitsApplied: progressData.masteredUnits ?? 0,
149375
+ pctCompleteApp,
149376
+ scoreStatus,
149377
+ inProgress
148953
149378
  };
148954
149379
  }
149380
+ async resolveContext(courseId, studentIdentifier, progressData) {
149381
+ const ids = deriveSourcedIds(courseId);
149382
+ const activityId = progressData.activityId || ids.componentResource || ids.resource || "unknown-activity";
149383
+ const activityName = progressData.activityName || kebabToTitleCase(activityId);
149384
+ const courseName = progressData.courseName || "Game Course";
149385
+ const student = await this.studentResolver.resolve(studentIdentifier, progressData.studentEmail);
149386
+ return { ids, activityId, activityName, courseName, student };
149387
+ }
149388
+ async resolveAssessmentLineItem(activityId, activityName, classId, ids) {
149389
+ const lineItemId = `${ids.course}-${activityId}-assessment`;
149390
+ let actualLineItemId = this.cacheManager.getAssessmentLineItem(lineItemId);
149391
+ if (!actualLineItemId) {
149392
+ actualLineItemId = await this.getOrCreateLineItem(lineItemId, activityName, classId, ids);
149393
+ this.cacheManager.setAssessmentLineItem(lineItemId, actualLineItemId);
149394
+ }
149395
+ return actualLineItemId;
149396
+ }
149397
+ async resolveAttemptNumber(providedAttemptNumber, score, studentId, lineItemId) {
149398
+ if (providedAttemptNumber) {
149399
+ return providedAttemptNumber;
149400
+ }
149401
+ if (score !== undefined) {
149402
+ return this.determineAttemptNumber(studentId, lineItemId);
149403
+ }
149404
+ return 1;
149405
+ }
149406
+ calculateXpForProgress(progressData, totalQuestions, correctQuestions, xpEarned, isFirstAttempt) {
149407
+ if (xpEarned !== undefined) {
149408
+ log3.debug("[ProgressRecorder] Using provided XP", { xpEarned });
149409
+ return xpEarned;
149410
+ }
149411
+ if (progressData.sessionDurationSeconds && totalQuestions && correctQuestions) {
149412
+ const accuracy = correctQuestions / totalQuestions;
149413
+ const calculatedXp = calculateXp(progressData.sessionDurationSeconds, accuracy, isFirstAttempt);
149414
+ log3.debug("[ProgressRecorder] Calculated XP", {
149415
+ durationSeconds: progressData.sessionDurationSeconds,
149416
+ accuracy,
149417
+ isFirstAttempt,
149418
+ calculatedXp
149419
+ });
149420
+ return calculatedXp;
149421
+ }
149422
+ return 0;
149423
+ }
148955
149424
  async getOrCreateLineItem(lineItemId, activityName, classId, ids) {
148956
149425
  try {
148957
149426
  const lineItem = await this.onerosterNamespace.assessmentLineItems.findOrCreate(lineItemId, {
148958
149427
  sourcedId: lineItemId,
148959
149428
  title: activityName,
148960
149429
  status: ONEROSTER_STATUS.active,
148961
- ...classId ? { class: { sourcedId: classId } } : { course: { sourcedId: ids.course } },
148962
- ...ids.componentResource ? { componentResource: { sourcedId: ids.componentResource } } : ids.component ? { component: { sourcedId: ids.component } } : {}
149430
+ ...classId ? { class: { sourcedId: classId } } : { course: { sourcedId: ids.course } }
148963
149431
  });
148964
149432
  if (!lineItem.sourcedId) {
148965
149433
  throw new TimebackError(`Assessment line item created but has no sourcedId. ` + `This should not happen and indicates an upstream API issue.`);
@@ -148977,39 +149445,15 @@ class ProgressRecorder {
148977
149445
  }
148978
149446
  }
148979
149447
  async determineAttemptNumber(studentId, lineItemId) {
148980
- const latestAttempt = await this.onerosterNamespace.assessmentResults.getLatestForStudent(studentId, lineItemId);
148981
- if (latestAttempt) {
148982
- const previousAttemptNumber = latestAttempt.metadata?.attemptNumber || 0;
148983
- const newAttemptNumber = previousAttemptNumber + 1;
148984
- log3.debug("[ProgressRecorder] Found previous attempt, incrementing", {
148985
- previousAttemptNumber,
148986
- newAttemptNumber,
148987
- previousScore: latestAttempt.score
148988
- });
148989
- return newAttemptNumber;
149448
+ const stats = await this.onerosterNamespace.assessmentResults.getAttemptStats(studentId, lineItemId);
149449
+ if (stats) {
149450
+ return stats.activeAttemptCount + 1;
148990
149451
  }
148991
149452
  return 1;
148992
149453
  }
148993
- calculateXpForProgress(progressData, totalQuestions, correctQuestions, xpEarned, isFirstAttempt) {
148994
- if (xpEarned !== undefined) {
148995
- log3.debug("[ProgressRecorder] Using provided XP", { xpEarned });
148996
- return xpEarned;
148997
- }
148998
- if (progressData.sessionDurationSeconds && totalQuestions && correctQuestions) {
148999
- const accuracy = correctQuestions / totalQuestions;
149000
- const calculatedXp = calculateXp(progressData.sessionDurationSeconds, accuracy, isFirstAttempt);
149001
- log3.debug("[ProgressRecorder] Calculated XP", {
149002
- durationSeconds: progressData.sessionDurationSeconds,
149003
- accuracy,
149004
- isFirstAttempt,
149005
- calculatedXp
149006
- });
149007
- return calculatedXp;
149008
- }
149009
- return 0;
149010
- }
149011
- async createGradebookEntry(lineItemId, studentId, attemptNumber, score, totalQuestions, correctQuestions, xp) {
149012
- const resultId = `${lineItemId}:${studentId}:attempt-${attemptNumber}`;
149454
+ async createGradebookEntry(lineItemId, studentId, attemptNumber, score, totalQuestions, correctQuestions, xp, masteredUnits, scoreStatus, inProgress, appName) {
149455
+ const timestamp4 = Date.now().toString(36);
149456
+ const resultId = `${lineItemId}:${studentId}:${timestamp4}`;
149013
149457
  await this.onerosterNamespace.assessmentResults.upsert(resultId, {
149014
149458
  sourcedId: resultId,
149015
149459
  status: ONEROSTER_STATUS.active,
@@ -149017,18 +149461,21 @@ class ProgressRecorder {
149017
149461
  student: { sourcedId: studentId },
149018
149462
  score,
149019
149463
  scoreDate: new Date().toISOString(),
149020
- scoreStatus: SCORE_STATUS.fullyGraded,
149464
+ scoreStatus,
149465
+ inProgress,
149021
149466
  metadata: {
149022
149467
  xp,
149023
149468
  totalQuestions,
149024
149469
  correctQuestions,
149025
149470
  accuracy: totalQuestions && correctQuestions ? correctQuestions / totalQuestions * 100 : undefined,
149026
149471
  attemptNumber,
149027
- lastUpdated: new Date().toISOString()
149472
+ lastUpdated: new Date().toISOString(),
149473
+ masteredUnits,
149474
+ appName
149028
149475
  }
149029
149476
  });
149030
149477
  }
149031
- async emitCaliperEvent(studentId, studentEmail, activityId, activityName, courseId, courseName, totalQuestions, correctQuestions, xpEarned, masteredUnits, attemptNumber, progressData) {
149478
+ async emitCaliperEvent(studentId, studentEmail, activityId, activityName, courseId, courseName, totalQuestions, correctQuestions, xpEarned, masteredUnits, attemptNumber, progressData, extensions) {
149032
149479
  await this.caliperNamespace.emitActivityEvent({
149033
149480
  studentId,
149034
149481
  studentEmail,
@@ -149043,7 +149490,8 @@ class ProgressRecorder {
149043
149490
  attemptNumber,
149044
149491
  subject: progressData.subject,
149045
149492
  appName: progressData.appName,
149046
- sensorUrl: progressData.sensorUrl
149493
+ sensorUrl: progressData.sensorUrl,
149494
+ extensions: extensions || progressData.extensions
149047
149495
  }).catch((error2) => {
149048
149496
  log3.error("[ProgressRecorder] Failed to emit activity event", { error: error2 });
149049
149497
  });
@@ -151776,9 +152224,9 @@ class ZodUnion2 extends ZodType2 {
151776
152224
  return this._def.options;
151777
152225
  }
151778
152226
  }
151779
- ZodUnion2.create = (types4, params) => {
152227
+ ZodUnion2.create = (types22, params) => {
151780
152228
  return new ZodUnion2({
151781
- options: types4,
152229
+ options: types22,
151782
152230
  typeName: ZodFirstPartyTypeKind2.ZodUnion,
151783
152231
  ...processCreateParams2(params)
151784
152232
  });
@@ -153158,9 +153606,11 @@ class TimebackClient {
153158
153606
  };
153159
153607
  this.oneroster = createOneRosterNamespace(this);
153160
153608
  this.caliper = createCaliperNamespace(this);
153609
+ this.edubridge = createEduBridgeNamespace(this);
153161
153610
  this.cacheManager = new TimebackCacheManager;
153162
153611
  this.studentResolver = new StudentResolver(this.cacheManager, this.oneroster);
153163
- this.progressRecorder = new ProgressRecorder(this.studentResolver, this.cacheManager, this.oneroster, this.caliper);
153612
+ const masteryTracker = new MasteryTracker(this.cacheManager, this.oneroster, this.edubridge);
153613
+ this.progressRecorder = new ProgressRecorder(this.studentResolver, this.cacheManager, this.oneroster, this.caliper, masteryTracker);
153164
153614
  this.sessionRecorder = new SessionRecorder(this.studentResolver, this.caliper);
153165
153615
  if (this.credentials) {
153166
153616
  this._ensureAuthenticated().catch((error2) => {
@@ -153269,6 +153719,9 @@ class TimebackClient {
153269
153719
  }
153270
153720
  await this.authenticate();
153271
153721
  }
153722
+ async resolveStudent(studentIdentifier, providedEmail) {
153723
+ return this.studentResolver.resolve(studentIdentifier, providedEmail);
153724
+ }
153272
153725
  async recordProgress(courseId, studentIdentifier, progressData) {
153273
153726
  await this._ensureAuthenticated();
153274
153727
  return this.progressRecorder.record(courseId, studentIdentifier, progressData);
@@ -153277,6 +153730,28 @@ class TimebackClient {
153277
153730
  await this._ensureAuthenticated();
153278
153731
  return this.sessionRecorder.record(courseId, studentIdentifier, sessionData);
153279
153732
  }
153733
+ async getEnrollments(studentId) {
153734
+ const cached = this.cacheManager.getEnrollments(studentId);
153735
+ if (cached) {
153736
+ return cached;
153737
+ }
153738
+ await this._ensureAuthenticated();
153739
+ const edubridgeEnrollments = await this.edubridge.enrollments.listByUser(studentId);
153740
+ const enrollments = edubridgeEnrollments.map((enrollment) => {
153741
+ const grades = enrollment.course.grades ? enrollment.course.grades.map((g5) => parseInt(g5, 10)).filter(isTimebackGrade) : null;
153742
+ const subjects = enrollment.course.subjects ? enrollment.course.subjects.filter(isTimebackSubject) : null;
153743
+ return {
153744
+ sourcedId: enrollment.id,
153745
+ title: enrollment.course.title,
153746
+ courseId: enrollment.course.id,
153747
+ status: "active",
153748
+ grades,
153749
+ subjects
153750
+ };
153751
+ });
153752
+ this.cacheManager.setEnrollments(studentId, enrollments);
153753
+ return enrollments;
153754
+ }
153280
153755
  clearCaches() {
153281
153756
  this.cacheManager.clearAll();
153282
153757
  }
@@ -153288,6 +153763,7 @@ class TimebackClient {
153288
153763
  }
153289
153764
  oneroster;
153290
153765
  caliper;
153766
+ edubridge;
153291
153767
  async setup(config2, options) {
153292
153768
  return setupTimebackResources(this, config2, options);
153293
153769
  }
@@ -153305,6 +153781,310 @@ class TimebackClient {
153305
153781
  }
153306
153782
  }
153307
153783
 
153784
+ // ../timeback/dist/types.js
153785
+ var __esm4 = (fn2, res) => () => (fn2 && (res = fn2(fn2 = 0)), res);
153786
+ var init_auth2 = () => {};
153787
+ var PLAYCADEMY_BASE_URLS2;
153788
+ var init_domains2 = __esm4(() => {
153789
+ PLAYCADEMY_BASE_URLS2 = {
153790
+ production: "https://hub.playcademy.net",
153791
+ staging: "https://hub.dev.playcademy.net"
153792
+ };
153793
+ });
153794
+ var init_env_vars2 = () => {};
153795
+ var ITEM_SLUGS3;
153796
+ var CURRENCIES3;
153797
+ var BADGES3;
153798
+ var init_overworld2 = __esm4(() => {
153799
+ ITEM_SLUGS3 = {
153800
+ PLAYCADEMY_CREDITS: "PLAYCADEMY_CREDITS",
153801
+ PLAYCADEMY_XP: "PLAYCADEMY_XP",
153802
+ FOUNDING_MEMBER_BADGE: "FOUNDING_MEMBER_BADGE",
153803
+ EARLY_ADOPTER_BADGE: "EARLY_ADOPTER_BADGE",
153804
+ FIRST_GAME_BADGE: "FIRST_GAME_BADGE",
153805
+ COMMON_SWORD: "COMMON_SWORD",
153806
+ SMALL_HEALTH_POTION: "SMALL_HEALTH_POTION",
153807
+ SMALL_BACKPACK: "SMALL_BACKPACK",
153808
+ LAVA_LAMP: "LAVA_LAMP",
153809
+ BOOMBOX: "BOOMBOX",
153810
+ CABIN_BED: "CABIN_BED"
153811
+ };
153812
+ CURRENCIES3 = {
153813
+ PRIMARY: ITEM_SLUGS3.PLAYCADEMY_CREDITS,
153814
+ XP: ITEM_SLUGS3.PLAYCADEMY_XP
153815
+ };
153816
+ BADGES3 = {
153817
+ FOUNDING_MEMBER: ITEM_SLUGS3.FOUNDING_MEMBER_BADGE,
153818
+ EARLY_ADOPTER: ITEM_SLUGS3.EARLY_ADOPTER_BADGE,
153819
+ FIRST_GAME: ITEM_SLUGS3.FIRST_GAME_BADGE
153820
+ };
153821
+ });
153822
+ var init_timeback2 = () => {};
153823
+ var init_workers2 = () => {};
153824
+ var init_src3 = __esm4(() => {
153825
+ init_auth2();
153826
+ init_domains2();
153827
+ init_env_vars2();
153828
+ init_overworld2();
153829
+ init_timeback2();
153830
+ init_workers2();
153831
+ });
153832
+ var TIMEBACK_API_URLS2;
153833
+ var TIMEBACK_AUTH_URLS2;
153834
+ var CALIPER_API_URLS2;
153835
+ var ONEROSTER_ENDPOINTS2;
153836
+ var CALIPER_ENDPOINTS2;
153837
+ var CALIPER_CONSTANTS2;
153838
+ var TIMEBACK_EVENT_TYPES2;
153839
+ var TIMEBACK_ACTIONS2;
153840
+ var TIMEBACK_TYPES2;
153841
+ var ACTIVITY_METRIC_TYPES2;
153842
+ var TIME_METRIC_TYPES2;
153843
+ var TIMEBACK_SUBJECTS2;
153844
+ var TIMEBACK_GRADE_LEVELS2;
153845
+ var TIMEBACK_GRADE_LEVEL_LABELS2;
153846
+ var CALIPER_SUBJECTS2;
153847
+ var ONEROSTER_STATUS2;
153848
+ var SCORE_STATUS2;
153849
+ var ENV_VARS2;
153850
+ var HTTP_DEFAULTS2;
153851
+ var AUTH_DEFAULTS2;
153852
+ var CACHE_DEFAULTS2;
153853
+ var CONFIG_DEFAULTS2;
153854
+ var DEFAULT_PLAYCADEMY_ORGANIZATION_ID2;
153855
+ var PLAYCADEMY_DEFAULTS2;
153856
+ var RESOURCE_DEFAULTS2;
153857
+ var HTTP_STATUS2;
153858
+ var ERROR_NAMES2;
153859
+ var init_constants2 = __esm4(() => {
153860
+ init_src3();
153861
+ TIMEBACK_API_URLS2 = {
153862
+ production: "https://api.alpha-1edtech.ai",
153863
+ staging: "https://api.staging.alpha-1edtech.com"
153864
+ };
153865
+ TIMEBACK_AUTH_URLS2 = {
153866
+ production: "https://prod-beyond-timeback-api-2-idp.auth.us-east-1.amazoncognito.com",
153867
+ staging: "https://alpha-auth-development-idp.auth.us-west-2.amazoncognito.com"
153868
+ };
153869
+ CALIPER_API_URLS2 = {
153870
+ production: "https://caliper.alpha-1edtech.ai",
153871
+ staging: "https://caliper-staging.alpha-1edtech.com"
153872
+ };
153873
+ ONEROSTER_ENDPOINTS2 = {
153874
+ organizations: "/ims/oneroster/rostering/v1p2/orgs",
153875
+ courses: "/ims/oneroster/rostering/v1p2/courses",
153876
+ courseComponents: "/ims/oneroster/rostering/v1p2/courses/components",
153877
+ resources: "/ims/oneroster/resources/v1p2/resources",
153878
+ componentResources: "/ims/oneroster/rostering/v1p2/courses/component-resources",
153879
+ classes: "/ims/oneroster/rostering/v1p2/classes",
153880
+ enrollments: "/ims/oneroster/rostering/v1p2/enrollments",
153881
+ assessmentLineItems: "/ims/oneroster/gradebook/v1p2/assessmentLineItems",
153882
+ assessmentResults: "/ims/oneroster/gradebook/v1p2/assessmentResults",
153883
+ users: "/ims/oneroster/rostering/v1p2/users"
153884
+ };
153885
+ CALIPER_ENDPOINTS2 = {
153886
+ events: "/caliper/event",
153887
+ validate: "/caliper/event/validate"
153888
+ };
153889
+ CALIPER_CONSTANTS2 = {
153890
+ context: "http://purl.imsglobal.org/ctx/caliper/v1p2",
153891
+ profile: "TimebackProfile",
153892
+ dataVersion: "http://purl.imsglobal.org/ctx/caliper/v1p2"
153893
+ };
153894
+ TIMEBACK_EVENT_TYPES2 = {
153895
+ activityEvent: "ActivityEvent",
153896
+ timeSpentEvent: "TimeSpentEvent"
153897
+ };
153898
+ TIMEBACK_ACTIONS2 = {
153899
+ completed: "Completed",
153900
+ spentTime: "SpentTime"
153901
+ };
153902
+ TIMEBACK_TYPES2 = {
153903
+ user: "TimebackUser",
153904
+ activityContext: "TimebackActivityContext",
153905
+ activityMetricsCollection: "TimebackActivityMetricsCollection",
153906
+ timeSpentMetricsCollection: "TimebackTimeSpentMetricsCollection"
153907
+ };
153908
+ ACTIVITY_METRIC_TYPES2 = {
153909
+ totalQuestions: "totalQuestions",
153910
+ correctQuestions: "correctQuestions",
153911
+ xpEarned: "xpEarned",
153912
+ masteredUnits: "masteredUnits"
153913
+ };
153914
+ TIME_METRIC_TYPES2 = {
153915
+ active: "active",
153916
+ inactive: "inactive",
153917
+ waste: "waste",
153918
+ unknown: "unknown",
153919
+ antiPattern: "anti-pattern"
153920
+ };
153921
+ TIMEBACK_SUBJECTS2 = [
153922
+ "Math",
153923
+ "FastMath",
153924
+ "Science",
153925
+ "Social Studies",
153926
+ "Language",
153927
+ "Reading",
153928
+ "Vocabulary",
153929
+ "Writing"
153930
+ ];
153931
+ TIMEBACK_GRADE_LEVELS2 = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
153932
+ TIMEBACK_GRADE_LEVEL_LABELS2 = {
153933
+ "-1": "pre-k",
153934
+ "0": "kindergarten",
153935
+ "1": "1st grade",
153936
+ "2": "2nd grade",
153937
+ "3": "3rd grade",
153938
+ "4": "4th grade",
153939
+ "5": "5th grade",
153940
+ "6": "6th grade",
153941
+ "7": "7th grade",
153942
+ "8": "8th grade",
153943
+ "9": "9th grade",
153944
+ "10": "10th grade",
153945
+ "11": "11th grade",
153946
+ "12": "12th grade",
153947
+ "13": "AP"
153948
+ };
153949
+ CALIPER_SUBJECTS2 = {
153950
+ Reading: "Reading",
153951
+ Language: "Language",
153952
+ Vocabulary: "Vocabulary",
153953
+ SocialStudies: "Social Studies",
153954
+ Writing: "Writing",
153955
+ Science: "Science",
153956
+ FastMath: "FastMath",
153957
+ Math: "Math",
153958
+ None: "None"
153959
+ };
153960
+ ONEROSTER_STATUS2 = {
153961
+ active: "active",
153962
+ toBeDeleted: "tobedeleted"
153963
+ };
153964
+ SCORE_STATUS2 = {
153965
+ exempt: "exempt",
153966
+ fullyGraded: "fully graded",
153967
+ notSubmitted: "not submitted",
153968
+ partiallyGraded: "partially graded",
153969
+ submitted: "submitted"
153970
+ };
153971
+ ENV_VARS2 = {
153972
+ clientId: "TIMEBACK_CLIENT_ID",
153973
+ clientSecret: "TIMEBACK_CLIENT_SECRET",
153974
+ baseUrl: "TIMEBACK_BASE_URL",
153975
+ environment: "TIMEBACK_ENVIRONMENT",
153976
+ vendorResourceId: "TIMEBACK_VENDOR_RESOURCE_ID",
153977
+ launchBaseUrl: "GAME_URL"
153978
+ };
153979
+ HTTP_DEFAULTS2 = {
153980
+ timeout: 30000,
153981
+ retries: 3,
153982
+ retryBackoffBase: 2
153983
+ };
153984
+ AUTH_DEFAULTS2 = {
153985
+ tokenCacheDuration: 50000
153986
+ };
153987
+ CACHE_DEFAULTS2 = {
153988
+ defaultTTL: 10 * 60 * 1000,
153989
+ defaultMaxSize: 500,
153990
+ defaultName: "TimebackCache",
153991
+ studentTTL: 10 * 60 * 1000,
153992
+ studentMaxSize: 500,
153993
+ assessmentTTL: 30 * 60 * 1000,
153994
+ assessmentMaxSize: 200,
153995
+ enrollmentTTL: 5 * 1000,
153996
+ enrollmentMaxSize: 100
153997
+ };
153998
+ CONFIG_DEFAULTS2 = {
153999
+ fileNames: ["timeback.config.js", "timeback.config.json"]
154000
+ };
154001
+ DEFAULT_PLAYCADEMY_ORGANIZATION_ID2 = process.env.TIMEBACK_ORG_SOURCE_ID || "PLAYCADEMY";
154002
+ PLAYCADEMY_DEFAULTS2 = {
154003
+ organization: DEFAULT_PLAYCADEMY_ORGANIZATION_ID2,
154004
+ launchBaseUrls: PLAYCADEMY_BASE_URLS2
154005
+ };
154006
+ RESOURCE_DEFAULTS2 = {
154007
+ organization: {
154008
+ name: "Playcademy Studios",
154009
+ type: "department"
154010
+ },
154011
+ course: {
154012
+ gradingScheme: "STANDARD",
154013
+ level: {
154014
+ elementary: "Elementary",
154015
+ middle: "Middle",
154016
+ high: "High",
154017
+ ap: "AP"
154018
+ },
154019
+ metadata: {
154020
+ goals: {
154021
+ dailyXp: 50,
154022
+ dailyLessons: 3
154023
+ },
154024
+ metrics: {
154025
+ totalXp: 1000,
154026
+ totalLessons: 50
154027
+ }
154028
+ }
154029
+ },
154030
+ component: {
154031
+ sortOrder: 1,
154032
+ prerequisiteCriteria: "ALL"
154033
+ },
154034
+ resource: {
154035
+ vendorId: "playcademy",
154036
+ roles: ["primary"],
154037
+ importance: "primary",
154038
+ metadata: {
154039
+ type: "interactive",
154040
+ toolProvider: "Playcademy",
154041
+ instructionalMethod: "exploratory",
154042
+ language: "en-US"
154043
+ }
154044
+ },
154045
+ componentResource: {
154046
+ sortOrder: 1,
154047
+ lessonType: "quiz"
154048
+ }
154049
+ };
154050
+ HTTP_STATUS2 = {
154051
+ CLIENT_ERROR_MIN: 400,
154052
+ CLIENT_ERROR_MAX: 500,
154053
+ SERVER_ERROR_MIN: 500
154054
+ };
154055
+ ERROR_NAMES2 = {
154056
+ timebackAuth: "TimebackAuthError",
154057
+ timebackApi: "TimebackApiError",
154058
+ timebackConfig: "TimebackConfigError",
154059
+ timebackSdk: "TimebackSDKError"
154060
+ };
154061
+ });
154062
+ init_constants2();
154063
+ var isObject3 = (value) => typeof value === "object" && value !== null;
154064
+ var SUBJECT_VALUES2 = TIMEBACK_SUBJECTS2;
154065
+ var GRADE_VALUES2 = TIMEBACK_GRADE_LEVELS2;
154066
+ function isCourseMetadata(value) {
154067
+ return isObject3(value);
154068
+ }
154069
+ function isResourceMetadata(value) {
154070
+ return isObject3(value);
154071
+ }
154072
+ function isPlaycademyResourceMetadata2(value) {
154073
+ if (!isObject3(value)) {
154074
+ return false;
154075
+ }
154076
+ if (!("mastery" in value) || value.mastery === undefined) {
154077
+ return true;
154078
+ }
154079
+ return isObject3(value.mastery);
154080
+ }
154081
+ function isTimebackSubject2(value) {
154082
+ return typeof value === "string" && SUBJECT_VALUES2.includes(value);
154083
+ }
154084
+ function isTimebackGrade2(value) {
154085
+ return typeof value === "number" && Number.isInteger(value) && GRADE_VALUES2.includes(value);
154086
+ }
154087
+
153308
154088
  // ../utils/src/uuid.ts
153309
154089
  var UUID_REGEX = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
153310
154090
  function isValidUUID(value) {
@@ -153590,6 +154370,95 @@ async function getTimebackClient() {
153590
154370
  }
153591
154371
  return timebackClient;
153592
154372
  }
154373
+ function buildResourceMetadata({
154374
+ baseMetadata,
154375
+ subject,
154376
+ grade,
154377
+ totalXp,
154378
+ masterableUnits
154379
+ }) {
154380
+ const normalizedBaseMetadata = isResourceMetadata(baseMetadata) ? baseMetadata : undefined;
154381
+ const metadata2 = {
154382
+ ...normalizedBaseMetadata || {}
154383
+ };
154384
+ metadata2.subject = subject;
154385
+ metadata2.grades = [grade];
154386
+ metadata2.xp = totalXp;
154387
+ if (masterableUnits !== undefined && masterableUnits !== null) {
154388
+ const existingPlaycademy = isPlaycademyResourceMetadata2(metadata2.playcademy) ? metadata2.playcademy : undefined;
154389
+ metadata2.playcademy = {
154390
+ ...existingPlaycademy || {},
154391
+ mastery: {
154392
+ ...existingPlaycademy?.mastery || {},
154393
+ masterableUnits
154394
+ }
154395
+ };
154396
+ }
154397
+ return metadata2;
154398
+ }
154399
+ // ../api-core/src/utils/timeback-enrollments.ts
154400
+ init_src();
154401
+ async function fetchEnrollmentsForUser(timebackId) {
154402
+ const db = getDatabase();
154403
+ const isLocal = process.env.PUBLIC_IS_LOCAL === "true";
154404
+ if (isLocal) {
154405
+ const allIntegrations = await db.query.gameTimebackIntegrations.findMany();
154406
+ return allIntegrations.map((integration) => ({
154407
+ gameId: integration.gameId,
154408
+ grade: integration.grade,
154409
+ subject: integration.subject,
154410
+ courseId: integration.courseId
154411
+ }));
154412
+ }
154413
+ log2.debug("[timeback-enrollments] Fetching student enrollments from TimeBack", { timebackId });
154414
+ try {
154415
+ const client = await getTimebackClient();
154416
+ const classes = await client.getEnrollments(timebackId);
154417
+ const courseIds = classes.map((cls) => cls.courseId).filter((id) => Boolean(id));
154418
+ if (courseIds.length === 0) {
154419
+ return [];
154420
+ }
154421
+ const integrations = await db.query.gameTimebackIntegrations.findMany({
154422
+ where: inArray(gameTimebackIntegrations.courseId, courseIds)
154423
+ });
154424
+ return integrations.map((integration) => ({
154425
+ gameId: integration.gameId,
154426
+ grade: integration.grade,
154427
+ subject: integration.subject,
154428
+ courseId: integration.courseId
154429
+ }));
154430
+ } catch (error2) {
154431
+ log2.warn("[timeback-enrollments] Failed to fetch TimeBack enrollments:", {
154432
+ error: error2,
154433
+ timebackId
154434
+ });
154435
+ return [];
154436
+ }
154437
+ }
154438
+ async function fetchUserRole(timebackId) {
154439
+ log2.debug("[timeback] Fetching user role from TimeBack", { timebackId });
154440
+ try {
154441
+ const client = await getTimebackClient();
154442
+ const user = await client.oneroster.users.get(timebackId);
154443
+ const primaryRole = user.roles.find((r2) => r2.roleType === "primary");
154444
+ const role = primaryRole?.role ?? user.roles[0]?.role ?? "student";
154445
+ log2.debug("[timeback] Resolved user role", { timebackId, role });
154446
+ return role;
154447
+ } catch (error2) {
154448
+ log2.warn("[timeback] Failed to fetch user role, defaulting to student:", {
154449
+ error: error2,
154450
+ timebackId
154451
+ });
154452
+ return "student";
154453
+ }
154454
+ }
154455
+ async function fetchUserTimebackData(timebackId) {
154456
+ const [role, enrollments] = await Promise.all([
154457
+ fetchUserRole(timebackId),
154458
+ fetchEnrollmentsForUser(timebackId)
154459
+ ]);
154460
+ return { role, enrollments };
154461
+ }
153593
154462
  // ../data/src/domains/achievement/types.ts
153594
154463
  var AchievementCompletionType;
153595
154464
  ((AchievementCompletionType2) => {
@@ -154125,6 +154994,249 @@ var AchievementService = {
154125
154994
  checkInteractionAchievement,
154126
154995
  generateAchievementMessage
154127
154996
  };
154997
+ // ../alerts/src/discord/embed.ts
154998
+ class DiscordEmbedBuilder {
154999
+ embed = {};
155000
+ setTitle(title) {
155001
+ this.embed.title = title;
155002
+ return this;
155003
+ }
155004
+ setDescription(description) {
155005
+ this.embed.description = description;
155006
+ return this;
155007
+ }
155008
+ setUrl(url) {
155009
+ this.embed.url = url;
155010
+ return this;
155011
+ }
155012
+ setColor(color) {
155013
+ this.embed.color = color;
155014
+ return this;
155015
+ }
155016
+ setTimestamp(date4) {
155017
+ this.embed.timestamp = (date4 || new Date).toISOString();
155018
+ return this;
155019
+ }
155020
+ setAuthor(name4, options) {
155021
+ this.embed.author = {
155022
+ name: name4,
155023
+ url: options?.url,
155024
+ icon_url: options?.iconUrl
155025
+ };
155026
+ return this;
155027
+ }
155028
+ setFooter(text5, iconUrl) {
155029
+ this.embed.footer = {
155030
+ text: text5,
155031
+ icon_url: iconUrl
155032
+ };
155033
+ return this;
155034
+ }
155035
+ setThumbnail(url) {
155036
+ this.embed.thumbnail = { url };
155037
+ return this;
155038
+ }
155039
+ setImage(url) {
155040
+ this.embed.image = { url };
155041
+ return this;
155042
+ }
155043
+ addField(name4, value, inline = false) {
155044
+ if (!this.embed.fields) {
155045
+ this.embed.fields = [];
155046
+ }
155047
+ this.embed.fields.push({ name: name4, value, inline });
155048
+ return this;
155049
+ }
155050
+ addFields(...fields) {
155051
+ for (const field of fields) {
155052
+ this.addField(field.name, field.value, field.inline);
155053
+ }
155054
+ return this;
155055
+ }
155056
+ build() {
155057
+ return this.embed;
155058
+ }
155059
+ static create() {
155060
+ return new DiscordEmbedBuilder;
155061
+ }
155062
+ }
155063
+
155064
+ // ../alerts/src/discord/client.ts
155065
+ class DiscordClient {
155066
+ config;
155067
+ constructor(config2) {
155068
+ if (!config2.url) {
155069
+ throw new Error("Discord webhook URL is required");
155070
+ }
155071
+ if (!config2.url.startsWith("https://discord.com/api/webhooks/")) {
155072
+ throw new Error("Invalid Discord webhook URL format");
155073
+ }
155074
+ this.config = config2;
155075
+ }
155076
+ async send(message2) {
155077
+ const payload = {
155078
+ content: message2,
155079
+ username: this.config.username,
155080
+ avatar_url: this.config.avatarUrl
155081
+ };
155082
+ await this.sendPayload(payload);
155083
+ }
155084
+ async sendEmbed(embed) {
155085
+ const payload = {
155086
+ embeds: [embed],
155087
+ username: this.config.username,
155088
+ avatar_url: this.config.avatarUrl
155089
+ };
155090
+ await this.sendPayload(payload);
155091
+ }
155092
+ async sendEmbeds(embeds) {
155093
+ if (embeds.length > 10) {
155094
+ throw new Error("Discord allows maximum 10 embeds per message");
155095
+ }
155096
+ const payload = {
155097
+ embeds,
155098
+ username: this.config.username,
155099
+ avatar_url: this.config.avatarUrl
155100
+ };
155101
+ await this.sendPayload(payload);
155102
+ }
155103
+ async sendWithEmbeds(message2, embeds) {
155104
+ const payload = {
155105
+ content: message2,
155106
+ embeds,
155107
+ username: this.config.username,
155108
+ avatar_url: this.config.avatarUrl
155109
+ };
155110
+ await this.sendPayload(payload);
155111
+ }
155112
+ async sendRich(message2) {
155113
+ const builder = new DiscordEmbedBuilder;
155114
+ if (message2.title)
155115
+ builder.setTitle(message2.title);
155116
+ if (message2.description)
155117
+ builder.setDescription(message2.description);
155118
+ if (message2.url)
155119
+ builder.setUrl(message2.url);
155120
+ if (message2.color) {
155121
+ const color = typeof message2.color === "string" ? parseInt(message2.color.replace("#", ""), 16) : message2.color;
155122
+ builder.setColor(color);
155123
+ }
155124
+ if (message2.author) {
155125
+ builder.setAuthor(message2.author.name, {
155126
+ url: message2.author.url,
155127
+ iconUrl: message2.author.iconUrl
155128
+ });
155129
+ }
155130
+ if (message2.fields) {
155131
+ for (const field of message2.fields) {
155132
+ builder.addField(field.name, field.value, field.inline);
155133
+ }
155134
+ }
155135
+ if (message2.footer) {
155136
+ builder.setFooter(message2.footer.text, message2.footer.iconUrl);
155137
+ }
155138
+ if (message2.timestamp) {
155139
+ const date4 = message2.timestamp instanceof Date ? message2.timestamp : new Date(message2.timestamp);
155140
+ builder.setTimestamp(date4);
155141
+ }
155142
+ if (message2.thumbnail) {
155143
+ builder.setThumbnail(message2.thumbnail.url);
155144
+ }
155145
+ if (message2.image) {
155146
+ builder.setImage(message2.image.url);
155147
+ }
155148
+ await this.sendEmbed(builder.build());
155149
+ }
155150
+ async sendPayload(payload) {
155151
+ const response = await fetch(this.config.url, {
155152
+ method: "POST",
155153
+ headers: {
155154
+ "Content-Type": "application/json"
155155
+ },
155156
+ body: JSON.stringify(payload)
155157
+ });
155158
+ if (!response.ok) {
155159
+ const errorText = await response.text().catch(() => "Unknown error");
155160
+ throw new Error(`Discord webhook request failed: ${response.status} ${response.statusText} - ${errorText}`);
155161
+ }
155162
+ }
155163
+ getRedactedUrl() {
155164
+ const parts2 = this.config.url.split("/");
155165
+ const token = parts2[parts2.length - 1];
155166
+ return `${token?.slice(0, 9)}...`;
155167
+ }
155168
+ }
155169
+ // ../alerts/src/discord/types.ts
155170
+ var DiscordColors = {
155171
+ DEFAULT: 0,
155172
+ WHITE: 16777215,
155173
+ AQUA: 1752220,
155174
+ GREEN: 5763719,
155175
+ BLUE: 3447003,
155176
+ YELLOW: 16705372,
155177
+ PURPLE: 10181046,
155178
+ LUMINOUS_VIVID_PINK: 15277667,
155179
+ FUCHSIA: 15418782,
155180
+ GOLD: 15844367,
155181
+ ORANGE: 15105570,
155182
+ RED: 15548997,
155183
+ GREY: 9807270,
155184
+ NAVY: 3426654,
155185
+ DARK_AQUA: 1146986,
155186
+ DARK_GREEN: 2067276,
155187
+ DARK_BLUE: 2123412,
155188
+ DARK_PURPLE: 7419530,
155189
+ DARK_VIVID_PINK: 11342935,
155190
+ DARK_GOLD: 12745742,
155191
+ DARK_ORANGE: 11027200,
155192
+ DARK_RED: 10038562,
155193
+ DARK_GREY: 9936031,
155194
+ DARKER_GREY: 8359053,
155195
+ LIGHT_GREY: 12370112,
155196
+ DARK_NAVY: 2899536,
155197
+ BLURPLE: 5793266,
155198
+ GREYPLE: 10070709,
155199
+ DARK_BUT_NOT_BLACK: 2895667,
155200
+ NOT_QUITE_BLACK: 2303786
155201
+ };
155202
+ // ../api-core/src/utils/alerts.ts
155203
+ init_src();
155204
+ function getDiscordClient() {
155205
+ const webhookUrl = process.env.DISCORD_WEBHOOK_PLATFORM;
155206
+ if (!webhookUrl) {
155207
+ log2.debug("[API] Discord webhook not configured, skipping notification");
155208
+ return null;
155209
+ }
155210
+ return new DiscordClient({
155211
+ url: webhookUrl,
155212
+ username: "Playcademy Platform",
155213
+ avatarUrl: process.env.DISCORD_WEBHOOK_AVATAR_URL
155214
+ });
155215
+ }
155216
+ function getEnvironment() {
155217
+ const stage = process.env.SST_STAGE;
155218
+ return stage === "production" ? "production" : stage === "dev" ? "staging" : "test";
155219
+ }
155220
+ async function sendDeveloperApplicationAlert(user) {
155221
+ const discord = getDiscordClient();
155222
+ if (!discord)
155223
+ return;
155224
+ const embed = new DiscordEmbedBuilder().setTitle("\uD83C\uDFAE New Developer Application").setDescription(`A user has applied for developer status (**${getEnvironment()}**).`).setColor(DiscordColors.GREEN).addField("User ID", user.id, true).addField("Email", user.email || "Not provided", true).setFooter("Playcademy Developer Platform").setTimestamp().build();
155225
+ await discord.sendEmbed(embed);
155226
+ log2.debug("[API] Discord notification sent successfully", {
155227
+ userId: user.id
155228
+ });
155229
+ }
155230
+ async function sendGameDeletionAlert(game) {
155231
+ const discord = getDiscordClient();
155232
+ if (!discord)
155233
+ return;
155234
+ const embed = new DiscordEmbedBuilder().setTitle("\uD83D\uDDD1️ Game Deleted").setDescription(`**${game.displayName || game.slug}** has been deleted (**${getEnvironment()}**).`).setColor(DiscordColors.ORANGE).addField("Slug", game.slug, true).addField("Developer", game.developer.email || game.developer.id, true).setFooter("Playcademy Developer Platform").setTimestamp().build();
155235
+ await discord.sendEmbed(embed);
155236
+ log2.debug("[API] Discord game deletion notification sent", {
155237
+ slug: game.slug
155238
+ });
155239
+ }
154128
155240
  // ../../node_modules/aws-jwt-verify/dist/esm/error.js
154129
155241
  class JwtBaseError extends Error {
154130
155242
  }
@@ -155262,10 +156374,8 @@ async function publishPersonalBestNotification(userId, gameId, rank, newScore, p
155262
156374
  });
155263
156375
  }
155264
156376
  // ../cloudflare/src/playcademy/provider.ts
155265
- import { execSync } from "node:child_process";
155266
- import { mkdir, rm, writeFile } from "node:fs/promises";
155267
- import { tmpdir } from "node:os";
155268
- import { join as join5 } from "node:path";
156377
+ import { readdir as readdir2, readFile as readFile2, stat } from "node:fs/promises";
156378
+ import { join as join4, relative } from "node:path";
155269
156379
  init_src();
155270
156380
 
155271
156381
  // ../cloudflare/src/core/client.ts
@@ -156094,7 +157204,6 @@ function normalizeDeploymentOptions(options) {
156094
157204
  assets: options?.bindings?.assets ?? false
156095
157205
  },
156096
157206
  assetsPath: options?.assetsPath,
156097
- assetsZip: options?.assetsZip,
156098
157207
  keepAssets: options?.keepAssets
156099
157208
  };
156100
157209
  }
@@ -156144,41 +157253,6 @@ var DEFAULT_COMPATIBILITY_DATE2 = new Date().toISOString().slice(0, 10);
156144
157253
  var GAME_WORKER_DOMAIN_PRODUCTION = GAME_WORKER_DOMAINS.production;
156145
157254
  var GAME_WORKER_DOMAIN_STAGING = GAME_WORKER_DOMAINS.staging;
156146
157255
 
156147
- // ../cloudflare/src/playcademy/workers/index.ts
156148
- import { dirname as dirname2, join as join4 } from "node:path";
156149
- import { fileURLToPath } from "node:url";
156150
-
156151
- // ../cloudflare/src/utils/bundler.ts
156152
- var esbuild = __toESM(require_main(), 1);
156153
- async function bundleWorker(filePath) {
156154
- const result = await esbuild.build({
156155
- entryPoints: [filePath],
156156
- bundle: true,
156157
- format: "esm",
156158
- platform: "browser",
156159
- target: "es2022",
156160
- write: false,
156161
- minify: false,
156162
- sourcemap: false,
156163
- logLevel: "error"
156164
- });
156165
- if (!result.outputFiles?.[0]) {
156166
- throw new Error(`No output generated for worker ${filePath}`);
156167
- }
156168
- return result.outputFiles[0].text;
156169
- }
156170
- // ../cloudflare/src/playcademy/workers/index.ts
156171
- var _cachedStubWorker = null;
156172
- async function getStubWorkerCode() {
156173
- if (_cachedStubWorker) {
156174
- return _cachedStubWorker;
156175
- }
156176
- const currentDir = dirname2(fileURLToPath(import.meta.url));
156177
- const workerPath = join4(currentDir, "stub-worker.ts");
156178
- _cachedStubWorker = await bundleWorker(workerPath);
156179
- return _cachedStubWorker;
156180
- }
156181
-
156182
157256
  // ../cloudflare/src/playcademy/provider.ts
156183
157257
  class CloudflareProvider {
156184
157258
  client;
@@ -156216,41 +157290,75 @@ class CloudflareProvider {
156216
157290
  getClient() {
156217
157291
  return this.client;
156218
157292
  }
157293
+ async checkIfRequiresR2Strategy(assetsPath) {
157294
+ async function scanDirectory(dir) {
157295
+ const entries = await readdir2(dir, { withFileTypes: true });
157296
+ for (const entry of entries) {
157297
+ const fullPath = join4(dir, entry.name);
157298
+ if (entry.isDirectory()) {
157299
+ if (await scanDirectory(fullPath))
157300
+ return true;
157301
+ } else {
157302
+ const stats = await stat(fullPath);
157303
+ if (stats.size >= 25 * 1024 * 1024) {
157304
+ log2.info("[CloudflareProvider] Large file detected, R2 strategy required", {
157305
+ file: entry.name,
157306
+ size: `${(stats.size / 1024 / 1024).toFixed(2)}MB`
157307
+ });
157308
+ return true;
157309
+ }
157310
+ }
157311
+ }
157312
+ return false;
157313
+ }
157314
+ return await scanDirectory(assetsPath);
157315
+ }
157316
+ async resolveAssetBasePath(dirPath) {
157317
+ const entries = await readdir2(dirPath, { withFileTypes: true });
157318
+ if (entries.length === 1 && entries[0]?.isDirectory()) {
157319
+ const unwrappedPath = join4(dirPath, entries[0].name);
157320
+ log2.debug("[CloudflareProvider] Unwrapping wrapper directory", {
157321
+ wrapper: entries[0].name
157322
+ });
157323
+ return await this.resolveAssetBasePath(unwrappedPath);
157324
+ }
157325
+ return dirPath;
157326
+ }
157327
+ async uploadFilesToR2(dir, baseDir, bucketName) {
157328
+ const entries = await readdir2(dir, { withFileTypes: true });
157329
+ for (const entry of entries) {
157330
+ const fullPath = join4(dir, entry.name);
157331
+ if (entry.isDirectory()) {
157332
+ await this.uploadFilesToR2(fullPath, baseDir, bucketName);
157333
+ } else {
157334
+ const content = await readFile2(fullPath);
157335
+ const relativePath = relative(baseDir, fullPath).replace(/\\/g, "/");
157336
+ const contentType = getContentType(relativePath);
157337
+ await this.client.r2.putObject(bucketName, relativePath, content, contentType);
157338
+ }
157339
+ }
157340
+ }
157341
+ async uploadDirectoryToR2(dirPath, bucketName) {
157342
+ const basePath = await this.resolveAssetBasePath(dirPath);
157343
+ log2.debug("[CloudflareProvider] Uploading assets to R2", {
157344
+ bucketName,
157345
+ basePath,
157346
+ filesFrom: relative(dirPath, basePath) || "(root)"
157347
+ });
157348
+ await this.uploadFilesToR2(basePath, basePath, bucketName);
157349
+ }
156219
157350
  async deploy(deploymentId, code, env2, options) {
156220
157351
  const opts = normalizeDeploymentOptions(options);
156221
157352
  const isFirstDeploy = env2.PLAYCADEMY_API_KEY !== undefined;
156222
- const hasAssets = !!(opts.assetsPath || opts.assetsZip);
156223
- let finalCode = code ?? "";
157353
+ const hasAssets = !!opts.assetsPath;
156224
157354
  if (!code) {
156225
- try {
156226
- log2.debug("[CloudflareProvider] Fetching existing script", {
156227
- deploymentId,
156228
- namespace: this.config.dispatchNamespace
156229
- });
156230
- finalCode = await this.client.workers.getScriptContent(this.config.dispatchNamespace, deploymentId);
156231
- } catch {
156232
- log2.debug("[CloudflareProvider] No existing script, using stub worker", {
156233
- deploymentId
156234
- });
156235
- finalCode = await getStubWorkerCode();
156236
- }
157355
+ throw new Error(`No worker code provided for deployment. ` + `Frontend-only deployments should include a stub worker from the CLI.`);
156237
157356
  }
157357
+ const finalCode = code;
156238
157358
  if (opts.keepAssets === undefined) {
156239
157359
  opts.keepAssets = true;
156240
157360
  }
156241
- let tempDir = null;
156242
- let finalAssetsPath = opts.assetsPath;
156243
- if (opts.assetsZip && !opts.assetsPath) {
156244
- tempDir = join5(tmpdir(), `playcademy-assets-${Date.now()}`);
156245
- finalAssetsPath = join5(tempDir, "dist");
156246
- await mkdir(finalAssetsPath, { recursive: true });
156247
- await writeFile(join5(tempDir, "temp.zip"), opts.assetsZip);
156248
- execSync(`unzip -q ${join5(tempDir, "temp.zip")} -d ${finalAssetsPath}`);
156249
- log2.debug("[CloudflareProvider] Extracted ZIP to temp directory", {
156250
- tempDir,
156251
- size: opts.assetsZip.length
156252
- });
156253
- }
157361
+ const finalAssetsPath = opts.assetsPath;
156254
157362
  log2.info("[CloudflareProvider] Deploying worker to dispatch namespace", {
156255
157363
  deploymentId,
156256
157364
  namespace: this.config.dispatchNamespace,
@@ -156299,16 +157407,45 @@ class CloudflareProvider {
156299
157407
  warnDurableObjectsNotImplemented(opts.bindings.durableObjects);
156300
157408
  addResourceBindings(bindings, resourceIds);
156301
157409
  let assetsJWT;
157410
+ let assetsStrategy;
157411
+ let assetsR2Bucket;
156302
157412
  if (finalAssetsPath) {
156303
- log2.debug("[CloudflareProvider] Uploading assets", {
156304
- deploymentId,
156305
- finalAssetsPath
156306
- });
156307
- assetsJWT = await this.client.workers.uploadAssets(this.config.dispatchNamespace, deploymentId, { type: "directory", path: finalAssetsPath });
156308
- }
156309
- if (finalAssetsPath || assetsJWT || opts.keepAssets) {
157413
+ const requiresR2 = await this.checkIfRequiresR2Strategy(finalAssetsPath);
157414
+ if (requiresR2) {
157415
+ assetsStrategy = "r2";
157416
+ assetsR2Bucket = `${deploymentId}-assets`;
157417
+ log2.info("[CloudflareProvider] Using R2 strategy for large files", {
157418
+ deploymentId,
157419
+ bucketName: assetsR2Bucket
157420
+ });
157421
+ await this.client.r2.create(assetsR2Bucket);
157422
+ await this.uploadDirectoryToR2(finalAssetsPath, assetsR2Bucket);
157423
+ bindings.push({
157424
+ type: "r2_bucket",
157425
+ name: "ASSETS",
157426
+ bucket_name: assetsR2Bucket
157427
+ });
157428
+ if (!resources.r2)
157429
+ resources.r2 = [];
157430
+ resources.r2.push({ name: assetsR2Bucket });
157431
+ } else {
157432
+ assetsStrategy = "workers-assets";
157433
+ log2.debug("[CloudflareProvider] Using Workers Assets strategy", {
157434
+ deploymentId,
157435
+ finalAssetsPath
157436
+ });
157437
+ assetsJWT = await this.client.workers.uploadAssets(this.config.dispatchNamespace, deploymentId, { type: "directory", path: finalAssetsPath });
157438
+ bindings.push({ type: "assets", name: "ASSETS" });
157439
+ }
157440
+ } else if (opts.keepAssets) {
156310
157441
  bindings.push({ type: "assets", name: "ASSETS" });
156311
157442
  }
157443
+ if (assetsStrategy) {
157444
+ resources.assetsStrategy = assetsStrategy;
157445
+ if (assetsR2Bucket) {
157446
+ resources.assetsR2Bucket = assetsR2Bucket;
157447
+ }
157448
+ }
156312
157449
  const keepBindingsValue = !isFirstDeploy ? ["secret_text"] : [];
156313
157450
  log2.info("[Cloudflare Provider] Deployment configuration", {
156314
157451
  deploymentId,
@@ -156350,10 +157487,6 @@ class CloudflareProvider {
156350
157487
  error: error2
156351
157488
  });
156352
157489
  throw new Error(`Failed to deploy to Cloudflare Workers for Platforms: ${error2 instanceof Error ? error2.message : String(error2)}`);
156353
- } finally {
156354
- if (tempDir) {
156355
- await rm(tempDir, { recursive: true, force: true }).catch(() => {});
156356
- }
156357
157490
  }
156358
157491
  }
156359
157492
  async delete(deploymentId, options) {
@@ -156389,7 +157522,8 @@ class CloudflareProvider {
156389
157522
  await Promise.all([
156390
157523
  this.client.d1.delete(deploymentId).catch(() => {}),
156391
157524
  this.client.kv.delete(deploymentId).catch(() => {}),
156392
- this.client.r2.delete(deploymentId).catch(() => {})
157525
+ this.client.r2.delete(deploymentId).catch(() => {}),
157526
+ this.client.r2.delete(`${deploymentId}-assets`).catch(() => {})
156393
157527
  ]);
156394
157528
  }
156395
157529
  async deleteCustomDomains(customDomains, gameSlug) {
@@ -156432,6 +157566,8 @@ class CloudflareProvider {
156432
157566
  });
156433
157567
  }
156434
157568
  }
157569
+ // ../cloudflare/src/utils/bundler.ts
157570
+ var esbuild = __toESM(require_main(), 1);
156435
157571
  // ../api-core/src/utils/cloudflare.ts
156436
157572
  init_src();
156437
157573
  var cloudflareProvider = null;
@@ -156728,20 +157864,6 @@ async function seedCoreGames(db) {
156728
157864
  console.error(`Error seeding core game '${gameData.slug}':`, error2);
156729
157865
  }
156730
157866
  }
156731
- if (config.timeback.courseId && hasTimebackCredentials()) {
156732
- try {
156733
- await db.insert(gameTimebackIntegrations).values({
156734
- id: crypto.randomUUID(),
156735
- gameId: CORE_GAME_UUIDS.PLAYGROUND,
156736
- courseId: config.timeback.courseId,
156737
- lastVerifiedAt: null,
156738
- createdAt: now2,
156739
- updatedAt: now2
156740
- }).onConflictDoNothing();
156741
- } catch (error2) {
156742
- console.error("Error seeding TimeBack integration for playground:", error2);
156743
- }
156744
- }
156745
157867
  }
156746
157868
  async function seedCurrentProjectGame(db, project) {
156747
157869
  const now2 = new Date;
@@ -156770,19 +157892,6 @@ async function seedCurrentProjectGame(db, project) {
156770
157892
  updatedAt: now2
156771
157893
  };
156772
157894
  const [newGame] = await db.insert(games).values(gameRecord).returning();
156773
- if (config.timeback.courseId && hasTimebackCredentials()) {
156774
- await db.insert(gameTimebackIntegrations).values({
156775
- id: crypto.randomUUID(),
156776
- gameId: newGame.id,
156777
- courseId: config.timeback.courseId,
156778
- lastVerifiedAt: null,
156779
- createdAt: now2,
156780
- updatedAt: now2
156781
- }).onConflictDoNothing();
156782
- } else if (hasTimebackCredentials() && !config.timeback.courseId) {
156783
- logger3.info(" ⚠ TimeBack credentials found but SANDBOX_TIMEBACK_COURSE_ID not set");
156784
- logger3.info(" Set SANDBOX_TIMEBACK_COURSE_ID to your real TimeBack course ID");
156785
- }
156786
157895
  return newGame;
156787
157896
  } catch (error2) {
156788
157897
  console.error("❌ Error seeding project game:", error2);
@@ -156844,13 +157953,26 @@ async function seedSpriteTemplates(db) {
156844
157953
  }
156845
157954
  }
156846
157955
 
157956
+ // src/database/seed/timeback.ts
157957
+ function resolveStudentId(studentId) {
157958
+ if (!studentId)
157959
+ return null;
157960
+ if (studentId === "mock")
157961
+ return `mock-student-${crypto.randomUUID().slice(0, 8)}`;
157962
+ return studentId;
157963
+ }
157964
+ function getAdminTimebackId() {
157965
+ return resolveStudentId(config.timeback.studentId);
157966
+ }
157967
+
156847
157968
  // src/database/seed/index.ts
156848
157969
  async function seedDemoData(db) {
156849
157970
  try {
157971
+ const adminTimebackId = getAdminTimebackId();
156850
157972
  for (const [role, user] of Object.entries(DEMO_USERS)) {
156851
157973
  const userValues = {
156852
157974
  ...user,
156853
- timebackId: role === "admin" && config.timeback.studentId ? config.timeback.studentId : null
157975
+ timebackId: role === "admin" ? adminTimebackId : null
156854
157976
  };
156855
157977
  await db.insert(users).values(userValues).onConflictDoNothing();
156856
157978
  }
@@ -156884,7 +158006,7 @@ async function checkIfNeedsSeeding(db) {
156884
158006
  }
156885
158007
  }
156886
158008
  async function setupServerDatabase(processedOptions, project) {
156887
- const { memoryOnly, recreateDb, databasePath, seed, verbose, quiet } = processedOptions;
158009
+ const { memoryOnly, recreateDb, databasePath, seed, quiet } = processedOptions;
156888
158010
  const effectiveDbPath = databasePath ?? process.env.PLAYCADEMY_SANDBOX_DB_PATH;
156889
158011
  const resolvedDbPath = memoryOnly || effectiveDbPath === ":memory:" ? ":memory:" : effectiveDbPath ? DatabasePathManager.resolveDatabasePath(effectiveDbPath) : DatabasePathManager.resolveDatabasePath();
156890
158012
  if (!memoryOnly && recreateDb && fs5.existsSync(resolvedDbPath)) {
@@ -156895,9 +158017,6 @@ async function setupServerDatabase(processedOptions, project) {
156895
158017
  }
156896
158018
  }
156897
158019
  const isNewDb = resolvedDbPath === ":memory:" ? true : !fs5.existsSync(resolvedDbPath);
156898
- if (verbose && !quiet && resolvedDbPath !== ":memory:") {
156899
- console.log(`[Sandbox] Database: ${resolvedDbPath}`);
156900
- }
156901
158020
  const db = await setupDatabase(resolvedDbPath);
156902
158021
  const shouldSeed = seed && (isNewDb || await checkIfNeedsSeeding(db));
156903
158022
  if (shouldSeed) {
@@ -157128,6 +158247,12 @@ async function applyForDeveloperStatus(ctx) {
157128
158247
  userId: user.id,
157129
158248
  newStatus: "pending"
157130
158249
  });
158250
+ sendDeveloperApplicationAlert(fullUser).catch((error2) => {
158251
+ log2.error("[API] Failed to send Discord notification for developer application", {
158252
+ userId: user.id,
158253
+ error: error2 instanceof Error ? error2.message : String(error2)
158254
+ });
158255
+ });
157131
158256
  } catch (error2) {
157132
158257
  log2.error(`Error updating developer status for user ${user.id}:`, {
157133
158258
  error: error2
@@ -161868,6 +162993,68 @@ var NotificationStatsSchema = exports_external2.object({
161868
162993
  startDate: exports_external2.date().optional(),
161869
162994
  endDate: exports_external2.date().optional()
161870
162995
  });
162996
+ // ../data/src/domains/timeback/schemas.ts
162997
+ var InsertTimebackDailyXpSchema = createInsertSchema(timebackDailyXp, {
162998
+ xp: exports_external2.number().min(0, "XP must be a non-negative number"),
162999
+ date: exports_external2.date()
163000
+ }).omit({ createdAt: true, updatedAt: true });
163001
+ var SelectTimebackDailyXpSchema = createSelectSchema(timebackDailyXp);
163002
+ var UpdateTimebackDailyXpSchema = createUpdateSchema(timebackDailyXp, {
163003
+ xp: exports_external2.number().min(0, "XP must be a non-negative number")
163004
+ }).omit({ userId: true, createdAt: true });
163005
+ var UpdateTimebackXpRequestSchema = exports_external2.object({
163006
+ xp: exports_external2.number().min(0, "XP must be a non-negative number"),
163007
+ userTimestamp: exports_external2.string().datetime().optional()
163008
+ });
163009
+ var TimebackDateQuerySchema = exports_external2.object({
163010
+ date: exports_external2.string().datetime().optional()
163011
+ });
163012
+ var TimebackHistoryQuerySchema = exports_external2.object({
163013
+ startDate: exports_external2.string().date().optional(),
163014
+ endDate: exports_external2.string().date().optional()
163015
+ });
163016
+ var InsertTimebackXpEventSchema = createInsertSchema(timebackXpEvents, {
163017
+ occurredAt: exports_external2.date(),
163018
+ xpDelta: exports_external2.number()
163019
+ }).omit({ createdAt: true, updatedAt: true });
163020
+ var SelectTimebackXpEventSchema = createSelectSchema(timebackXpEvents);
163021
+ var InsertGameTimebackIntegrationSchema = createInsertSchema(gameTimebackIntegrations, {
163022
+ gameId: exports_external2.string().uuid(),
163023
+ courseId: exports_external2.string().min(1),
163024
+ grade: exports_external2.number().int(),
163025
+ subject: exports_external2.string().min(1),
163026
+ totalXp: exports_external2.number().int().positive().optional()
163027
+ }).omit({ id: true, createdAt: true, updatedAt: true });
163028
+ var SelectGameTimebackIntegrationSchema = createSelectSchema(gameTimebackIntegrations);
163029
+ var UpdateGameTimebackIntegrationSchema = createUpdateSchema(gameTimebackIntegrations, {
163030
+ courseId: exports_external2.string().min(1).optional(),
163031
+ totalXp: exports_external2.number().int().positive().optional(),
163032
+ lastVerifiedAt: exports_external2.date().optional()
163033
+ }).omit({ id: true, gameId: true, grade: true, subject: true, createdAt: true });
163034
+ var EndActivityRequestSchema = exports_external2.object({
163035
+ gameId: exports_external2.string().uuid(),
163036
+ studentId: exports_external2.string().min(1),
163037
+ activityData: exports_external2.object({
163038
+ activityId: exports_external2.string().min(1),
163039
+ activityName: exports_external2.string().optional(),
163040
+ grade: exports_external2.number().int(),
163041
+ subject: exports_external2.string().min(1),
163042
+ appName: exports_external2.string().optional(),
163043
+ sensorUrl: exports_external2.string().url().optional(),
163044
+ courseId: exports_external2.string().optional(),
163045
+ courseName: exports_external2.string().optional(),
163046
+ studentEmail: exports_external2.string().email().optional()
163047
+ }),
163048
+ scoreData: exports_external2.object({
163049
+ correctQuestions: exports_external2.number().int().min(0),
163050
+ totalQuestions: exports_external2.number().int().min(0)
163051
+ }),
163052
+ timingData: exports_external2.object({
163053
+ durationSeconds: exports_external2.number().positive()
163054
+ }),
163055
+ xpEarned: exports_external2.number().optional(),
163056
+ masteredUnits: exports_external2.number().nonnegative().optional()
163057
+ });
161871
163058
  // ../api-core/src/handlers/games/leaderboard.ts
161872
163059
  init_src();
161873
163060
  async function submitScore(ctx) {
@@ -162036,6 +163223,26 @@ async function getUserAllScores(ctx) {
162036
163223
  throw ApiError.internal("Failed to fetch user scores");
162037
163224
  }
162038
163225
  }
163226
+ async function getUserScores(ctx) {
163227
+ const user = ctx.user;
163228
+ if (!user) {
163229
+ throw ApiError.unauthorized("Must be logged in to view user scores");
163230
+ }
163231
+ const { params, url } = ctx;
163232
+ const gameId = params.gameId;
163233
+ const userId = params.userId;
163234
+ if (!gameId || !userId) {
163235
+ throw ApiError.badRequest("Game ID and User ID are required");
163236
+ }
163237
+ const limit2 = Math.min(Number(url.searchParams.get("limit") || "10"), 100);
163238
+ const db = getDatabase();
163239
+ try {
163240
+ const scores = await db.select().from(gameScores).where(and(eq(gameScores.gameId, gameId), eq(gameScores.userId, userId))).orderBy(desc(gameScores.achievedAt)).limit(limit2);
163241
+ return scores;
163242
+ } catch {
163243
+ throw ApiError.internal("Failed to fetch user scores");
163244
+ }
163245
+ }
162039
163246
 
162040
163247
  // ../api-core/src/handlers/levels/index.ts
162041
163248
  init_src();
@@ -162172,9 +163379,11 @@ async function getUserMe(ctx) {
162172
163379
  const timebackAccount = await db.query.accounts.findFirst({
162173
163380
  where: and(eq(accounts.userId, user.id), eq(accounts.providerId, "timeback"))
162174
163381
  });
163382
+ const timeback3 = userData.timebackId ? await fetchUserTimebackData(userData.timebackId) : undefined;
162175
163383
  return {
162176
163384
  ...userData,
162177
- hasTimebackAccount: !!timebackAccount
163385
+ hasTimebackAccount: !!timebackAccount,
163386
+ timeback: timeback3
162178
163387
  };
162179
163388
  } catch (error2) {
162180
163389
  if (error2 instanceof ApiError)
@@ -162704,7 +163913,7 @@ async function deleteGame(ctx) {
162704
163913
  const db = getDatabase();
162705
163914
  const gameToDelete = await db.query.games.findFirst({
162706
163915
  where: eq(games.id, gameId),
162707
- columns: { id: true, slug: true }
163916
+ columns: { id: true, slug: true, displayName: true }
162708
163917
  });
162709
163918
  if (!gameToDelete || !gameToDelete.slug) {
162710
163919
  throw ApiError.notFound("Game not found for deletion");
@@ -162726,6 +163935,16 @@ async function deleteGame(ctx) {
162726
163935
  hadActiveDeployment: !!activeDeployment,
162727
163936
  customDomainsCount: customDomains.length
162728
163937
  });
163938
+ sendGameDeletionAlert({
163939
+ slug: gameToDelete.slug,
163940
+ displayName: gameToDelete.displayName,
163941
+ developer: {
163942
+ id: user.id,
163943
+ email: user.email
163944
+ }
163945
+ }).catch((err2) => {
163946
+ log2.warn("[API] Failed to send Discord game deletion notification", { err: err2 });
163947
+ });
162729
163948
  if (activeDeployment?.provider === "cloudflare") {
162730
163949
  try {
162731
163950
  const cloudflare2 = getCloudflareProvider();
@@ -164307,6 +165526,26 @@ gameScoresRouter.post("/:gameId/scores", async (c3) => {
164307
165526
  return c3.json(createUnknownErrorResponse(error2), 500);
164308
165527
  }
164309
165528
  });
165529
+ gameScoresRouter.get("/:gameId/users/:userId/scores", async (c3) => {
165530
+ const gameId = c3.req.param("gameId");
165531
+ const userId = c3.req.param("userId");
165532
+ const ctx = {
165533
+ user: c3.get("user"),
165534
+ params: { gameId, userId },
165535
+ url: new URL(c3.req.url),
165536
+ request: c3.req.raw
165537
+ };
165538
+ try {
165539
+ const result = await getUserScores(ctx);
165540
+ return c3.json(result, 200);
165541
+ } catch (error2) {
165542
+ if (error2 instanceof ApiError) {
165543
+ return c3.json(createErrorResponse(error2), error2.statusCode);
165544
+ }
165545
+ console.error("Error in getUserScores:", error2);
165546
+ return c3.json(createUnknownErrorResponse(error2), 500);
165547
+ }
165548
+ });
164310
165549
 
164311
165550
  // ../api-core/src/handlers/games/sessions.ts
164312
165551
  init_src();
@@ -166878,8 +168117,8 @@ async function getNotificationStats(ctx) {
166878
168117
  status: notifications.status,
166879
168118
  count: sql`count(*)`
166880
168119
  }).from(notifications).where(and(...conditions)).groupBy(notifications.status);
166881
- const statsMap = stats.reduce((acc, stat) => {
166882
- acc[stat.status] = Number(stat.count);
168120
+ const statsMap = stats.reduce((acc, stat2) => {
168121
+ acc[stat2.status] = Number(stat2.count);
166883
168122
  return acc;
166884
168123
  }, {});
166885
168124
  const total = Object.values(statsMap).reduce((sum3, count2) => sum3 + count2, 0);
@@ -167094,39 +168333,21 @@ async function endActivity(ctx) {
167094
168333
  throw ApiError.unauthorized("Must be logged in to end activity");
167095
168334
  }
167096
168335
  log2.debug("[API] Ending activity", { userId: user.id });
167097
- let request3;
167098
- try {
167099
- const requestBody = await ctx.request.json();
167100
- request3 = requestBody;
167101
- } catch (error2) {
167102
- log2.error("[API] Failed to parse request body:", { error: error2 });
167103
- throw ApiError.badRequest("Invalid JSON body");
167104
- }
167105
- const { gameId, studentId, activityData, scoreData, timingData, xpEarned } = request3;
167106
- if (!activityData?.activityId) {
167107
- throw ApiError.badRequest("activityId is required");
167108
- }
167109
- if (typeof scoreData?.correctQuestions !== "number" || typeof scoreData?.totalQuestions !== "number") {
167110
- throw ApiError.badRequest("correctQuestions and totalQuestions are required");
167111
- }
167112
- if (typeof timingData?.durationSeconds !== "number") {
167113
- throw ApiError.badRequest("durationSeconds is required");
168336
+ const requestBody = await ctx.request.json();
168337
+ const validation2 = EndActivityRequestSchema.safeParse(requestBody);
168338
+ if (!validation2.success) {
168339
+ throw ApiError.badRequest(formatValidationErrors(validation2.error));
167114
168340
  }
168341
+ const { gameId, studentId, activityData, scoreData, timingData, xpEarned, masteredUnits } = validation2.data;
168342
+ await verifyGameAccessById(gameId, user);
167115
168343
  const db = getDatabase();
167116
- const game = await db.query.games.findFirst({
167117
- where: eq(games.id, gameId)
167118
- });
167119
- if (!game) {
167120
- throw ApiError.notFound("Game not found");
167121
- }
167122
- if (user.role !== "admin" && game.developerId !== user.id) {
167123
- throw ApiError.forbidden("You do not own this game");
167124
- }
168344
+ const { grade, subject } = activityData;
167125
168345
  const integration = await db.query.gameTimebackIntegrations.findFirst({
167126
- where: eq(gameTimebackIntegrations.gameId, gameId)
168346
+ where: (table14, { eq: eq3, and: and3 }) => and3(eq3(table14.gameId, gameId), eq3(table14.grade, grade), eq3(table14.subject, subject))
167127
168347
  });
167128
168348
  if (!integration) {
167129
- throw ApiError.notFound("No TimeBack integration found for this game. Run setup first.");
168349
+ log2.error("[API] No TimeBack integration found", { gameId, grade, subject });
168350
+ throw ApiError.notFound(`No TimeBack integration found for this game (grade ${grade}, subject ${subject}). Run setup first.`);
167130
168351
  }
167131
168352
  const client2 = await getTimebackClient();
167132
168353
  try {
@@ -167137,6 +168358,7 @@ async function endActivity(ctx) {
167137
168358
  correctQuestions: scoreData.correctQuestions,
167138
168359
  sessionDurationSeconds: timingData.durationSeconds,
167139
168360
  xpEarned,
168361
+ masteredUnits,
167140
168362
  activityId: activityData.activityId,
167141
168363
  activityName: activityData.activityName,
167142
168364
  subject: activityData.subject,
@@ -167144,8 +168366,11 @@ async function endActivity(ctx) {
167144
168366
  sensorUrl: activityData.sensorUrl,
167145
168367
  courseId: activityData.courseId,
167146
168368
  courseName: activityData.courseName,
167147
- studentEmail: activityData.studentEmail
168369
+ studentEmail: activityData.studentEmail,
168370
+ courseTotalXp: integration.totalXp
167148
168371
  });
168372
+ const studentData = await client2.resolveStudent(studentId);
168373
+ const studentEmail = studentData.email;
167149
168374
  await client2.recordSessionEnd(integration.courseId, studentId, {
167150
168375
  activeTimeSeconds: timingData.durationSeconds,
167151
168376
  activityId: activityData.activityId,
@@ -167161,17 +168386,26 @@ async function endActivity(ctx) {
167161
168386
  gameId,
167162
168387
  courseId: integration.courseId,
167163
168388
  studentId,
168389
+ studentEmail,
167164
168390
  activityId: activityData.activityId,
167165
168391
  score: scorePercentage,
167166
168392
  durationSeconds: timingData.durationSeconds,
167167
168393
  attemptNumber: result.attemptNumber,
167168
168394
  isFirstAttempt: result.attemptNumber === 1,
167169
- xpAwarded: result.xpAwarded
168395
+ xpAwarded: result.xpAwarded,
168396
+ masteredUnits,
168397
+ pctCompleteApp: result.pctCompleteApp,
168398
+ scoreStatus: result.scoreStatus,
168399
+ inProgress: result.inProgress
167170
168400
  });
167171
168401
  return {
167172
168402
  status: "ok",
167173
168403
  courseId: integration.courseId,
167174
- xpAwarded: result.xpAwarded
168404
+ xpAwarded: result.xpAwarded,
168405
+ masteredUnits: result.masteredUnitsApplied,
168406
+ pctCompleteApp: result.pctCompleteApp,
168407
+ scoreStatus: result.scoreStatus,
168408
+ inProgress: result.inProgress
167175
168409
  };
167176
168410
  } catch (error2) {
167177
168411
  logTimebackError2("end activity", error2, {
@@ -167198,68 +168432,160 @@ async function setupTimebackIntegration(ctx) {
167198
168432
  log2.error("Failed to parse request body:", { error: error2 });
167199
168433
  throw ApiError.badRequest("Invalid JSON body");
167200
168434
  }
167201
- const { gameId, config: config2, verbose } = request3;
168435
+ const { gameId, courses, baseConfig, verbose } = request3;
167202
168436
  log2.debug("[API] TimeBack setup request", {
167203
168437
  gameId,
167204
- courseTitle: config2.course.title
168438
+ courseCount: courses.length
167205
168439
  });
168440
+ await verifyGameAccessById(gameId, user);
167206
168441
  const db = getDatabase();
167207
- const game = await db.query.games.findFirst({
167208
- where: eq(games.id, gameId)
167209
- });
167210
- if (!game) {
167211
- throw ApiError.notFound("Game not found");
167212
- }
167213
- if (user.role !== "admin" && game.developerId !== user.id) {
167214
- throw ApiError.forbidden("You do not own this game");
167215
- }
167216
- const existing = await db.query.gameTimebackIntegrations.findFirst({
168442
+ const existing = await db.query.gameTimebackIntegrations.findMany({
167217
168443
  where: eq(gameTimebackIntegrations.gameId, gameId)
167218
168444
  });
167219
168445
  const client2 = await getTimebackClient();
167220
- if (existing) {
167221
- try {
167222
- await client2.update(existing.courseId, config2);
167223
- } catch (error2) {
167224
- logTimebackError2("update", error2, { gameId, courseId: existing.courseId });
167225
- throw error2;
167226
- }
167227
- const [updated] = await db.update(gameTimebackIntegrations).set({
167228
- updatedAt: new Date
167229
- }).where(eq(gameTimebackIntegrations.id, existing.id)).returning();
167230
- log2.info("[API] Updated TimeBack integration", {
167231
- gameId,
167232
- courseId: updated.courseId
167233
- });
167234
- return {
167235
- integration: updated,
167236
- courseId: updated.courseId
168446
+ const integrations = [];
168447
+ const verboseData = [];
168448
+ for (const courseConfig of courses) {
168449
+ const {
168450
+ subject,
168451
+ grade,
168452
+ title,
168453
+ courseCode,
168454
+ level,
168455
+ metadata: metadata2,
168456
+ totalXp: derivedTotalXp,
168457
+ masterableUnits: derivedMasterableUnits
168458
+ } = courseConfig;
168459
+ if (!isTimebackSubject2(subject)) {
168460
+ log2.error("[API] Invalid subject in TimeBack request", { subject });
168461
+ throw ApiError.badRequest(`Invalid subject "${subject}" provided for course "${title}".`);
168462
+ }
168463
+ if (!isTimebackGrade2(grade)) {
168464
+ log2.error("[API] Invalid grade in TimeBack request", { grade });
168465
+ throw ApiError.badRequest(`Invalid grade "${grade}" provided for course "${title}".`);
168466
+ }
168467
+ const courseSubject = subject;
168468
+ const courseGrade = grade;
168469
+ const courseMetadata = isCourseMetadata(metadata2) ? metadata2 : undefined;
168470
+ const totalXp = derivedTotalXp ?? courseMetadata?.metrics?.totalXp;
168471
+ const masterableUnits = derivedMasterableUnits ?? (isPlaycademyResourceMetadata2(courseMetadata?.playcademy) ? courseMetadata?.playcademy?.mastery?.masterableUnits : undefined);
168472
+ if (typeof totalXp !== "number") {
168473
+ log2.error("[API] Missing totalXp in course metadata", { gameId, grade, subject, title });
168474
+ throw ApiError.badRequest(`Course "${title}" (grade ${grade}, subject ${subject}) is missing \`metadata.metrics.totalXp\``);
168475
+ }
168476
+ const suffix = baseConfig.component.titleSuffix || "";
168477
+ const applySuffix = (text5) => suffix ? `${text5} ${suffix}` : text5;
168478
+ const fullConfig = {
168479
+ organization: baseConfig.organization,
168480
+ course: {
168481
+ title,
168482
+ subjects: [courseSubject],
168483
+ grades: [courseGrade],
168484
+ courseCode,
168485
+ level,
168486
+ gradingScheme: "STANDARD",
168487
+ metadata: metadata2
168488
+ },
168489
+ component: {
168490
+ ...baseConfig.component,
168491
+ title: applySuffix(baseConfig.component.title || `${title} Activities`)
168492
+ },
168493
+ resource: {
168494
+ ...baseConfig.resource,
168495
+ title: applySuffix(baseConfig.resource.title || `${title} Game`),
168496
+ metadata: buildResourceMetadata({
168497
+ baseMetadata: baseConfig.resource.metadata,
168498
+ subject: courseSubject,
168499
+ grade: courseGrade,
168500
+ totalXp,
168501
+ masterableUnits
168502
+ })
168503
+ },
168504
+ componentResource: {
168505
+ ...baseConfig.componentResource,
168506
+ title: applySuffix(baseConfig.componentResource.title || "")
168507
+ }
167237
168508
  };
167238
- }
167239
- let result;
167240
- try {
167241
- result = await client2.setup(config2, { verbose });
167242
- } catch (error2) {
167243
- logTimebackError2("setup", error2, { gameId });
167244
- throw error2;
167245
- }
167246
- const integrationData = {
167247
- gameId,
167248
- courseId: result.courseId
167249
- };
167250
- const [integration] = await db.insert(gameTimebackIntegrations).values(integrationData).returning();
167251
- if (!integration) {
167252
- throw ApiError.internal("Failed to create TimeBack integration record");
168509
+ const existingIntegration = existing.find((i4) => i4.grade === grade && i4.subject === subject);
168510
+ if (existingIntegration) {
168511
+ try {
168512
+ await client2.update(existingIntegration.courseId, fullConfig);
168513
+ } catch (error2) {
168514
+ logTimebackError2("update", error2, {
168515
+ gameId,
168516
+ grade,
168517
+ subject,
168518
+ courseId: existingIntegration.courseId
168519
+ });
168520
+ throw error2;
168521
+ }
168522
+ const [updated] = await db.update(gameTimebackIntegrations).set({
168523
+ totalXp,
168524
+ updatedAt: new Date
168525
+ }).where(eq(gameTimebackIntegrations.id, existingIntegration.id)).returning();
168526
+ if (!updated) {
168527
+ log2.error("[API] Failed to update TimeBack integration record", {
168528
+ gameId,
168529
+ grade,
168530
+ subject,
168531
+ integrationId: existingIntegration.id
168532
+ });
168533
+ throw ApiError.internal("Failed to update TimeBack integration record");
168534
+ }
168535
+ integrations.push(updated);
168536
+ log2.info("[API] Updated TimeBack course", {
168537
+ gameId,
168538
+ courseId: updated.courseId,
168539
+ grade,
168540
+ subject
168541
+ });
168542
+ } else {
168543
+ let result;
168544
+ try {
168545
+ result = await client2.setup(fullConfig, { verbose });
168546
+ } catch (error2) {
168547
+ logTimebackError2("setup", error2, { gameId, grade, subject });
168548
+ throw error2;
168549
+ }
168550
+ const integrationData = {
168551
+ gameId,
168552
+ courseId: result.courseId,
168553
+ grade,
168554
+ subject,
168555
+ totalXp
168556
+ };
168557
+ const [integration] = await db.insert(gameTimebackIntegrations).values(integrationData).returning();
168558
+ if (!integration) {
168559
+ log2.error("[API] Failed to create TimeBack integration record", {
168560
+ gameId,
168561
+ grade,
168562
+ subject,
168563
+ courseId: result.courseId
168564
+ });
168565
+ throw ApiError.internal("Failed to create TimeBack integration record");
168566
+ }
168567
+ integrations.push(integration);
168568
+ if (verbose && result.verboseData) {
168569
+ verboseData.push({
168570
+ integration,
168571
+ config: result.verboseData
168572
+ });
168573
+ }
168574
+ log2.info("[API] Created TimeBack course", {
168575
+ gameId,
168576
+ courseId: integration.courseId,
168577
+ grade,
168578
+ subject
168579
+ });
168580
+ }
167253
168581
  }
167254
168582
  log2.info("[API] Created TimeBack integration", {
167255
168583
  gameId,
167256
- courseId: integration.courseId,
167257
- ...result.verboseData && { verbose: result.verboseData }
168584
+ courseCount: integrations.length
167258
168585
  });
167259
168586
  return {
167260
- integration,
167261
- courseId: integration.courseId,
167262
- ...result.verboseData && { verbose: result.verboseData }
168587
+ integrations,
168588
+ ...verbose && verboseData.length > 0 && { verbose: verboseData }
167263
168589
  };
167264
168590
  }
167265
168591
  async function getTimebackIntegration(ctx) {
@@ -167271,21 +168597,13 @@ async function getTimebackIntegration(ctx) {
167271
168597
  if (!gameId) {
167272
168598
  throw ApiError.badRequest("Missing gameId parameter");
167273
168599
  }
167274
- log2.debug("[API] Getting TimeBack integration", { userId: user.id, gameId });
168600
+ log2.debug("[API] Getting TimeBack integrations", { userId: user.id, gameId });
168601
+ await verifyGameAccessById(gameId, user);
167275
168602
  const db = getDatabase();
167276
- const game = await db.query.games.findFirst({
167277
- where: eq(games.id, gameId)
167278
- });
167279
- if (!game) {
167280
- throw ApiError.notFound("Game not found");
167281
- }
167282
- if (user.role !== "admin" && game.developerId !== user.id) {
167283
- throw ApiError.forbidden("You do not own this game");
167284
- }
167285
- const integration = await db.query.gameTimebackIntegrations.findFirst({
168603
+ const integrations = await db.query.gameTimebackIntegrations.findMany({
167286
168604
  where: eq(gameTimebackIntegrations.gameId, gameId)
167287
168605
  });
167288
- return integration || null;
168606
+ return integrations;
167289
168607
  }
167290
168608
  async function verifyTimebackIntegration(ctx) {
167291
168609
  const user = ctx.user;
@@ -167297,39 +168615,40 @@ async function verifyTimebackIntegration(ctx) {
167297
168615
  throw ApiError.badRequest("Missing gameId parameter");
167298
168616
  }
167299
168617
  log2.debug("[API] Verifying TimeBack integration", { userId: user.id, gameId });
168618
+ await verifyGameAccessById(gameId, user);
167300
168619
  const db = getDatabase();
167301
- const game = await db.query.games.findFirst({
167302
- where: eq(games.id, gameId)
167303
- });
167304
- if (!game) {
167305
- throw ApiError.notFound("Game not found");
167306
- }
167307
- if (user.role !== "admin" && game.developerId !== user.id) {
167308
- log2.error("[API] User does not own game", { userId: user.id, gameId });
167309
- throw ApiError.forbidden("You do not own this game");
167310
- }
167311
- const integration = await db.query.gameTimebackIntegrations.findFirst({
168620
+ const integrations = await db.query.gameTimebackIntegrations.findMany({
167312
168621
  where: eq(gameTimebackIntegrations.gameId, gameId)
167313
168622
  });
167314
- if (!integration) {
168623
+ if (integrations.length === 0) {
168624
+ log2.error("[API] No TimeBack integration found", { gameId });
167315
168625
  throw ApiError.notFound("No TimeBack integration found for this game");
167316
168626
  }
167317
168627
  const client2 = await getTimebackClient();
167318
- const resources = await client2.verify(integration.courseId);
167319
- const resourceValues = Object.values(resources);
167320
- const allFound = resourceValues.every((r3) => r3.found);
167321
- const errors3 = Object.entries(resources).filter(([_5, r3]) => !r3.found).map(([name4]) => `${name4} not found`);
167322
- await db.update(gameTimebackIntegrations).set({ lastVerifiedAt: new Date }).where(eq(gameTimebackIntegrations.id, integration.id));
167323
- log2.info("[API] Verified TimeBack integration", {
168628
+ const now2 = new Date;
168629
+ const results = await Promise.all(integrations.map(async (integration) => {
168630
+ const resources = await client2.verify(integration.courseId);
168631
+ const resourceValues = Object.values(resources);
168632
+ const allFound = resourceValues.every((r3) => r3.found);
168633
+ const errors3 = Object.entries(resources).filter(([_5, r3]) => !r3.found).map(([name4]) => `${name4} not found`);
168634
+ const status = allFound ? "success" : "error";
168635
+ return {
168636
+ integration: { ...integration, lastVerifiedAt: now2 },
168637
+ resources,
168638
+ status,
168639
+ ...errors3.length > 0 && { errors: errors3 }
168640
+ };
168641
+ }));
168642
+ await db.update(gameTimebackIntegrations).set({ lastVerifiedAt: now2 }).where(eq(gameTimebackIntegrations.gameId, gameId));
168643
+ const overallSuccess = results.every((r3) => r3.status === "success");
168644
+ log2.info("[API] Verified TimeBack integrations", {
167324
168645
  gameId,
167325
- courseId: integration.courseId,
167326
- status: allFound ? "success" : "error"
168646
+ courseCount: integrations.length,
168647
+ status: overallSuccess ? "success" : "error"
167327
168648
  });
167328
168649
  return {
167329
- status: allFound ? "success" : "error",
167330
- integration: { ...integration, lastVerifiedAt: new Date },
167331
- resources,
167332
- ...errors3.length > 0 && { errors: errors3 }
168650
+ status: overallSuccess ? "success" : "error",
168651
+ results
167333
168652
  };
167334
168653
  }
167335
168654
  async function getTimebackConfig(ctx) {
@@ -167342,20 +168661,13 @@ async function getTimebackConfig(ctx) {
167342
168661
  throw ApiError.badRequest("Missing gameId parameter");
167343
168662
  }
167344
168663
  log2.debug("[API] Getting TimeBack config", { userId: user.id, gameId });
168664
+ await verifyGameAccessById(gameId, user);
167345
168665
  const db = getDatabase();
167346
- const game = await db.query.games.findFirst({
167347
- where: eq(games.id, gameId)
167348
- });
167349
- if (!game) {
167350
- throw ApiError.notFound("Game not found");
167351
- }
167352
- if (user.role !== "admin" && game.developerId !== user.id) {
167353
- throw ApiError.forbidden("You do not own this game");
167354
- }
167355
168666
  const integration = await db.query.gameTimebackIntegrations.findFirst({
167356
168667
  where: eq(gameTimebackIntegrations.gameId, gameId)
167357
168668
  });
167358
168669
  if (!integration) {
168670
+ log2.error("[API] No TimeBack integration found", { gameId });
167359
168671
  throw ApiError.notFound("No TimeBack integration found for this game");
167360
168672
  }
167361
168673
  const client2 = await getTimebackClient();
@@ -167370,34 +168682,40 @@ async function deleteTimebackIntegration(ctx) {
167370
168682
  if (!gameId) {
167371
168683
  throw ApiError.badRequest("Missing gameId parameter");
167372
168684
  }
167373
- log2.debug("[API] Deleting TimeBack integration", { userId: user.id, gameId });
168685
+ log2.debug("[API] Deleting TimeBack integrations", { userId: user.id, gameId });
168686
+ await verifyGameAccessById(gameId, user);
167374
168687
  const db = getDatabase();
167375
- const game = await db.query.games.findFirst({
167376
- where: eq(games.id, gameId)
167377
- });
167378
- if (!game) {
167379
- throw ApiError.notFound("Game not found");
167380
- }
167381
- if (user.role !== "admin" && game.developerId !== user.id) {
167382
- throw ApiError.forbidden("You do not own this game");
167383
- }
167384
- const integration = await db.query.gameTimebackIntegrations.findFirst({
168688
+ const integrations = await db.query.gameTimebackIntegrations.findMany({
167385
168689
  where: eq(gameTimebackIntegrations.gameId, gameId)
167386
168690
  });
167387
- if (!integration) {
168691
+ if (integrations.length === 0) {
168692
+ log2.error("[API] No TimeBack integrations found", { gameId });
167388
168693
  throw ApiError.notFound("No TimeBack integration found for this game");
167389
168694
  }
167390
168695
  const client2 = await getTimebackClient();
167391
- try {
167392
- await client2.cleanup(integration.courseId);
167393
- } catch (error2) {
167394
- logTimebackError2("cleanup", error2, { gameId, courseId: integration.courseId });
167395
- throw error2;
168696
+ for (const integration of integrations) {
168697
+ try {
168698
+ await client2.cleanup(integration.courseId);
168699
+ log2.info("[API] Cleaned up TimeBack course", {
168700
+ gameId,
168701
+ courseId: integration.courseId,
168702
+ grade: integration.grade,
168703
+ subject: integration.subject
168704
+ });
168705
+ } catch (error2) {
168706
+ logTimebackError2("cleanup", error2, {
168707
+ gameId,
168708
+ courseId: integration.courseId,
168709
+ grade: integration.grade,
168710
+ subject: integration.subject
168711
+ });
168712
+ throw error2;
168713
+ }
167396
168714
  }
167397
- await db.delete(gameTimebackIntegrations).where(eq(gameTimebackIntegrations.id, integration.id));
167398
- log2.info("[API] Deleted TimeBack integration", {
168715
+ await db.delete(gameTimebackIntegrations).where(eq(gameTimebackIntegrations.gameId, gameId));
168716
+ log2.info("[API] Deleted TimeBack integrations", {
167399
168717
  gameId,
167400
- courseId: integration.courseId
168718
+ coursesDeleted: integrations.length
167401
168719
  });
167402
168720
  }
167403
168721
  // ../api-core/src/handlers/timeback/xp.ts
@@ -167529,6 +168847,26 @@ async function getTimeBackXpHistory(ctx) {
167529
168847
  throw ApiError.internal("Failed to get TimeBack XP history", error2);
167530
168848
  }
167531
168849
  }
168850
+ // ../api-core/src/handlers/timeback/enrollments.ts
168851
+ init_src();
168852
+ async function getStudentEnrollments(ctx) {
168853
+ const user = ctx.user;
168854
+ if (!user) {
168855
+ throw ApiError.unauthorized("Must be logged in to get enrollments");
168856
+ }
168857
+ const timebackId = ctx.params.timebackId;
168858
+ if (!timebackId) {
168859
+ throw ApiError.badRequest("Missing timebackId parameter");
168860
+ }
168861
+ log2.debug("[API] Getting student enrollments", { userId: user.id, timebackId });
168862
+ const enrollments = await fetchEnrollmentsForUser(timebackId);
168863
+ log2.info("[API] Retrieved student enrollments", {
168864
+ userId: user.id,
168865
+ timebackId,
168866
+ enrollmentCount: enrollments.length
168867
+ });
168868
+ return { enrollments };
168869
+ }
167532
168870
  // src/routes/integrations/timeback.ts
167533
168871
  var timebackRouter = new Hono2;
167534
168872
  timebackRouter.post("/populate-student", async (c3) => {
@@ -167720,6 +169058,25 @@ timebackRouter.post("/end-activity", async (c3) => {
167720
169058
  return c3.json(createUnknownErrorResponse(error2), 500);
167721
169059
  }
167722
169060
  });
169061
+ timebackRouter.get("/enrollments/:timebackId", async (c3) => {
169062
+ const timebackId = c3.req.param("timebackId");
169063
+ const ctx = {
169064
+ user: c3.get("user"),
169065
+ params: { timebackId },
169066
+ url: new URL(c3.req.url),
169067
+ request: c3.req.raw
169068
+ };
169069
+ try {
169070
+ const result = await getStudentEnrollments(ctx);
169071
+ return c3.json(result);
169072
+ } catch (error2) {
169073
+ if (error2 instanceof ApiError) {
169074
+ return c3.json(createErrorResponse(error2), error2.statusCode);
169075
+ }
169076
+ console.error("Error in getStudentEnrollments:", error2);
169077
+ return c3.json(createUnknownErrorResponse(error2), 500);
169078
+ }
169079
+ });
167723
169080
  // ../api-core/src/handlers/lti-timeback/index.ts
167724
169081
  import * as crypto7 from "node:crypto";
167725
169082
  init_src();