@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/cli.js CHANGED
@@ -60743,7 +60743,7 @@ var require_event_target_shim = __commonJS((exports, module2) => {
60743
60743
  var CAPTURE = 1;
60744
60744
  var BUBBLE = 2;
60745
60745
  var ATTRIBUTE = 3;
60746
- function isObject2(x6) {
60746
+ function isObject4(x6) {
60747
60747
  return x6 !== null && typeof x6 === "object";
60748
60748
  }
60749
60749
  function getListeners(eventTarget) {
@@ -60767,7 +60767,7 @@ var require_event_target_shim = __commonJS((exports, module2) => {
60767
60767
  return null;
60768
60768
  },
60769
60769
  set(listener) {
60770
- if (typeof listener !== "function" && !isObject2(listener)) {
60770
+ if (typeof listener !== "function" && !isObject4(listener)) {
60771
60771
  listener = null;
60772
60772
  }
60773
60773
  const listeners = getListeners(this);
@@ -60847,11 +60847,11 @@ var require_event_target_shim = __commonJS((exports, module2) => {
60847
60847
  if (listener == null) {
60848
60848
  return;
60849
60849
  }
60850
- if (typeof listener !== "function" && !isObject2(listener)) {
60850
+ if (typeof listener !== "function" && !isObject4(listener)) {
60851
60851
  throw new TypeError("'listener' should be a function or an object.");
60852
60852
  }
60853
60853
  const listeners = getListeners(this);
60854
- const optionsIsObj = isObject2(options);
60854
+ const optionsIsObj = isObject4(options);
60855
60855
  const capture = optionsIsObj ? Boolean(options.capture) : Boolean(options);
60856
60856
  const listenerType = capture ? CAPTURE : BUBBLE;
60857
60857
  const newNode = {
@@ -60881,7 +60881,7 @@ var require_event_target_shim = __commonJS((exports, module2) => {
60881
60881
  return;
60882
60882
  }
60883
60883
  const listeners = getListeners(this);
60884
- const capture = isObject2(options) ? Boolean(options.capture) : Boolean(options);
60884
+ const capture = isObject4(options) ? Boolean(options.capture) : Boolean(options);
60885
60885
  const listenerType = capture ? CAPTURE : BUBBLE;
60886
60886
  let prev = null;
60887
60887
  let node = listeners.get(eventName);
@@ -66891,7 +66891,7 @@ var init_block_senders = __esm(() => {
66891
66891
 
66892
66892
  // ../../node_modules/cloudflare/resources/email-security/settings/domains.mjs
66893
66893
  var Domains, DomainListResponsesV4PagePaginationArray, DomainBulkDeleteResponsesSinglePage;
66894
- var init_domains = __esm(() => {
66894
+ var init_domains3 = __esm(() => {
66895
66895
  init_pagination();
66896
66896
  Domains = class Domains extends APIResource {
66897
66897
  list(params, options) {
@@ -67001,8 +67001,8 @@ var init_settings4 = __esm(() => {
67001
67001
  init_allow_policies();
67002
67002
  init_block_senders();
67003
67003
  init_block_senders();
67004
- init_domains();
67005
- init_domains();
67004
+ init_domains3();
67005
+ init_domains3();
67006
67006
  init_impersonation_registry();
67007
67007
  init_impersonation_registry();
67008
67008
  init_trusted_domains();
@@ -68292,7 +68292,7 @@ var init_bulks = __esm(() => {
68292
68292
 
68293
68293
  // ../../node_modules/cloudflare/resources/intel/domains/domains.mjs
68294
68294
  var Domains2;
68295
- var init_domains2 = __esm(() => {
68295
+ var init_domains4 = __esm(() => {
68296
68296
  init_bulks();
68297
68297
  init_bulks();
68298
68298
  Domains2 = class Domains2 extends APIResource {
@@ -68427,8 +68427,8 @@ var init_intel = __esm(() => {
68427
68427
  init_asn3();
68428
68428
  init_attack_surface_report();
68429
68429
  init_attack_surface_report();
68430
- init_domains2();
68431
- init_domains2();
68430
+ init_domains4();
68431
+ init_domains4();
68432
68432
  init_indicator_feeds();
68433
68433
  init_indicator_feeds();
68434
68434
  Intel = class Intel extends APIResource {
@@ -71306,7 +71306,7 @@ var init_page_shield = __esm(() => {
71306
71306
 
71307
71307
  // ../../node_modules/cloudflare/resources/pages/projects/domains.mjs
71308
71308
  var Domains3, DomainListResponsesSinglePage;
71309
- var init_domains3 = __esm(() => {
71309
+ var init_domains5 = __esm(() => {
71310
71310
  init_pagination();
71311
71311
  Domains3 = class Domains3 extends APIResource {
71312
71312
  create(projectName, params, options) {
@@ -71409,8 +71409,8 @@ var init_deployments = __esm(() => {
71409
71409
  // ../../node_modules/cloudflare/resources/pages/projects/projects.mjs
71410
71410
  var Projects, DeploymentsSinglePage;
71411
71411
  var init_projects = __esm(() => {
71412
- init_domains3();
71413
- init_domains3();
71412
+ init_domains5();
71413
+ init_domains5();
71414
71414
  init_deployments();
71415
71415
  init_deployments();
71416
71416
  init_pagination();
@@ -71977,7 +71977,7 @@ var init_managed = __esm(() => {
71977
71977
 
71978
71978
  // ../../node_modules/cloudflare/resources/r2/buckets/domains/domains.mjs
71979
71979
  var Domains4;
71980
- var init_domains4 = __esm(() => {
71980
+ var init_domains6 = __esm(() => {
71981
71981
  init_custom5();
71982
71982
  init_custom5();
71983
71983
  init_managed();
@@ -72008,8 +72008,8 @@ var init_buckets = __esm(() => {
72008
72008
  init_metrics();
72009
72009
  init_sippy();
72010
72010
  init_sippy();
72011
- init_domains4();
72012
- init_domains4();
72011
+ init_domains6();
72012
+ init_domains6();
72013
72013
  Buckets = class Buckets extends APIResource {
72014
72014
  constructor() {
72015
72015
  super(...arguments);
@@ -75361,7 +75361,7 @@ var init_rate_limits = __esm(() => {
75361
75361
 
75362
75362
  // ../../node_modules/cloudflare/resources/registrar/domains.mjs
75363
75363
  var Domains5, DomainsSinglePage;
75364
- var init_domains5 = __esm(() => {
75364
+ var init_domains7 = __esm(() => {
75365
75365
  init_pagination();
75366
75366
  Domains5 = class Domains5 extends APIResource {
75367
75367
  update(domainName, params, options) {
@@ -75388,8 +75388,8 @@ var init_domains5 = __esm(() => {
75388
75388
  // ../../node_modules/cloudflare/resources/registrar/registrar.mjs
75389
75389
  var Registrar;
75390
75390
  var init_registrar = __esm(() => {
75391
- init_domains5();
75392
- init_domains5();
75391
+ init_domains7();
75392
+ init_domains7();
75393
75393
  Registrar = class Registrar extends APIResource {
75394
75394
  constructor() {
75395
75395
  super(...arguments);
@@ -78355,7 +78355,7 @@ var init_account_settings = __esm(() => {
78355
78355
 
78356
78356
  // ../../node_modules/cloudflare/resources/workers/domains.mjs
78357
78357
  var Domains6, DomainsSinglePage2;
78358
- var init_domains6 = __esm(() => {
78358
+ var init_domains8 = __esm(() => {
78359
78359
  init_pagination();
78360
78360
  Domains6 = class Domains6 extends APIResource {
78361
78361
  update(params, options) {
@@ -78497,7 +78497,7 @@ var init_versions3 = __esm(() => {
78497
78497
 
78498
78498
  // ../../node_modules/cloudflare/resources/workers/beta/workers/workers.mjs
78499
78499
  var Workers, WorkersV4PagePaginationArray;
78500
- var init_workers = __esm(() => {
78500
+ var init_workers3 = __esm(() => {
78501
78501
  init_versions3();
78502
78502
  init_versions3();
78503
78503
  init_pagination();
@@ -78551,8 +78551,8 @@ var init_workers = __esm(() => {
78551
78551
  // ../../node_modules/cloudflare/resources/workers/beta/beta.mjs
78552
78552
  var Beta;
78553
78553
  var init_beta = __esm(() => {
78554
- init_workers();
78555
- init_workers();
78554
+ init_workers3();
78555
+ init_workers3();
78556
78556
  Beta = class Beta extends APIResource {
78557
78557
  constructor() {
78558
78558
  super(...arguments);
@@ -78944,11 +78944,11 @@ var init_scripts2 = __esm(() => {
78944
78944
 
78945
78945
  // ../../node_modules/cloudflare/resources/workers/workers.mjs
78946
78946
  var Workers2;
78947
- var init_workers2 = __esm(() => {
78947
+ var init_workers4 = __esm(() => {
78948
78948
  init_account_settings();
78949
78949
  init_account_settings();
78950
- init_domains6();
78951
- init_domains6();
78950
+ init_domains8();
78951
+ init_domains8();
78952
78952
  init_routes3();
78953
78953
  init_routes3();
78954
78954
  init_subdomains();
@@ -83973,7 +83973,7 @@ var init_resources3 = __esm(() => {
83973
83973
  init_vectorize();
83974
83974
  init_waiting_rooms();
83975
83975
  init_web3();
83976
- init_workers2();
83976
+ init_workers4();
83977
83977
  init_workers_for_platforms();
83978
83978
  init_workflows();
83979
83979
  init_zaraz();
@@ -84080,7 +84080,7 @@ var init_cloudflare = __esm(() => {
84080
84080
  init_waiting_rooms();
84081
84081
  init_web3();
84082
84082
  init_workers_for_platforms();
84083
- init_workers2();
84083
+ init_workers4();
84084
84084
  init_workflows();
84085
84085
  init_zaraz();
84086
84086
  init_zero_trust();
@@ -84549,7 +84549,7 @@ var require_dist_cjs2 = __commonJS((exports, module2) => {
84549
84549
  Fields: () => Fields3,
84550
84550
  HttpRequest: () => HttpRequest,
84551
84551
  HttpResponse: () => HttpResponse,
84552
- IHttpRequest: () => import_types4.HttpRequest,
84552
+ IHttpRequest: () => import_types6.HttpRequest,
84553
84553
  getHttpHandlerExtensionConfiguration: () => getHttpHandlerExtensionConfiguration,
84554
84554
  isValidHostname: () => isValidHostname,
84555
84555
  resolveHttpHandlerRuntimeConfig: () => resolveHttpHandlerRuntimeConfig
@@ -84576,12 +84576,12 @@ var require_dist_cjs2 = __commonJS((exports, module2) => {
84576
84576
  httpHandler: httpHandlerExtensionConfiguration.httpHandler()
84577
84577
  };
84578
84578
  }, "resolveHttpHandlerRuntimeConfig");
84579
- var import_types4 = require_dist_cjs();
84579
+ var import_types6 = require_dist_cjs();
84580
84580
  var Field = class {
84581
84581
  static {
84582
84582
  __name(this, "Field");
84583
84583
  }
84584
- constructor({ name: name4, kind: kind2 = import_types4.FieldPosition.HEADER, values = [] }) {
84584
+ constructor({ name: name4, kind: kind2 = import_types6.FieldPosition.HEADER, values = [] }) {
84585
84585
  this.name = name4;
84586
84586
  this.kind = kind2;
84587
84587
  this.values = values;
@@ -85450,8 +85450,8 @@ var require_dist_cjs4 = __commonJS((exports, module2) => {
85450
85450
  normalizeProvider: () => normalizeProvider
85451
85451
  });
85452
85452
  module2.exports = __toCommonJS(src_exports);
85453
- var import_types4 = require_dist_cjs();
85454
- var getSmithyContext = /* @__PURE__ */ __name((context) => context[import_types4.SMITHY_CONTEXT_KEY] || (context[import_types4.SMITHY_CONTEXT_KEY] = {}), "getSmithyContext");
85453
+ var import_types6 = require_dist_cjs();
85454
+ var getSmithyContext = /* @__PURE__ */ __name((context) => context[import_types6.SMITHY_CONTEXT_KEY] || (context[import_types6.SMITHY_CONTEXT_KEY] = {}), "getSmithyContext");
85455
85455
  var normalizeProvider = /* @__PURE__ */ __name((input) => {
85456
85456
  if (typeof input === "function")
85457
85457
  return input;
@@ -89531,8 +89531,8 @@ var require_dist_cjs16 = __commonJS((exports, module2) => {
89531
89531
  setFeature: () => setFeature
89532
89532
  });
89533
89533
  module2.exports = __toCommonJS(src_exports);
89534
- var import_types4 = require_dist_cjs();
89535
- var getSmithyContext = /* @__PURE__ */ __name((context) => context[import_types4.SMITHY_CONTEXT_KEY] || (context[import_types4.SMITHY_CONTEXT_KEY] = {}), "getSmithyContext");
89534
+ var import_types6 = require_dist_cjs();
89535
+ var getSmithyContext = /* @__PURE__ */ __name((context) => context[import_types6.SMITHY_CONTEXT_KEY] || (context[import_types6.SMITHY_CONTEXT_KEY] = {}), "getSmithyContext");
89536
89536
  var import_util_middleware = require_dist_cjs4();
89537
89537
  var resolveAuthOptions = /* @__PURE__ */ __name((candidateAuthOptions, authSchemePreference) => {
89538
89538
  if (!authSchemePreference || authSchemePreference.length === 0) {
@@ -89768,9 +89768,9 @@ var require_dist_cjs16 = __commonJS((exports, module2) => {
89768
89768
  throw new Error("request could not be signed with `apiKey` since the `apiKey` is not defined");
89769
89769
  }
89770
89770
  const clonedRequest = import_protocol_http.HttpRequest.clone(httpRequest);
89771
- if (signingProperties.in === import_types4.HttpApiKeyAuthLocation.QUERY) {
89771
+ if (signingProperties.in === import_types6.HttpApiKeyAuthLocation.QUERY) {
89772
89772
  clonedRequest.query[signingProperties.name] = identity.apiKey;
89773
- } else if (signingProperties.in === import_types4.HttpApiKeyAuthLocation.HEADER) {
89773
+ } else if (signingProperties.in === import_types6.HttpApiKeyAuthLocation.HEADER) {
89774
89774
  clonedRequest.headers[signingProperties.name] = signingProperties.scheme ? `${signingProperties.scheme} ${identity.apiKey}` : identity.apiKey;
89775
89775
  } else {
89776
89776
  throw new Error("request can only be signed with `apiKey` locations `query` or `header`, but found: `" + signingProperties.in + "`");
@@ -90853,7 +90853,7 @@ var require_httpAuthSchemes = __commonJS((exports, module2) => {
90853
90853
  },
90854
90854
  default: undefined
90855
90855
  };
90856
- var import_client = require_client();
90856
+ var import_client2 = require_client();
90857
90857
  var import_core210 = require_dist_cjs16();
90858
90858
  var import_signature_v4 = require_dist_cjs20();
90859
90859
  var resolveAwsSdkSigV4Config = /* @__PURE__ */ __name((config2) => {
@@ -90872,7 +90872,7 @@ var require_httpAuthSchemes = __commonJS((exports, module2) => {
90872
90872
  });
90873
90873
  const boundProvider = bindCallerConfig(config2, memoizedProvider);
90874
90874
  if (isUserSupplied && !boundProvider.attributed) {
90875
- resolvedCredentials = /* @__PURE__ */ __name(async (options) => boundProvider(options).then((creds) => (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_CODE", "e")), "resolvedCredentials");
90875
+ resolvedCredentials = /* @__PURE__ */ __name(async (options) => boundProvider(options).then((creds) => (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_CODE", "e")), "resolvedCredentials");
90876
90876
  resolvedCredentials.memoized = boundProvider.memoized;
90877
90877
  resolvedCredentials.configBound = boundProvider.configBound;
90878
90878
  resolvedCredentials.attributed = true;
@@ -91420,7 +91420,7 @@ var require_dist_cjs23 = __commonJS((exports, module2) => {
91420
91420
  }
91421
91421
  };
91422
91422
  var import_protocols = require_protocols();
91423
- var import_types4 = require_dist_cjs();
91423
+ var import_types6 = require_dist_cjs();
91424
91424
  var Command = class {
91425
91425
  constructor() {
91426
91426
  this.middlewareStack = (0, import_middleware_stack.constructStack)();
@@ -91452,7 +91452,7 @@ var require_dist_cjs23 = __commonJS((exports, module2) => {
91452
91452
  commandName,
91453
91453
  inputFilterSensitiveLog,
91454
91454
  outputFilterSensitiveLog,
91455
- [import_types4.SMITHY_CONTEXT_KEY]: {
91455
+ [import_types6.SMITHY_CONTEXT_KEY]: {
91456
91456
  commandInstance: this,
91457
91457
  ...smithyContext
91458
91458
  },
@@ -91677,8 +91677,8 @@ var require_dist_cjs23 = __commonJS((exports, module2) => {
91677
91677
  }, "emitWarningIfUnsupportedVersion");
91678
91678
  var getChecksumConfiguration = /* @__PURE__ */ __name((runtimeConfig) => {
91679
91679
  const checksumAlgorithms = [];
91680
- for (const id in import_types4.AlgorithmId) {
91681
- const algorithmId = import_types4.AlgorithmId[id];
91680
+ for (const id in import_types6.AlgorithmId) {
91681
+ const algorithmId = import_types6.AlgorithmId[id];
91682
91682
  if (runtimeConfig[algorithmId] === undefined) {
91683
91683
  continue;
91684
91684
  }
@@ -93862,7 +93862,7 @@ var require_protocols2 = __commonJS((exports, module2) => {
93862
93862
  return this._read(schema4, data);
93863
93863
  }
93864
93864
  _read(schema4, value) {
93865
- const isObject2 = value !== null && typeof value === "object";
93865
+ const isObject4 = value !== null && typeof value === "object";
93866
93866
  const ns = import_schema2.NormalizedSchema.of(schema4);
93867
93867
  if (ns.isListSchema() && Array.isArray(value)) {
93868
93868
  const listMember = ns.getValueSchema();
@@ -93874,7 +93874,7 @@ var require_protocols2 = __commonJS((exports, module2) => {
93874
93874
  }
93875
93875
  }
93876
93876
  return out2;
93877
- } else if (ns.isMapSchema() && isObject2) {
93877
+ } else if (ns.isMapSchema() && isObject4) {
93878
93878
  const mapMember = ns.getValueSchema();
93879
93879
  const out2 = {};
93880
93880
  const sparse = !!ns.getMergedTraits().sparse;
@@ -93884,7 +93884,7 @@ var require_protocols2 = __commonJS((exports, module2) => {
93884
93884
  }
93885
93885
  }
93886
93886
  return out2;
93887
- } else if (ns.isStructSchema() && isObject2) {
93887
+ } else if (ns.isStructSchema() && isObject4) {
93888
93888
  const out2 = {};
93889
93889
  for (const [memberName, memberSchema] of ns.structIterator()) {
93890
93890
  const fromKey = this.settings.jsonName ? memberSchema.getMergedTraits().jsonName ?? memberName : memberName;
@@ -94016,7 +94016,7 @@ var require_protocols2 = __commonJS((exports, module2) => {
94016
94016
  return this.buffer;
94017
94017
  }
94018
94018
  _write(schema4, value, container) {
94019
- const isObject2 = value !== null && typeof value === "object";
94019
+ const isObject4 = value !== null && typeof value === "object";
94020
94020
  const ns = import_schema22.NormalizedSchema.of(schema4);
94021
94021
  if (ns.isListSchema() && Array.isArray(value)) {
94022
94022
  const listMember = ns.getValueSchema();
@@ -94028,7 +94028,7 @@ var require_protocols2 = __commonJS((exports, module2) => {
94028
94028
  }
94029
94029
  }
94030
94030
  return out2;
94031
- } else if (ns.isMapSchema() && isObject2) {
94031
+ } else if (ns.isMapSchema() && isObject4) {
94032
94032
  const mapMember = ns.getValueSchema();
94033
94033
  const out2 = {};
94034
94034
  const sparse = !!ns.getMergedTraits().sparse;
@@ -94038,7 +94038,7 @@ var require_protocols2 = __commonJS((exports, module2) => {
94038
94038
  }
94039
94039
  }
94040
94040
  return out2;
94041
- } else if (ns.isStructSchema() && isObject2) {
94041
+ } else if (ns.isStructSchema() && isObject4) {
94042
94042
  const out2 = {};
94043
94043
  for (const [memberName, memberSchema] of ns.structIterator()) {
94044
94044
  const targetKey = this.settings.jsonName ? memberSchema.getMergedTraits().jsonName ?? memberName : memberName;
@@ -101588,16 +101588,16 @@ var require_dist_cjs52 = __commonJS((exports, module2) => {
101588
101588
  var getProfileName = /* @__PURE__ */ __name((init3) => init3.profile || process.env[ENV_PROFILE] || DEFAULT_PROFILE, "getProfileName");
101589
101589
  __reExport(src_exports, require_getSSOTokenFilepath(), module2.exports);
101590
101590
  __reExport(src_exports, require_getSSOTokenFromFile(), module2.exports);
101591
- var import_types4 = require_dist_cjs();
101591
+ var import_types6 = require_dist_cjs();
101592
101592
  var getConfigData = /* @__PURE__ */ __name((data) => Object.entries(data).filter(([key]) => {
101593
101593
  const indexOfSeparator = key.indexOf(CONFIG_PREFIX_SEPARATOR);
101594
101594
  if (indexOfSeparator === -1) {
101595
101595
  return false;
101596
101596
  }
101597
- return Object.values(import_types4.IniSectionType).includes(key.substring(0, indexOfSeparator));
101597
+ return Object.values(import_types6.IniSectionType).includes(key.substring(0, indexOfSeparator));
101598
101598
  }).reduce((acc, [key, value]) => {
101599
101599
  const indexOfSeparator = key.indexOf(CONFIG_PREFIX_SEPARATOR);
101600
- const updatedKey = key.substring(0, indexOfSeparator) === import_types4.IniSectionType.PROFILE ? key.substring(indexOfSeparator + 1) : key;
101600
+ const updatedKey = key.substring(0, indexOfSeparator) === import_types6.IniSectionType.PROFILE ? key.substring(indexOfSeparator + 1) : key;
101601
101601
  acc[updatedKey] = value;
101602
101602
  return acc;
101603
101603
  }, {
@@ -101627,7 +101627,7 @@ var require_dist_cjs52 = __commonJS((exports, module2) => {
101627
101627
  const matches = prefixKeyRegex.exec(sectionName);
101628
101628
  if (matches) {
101629
101629
  const [, prefix2, , name4] = matches;
101630
- if (Object.values(import_types4.IniSectionType).includes(prefix2)) {
101630
+ if (Object.values(import_types6.IniSectionType).includes(prefix2)) {
101631
101631
  currentSection = [prefix2, name4].join(CONFIG_PREFIX_SEPARATOR);
101632
101632
  }
101633
101633
  } else {
@@ -101686,7 +101686,7 @@ var require_dist_cjs52 = __commonJS((exports, module2) => {
101686
101686
  credentialsFile: parsedFiles[1]
101687
101687
  };
101688
101688
  }, "loadSharedConfigFiles");
101689
- 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");
101689
+ 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");
101690
101690
  var import_slurpFile2 = require_slurpFile();
101691
101691
  var swallowError2 = /* @__PURE__ */ __name(() => ({}), "swallowError");
101692
101692
  var loadSsoSessionData = /* @__PURE__ */ __name(async (init3 = {}) => (0, import_slurpFile2.slurpFile)(init3.configFilepath ?? getConfigFilepath()).then(parseIni).then(getSsoSessionData).catch(swallowError2), "loadSsoSessionData");
@@ -102685,7 +102685,7 @@ var require_dist_cjs57 = __commonJS((exports, module2) => {
102685
102685
  fromEnv: () => fromEnv
102686
102686
  });
102687
102687
  module2.exports = __toCommonJS(index_exports);
102688
- var import_client = require_client();
102688
+ var import_client2 = require_client();
102689
102689
  var import_property_provider = require_dist_cjs17();
102690
102690
  var ENV_KEY = "AWS_ACCESS_KEY_ID";
102691
102691
  var ENV_SECRET = "AWS_SECRET_ACCESS_KEY";
@@ -102710,7 +102710,7 @@ var require_dist_cjs57 = __commonJS((exports, module2) => {
102710
102710
  ...credentialScope && { credentialScope },
102711
102711
  ...accountId && { accountId }
102712
102712
  };
102713
- (0, import_client.setCredentialFeature)(credentials2, "CREDENTIALS_ENV_VARS", "g");
102713
+ (0, import_client2.setCredentialFeature)(credentials2, "CREDENTIALS_ENV_VARS", "g");
102714
102714
  return credentials2;
102715
102715
  }
102716
102716
  throw new import_property_provider.CredentialsProviderError("Unable to find environment variable credentials.", { logger: init3?.logger });
@@ -105631,7 +105631,7 @@ var require_dist_cjs67 = __commonJS((exports, module2) => {
105631
105631
  var __getOwnPropNames3 = Object.getOwnPropertyNames;
105632
105632
  var __hasOwnProp3 = Object.prototype.hasOwnProperty;
105633
105633
  var __name = (target, value) => __defProp4(target, "name", { value, configurable: true });
105634
- var __esm3 = (fn2, res) => function __init() {
105634
+ var __esm5 = (fn2, res) => function __init() {
105635
105635
  return fn2 && (res = (0, fn2[__getOwnPropNames3(fn2)[0]])(fn2 = 0)), res;
105636
105636
  };
105637
105637
  var __export4 = (target, all) => {
@@ -105653,7 +105653,7 @@ var require_dist_cjs67 = __commonJS((exports, module2) => {
105653
105653
  SSOClient: () => import_client_sso.SSOClient
105654
105654
  });
105655
105655
  var import_client_sso;
105656
- var init_loadSso = __esm3({
105656
+ var init_loadSso = __esm5({
105657
105657
  "src/loadSso.ts"() {
105658
105658
  import_client_sso = require_dist_cjs65();
105659
105659
  }
@@ -105666,7 +105666,7 @@ var require_dist_cjs67 = __commonJS((exports, module2) => {
105666
105666
  });
105667
105667
  module2.exports = __toCommonJS(index_exports);
105668
105668
  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");
105669
- var import_client = require_client();
105669
+ var import_client2 = require_client();
105670
105670
  var import_token_providers = require_dist_cjs66();
105671
105671
  var import_property_provider = require_dist_cjs17();
105672
105672
  var import_shared_ini_file_loader = require_dist_cjs52();
@@ -105751,9 +105751,9 @@ var require_dist_cjs67 = __commonJS((exports, module2) => {
105751
105751
  ...accountId && { accountId }
105752
105752
  };
105753
105753
  if (ssoSession) {
105754
- (0, import_client.setCredentialFeature)(credentials2, "CREDENTIALS_SSO", "s");
105754
+ (0, import_client2.setCredentialFeature)(credentials2, "CREDENTIALS_SSO", "s");
105755
105755
  } else {
105756
- (0, import_client.setCredentialFeature)(credentials2, "CREDENTIALS_SSO_LEGACY", "u");
105756
+ (0, import_client2.setCredentialFeature)(credentials2, "CREDENTIALS_SSO_LEGACY", "u");
105757
105757
  }
105758
105758
  return credentials2;
105759
105759
  }, "resolveSSOCredentials");
@@ -106953,7 +106953,7 @@ var require_sts = __commonJS((exports, module2) => {
106953
106953
  };
106954
106954
  (0, import_smithy_client6.createAggregatedClient)(commands, STS);
106955
106955
  var import_EndpointParameters3 = require_EndpointParameters();
106956
- var import_client = require_client();
106956
+ var import_client2 = require_client();
106957
106957
  var ASSUME_ROLE_DEFAULT_REGION = "us-east-1";
106958
106958
  var getAccountIdFromAssumedRoleUser = /* @__PURE__ */ __name((assumedRoleUser) => {
106959
106959
  if (typeof assumedRoleUser?.Arn === "string") {
@@ -107005,7 +107005,7 @@ var require_sts = __commonJS((exports, module2) => {
107005
107005
  ...Credentials2.CredentialScope && { credentialScope: Credentials2.CredentialScope },
107006
107006
  ...accountId && { accountId }
107007
107007
  };
107008
- (0, import_client.setCredentialFeature)(credentials2, "CREDENTIALS_STS_ASSUME_ROLE", "i");
107008
+ (0, import_client2.setCredentialFeature)(credentials2, "CREDENTIALS_STS_ASSUME_ROLE", "i");
107009
107009
  return credentials2;
107010
107010
  };
107011
107011
  }, "getDefaultRoleAssumer");
@@ -107042,9 +107042,9 @@ var require_sts = __commonJS((exports, module2) => {
107042
107042
  ...accountId && { accountId }
107043
107043
  };
107044
107044
  if (accountId) {
107045
- (0, import_client.setCredentialFeature)(credentials2, "RESOLVED_ACCOUNT_ID", "T");
107045
+ (0, import_client2.setCredentialFeature)(credentials2, "RESOLVED_ACCOUNT_ID", "T");
107046
107046
  }
107047
- (0, import_client.setCredentialFeature)(credentials2, "CREDENTIALS_STS_ASSUME_ROLE_WEB_ID", "k");
107047
+ (0, import_client2.setCredentialFeature)(credentials2, "CREDENTIALS_STS_ASSUME_ROLE_WEB_ID", "k");
107048
107048
  return credentials2;
107049
107049
  };
107050
107050
  }, "getDefaultRoleAssumerWithWebIdentity");
@@ -107106,7 +107106,7 @@ var require_dist_cjs68 = __commonJS((exports, module2) => {
107106
107106
  var import_property_provider = require_dist_cjs17();
107107
107107
  var import_child_process = __require("child_process");
107108
107108
  var import_util3 = __require("util");
107109
- var import_client = require_client();
107109
+ var import_client2 = require_client();
107110
107110
  var getValidatedProcessCredentials = /* @__PURE__ */ __name((profileName, data, profiles) => {
107111
107111
  if (data.Version !== 1) {
107112
107112
  throw Error(`Profile ${profileName} credential_process did not return Version 1.`);
@@ -107133,7 +107133,7 @@ var require_dist_cjs68 = __commonJS((exports, module2) => {
107133
107133
  ...data.CredentialScope && { credentialScope: data.CredentialScope },
107134
107134
  ...accountId && { accountId }
107135
107135
  };
107136
- (0, import_client.setCredentialFeature)(credentials2, "CREDENTIALS_PROCESS", "w");
107136
+ (0, import_client2.setCredentialFeature)(credentials2, "CREDENTIALS_PROCESS", "w");
107137
107137
  return credentials2;
107138
107138
  }, "getValidatedProcessCredentials");
107139
107139
  var resolveProcessCredentials = /* @__PURE__ */ __name(async (profileName, profiles, logger3) => {
@@ -107334,7 +107334,7 @@ var require_dist_cjs70 = __commonJS((exports, module2) => {
107334
107334
  });
107335
107335
  module2.exports = __toCommonJS(index_exports);
107336
107336
  var import_shared_ini_file_loader = require_dist_cjs52();
107337
- var import_client = require_client();
107337
+ var import_client2 = require_client();
107338
107338
  var import_property_provider = require_dist_cjs17();
107339
107339
  var resolveCredentialSource = /* @__PURE__ */ __name((credentialSource, profileName, logger3) => {
107340
107340
  const sourceProvidersMap = {
@@ -107361,7 +107361,7 @@ var require_dist_cjs70 = __commonJS((exports, module2) => {
107361
107361
  throw new import_property_provider.CredentialsProviderError(`Unsupported credential source in profile ${profileName}. Got ${credentialSource}, expected EcsContainer or Ec2InstanceMetadata or Environment.`, { logger: logger3 });
107362
107362
  }
107363
107363
  }, "resolveCredentialSource");
107364
- var setNamedProvider = /* @__PURE__ */ __name((creds) => (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_NAMED_PROVIDER", "p"), "setNamedProvider");
107364
+ var setNamedProvider = /* @__PURE__ */ __name((creds) => (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_NAMED_PROVIDER", "p"), "setNamedProvider");
107365
107365
  var isAssumeRoleProfile = /* @__PURE__ */ __name((arg, { profile = "default", logger: logger3 } = {}) => {
107366
107366
  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 }));
107367
107367
  }, "isAssumeRoleProfile");
@@ -107403,7 +107403,7 @@ var require_dist_cjs70 = __commonJS((exports, module2) => {
107403
107403
  [source_profile]: true
107404
107404
  }, isCredentialSourceWithoutRoleArn(profiles[source_profile] ?? {})) : (await resolveCredentialSource(profileData.credential_source, profileName, options.logger)(options))();
107405
107405
  if (isCredentialSourceWithoutRoleArn(profileData)) {
107406
- return sourceCredsProvider.then((creds) => (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SOURCE_PROFILE", "o"));
107406
+ return sourceCredsProvider.then((creds) => (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SOURCE_PROFILE", "o"));
107407
107407
  } else {
107408
107408
  const params = {
107409
107409
  RoleArn: profileData.role_arn,
@@ -107420,7 +107420,7 @@ var require_dist_cjs70 = __commonJS((exports, module2) => {
107420
107420
  params.TokenCode = await options.mfaCodeProvider(mfa_serial);
107421
107421
  }
107422
107422
  const sourceCreds = await sourceCredsProvider;
107423
- return options.roleAssumer(sourceCreds, params).then((creds) => (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SOURCE_PROFILE", "o"));
107423
+ return options.roleAssumer(sourceCreds, params).then((creds) => (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SOURCE_PROFILE", "o"));
107424
107424
  }
107425
107425
  }, "resolveAssumeRoleCredentials");
107426
107426
  var isCredentialSourceWithoutRoleArn = /* @__PURE__ */ __name((section) => {
@@ -107430,7 +107430,7 @@ var require_dist_cjs70 = __commonJS((exports, module2) => {
107430
107430
  var resolveProcessCredentials = /* @__PURE__ */ __name(async (options, profile) => Promise.resolve().then(() => __toESM3(require_dist_cjs68())).then(({ fromProcess }) => fromProcess({
107431
107431
  ...options,
107432
107432
  profile
107433
- })().then((creds) => (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_PROCESS", "v"))), "resolveProcessCredentials");
107433
+ })().then((creds) => (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_PROCESS", "v"))), "resolveProcessCredentials");
107434
107434
  var resolveSsoCredentials = /* @__PURE__ */ __name(async (profile, profileData, options = {}) => {
107435
107435
  const { fromSSO } = await Promise.resolve().then(() => __toESM3(require_dist_cjs67()));
107436
107436
  return fromSSO({
@@ -107440,9 +107440,9 @@ var require_dist_cjs70 = __commonJS((exports, module2) => {
107440
107440
  clientConfig: options.clientConfig
107441
107441
  })().then((creds) => {
107442
107442
  if (profileData.sso_session) {
107443
- return (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SSO", "r");
107443
+ return (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SSO", "r");
107444
107444
  } else {
107445
- return (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SSO_LEGACY", "t");
107445
+ return (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SSO_LEGACY", "t");
107446
107446
  }
107447
107447
  });
107448
107448
  }, "resolveSsoCredentials");
@@ -107457,7 +107457,7 @@ var require_dist_cjs70 = __commonJS((exports, module2) => {
107457
107457
  ...profile.aws_credential_scope && { credentialScope: profile.aws_credential_scope },
107458
107458
  ...profile.aws_account_id && { accountId: profile.aws_account_id }
107459
107459
  };
107460
- return (0, import_client.setCredentialFeature)(credentials2, "CREDENTIALS_PROFILE", "n");
107460
+ return (0, import_client2.setCredentialFeature)(credentials2, "CREDENTIALS_PROFILE", "n");
107461
107461
  }, "resolveStaticCredentials");
107462
107462
  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");
107463
107463
  var resolveWebIdentityCredentials = /* @__PURE__ */ __name(async (profile, options) => Promise.resolve().then(() => __toESM3(require_dist_cjs69())).then(({ fromTokenFile: fromTokenFile2 }) => fromTokenFile2({
@@ -107467,7 +107467,7 @@ var require_dist_cjs70 = __commonJS((exports, module2) => {
107467
107467
  roleAssumerWithWebIdentity: options.roleAssumerWithWebIdentity,
107468
107468
  logger: options.logger,
107469
107469
  parentClientConfig: options.parentClientConfig
107470
- })().then((creds) => (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_STS_WEB_ID_TOKEN", "q"))), "resolveWebIdentityCredentials");
107470
+ })().then((creds) => (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_STS_WEB_ID_TOKEN", "q"))), "resolveWebIdentityCredentials");
107471
107471
  var resolveProfileData = /* @__PURE__ */ __name(async (profileName, profiles, options, visitedProfiles = {}, isAssumeRoleRecursiveCall = false) => {
107472
107472
  const data = profiles[profileName];
107473
107473
  if (Object.keys(visitedProfiles).length > 0 && isStaticCredsProfile(data)) {
@@ -126309,7 +126309,7 @@ var require_lodash2 = __commonJS((exports, module2) => {
126309
126309
  }
126310
126310
  }
126311
126311
  function baseKeysIn(object) {
126312
- if (!isObject2(object)) {
126312
+ if (!isObject4(object)) {
126313
126313
  return nativeKeysIn(object);
126314
126314
  }
126315
126315
  var isProto = isPrototype(object), result = [];
@@ -126369,7 +126369,7 @@ var require_lodash2 = __commonJS((exports, module2) => {
126369
126369
  return !!length && (typeof value == "number" || reIsUint.test(value)) && (value > -1 && value % 1 == 0 && value < length);
126370
126370
  }
126371
126371
  function isIterateeCall(value, index6, object) {
126372
- if (!isObject2(object)) {
126372
+ if (!isObject4(object)) {
126373
126373
  return false;
126374
126374
  }
126375
126375
  var type = typeof index6;
@@ -126405,13 +126405,13 @@ var require_lodash2 = __commonJS((exports, module2) => {
126405
126405
  return isObjectLike2(value) && isArrayLike(value);
126406
126406
  }
126407
126407
  function isFunction3(value) {
126408
- var tag2 = isObject2(value) ? objectToString.call(value) : "";
126408
+ var tag2 = isObject4(value) ? objectToString.call(value) : "";
126409
126409
  return tag2 == funcTag || tag2 == genTag;
126410
126410
  }
126411
126411
  function isLength(value) {
126412
126412
  return typeof value == "number" && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
126413
126413
  }
126414
- function isObject2(value) {
126414
+ function isObject4(value) {
126415
126415
  var type = typeof value;
126416
126416
  return !!value && (type == "object" || type == "function");
126417
126417
  }
@@ -126451,13 +126451,13 @@ var require_lodash3 = __commonJS((exports, module2) => {
126451
126451
  return isObjectLike2(value) && isArrayLike(value);
126452
126452
  }
126453
126453
  function isFunction3(value) {
126454
- var tag2 = isObject2(value) ? objectToString.call(value) : "";
126454
+ var tag2 = isObject4(value) ? objectToString.call(value) : "";
126455
126455
  return tag2 == funcTag || tag2 == genTag;
126456
126456
  }
126457
126457
  function isLength(value) {
126458
126458
  return typeof value == "number" && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
126459
126459
  }
126460
- function isObject2(value) {
126460
+ function isObject4(value) {
126461
126461
  var type = typeof value;
126462
126462
  return !!value && (type == "object" || type == "function");
126463
126463
  }
@@ -131608,7 +131608,7 @@ var require_built3 = __commonJS((exports, module2) => {
131608
131608
 
131609
131609
  // ../realtime/src/server/domain/constants.ts
131610
131610
  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;
131611
- var init_constants = __esm(() => {
131611
+ var init_constants3 = __esm(() => {
131612
131612
  HUB_PREFIX = process.env.HUB_PREFIX ?? "hub:";
131613
131613
  MAP_PREFIX = process.env.MAP_PREFIX ?? "map_";
131614
131614
  PRESENCE_CHANNEL_NAME = process.env.PRESENCE_CHANNEL_NAME ?? "presence";
@@ -131757,7 +131757,7 @@ function isRedisConnected() {
131757
131757
  var import_ioredis, client = null;
131758
131758
  var init_client = __esm(() => {
131759
131759
  init_src();
131760
- init_constants();
131760
+ init_constants3();
131761
131761
  import_ioredis = __toESM(require_built3(), 1);
131762
131762
  });
131763
131763
 
@@ -131870,7 +131870,7 @@ async function updateUserNameplateBg(userId, color) {
131870
131870
  }
131871
131871
  var init_presence = __esm(() => {
131872
131872
  init_src();
131873
- init_constants();
131873
+ init_constants3();
131874
131874
  init_client();
131875
131875
  });
131876
131876
 
@@ -131921,7 +131921,7 @@ function extractTokenFromUrl(url) {
131921
131921
  return null;
131922
131922
  }
131923
131923
  }
131924
- var init_auth = __esm(() => {
131924
+ var init_auth3 = __esm(() => {
131925
131925
  init_esm2();
131926
131926
  init_src();
131927
131927
  });
@@ -132057,7 +132057,7 @@ var init_sandbox = __esm(() => {
132057
132057
  init_wrapper();
132058
132058
  init_src();
132059
132059
  init_infrastructure2();
132060
- init_auth();
132060
+ init_auth3();
132061
132061
  init_config4();
132062
132062
  });
132063
132063
  // ../realtime/src/server/application/services/presence.ts
@@ -132144,7 +132144,7 @@ var init_presence2 = __esm(() => {
132144
132144
  var playerPositions, PlayerPositionService;
132145
132145
  var init_player_position = __esm(() => {
132146
132146
  init_src();
132147
- init_constants();
132147
+ init_constants3();
132148
132148
  init_infrastructure2();
132149
132149
  playerPositions = new Map;
132150
132150
  PlayerPositionService = {
@@ -132276,7 +132276,7 @@ function computeInitialChannelKey(gameId, requested) {
132276
132276
  return `${HUB_PREFIX}${channel}`;
132277
132277
  }
132278
132278
  var init_channel_keys = __esm(() => {
132279
- init_constants();
132279
+ init_constants3();
132280
132280
  });
132281
132281
 
132282
132282
  // ../realtime/src/server/shared/utils/websocket.ts
@@ -132297,7 +132297,7 @@ function checkMatchingPlayerId(ws, payload) {
132297
132297
  }
132298
132298
  var init_websocket = __esm(() => {
132299
132299
  init_src();
132300
- init_constants();
132300
+ init_constants3();
132301
132301
  });
132302
132302
 
132303
132303
  // ../realtime/src/server/shared/utils/route.ts
@@ -132652,7 +132652,7 @@ async function handleWebSocketUpgrade(req, server, config2) {
132652
132652
  }
132653
132653
  var init_websocket2 = __esm(() => {
132654
132654
  init_src();
132655
- init_auth();
132655
+ init_auth3();
132656
132656
  init_response();
132657
132657
  });
132658
132658
 
@@ -132839,7 +132839,7 @@ var init_open = __esm(() => {
132839
132839
  init_src();
132840
132840
  init_presence4();
132841
132841
  init_services2();
132842
- init_constants();
132842
+ init_constants3();
132843
132843
  init_events();
132844
132844
  init_infrastructure2();
132845
132845
  init_utils13();
@@ -135157,6 +135157,7 @@ var config = {
135157
135157
  };
135158
135158
  process.env.BETTER_AUTH_SECRET = config.auth.betterAuthSecret;
135159
135159
  process.env.GAME_JWT_SECRET = config.auth.gameJwtSecret;
135160
+ process.env.PUBLIC_IS_LOCAL = "true";
135160
135161
 
135161
135162
  // ../../node_modules/@hono/node-server/dist/index.mjs
135162
135163
  import { createServer as createServerHTTP } from "http";
@@ -135702,7 +135703,7 @@ var serve = (options, listeningListener) => {
135702
135703
  // package.json
135703
135704
  var package_default = {
135704
135705
  name: "@playcademy/sandbox",
135705
- version: "0.1.9",
135706
+ version: "0.1.11",
135706
135707
  description: "Local development server for Playcademy game development",
135707
135708
  type: "module",
135708
135709
  exports: {
@@ -142782,12 +142783,17 @@ var timebackXpEvents = pgTable("timeback_xp_event", {
142782
142783
  }, (table) => [uniqueIndex("timeback_xp_events_source_id_idx").on(table.source, table.sourceId)]);
142783
142784
  var gameTimebackIntegrations = pgTable("game_timeback_integrations", {
142784
142785
  id: uuid("id").primaryKey().defaultRandom(),
142785
- gameId: uuid("game_id").notNull().unique().references(() => games.id, { onDelete: "cascade" }),
142786
+ gameId: uuid("game_id").notNull().references(() => games.id, { onDelete: "cascade" }),
142786
142787
  courseId: text("course_id").notNull(),
142788
+ grade: integer("grade").notNull(),
142789
+ subject: text("subject").notNull(),
142790
+ totalXp: integer("total_xp"),
142787
142791
  lastVerifiedAt: timestamp("last_verified_at", { withTimezone: true }),
142788
142792
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
142789
142793
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
142790
- });
142794
+ }, (table) => [
142795
+ uniqueIndex("game_timeback_integrations_game_grade_subject_idx").on(table.gameId, table.grade, table.subject)
142796
+ ]);
142791
142797
  // ../data/src/domains/achievement/table.ts
142792
142798
  var achievementScopeEnum = pgEnum("achievement_scope", [
142793
142799
  "daily",
@@ -149370,6 +149376,292 @@ var __export3 = (target, all) => {
149370
149376
  set: (newValue) => all[name3] = () => newValue
149371
149377
  });
149372
149378
  };
149379
+ var __esm3 = (fn2, res) => () => (fn2 && (res = fn2(fn2 = 0)), res);
149380
+ var init_auth = () => {};
149381
+ var PLAYCADEMY_BASE_URLS;
149382
+ var init_domains = __esm3(() => {
149383
+ PLAYCADEMY_BASE_URLS = {
149384
+ production: "https://hub.playcademy.net",
149385
+ staging: "https://hub.dev.playcademy.net"
149386
+ };
149387
+ });
149388
+ var init_env_vars = () => {};
149389
+ var ITEM_SLUGS2;
149390
+ var CURRENCIES2;
149391
+ var BADGES2;
149392
+ var init_overworld = __esm3(() => {
149393
+ ITEM_SLUGS2 = {
149394
+ PLAYCADEMY_CREDITS: "PLAYCADEMY_CREDITS",
149395
+ PLAYCADEMY_XP: "PLAYCADEMY_XP",
149396
+ FOUNDING_MEMBER_BADGE: "FOUNDING_MEMBER_BADGE",
149397
+ EARLY_ADOPTER_BADGE: "EARLY_ADOPTER_BADGE",
149398
+ FIRST_GAME_BADGE: "FIRST_GAME_BADGE",
149399
+ COMMON_SWORD: "COMMON_SWORD",
149400
+ SMALL_HEALTH_POTION: "SMALL_HEALTH_POTION",
149401
+ SMALL_BACKPACK: "SMALL_BACKPACK",
149402
+ LAVA_LAMP: "LAVA_LAMP",
149403
+ BOOMBOX: "BOOMBOX",
149404
+ CABIN_BED: "CABIN_BED"
149405
+ };
149406
+ CURRENCIES2 = {
149407
+ PRIMARY: ITEM_SLUGS2.PLAYCADEMY_CREDITS,
149408
+ XP: ITEM_SLUGS2.PLAYCADEMY_XP
149409
+ };
149410
+ BADGES2 = {
149411
+ FOUNDING_MEMBER: ITEM_SLUGS2.FOUNDING_MEMBER_BADGE,
149412
+ EARLY_ADOPTER: ITEM_SLUGS2.EARLY_ADOPTER_BADGE,
149413
+ FIRST_GAME: ITEM_SLUGS2.FIRST_GAME_BADGE
149414
+ };
149415
+ });
149416
+ var init_timeback = () => {};
149417
+ var init_workers = () => {};
149418
+ var init_src2 = __esm3(() => {
149419
+ init_auth();
149420
+ init_domains();
149421
+ init_env_vars();
149422
+ init_overworld();
149423
+ init_timeback();
149424
+ init_workers();
149425
+ });
149426
+ var TIMEBACK_API_URLS;
149427
+ var TIMEBACK_AUTH_URLS;
149428
+ var CALIPER_API_URLS;
149429
+ var ONEROSTER_ENDPOINTS;
149430
+ var CALIPER_ENDPOINTS;
149431
+ var createOneRosterUrls = (baseUrl) => {
149432
+ const effective = baseUrl || TIMEBACK_API_URLS.production;
149433
+ const base = effective.replace(/\/$/, "");
149434
+ return {
149435
+ user: (userId) => `${base}${ONEROSTER_ENDPOINTS.users}/${userId}`,
149436
+ course: (courseId) => `${base}${ONEROSTER_ENDPOINTS.courses}/${courseId}`,
149437
+ componentResource: (resourceId) => `${base}${ONEROSTER_ENDPOINTS.componentResources}/${resourceId}`
149438
+ };
149439
+ };
149440
+ var CALIPER_CONSTANTS;
149441
+ var TIMEBACK_EVENT_TYPES;
149442
+ var TIMEBACK_ACTIONS;
149443
+ var TIMEBACK_TYPES;
149444
+ var ACTIVITY_METRIC_TYPES;
149445
+ var TIME_METRIC_TYPES;
149446
+ var TIMEBACK_SUBJECTS;
149447
+ var TIMEBACK_GRADE_LEVELS;
149448
+ var TIMEBACK_GRADE_LEVEL_LABELS;
149449
+ var CALIPER_SUBJECTS;
149450
+ var ONEROSTER_STATUS;
149451
+ var SCORE_STATUS;
149452
+ var ENV_VARS;
149453
+ var HTTP_DEFAULTS;
149454
+ var AUTH_DEFAULTS;
149455
+ var CACHE_DEFAULTS;
149456
+ var CONFIG_DEFAULTS;
149457
+ var DEFAULT_PLAYCADEMY_ORGANIZATION_ID;
149458
+ var PLAYCADEMY_DEFAULTS;
149459
+ var RESOURCE_DEFAULTS;
149460
+ var HTTP_STATUS;
149461
+ var ERROR_NAMES;
149462
+ var init_constants = __esm3(() => {
149463
+ init_src2();
149464
+ TIMEBACK_API_URLS = {
149465
+ production: "https://api.alpha-1edtech.ai",
149466
+ staging: "https://api.staging.alpha-1edtech.com"
149467
+ };
149468
+ TIMEBACK_AUTH_URLS = {
149469
+ production: "https://prod-beyond-timeback-api-2-idp.auth.us-east-1.amazoncognito.com",
149470
+ staging: "https://alpha-auth-development-idp.auth.us-west-2.amazoncognito.com"
149471
+ };
149472
+ CALIPER_API_URLS = {
149473
+ production: "https://caliper.alpha-1edtech.ai",
149474
+ staging: "https://caliper-staging.alpha-1edtech.com"
149475
+ };
149476
+ ONEROSTER_ENDPOINTS = {
149477
+ organizations: "/ims/oneroster/rostering/v1p2/orgs",
149478
+ courses: "/ims/oneroster/rostering/v1p2/courses",
149479
+ courseComponents: "/ims/oneroster/rostering/v1p2/courses/components",
149480
+ resources: "/ims/oneroster/resources/v1p2/resources",
149481
+ componentResources: "/ims/oneroster/rostering/v1p2/courses/component-resources",
149482
+ classes: "/ims/oneroster/rostering/v1p2/classes",
149483
+ enrollments: "/ims/oneroster/rostering/v1p2/enrollments",
149484
+ assessmentLineItems: "/ims/oneroster/gradebook/v1p2/assessmentLineItems",
149485
+ assessmentResults: "/ims/oneroster/gradebook/v1p2/assessmentResults",
149486
+ users: "/ims/oneroster/rostering/v1p2/users"
149487
+ };
149488
+ CALIPER_ENDPOINTS = {
149489
+ events: "/caliper/event",
149490
+ validate: "/caliper/event/validate"
149491
+ };
149492
+ CALIPER_CONSTANTS = {
149493
+ context: "http://purl.imsglobal.org/ctx/caliper/v1p2",
149494
+ profile: "TimebackProfile",
149495
+ dataVersion: "http://purl.imsglobal.org/ctx/caliper/v1p2"
149496
+ };
149497
+ TIMEBACK_EVENT_TYPES = {
149498
+ activityEvent: "ActivityEvent",
149499
+ timeSpentEvent: "TimeSpentEvent"
149500
+ };
149501
+ TIMEBACK_ACTIONS = {
149502
+ completed: "Completed",
149503
+ spentTime: "SpentTime"
149504
+ };
149505
+ TIMEBACK_TYPES = {
149506
+ user: "TimebackUser",
149507
+ activityContext: "TimebackActivityContext",
149508
+ activityMetricsCollection: "TimebackActivityMetricsCollection",
149509
+ timeSpentMetricsCollection: "TimebackTimeSpentMetricsCollection"
149510
+ };
149511
+ ACTIVITY_METRIC_TYPES = {
149512
+ totalQuestions: "totalQuestions",
149513
+ correctQuestions: "correctQuestions",
149514
+ xpEarned: "xpEarned",
149515
+ masteredUnits: "masteredUnits"
149516
+ };
149517
+ TIME_METRIC_TYPES = {
149518
+ active: "active",
149519
+ inactive: "inactive",
149520
+ waste: "waste",
149521
+ unknown: "unknown",
149522
+ antiPattern: "anti-pattern"
149523
+ };
149524
+ TIMEBACK_SUBJECTS = [
149525
+ "Math",
149526
+ "FastMath",
149527
+ "Science",
149528
+ "Social Studies",
149529
+ "Language",
149530
+ "Reading",
149531
+ "Vocabulary",
149532
+ "Writing"
149533
+ ];
149534
+ TIMEBACK_GRADE_LEVELS = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
149535
+ TIMEBACK_GRADE_LEVEL_LABELS = {
149536
+ "-1": "pre-k",
149537
+ "0": "kindergarten",
149538
+ "1": "1st grade",
149539
+ "2": "2nd grade",
149540
+ "3": "3rd grade",
149541
+ "4": "4th grade",
149542
+ "5": "5th grade",
149543
+ "6": "6th grade",
149544
+ "7": "7th grade",
149545
+ "8": "8th grade",
149546
+ "9": "9th grade",
149547
+ "10": "10th grade",
149548
+ "11": "11th grade",
149549
+ "12": "12th grade",
149550
+ "13": "AP"
149551
+ };
149552
+ CALIPER_SUBJECTS = {
149553
+ Reading: "Reading",
149554
+ Language: "Language",
149555
+ Vocabulary: "Vocabulary",
149556
+ SocialStudies: "Social Studies",
149557
+ Writing: "Writing",
149558
+ Science: "Science",
149559
+ FastMath: "FastMath",
149560
+ Math: "Math",
149561
+ None: "None"
149562
+ };
149563
+ ONEROSTER_STATUS = {
149564
+ active: "active",
149565
+ toBeDeleted: "tobedeleted"
149566
+ };
149567
+ SCORE_STATUS = {
149568
+ exempt: "exempt",
149569
+ fullyGraded: "fully graded",
149570
+ notSubmitted: "not submitted",
149571
+ partiallyGraded: "partially graded",
149572
+ submitted: "submitted"
149573
+ };
149574
+ ENV_VARS = {
149575
+ clientId: "TIMEBACK_CLIENT_ID",
149576
+ clientSecret: "TIMEBACK_CLIENT_SECRET",
149577
+ baseUrl: "TIMEBACK_BASE_URL",
149578
+ environment: "TIMEBACK_ENVIRONMENT",
149579
+ vendorResourceId: "TIMEBACK_VENDOR_RESOURCE_ID",
149580
+ launchBaseUrl: "GAME_URL"
149581
+ };
149582
+ HTTP_DEFAULTS = {
149583
+ timeout: 30000,
149584
+ retries: 3,
149585
+ retryBackoffBase: 2
149586
+ };
149587
+ AUTH_DEFAULTS = {
149588
+ tokenCacheDuration: 50000
149589
+ };
149590
+ CACHE_DEFAULTS = {
149591
+ defaultTTL: 10 * 60 * 1000,
149592
+ defaultMaxSize: 500,
149593
+ defaultName: "TimebackCache",
149594
+ studentTTL: 10 * 60 * 1000,
149595
+ studentMaxSize: 500,
149596
+ assessmentTTL: 30 * 60 * 1000,
149597
+ assessmentMaxSize: 200,
149598
+ enrollmentTTL: 5 * 1000,
149599
+ enrollmentMaxSize: 100
149600
+ };
149601
+ CONFIG_DEFAULTS = {
149602
+ fileNames: ["timeback.config.js", "timeback.config.json"]
149603
+ };
149604
+ DEFAULT_PLAYCADEMY_ORGANIZATION_ID = process.env.TIMEBACK_ORG_SOURCE_ID || "PLAYCADEMY";
149605
+ PLAYCADEMY_DEFAULTS = {
149606
+ organization: DEFAULT_PLAYCADEMY_ORGANIZATION_ID,
149607
+ launchBaseUrls: PLAYCADEMY_BASE_URLS
149608
+ };
149609
+ RESOURCE_DEFAULTS = {
149610
+ organization: {
149611
+ name: "Playcademy Studios",
149612
+ type: "department"
149613
+ },
149614
+ course: {
149615
+ gradingScheme: "STANDARD",
149616
+ level: {
149617
+ elementary: "Elementary",
149618
+ middle: "Middle",
149619
+ high: "High",
149620
+ ap: "AP"
149621
+ },
149622
+ metadata: {
149623
+ goals: {
149624
+ dailyXp: 50,
149625
+ dailyLessons: 3
149626
+ },
149627
+ metrics: {
149628
+ totalXp: 1000,
149629
+ totalLessons: 50
149630
+ }
149631
+ }
149632
+ },
149633
+ component: {
149634
+ sortOrder: 1,
149635
+ prerequisiteCriteria: "ALL"
149636
+ },
149637
+ resource: {
149638
+ vendorId: "playcademy",
149639
+ roles: ["primary"],
149640
+ importance: "primary",
149641
+ metadata: {
149642
+ type: "interactive",
149643
+ toolProvider: "Playcademy",
149644
+ instructionalMethod: "exploratory",
149645
+ language: "en-US"
149646
+ }
149647
+ },
149648
+ componentResource: {
149649
+ sortOrder: 1,
149650
+ lessonType: "quiz"
149651
+ }
149652
+ };
149653
+ HTTP_STATUS = {
149654
+ CLIENT_ERROR_MIN: 400,
149655
+ CLIENT_ERROR_MAX: 500,
149656
+ SERVER_ERROR_MIN: 500
149657
+ };
149658
+ ERROR_NAMES = {
149659
+ timebackAuth: "TimebackAuthError",
149660
+ timebackApi: "TimebackApiError",
149661
+ timebackConfig: "TimebackConfigError",
149662
+ timebackSdk: "TimebackSDKError"
149663
+ };
149664
+ });
149373
149665
  function deriveSourcedIds(courseId) {
149374
149666
  return {
149375
149667
  course: courseId,
@@ -149385,7 +149677,8 @@ __export3(exports_verify, {
149385
149677
  });
149386
149678
  async function fetchTimebackConfig(client, courseId) {
149387
149679
  const sourcedIds = deriveSourcedIds(courseId);
149388
- const [course, component, resource, componentResource] = await Promise.all([
149680
+ const [org, course, component, resource, componentResource] = await Promise.all([
149681
+ client.oneroster.organizations.get(PLAYCADEMY_DEFAULTS.organization),
149389
149682
  client.oneroster.courses.get(sourcedIds.course),
149390
149683
  client.oneroster.courseComponents.get(sourcedIds.component),
149391
149684
  client.oneroster.resources.get(sourcedIds.resource),
@@ -149393,9 +149686,9 @@ async function fetchTimebackConfig(client, courseId) {
149393
149686
  ]);
149394
149687
  return {
149395
149688
  organization: {
149396
- name: "Playcademy Studios",
149397
- type: "department",
149398
- identifier: "PLAYCADEMY"
149689
+ name: org.name,
149690
+ type: org.type,
149691
+ identifier: org.identifier || PLAYCADEMY_DEFAULTS.organization
149399
149692
  },
149400
149693
  course: {
149401
149694
  title: course.title || "",
@@ -149455,145 +149748,10 @@ async function verifyTimebackResources(client, courseId) {
149455
149748
  }
149456
149749
  };
149457
149750
  }
149458
- var init_verify5 = () => {};
149459
- var ITEM_SLUGS2 = {
149460
- PLAYCADEMY_CREDITS: "PLAYCADEMY_CREDITS",
149461
- PLAYCADEMY_XP: "PLAYCADEMY_XP",
149462
- FOUNDING_MEMBER_BADGE: "FOUNDING_MEMBER_BADGE",
149463
- EARLY_ADOPTER_BADGE: "EARLY_ADOPTER_BADGE",
149464
- FIRST_GAME_BADGE: "FIRST_GAME_BADGE",
149465
- COMMON_SWORD: "COMMON_SWORD",
149466
- SMALL_HEALTH_POTION: "SMALL_HEALTH_POTION",
149467
- SMALL_BACKPACK: "SMALL_BACKPACK",
149468
- LAVA_LAMP: "LAVA_LAMP",
149469
- BOOMBOX: "BOOMBOX",
149470
- CABIN_BED: "CABIN_BED"
149471
- };
149472
- var CURRENCIES2 = {
149473
- PRIMARY: ITEM_SLUGS2.PLAYCADEMY_CREDITS,
149474
- XP: ITEM_SLUGS2.PLAYCADEMY_XP
149475
- };
149476
- var BADGES2 = {
149477
- FOUNDING_MEMBER: ITEM_SLUGS2.FOUNDING_MEMBER_BADGE,
149478
- EARLY_ADOPTER: ITEM_SLUGS2.EARLY_ADOPTER_BADGE,
149479
- FIRST_GAME: ITEM_SLUGS2.FIRST_GAME_BADGE
149480
- };
149481
- var TIMEBACK_API_URLS = {
149482
- production: "https://api.alpha-1edtech.ai",
149483
- staging: "https://api.staging.alpha-1edtech.com"
149484
- };
149485
- var TIMEBACK_AUTH_URLS = {
149486
- production: "https://prod-beyond-timeback-api-2-idp.auth.us-east-1.amazoncognito.com",
149487
- staging: "https://alpha-auth-development-idp.auth.us-west-2.amazoncognito.com"
149488
- };
149489
- var CALIPER_API_URLS = {
149490
- production: "https://caliper.alpha-1edtech.ai",
149491
- staging: "https://caliper-staging.alpha-1edtech.com"
149492
- };
149493
- var ONEROSTER_ENDPOINTS = {
149494
- organizations: "/ims/oneroster/rostering/v1p2/orgs",
149495
- courses: "/ims/oneroster/rostering/v1p2/courses",
149496
- courseComponents: "/ims/oneroster/rostering/v1p2/courses/components",
149497
- resources: "/ims/oneroster/resources/v1p2/resources",
149498
- componentResources: "/ims/oneroster/rostering/v1p2/courses/component-resources",
149499
- classes: "/ims/oneroster/rostering/v1p2/classes",
149500
- enrollments: "/ims/oneroster/rostering/v1p2/enrollments",
149501
- assessmentLineItems: "/ims/oneroster/gradebook/v1p2/assessmentLineItems",
149502
- assessmentResults: "/ims/oneroster/gradebook/v1p2/assessmentResults",
149503
- users: "/ims/oneroster/rostering/v1p2/users"
149504
- };
149505
- var CALIPER_ENDPOINTS = {
149506
- events: "/caliper/event",
149507
- validate: "/caliper/event/validate"
149508
- };
149509
- var createOneRosterUrls = (baseUrl) => {
149510
- const effective = baseUrl || TIMEBACK_API_URLS.production;
149511
- const base = effective.replace(/\/$/, "");
149512
- return {
149513
- user: (userId) => `${base}${ONEROSTER_ENDPOINTS.users}/${userId}`,
149514
- course: (courseId) => `${base}${ONEROSTER_ENDPOINTS.courses}/${courseId}`,
149515
- componentResource: (resourceId) => `${base}${ONEROSTER_ENDPOINTS.componentResources}/${resourceId}`
149516
- };
149517
- };
149518
- var CALIPER_CONSTANTS = {
149519
- context: "http://purl.imsglobal.org/ctx/caliper/v1p2",
149520
- profile: "TimebackProfile",
149521
- dataVersion: "http://purl.imsglobal.org/ctx/caliper/v1p2"
149522
- };
149523
- var TIMEBACK_EVENT_TYPES = {
149524
- activityEvent: "ActivityEvent",
149525
- timeSpentEvent: "TimeSpentEvent"
149526
- };
149527
- var TIMEBACK_ACTIONS = {
149528
- completed: "Completed",
149529
- spentTime: "SpentTime"
149530
- };
149531
- var TIMEBACK_TYPES = {
149532
- user: "TimebackUser",
149533
- activityContext: "TimebackActivityContext",
149534
- activityMetricsCollection: "TimebackActivityMetricsCollection",
149535
- timeSpentMetricsCollection: "TimebackTimeSpentMetricsCollection"
149536
- };
149537
- var ACTIVITY_METRIC_TYPES = {
149538
- totalQuestions: "totalQuestions",
149539
- correctQuestions: "correctQuestions",
149540
- xpEarned: "xpEarned",
149541
- masteredUnits: "masteredUnits"
149542
- };
149543
- var TIME_METRIC_TYPES = {
149544
- active: "active",
149545
- inactive: "inactive",
149546
- waste: "waste",
149547
- unknown: "unknown",
149548
- antiPattern: "anti-pattern"
149549
- };
149550
- var ONEROSTER_STATUS = {
149551
- active: "active",
149552
- toBeDeleted: "tobedeleted"
149553
- };
149554
- var SCORE_STATUS = {
149555
- exempt: "exempt",
149556
- fullyGraded: "fully graded",
149557
- notSubmitted: "not submitted",
149558
- partiallyGraded: "partially graded",
149559
- submitted: "submitted"
149560
- };
149561
- var ENV_VARS = {
149562
- clientId: "TIMEBACK_CLIENT_ID",
149563
- clientSecret: "TIMEBACK_CLIENT_SECRET",
149564
- baseUrl: "TIMEBACK_BASE_URL",
149565
- environment: "TIMEBACK_ENVIRONMENT",
149566
- vendorResourceId: "TIMEBACK_VENDOR_RESOURCE_ID",
149567
- launchBaseUrl: "GAME_URL"
149568
- };
149569
- var HTTP_DEFAULTS = {
149570
- timeout: 30000,
149571
- retries: 3,
149572
- retryBackoffBase: 2
149573
- };
149574
- var AUTH_DEFAULTS = {
149575
- tokenCacheDuration: 50000
149576
- };
149577
- var CACHE_DEFAULTS = {
149578
- defaultTTL: 10 * 60 * 1000,
149579
- defaultMaxSize: 500,
149580
- defaultName: "TimebackCache",
149581
- studentTTL: 10 * 60 * 1000,
149582
- studentMaxSize: 500,
149583
- assessmentTTL: 30 * 60 * 1000,
149584
- assessmentMaxSize: 200
149585
- };
149586
- var HTTP_STATUS = {
149587
- CLIENT_ERROR_MIN: 400,
149588
- CLIENT_ERROR_MAX: 500,
149589
- SERVER_ERROR_MIN: 500
149590
- };
149591
- var ERROR_NAMES = {
149592
- timebackAuth: "TimebackAuthError",
149593
- timebackApi: "TimebackApiError",
149594
- timebackConfig: "TimebackConfigError",
149595
- timebackSdk: "TimebackSDKError"
149596
- };
149751
+ var init_verify5 = __esm3(() => {
149752
+ init_constants();
149753
+ });
149754
+ init_constants();
149597
149755
 
149598
149756
  class TimebackError extends Error {
149599
149757
  constructor(message2) {
@@ -149655,6 +149813,25 @@ class ResourceNotFoundError extends TimebackError {
149655
149813
  Object.setPrototypeOf(this, ResourceNotFoundError.prototype);
149656
149814
  }
149657
149815
  }
149816
+ init_constants();
149817
+ var isObject2 = (value) => typeof value === "object" && value !== null;
149818
+ var SUBJECT_VALUES = TIMEBACK_SUBJECTS;
149819
+ var GRADE_VALUES = TIMEBACK_GRADE_LEVELS;
149820
+ function isPlaycademyResourceMetadata(value) {
149821
+ if (!isObject2(value)) {
149822
+ return false;
149823
+ }
149824
+ if (!("mastery" in value) || value.mastery === undefined) {
149825
+ return true;
149826
+ }
149827
+ return isObject2(value.mastery);
149828
+ }
149829
+ function isTimebackSubject(value) {
149830
+ return typeof value === "string" && SUBJECT_VALUES.includes(value);
149831
+ }
149832
+ function isTimebackGrade(value) {
149833
+ return typeof value === "number" && Number.isInteger(value) && GRADE_VALUES.includes(value);
149834
+ }
149658
149835
  var isBrowser2 = () => {
149659
149836
  const g5 = globalThis;
149660
149837
  return typeof g5.window !== "undefined" && typeof g5.document !== "undefined";
@@ -150103,6 +150280,8 @@ async function updateTimebackResources(client, courseId, config2) {
150103
150280
  updateComponentResourceLink(client, config2, sourcedIds)
150104
150281
  ]);
150105
150282
  }
150283
+ init_constants();
150284
+ init_constants();
150106
150285
  if (process.env.DEBUG === "true") {
150107
150286
  process.env.TERM = "dumb";
150108
150287
  }
@@ -150202,6 +150381,7 @@ async function getTimebackTokenResponse(config2) {
150202
150381
  function getAuthUrl(environment = "production") {
150203
150382
  return TIMEBACK_AUTH_URLS[environment];
150204
150383
  }
150384
+ init_constants();
150205
150385
  async function request({
150206
150386
  path: path2,
150207
150387
  baseUrl,
@@ -150307,6 +150487,154 @@ async function requestCaliper(options) {
150307
150487
  baseUrl: caliperBase
150308
150488
  });
150309
150489
  }
150490
+ init_constants();
150491
+ function createCaliperNamespace(client) {
150492
+ const urls = createOneRosterUrls(client.getBaseUrl());
150493
+ const caliper = {
150494
+ emit: async (event, sensorUrl) => {
150495
+ const envelope = {
150496
+ sensor: sensorUrl,
150497
+ sendTime: new Date().toISOString(),
150498
+ dataVersion: CALIPER_CONSTANTS.dataVersion,
150499
+ data: [event]
150500
+ };
150501
+ return client["requestCaliper"](CALIPER_ENDPOINTS.events, "POST", envelope);
150502
+ },
150503
+ emitBatch: async (events, sensorUrl) => {
150504
+ if (events.length === 0)
150505
+ return;
150506
+ const envelope = {
150507
+ sensor: sensorUrl,
150508
+ sendTime: new Date().toISOString(),
150509
+ dataVersion: CALIPER_CONSTANTS.dataVersion,
150510
+ data: events
150511
+ };
150512
+ return client["requestCaliper"](CALIPER_ENDPOINTS.events, "POST", envelope);
150513
+ },
150514
+ emitActivityEvent: async (data) => {
150515
+ const event = {
150516
+ "@context": CALIPER_CONSTANTS.context,
150517
+ id: `urn:uuid:${crypto.randomUUID()}`,
150518
+ type: TIMEBACK_EVENT_TYPES.activityEvent,
150519
+ eventTime: new Date().toISOString(),
150520
+ profile: CALIPER_CONSTANTS.profile,
150521
+ actor: {
150522
+ id: urls.user(data.studentId),
150523
+ type: TIMEBACK_TYPES.user,
150524
+ email: data.studentEmail
150525
+ },
150526
+ action: TIMEBACK_ACTIONS.completed,
150527
+ object: {
150528
+ id: caliper.buildActivityUrl(data),
150529
+ type: TIMEBACK_TYPES.activityContext,
150530
+ subject: data.subject,
150531
+ app: {
150532
+ name: data.appName
150533
+ },
150534
+ activity: {
150535
+ name: data.activityName
150536
+ },
150537
+ course: { id: urls.course(data.courseId), name: data.activityName },
150538
+ process: false
150539
+ },
150540
+ generated: {
150541
+ id: `urn:timeback:metrics:activity-completion-${crypto.randomUUID()}`,
150542
+ type: TIMEBACK_TYPES.activityMetricsCollection,
150543
+ attempt: data.attemptNumber || 1,
150544
+ items: [
150545
+ ...data.totalQuestions !== undefined ? [
150546
+ {
150547
+ type: ACTIVITY_METRIC_TYPES.totalQuestions,
150548
+ value: data.totalQuestions
150549
+ }
150550
+ ] : [],
150551
+ ...data.correctQuestions !== undefined ? [
150552
+ {
150553
+ type: ACTIVITY_METRIC_TYPES.correctQuestions,
150554
+ value: data.correctQuestions
150555
+ }
150556
+ ] : [],
150557
+ ...data.xpEarned !== undefined ? [{ type: ACTIVITY_METRIC_TYPES.xpEarned, value: data.xpEarned }] : [],
150558
+ ...data.masteredUnits !== undefined ? [
150559
+ {
150560
+ type: ACTIVITY_METRIC_TYPES.masteredUnits,
150561
+ value: data.masteredUnits
150562
+ }
150563
+ ] : []
150564
+ ],
150565
+ ...data.extensions ? { extensions: data.extensions } : {}
150566
+ }
150567
+ };
150568
+ return caliper.emit(event, data.sensorUrl);
150569
+ },
150570
+ emitTimeSpentEvent: async (data) => {
150571
+ const event = {
150572
+ "@context": CALIPER_CONSTANTS.context,
150573
+ id: `urn:uuid:${crypto.randomUUID()}`,
150574
+ type: TIMEBACK_EVENT_TYPES.timeSpentEvent,
150575
+ eventTime: new Date().toISOString(),
150576
+ profile: CALIPER_CONSTANTS.profile,
150577
+ actor: {
150578
+ id: urls.user(data.studentId),
150579
+ type: TIMEBACK_TYPES.user,
150580
+ email: data.studentEmail
150581
+ },
150582
+ action: TIMEBACK_ACTIONS.spentTime,
150583
+ object: {
150584
+ id: caliper.buildActivityUrl(data),
150585
+ type: TIMEBACK_TYPES.activityContext,
150586
+ subject: data.subject,
150587
+ app: {
150588
+ name: data.appName
150589
+ },
150590
+ activity: {
150591
+ name: data.activityName
150592
+ },
150593
+ course: { id: urls.course(data.courseId), name: data.activityName },
150594
+ process: false
150595
+ },
150596
+ generated: {
150597
+ id: `urn:timeback:metrics:time-spent-${crypto.randomUUID()}`,
150598
+ type: TIMEBACK_TYPES.timeSpentMetricsCollection,
150599
+ items: [
150600
+ { type: TIME_METRIC_TYPES.active, value: data.activeTimeSeconds },
150601
+ ...data.inactiveTimeSeconds !== undefined ? [
150602
+ {
150603
+ type: TIME_METRIC_TYPES.inactive,
150604
+ value: data.inactiveTimeSeconds
150605
+ }
150606
+ ] : [],
150607
+ ...data.wasteTimeSeconds !== undefined ? [{ type: TIME_METRIC_TYPES.waste, value: data.wasteTimeSeconds }] : []
150608
+ ]
150609
+ }
150610
+ };
150611
+ return caliper.emit(event, data.sensorUrl);
150612
+ },
150613
+ buildActivityUrl: (data) => {
150614
+ const base = data.sensorUrl.replace(/\/$/, "");
150615
+ return `${base}/activities/${data.courseId}/${data.activityId}/${crypto.randomUUID()}`;
150616
+ }
150617
+ };
150618
+ return caliper;
150619
+ }
150620
+ function createEduBridgeNamespace(client) {
150621
+ const enrollments = {
150622
+ listByUser: async (userId) => {
150623
+ const response = await client["request"](`/edubridge/enrollments/user/${userId}`, "GET");
150624
+ return response.data;
150625
+ }
150626
+ };
150627
+ const analytics = {
150628
+ getEnrollmentFacts: async (enrollmentId) => {
150629
+ return client["request"](`/edubridge/analytics/enrollment/${enrollmentId}`, "GET");
150630
+ }
150631
+ };
150632
+ return {
150633
+ enrollments,
150634
+ analytics
150635
+ };
150636
+ }
150637
+ init_constants();
150310
150638
  function logTimebackError(operation, error2, context) {
150311
150639
  const errorMessage = error2 instanceof Error ? error2.message : String(error2);
150312
150640
  if (error2 instanceof TimebackApiError) {
@@ -150342,6 +150670,17 @@ function createOneRosterNamespace(client) {
150342
150670
  listByCourse: async (courseSourcedId) => {
150343
150671
  const res = await client["request"](`${ONEROSTER_ENDPOINTS.courses}/${courseSourcedId}/classes`, "GET");
150344
150672
  return res.classes;
150673
+ },
150674
+ listByStudent: async (userSourcedId, options) => {
150675
+ const queryParams = new URLSearchParams;
150676
+ if (options?.limit)
150677
+ queryParams.set("limit", String(options.limit));
150678
+ if (options?.offset)
150679
+ queryParams.set("offset", String(options.offset));
150680
+ const endpoint = `${ONEROSTER_ENDPOINTS.users}/${userSourcedId}/classes`;
150681
+ const url = queryParams.toString() ? `${endpoint}?${queryParams}` : endpoint;
150682
+ const res = await client["request"](url, "GET");
150683
+ return res.classes || [];
150345
150684
  }
150346
150685
  },
150347
150686
  organizations: {
@@ -150349,7 +150688,7 @@ function createOneRosterNamespace(client) {
150349
150688
  return client["request"](ONEROSTER_ENDPOINTS.organizations, "POST", data);
150350
150689
  },
150351
150690
  get: async (sourcedId) => {
150352
- return client["request"](`${ONEROSTER_ENDPOINTS.organizations}/${sourcedId}`, "GET");
150691
+ return client["request"](`${ONEROSTER_ENDPOINTS.organizations}/${sourcedId}`, "GET").then((res) => res.org);
150353
150692
  },
150354
150693
  update: async (sourcedId, data) => {
150355
150694
  return client["request"](`${ONEROSTER_ENDPOINTS.organizations}/${sourcedId}`, "PUT", data);
@@ -150457,14 +150796,30 @@ function createOneRosterNamespace(client) {
150457
150796
  update: async (sourcedId, data) => {
150458
150797
  return client["request"](`${ONEROSTER_ENDPOINTS.assessmentResults}/${sourcedId}`, "PUT", data);
150459
150798
  },
150460
- getLatestForStudent: async (studentId, lineItemId) => {
150799
+ getAttemptStats: async (studentId, lineItemId) => {
150461
150800
  try {
150462
150801
  const filter2 = `student.sourcedId='${studentId}' AND assessmentLineItem.sourcedId='${lineItemId}'`;
150463
- const url = `${ONEROSTER_ENDPOINTS.assessmentResults}?filter=${encodeURIComponent(filter2)}&sort=scoreDate&orderBy=desc&limit=1`;
150802
+ const url = `${ONEROSTER_ENDPOINTS.assessmentResults}?filter=${encodeURIComponent(filter2)}`;
150464
150803
  const response = await client["request"](url, "GET");
150465
- return response.assessmentResults[0] || null;
150804
+ const results = response.assessmentResults || [];
150805
+ if (results.length === 0)
150806
+ return null;
150807
+ let maxAttemptResult = results[0];
150808
+ let maxAttemptNumber = maxAttemptResult.metadata?.attemptNumber || 0;
150809
+ let activeAttemptCount = 0;
150810
+ for (const result of results) {
150811
+ const attemptNumber = result.metadata?.attemptNumber || 0;
150812
+ if (attemptNumber > maxAttemptNumber) {
150813
+ maxAttemptNumber = attemptNumber;
150814
+ maxAttemptResult = result;
150815
+ }
150816
+ if (result.status === "active") {
150817
+ activeAttemptCount++;
150818
+ }
150819
+ }
150820
+ return { maxAttemptNumber, activeAttemptCount, maxAttemptResult };
150466
150821
  } catch (error2) {
150467
- logTimebackError("query latest assessment result", error2, {
150822
+ logTimebackError("query attempt stats", error2, {
150468
150823
  studentId,
150469
150824
  lineItemId
150470
150825
  });
@@ -150477,7 +150832,7 @@ function createOneRosterNamespace(client) {
150477
150832
  return client["request"](ONEROSTER_ENDPOINTS.users, "POST", { user: data });
150478
150833
  },
150479
150834
  get: async (sourcedId) => {
150480
- return client["request"](`${ONEROSTER_ENDPOINTS.users}/${sourcedId}`, "GET");
150835
+ return client["request"](`${ONEROSTER_ENDPOINTS.users}/${sourcedId}`, "GET").then((res) => res.user);
150481
150836
  },
150482
150837
  findByEmail: async (email) => {
150483
150838
  const params = new URLSearchParams({ filter: `email='${email}'` });
@@ -150493,130 +150848,8 @@ function createOneRosterNamespace(client) {
150493
150848
  }
150494
150849
  };
150495
150850
  }
150496
- function createCaliperNamespace(client) {
150497
- const urls = createOneRosterUrls(client.getBaseUrl());
150498
- const caliper = {
150499
- emit: async (event, sensorUrl) => {
150500
- const envelope = {
150501
- sensor: sensorUrl,
150502
- sendTime: new Date().toISOString(),
150503
- dataVersion: CALIPER_CONSTANTS.dataVersion,
150504
- data: [event]
150505
- };
150506
- return client["requestCaliper"](CALIPER_ENDPOINTS.events, "POST", envelope);
150507
- },
150508
- emitBatch: async (events, sensorUrl) => {
150509
- if (events.length === 0)
150510
- return;
150511
- const envelope = {
150512
- sensor: sensorUrl,
150513
- sendTime: new Date().toISOString(),
150514
- dataVersion: CALIPER_CONSTANTS.dataVersion,
150515
- data: events
150516
- };
150517
- return client["requestCaliper"](CALIPER_ENDPOINTS.events, "POST", envelope);
150518
- },
150519
- emitActivityEvent: async (data) => {
150520
- const event = {
150521
- "@context": CALIPER_CONSTANTS.context,
150522
- id: `urn:uuid:${crypto.randomUUID()}`,
150523
- type: TIMEBACK_EVENT_TYPES.activityEvent,
150524
- eventTime: new Date().toISOString(),
150525
- profile: CALIPER_CONSTANTS.profile,
150526
- actor: {
150527
- id: urls.user(data.studentId),
150528
- type: TIMEBACK_TYPES.user,
150529
- email: data.studentEmail
150530
- },
150531
- action: TIMEBACK_ACTIONS.completed,
150532
- object: {
150533
- id: urls.componentResource(data.activityId),
150534
- type: TIMEBACK_TYPES.activityContext,
150535
- subject: data.subject,
150536
- app: {
150537
- name: data.appName
150538
- },
150539
- activity: {
150540
- name: data.activityName
150541
- },
150542
- course: { id: urls.course(data.courseId), name: data.activityName },
150543
- process: true
150544
- },
150545
- generated: {
150546
- id: `urn:timeback:metrics:activity-completion-${crypto.randomUUID()}`,
150547
- type: TIMEBACK_TYPES.activityMetricsCollection,
150548
- attempt: data.attemptNumber || 1,
150549
- items: [
150550
- ...data.totalQuestions !== undefined ? [
150551
- {
150552
- type: ACTIVITY_METRIC_TYPES.totalQuestions,
150553
- value: data.totalQuestions
150554
- }
150555
- ] : [],
150556
- ...data.correctQuestions !== undefined ? [
150557
- {
150558
- type: ACTIVITY_METRIC_TYPES.correctQuestions,
150559
- value: data.correctQuestions
150560
- }
150561
- ] : [],
150562
- ...data.xpEarned !== undefined ? [{ type: ACTIVITY_METRIC_TYPES.xpEarned, value: data.xpEarned }] : [],
150563
- ...data.masteredUnits !== undefined ? [
150564
- {
150565
- type: ACTIVITY_METRIC_TYPES.masteredUnits,
150566
- value: data.masteredUnits
150567
- }
150568
- ] : []
150569
- ]
150570
- }
150571
- };
150572
- return caliper.emit(event, data.sensorUrl);
150573
- },
150574
- emitTimeSpentEvent: async (data) => {
150575
- const event = {
150576
- "@context": CALIPER_CONSTANTS.context,
150577
- id: `urn:uuid:${crypto.randomUUID()}`,
150578
- type: TIMEBACK_EVENT_TYPES.timeSpentEvent,
150579
- eventTime: new Date().toISOString(),
150580
- profile: CALIPER_CONSTANTS.profile,
150581
- actor: {
150582
- id: urls.user(data.studentId),
150583
- type: TIMEBACK_TYPES.user,
150584
- email: data.studentEmail
150585
- },
150586
- action: TIMEBACK_ACTIONS.spentTime,
150587
- object: {
150588
- id: urls.componentResource(data.activityId),
150589
- type: TIMEBACK_TYPES.activityContext,
150590
- subject: data.subject,
150591
- app: {
150592
- name: data.appName
150593
- },
150594
- activity: {
150595
- name: data.activityName
150596
- },
150597
- course: { id: urls.course(data.courseId), name: data.activityName },
150598
- process: false
150599
- },
150600
- generated: {
150601
- id: `urn:timeback:metrics:time-spent-${crypto.randomUUID()}`,
150602
- type: TIMEBACK_TYPES.timeSpentMetricsCollection,
150603
- items: [
150604
- { type: TIME_METRIC_TYPES.active, value: data.activeTimeSeconds },
150605
- ...data.inactiveTimeSeconds !== undefined ? [
150606
- {
150607
- type: TIME_METRIC_TYPES.inactive,
150608
- value: data.inactiveTimeSeconds
150609
- }
150610
- ] : [],
150611
- ...data.wasteTimeSeconds !== undefined ? [{ type: TIME_METRIC_TYPES.waste, value: data.wasteTimeSeconds }] : []
150612
- ]
150613
- }
150614
- };
150615
- return caliper.emit(event, data.sensorUrl);
150616
- }
150617
- };
150618
- return caliper;
150619
- }
150851
+ init_constants();
150852
+ init_constants();
150620
150853
 
150621
150854
  class TimebackCache {
150622
150855
  cache = new Map;
@@ -150722,6 +150955,8 @@ class TimebackCache {
150722
150955
  class TimebackCacheManager {
150723
150956
  studentCache;
150724
150957
  assessmentLineItemCache;
150958
+ resourceMasteryCache;
150959
+ enrollmentCache;
150725
150960
  constructor() {
150726
150961
  this.studentCache = new TimebackCache({
150727
150962
  defaultTTL: CACHE_DEFAULTS.studentTTL,
@@ -150733,6 +150968,16 @@ class TimebackCacheManager {
150733
150968
  maxSize: CACHE_DEFAULTS.assessmentMaxSize,
150734
150969
  name: "AssessmentLineItemCache"
150735
150970
  });
150971
+ this.resourceMasteryCache = new TimebackCache({
150972
+ defaultTTL: CACHE_DEFAULTS.assessmentTTL,
150973
+ maxSize: CACHE_DEFAULTS.assessmentMaxSize,
150974
+ name: "ResourceMasteryCache"
150975
+ });
150976
+ this.enrollmentCache = new TimebackCache({
150977
+ defaultTTL: CACHE_DEFAULTS.enrollmentTTL,
150978
+ maxSize: CACHE_DEFAULTS.enrollmentMaxSize,
150979
+ name: "EnrollmentCache"
150980
+ });
150736
150981
  }
150737
150982
  getStudent(key) {
150738
150983
  return this.studentCache.get(key);
@@ -150746,26 +150991,188 @@ class TimebackCacheManager {
150746
150991
  setAssessmentLineItem(key, lineItemId) {
150747
150992
  this.assessmentLineItemCache.set(key, lineItemId);
150748
150993
  }
150994
+ getResourceMasterableUnits(key) {
150995
+ return this.resourceMasteryCache.get(key);
150996
+ }
150997
+ setResourceMasterableUnits(key, value) {
150998
+ this.resourceMasteryCache.set(key, value);
150999
+ }
151000
+ getEnrollments(studentId) {
151001
+ return this.enrollmentCache.get(studentId);
151002
+ }
151003
+ setEnrollments(studentId, enrollments) {
151004
+ this.enrollmentCache.set(studentId, enrollments);
151005
+ }
150749
151006
  clearAll() {
150750
151007
  this.studentCache.clear();
150751
151008
  this.assessmentLineItemCache.clear();
151009
+ this.resourceMasteryCache.clear();
151010
+ this.enrollmentCache.clear();
150752
151011
  log3.info("[TimebackCacheManager] All caches cleared");
150753
151012
  }
150754
151013
  getStats() {
150755
151014
  return {
150756
151015
  studentCache: this.studentCache.stats(),
150757
- assessmentLineItemCache: this.assessmentLineItemCache.stats()
151016
+ assessmentLineItemCache: this.assessmentLineItemCache.stats(),
151017
+ resourceMasteryCache: this.resourceMasteryCache.stats(),
151018
+ enrollmentCache: this.enrollmentCache.stats()
150758
151019
  };
150759
151020
  }
150760
151021
  cleanup() {
150761
151022
  this.studentCache.cleanup();
150762
151023
  this.assessmentLineItemCache.cleanup();
151024
+ this.resourceMasteryCache.cleanup();
151025
+ this.enrollmentCache.cleanup();
150763
151026
  log3.debug("[TimebackCacheManager] Cache cleanup completed");
150764
151027
  }
150765
151028
  }
151029
+ init_constants();
151030
+
151031
+ class MasteryTracker {
151032
+ cacheManager;
151033
+ onerosterNamespace;
151034
+ edubridgeNamespace;
151035
+ constructor(cacheManager, onerosterNamespace, edubridgeNamespace) {
151036
+ this.cacheManager = cacheManager;
151037
+ this.onerosterNamespace = onerosterNamespace;
151038
+ this.edubridgeNamespace = edubridgeNamespace;
151039
+ }
151040
+ async checkProgress(input) {
151041
+ const { studentId, courseId, resourceId, masteredUnits } = input;
151042
+ if (typeof masteredUnits !== "number" || masteredUnits <= 0) {
151043
+ return;
151044
+ }
151045
+ const masterableUnits = await this.resolveMasterableUnits(resourceId);
151046
+ if (!masterableUnits || masterableUnits <= 0) {
151047
+ log3.warn("[MasteryTracker] No masterableUnits configured for course", {
151048
+ courseId,
151049
+ resourceId
151050
+ });
151051
+ return;
151052
+ }
151053
+ const facts = await this.fetchEnrollmentAnalyticsFacts(studentId, courseId);
151054
+ if (!facts) {
151055
+ log3.warn("[MasteryTracker] Unable to retrieve analytics for mastery-based completion", {
151056
+ studentId,
151057
+ courseId
151058
+ });
151059
+ return;
151060
+ }
151061
+ const historicalMasteredUnits = this.sumAnalyticsMetric(facts, "masteredUnits");
151062
+ const totalMastered = historicalMasteredUnits + masteredUnits;
151063
+ const rawPct = totalMastered / masterableUnits * 100;
151064
+ const pctCompleteApp = Math.min(100, Math.max(0, Math.round(rawPct)));
151065
+ const masteryAchieved = totalMastered >= masterableUnits;
151066
+ return { pctCompleteApp, masteryAchieved };
151067
+ }
151068
+ async createCompletionEntry(studentId, courseId, classId, appName) {
151069
+ const ids = deriveSourcedIds(courseId);
151070
+ const lineItemId = `${ids.course}-mastery-completion-assessment`;
151071
+ const resultId = `${lineItemId}:${studentId}:completion`;
151072
+ try {
151073
+ await this.onerosterNamespace.assessmentLineItems.findOrCreate(lineItemId, {
151074
+ sourcedId: lineItemId,
151075
+ title: "Mastery Completion",
151076
+ status: ONEROSTER_STATUS.active,
151077
+ ...classId ? { class: { sourcedId: classId } } : { course: { sourcedId: ids.course } },
151078
+ ...ids.componentResource ? { componentResource: { sourcedId: ids.componentResource } } : {}
151079
+ });
151080
+ await this.onerosterNamespace.assessmentResults.upsert(resultId, {
151081
+ sourcedId: resultId,
151082
+ status: ONEROSTER_STATUS.active,
151083
+ assessmentLineItem: { sourcedId: lineItemId },
151084
+ student: { sourcedId: studentId },
151085
+ score: 100,
151086
+ scoreDate: new Date().toISOString(),
151087
+ scoreStatus: SCORE_STATUS.fullyGraded,
151088
+ inProgress: "false",
151089
+ metadata: {
151090
+ isMasteryCompletion: true,
151091
+ completedAt: new Date().toISOString(),
151092
+ appName
151093
+ }
151094
+ });
151095
+ log3.info("[MasteryTracker] Created mastery completion entry", {
151096
+ studentId,
151097
+ lineItemId,
151098
+ resultId
151099
+ });
151100
+ } catch (error2) {
151101
+ log3.error("[MasteryTracker] Failed to create mastery completion entry", {
151102
+ studentId,
151103
+ lineItemId,
151104
+ error: error2
151105
+ });
151106
+ }
151107
+ }
151108
+ async resolveMasterableUnits(resourceId) {
151109
+ if (!resourceId) {
151110
+ return;
151111
+ }
151112
+ const cached = this.cacheManager.getResourceMasterableUnits(resourceId);
151113
+ if (cached !== undefined) {
151114
+ return cached === null ? undefined : cached;
151115
+ }
151116
+ try {
151117
+ const resource = await this.onerosterNamespace.resources.get(resourceId);
151118
+ const playcademyMetadata = resource.metadata?.playcademy;
151119
+ if (!playcademyMetadata) {
151120
+ return;
151121
+ }
151122
+ const masterableUnits = isPlaycademyResourceMetadata(playcademyMetadata) ? playcademyMetadata.mastery?.masterableUnits : undefined;
151123
+ this.cacheManager.setResourceMasterableUnits(resourceId, masterableUnits ?? null);
151124
+ return masterableUnits;
151125
+ } catch (error2) {
151126
+ log3.error("[MasteryTracker] Failed to fetch resource metadata for mastery config", {
151127
+ resourceId,
151128
+ error: error2
151129
+ });
151130
+ this.cacheManager.setResourceMasterableUnits(resourceId, null);
151131
+ return;
151132
+ }
151133
+ }
151134
+ async fetchEnrollmentAnalyticsFacts(studentId, courseId) {
151135
+ try {
151136
+ const enrollments = await this.edubridgeNamespace.enrollments.listByUser(studentId);
151137
+ const enrollment = enrollments.find((e) => e.course.id === courseId);
151138
+ if (!enrollment) {
151139
+ log3.warn("[MasteryTracker] Enrollment not found for student/course", {
151140
+ studentId,
151141
+ courseId
151142
+ });
151143
+ return;
151144
+ }
151145
+ const analytics = await this.edubridgeNamespace.analytics.getEnrollmentFacts(enrollment.id);
151146
+ return analytics.facts;
151147
+ } catch (error2) {
151148
+ log3.error("[MasteryTracker] Failed to load enrollment analytics facts", {
151149
+ studentId,
151150
+ courseId,
151151
+ error: error2
151152
+ });
151153
+ return;
151154
+ }
151155
+ }
151156
+ sumAnalyticsMetric(facts, metric) {
151157
+ if (!facts) {
151158
+ return 0;
151159
+ }
151160
+ let total = 0;
151161
+ Object.values(facts).forEach((dateFacts) => {
151162
+ Object.values(dateFacts).forEach((subjectFacts) => {
151163
+ const metrics = subjectFacts.activityMetrics;
151164
+ if (metrics && typeof metrics[metric] === "number") {
151165
+ total += metrics[metric];
151166
+ }
151167
+ });
151168
+ });
151169
+ return total;
151170
+ }
151171
+ }
150766
151172
  function kebabToTitleCase(kebabStr) {
150767
151173
  return kebabStr.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
150768
151174
  }
151175
+ init_constants();
150769
151176
  function validateProgressData(progressData) {
150770
151177
  if (!progressData.subject) {
150771
151178
  throw new ConfigurationError("subject", "Subject is required for Caliper events. Provide it in progressData.subject");
@@ -150819,36 +151226,47 @@ class ProgressRecorder {
150819
151226
  cacheManager;
150820
151227
  onerosterNamespace;
150821
151228
  caliperNamespace;
150822
- constructor(studentResolver, cacheManager, onerosterNamespace, caliperNamespace) {
151229
+ masteryTracker;
151230
+ constructor(studentResolver, cacheManager, onerosterNamespace, caliperNamespace, masteryTracker) {
150823
151231
  this.studentResolver = studentResolver;
150824
151232
  this.cacheManager = cacheManager;
150825
151233
  this.onerosterNamespace = onerosterNamespace;
150826
151234
  this.caliperNamespace = caliperNamespace;
151235
+ this.masteryTracker = masteryTracker;
150827
151236
  }
150828
151237
  async record(courseId, studentIdentifier, progressData) {
150829
151238
  validateProgressData(progressData);
150830
- const ids = deriveSourcedIds(courseId);
150831
- const activityId = progressData.activityId || ids.componentResource || ids.resource || "unknown-activity";
150832
- const activityName = progressData.activityName || kebabToTitleCase(activityId);
150833
- const classId = progressData.classId;
150834
- const courseName = progressData.courseName || "Game Course";
150835
- const student = await this.studentResolver.resolve(studentIdentifier, progressData.studentEmail);
151239
+ const { ids, activityId, activityName, courseName, student } = await this.resolveContext(courseId, studentIdentifier, progressData);
150836
151240
  const { id: studentId, email: studentEmail } = student;
150837
151241
  const { score, totalQuestions, correctQuestions, xpEarned, masteredUnits, attemptNumber } = progressData;
150838
- const lineItemId = `${activityId}-assessment`;
150839
- let actualLineItemId = this.cacheManager.getAssessmentLineItem(lineItemId);
150840
- if (!actualLineItemId) {
150841
- actualLineItemId = await this.getOrCreateLineItem(lineItemId, activityName, classId, ids);
150842
- this.cacheManager.setAssessmentLineItem(lineItemId, actualLineItemId);
150843
- }
150844
- let currentAttemptNumber = attemptNumber || 1;
150845
- const isFirstAttempt = currentAttemptNumber === 1;
150846
- if (!attemptNumber && score !== undefined) {
150847
- currentAttemptNumber = await this.determineAttemptNumber(studentId, actualLineItemId);
151242
+ const actualLineItemId = await this.resolveAssessmentLineItem(activityId, activityName, progressData.classId, ids);
151243
+ const currentAttemptNumber = await this.resolveAttemptNumber(attemptNumber, score, studentId, actualLineItemId);
151244
+ const isFirstActiveAttempt = currentAttemptNumber === 1;
151245
+ const calculatedXp = this.calculateXpForProgress(progressData, totalQuestions, correctQuestions, xpEarned, isFirstActiveAttempt);
151246
+ let extensions = progressData.extensions;
151247
+ const masteryProgress = await this.masteryTracker.checkProgress({
151248
+ studentId,
151249
+ courseId,
151250
+ resourceId: ids.resource,
151251
+ masteredUnits: progressData.masteredUnits ?? 0
151252
+ });
151253
+ let pctCompleteApp;
151254
+ let masteryAchieved = false;
151255
+ let scoreStatus = SCORE_STATUS.fullyGraded;
151256
+ const inProgress = "false";
151257
+ if (masteryProgress) {
151258
+ masteryAchieved = masteryProgress.masteryAchieved;
151259
+ pctCompleteApp = masteryProgress.pctCompleteApp;
151260
+ extensions = {
151261
+ ...extensions || {},
151262
+ ...pctCompleteApp !== undefined ? { pctCompleteApp } : {}
151263
+ };
151264
+ if (masteryAchieved) {
151265
+ scoreStatus = SCORE_STATUS.fullyGraded;
151266
+ }
150848
151267
  }
150849
- const calculatedXp = this.calculateXpForProgress(progressData, totalQuestions, correctQuestions, xpEarned, isFirstAttempt);
150850
151268
  if (score !== undefined) {
150851
- await this.createGradebookEntry(actualLineItemId, studentId, currentAttemptNumber, score, totalQuestions, correctQuestions, calculatedXp);
151269
+ await this.createGradebookEntry(actualLineItemId, studentId, currentAttemptNumber, score, totalQuestions, correctQuestions, calculatedXp, masteredUnits, scoreStatus, inProgress, progressData.appName);
150852
151270
  } else {
150853
151271
  log3.warn("[ProgressRecorder] Score not provided, skipping gradebook entry", {
150854
151272
  studentId,
@@ -150856,20 +151274,70 @@ class ProgressRecorder {
150856
151274
  attemptNumber: currentAttemptNumber
150857
151275
  });
150858
151276
  }
150859
- await this.emitCaliperEvent(studentId, studentEmail, activityId, activityName, ids.course, courseName, totalQuestions, correctQuestions, calculatedXp, masteredUnits, currentAttemptNumber, progressData);
151277
+ if (masteryAchieved) {
151278
+ await this.masteryTracker.createCompletionEntry(studentId, courseId, progressData.classId, progressData.appName);
151279
+ }
151280
+ await this.emitCaliperEvent(studentId, studentEmail, activityId, activityName, ids.course, courseName, totalQuestions, correctQuestions, calculatedXp, masteredUnits, currentAttemptNumber, progressData, extensions);
150860
151281
  return {
150861
151282
  xpAwarded: calculatedXp,
150862
- attemptNumber: currentAttemptNumber
151283
+ attemptNumber: currentAttemptNumber,
151284
+ masteredUnitsApplied: progressData.masteredUnits ?? 0,
151285
+ pctCompleteApp,
151286
+ scoreStatus,
151287
+ inProgress
150863
151288
  };
150864
151289
  }
151290
+ async resolveContext(courseId, studentIdentifier, progressData) {
151291
+ const ids = deriveSourcedIds(courseId);
151292
+ const activityId = progressData.activityId || ids.componentResource || ids.resource || "unknown-activity";
151293
+ const activityName = progressData.activityName || kebabToTitleCase(activityId);
151294
+ const courseName = progressData.courseName || "Game Course";
151295
+ const student = await this.studentResolver.resolve(studentIdentifier, progressData.studentEmail);
151296
+ return { ids, activityId, activityName, courseName, student };
151297
+ }
151298
+ async resolveAssessmentLineItem(activityId, activityName, classId, ids) {
151299
+ const lineItemId = `${ids.course}-${activityId}-assessment`;
151300
+ let actualLineItemId = this.cacheManager.getAssessmentLineItem(lineItemId);
151301
+ if (!actualLineItemId) {
151302
+ actualLineItemId = await this.getOrCreateLineItem(lineItemId, activityName, classId, ids);
151303
+ this.cacheManager.setAssessmentLineItem(lineItemId, actualLineItemId);
151304
+ }
151305
+ return actualLineItemId;
151306
+ }
151307
+ async resolveAttemptNumber(providedAttemptNumber, score, studentId, lineItemId) {
151308
+ if (providedAttemptNumber) {
151309
+ return providedAttemptNumber;
151310
+ }
151311
+ if (score !== undefined) {
151312
+ return this.determineAttemptNumber(studentId, lineItemId);
151313
+ }
151314
+ return 1;
151315
+ }
151316
+ calculateXpForProgress(progressData, totalQuestions, correctQuestions, xpEarned, isFirstAttempt) {
151317
+ if (xpEarned !== undefined) {
151318
+ log3.debug("[ProgressRecorder] Using provided XP", { xpEarned });
151319
+ return xpEarned;
151320
+ }
151321
+ if (progressData.sessionDurationSeconds && totalQuestions && correctQuestions) {
151322
+ const accuracy = correctQuestions / totalQuestions;
151323
+ const calculatedXp = calculateXp(progressData.sessionDurationSeconds, accuracy, isFirstAttempt);
151324
+ log3.debug("[ProgressRecorder] Calculated XP", {
151325
+ durationSeconds: progressData.sessionDurationSeconds,
151326
+ accuracy,
151327
+ isFirstAttempt,
151328
+ calculatedXp
151329
+ });
151330
+ return calculatedXp;
151331
+ }
151332
+ return 0;
151333
+ }
150865
151334
  async getOrCreateLineItem(lineItemId, activityName, classId, ids) {
150866
151335
  try {
150867
151336
  const lineItem = await this.onerosterNamespace.assessmentLineItems.findOrCreate(lineItemId, {
150868
151337
  sourcedId: lineItemId,
150869
151338
  title: activityName,
150870
151339
  status: ONEROSTER_STATUS.active,
150871
- ...classId ? { class: { sourcedId: classId } } : { course: { sourcedId: ids.course } },
150872
- ...ids.componentResource ? { componentResource: { sourcedId: ids.componentResource } } : ids.component ? { component: { sourcedId: ids.component } } : {}
151340
+ ...classId ? { class: { sourcedId: classId } } : { course: { sourcedId: ids.course } }
150873
151341
  });
150874
151342
  if (!lineItem.sourcedId) {
150875
151343
  throw new TimebackError(`Assessment line item created but has no sourcedId. ` + `This should not happen and indicates an upstream API issue.`);
@@ -150887,39 +151355,15 @@ class ProgressRecorder {
150887
151355
  }
150888
151356
  }
150889
151357
  async determineAttemptNumber(studentId, lineItemId) {
150890
- const latestAttempt = await this.onerosterNamespace.assessmentResults.getLatestForStudent(studentId, lineItemId);
150891
- if (latestAttempt) {
150892
- const previousAttemptNumber = latestAttempt.metadata?.attemptNumber || 0;
150893
- const newAttemptNumber = previousAttemptNumber + 1;
150894
- log3.debug("[ProgressRecorder] Found previous attempt, incrementing", {
150895
- previousAttemptNumber,
150896
- newAttemptNumber,
150897
- previousScore: latestAttempt.score
150898
- });
150899
- return newAttemptNumber;
151358
+ const stats = await this.onerosterNamespace.assessmentResults.getAttemptStats(studentId, lineItemId);
151359
+ if (stats) {
151360
+ return stats.activeAttemptCount + 1;
150900
151361
  }
150901
151362
  return 1;
150902
151363
  }
150903
- calculateXpForProgress(progressData, totalQuestions, correctQuestions, xpEarned, isFirstAttempt) {
150904
- if (xpEarned !== undefined) {
150905
- log3.debug("[ProgressRecorder] Using provided XP", { xpEarned });
150906
- return xpEarned;
150907
- }
150908
- if (progressData.sessionDurationSeconds && totalQuestions && correctQuestions) {
150909
- const accuracy = correctQuestions / totalQuestions;
150910
- const calculatedXp = calculateXp(progressData.sessionDurationSeconds, accuracy, isFirstAttempt);
150911
- log3.debug("[ProgressRecorder] Calculated XP", {
150912
- durationSeconds: progressData.sessionDurationSeconds,
150913
- accuracy,
150914
- isFirstAttempt,
150915
- calculatedXp
150916
- });
150917
- return calculatedXp;
150918
- }
150919
- return 0;
150920
- }
150921
- async createGradebookEntry(lineItemId, studentId, attemptNumber, score, totalQuestions, correctQuestions, xp) {
150922
- const resultId = `${lineItemId}:${studentId}:attempt-${attemptNumber}`;
151364
+ async createGradebookEntry(lineItemId, studentId, attemptNumber, score, totalQuestions, correctQuestions, xp, masteredUnits, scoreStatus, inProgress, appName) {
151365
+ const timestamp4 = Date.now().toString(36);
151366
+ const resultId = `${lineItemId}:${studentId}:${timestamp4}`;
150923
151367
  await this.onerosterNamespace.assessmentResults.upsert(resultId, {
150924
151368
  sourcedId: resultId,
150925
151369
  status: ONEROSTER_STATUS.active,
@@ -150927,18 +151371,21 @@ class ProgressRecorder {
150927
151371
  student: { sourcedId: studentId },
150928
151372
  score,
150929
151373
  scoreDate: new Date().toISOString(),
150930
- scoreStatus: SCORE_STATUS.fullyGraded,
151374
+ scoreStatus,
151375
+ inProgress,
150931
151376
  metadata: {
150932
151377
  xp,
150933
151378
  totalQuestions,
150934
151379
  correctQuestions,
150935
151380
  accuracy: totalQuestions && correctQuestions ? correctQuestions / totalQuestions * 100 : undefined,
150936
151381
  attemptNumber,
150937
- lastUpdated: new Date().toISOString()
151382
+ lastUpdated: new Date().toISOString(),
151383
+ masteredUnits,
151384
+ appName
150938
151385
  }
150939
151386
  });
150940
151387
  }
150941
- async emitCaliperEvent(studentId, studentEmail, activityId, activityName, courseId, courseName, totalQuestions, correctQuestions, xpEarned, masteredUnits, attemptNumber, progressData) {
151388
+ async emitCaliperEvent(studentId, studentEmail, activityId, activityName, courseId, courseName, totalQuestions, correctQuestions, xpEarned, masteredUnits, attemptNumber, progressData, extensions) {
150942
151389
  await this.caliperNamespace.emitActivityEvent({
150943
151390
  studentId,
150944
151391
  studentEmail,
@@ -150953,7 +151400,8 @@ class ProgressRecorder {
150953
151400
  attemptNumber,
150954
151401
  subject: progressData.subject,
150955
151402
  appName: progressData.appName,
150956
- sensorUrl: progressData.sensorUrl
151403
+ sensorUrl: progressData.sensorUrl,
151404
+ extensions: extensions || progressData.extensions
150957
151405
  }).catch((error2) => {
150958
151406
  log3.error("[ProgressRecorder] Failed to emit activity event", { error: error2 });
150959
151407
  });
@@ -153686,9 +154134,9 @@ class ZodUnion2 extends ZodType2 {
153686
154134
  return this._def.options;
153687
154135
  }
153688
154136
  }
153689
- ZodUnion2.create = (types4, params) => {
154137
+ ZodUnion2.create = (types22, params) => {
153690
154138
  return new ZodUnion2({
153691
- options: types4,
154139
+ options: types22,
153692
154140
  typeName: ZodFirstPartyTypeKind2.ZodUnion,
153693
154141
  ...processCreateParams2(params)
153694
154142
  });
@@ -155068,9 +155516,11 @@ class TimebackClient {
155068
155516
  };
155069
155517
  this.oneroster = createOneRosterNamespace(this);
155070
155518
  this.caliper = createCaliperNamespace(this);
155519
+ this.edubridge = createEduBridgeNamespace(this);
155071
155520
  this.cacheManager = new TimebackCacheManager;
155072
155521
  this.studentResolver = new StudentResolver(this.cacheManager, this.oneroster);
155073
- this.progressRecorder = new ProgressRecorder(this.studentResolver, this.cacheManager, this.oneroster, this.caliper);
155522
+ const masteryTracker = new MasteryTracker(this.cacheManager, this.oneroster, this.edubridge);
155523
+ this.progressRecorder = new ProgressRecorder(this.studentResolver, this.cacheManager, this.oneroster, this.caliper, masteryTracker);
155074
155524
  this.sessionRecorder = new SessionRecorder(this.studentResolver, this.caliper);
155075
155525
  if (this.credentials) {
155076
155526
  this._ensureAuthenticated().catch((error2) => {
@@ -155179,6 +155629,9 @@ class TimebackClient {
155179
155629
  }
155180
155630
  await this.authenticate();
155181
155631
  }
155632
+ async resolveStudent(studentIdentifier, providedEmail) {
155633
+ return this.studentResolver.resolve(studentIdentifier, providedEmail);
155634
+ }
155182
155635
  async recordProgress(courseId, studentIdentifier, progressData) {
155183
155636
  await this._ensureAuthenticated();
155184
155637
  return this.progressRecorder.record(courseId, studentIdentifier, progressData);
@@ -155187,6 +155640,28 @@ class TimebackClient {
155187
155640
  await this._ensureAuthenticated();
155188
155641
  return this.sessionRecorder.record(courseId, studentIdentifier, sessionData);
155189
155642
  }
155643
+ async getEnrollments(studentId) {
155644
+ const cached = this.cacheManager.getEnrollments(studentId);
155645
+ if (cached) {
155646
+ return cached;
155647
+ }
155648
+ await this._ensureAuthenticated();
155649
+ const edubridgeEnrollments = await this.edubridge.enrollments.listByUser(studentId);
155650
+ const enrollments = edubridgeEnrollments.map((enrollment) => {
155651
+ const grades = enrollment.course.grades ? enrollment.course.grades.map((g5) => parseInt(g5, 10)).filter(isTimebackGrade) : null;
155652
+ const subjects = enrollment.course.subjects ? enrollment.course.subjects.filter(isTimebackSubject) : null;
155653
+ return {
155654
+ sourcedId: enrollment.id,
155655
+ title: enrollment.course.title,
155656
+ courseId: enrollment.course.id,
155657
+ status: "active",
155658
+ grades,
155659
+ subjects
155660
+ };
155661
+ });
155662
+ this.cacheManager.setEnrollments(studentId, enrollments);
155663
+ return enrollments;
155664
+ }
155190
155665
  clearCaches() {
155191
155666
  this.cacheManager.clearAll();
155192
155667
  }
@@ -155198,6 +155673,7 @@ class TimebackClient {
155198
155673
  }
155199
155674
  oneroster;
155200
155675
  caliper;
155676
+ edubridge;
155201
155677
  async setup(config2, options) {
155202
155678
  return setupTimebackResources(this, config2, options);
155203
155679
  }
@@ -155215,6 +155691,310 @@ class TimebackClient {
155215
155691
  }
155216
155692
  }
155217
155693
 
155694
+ // ../timeback/dist/types.js
155695
+ var __esm4 = (fn2, res) => () => (fn2 && (res = fn2(fn2 = 0)), res);
155696
+ var init_auth2 = () => {};
155697
+ var PLAYCADEMY_BASE_URLS2;
155698
+ var init_domains2 = __esm4(() => {
155699
+ PLAYCADEMY_BASE_URLS2 = {
155700
+ production: "https://hub.playcademy.net",
155701
+ staging: "https://hub.dev.playcademy.net"
155702
+ };
155703
+ });
155704
+ var init_env_vars2 = () => {};
155705
+ var ITEM_SLUGS3;
155706
+ var CURRENCIES3;
155707
+ var BADGES3;
155708
+ var init_overworld2 = __esm4(() => {
155709
+ ITEM_SLUGS3 = {
155710
+ PLAYCADEMY_CREDITS: "PLAYCADEMY_CREDITS",
155711
+ PLAYCADEMY_XP: "PLAYCADEMY_XP",
155712
+ FOUNDING_MEMBER_BADGE: "FOUNDING_MEMBER_BADGE",
155713
+ EARLY_ADOPTER_BADGE: "EARLY_ADOPTER_BADGE",
155714
+ FIRST_GAME_BADGE: "FIRST_GAME_BADGE",
155715
+ COMMON_SWORD: "COMMON_SWORD",
155716
+ SMALL_HEALTH_POTION: "SMALL_HEALTH_POTION",
155717
+ SMALL_BACKPACK: "SMALL_BACKPACK",
155718
+ LAVA_LAMP: "LAVA_LAMP",
155719
+ BOOMBOX: "BOOMBOX",
155720
+ CABIN_BED: "CABIN_BED"
155721
+ };
155722
+ CURRENCIES3 = {
155723
+ PRIMARY: ITEM_SLUGS3.PLAYCADEMY_CREDITS,
155724
+ XP: ITEM_SLUGS3.PLAYCADEMY_XP
155725
+ };
155726
+ BADGES3 = {
155727
+ FOUNDING_MEMBER: ITEM_SLUGS3.FOUNDING_MEMBER_BADGE,
155728
+ EARLY_ADOPTER: ITEM_SLUGS3.EARLY_ADOPTER_BADGE,
155729
+ FIRST_GAME: ITEM_SLUGS3.FIRST_GAME_BADGE
155730
+ };
155731
+ });
155732
+ var init_timeback2 = () => {};
155733
+ var init_workers2 = () => {};
155734
+ var init_src3 = __esm4(() => {
155735
+ init_auth2();
155736
+ init_domains2();
155737
+ init_env_vars2();
155738
+ init_overworld2();
155739
+ init_timeback2();
155740
+ init_workers2();
155741
+ });
155742
+ var TIMEBACK_API_URLS2;
155743
+ var TIMEBACK_AUTH_URLS2;
155744
+ var CALIPER_API_URLS2;
155745
+ var ONEROSTER_ENDPOINTS2;
155746
+ var CALIPER_ENDPOINTS2;
155747
+ var CALIPER_CONSTANTS2;
155748
+ var TIMEBACK_EVENT_TYPES2;
155749
+ var TIMEBACK_ACTIONS2;
155750
+ var TIMEBACK_TYPES2;
155751
+ var ACTIVITY_METRIC_TYPES2;
155752
+ var TIME_METRIC_TYPES2;
155753
+ var TIMEBACK_SUBJECTS2;
155754
+ var TIMEBACK_GRADE_LEVELS2;
155755
+ var TIMEBACK_GRADE_LEVEL_LABELS2;
155756
+ var CALIPER_SUBJECTS2;
155757
+ var ONEROSTER_STATUS2;
155758
+ var SCORE_STATUS2;
155759
+ var ENV_VARS2;
155760
+ var HTTP_DEFAULTS2;
155761
+ var AUTH_DEFAULTS2;
155762
+ var CACHE_DEFAULTS2;
155763
+ var CONFIG_DEFAULTS2;
155764
+ var DEFAULT_PLAYCADEMY_ORGANIZATION_ID2;
155765
+ var PLAYCADEMY_DEFAULTS2;
155766
+ var RESOURCE_DEFAULTS2;
155767
+ var HTTP_STATUS2;
155768
+ var ERROR_NAMES2;
155769
+ var init_constants2 = __esm4(() => {
155770
+ init_src3();
155771
+ TIMEBACK_API_URLS2 = {
155772
+ production: "https://api.alpha-1edtech.ai",
155773
+ staging: "https://api.staging.alpha-1edtech.com"
155774
+ };
155775
+ TIMEBACK_AUTH_URLS2 = {
155776
+ production: "https://prod-beyond-timeback-api-2-idp.auth.us-east-1.amazoncognito.com",
155777
+ staging: "https://alpha-auth-development-idp.auth.us-west-2.amazoncognito.com"
155778
+ };
155779
+ CALIPER_API_URLS2 = {
155780
+ production: "https://caliper.alpha-1edtech.ai",
155781
+ staging: "https://caliper-staging.alpha-1edtech.com"
155782
+ };
155783
+ ONEROSTER_ENDPOINTS2 = {
155784
+ organizations: "/ims/oneroster/rostering/v1p2/orgs",
155785
+ courses: "/ims/oneroster/rostering/v1p2/courses",
155786
+ courseComponents: "/ims/oneroster/rostering/v1p2/courses/components",
155787
+ resources: "/ims/oneroster/resources/v1p2/resources",
155788
+ componentResources: "/ims/oneroster/rostering/v1p2/courses/component-resources",
155789
+ classes: "/ims/oneroster/rostering/v1p2/classes",
155790
+ enrollments: "/ims/oneroster/rostering/v1p2/enrollments",
155791
+ assessmentLineItems: "/ims/oneroster/gradebook/v1p2/assessmentLineItems",
155792
+ assessmentResults: "/ims/oneroster/gradebook/v1p2/assessmentResults",
155793
+ users: "/ims/oneroster/rostering/v1p2/users"
155794
+ };
155795
+ CALIPER_ENDPOINTS2 = {
155796
+ events: "/caliper/event",
155797
+ validate: "/caliper/event/validate"
155798
+ };
155799
+ CALIPER_CONSTANTS2 = {
155800
+ context: "http://purl.imsglobal.org/ctx/caliper/v1p2",
155801
+ profile: "TimebackProfile",
155802
+ dataVersion: "http://purl.imsglobal.org/ctx/caliper/v1p2"
155803
+ };
155804
+ TIMEBACK_EVENT_TYPES2 = {
155805
+ activityEvent: "ActivityEvent",
155806
+ timeSpentEvent: "TimeSpentEvent"
155807
+ };
155808
+ TIMEBACK_ACTIONS2 = {
155809
+ completed: "Completed",
155810
+ spentTime: "SpentTime"
155811
+ };
155812
+ TIMEBACK_TYPES2 = {
155813
+ user: "TimebackUser",
155814
+ activityContext: "TimebackActivityContext",
155815
+ activityMetricsCollection: "TimebackActivityMetricsCollection",
155816
+ timeSpentMetricsCollection: "TimebackTimeSpentMetricsCollection"
155817
+ };
155818
+ ACTIVITY_METRIC_TYPES2 = {
155819
+ totalQuestions: "totalQuestions",
155820
+ correctQuestions: "correctQuestions",
155821
+ xpEarned: "xpEarned",
155822
+ masteredUnits: "masteredUnits"
155823
+ };
155824
+ TIME_METRIC_TYPES2 = {
155825
+ active: "active",
155826
+ inactive: "inactive",
155827
+ waste: "waste",
155828
+ unknown: "unknown",
155829
+ antiPattern: "anti-pattern"
155830
+ };
155831
+ TIMEBACK_SUBJECTS2 = [
155832
+ "Math",
155833
+ "FastMath",
155834
+ "Science",
155835
+ "Social Studies",
155836
+ "Language",
155837
+ "Reading",
155838
+ "Vocabulary",
155839
+ "Writing"
155840
+ ];
155841
+ TIMEBACK_GRADE_LEVELS2 = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
155842
+ TIMEBACK_GRADE_LEVEL_LABELS2 = {
155843
+ "-1": "pre-k",
155844
+ "0": "kindergarten",
155845
+ "1": "1st grade",
155846
+ "2": "2nd grade",
155847
+ "3": "3rd grade",
155848
+ "4": "4th grade",
155849
+ "5": "5th grade",
155850
+ "6": "6th grade",
155851
+ "7": "7th grade",
155852
+ "8": "8th grade",
155853
+ "9": "9th grade",
155854
+ "10": "10th grade",
155855
+ "11": "11th grade",
155856
+ "12": "12th grade",
155857
+ "13": "AP"
155858
+ };
155859
+ CALIPER_SUBJECTS2 = {
155860
+ Reading: "Reading",
155861
+ Language: "Language",
155862
+ Vocabulary: "Vocabulary",
155863
+ SocialStudies: "Social Studies",
155864
+ Writing: "Writing",
155865
+ Science: "Science",
155866
+ FastMath: "FastMath",
155867
+ Math: "Math",
155868
+ None: "None"
155869
+ };
155870
+ ONEROSTER_STATUS2 = {
155871
+ active: "active",
155872
+ toBeDeleted: "tobedeleted"
155873
+ };
155874
+ SCORE_STATUS2 = {
155875
+ exempt: "exempt",
155876
+ fullyGraded: "fully graded",
155877
+ notSubmitted: "not submitted",
155878
+ partiallyGraded: "partially graded",
155879
+ submitted: "submitted"
155880
+ };
155881
+ ENV_VARS2 = {
155882
+ clientId: "TIMEBACK_CLIENT_ID",
155883
+ clientSecret: "TIMEBACK_CLIENT_SECRET",
155884
+ baseUrl: "TIMEBACK_BASE_URL",
155885
+ environment: "TIMEBACK_ENVIRONMENT",
155886
+ vendorResourceId: "TIMEBACK_VENDOR_RESOURCE_ID",
155887
+ launchBaseUrl: "GAME_URL"
155888
+ };
155889
+ HTTP_DEFAULTS2 = {
155890
+ timeout: 30000,
155891
+ retries: 3,
155892
+ retryBackoffBase: 2
155893
+ };
155894
+ AUTH_DEFAULTS2 = {
155895
+ tokenCacheDuration: 50000
155896
+ };
155897
+ CACHE_DEFAULTS2 = {
155898
+ defaultTTL: 10 * 60 * 1000,
155899
+ defaultMaxSize: 500,
155900
+ defaultName: "TimebackCache",
155901
+ studentTTL: 10 * 60 * 1000,
155902
+ studentMaxSize: 500,
155903
+ assessmentTTL: 30 * 60 * 1000,
155904
+ assessmentMaxSize: 200,
155905
+ enrollmentTTL: 5 * 1000,
155906
+ enrollmentMaxSize: 100
155907
+ };
155908
+ CONFIG_DEFAULTS2 = {
155909
+ fileNames: ["timeback.config.js", "timeback.config.json"]
155910
+ };
155911
+ DEFAULT_PLAYCADEMY_ORGANIZATION_ID2 = process.env.TIMEBACK_ORG_SOURCE_ID || "PLAYCADEMY";
155912
+ PLAYCADEMY_DEFAULTS2 = {
155913
+ organization: DEFAULT_PLAYCADEMY_ORGANIZATION_ID2,
155914
+ launchBaseUrls: PLAYCADEMY_BASE_URLS2
155915
+ };
155916
+ RESOURCE_DEFAULTS2 = {
155917
+ organization: {
155918
+ name: "Playcademy Studios",
155919
+ type: "department"
155920
+ },
155921
+ course: {
155922
+ gradingScheme: "STANDARD",
155923
+ level: {
155924
+ elementary: "Elementary",
155925
+ middle: "Middle",
155926
+ high: "High",
155927
+ ap: "AP"
155928
+ },
155929
+ metadata: {
155930
+ goals: {
155931
+ dailyXp: 50,
155932
+ dailyLessons: 3
155933
+ },
155934
+ metrics: {
155935
+ totalXp: 1000,
155936
+ totalLessons: 50
155937
+ }
155938
+ }
155939
+ },
155940
+ component: {
155941
+ sortOrder: 1,
155942
+ prerequisiteCriteria: "ALL"
155943
+ },
155944
+ resource: {
155945
+ vendorId: "playcademy",
155946
+ roles: ["primary"],
155947
+ importance: "primary",
155948
+ metadata: {
155949
+ type: "interactive",
155950
+ toolProvider: "Playcademy",
155951
+ instructionalMethod: "exploratory",
155952
+ language: "en-US"
155953
+ }
155954
+ },
155955
+ componentResource: {
155956
+ sortOrder: 1,
155957
+ lessonType: "quiz"
155958
+ }
155959
+ };
155960
+ HTTP_STATUS2 = {
155961
+ CLIENT_ERROR_MIN: 400,
155962
+ CLIENT_ERROR_MAX: 500,
155963
+ SERVER_ERROR_MIN: 500
155964
+ };
155965
+ ERROR_NAMES2 = {
155966
+ timebackAuth: "TimebackAuthError",
155967
+ timebackApi: "TimebackApiError",
155968
+ timebackConfig: "TimebackConfigError",
155969
+ timebackSdk: "TimebackSDKError"
155970
+ };
155971
+ });
155972
+ init_constants2();
155973
+ var isObject3 = (value) => typeof value === "object" && value !== null;
155974
+ var SUBJECT_VALUES2 = TIMEBACK_SUBJECTS2;
155975
+ var GRADE_VALUES2 = TIMEBACK_GRADE_LEVELS2;
155976
+ function isCourseMetadata(value) {
155977
+ return isObject3(value);
155978
+ }
155979
+ function isResourceMetadata(value) {
155980
+ return isObject3(value);
155981
+ }
155982
+ function isPlaycademyResourceMetadata2(value) {
155983
+ if (!isObject3(value)) {
155984
+ return false;
155985
+ }
155986
+ if (!("mastery" in value) || value.mastery === undefined) {
155987
+ return true;
155988
+ }
155989
+ return isObject3(value.mastery);
155990
+ }
155991
+ function isTimebackSubject2(value) {
155992
+ return typeof value === "string" && SUBJECT_VALUES2.includes(value);
155993
+ }
155994
+ function isTimebackGrade2(value) {
155995
+ return typeof value === "number" && Number.isInteger(value) && GRADE_VALUES2.includes(value);
155996
+ }
155997
+
155218
155998
  // ../utils/src/uuid.ts
155219
155999
  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}$/;
155220
156000
  function isValidUUID(value) {
@@ -155500,6 +156280,95 @@ async function getTimebackClient() {
155500
156280
  }
155501
156281
  return timebackClient;
155502
156282
  }
156283
+ function buildResourceMetadata({
156284
+ baseMetadata,
156285
+ subject,
156286
+ grade,
156287
+ totalXp,
156288
+ masterableUnits
156289
+ }) {
156290
+ const normalizedBaseMetadata = isResourceMetadata(baseMetadata) ? baseMetadata : undefined;
156291
+ const metadata2 = {
156292
+ ...normalizedBaseMetadata || {}
156293
+ };
156294
+ metadata2.subject = subject;
156295
+ metadata2.grades = [grade];
156296
+ metadata2.xp = totalXp;
156297
+ if (masterableUnits !== undefined && masterableUnits !== null) {
156298
+ const existingPlaycademy = isPlaycademyResourceMetadata2(metadata2.playcademy) ? metadata2.playcademy : undefined;
156299
+ metadata2.playcademy = {
156300
+ ...existingPlaycademy || {},
156301
+ mastery: {
156302
+ ...existingPlaycademy?.mastery || {},
156303
+ masterableUnits
156304
+ }
156305
+ };
156306
+ }
156307
+ return metadata2;
156308
+ }
156309
+ // ../api-core/src/utils/timeback-enrollments.ts
156310
+ init_src();
156311
+ async function fetchEnrollmentsForUser(timebackId) {
156312
+ const db = getDatabase();
156313
+ const isLocal = process.env.PUBLIC_IS_LOCAL === "true";
156314
+ if (isLocal) {
156315
+ const allIntegrations = await db.query.gameTimebackIntegrations.findMany();
156316
+ return allIntegrations.map((integration) => ({
156317
+ gameId: integration.gameId,
156318
+ grade: integration.grade,
156319
+ subject: integration.subject,
156320
+ courseId: integration.courseId
156321
+ }));
156322
+ }
156323
+ log2.debug("[timeback-enrollments] Fetching student enrollments from TimeBack", { timebackId });
156324
+ try {
156325
+ const client = await getTimebackClient();
156326
+ const classes = await client.getEnrollments(timebackId);
156327
+ const courseIds = classes.map((cls) => cls.courseId).filter((id) => Boolean(id));
156328
+ if (courseIds.length === 0) {
156329
+ return [];
156330
+ }
156331
+ const integrations = await db.query.gameTimebackIntegrations.findMany({
156332
+ where: inArray(gameTimebackIntegrations.courseId, courseIds)
156333
+ });
156334
+ return integrations.map((integration) => ({
156335
+ gameId: integration.gameId,
156336
+ grade: integration.grade,
156337
+ subject: integration.subject,
156338
+ courseId: integration.courseId
156339
+ }));
156340
+ } catch (error2) {
156341
+ log2.warn("[timeback-enrollments] Failed to fetch TimeBack enrollments:", {
156342
+ error: error2,
156343
+ timebackId
156344
+ });
156345
+ return [];
156346
+ }
156347
+ }
156348
+ async function fetchUserRole(timebackId) {
156349
+ log2.debug("[timeback] Fetching user role from TimeBack", { timebackId });
156350
+ try {
156351
+ const client = await getTimebackClient();
156352
+ const user = await client.oneroster.users.get(timebackId);
156353
+ const primaryRole = user.roles.find((r2) => r2.roleType === "primary");
156354
+ const role = primaryRole?.role ?? user.roles[0]?.role ?? "student";
156355
+ log2.debug("[timeback] Resolved user role", { timebackId, role });
156356
+ return role;
156357
+ } catch (error2) {
156358
+ log2.warn("[timeback] Failed to fetch user role, defaulting to student:", {
156359
+ error: error2,
156360
+ timebackId
156361
+ });
156362
+ return "student";
156363
+ }
156364
+ }
156365
+ async function fetchUserTimebackData(timebackId) {
156366
+ const [role, enrollments] = await Promise.all([
156367
+ fetchUserRole(timebackId),
156368
+ fetchEnrollmentsForUser(timebackId)
156369
+ ]);
156370
+ return { role, enrollments };
156371
+ }
155503
156372
  // ../data/src/domains/achievement/types.ts
155504
156373
  var AchievementCompletionType;
155505
156374
  ((AchievementCompletionType2) => {
@@ -156035,6 +156904,249 @@ var AchievementService = {
156035
156904
  checkInteractionAchievement,
156036
156905
  generateAchievementMessage
156037
156906
  };
156907
+ // ../alerts/src/discord/embed.ts
156908
+ class DiscordEmbedBuilder {
156909
+ embed = {};
156910
+ setTitle(title) {
156911
+ this.embed.title = title;
156912
+ return this;
156913
+ }
156914
+ setDescription(description) {
156915
+ this.embed.description = description;
156916
+ return this;
156917
+ }
156918
+ setUrl(url) {
156919
+ this.embed.url = url;
156920
+ return this;
156921
+ }
156922
+ setColor(color) {
156923
+ this.embed.color = color;
156924
+ return this;
156925
+ }
156926
+ setTimestamp(date4) {
156927
+ this.embed.timestamp = (date4 || new Date).toISOString();
156928
+ return this;
156929
+ }
156930
+ setAuthor(name4, options) {
156931
+ this.embed.author = {
156932
+ name: name4,
156933
+ url: options?.url,
156934
+ icon_url: options?.iconUrl
156935
+ };
156936
+ return this;
156937
+ }
156938
+ setFooter(text5, iconUrl) {
156939
+ this.embed.footer = {
156940
+ text: text5,
156941
+ icon_url: iconUrl
156942
+ };
156943
+ return this;
156944
+ }
156945
+ setThumbnail(url) {
156946
+ this.embed.thumbnail = { url };
156947
+ return this;
156948
+ }
156949
+ setImage(url) {
156950
+ this.embed.image = { url };
156951
+ return this;
156952
+ }
156953
+ addField(name4, value, inline = false) {
156954
+ if (!this.embed.fields) {
156955
+ this.embed.fields = [];
156956
+ }
156957
+ this.embed.fields.push({ name: name4, value, inline });
156958
+ return this;
156959
+ }
156960
+ addFields(...fields) {
156961
+ for (const field of fields) {
156962
+ this.addField(field.name, field.value, field.inline);
156963
+ }
156964
+ return this;
156965
+ }
156966
+ build() {
156967
+ return this.embed;
156968
+ }
156969
+ static create() {
156970
+ return new DiscordEmbedBuilder;
156971
+ }
156972
+ }
156973
+
156974
+ // ../alerts/src/discord/client.ts
156975
+ class DiscordClient {
156976
+ config;
156977
+ constructor(config2) {
156978
+ if (!config2.url) {
156979
+ throw new Error("Discord webhook URL is required");
156980
+ }
156981
+ if (!config2.url.startsWith("https://discord.com/api/webhooks/")) {
156982
+ throw new Error("Invalid Discord webhook URL format");
156983
+ }
156984
+ this.config = config2;
156985
+ }
156986
+ async send(message2) {
156987
+ const payload = {
156988
+ content: message2,
156989
+ username: this.config.username,
156990
+ avatar_url: this.config.avatarUrl
156991
+ };
156992
+ await this.sendPayload(payload);
156993
+ }
156994
+ async sendEmbed(embed) {
156995
+ const payload = {
156996
+ embeds: [embed],
156997
+ username: this.config.username,
156998
+ avatar_url: this.config.avatarUrl
156999
+ };
157000
+ await this.sendPayload(payload);
157001
+ }
157002
+ async sendEmbeds(embeds) {
157003
+ if (embeds.length > 10) {
157004
+ throw new Error("Discord allows maximum 10 embeds per message");
157005
+ }
157006
+ const payload = {
157007
+ embeds,
157008
+ username: this.config.username,
157009
+ avatar_url: this.config.avatarUrl
157010
+ };
157011
+ await this.sendPayload(payload);
157012
+ }
157013
+ async sendWithEmbeds(message2, embeds) {
157014
+ const payload = {
157015
+ content: message2,
157016
+ embeds,
157017
+ username: this.config.username,
157018
+ avatar_url: this.config.avatarUrl
157019
+ };
157020
+ await this.sendPayload(payload);
157021
+ }
157022
+ async sendRich(message2) {
157023
+ const builder = new DiscordEmbedBuilder;
157024
+ if (message2.title)
157025
+ builder.setTitle(message2.title);
157026
+ if (message2.description)
157027
+ builder.setDescription(message2.description);
157028
+ if (message2.url)
157029
+ builder.setUrl(message2.url);
157030
+ if (message2.color) {
157031
+ const color = typeof message2.color === "string" ? parseInt(message2.color.replace("#", ""), 16) : message2.color;
157032
+ builder.setColor(color);
157033
+ }
157034
+ if (message2.author) {
157035
+ builder.setAuthor(message2.author.name, {
157036
+ url: message2.author.url,
157037
+ iconUrl: message2.author.iconUrl
157038
+ });
157039
+ }
157040
+ if (message2.fields) {
157041
+ for (const field of message2.fields) {
157042
+ builder.addField(field.name, field.value, field.inline);
157043
+ }
157044
+ }
157045
+ if (message2.footer) {
157046
+ builder.setFooter(message2.footer.text, message2.footer.iconUrl);
157047
+ }
157048
+ if (message2.timestamp) {
157049
+ const date4 = message2.timestamp instanceof Date ? message2.timestamp : new Date(message2.timestamp);
157050
+ builder.setTimestamp(date4);
157051
+ }
157052
+ if (message2.thumbnail) {
157053
+ builder.setThumbnail(message2.thumbnail.url);
157054
+ }
157055
+ if (message2.image) {
157056
+ builder.setImage(message2.image.url);
157057
+ }
157058
+ await this.sendEmbed(builder.build());
157059
+ }
157060
+ async sendPayload(payload) {
157061
+ const response = await fetch(this.config.url, {
157062
+ method: "POST",
157063
+ headers: {
157064
+ "Content-Type": "application/json"
157065
+ },
157066
+ body: JSON.stringify(payload)
157067
+ });
157068
+ if (!response.ok) {
157069
+ const errorText = await response.text().catch(() => "Unknown error");
157070
+ throw new Error(`Discord webhook request failed: ${response.status} ${response.statusText} - ${errorText}`);
157071
+ }
157072
+ }
157073
+ getRedactedUrl() {
157074
+ const parts2 = this.config.url.split("/");
157075
+ const token = parts2[parts2.length - 1];
157076
+ return `${token?.slice(0, 9)}...`;
157077
+ }
157078
+ }
157079
+ // ../alerts/src/discord/types.ts
157080
+ var DiscordColors = {
157081
+ DEFAULT: 0,
157082
+ WHITE: 16777215,
157083
+ AQUA: 1752220,
157084
+ GREEN: 5763719,
157085
+ BLUE: 3447003,
157086
+ YELLOW: 16705372,
157087
+ PURPLE: 10181046,
157088
+ LUMINOUS_VIVID_PINK: 15277667,
157089
+ FUCHSIA: 15418782,
157090
+ GOLD: 15844367,
157091
+ ORANGE: 15105570,
157092
+ RED: 15548997,
157093
+ GREY: 9807270,
157094
+ NAVY: 3426654,
157095
+ DARK_AQUA: 1146986,
157096
+ DARK_GREEN: 2067276,
157097
+ DARK_BLUE: 2123412,
157098
+ DARK_PURPLE: 7419530,
157099
+ DARK_VIVID_PINK: 11342935,
157100
+ DARK_GOLD: 12745742,
157101
+ DARK_ORANGE: 11027200,
157102
+ DARK_RED: 10038562,
157103
+ DARK_GREY: 9936031,
157104
+ DARKER_GREY: 8359053,
157105
+ LIGHT_GREY: 12370112,
157106
+ DARK_NAVY: 2899536,
157107
+ BLURPLE: 5793266,
157108
+ GREYPLE: 10070709,
157109
+ DARK_BUT_NOT_BLACK: 2895667,
157110
+ NOT_QUITE_BLACK: 2303786
157111
+ };
157112
+ // ../api-core/src/utils/alerts.ts
157113
+ init_src();
157114
+ function getDiscordClient() {
157115
+ const webhookUrl = process.env.DISCORD_WEBHOOK_PLATFORM;
157116
+ if (!webhookUrl) {
157117
+ log2.debug("[API] Discord webhook not configured, skipping notification");
157118
+ return null;
157119
+ }
157120
+ return new DiscordClient({
157121
+ url: webhookUrl,
157122
+ username: "Playcademy Platform",
157123
+ avatarUrl: process.env.DISCORD_WEBHOOK_AVATAR_URL
157124
+ });
157125
+ }
157126
+ function getEnvironment() {
157127
+ const stage = process.env.SST_STAGE;
157128
+ return stage === "production" ? "production" : stage === "dev" ? "staging" : "test";
157129
+ }
157130
+ async function sendDeveloperApplicationAlert(user) {
157131
+ const discord = getDiscordClient();
157132
+ if (!discord)
157133
+ return;
157134
+ 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();
157135
+ await discord.sendEmbed(embed);
157136
+ log2.debug("[API] Discord notification sent successfully", {
157137
+ userId: user.id
157138
+ });
157139
+ }
157140
+ async function sendGameDeletionAlert(game) {
157141
+ const discord = getDiscordClient();
157142
+ if (!discord)
157143
+ return;
157144
+ 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();
157145
+ await discord.sendEmbed(embed);
157146
+ log2.debug("[API] Discord game deletion notification sent", {
157147
+ slug: game.slug
157148
+ });
157149
+ }
156038
157150
  // ../../node_modules/aws-jwt-verify/dist/esm/error.js
156039
157151
  class JwtBaseError extends Error {
156040
157152
  }
@@ -157172,10 +158284,8 @@ async function publishPersonalBestNotification(userId, gameId, rank, newScore, p
157172
158284
  });
157173
158285
  }
157174
158286
  // ../cloudflare/src/playcademy/provider.ts
157175
- import { execSync } from "node:child_process";
157176
- import { mkdir, rm, writeFile } from "node:fs/promises";
157177
- import { tmpdir } from "node:os";
157178
- import { join as join5 } from "node:path";
158287
+ import { readdir as readdir2, readFile as readFile2, stat } from "node:fs/promises";
158288
+ import { join as join4, relative } from "node:path";
157179
158289
  init_src();
157180
158290
 
157181
158291
  // ../cloudflare/src/core/client.ts
@@ -158004,7 +159114,6 @@ function normalizeDeploymentOptions(options) {
158004
159114
  assets: options?.bindings?.assets ?? false
158005
159115
  },
158006
159116
  assetsPath: options?.assetsPath,
158007
- assetsZip: options?.assetsZip,
158008
159117
  keepAssets: options?.keepAssets
158009
159118
  };
158010
159119
  }
@@ -158054,41 +159163,6 @@ var DEFAULT_COMPATIBILITY_DATE2 = new Date().toISOString().slice(0, 10);
158054
159163
  var GAME_WORKER_DOMAIN_PRODUCTION = GAME_WORKER_DOMAINS.production;
158055
159164
  var GAME_WORKER_DOMAIN_STAGING = GAME_WORKER_DOMAINS.staging;
158056
159165
 
158057
- // ../cloudflare/src/playcademy/workers/index.ts
158058
- import { dirname as dirname2, join as join4 } from "node:path";
158059
- import { fileURLToPath } from "node:url";
158060
-
158061
- // ../cloudflare/src/utils/bundler.ts
158062
- var esbuild = __toESM(require_main(), 1);
158063
- async function bundleWorker(filePath) {
158064
- const result = await esbuild.build({
158065
- entryPoints: [filePath],
158066
- bundle: true,
158067
- format: "esm",
158068
- platform: "browser",
158069
- target: "es2022",
158070
- write: false,
158071
- minify: false,
158072
- sourcemap: false,
158073
- logLevel: "error"
158074
- });
158075
- if (!result.outputFiles?.[0]) {
158076
- throw new Error(`No output generated for worker ${filePath}`);
158077
- }
158078
- return result.outputFiles[0].text;
158079
- }
158080
- // ../cloudflare/src/playcademy/workers/index.ts
158081
- var _cachedStubWorker = null;
158082
- async function getStubWorkerCode() {
158083
- if (_cachedStubWorker) {
158084
- return _cachedStubWorker;
158085
- }
158086
- const currentDir = dirname2(fileURLToPath(import.meta.url));
158087
- const workerPath = join4(currentDir, "stub-worker.ts");
158088
- _cachedStubWorker = await bundleWorker(workerPath);
158089
- return _cachedStubWorker;
158090
- }
158091
-
158092
159166
  // ../cloudflare/src/playcademy/provider.ts
158093
159167
  class CloudflareProvider {
158094
159168
  client;
@@ -158126,41 +159200,75 @@ class CloudflareProvider {
158126
159200
  getClient() {
158127
159201
  return this.client;
158128
159202
  }
159203
+ async checkIfRequiresR2Strategy(assetsPath) {
159204
+ async function scanDirectory(dir) {
159205
+ const entries = await readdir2(dir, { withFileTypes: true });
159206
+ for (const entry of entries) {
159207
+ const fullPath = join4(dir, entry.name);
159208
+ if (entry.isDirectory()) {
159209
+ if (await scanDirectory(fullPath))
159210
+ return true;
159211
+ } else {
159212
+ const stats = await stat(fullPath);
159213
+ if (stats.size >= 25 * 1024 * 1024) {
159214
+ log2.info("[CloudflareProvider] Large file detected, R2 strategy required", {
159215
+ file: entry.name,
159216
+ size: `${(stats.size / 1024 / 1024).toFixed(2)}MB`
159217
+ });
159218
+ return true;
159219
+ }
159220
+ }
159221
+ }
159222
+ return false;
159223
+ }
159224
+ return await scanDirectory(assetsPath);
159225
+ }
159226
+ async resolveAssetBasePath(dirPath) {
159227
+ const entries = await readdir2(dirPath, { withFileTypes: true });
159228
+ if (entries.length === 1 && entries[0]?.isDirectory()) {
159229
+ const unwrappedPath = join4(dirPath, entries[0].name);
159230
+ log2.debug("[CloudflareProvider] Unwrapping wrapper directory", {
159231
+ wrapper: entries[0].name
159232
+ });
159233
+ return await this.resolveAssetBasePath(unwrappedPath);
159234
+ }
159235
+ return dirPath;
159236
+ }
159237
+ async uploadFilesToR2(dir, baseDir, bucketName) {
159238
+ const entries = await readdir2(dir, { withFileTypes: true });
159239
+ for (const entry of entries) {
159240
+ const fullPath = join4(dir, entry.name);
159241
+ if (entry.isDirectory()) {
159242
+ await this.uploadFilesToR2(fullPath, baseDir, bucketName);
159243
+ } else {
159244
+ const content = await readFile2(fullPath);
159245
+ const relativePath = relative(baseDir, fullPath).replace(/\\/g, "/");
159246
+ const contentType = getContentType(relativePath);
159247
+ await this.client.r2.putObject(bucketName, relativePath, content, contentType);
159248
+ }
159249
+ }
159250
+ }
159251
+ async uploadDirectoryToR2(dirPath, bucketName) {
159252
+ const basePath = await this.resolveAssetBasePath(dirPath);
159253
+ log2.debug("[CloudflareProvider] Uploading assets to R2", {
159254
+ bucketName,
159255
+ basePath,
159256
+ filesFrom: relative(dirPath, basePath) || "(root)"
159257
+ });
159258
+ await this.uploadFilesToR2(basePath, basePath, bucketName);
159259
+ }
158129
159260
  async deploy(deploymentId, code, env2, options) {
158130
159261
  const opts = normalizeDeploymentOptions(options);
158131
159262
  const isFirstDeploy = env2.PLAYCADEMY_API_KEY !== undefined;
158132
- const hasAssets = !!(opts.assetsPath || opts.assetsZip);
158133
- let finalCode = code ?? "";
159263
+ const hasAssets = !!opts.assetsPath;
158134
159264
  if (!code) {
158135
- try {
158136
- log2.debug("[CloudflareProvider] Fetching existing script", {
158137
- deploymentId,
158138
- namespace: this.config.dispatchNamespace
158139
- });
158140
- finalCode = await this.client.workers.getScriptContent(this.config.dispatchNamespace, deploymentId);
158141
- } catch {
158142
- log2.debug("[CloudflareProvider] No existing script, using stub worker", {
158143
- deploymentId
158144
- });
158145
- finalCode = await getStubWorkerCode();
158146
- }
159265
+ throw new Error(`No worker code provided for deployment. ` + `Frontend-only deployments should include a stub worker from the CLI.`);
158147
159266
  }
159267
+ const finalCode = code;
158148
159268
  if (opts.keepAssets === undefined) {
158149
159269
  opts.keepAssets = true;
158150
159270
  }
158151
- let tempDir = null;
158152
- let finalAssetsPath = opts.assetsPath;
158153
- if (opts.assetsZip && !opts.assetsPath) {
158154
- tempDir = join5(tmpdir(), `playcademy-assets-${Date.now()}`);
158155
- finalAssetsPath = join5(tempDir, "dist");
158156
- await mkdir(finalAssetsPath, { recursive: true });
158157
- await writeFile(join5(tempDir, "temp.zip"), opts.assetsZip);
158158
- execSync(`unzip -q ${join5(tempDir, "temp.zip")} -d ${finalAssetsPath}`);
158159
- log2.debug("[CloudflareProvider] Extracted ZIP to temp directory", {
158160
- tempDir,
158161
- size: opts.assetsZip.length
158162
- });
158163
- }
159271
+ const finalAssetsPath = opts.assetsPath;
158164
159272
  log2.info("[CloudflareProvider] Deploying worker to dispatch namespace", {
158165
159273
  deploymentId,
158166
159274
  namespace: this.config.dispatchNamespace,
@@ -158209,16 +159317,45 @@ class CloudflareProvider {
158209
159317
  warnDurableObjectsNotImplemented(opts.bindings.durableObjects);
158210
159318
  addResourceBindings(bindings, resourceIds);
158211
159319
  let assetsJWT;
159320
+ let assetsStrategy;
159321
+ let assetsR2Bucket;
158212
159322
  if (finalAssetsPath) {
158213
- log2.debug("[CloudflareProvider] Uploading assets", {
158214
- deploymentId,
158215
- finalAssetsPath
158216
- });
158217
- assetsJWT = await this.client.workers.uploadAssets(this.config.dispatchNamespace, deploymentId, { type: "directory", path: finalAssetsPath });
158218
- }
158219
- if (finalAssetsPath || assetsJWT || opts.keepAssets) {
159323
+ const requiresR2 = await this.checkIfRequiresR2Strategy(finalAssetsPath);
159324
+ if (requiresR2) {
159325
+ assetsStrategy = "r2";
159326
+ assetsR2Bucket = `${deploymentId}-assets`;
159327
+ log2.info("[CloudflareProvider] Using R2 strategy for large files", {
159328
+ deploymentId,
159329
+ bucketName: assetsR2Bucket
159330
+ });
159331
+ await this.client.r2.create(assetsR2Bucket);
159332
+ await this.uploadDirectoryToR2(finalAssetsPath, assetsR2Bucket);
159333
+ bindings.push({
159334
+ type: "r2_bucket",
159335
+ name: "ASSETS",
159336
+ bucket_name: assetsR2Bucket
159337
+ });
159338
+ if (!resources.r2)
159339
+ resources.r2 = [];
159340
+ resources.r2.push({ name: assetsR2Bucket });
159341
+ } else {
159342
+ assetsStrategy = "workers-assets";
159343
+ log2.debug("[CloudflareProvider] Using Workers Assets strategy", {
159344
+ deploymentId,
159345
+ finalAssetsPath
159346
+ });
159347
+ assetsJWT = await this.client.workers.uploadAssets(this.config.dispatchNamespace, deploymentId, { type: "directory", path: finalAssetsPath });
159348
+ bindings.push({ type: "assets", name: "ASSETS" });
159349
+ }
159350
+ } else if (opts.keepAssets) {
158220
159351
  bindings.push({ type: "assets", name: "ASSETS" });
158221
159352
  }
159353
+ if (assetsStrategy) {
159354
+ resources.assetsStrategy = assetsStrategy;
159355
+ if (assetsR2Bucket) {
159356
+ resources.assetsR2Bucket = assetsR2Bucket;
159357
+ }
159358
+ }
158222
159359
  const keepBindingsValue = !isFirstDeploy ? ["secret_text"] : [];
158223
159360
  log2.info("[Cloudflare Provider] Deployment configuration", {
158224
159361
  deploymentId,
@@ -158260,10 +159397,6 @@ class CloudflareProvider {
158260
159397
  error: error2
158261
159398
  });
158262
159399
  throw new Error(`Failed to deploy to Cloudflare Workers for Platforms: ${error2 instanceof Error ? error2.message : String(error2)}`);
158263
- } finally {
158264
- if (tempDir) {
158265
- await rm(tempDir, { recursive: true, force: true }).catch(() => {});
158266
- }
158267
159400
  }
158268
159401
  }
158269
159402
  async delete(deploymentId, options) {
@@ -158299,7 +159432,8 @@ class CloudflareProvider {
158299
159432
  await Promise.all([
158300
159433
  this.client.d1.delete(deploymentId).catch(() => {}),
158301
159434
  this.client.kv.delete(deploymentId).catch(() => {}),
158302
- this.client.r2.delete(deploymentId).catch(() => {})
159435
+ this.client.r2.delete(deploymentId).catch(() => {}),
159436
+ this.client.r2.delete(`${deploymentId}-assets`).catch(() => {})
158303
159437
  ]);
158304
159438
  }
158305
159439
  async deleteCustomDomains(customDomains, gameSlug) {
@@ -158342,6 +159476,8 @@ class CloudflareProvider {
158342
159476
  });
158343
159477
  }
158344
159478
  }
159479
+ // ../cloudflare/src/utils/bundler.ts
159480
+ var esbuild = __toESM(require_main(), 1);
158345
159481
  // ../api-core/src/utils/cloudflare.ts
158346
159482
  init_src();
158347
159483
  var cloudflareProvider = null;
@@ -158638,20 +159774,6 @@ async function seedCoreGames(db) {
158638
159774
  console.error(`Error seeding core game '${gameData.slug}':`, error2);
158639
159775
  }
158640
159776
  }
158641
- if (config.timeback.courseId && hasTimebackCredentials()) {
158642
- try {
158643
- await db.insert(gameTimebackIntegrations).values({
158644
- id: crypto.randomUUID(),
158645
- gameId: CORE_GAME_UUIDS.PLAYGROUND,
158646
- courseId: config.timeback.courseId,
158647
- lastVerifiedAt: null,
158648
- createdAt: now2,
158649
- updatedAt: now2
158650
- }).onConflictDoNothing();
158651
- } catch (error2) {
158652
- console.error("Error seeding TimeBack integration for playground:", error2);
158653
- }
158654
- }
158655
159777
  }
158656
159778
  async function seedCurrentProjectGame(db, project) {
158657
159779
  const now2 = new Date;
@@ -158680,19 +159802,6 @@ async function seedCurrentProjectGame(db, project) {
158680
159802
  updatedAt: now2
158681
159803
  };
158682
159804
  const [newGame] = await db.insert(games).values(gameRecord).returning();
158683
- if (config.timeback.courseId && hasTimebackCredentials()) {
158684
- await db.insert(gameTimebackIntegrations).values({
158685
- id: crypto.randomUUID(),
158686
- gameId: newGame.id,
158687
- courseId: config.timeback.courseId,
158688
- lastVerifiedAt: null,
158689
- createdAt: now2,
158690
- updatedAt: now2
158691
- }).onConflictDoNothing();
158692
- } else if (hasTimebackCredentials() && !config.timeback.courseId) {
158693
- logger3.info(" ⚠ TimeBack credentials found but SANDBOX_TIMEBACK_COURSE_ID not set");
158694
- logger3.info(" Set SANDBOX_TIMEBACK_COURSE_ID to your real TimeBack course ID");
158695
- }
158696
159805
  return newGame;
158697
159806
  } catch (error2) {
158698
159807
  console.error("❌ Error seeding project game:", error2);
@@ -158754,13 +159863,26 @@ async function seedSpriteTemplates(db) {
158754
159863
  }
158755
159864
  }
158756
159865
 
159866
+ // src/database/seed/timeback.ts
159867
+ function resolveStudentId(studentId) {
159868
+ if (!studentId)
159869
+ return null;
159870
+ if (studentId === "mock")
159871
+ return `mock-student-${crypto.randomUUID().slice(0, 8)}`;
159872
+ return studentId;
159873
+ }
159874
+ function getAdminTimebackId() {
159875
+ return resolveStudentId(config.timeback.studentId);
159876
+ }
159877
+
158757
159878
  // src/database/seed/index.ts
158758
159879
  async function seedDemoData(db) {
158759
159880
  try {
159881
+ const adminTimebackId = getAdminTimebackId();
158760
159882
  for (const [role, user] of Object.entries(DEMO_USERS)) {
158761
159883
  const userValues = {
158762
159884
  ...user,
158763
- timebackId: role === "admin" && config.timeback.studentId ? config.timeback.studentId : null
159885
+ timebackId: role === "admin" ? adminTimebackId : null
158764
159886
  };
158765
159887
  await db.insert(users).values(userValues).onConflictDoNothing();
158766
159888
  }
@@ -158794,7 +159916,7 @@ async function checkIfNeedsSeeding(db) {
158794
159916
  }
158795
159917
  }
158796
159918
  async function setupServerDatabase(processedOptions, project) {
158797
- const { memoryOnly, recreateDb, databasePath, seed, verbose, quiet } = processedOptions;
159919
+ const { memoryOnly, recreateDb, databasePath, seed, quiet } = processedOptions;
158798
159920
  const effectiveDbPath = databasePath ?? process.env.PLAYCADEMY_SANDBOX_DB_PATH;
158799
159921
  const resolvedDbPath = memoryOnly || effectiveDbPath === ":memory:" ? ":memory:" : effectiveDbPath ? DatabasePathManager.resolveDatabasePath(effectiveDbPath) : DatabasePathManager.resolveDatabasePath();
158800
159922
  if (!memoryOnly && recreateDb && fs5.existsSync(resolvedDbPath)) {
@@ -158805,9 +159927,6 @@ async function setupServerDatabase(processedOptions, project) {
158805
159927
  }
158806
159928
  }
158807
159929
  const isNewDb = resolvedDbPath === ":memory:" ? true : !fs5.existsSync(resolvedDbPath);
158808
- if (verbose && !quiet && resolvedDbPath !== ":memory:") {
158809
- console.log(`[Sandbox] Database: ${resolvedDbPath}`);
158810
- }
158811
159930
  const db = await setupDatabase(resolvedDbPath);
158812
159931
  const shouldSeed = seed && (isNewDb || await checkIfNeedsSeeding(db));
158813
159932
  if (shouldSeed) {
@@ -159038,6 +160157,12 @@ async function applyForDeveloperStatus(ctx) {
159038
160157
  userId: user.id,
159039
160158
  newStatus: "pending"
159040
160159
  });
160160
+ sendDeveloperApplicationAlert(fullUser).catch((error2) => {
160161
+ log2.error("[API] Failed to send Discord notification for developer application", {
160162
+ userId: user.id,
160163
+ error: error2 instanceof Error ? error2.message : String(error2)
160164
+ });
160165
+ });
159041
160166
  } catch (error2) {
159042
160167
  log2.error(`Error updating developer status for user ${user.id}:`, {
159043
160168
  error: error2
@@ -163778,6 +164903,68 @@ var NotificationStatsSchema = exports_external2.object({
163778
164903
  startDate: exports_external2.date().optional(),
163779
164904
  endDate: exports_external2.date().optional()
163780
164905
  });
164906
+ // ../data/src/domains/timeback/schemas.ts
164907
+ var InsertTimebackDailyXpSchema = createInsertSchema(timebackDailyXp, {
164908
+ xp: exports_external2.number().min(0, "XP must be a non-negative number"),
164909
+ date: exports_external2.date()
164910
+ }).omit({ createdAt: true, updatedAt: true });
164911
+ var SelectTimebackDailyXpSchema = createSelectSchema(timebackDailyXp);
164912
+ var UpdateTimebackDailyXpSchema = createUpdateSchema(timebackDailyXp, {
164913
+ xp: exports_external2.number().min(0, "XP must be a non-negative number")
164914
+ }).omit({ userId: true, createdAt: true });
164915
+ var UpdateTimebackXpRequestSchema = exports_external2.object({
164916
+ xp: exports_external2.number().min(0, "XP must be a non-negative number"),
164917
+ userTimestamp: exports_external2.string().datetime().optional()
164918
+ });
164919
+ var TimebackDateQuerySchema = exports_external2.object({
164920
+ date: exports_external2.string().datetime().optional()
164921
+ });
164922
+ var TimebackHistoryQuerySchema = exports_external2.object({
164923
+ startDate: exports_external2.string().date().optional(),
164924
+ endDate: exports_external2.string().date().optional()
164925
+ });
164926
+ var InsertTimebackXpEventSchema = createInsertSchema(timebackXpEvents, {
164927
+ occurredAt: exports_external2.date(),
164928
+ xpDelta: exports_external2.number()
164929
+ }).omit({ createdAt: true, updatedAt: true });
164930
+ var SelectTimebackXpEventSchema = createSelectSchema(timebackXpEvents);
164931
+ var InsertGameTimebackIntegrationSchema = createInsertSchema(gameTimebackIntegrations, {
164932
+ gameId: exports_external2.string().uuid(),
164933
+ courseId: exports_external2.string().min(1),
164934
+ grade: exports_external2.number().int(),
164935
+ subject: exports_external2.string().min(1),
164936
+ totalXp: exports_external2.number().int().positive().optional()
164937
+ }).omit({ id: true, createdAt: true, updatedAt: true });
164938
+ var SelectGameTimebackIntegrationSchema = createSelectSchema(gameTimebackIntegrations);
164939
+ var UpdateGameTimebackIntegrationSchema = createUpdateSchema(gameTimebackIntegrations, {
164940
+ courseId: exports_external2.string().min(1).optional(),
164941
+ totalXp: exports_external2.number().int().positive().optional(),
164942
+ lastVerifiedAt: exports_external2.date().optional()
164943
+ }).omit({ id: true, gameId: true, grade: true, subject: true, createdAt: true });
164944
+ var EndActivityRequestSchema = exports_external2.object({
164945
+ gameId: exports_external2.string().uuid(),
164946
+ studentId: exports_external2.string().min(1),
164947
+ activityData: exports_external2.object({
164948
+ activityId: exports_external2.string().min(1),
164949
+ activityName: exports_external2.string().optional(),
164950
+ grade: exports_external2.number().int(),
164951
+ subject: exports_external2.string().min(1),
164952
+ appName: exports_external2.string().optional(),
164953
+ sensorUrl: exports_external2.string().url().optional(),
164954
+ courseId: exports_external2.string().optional(),
164955
+ courseName: exports_external2.string().optional(),
164956
+ studentEmail: exports_external2.string().email().optional()
164957
+ }),
164958
+ scoreData: exports_external2.object({
164959
+ correctQuestions: exports_external2.number().int().min(0),
164960
+ totalQuestions: exports_external2.number().int().min(0)
164961
+ }),
164962
+ timingData: exports_external2.object({
164963
+ durationSeconds: exports_external2.number().positive()
164964
+ }),
164965
+ xpEarned: exports_external2.number().optional(),
164966
+ masteredUnits: exports_external2.number().nonnegative().optional()
164967
+ });
163781
164968
  // ../api-core/src/handlers/games/leaderboard.ts
163782
164969
  init_src();
163783
164970
  async function submitScore(ctx) {
@@ -163946,6 +165133,26 @@ async function getUserAllScores(ctx) {
163946
165133
  throw ApiError.internal("Failed to fetch user scores");
163947
165134
  }
163948
165135
  }
165136
+ async function getUserScores(ctx) {
165137
+ const user = ctx.user;
165138
+ if (!user) {
165139
+ throw ApiError.unauthorized("Must be logged in to view user scores");
165140
+ }
165141
+ const { params, url } = ctx;
165142
+ const gameId = params.gameId;
165143
+ const userId = params.userId;
165144
+ if (!gameId || !userId) {
165145
+ throw ApiError.badRequest("Game ID and User ID are required");
165146
+ }
165147
+ const limit2 = Math.min(Number(url.searchParams.get("limit") || "10"), 100);
165148
+ const db = getDatabase();
165149
+ try {
165150
+ const scores = await db.select().from(gameScores).where(and(eq(gameScores.gameId, gameId), eq(gameScores.userId, userId))).orderBy(desc(gameScores.achievedAt)).limit(limit2);
165151
+ return scores;
165152
+ } catch {
165153
+ throw ApiError.internal("Failed to fetch user scores");
165154
+ }
165155
+ }
163949
165156
 
163950
165157
  // ../api-core/src/handlers/levels/index.ts
163951
165158
  init_src();
@@ -164082,9 +165289,11 @@ async function getUserMe(ctx) {
164082
165289
  const timebackAccount = await db.query.accounts.findFirst({
164083
165290
  where: and(eq(accounts.userId, user.id), eq(accounts.providerId, "timeback"))
164084
165291
  });
165292
+ const timeback3 = userData.timebackId ? await fetchUserTimebackData(userData.timebackId) : undefined;
164085
165293
  return {
164086
165294
  ...userData,
164087
- hasTimebackAccount: !!timebackAccount
165295
+ hasTimebackAccount: !!timebackAccount,
165296
+ timeback: timeback3
164088
165297
  };
164089
165298
  } catch (error2) {
164090
165299
  if (error2 instanceof ApiError)
@@ -164614,7 +165823,7 @@ async function deleteGame(ctx) {
164614
165823
  const db = getDatabase();
164615
165824
  const gameToDelete = await db.query.games.findFirst({
164616
165825
  where: eq(games.id, gameId),
164617
- columns: { id: true, slug: true }
165826
+ columns: { id: true, slug: true, displayName: true }
164618
165827
  });
164619
165828
  if (!gameToDelete || !gameToDelete.slug) {
164620
165829
  throw ApiError.notFound("Game not found for deletion");
@@ -164636,6 +165845,16 @@ async function deleteGame(ctx) {
164636
165845
  hadActiveDeployment: !!activeDeployment,
164637
165846
  customDomainsCount: customDomains.length
164638
165847
  });
165848
+ sendGameDeletionAlert({
165849
+ slug: gameToDelete.slug,
165850
+ displayName: gameToDelete.displayName,
165851
+ developer: {
165852
+ id: user.id,
165853
+ email: user.email
165854
+ }
165855
+ }).catch((err2) => {
165856
+ log2.warn("[API] Failed to send Discord game deletion notification", { err: err2 });
165857
+ });
164639
165858
  if (activeDeployment?.provider === "cloudflare") {
164640
165859
  try {
164641
165860
  const cloudflare2 = getCloudflareProvider();
@@ -166217,6 +167436,26 @@ gameScoresRouter.post("/:gameId/scores", async (c3) => {
166217
167436
  return c3.json(createUnknownErrorResponse(error2), 500);
166218
167437
  }
166219
167438
  });
167439
+ gameScoresRouter.get("/:gameId/users/:userId/scores", async (c3) => {
167440
+ const gameId = c3.req.param("gameId");
167441
+ const userId = c3.req.param("userId");
167442
+ const ctx = {
167443
+ user: c3.get("user"),
167444
+ params: { gameId, userId },
167445
+ url: new URL(c3.req.url),
167446
+ request: c3.req.raw
167447
+ };
167448
+ try {
167449
+ const result = await getUserScores(ctx);
167450
+ return c3.json(result, 200);
167451
+ } catch (error2) {
167452
+ if (error2 instanceof ApiError) {
167453
+ return c3.json(createErrorResponse(error2), error2.statusCode);
167454
+ }
167455
+ console.error("Error in getUserScores:", error2);
167456
+ return c3.json(createUnknownErrorResponse(error2), 500);
167457
+ }
167458
+ });
166220
167459
 
166221
167460
  // ../api-core/src/handlers/games/sessions.ts
166222
167461
  init_src();
@@ -168788,8 +170027,8 @@ async function getNotificationStats(ctx) {
168788
170027
  status: notifications.status,
168789
170028
  count: sql`count(*)`
168790
170029
  }).from(notifications).where(and(...conditions)).groupBy(notifications.status);
168791
- const statsMap = stats.reduce((acc, stat) => {
168792
- acc[stat.status] = Number(stat.count);
170030
+ const statsMap = stats.reduce((acc, stat2) => {
170031
+ acc[stat2.status] = Number(stat2.count);
168793
170032
  return acc;
168794
170033
  }, {});
168795
170034
  const total = Object.values(statsMap).reduce((sum3, count2) => sum3 + count2, 0);
@@ -169004,39 +170243,21 @@ async function endActivity(ctx) {
169004
170243
  throw ApiError.unauthorized("Must be logged in to end activity");
169005
170244
  }
169006
170245
  log2.debug("[API] Ending activity", { userId: user.id });
169007
- let request3;
169008
- try {
169009
- const requestBody = await ctx.request.json();
169010
- request3 = requestBody;
169011
- } catch (error2) {
169012
- log2.error("[API] Failed to parse request body:", { error: error2 });
169013
- throw ApiError.badRequest("Invalid JSON body");
169014
- }
169015
- const { gameId, studentId, activityData, scoreData, timingData, xpEarned } = request3;
169016
- if (!activityData?.activityId) {
169017
- throw ApiError.badRequest("activityId is required");
169018
- }
169019
- if (typeof scoreData?.correctQuestions !== "number" || typeof scoreData?.totalQuestions !== "number") {
169020
- throw ApiError.badRequest("correctQuestions and totalQuestions are required");
169021
- }
169022
- if (typeof timingData?.durationSeconds !== "number") {
169023
- throw ApiError.badRequest("durationSeconds is required");
170246
+ const requestBody = await ctx.request.json();
170247
+ const validation2 = EndActivityRequestSchema.safeParse(requestBody);
170248
+ if (!validation2.success) {
170249
+ throw ApiError.badRequest(formatValidationErrors(validation2.error));
169024
170250
  }
170251
+ const { gameId, studentId, activityData, scoreData, timingData, xpEarned, masteredUnits } = validation2.data;
170252
+ await verifyGameAccessById(gameId, user);
169025
170253
  const db = getDatabase();
169026
- const game = await db.query.games.findFirst({
169027
- where: eq(games.id, gameId)
169028
- });
169029
- if (!game) {
169030
- throw ApiError.notFound("Game not found");
169031
- }
169032
- if (user.role !== "admin" && game.developerId !== user.id) {
169033
- throw ApiError.forbidden("You do not own this game");
169034
- }
170254
+ const { grade, subject } = activityData;
169035
170255
  const integration = await db.query.gameTimebackIntegrations.findFirst({
169036
- where: eq(gameTimebackIntegrations.gameId, gameId)
170256
+ where: (table14, { eq: eq3, and: and3 }) => and3(eq3(table14.gameId, gameId), eq3(table14.grade, grade), eq3(table14.subject, subject))
169037
170257
  });
169038
170258
  if (!integration) {
169039
- throw ApiError.notFound("No TimeBack integration found for this game. Run setup first.");
170259
+ log2.error("[API] No TimeBack integration found", { gameId, grade, subject });
170260
+ throw ApiError.notFound(`No TimeBack integration found for this game (grade ${grade}, subject ${subject}). Run setup first.`);
169040
170261
  }
169041
170262
  const client2 = await getTimebackClient();
169042
170263
  try {
@@ -169047,6 +170268,7 @@ async function endActivity(ctx) {
169047
170268
  correctQuestions: scoreData.correctQuestions,
169048
170269
  sessionDurationSeconds: timingData.durationSeconds,
169049
170270
  xpEarned,
170271
+ masteredUnits,
169050
170272
  activityId: activityData.activityId,
169051
170273
  activityName: activityData.activityName,
169052
170274
  subject: activityData.subject,
@@ -169054,8 +170276,11 @@ async function endActivity(ctx) {
169054
170276
  sensorUrl: activityData.sensorUrl,
169055
170277
  courseId: activityData.courseId,
169056
170278
  courseName: activityData.courseName,
169057
- studentEmail: activityData.studentEmail
170279
+ studentEmail: activityData.studentEmail,
170280
+ courseTotalXp: integration.totalXp
169058
170281
  });
170282
+ const studentData = await client2.resolveStudent(studentId);
170283
+ const studentEmail = studentData.email;
169059
170284
  await client2.recordSessionEnd(integration.courseId, studentId, {
169060
170285
  activeTimeSeconds: timingData.durationSeconds,
169061
170286
  activityId: activityData.activityId,
@@ -169071,17 +170296,26 @@ async function endActivity(ctx) {
169071
170296
  gameId,
169072
170297
  courseId: integration.courseId,
169073
170298
  studentId,
170299
+ studentEmail,
169074
170300
  activityId: activityData.activityId,
169075
170301
  score: scorePercentage,
169076
170302
  durationSeconds: timingData.durationSeconds,
169077
170303
  attemptNumber: result.attemptNumber,
169078
170304
  isFirstAttempt: result.attemptNumber === 1,
169079
- xpAwarded: result.xpAwarded
170305
+ xpAwarded: result.xpAwarded,
170306
+ masteredUnits,
170307
+ pctCompleteApp: result.pctCompleteApp,
170308
+ scoreStatus: result.scoreStatus,
170309
+ inProgress: result.inProgress
169080
170310
  });
169081
170311
  return {
169082
170312
  status: "ok",
169083
170313
  courseId: integration.courseId,
169084
- xpAwarded: result.xpAwarded
170314
+ xpAwarded: result.xpAwarded,
170315
+ masteredUnits: result.masteredUnitsApplied,
170316
+ pctCompleteApp: result.pctCompleteApp,
170317
+ scoreStatus: result.scoreStatus,
170318
+ inProgress: result.inProgress
169085
170319
  };
169086
170320
  } catch (error2) {
169087
170321
  logTimebackError2("end activity", error2, {
@@ -169108,68 +170342,160 @@ async function setupTimebackIntegration(ctx) {
169108
170342
  log2.error("Failed to parse request body:", { error: error2 });
169109
170343
  throw ApiError.badRequest("Invalid JSON body");
169110
170344
  }
169111
- const { gameId, config: config2, verbose } = request3;
170345
+ const { gameId, courses, baseConfig, verbose } = request3;
169112
170346
  log2.debug("[API] TimeBack setup request", {
169113
170347
  gameId,
169114
- courseTitle: config2.course.title
170348
+ courseCount: courses.length
169115
170349
  });
170350
+ await verifyGameAccessById(gameId, user);
169116
170351
  const db = getDatabase();
169117
- const game = await db.query.games.findFirst({
169118
- where: eq(games.id, gameId)
169119
- });
169120
- if (!game) {
169121
- throw ApiError.notFound("Game not found");
169122
- }
169123
- if (user.role !== "admin" && game.developerId !== user.id) {
169124
- throw ApiError.forbidden("You do not own this game");
169125
- }
169126
- const existing = await db.query.gameTimebackIntegrations.findFirst({
170352
+ const existing = await db.query.gameTimebackIntegrations.findMany({
169127
170353
  where: eq(gameTimebackIntegrations.gameId, gameId)
169128
170354
  });
169129
170355
  const client2 = await getTimebackClient();
169130
- if (existing) {
169131
- try {
169132
- await client2.update(existing.courseId, config2);
169133
- } catch (error2) {
169134
- logTimebackError2("update", error2, { gameId, courseId: existing.courseId });
169135
- throw error2;
169136
- }
169137
- const [updated] = await db.update(gameTimebackIntegrations).set({
169138
- updatedAt: new Date
169139
- }).where(eq(gameTimebackIntegrations.id, existing.id)).returning();
169140
- log2.info("[API] Updated TimeBack integration", {
169141
- gameId,
169142
- courseId: updated.courseId
169143
- });
169144
- return {
169145
- integration: updated,
169146
- courseId: updated.courseId
170356
+ const integrations = [];
170357
+ const verboseData = [];
170358
+ for (const courseConfig of courses) {
170359
+ const {
170360
+ subject,
170361
+ grade,
170362
+ title,
170363
+ courseCode,
170364
+ level,
170365
+ metadata: metadata2,
170366
+ totalXp: derivedTotalXp,
170367
+ masterableUnits: derivedMasterableUnits
170368
+ } = courseConfig;
170369
+ if (!isTimebackSubject2(subject)) {
170370
+ log2.error("[API] Invalid subject in TimeBack request", { subject });
170371
+ throw ApiError.badRequest(`Invalid subject "${subject}" provided for course "${title}".`);
170372
+ }
170373
+ if (!isTimebackGrade2(grade)) {
170374
+ log2.error("[API] Invalid grade in TimeBack request", { grade });
170375
+ throw ApiError.badRequest(`Invalid grade "${grade}" provided for course "${title}".`);
170376
+ }
170377
+ const courseSubject = subject;
170378
+ const courseGrade = grade;
170379
+ const courseMetadata = isCourseMetadata(metadata2) ? metadata2 : undefined;
170380
+ const totalXp = derivedTotalXp ?? courseMetadata?.metrics?.totalXp;
170381
+ const masterableUnits = derivedMasterableUnits ?? (isPlaycademyResourceMetadata2(courseMetadata?.playcademy) ? courseMetadata?.playcademy?.mastery?.masterableUnits : undefined);
170382
+ if (typeof totalXp !== "number") {
170383
+ log2.error("[API] Missing totalXp in course metadata", { gameId, grade, subject, title });
170384
+ throw ApiError.badRequest(`Course "${title}" (grade ${grade}, subject ${subject}) is missing \`metadata.metrics.totalXp\``);
170385
+ }
170386
+ const suffix = baseConfig.component.titleSuffix || "";
170387
+ const applySuffix = (text5) => suffix ? `${text5} ${suffix}` : text5;
170388
+ const fullConfig = {
170389
+ organization: baseConfig.organization,
170390
+ course: {
170391
+ title,
170392
+ subjects: [courseSubject],
170393
+ grades: [courseGrade],
170394
+ courseCode,
170395
+ level,
170396
+ gradingScheme: "STANDARD",
170397
+ metadata: metadata2
170398
+ },
170399
+ component: {
170400
+ ...baseConfig.component,
170401
+ title: applySuffix(baseConfig.component.title || `${title} Activities`)
170402
+ },
170403
+ resource: {
170404
+ ...baseConfig.resource,
170405
+ title: applySuffix(baseConfig.resource.title || `${title} Game`),
170406
+ metadata: buildResourceMetadata({
170407
+ baseMetadata: baseConfig.resource.metadata,
170408
+ subject: courseSubject,
170409
+ grade: courseGrade,
170410
+ totalXp,
170411
+ masterableUnits
170412
+ })
170413
+ },
170414
+ componentResource: {
170415
+ ...baseConfig.componentResource,
170416
+ title: applySuffix(baseConfig.componentResource.title || "")
170417
+ }
169147
170418
  };
169148
- }
169149
- let result;
169150
- try {
169151
- result = await client2.setup(config2, { verbose });
169152
- } catch (error2) {
169153
- logTimebackError2("setup", error2, { gameId });
169154
- throw error2;
169155
- }
169156
- const integrationData = {
169157
- gameId,
169158
- courseId: result.courseId
169159
- };
169160
- const [integration] = await db.insert(gameTimebackIntegrations).values(integrationData).returning();
169161
- if (!integration) {
169162
- throw ApiError.internal("Failed to create TimeBack integration record");
170419
+ const existingIntegration = existing.find((i4) => i4.grade === grade && i4.subject === subject);
170420
+ if (existingIntegration) {
170421
+ try {
170422
+ await client2.update(existingIntegration.courseId, fullConfig);
170423
+ } catch (error2) {
170424
+ logTimebackError2("update", error2, {
170425
+ gameId,
170426
+ grade,
170427
+ subject,
170428
+ courseId: existingIntegration.courseId
170429
+ });
170430
+ throw error2;
170431
+ }
170432
+ const [updated] = await db.update(gameTimebackIntegrations).set({
170433
+ totalXp,
170434
+ updatedAt: new Date
170435
+ }).where(eq(gameTimebackIntegrations.id, existingIntegration.id)).returning();
170436
+ if (!updated) {
170437
+ log2.error("[API] Failed to update TimeBack integration record", {
170438
+ gameId,
170439
+ grade,
170440
+ subject,
170441
+ integrationId: existingIntegration.id
170442
+ });
170443
+ throw ApiError.internal("Failed to update TimeBack integration record");
170444
+ }
170445
+ integrations.push(updated);
170446
+ log2.info("[API] Updated TimeBack course", {
170447
+ gameId,
170448
+ courseId: updated.courseId,
170449
+ grade,
170450
+ subject
170451
+ });
170452
+ } else {
170453
+ let result;
170454
+ try {
170455
+ result = await client2.setup(fullConfig, { verbose });
170456
+ } catch (error2) {
170457
+ logTimebackError2("setup", error2, { gameId, grade, subject });
170458
+ throw error2;
170459
+ }
170460
+ const integrationData = {
170461
+ gameId,
170462
+ courseId: result.courseId,
170463
+ grade,
170464
+ subject,
170465
+ totalXp
170466
+ };
170467
+ const [integration] = await db.insert(gameTimebackIntegrations).values(integrationData).returning();
170468
+ if (!integration) {
170469
+ log2.error("[API] Failed to create TimeBack integration record", {
170470
+ gameId,
170471
+ grade,
170472
+ subject,
170473
+ courseId: result.courseId
170474
+ });
170475
+ throw ApiError.internal("Failed to create TimeBack integration record");
170476
+ }
170477
+ integrations.push(integration);
170478
+ if (verbose && result.verboseData) {
170479
+ verboseData.push({
170480
+ integration,
170481
+ config: result.verboseData
170482
+ });
170483
+ }
170484
+ log2.info("[API] Created TimeBack course", {
170485
+ gameId,
170486
+ courseId: integration.courseId,
170487
+ grade,
170488
+ subject
170489
+ });
170490
+ }
169163
170491
  }
169164
170492
  log2.info("[API] Created TimeBack integration", {
169165
170493
  gameId,
169166
- courseId: integration.courseId,
169167
- ...result.verboseData && { verbose: result.verboseData }
170494
+ courseCount: integrations.length
169168
170495
  });
169169
170496
  return {
169170
- integration,
169171
- courseId: integration.courseId,
169172
- ...result.verboseData && { verbose: result.verboseData }
170497
+ integrations,
170498
+ ...verbose && verboseData.length > 0 && { verbose: verboseData }
169173
170499
  };
169174
170500
  }
169175
170501
  async function getTimebackIntegration(ctx) {
@@ -169181,21 +170507,13 @@ async function getTimebackIntegration(ctx) {
169181
170507
  if (!gameId) {
169182
170508
  throw ApiError.badRequest("Missing gameId parameter");
169183
170509
  }
169184
- log2.debug("[API] Getting TimeBack integration", { userId: user.id, gameId });
170510
+ log2.debug("[API] Getting TimeBack integrations", { userId: user.id, gameId });
170511
+ await verifyGameAccessById(gameId, user);
169185
170512
  const db = getDatabase();
169186
- const game = await db.query.games.findFirst({
169187
- where: eq(games.id, gameId)
169188
- });
169189
- if (!game) {
169190
- throw ApiError.notFound("Game not found");
169191
- }
169192
- if (user.role !== "admin" && game.developerId !== user.id) {
169193
- throw ApiError.forbidden("You do not own this game");
169194
- }
169195
- const integration = await db.query.gameTimebackIntegrations.findFirst({
170513
+ const integrations = await db.query.gameTimebackIntegrations.findMany({
169196
170514
  where: eq(gameTimebackIntegrations.gameId, gameId)
169197
170515
  });
169198
- return integration || null;
170516
+ return integrations;
169199
170517
  }
169200
170518
  async function verifyTimebackIntegration(ctx) {
169201
170519
  const user = ctx.user;
@@ -169207,39 +170525,40 @@ async function verifyTimebackIntegration(ctx) {
169207
170525
  throw ApiError.badRequest("Missing gameId parameter");
169208
170526
  }
169209
170527
  log2.debug("[API] Verifying TimeBack integration", { userId: user.id, gameId });
170528
+ await verifyGameAccessById(gameId, user);
169210
170529
  const db = getDatabase();
169211
- const game = await db.query.games.findFirst({
169212
- where: eq(games.id, gameId)
169213
- });
169214
- if (!game) {
169215
- throw ApiError.notFound("Game not found");
169216
- }
169217
- if (user.role !== "admin" && game.developerId !== user.id) {
169218
- log2.error("[API] User does not own game", { userId: user.id, gameId });
169219
- throw ApiError.forbidden("You do not own this game");
169220
- }
169221
- const integration = await db.query.gameTimebackIntegrations.findFirst({
170530
+ const integrations = await db.query.gameTimebackIntegrations.findMany({
169222
170531
  where: eq(gameTimebackIntegrations.gameId, gameId)
169223
170532
  });
169224
- if (!integration) {
170533
+ if (integrations.length === 0) {
170534
+ log2.error("[API] No TimeBack integration found", { gameId });
169225
170535
  throw ApiError.notFound("No TimeBack integration found for this game");
169226
170536
  }
169227
170537
  const client2 = await getTimebackClient();
169228
- const resources = await client2.verify(integration.courseId);
169229
- const resourceValues = Object.values(resources);
169230
- const allFound = resourceValues.every((r3) => r3.found);
169231
- const errors3 = Object.entries(resources).filter(([_5, r3]) => !r3.found).map(([name4]) => `${name4} not found`);
169232
- await db.update(gameTimebackIntegrations).set({ lastVerifiedAt: new Date }).where(eq(gameTimebackIntegrations.id, integration.id));
169233
- log2.info("[API] Verified TimeBack integration", {
170538
+ const now2 = new Date;
170539
+ const results = await Promise.all(integrations.map(async (integration) => {
170540
+ const resources = await client2.verify(integration.courseId);
170541
+ const resourceValues = Object.values(resources);
170542
+ const allFound = resourceValues.every((r3) => r3.found);
170543
+ const errors3 = Object.entries(resources).filter(([_5, r3]) => !r3.found).map(([name4]) => `${name4} not found`);
170544
+ const status = allFound ? "success" : "error";
170545
+ return {
170546
+ integration: { ...integration, lastVerifiedAt: now2 },
170547
+ resources,
170548
+ status,
170549
+ ...errors3.length > 0 && { errors: errors3 }
170550
+ };
170551
+ }));
170552
+ await db.update(gameTimebackIntegrations).set({ lastVerifiedAt: now2 }).where(eq(gameTimebackIntegrations.gameId, gameId));
170553
+ const overallSuccess = results.every((r3) => r3.status === "success");
170554
+ log2.info("[API] Verified TimeBack integrations", {
169234
170555
  gameId,
169235
- courseId: integration.courseId,
169236
- status: allFound ? "success" : "error"
170556
+ courseCount: integrations.length,
170557
+ status: overallSuccess ? "success" : "error"
169237
170558
  });
169238
170559
  return {
169239
- status: allFound ? "success" : "error",
169240
- integration: { ...integration, lastVerifiedAt: new Date },
169241
- resources,
169242
- ...errors3.length > 0 && { errors: errors3 }
170560
+ status: overallSuccess ? "success" : "error",
170561
+ results
169243
170562
  };
169244
170563
  }
169245
170564
  async function getTimebackConfig(ctx) {
@@ -169252,20 +170571,13 @@ async function getTimebackConfig(ctx) {
169252
170571
  throw ApiError.badRequest("Missing gameId parameter");
169253
170572
  }
169254
170573
  log2.debug("[API] Getting TimeBack config", { userId: user.id, gameId });
170574
+ await verifyGameAccessById(gameId, user);
169255
170575
  const db = getDatabase();
169256
- const game = await db.query.games.findFirst({
169257
- where: eq(games.id, gameId)
169258
- });
169259
- if (!game) {
169260
- throw ApiError.notFound("Game not found");
169261
- }
169262
- if (user.role !== "admin" && game.developerId !== user.id) {
169263
- throw ApiError.forbidden("You do not own this game");
169264
- }
169265
170576
  const integration = await db.query.gameTimebackIntegrations.findFirst({
169266
170577
  where: eq(gameTimebackIntegrations.gameId, gameId)
169267
170578
  });
169268
170579
  if (!integration) {
170580
+ log2.error("[API] No TimeBack integration found", { gameId });
169269
170581
  throw ApiError.notFound("No TimeBack integration found for this game");
169270
170582
  }
169271
170583
  const client2 = await getTimebackClient();
@@ -169280,34 +170592,40 @@ async function deleteTimebackIntegration(ctx) {
169280
170592
  if (!gameId) {
169281
170593
  throw ApiError.badRequest("Missing gameId parameter");
169282
170594
  }
169283
- log2.debug("[API] Deleting TimeBack integration", { userId: user.id, gameId });
170595
+ log2.debug("[API] Deleting TimeBack integrations", { userId: user.id, gameId });
170596
+ await verifyGameAccessById(gameId, user);
169284
170597
  const db = getDatabase();
169285
- const game = await db.query.games.findFirst({
169286
- where: eq(games.id, gameId)
169287
- });
169288
- if (!game) {
169289
- throw ApiError.notFound("Game not found");
169290
- }
169291
- if (user.role !== "admin" && game.developerId !== user.id) {
169292
- throw ApiError.forbidden("You do not own this game");
169293
- }
169294
- const integration = await db.query.gameTimebackIntegrations.findFirst({
170598
+ const integrations = await db.query.gameTimebackIntegrations.findMany({
169295
170599
  where: eq(gameTimebackIntegrations.gameId, gameId)
169296
170600
  });
169297
- if (!integration) {
170601
+ if (integrations.length === 0) {
170602
+ log2.error("[API] No TimeBack integrations found", { gameId });
169298
170603
  throw ApiError.notFound("No TimeBack integration found for this game");
169299
170604
  }
169300
170605
  const client2 = await getTimebackClient();
169301
- try {
169302
- await client2.cleanup(integration.courseId);
169303
- } catch (error2) {
169304
- logTimebackError2("cleanup", error2, { gameId, courseId: integration.courseId });
169305
- throw error2;
170606
+ for (const integration of integrations) {
170607
+ try {
170608
+ await client2.cleanup(integration.courseId);
170609
+ log2.info("[API] Cleaned up TimeBack course", {
170610
+ gameId,
170611
+ courseId: integration.courseId,
170612
+ grade: integration.grade,
170613
+ subject: integration.subject
170614
+ });
170615
+ } catch (error2) {
170616
+ logTimebackError2("cleanup", error2, {
170617
+ gameId,
170618
+ courseId: integration.courseId,
170619
+ grade: integration.grade,
170620
+ subject: integration.subject
170621
+ });
170622
+ throw error2;
170623
+ }
169306
170624
  }
169307
- await db.delete(gameTimebackIntegrations).where(eq(gameTimebackIntegrations.id, integration.id));
169308
- log2.info("[API] Deleted TimeBack integration", {
170625
+ await db.delete(gameTimebackIntegrations).where(eq(gameTimebackIntegrations.gameId, gameId));
170626
+ log2.info("[API] Deleted TimeBack integrations", {
169309
170627
  gameId,
169310
- courseId: integration.courseId
170628
+ coursesDeleted: integrations.length
169311
170629
  });
169312
170630
  }
169313
170631
  // ../api-core/src/handlers/timeback/xp.ts
@@ -169439,6 +170757,26 @@ async function getTimeBackXpHistory(ctx) {
169439
170757
  throw ApiError.internal("Failed to get TimeBack XP history", error2);
169440
170758
  }
169441
170759
  }
170760
+ // ../api-core/src/handlers/timeback/enrollments.ts
170761
+ init_src();
170762
+ async function getStudentEnrollments(ctx) {
170763
+ const user = ctx.user;
170764
+ if (!user) {
170765
+ throw ApiError.unauthorized("Must be logged in to get enrollments");
170766
+ }
170767
+ const timebackId = ctx.params.timebackId;
170768
+ if (!timebackId) {
170769
+ throw ApiError.badRequest("Missing timebackId parameter");
170770
+ }
170771
+ log2.debug("[API] Getting student enrollments", { userId: user.id, timebackId });
170772
+ const enrollments = await fetchEnrollmentsForUser(timebackId);
170773
+ log2.info("[API] Retrieved student enrollments", {
170774
+ userId: user.id,
170775
+ timebackId,
170776
+ enrollmentCount: enrollments.length
170777
+ });
170778
+ return { enrollments };
170779
+ }
169442
170780
  // src/routes/integrations/timeback.ts
169443
170781
  var timebackRouter = new Hono2;
169444
170782
  timebackRouter.post("/populate-student", async (c3) => {
@@ -169630,6 +170968,25 @@ timebackRouter.post("/end-activity", async (c3) => {
169630
170968
  return c3.json(createUnknownErrorResponse(error2), 500);
169631
170969
  }
169632
170970
  });
170971
+ timebackRouter.get("/enrollments/:timebackId", async (c3) => {
170972
+ const timebackId = c3.req.param("timebackId");
170973
+ const ctx = {
170974
+ user: c3.get("user"),
170975
+ params: { timebackId },
170976
+ url: new URL(c3.req.url),
170977
+ request: c3.req.raw
170978
+ };
170979
+ try {
170980
+ const result = await getStudentEnrollments(ctx);
170981
+ return c3.json(result);
170982
+ } catch (error2) {
170983
+ if (error2 instanceof ApiError) {
170984
+ return c3.json(createErrorResponse(error2), error2.statusCode);
170985
+ }
170986
+ console.error("Error in getStudentEnrollments:", error2);
170987
+ return c3.json(createUnknownErrorResponse(error2), 500);
170988
+ }
170989
+ });
169633
170990
  // ../api-core/src/handlers/lti-timeback/index.ts
169634
170991
  import * as crypto7 from "node:crypto";
169635
170992
  init_src();
@@ -169976,7 +171333,7 @@ var {
169976
171333
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "node:fs";
169977
171334
  import { createServer } from "node:net";
169978
171335
  import { homedir } from "node:os";
169979
- import { join as join6 } from "node:path";
171336
+ import { join as join5 } from "node:path";
169980
171337
  async function isPortAvailableOnHost(port, host) {
169981
171338
  return new Promise((resolve2) => {
169982
171339
  const server2 = createServer();
@@ -170015,11 +171372,11 @@ async function findAvailablePort(startPort = 4321) {
170015
171372
  }
170016
171373
  function getRegistryPath() {
170017
171374
  const home = homedir();
170018
- const dir = join6(home, ".playcademy");
171375
+ const dir = join5(home, ".playcademy");
170019
171376
  if (!existsSync2(dir)) {
170020
171377
  mkdirSync2(dir, { recursive: true });
170021
171378
  }
170022
- return join6(dir, ".proc");
171379
+ return join5(dir, ".proc");
170023
171380
  }
170024
171381
  function readRegistry() {
170025
171382
  const registryPath = getRegistryPath();