@playcademy/vite-plugin 0.1.30 → 0.1.32-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -41199,7 +41199,7 @@ var import_picocolors7 = __toESM(require_picocolors(), 1);
41199
41199
  // package.json
41200
41200
  var package_default = {
41201
41201
  name: "@playcademy/vite-plugin",
41202
- version: "0.1.29",
41202
+ version: "0.1.32",
41203
41203
  type: "module",
41204
41204
  exports: {
41205
41205
  ".": {
@@ -41236,7 +41236,7 @@ var package_default = {
41236
41236
 
41237
41237
  // src/lib/backend/server.ts
41238
41238
  import {
41239
- loadPlaycademyConfig as loadConfig,
41239
+ loadPlaycademyConfigAndSetWorkspace as loadConfigAndSetWorkspace,
41240
41240
  startPlaycademyDevServer,
41241
41241
  startPlaycademyHotReload
41242
41242
  } from "playcademy/utils";
@@ -41306,9 +41306,9 @@ function createHotReloadCallbacks(viteConfig) {
41306
41306
  }
41307
41307
 
41308
41308
  // src/lib/backend/server.ts
41309
- async function tryLoadConfig(viteConfig) {
41309
+ async function tryLoadConfig(viteConfig, configPath) {
41310
41310
  try {
41311
- return await loadConfig();
41311
+ return await loadConfigAndSetWorkspace(configPath);
41312
41312
  } catch (error) {
41313
41313
  if (error instanceof Error && !error.message.includes("Could not find")) {
41314
41314
  viteConfig.logger.warn(`Could not load playcademy.config.js: ${error.message}`);
@@ -41337,8 +41337,8 @@ function setupHotReload(serverRef, options) {
41337
41337
  return () => watcher.close();
41338
41338
  }
41339
41339
  async function setupCliDevServer(options) {
41340
- const { preferredPort, viteConfig, platformUrl } = options;
41341
- const config = await tryLoadConfig(viteConfig);
41340
+ const { preferredPort, viteConfig, platformUrl, configPath } = options;
41341
+ const config = await tryLoadConfig(viteConfig, configPath);
41342
41342
  if (!config)
41343
41343
  return null;
41344
41344
  if (!needsCliDevServer(config))
@@ -41406,15 +41406,11 @@ import { stdout } from "process";
41406
41406
  import { createPublicKey as createPublicKey2, createVerify, verify as verify3 } from "crypto";
41407
41407
  import { request as request2 } from "https";
41408
41408
  import { pipeline } from "stream";
41409
- import { execSync } from "node:child_process";
41410
- import { mkdir, readdir as readdir2, readFile as readFile2, rm, stat, writeFile } from "node:fs/promises";
41411
- import { tmpdir } from "node:os";
41412
- import { join as join5, relative } from "node:path";
41409
+ import { readdir as readdir2, readFile as readFile2, stat } from "node:fs/promises";
41410
+ import { join as join4, relative } from "node:path";
41413
41411
  import { createHash } from "node:crypto";
41414
41412
  import { readdir, readFile } from "node:fs/promises";
41415
41413
  import { join as join3 } from "node:path";
41416
- import { dirname as dirname2, join as join4 } from "node:path";
41417
- import { fileURLToPath } from "node:url";
41418
41414
  import crypto6 from "node:crypto";
41419
41415
  import * as crypto7 from "node:crypto";
41420
41416
  var __create2 = Object.create;
@@ -104543,7 +104539,7 @@ var require_event_target_shim2 = __commonJS2((exports, module2) => {
104543
104539
  var CAPTURE = 1;
104544
104540
  var BUBBLE = 2;
104545
104541
  var ATTRIBUTE = 3;
104546
- function isObject2(x6) {
104542
+ function isObject4(x6) {
104547
104543
  return x6 !== null && typeof x6 === "object";
104548
104544
  }
104549
104545
  function getListeners(eventTarget) {
@@ -104567,7 +104563,7 @@ var require_event_target_shim2 = __commonJS2((exports, module2) => {
104567
104563
  return null;
104568
104564
  },
104569
104565
  set(listener) {
104570
- if (typeof listener !== "function" && !isObject2(listener)) {
104566
+ if (typeof listener !== "function" && !isObject4(listener)) {
104571
104567
  listener = null;
104572
104568
  }
104573
104569
  const listeners = getListeners(this);
@@ -104647,11 +104643,11 @@ var require_event_target_shim2 = __commonJS2((exports, module2) => {
104647
104643
  if (listener == null) {
104648
104644
  return;
104649
104645
  }
104650
- if (typeof listener !== "function" && !isObject2(listener)) {
104646
+ if (typeof listener !== "function" && !isObject4(listener)) {
104651
104647
  throw new TypeError("'listener' should be a function or an object.");
104652
104648
  }
104653
104649
  const listeners = getListeners(this);
104654
- const optionsIsObj = isObject2(options);
104650
+ const optionsIsObj = isObject4(options);
104655
104651
  const capture = optionsIsObj ? Boolean(options.capture) : Boolean(options);
104656
104652
  const listenerType = capture ? CAPTURE : BUBBLE;
104657
104653
  const newNode = {
@@ -104681,7 +104677,7 @@ var require_event_target_shim2 = __commonJS2((exports, module2) => {
104681
104677
  return;
104682
104678
  }
104683
104679
  const listeners = getListeners(this);
104684
- const capture = isObject2(options) ? Boolean(options.capture) : Boolean(options);
104680
+ const capture = isObject4(options) ? Boolean(options.capture) : Boolean(options);
104685
104681
  const listenerType = capture ? CAPTURE : BUBBLE;
104686
104682
  let prev = null;
104687
104683
  let node = listeners.get(eventName);
@@ -110422,7 +110418,7 @@ var init_block_senders = __esm(() => {
110422
110418
  var Domains;
110423
110419
  var DomainListResponsesV4PagePaginationArray;
110424
110420
  var DomainBulkDeleteResponsesSinglePage;
110425
- var init_domains = __esm(() => {
110421
+ var init_domains3 = __esm(() => {
110426
110422
  init_pagination();
110427
110423
  Domains = class Domains2 extends APIResource {
110428
110424
  list(params, options) {
@@ -110528,8 +110524,8 @@ var init_settings4 = __esm(() => {
110528
110524
  init_allow_policies();
110529
110525
  init_block_senders();
110530
110526
  init_block_senders();
110531
- init_domains();
110532
- init_domains();
110527
+ init_domains3();
110528
+ init_domains3();
110533
110529
  init_impersonation_registry();
110534
110530
  init_impersonation_registry();
110535
110531
  init_trusted_domains();
@@ -111750,7 +111746,7 @@ var init_bulks = __esm(() => {
111750
111746
  };
111751
111747
  });
111752
111748
  var Domains2;
111753
- var init_domains2 = __esm(() => {
111749
+ var init_domains4 = __esm(() => {
111754
111750
  init_bulks();
111755
111751
  init_bulks();
111756
111752
  Domains2 = class Domains22 extends APIResource {
@@ -111876,8 +111872,8 @@ var init_intel = __esm(() => {
111876
111872
  init_asn3();
111877
111873
  init_attack_surface_report();
111878
111874
  init_attack_surface_report();
111879
- init_domains2();
111880
- init_domains2();
111875
+ init_domains4();
111876
+ init_domains4();
111881
111877
  init_indicator_feeds();
111882
111878
  init_indicator_feeds();
111883
111879
  Intel = class Intel2 extends APIResource {
@@ -114616,7 +114612,7 @@ var init_page_shield = __esm(() => {
114616
114612
  });
114617
114613
  var Domains3;
114618
114614
  var DomainListResponsesSinglePage;
114619
- var init_domains3 = __esm(() => {
114615
+ var init_domains5 = __esm(() => {
114620
114616
  init_pagination();
114621
114617
  Domains3 = class Domains32 extends APIResource {
114622
114618
  create(projectName, params, options) {
@@ -114712,8 +114708,8 @@ var init_deployments = __esm(() => {
114712
114708
  var Projects;
114713
114709
  var DeploymentsSinglePage;
114714
114710
  var init_projects = __esm(() => {
114715
- init_domains3();
114716
- init_domains3();
114711
+ init_domains5();
114712
+ init_domains5();
114717
114713
  init_deployments();
114718
114714
  init_deployments();
114719
114715
  init_pagination();
@@ -115249,7 +115245,7 @@ var init_managed = __esm(() => {
115249
115245
  };
115250
115246
  });
115251
115247
  var Domains4;
115252
- var init_domains4 = __esm(() => {
115248
+ var init_domains6 = __esm(() => {
115253
115249
  init_custom5();
115254
115250
  init_custom5();
115255
115251
  init_managed();
@@ -115278,8 +115274,8 @@ var init_buckets = __esm(() => {
115278
115274
  init_metrics();
115279
115275
  init_sippy();
115280
115276
  init_sippy();
115281
- init_domains4();
115282
- init_domains4();
115277
+ init_domains6();
115278
+ init_domains6();
115283
115279
  Buckets = class Buckets2 extends APIResource {
115284
115280
  constructor() {
115285
115281
  super(...arguments);
@@ -118409,7 +118405,7 @@ var init_rate_limits = __esm(() => {
118409
118405
  });
118410
118406
  var Domains5;
118411
118407
  var DomainsSinglePage;
118412
- var init_domains5 = __esm(() => {
118408
+ var init_domains7 = __esm(() => {
118413
118409
  init_pagination();
118414
118410
  Domains5 = class Domains52 extends APIResource {
118415
118411
  update(domainName, params, options) {
@@ -118434,8 +118430,8 @@ var init_domains5 = __esm(() => {
118434
118430
  });
118435
118431
  var Registrar;
118436
118432
  var init_registrar = __esm(() => {
118437
- init_domains5();
118438
- init_domains5();
118433
+ init_domains7();
118434
+ init_domains7();
118439
118435
  Registrar = class Registrar2 extends APIResource {
118440
118436
  constructor() {
118441
118437
  super(...arguments);
@@ -121236,7 +121232,7 @@ var init_account_settings = __esm(() => {
121236
121232
  });
121237
121233
  var Domains6;
121238
121234
  var DomainsSinglePage2;
121239
- var init_domains6 = __esm(() => {
121235
+ var init_domains8 = __esm(() => {
121240
121236
  init_pagination();
121241
121237
  Domains6 = class Domains62 extends APIResource {
121242
121238
  update(params, options) {
@@ -121369,7 +121365,7 @@ var init_versions3 = __esm(() => {
121369
121365
  });
121370
121366
  var Workers;
121371
121367
  var WorkersV4PagePaginationArray;
121372
- var init_workers = __esm(() => {
121368
+ var init_workers3 = __esm(() => {
121373
121369
  init_versions3();
121374
121370
  init_versions3();
121375
121371
  init_pagination();
@@ -121421,8 +121417,8 @@ var init_workers = __esm(() => {
121421
121417
  });
121422
121418
  var Beta;
121423
121419
  var init_beta = __esm(() => {
121424
- init_workers();
121425
- init_workers();
121420
+ init_workers3();
121421
+ init_workers3();
121426
121422
  Beta = class Beta2 extends APIResource {
121427
121423
  constructor() {
121428
121424
  super(...arguments);
@@ -121789,11 +121785,11 @@ var init_scripts2 = __esm(() => {
121789
121785
  Scripts2.ScriptAndVersionSettings = ScriptAndVersionSettings;
121790
121786
  });
121791
121787
  var Workers2;
121792
- var init_workers2 = __esm(() => {
121788
+ var init_workers4 = __esm(() => {
121793
121789
  init_account_settings();
121794
121790
  init_account_settings();
121795
- init_domains6();
121796
- init_domains6();
121791
+ init_domains8();
121792
+ init_domains8();
121797
121793
  init_routes3();
121798
121794
  init_routes3();
121799
121795
  init_subdomains();
@@ -126573,7 +126569,7 @@ var init_resources3 = __esm(() => {
126573
126569
  init_vectorize();
126574
126570
  init_waiting_rooms();
126575
126571
  init_web3();
126576
- init_workers2();
126572
+ init_workers4();
126577
126573
  init_workers_for_platforms();
126578
126574
  init_workflows();
126579
126575
  init_zaraz();
@@ -126682,7 +126678,7 @@ var init_cloudflare = __esm(() => {
126682
126678
  init_waiting_rooms();
126683
126679
  init_web3();
126684
126680
  init_workers_for_platforms();
126685
- init_workers2();
126681
+ init_workers4();
126686
126682
  init_workflows();
126687
126683
  init_zaraz();
126688
126684
  init_zero_trust();
@@ -127147,7 +127143,7 @@ var require_dist_cjs2 = __commonJS2((exports, module2) => {
127147
127143
  Fields: () => Fields3,
127148
127144
  HttpRequest: () => HttpRequest,
127149
127145
  HttpResponse: () => HttpResponse,
127150
- IHttpRequest: () => import_types4.HttpRequest,
127146
+ IHttpRequest: () => import_types6.HttpRequest,
127151
127147
  getHttpHandlerExtensionConfiguration: () => getHttpHandlerExtensionConfiguration,
127152
127148
  isValidHostname: () => isValidHostname,
127153
127149
  resolveHttpHandlerRuntimeConfig: () => resolveHttpHandlerRuntimeConfig
@@ -127174,12 +127170,12 @@ var require_dist_cjs2 = __commonJS2((exports, module2) => {
127174
127170
  httpHandler: httpHandlerExtensionConfiguration.httpHandler()
127175
127171
  };
127176
127172
  }, "resolveHttpHandlerRuntimeConfig");
127177
- var import_types4 = require_dist_cjs();
127173
+ var import_types6 = require_dist_cjs();
127178
127174
  var Field = class {
127179
127175
  static {
127180
127176
  __name(this, "Field");
127181
127177
  }
127182
- constructor({ name: name4, kind: kind2 = import_types4.FieldPosition.HEADER, values = [] }) {
127178
+ constructor({ name: name4, kind: kind2 = import_types6.FieldPosition.HEADER, values = [] }) {
127183
127179
  this.name = name4;
127184
127180
  this.kind = kind2;
127185
127181
  this.values = values;
@@ -128040,8 +128036,8 @@ var require_dist_cjs4 = __commonJS2((exports, module2) => {
128040
128036
  normalizeProvider: () => normalizeProvider
128041
128037
  });
128042
128038
  module2.exports = __toCommonJS(src_exports);
128043
- var import_types4 = require_dist_cjs();
128044
- var getSmithyContext = /* @__PURE__ */ __name((context) => context[import_types4.SMITHY_CONTEXT_KEY] || (context[import_types4.SMITHY_CONTEXT_KEY] = {}), "getSmithyContext");
128039
+ var import_types6 = require_dist_cjs();
128040
+ var getSmithyContext = /* @__PURE__ */ __name((context) => context[import_types6.SMITHY_CONTEXT_KEY] || (context[import_types6.SMITHY_CONTEXT_KEY] = {}), "getSmithyContext");
128045
128041
  var normalizeProvider = /* @__PURE__ */ __name((input) => {
128046
128042
  if (typeof input === "function")
128047
128043
  return input;
@@ -132057,8 +132053,8 @@ var require_dist_cjs16 = __commonJS2((exports, module2) => {
132057
132053
  setFeature: () => setFeature
132058
132054
  });
132059
132055
  module2.exports = __toCommonJS(src_exports);
132060
- var import_types4 = require_dist_cjs();
132061
- var getSmithyContext = /* @__PURE__ */ __name((context) => context[import_types4.SMITHY_CONTEXT_KEY] || (context[import_types4.SMITHY_CONTEXT_KEY] = {}), "getSmithyContext");
132056
+ var import_types6 = require_dist_cjs();
132057
+ var getSmithyContext = /* @__PURE__ */ __name((context) => context[import_types6.SMITHY_CONTEXT_KEY] || (context[import_types6.SMITHY_CONTEXT_KEY] = {}), "getSmithyContext");
132062
132058
  var import_util_middleware = require_dist_cjs4();
132063
132059
  var resolveAuthOptions = /* @__PURE__ */ __name((candidateAuthOptions, authSchemePreference) => {
132064
132060
  if (!authSchemePreference || authSchemePreference.length === 0) {
@@ -132294,9 +132290,9 @@ var require_dist_cjs16 = __commonJS2((exports, module2) => {
132294
132290
  throw new Error("request could not be signed with `apiKey` since the `apiKey` is not defined");
132295
132291
  }
132296
132292
  const clonedRequest = import_protocol_http.HttpRequest.clone(httpRequest);
132297
- if (signingProperties.in === import_types4.HttpApiKeyAuthLocation.QUERY) {
132293
+ if (signingProperties.in === import_types6.HttpApiKeyAuthLocation.QUERY) {
132298
132294
  clonedRequest.query[signingProperties.name] = identity.apiKey;
132299
- } else if (signingProperties.in === import_types4.HttpApiKeyAuthLocation.HEADER) {
132295
+ } else if (signingProperties.in === import_types6.HttpApiKeyAuthLocation.HEADER) {
132300
132296
  clonedRequest.headers[signingProperties.name] = signingProperties.scheme ? `${signingProperties.scheme} ${identity.apiKey}` : identity.apiKey;
132301
132297
  } else {
132302
132298
  throw new Error("request can only be signed with `apiKey` locations `query` or `header`, but found: `" + signingProperties.in + "`");
@@ -133369,7 +133365,7 @@ var require_httpAuthSchemes = __commonJS2((exports, module2) => {
133369
133365
  },
133370
133366
  default: undefined
133371
133367
  };
133372
- var import_client = require_client();
133368
+ var import_client2 = require_client();
133373
133369
  var import_core210 = require_dist_cjs16();
133374
133370
  var import_signature_v4 = require_dist_cjs20();
133375
133371
  var resolveAwsSdkSigV4Config = /* @__PURE__ */ __name((config2) => {
@@ -133388,7 +133384,7 @@ var require_httpAuthSchemes = __commonJS2((exports, module2) => {
133388
133384
  });
133389
133385
  const boundProvider = bindCallerConfig(config2, memoizedProvider);
133390
133386
  if (isUserSupplied && !boundProvider.attributed) {
133391
- resolvedCredentials = /* @__PURE__ */ __name(async (options) => boundProvider(options).then((creds) => (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_CODE", "e")), "resolvedCredentials");
133387
+ resolvedCredentials = /* @__PURE__ */ __name(async (options) => boundProvider(options).then((creds) => (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_CODE", "e")), "resolvedCredentials");
133392
133388
  resolvedCredentials.memoized = boundProvider.memoized;
133393
133389
  resolvedCredentials.configBound = boundProvider.configBound;
133394
133390
  resolvedCredentials.attributed = true;
@@ -133930,7 +133926,7 @@ var require_dist_cjs23 = __commonJS2((exports, module2) => {
133930
133926
  }
133931
133927
  };
133932
133928
  var import_protocols = require_protocols();
133933
- var import_types4 = require_dist_cjs();
133929
+ var import_types6 = require_dist_cjs();
133934
133930
  var Command = class {
133935
133931
  constructor() {
133936
133932
  this.middlewareStack = (0, import_middleware_stack.constructStack)();
@@ -133962,7 +133958,7 @@ var require_dist_cjs23 = __commonJS2((exports, module2) => {
133962
133958
  commandName,
133963
133959
  inputFilterSensitiveLog,
133964
133960
  outputFilterSensitiveLog,
133965
- [import_types4.SMITHY_CONTEXT_KEY]: {
133961
+ [import_types6.SMITHY_CONTEXT_KEY]: {
133966
133962
  commandInstance: this,
133967
133963
  ...smithyContext
133968
133964
  },
@@ -134187,8 +134183,8 @@ var require_dist_cjs23 = __commonJS2((exports, module2) => {
134187
134183
  }, "emitWarningIfUnsupportedVersion");
134188
134184
  var getChecksumConfiguration = /* @__PURE__ */ __name((runtimeConfig) => {
134189
134185
  const checksumAlgorithms = [];
134190
- for (const id in import_types4.AlgorithmId) {
134191
- const algorithmId = import_types4.AlgorithmId[id];
134186
+ for (const id in import_types6.AlgorithmId) {
134187
+ const algorithmId = import_types6.AlgorithmId[id];
134192
134188
  if (runtimeConfig[algorithmId] === undefined) {
134193
134189
  continue;
134194
134190
  }
@@ -136345,7 +136341,7 @@ var require_protocols2 = __commonJS2((exports, module2) => {
136345
136341
  return this._read(schema4, data);
136346
136342
  }
136347
136343
  _read(schema4, value) {
136348
- const isObject2 = value !== null && typeof value === "object";
136344
+ const isObject4 = value !== null && typeof value === "object";
136349
136345
  const ns = import_schema2.NormalizedSchema.of(schema4);
136350
136346
  if (ns.isListSchema() && Array.isArray(value)) {
136351
136347
  const listMember = ns.getValueSchema();
@@ -136357,7 +136353,7 @@ var require_protocols2 = __commonJS2((exports, module2) => {
136357
136353
  }
136358
136354
  }
136359
136355
  return out2;
136360
- } else if (ns.isMapSchema() && isObject2) {
136356
+ } else if (ns.isMapSchema() && isObject4) {
136361
136357
  const mapMember = ns.getValueSchema();
136362
136358
  const out2 = {};
136363
136359
  const sparse = !!ns.getMergedTraits().sparse;
@@ -136367,7 +136363,7 @@ var require_protocols2 = __commonJS2((exports, module2) => {
136367
136363
  }
136368
136364
  }
136369
136365
  return out2;
136370
- } else if (ns.isStructSchema() && isObject2) {
136366
+ } else if (ns.isStructSchema() && isObject4) {
136371
136367
  const out2 = {};
136372
136368
  for (const [memberName, memberSchema] of ns.structIterator()) {
136373
136369
  const fromKey = this.settings.jsonName ? memberSchema.getMergedTraits().jsonName ?? memberName : memberName;
@@ -136499,7 +136495,7 @@ var require_protocols2 = __commonJS2((exports, module2) => {
136499
136495
  return this.buffer;
136500
136496
  }
136501
136497
  _write(schema4, value, container) {
136502
- const isObject2 = value !== null && typeof value === "object";
136498
+ const isObject4 = value !== null && typeof value === "object";
136503
136499
  const ns = import_schema22.NormalizedSchema.of(schema4);
136504
136500
  if (ns.isListSchema() && Array.isArray(value)) {
136505
136501
  const listMember = ns.getValueSchema();
@@ -136511,7 +136507,7 @@ var require_protocols2 = __commonJS2((exports, module2) => {
136511
136507
  }
136512
136508
  }
136513
136509
  return out2;
136514
- } else if (ns.isMapSchema() && isObject2) {
136510
+ } else if (ns.isMapSchema() && isObject4) {
136515
136511
  const mapMember = ns.getValueSchema();
136516
136512
  const out2 = {};
136517
136513
  const sparse = !!ns.getMergedTraits().sparse;
@@ -136521,7 +136517,7 @@ var require_protocols2 = __commonJS2((exports, module2) => {
136521
136517
  }
136522
136518
  }
136523
136519
  return out2;
136524
- } else if (ns.isStructSchema() && isObject2) {
136520
+ } else if (ns.isStructSchema() && isObject4) {
136525
136521
  const out2 = {};
136526
136522
  for (const [memberName, memberSchema] of ns.structIterator()) {
136527
136523
  const targetKey = this.settings.jsonName ? memberSchema.getMergedTraits().jsonName ?? memberName : memberName;
@@ -143953,16 +143949,16 @@ var require_dist_cjs52 = __commonJS2((exports, module2) => {
143953
143949
  var getProfileName = /* @__PURE__ */ __name((init3) => init3.profile || process.env[ENV_PROFILE] || DEFAULT_PROFILE, "getProfileName");
143954
143950
  __reExport(src_exports, require_getSSOTokenFilepath(), module2.exports);
143955
143951
  __reExport(src_exports, require_getSSOTokenFromFile(), module2.exports);
143956
- var import_types4 = require_dist_cjs();
143952
+ var import_types6 = require_dist_cjs();
143957
143953
  var getConfigData = /* @__PURE__ */ __name((data) => Object.entries(data).filter(([key]) => {
143958
143954
  const indexOfSeparator = key.indexOf(CONFIG_PREFIX_SEPARATOR);
143959
143955
  if (indexOfSeparator === -1) {
143960
143956
  return false;
143961
143957
  }
143962
- return Object.values(import_types4.IniSectionType).includes(key.substring(0, indexOfSeparator));
143958
+ return Object.values(import_types6.IniSectionType).includes(key.substring(0, indexOfSeparator));
143963
143959
  }).reduce((acc, [key, value]) => {
143964
143960
  const indexOfSeparator = key.indexOf(CONFIG_PREFIX_SEPARATOR);
143965
- const updatedKey = key.substring(0, indexOfSeparator) === import_types4.IniSectionType.PROFILE ? key.substring(indexOfSeparator + 1) : key;
143961
+ const updatedKey = key.substring(0, indexOfSeparator) === import_types6.IniSectionType.PROFILE ? key.substring(indexOfSeparator + 1) : key;
143966
143962
  acc[updatedKey] = value;
143967
143963
  return acc;
143968
143964
  }, {
@@ -143992,7 +143988,7 @@ var require_dist_cjs52 = __commonJS2((exports, module2) => {
143992
143988
  const matches = prefixKeyRegex.exec(sectionName);
143993
143989
  if (matches) {
143994
143990
  const [, prefix2, , name4] = matches;
143995
- if (Object.values(import_types4.IniSectionType).includes(prefix2)) {
143991
+ if (Object.values(import_types6.IniSectionType).includes(prefix2)) {
143996
143992
  currentSection = [prefix2, name4].join(CONFIG_PREFIX_SEPARATOR);
143997
143993
  }
143998
143994
  } else {
@@ -144051,7 +144047,7 @@ var require_dist_cjs52 = __commonJS2((exports, module2) => {
144051
144047
  credentialsFile: parsedFiles[1]
144052
144048
  };
144053
144049
  }, "loadSharedConfigFiles");
144054
- 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");
144050
+ 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");
144055
144051
  var import_slurpFile2 = require_slurpFile();
144056
144052
  var swallowError2 = /* @__PURE__ */ __name(() => ({}), "swallowError");
144057
144053
  var loadSsoSessionData = /* @__PURE__ */ __name(async (init3 = {}) => (0, import_slurpFile2.slurpFile)(init3.configFilepath ?? getConfigFilepath()).then(parseIni).then(getSsoSessionData).catch(swallowError2), "loadSsoSessionData");
@@ -144094,7 +144090,7 @@ var require_dist_cjs53 = __commonJS2((exports, module2) => {
144094
144090
  var __toCommonJS = (mod) => __copyProps2(__defProp4({}, "__esModule", { value: true }), mod);
144095
144091
  var src_exports = {};
144096
144092
  __export4(src_exports, {
144097
- loadConfig: () => loadConfig2
144093
+ loadConfig: () => loadConfig
144098
144094
  });
144099
144095
  module2.exports = __toCommonJS(src_exports);
144100
144096
  var import_property_provider = require_dist_cjs17();
@@ -144141,7 +144137,7 @@ var require_dist_cjs53 = __commonJS2((exports, module2) => {
144141
144137
  }, "fromSharedConfigFiles");
144142
144138
  var isFunction3 = /* @__PURE__ */ __name((func2) => typeof func2 === "function", "isFunction");
144143
144139
  var fromStatic = /* @__PURE__ */ __name((defaultValue) => isFunction3(defaultValue) ? async () => await defaultValue() : (0, import_property_provider.fromStatic)(defaultValue), "fromStatic");
144144
- var loadConfig2 = /* @__PURE__ */ __name(({ environmentVariableSelector, configFileSelector, default: defaultValue }, configuration = {}) => {
144140
+ var loadConfig = /* @__PURE__ */ __name(({ environmentVariableSelector, configFileSelector, default: defaultValue }, configuration = {}) => {
144145
144141
  const { signingName, logger: logger3 } = configuration;
144146
144142
  const envOptions = { signingName, logger: logger3 };
144147
144143
  return (0, import_property_provider.memoize)((0, import_property_provider.chain)(fromEnv(environmentVariableSelector, envOptions), fromSharedConfigFiles(configFileSelector, configuration), fromStatic(defaultValue)));
@@ -145028,7 +145024,7 @@ var require_dist_cjs57 = __commonJS2((exports, module2) => {
145028
145024
  fromEnv: () => fromEnv
145029
145025
  });
145030
145026
  module2.exports = __toCommonJS(index_exports);
145031
- var import_client = require_client();
145027
+ var import_client2 = require_client();
145032
145028
  var import_property_provider = require_dist_cjs17();
145033
145029
  var ENV_KEY = "AWS_ACCESS_KEY_ID";
145034
145030
  var ENV_SECRET = "AWS_SECRET_ACCESS_KEY";
@@ -145053,7 +145049,7 @@ var require_dist_cjs57 = __commonJS2((exports, module2) => {
145053
145049
  ...credentialScope && { credentialScope },
145054
145050
  ...accountId && { accountId }
145055
145051
  };
145056
- (0, import_client.setCredentialFeature)(credentials2, "CREDENTIALS_ENV_VARS", "g");
145052
+ (0, import_client2.setCredentialFeature)(credentials2, "CREDENTIALS_ENV_VARS", "g");
145057
145053
  return credentials2;
145058
145054
  }
145059
145055
  throw new import_property_provider.CredentialsProviderError("Unable to find environment variable credentials.", { logger: init3?.logger });
@@ -147821,11 +147817,11 @@ var require_dist_cjs66 = __commonJS2((exports, module2) => {
147821
147817
  }, "validateTokenKey");
147822
147818
  var import_shared_ini_file_loader = require_dist_cjs52();
147823
147819
  var import_fs2 = __require2("fs");
147824
- var { writeFile: writeFile2 } = import_fs2.promises;
147820
+ var { writeFile } = import_fs2.promises;
147825
147821
  var writeSSOTokenToFile = /* @__PURE__ */ __name((id, ssoToken) => {
147826
147822
  const tokenFilepath = (0, import_shared_ini_file_loader.getSSOTokenFilepath)(id);
147827
147823
  const tokenString = JSON.stringify(ssoToken, null, 2);
147828
- return writeFile2(tokenFilepath, tokenString);
147824
+ return writeFile(tokenFilepath, tokenString);
147829
147825
  }, "writeSSOTokenToFile");
147830
147826
  var lastRefreshAttemptTime = /* @__PURE__ */ new Date(0);
147831
147827
  var fromSso = /* @__PURE__ */ __name((_init = {}) => async ({ callerClientConfig } = {}) => {
@@ -147920,7 +147916,7 @@ var require_dist_cjs67 = __commonJS2((exports, module2) => {
147920
147916
  var __getOwnPropNames3 = Object.getOwnPropertyNames;
147921
147917
  var __hasOwnProp3 = Object.prototype.hasOwnProperty;
147922
147918
  var __name = (target, value) => __defProp4(target, "name", { value, configurable: true });
147923
- var __esm3 = (fn2, res) => function __init() {
147919
+ var __esm5 = (fn2, res) => function __init() {
147924
147920
  return fn2 && (res = (0, fn2[__getOwnPropNames3(fn2)[0]])(fn2 = 0)), res;
147925
147921
  };
147926
147922
  var __export4 = (target, all) => {
@@ -147942,7 +147938,7 @@ var require_dist_cjs67 = __commonJS2((exports, module2) => {
147942
147938
  SSOClient: () => import_client_sso.SSOClient
147943
147939
  });
147944
147940
  var import_client_sso;
147945
- var init_loadSso = __esm3({
147941
+ var init_loadSso = __esm5({
147946
147942
  "src/loadSso.ts"() {
147947
147943
  import_client_sso = require_dist_cjs65();
147948
147944
  }
@@ -147955,7 +147951,7 @@ var require_dist_cjs67 = __commonJS2((exports, module2) => {
147955
147951
  });
147956
147952
  module2.exports = __toCommonJS(index_exports);
147957
147953
  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");
147958
- var import_client = require_client();
147954
+ var import_client2 = require_client();
147959
147955
  var import_token_providers = require_dist_cjs66();
147960
147956
  var import_property_provider = require_dist_cjs17();
147961
147957
  var import_shared_ini_file_loader = require_dist_cjs52();
@@ -148040,9 +148036,9 @@ var require_dist_cjs67 = __commonJS2((exports, module2) => {
148040
148036
  ...accountId && { accountId }
148041
148037
  };
148042
148038
  if (ssoSession) {
148043
- (0, import_client.setCredentialFeature)(credentials2, "CREDENTIALS_SSO", "s");
148039
+ (0, import_client2.setCredentialFeature)(credentials2, "CREDENTIALS_SSO", "s");
148044
148040
  } else {
148045
- (0, import_client.setCredentialFeature)(credentials2, "CREDENTIALS_SSO_LEGACY", "u");
148041
+ (0, import_client2.setCredentialFeature)(credentials2, "CREDENTIALS_SSO_LEGACY", "u");
148046
148042
  }
148047
148043
  return credentials2;
148048
148044
  }, "resolveSSOCredentials");
@@ -149222,7 +149218,7 @@ var require_sts = __commonJS2((exports, module2) => {
149222
149218
  };
149223
149219
  (0, import_smithy_client6.createAggregatedClient)(commands, STS);
149224
149220
  var import_EndpointParameters3 = require_EndpointParameters();
149225
- var import_client = require_client();
149221
+ var import_client2 = require_client();
149226
149222
  var ASSUME_ROLE_DEFAULT_REGION = "us-east-1";
149227
149223
  var getAccountIdFromAssumedRoleUser = /* @__PURE__ */ __name((assumedRoleUser) => {
149228
149224
  if (typeof assumedRoleUser?.Arn === "string") {
@@ -149274,7 +149270,7 @@ var require_sts = __commonJS2((exports, module2) => {
149274
149270
  ...Credentials2.CredentialScope && { credentialScope: Credentials2.CredentialScope },
149275
149271
  ...accountId && { accountId }
149276
149272
  };
149277
- (0, import_client.setCredentialFeature)(credentials2, "CREDENTIALS_STS_ASSUME_ROLE", "i");
149273
+ (0, import_client2.setCredentialFeature)(credentials2, "CREDENTIALS_STS_ASSUME_ROLE", "i");
149278
149274
  return credentials2;
149279
149275
  };
149280
149276
  }, "getDefaultRoleAssumer");
@@ -149311,9 +149307,9 @@ var require_sts = __commonJS2((exports, module2) => {
149311
149307
  ...accountId && { accountId }
149312
149308
  };
149313
149309
  if (accountId) {
149314
- (0, import_client.setCredentialFeature)(credentials2, "RESOLVED_ACCOUNT_ID", "T");
149310
+ (0, import_client2.setCredentialFeature)(credentials2, "RESOLVED_ACCOUNT_ID", "T");
149315
149311
  }
149316
- (0, import_client.setCredentialFeature)(credentials2, "CREDENTIALS_STS_ASSUME_ROLE_WEB_ID", "k");
149312
+ (0, import_client2.setCredentialFeature)(credentials2, "CREDENTIALS_STS_ASSUME_ROLE_WEB_ID", "k");
149317
149313
  return credentials2;
149318
149314
  };
149319
149315
  }, "getDefaultRoleAssumerWithWebIdentity");
@@ -149373,7 +149369,7 @@ var require_dist_cjs68 = __commonJS2((exports, module2) => {
149373
149369
  var import_property_provider = require_dist_cjs17();
149374
149370
  var import_child_process = __require2("child_process");
149375
149371
  var import_util3 = __require2("util");
149376
- var import_client = require_client();
149372
+ var import_client2 = require_client();
149377
149373
  var getValidatedProcessCredentials = /* @__PURE__ */ __name((profileName, data, profiles) => {
149378
149374
  if (data.Version !== 1) {
149379
149375
  throw Error(`Profile ${profileName} credential_process did not return Version 1.`);
@@ -149400,7 +149396,7 @@ var require_dist_cjs68 = __commonJS2((exports, module2) => {
149400
149396
  ...data.CredentialScope && { credentialScope: data.CredentialScope },
149401
149397
  ...accountId && { accountId }
149402
149398
  };
149403
- (0, import_client.setCredentialFeature)(credentials2, "CREDENTIALS_PROCESS", "w");
149399
+ (0, import_client2.setCredentialFeature)(credentials2, "CREDENTIALS_PROCESS", "w");
149404
149400
  return credentials2;
149405
149401
  }, "getValidatedProcessCredentials");
149406
149402
  var resolveProcessCredentials = /* @__PURE__ */ __name(async (profileName, profiles, logger3) => {
@@ -149593,7 +149589,7 @@ var require_dist_cjs70 = __commonJS2((exports, module2) => {
149593
149589
  });
149594
149590
  module2.exports = __toCommonJS(index_exports);
149595
149591
  var import_shared_ini_file_loader = require_dist_cjs52();
149596
- var import_client = require_client();
149592
+ var import_client2 = require_client();
149597
149593
  var import_property_provider = require_dist_cjs17();
149598
149594
  var resolveCredentialSource = /* @__PURE__ */ __name((credentialSource, profileName, logger3) => {
149599
149595
  const sourceProvidersMap = {
@@ -149620,7 +149616,7 @@ var require_dist_cjs70 = __commonJS2((exports, module2) => {
149620
149616
  throw new import_property_provider.CredentialsProviderError(`Unsupported credential source in profile ${profileName}. Got ${credentialSource}, expected EcsContainer or Ec2InstanceMetadata or Environment.`, { logger: logger3 });
149621
149617
  }
149622
149618
  }, "resolveCredentialSource");
149623
- var setNamedProvider = /* @__PURE__ */ __name((creds) => (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_NAMED_PROVIDER", "p"), "setNamedProvider");
149619
+ var setNamedProvider = /* @__PURE__ */ __name((creds) => (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_NAMED_PROVIDER", "p"), "setNamedProvider");
149624
149620
  var isAssumeRoleProfile = /* @__PURE__ */ __name((arg, { profile = "default", logger: logger3 } = {}) => {
149625
149621
  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 }));
149626
149622
  }, "isAssumeRoleProfile");
@@ -149662,7 +149658,7 @@ var require_dist_cjs70 = __commonJS2((exports, module2) => {
149662
149658
  [source_profile]: true
149663
149659
  }, isCredentialSourceWithoutRoleArn(profiles[source_profile] ?? {})) : (await resolveCredentialSource(profileData.credential_source, profileName, options.logger)(options))();
149664
149660
  if (isCredentialSourceWithoutRoleArn(profileData)) {
149665
- return sourceCredsProvider.then((creds) => (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SOURCE_PROFILE", "o"));
149661
+ return sourceCredsProvider.then((creds) => (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SOURCE_PROFILE", "o"));
149666
149662
  } else {
149667
149663
  const params = {
149668
149664
  RoleArn: profileData.role_arn,
@@ -149679,7 +149675,7 @@ var require_dist_cjs70 = __commonJS2((exports, module2) => {
149679
149675
  params.TokenCode = await options.mfaCodeProvider(mfa_serial);
149680
149676
  }
149681
149677
  const sourceCreds = await sourceCredsProvider;
149682
- return options.roleAssumer(sourceCreds, params).then((creds) => (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SOURCE_PROFILE", "o"));
149678
+ return options.roleAssumer(sourceCreds, params).then((creds) => (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SOURCE_PROFILE", "o"));
149683
149679
  }
149684
149680
  }, "resolveAssumeRoleCredentials");
149685
149681
  var isCredentialSourceWithoutRoleArn = /* @__PURE__ */ __name((section) => {
@@ -149689,7 +149685,7 @@ var require_dist_cjs70 = __commonJS2((exports, module2) => {
149689
149685
  var resolveProcessCredentials = /* @__PURE__ */ __name(async (options, profile) => Promise.resolve().then(() => __toESM3(require_dist_cjs68())).then(({ fromProcess }) => fromProcess({
149690
149686
  ...options,
149691
149687
  profile
149692
- })().then((creds) => (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_PROCESS", "v"))), "resolveProcessCredentials");
149688
+ })().then((creds) => (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_PROCESS", "v"))), "resolveProcessCredentials");
149693
149689
  var resolveSsoCredentials = /* @__PURE__ */ __name(async (profile, profileData, options = {}) => {
149694
149690
  const { fromSSO } = await Promise.resolve().then(() => __toESM3(require_dist_cjs67()));
149695
149691
  return fromSSO({
@@ -149699,9 +149695,9 @@ var require_dist_cjs70 = __commonJS2((exports, module2) => {
149699
149695
  clientConfig: options.clientConfig
149700
149696
  })().then((creds) => {
149701
149697
  if (profileData.sso_session) {
149702
- return (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SSO", "r");
149698
+ return (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SSO", "r");
149703
149699
  } else {
149704
- return (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SSO_LEGACY", "t");
149700
+ return (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_SSO_LEGACY", "t");
149705
149701
  }
149706
149702
  });
149707
149703
  }, "resolveSsoCredentials");
@@ -149716,7 +149712,7 @@ var require_dist_cjs70 = __commonJS2((exports, module2) => {
149716
149712
  ...profile.aws_credential_scope && { credentialScope: profile.aws_credential_scope },
149717
149713
  ...profile.aws_account_id && { accountId: profile.aws_account_id }
149718
149714
  };
149719
- return (0, import_client.setCredentialFeature)(credentials2, "CREDENTIALS_PROFILE", "n");
149715
+ return (0, import_client2.setCredentialFeature)(credentials2, "CREDENTIALS_PROFILE", "n");
149720
149716
  }, "resolveStaticCredentials");
149721
149717
  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");
149722
149718
  var resolveWebIdentityCredentials = /* @__PURE__ */ __name(async (profile, options) => Promise.resolve().then(() => __toESM3(require_dist_cjs69())).then(({ fromTokenFile: fromTokenFile2 }) => fromTokenFile2({
@@ -149726,7 +149722,7 @@ var require_dist_cjs70 = __commonJS2((exports, module2) => {
149726
149722
  roleAssumerWithWebIdentity: options.roleAssumerWithWebIdentity,
149727
149723
  logger: options.logger,
149728
149724
  parentClientConfig: options.parentClientConfig
149729
- })().then((creds) => (0, import_client.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_STS_WEB_ID_TOKEN", "q"))), "resolveWebIdentityCredentials");
149725
+ })().then((creds) => (0, import_client2.setCredentialFeature)(creds, "CREDENTIALS_PROFILE_STS_WEB_ID_TOKEN", "q"))), "resolveWebIdentityCredentials");
149730
149726
  var resolveProfileData = /* @__PURE__ */ __name(async (profileName, profiles, options, visitedProfiles = {}, isAssumeRoleRecursiveCall = false) => {
149731
149727
  const data = profiles[profileName];
149732
149728
  if (Object.keys(visitedProfiles).length > 0 && isStaticCredsProfile(data)) {
@@ -168490,7 +168486,7 @@ var require_lodash2 = __commonJS2((exports, module2) => {
168490
168486
  }
168491
168487
  }
168492
168488
  function baseKeysIn(object) {
168493
- if (!isObject2(object)) {
168489
+ if (!isObject4(object)) {
168494
168490
  return nativeKeysIn(object);
168495
168491
  }
168496
168492
  var isProto = isPrototype(object), result = [];
@@ -168550,7 +168546,7 @@ var require_lodash2 = __commonJS2((exports, module2) => {
168550
168546
  return !!length && (typeof value == "number" || reIsUint.test(value)) && (value > -1 && value % 1 == 0 && value < length);
168551
168547
  }
168552
168548
  function isIterateeCall(value, index6, object) {
168553
- if (!isObject2(object)) {
168549
+ if (!isObject4(object)) {
168554
168550
  return false;
168555
168551
  }
168556
168552
  var type = typeof index6;
@@ -168586,13 +168582,13 @@ var require_lodash2 = __commonJS2((exports, module2) => {
168586
168582
  return isObjectLike2(value) && isArrayLike(value);
168587
168583
  }
168588
168584
  function isFunction3(value) {
168589
- var tag2 = isObject2(value) ? objectToString.call(value) : "";
168585
+ var tag2 = isObject4(value) ? objectToString.call(value) : "";
168590
168586
  return tag2 == funcTag || tag2 == genTag;
168591
168587
  }
168592
168588
  function isLength(value) {
168593
168589
  return typeof value == "number" && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
168594
168590
  }
168595
- function isObject2(value) {
168591
+ function isObject4(value) {
168596
168592
  var type = typeof value;
168597
168593
  return !!value && (type == "object" || type == "function");
168598
168594
  }
@@ -168630,13 +168626,13 @@ var require_lodash3 = __commonJS2((exports, module2) => {
168630
168626
  return isObjectLike2(value) && isArrayLike(value);
168631
168627
  }
168632
168628
  function isFunction3(value) {
168633
- var tag2 = isObject2(value) ? objectToString.call(value) : "";
168629
+ var tag2 = isObject4(value) ? objectToString.call(value) : "";
168634
168630
  return tag2 == funcTag || tag2 == genTag;
168635
168631
  }
168636
168632
  function isLength(value) {
168637
168633
  return typeof value == "number" && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
168638
168634
  }
168639
- function isObject2(value) {
168635
+ function isObject4(value) {
168640
168636
  var type = typeof value;
168641
168637
  return !!value && (type == "object" || type == "function");
168642
168638
  }
@@ -173727,7 +173723,7 @@ var REDIS_PRESENCE_KEY_PREFIX;
173727
173723
  var REDIS_ONLINE_USERS_KEY;
173728
173724
  var REDIS_POSITION_KEY_PREFIX;
173729
173725
  var REDIS_MAP_PLAYERS_KEY_PREFIX;
173730
- var init_constants = __esm(() => {
173726
+ var init_constants3 = __esm(() => {
173731
173727
  HUB_PREFIX = process.env.HUB_PREFIX ?? "hub:";
173732
173728
  MAP_PREFIX = process.env.MAP_PREFIX ?? "map_";
173733
173729
  PRESENCE_CHANNEL_NAME = process.env.PRESENCE_CHANNEL_NAME ?? "presence";
@@ -173875,7 +173871,7 @@ var import_ioredis;
173875
173871
  var client = null;
173876
173872
  var init_client = __esm(() => {
173877
173873
  init_src();
173878
- init_constants();
173874
+ init_constants3();
173879
173875
  import_ioredis = __toESM2(require_built3(), 1);
173880
173876
  });
173881
173877
  async function setUserOnline(userId, metadata2) {
@@ -173986,7 +173982,7 @@ async function updateUserNameplateBg(userId, color) {
173986
173982
  }
173987
173983
  var init_presence = __esm(() => {
173988
173984
  init_src();
173989
- init_constants();
173985
+ init_constants3();
173990
173986
  init_client();
173991
173987
  });
173992
173988
  var init_redis = __esm(() => {
@@ -174031,11 +174027,11 @@ function extractTokenFromUrl(url) {
174031
174027
  return null;
174032
174028
  }
174033
174029
  }
174034
- var init_auth = __esm(() => {
174030
+ var init_auth3 = __esm(() => {
174035
174031
  init_esm2();
174036
174032
  init_src();
174037
174033
  });
174038
- function loadConfig2(options = {}) {
174034
+ function loadConfig(options = {}) {
174039
174035
  const rawRedisHost = process.env.REDIS_HOST || "localhost";
174040
174036
  const rawRedisPort = parseInt(process.env.REDIS_PORT || "6379", 10);
174041
174037
  const rawRedisPassword = process.env.REDIS_PASSWORD;
@@ -174080,7 +174076,7 @@ __export(exports_sandbox, {
174080
174076
  createSandboxRealtimeServer: () => createSandboxRealtimeServer
174081
174077
  });
174082
174078
  async function createSandboxRealtimeServer(options = {}) {
174083
- const config2 = await loadConfig2(options);
174079
+ const config2 = await loadConfig(options);
174084
174080
  const httpServer = http.createServer((req, res) => {
174085
174081
  if (req.url?.endsWith(config2.healthCheckPath)) {
174086
174082
  const body2 = JSON.stringify({
@@ -174162,7 +174158,7 @@ var init_sandbox = __esm(() => {
174162
174158
  init_wrapper();
174163
174159
  init_src();
174164
174160
  init_infrastructure2();
174165
- init_auth();
174161
+ init_auth3();
174166
174162
  init_config4();
174167
174163
  });
174168
174164
  var inMemoryUsers;
@@ -174248,7 +174244,7 @@ var playerPositions;
174248
174244
  var PlayerPositionService;
174249
174245
  var init_player_position = __esm(() => {
174250
174246
  init_src();
174251
- init_constants();
174247
+ init_constants3();
174252
174248
  init_infrastructure2();
174253
174249
  playerPositions = new Map;
174254
174250
  PlayerPositionService = {
@@ -174378,7 +174374,7 @@ function computeInitialChannelKey(gameId, requested) {
174378
174374
  return `${HUB_PREFIX}${channel}`;
174379
174375
  }
174380
174376
  var init_channel_keys = __esm(() => {
174381
- init_constants();
174377
+ init_constants3();
174382
174378
  });
174383
174379
  function ensureAuthenticated(ws) {
174384
174380
  if (!ws.data.isAuthenticated || !ws.data.userId) {
@@ -174397,7 +174393,7 @@ function checkMatchingPlayerId(ws, payload) {
174397
174393
  }
174398
174394
  var init_websocket = __esm(() => {
174399
174395
  init_src();
174400
- init_constants();
174396
+ init_constants3();
174401
174397
  });
174402
174398
  function extractUserId(pathname) {
174403
174399
  const match2 = pathname.match(/^\/presence\/([^/]+)$/);
@@ -174733,7 +174729,7 @@ async function handleWebSocketUpgrade(req, server, config2) {
174733
174729
  }
174734
174730
  var init_websocket2 = __esm(() => {
174735
174731
  init_src();
174736
- init_auth();
174732
+ init_auth3();
174737
174733
  init_response();
174738
174734
  });
174739
174735
  var init_http2 = __esm(() => {
@@ -174912,7 +174908,7 @@ var init_open = __esm(() => {
174912
174908
  init_src();
174913
174909
  init_presence4();
174914
174910
  init_services2();
174915
- init_constants();
174911
+ init_constants3();
174916
174912
  init_events();
174917
174913
  init_infrastructure2();
174918
174914
  init_utils13();
@@ -175136,7 +175132,7 @@ var init_websocket3 = __esm(() => {
175136
175132
  init_close();
175137
175133
  });
175138
175134
  async function createRealtimeServer(options = {}) {
175139
- const config2 = loadConfig2(options);
175135
+ const config2 = loadConfig(options);
175140
175136
  const { redisEnabled, positionSyncInterval } = await initializeServer(config2);
175141
175137
  const server = Bun.serve({
175142
175138
  port: config2.port,
@@ -178272,7 +178268,7 @@ function sql(strings, ...params) {
178272
178268
  return new SQL([new StringChunk(str)]);
178273
178269
  }
178274
178270
  sql22.raw = raw2;
178275
- function join6(chunks, separator) {
178271
+ function join5(chunks, separator) {
178276
178272
  const result = [];
178277
178273
  for (const [i22, chunk] of chunks.entries()) {
178278
178274
  if (i22 > 0 && separator !== undefined) {
@@ -178282,7 +178278,7 @@ function sql(strings, ...params) {
178282
178278
  }
178283
178279
  return new SQL(result);
178284
178280
  }
178285
- sql22.join = join6;
178281
+ sql22.join = join5;
178286
178282
  function identifier(value) {
178287
178283
  return new Name(value);
178288
178284
  }
@@ -181195,7 +181191,7 @@ class PgSelectQueryBuilderBase extends TypedQueryBuilder {
181195
181191
  return (table, on) => {
181196
181192
  const baseTableName = this.tableName;
181197
181193
  const tableName = getTableLikeName(table);
181198
- if (typeof tableName === "string" && this.config.joins?.some((join6) => join6.alias === tableName)) {
181194
+ if (typeof tableName === "string" && this.config.joins?.some((join5) => join5.alias === tableName)) {
181199
181195
  throw new Error(`Alias "${tableName}" is already used in this query`);
181200
181196
  }
181201
181197
  if (!this.isPartialSelect) {
@@ -181700,7 +181696,7 @@ class PgUpdateBase extends QueryPromise {
181700
181696
  createJoin(joinType) {
181701
181697
  return (table, on) => {
181702
181698
  const tableName = getTableLikeName(table);
181703
- if (typeof tableName === "string" && this.config.joins.some((join6) => join6.alias === tableName)) {
181699
+ if (typeof tableName === "string" && this.config.joins.some((join5) => join5.alias === tableName)) {
181704
181700
  throw new Error(`Alias "${tableName}" is already used in this query`);
181705
181701
  }
181706
181702
  if (typeof on === "function") {
@@ -181750,10 +181746,10 @@ class PgUpdateBase extends QueryPromise {
181750
181746
  const fromFields = this.getTableLikeFields(this.config.from);
181751
181747
  fields[tableName] = fromFields;
181752
181748
  }
181753
- for (const join6 of this.config.joins) {
181754
- const tableName2 = getTableLikeName(join6.table);
181755
- if (typeof tableName2 === "string" && !is(join6.table, SQL)) {
181756
- const fromFields = this.getTableLikeFields(join6.table);
181749
+ for (const join5 of this.config.joins) {
181750
+ const tableName2 = getTableLikeName(join5.table);
181751
+ if (typeof tableName2 === "string" && !is(join5.table, SQL)) {
181752
+ const fromFields = this.getTableLikeFields(join5.table);
181757
181753
  fields[tableName2] = fromFields;
181758
181754
  }
181759
181755
  }
@@ -182754,12 +182750,17 @@ var timebackXpEvents = pgTable("timeback_xp_event", {
182754
182750
  }, (table) => [uniqueIndex("timeback_xp_events_source_id_idx").on(table.source, table.sourceId)]);
182755
182751
  var gameTimebackIntegrations = pgTable("game_timeback_integrations", {
182756
182752
  id: uuid("id").primaryKey().defaultRandom(),
182757
- gameId: uuid("game_id").notNull().unique().references(() => games.id, { onDelete: "cascade" }),
182753
+ gameId: uuid("game_id").notNull().references(() => games.id, { onDelete: "cascade" }),
182758
182754
  courseId: text("course_id").notNull(),
182755
+ grade: integer("grade").notNull(),
182756
+ subject: text("subject").notNull(),
182757
+ totalXp: integer("total_xp"),
182759
182758
  lastVerifiedAt: timestamp("last_verified_at", { withTimezone: true }),
182760
182759
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
182761
182760
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
182762
- });
182761
+ }, (table) => [
182762
+ uniqueIndex("game_timeback_integrations_game_grade_subject_idx").on(table.gameId, table.grade, table.subject)
182763
+ ]);
182763
182764
  var achievementScopeEnum = pgEnum("achievement_scope", [
182764
182765
  "daily",
182765
182766
  "weekly",
@@ -189296,6 +189297,290 @@ var __export3 = (target, all) => {
189296
189297
  set: (newValue) => all[name3] = () => newValue
189297
189298
  });
189298
189299
  };
189300
+ var __esm3 = (fn2, res) => () => (fn2 && (res = fn2(fn2 = 0)), res);
189301
+ var init_auth = () => {};
189302
+ var PLAYCADEMY_BASE_URLS;
189303
+ var init_domains = __esm3(() => {
189304
+ PLAYCADEMY_BASE_URLS = {
189305
+ production: "https://hub.playcademy.net",
189306
+ staging: "https://hub.dev.playcademy.net"
189307
+ };
189308
+ });
189309
+ var init_env_vars = () => {};
189310
+ var ITEM_SLUGS2;
189311
+ var CURRENCIES2;
189312
+ var BADGES2;
189313
+ var init_overworld = __esm3(() => {
189314
+ ITEM_SLUGS2 = {
189315
+ PLAYCADEMY_CREDITS: "PLAYCADEMY_CREDITS",
189316
+ PLAYCADEMY_XP: "PLAYCADEMY_XP",
189317
+ FOUNDING_MEMBER_BADGE: "FOUNDING_MEMBER_BADGE",
189318
+ EARLY_ADOPTER_BADGE: "EARLY_ADOPTER_BADGE",
189319
+ FIRST_GAME_BADGE: "FIRST_GAME_BADGE",
189320
+ COMMON_SWORD: "COMMON_SWORD",
189321
+ SMALL_HEALTH_POTION: "SMALL_HEALTH_POTION",
189322
+ SMALL_BACKPACK: "SMALL_BACKPACK",
189323
+ LAVA_LAMP: "LAVA_LAMP",
189324
+ BOOMBOX: "BOOMBOX",
189325
+ CABIN_BED: "CABIN_BED"
189326
+ };
189327
+ CURRENCIES2 = {
189328
+ PRIMARY: ITEM_SLUGS2.PLAYCADEMY_CREDITS,
189329
+ XP: ITEM_SLUGS2.PLAYCADEMY_XP
189330
+ };
189331
+ BADGES2 = {
189332
+ FOUNDING_MEMBER: ITEM_SLUGS2.FOUNDING_MEMBER_BADGE,
189333
+ EARLY_ADOPTER: ITEM_SLUGS2.EARLY_ADOPTER_BADGE,
189334
+ FIRST_GAME: ITEM_SLUGS2.FIRST_GAME_BADGE
189335
+ };
189336
+ });
189337
+ var init_timeback = () => {};
189338
+ var init_workers = () => {};
189339
+ var init_src2 = __esm3(() => {
189340
+ init_auth();
189341
+ init_domains();
189342
+ init_env_vars();
189343
+ init_overworld();
189344
+ init_timeback();
189345
+ init_workers();
189346
+ });
189347
+ var TIMEBACK_API_URLS;
189348
+ var TIMEBACK_AUTH_URLS;
189349
+ var CALIPER_API_URLS;
189350
+ var ONEROSTER_ENDPOINTS;
189351
+ var CALIPER_ENDPOINTS;
189352
+ var createOneRosterUrls = (baseUrl) => {
189353
+ const effective = baseUrl || TIMEBACK_API_URLS.production;
189354
+ const base = effective.replace(/\/$/, "");
189355
+ return {
189356
+ user: (userId) => `${base}${ONEROSTER_ENDPOINTS.users}/${userId}`,
189357
+ course: (courseId) => `${base}${ONEROSTER_ENDPOINTS.courses}/${courseId}`,
189358
+ componentResource: (resourceId) => `${base}${ONEROSTER_ENDPOINTS.componentResources}/${resourceId}`
189359
+ };
189360
+ };
189361
+ var CALIPER_CONSTANTS;
189362
+ var TIMEBACK_EVENT_TYPES;
189363
+ var TIMEBACK_ACTIONS;
189364
+ var TIMEBACK_TYPES;
189365
+ var ACTIVITY_METRIC_TYPES;
189366
+ var TIME_METRIC_TYPES;
189367
+ var TIMEBACK_SUBJECTS;
189368
+ var TIMEBACK_GRADE_LEVELS;
189369
+ var TIMEBACK_GRADE_LEVEL_LABELS;
189370
+ var CALIPER_SUBJECTS;
189371
+ var ONEROSTER_STATUS;
189372
+ var SCORE_STATUS;
189373
+ var ENV_VARS;
189374
+ var HTTP_DEFAULTS;
189375
+ var AUTH_DEFAULTS;
189376
+ var CACHE_DEFAULTS;
189377
+ var CONFIG_DEFAULTS;
189378
+ var DEFAULT_PLAYCADEMY_ORGANIZATION_ID;
189379
+ var PLAYCADEMY_DEFAULTS;
189380
+ var RESOURCE_DEFAULTS;
189381
+ var HTTP_STATUS;
189382
+ var ERROR_NAMES;
189383
+ var init_constants = __esm3(() => {
189384
+ init_src2();
189385
+ TIMEBACK_API_URLS = {
189386
+ production: "https://api.alpha-1edtech.ai",
189387
+ staging: "https://api.staging.alpha-1edtech.com"
189388
+ };
189389
+ TIMEBACK_AUTH_URLS = {
189390
+ production: "https://prod-beyond-timeback-api-2-idp.auth.us-east-1.amazoncognito.com",
189391
+ staging: "https://alpha-auth-development-idp.auth.us-west-2.amazoncognito.com"
189392
+ };
189393
+ CALIPER_API_URLS = {
189394
+ production: "https://caliper.alpha-1edtech.ai",
189395
+ staging: "https://caliper-staging.alpha-1edtech.com"
189396
+ };
189397
+ ONEROSTER_ENDPOINTS = {
189398
+ organizations: "/ims/oneroster/rostering/v1p2/orgs",
189399
+ courses: "/ims/oneroster/rostering/v1p2/courses",
189400
+ courseComponents: "/ims/oneroster/rostering/v1p2/courses/components",
189401
+ resources: "/ims/oneroster/resources/v1p2/resources",
189402
+ componentResources: "/ims/oneroster/rostering/v1p2/courses/component-resources",
189403
+ classes: "/ims/oneroster/rostering/v1p2/classes",
189404
+ enrollments: "/ims/oneroster/rostering/v1p2/enrollments",
189405
+ assessmentLineItems: "/ims/oneroster/gradebook/v1p2/assessmentLineItems",
189406
+ assessmentResults: "/ims/oneroster/gradebook/v1p2/assessmentResults",
189407
+ users: "/ims/oneroster/rostering/v1p2/users"
189408
+ };
189409
+ CALIPER_ENDPOINTS = {
189410
+ events: "/caliper/event",
189411
+ validate: "/caliper/event/validate"
189412
+ };
189413
+ CALIPER_CONSTANTS = {
189414
+ context: "http://purl.imsglobal.org/ctx/caliper/v1p2",
189415
+ profile: "TimebackProfile",
189416
+ dataVersion: "http://purl.imsglobal.org/ctx/caliper/v1p2"
189417
+ };
189418
+ TIMEBACK_EVENT_TYPES = {
189419
+ activityEvent: "ActivityEvent",
189420
+ timeSpentEvent: "TimeSpentEvent"
189421
+ };
189422
+ TIMEBACK_ACTIONS = {
189423
+ completed: "Completed",
189424
+ spentTime: "SpentTime"
189425
+ };
189426
+ TIMEBACK_TYPES = {
189427
+ user: "TimebackUser",
189428
+ activityContext: "TimebackActivityContext",
189429
+ activityMetricsCollection: "TimebackActivityMetricsCollection",
189430
+ timeSpentMetricsCollection: "TimebackTimeSpentMetricsCollection"
189431
+ };
189432
+ ACTIVITY_METRIC_TYPES = {
189433
+ totalQuestions: "totalQuestions",
189434
+ correctQuestions: "correctQuestions",
189435
+ xpEarned: "xpEarned",
189436
+ masteredUnits: "masteredUnits"
189437
+ };
189438
+ TIME_METRIC_TYPES = {
189439
+ active: "active",
189440
+ inactive: "inactive",
189441
+ waste: "waste",
189442
+ unknown: "unknown",
189443
+ antiPattern: "anti-pattern"
189444
+ };
189445
+ TIMEBACK_SUBJECTS = [
189446
+ "Math",
189447
+ "FastMath",
189448
+ "Science",
189449
+ "Social Studies",
189450
+ "Language",
189451
+ "Reading",
189452
+ "Vocabulary",
189453
+ "Writing"
189454
+ ];
189455
+ TIMEBACK_GRADE_LEVELS = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
189456
+ TIMEBACK_GRADE_LEVEL_LABELS = {
189457
+ "-1": "pre-k",
189458
+ "0": "kindergarten",
189459
+ "1": "1st grade",
189460
+ "2": "2nd grade",
189461
+ "3": "3rd grade",
189462
+ "4": "4th grade",
189463
+ "5": "5th grade",
189464
+ "6": "6th grade",
189465
+ "7": "7th grade",
189466
+ "8": "8th grade",
189467
+ "9": "9th grade",
189468
+ "10": "10th grade",
189469
+ "11": "11th grade",
189470
+ "12": "12th grade",
189471
+ "13": "AP"
189472
+ };
189473
+ CALIPER_SUBJECTS = {
189474
+ Reading: "Reading",
189475
+ Language: "Language",
189476
+ Vocabulary: "Vocabulary",
189477
+ SocialStudies: "Social Studies",
189478
+ Writing: "Writing",
189479
+ Science: "Science",
189480
+ FastMath: "FastMath",
189481
+ Math: "Math",
189482
+ None: "None"
189483
+ };
189484
+ ONEROSTER_STATUS = {
189485
+ active: "active",
189486
+ toBeDeleted: "tobedeleted"
189487
+ };
189488
+ SCORE_STATUS = {
189489
+ exempt: "exempt",
189490
+ fullyGraded: "fully graded",
189491
+ notSubmitted: "not submitted",
189492
+ partiallyGraded: "partially graded",
189493
+ submitted: "submitted"
189494
+ };
189495
+ ENV_VARS = {
189496
+ clientId: "TIMEBACK_CLIENT_ID",
189497
+ clientSecret: "TIMEBACK_CLIENT_SECRET",
189498
+ baseUrl: "TIMEBACK_BASE_URL",
189499
+ environment: "TIMEBACK_ENVIRONMENT",
189500
+ vendorResourceId: "TIMEBACK_VENDOR_RESOURCE_ID",
189501
+ launchBaseUrl: "GAME_URL"
189502
+ };
189503
+ HTTP_DEFAULTS = {
189504
+ timeout: 30000,
189505
+ retries: 3,
189506
+ retryBackoffBase: 2
189507
+ };
189508
+ AUTH_DEFAULTS = {
189509
+ tokenCacheDuration: 50000
189510
+ };
189511
+ CACHE_DEFAULTS = {
189512
+ defaultTTL: 600000,
189513
+ defaultMaxSize: 500,
189514
+ defaultName: "TimebackCache",
189515
+ studentTTL: 600000,
189516
+ studentMaxSize: 500,
189517
+ assessmentTTL: 1800000,
189518
+ assessmentMaxSize: 200
189519
+ };
189520
+ CONFIG_DEFAULTS = {
189521
+ fileNames: ["timeback.config.js", "timeback.config.json"]
189522
+ };
189523
+ DEFAULT_PLAYCADEMY_ORGANIZATION_ID = process.env.TIMEBACK_ORG_SOURCE_ID || "PLAYCADEMY";
189524
+ PLAYCADEMY_DEFAULTS = {
189525
+ organization: DEFAULT_PLAYCADEMY_ORGANIZATION_ID,
189526
+ launchBaseUrls: PLAYCADEMY_BASE_URLS
189527
+ };
189528
+ RESOURCE_DEFAULTS = {
189529
+ organization: {
189530
+ name: "Playcademy Studios",
189531
+ type: "department"
189532
+ },
189533
+ course: {
189534
+ gradingScheme: "STANDARD",
189535
+ level: {
189536
+ elementary: "Elementary",
189537
+ middle: "Middle",
189538
+ high: "High",
189539
+ ap: "AP"
189540
+ },
189541
+ metadata: {
189542
+ goals: {
189543
+ dailyXp: 50,
189544
+ dailyLessons: 3
189545
+ },
189546
+ metrics: {
189547
+ totalXp: 1000,
189548
+ totalLessons: 50
189549
+ }
189550
+ }
189551
+ },
189552
+ component: {
189553
+ sortOrder: 1,
189554
+ prerequisiteCriteria: "ALL"
189555
+ },
189556
+ resource: {
189557
+ vendorId: "playcademy",
189558
+ roles: ["primary"],
189559
+ importance: "primary",
189560
+ metadata: {
189561
+ type: "interactive",
189562
+ toolProvider: "Playcademy",
189563
+ instructionalMethod: "exploratory",
189564
+ language: "en-US"
189565
+ }
189566
+ },
189567
+ componentResource: {
189568
+ sortOrder: 1,
189569
+ lessonType: "quiz"
189570
+ }
189571
+ };
189572
+ HTTP_STATUS = {
189573
+ CLIENT_ERROR_MIN: 400,
189574
+ CLIENT_ERROR_MAX: 500,
189575
+ SERVER_ERROR_MIN: 500
189576
+ };
189577
+ ERROR_NAMES = {
189578
+ timebackAuth: "TimebackAuthError",
189579
+ timebackApi: "TimebackApiError",
189580
+ timebackConfig: "TimebackConfigError",
189581
+ timebackSdk: "TimebackSDKError"
189582
+ };
189583
+ });
189299
189584
  function deriveSourcedIds(courseId) {
189300
189585
  return {
189301
189586
  course: courseId,
@@ -189311,7 +189596,8 @@ __export3(exports_verify, {
189311
189596
  });
189312
189597
  async function fetchTimebackConfig(client2, courseId) {
189313
189598
  const sourcedIds = deriveSourcedIds(courseId);
189314
- const [course, component, resource, componentResource] = await Promise.all([
189599
+ const [org, course, component, resource, componentResource] = await Promise.all([
189600
+ client2.oneroster.organizations.get(PLAYCADEMY_DEFAULTS.organization),
189315
189601
  client2.oneroster.courses.get(sourcedIds.course),
189316
189602
  client2.oneroster.courseComponents.get(sourcedIds.component),
189317
189603
  client2.oneroster.resources.get(sourcedIds.resource),
@@ -189319,9 +189605,9 @@ async function fetchTimebackConfig(client2, courseId) {
189319
189605
  ]);
189320
189606
  return {
189321
189607
  organization: {
189322
- name: "Playcademy Studios",
189323
- type: "department",
189324
- identifier: "PLAYCADEMY"
189608
+ name: org.name,
189609
+ type: org.type,
189610
+ identifier: org.identifier || PLAYCADEMY_DEFAULTS.organization
189325
189611
  },
189326
189612
  course: {
189327
189613
  title: course.title || "",
@@ -189381,145 +189667,10 @@ async function verifyTimebackResources(client2, courseId) {
189381
189667
  }
189382
189668
  };
189383
189669
  }
189384
- var init_verify5 = () => {};
189385
- var ITEM_SLUGS2 = {
189386
- PLAYCADEMY_CREDITS: "PLAYCADEMY_CREDITS",
189387
- PLAYCADEMY_XP: "PLAYCADEMY_XP",
189388
- FOUNDING_MEMBER_BADGE: "FOUNDING_MEMBER_BADGE",
189389
- EARLY_ADOPTER_BADGE: "EARLY_ADOPTER_BADGE",
189390
- FIRST_GAME_BADGE: "FIRST_GAME_BADGE",
189391
- COMMON_SWORD: "COMMON_SWORD",
189392
- SMALL_HEALTH_POTION: "SMALL_HEALTH_POTION",
189393
- SMALL_BACKPACK: "SMALL_BACKPACK",
189394
- LAVA_LAMP: "LAVA_LAMP",
189395
- BOOMBOX: "BOOMBOX",
189396
- CABIN_BED: "CABIN_BED"
189397
- };
189398
- var CURRENCIES2 = {
189399
- PRIMARY: ITEM_SLUGS2.PLAYCADEMY_CREDITS,
189400
- XP: ITEM_SLUGS2.PLAYCADEMY_XP
189401
- };
189402
- var BADGES2 = {
189403
- FOUNDING_MEMBER: ITEM_SLUGS2.FOUNDING_MEMBER_BADGE,
189404
- EARLY_ADOPTER: ITEM_SLUGS2.EARLY_ADOPTER_BADGE,
189405
- FIRST_GAME: ITEM_SLUGS2.FIRST_GAME_BADGE
189406
- };
189407
- var TIMEBACK_API_URLS = {
189408
- production: "https://api.alpha-1edtech.ai",
189409
- staging: "https://api.staging.alpha-1edtech.com"
189410
- };
189411
- var TIMEBACK_AUTH_URLS = {
189412
- production: "https://prod-beyond-timeback-api-2-idp.auth.us-east-1.amazoncognito.com",
189413
- staging: "https://alpha-auth-development-idp.auth.us-west-2.amazoncognito.com"
189414
- };
189415
- var CALIPER_API_URLS = {
189416
- production: "https://caliper.alpha-1edtech.ai",
189417
- staging: "https://caliper-staging.alpha-1edtech.com"
189418
- };
189419
- var ONEROSTER_ENDPOINTS = {
189420
- organizations: "/ims/oneroster/rostering/v1p2/orgs",
189421
- courses: "/ims/oneroster/rostering/v1p2/courses",
189422
- courseComponents: "/ims/oneroster/rostering/v1p2/courses/components",
189423
- resources: "/ims/oneroster/resources/v1p2/resources",
189424
- componentResources: "/ims/oneroster/rostering/v1p2/courses/component-resources",
189425
- classes: "/ims/oneroster/rostering/v1p2/classes",
189426
- enrollments: "/ims/oneroster/rostering/v1p2/enrollments",
189427
- assessmentLineItems: "/ims/oneroster/gradebook/v1p2/assessmentLineItems",
189428
- assessmentResults: "/ims/oneroster/gradebook/v1p2/assessmentResults",
189429
- users: "/ims/oneroster/rostering/v1p2/users"
189430
- };
189431
- var CALIPER_ENDPOINTS = {
189432
- events: "/caliper/event",
189433
- validate: "/caliper/event/validate"
189434
- };
189435
- var createOneRosterUrls = (baseUrl) => {
189436
- const effective = baseUrl || TIMEBACK_API_URLS.production;
189437
- const base = effective.replace(/\/$/, "");
189438
- return {
189439
- user: (userId) => `${base}${ONEROSTER_ENDPOINTS.users}/${userId}`,
189440
- course: (courseId) => `${base}${ONEROSTER_ENDPOINTS.courses}/${courseId}`,
189441
- componentResource: (resourceId) => `${base}${ONEROSTER_ENDPOINTS.componentResources}/${resourceId}`
189442
- };
189443
- };
189444
- var CALIPER_CONSTANTS = {
189445
- context: "http://purl.imsglobal.org/ctx/caliper/v1p2",
189446
- profile: "TimebackProfile",
189447
- dataVersion: "http://purl.imsglobal.org/ctx/caliper/v1p2"
189448
- };
189449
- var TIMEBACK_EVENT_TYPES = {
189450
- activityEvent: "ActivityEvent",
189451
- timeSpentEvent: "TimeSpentEvent"
189452
- };
189453
- var TIMEBACK_ACTIONS = {
189454
- completed: "Completed",
189455
- spentTime: "SpentTime"
189456
- };
189457
- var TIMEBACK_TYPES = {
189458
- user: "TimebackUser",
189459
- activityContext: "TimebackActivityContext",
189460
- activityMetricsCollection: "TimebackActivityMetricsCollection",
189461
- timeSpentMetricsCollection: "TimebackTimeSpentMetricsCollection"
189462
- };
189463
- var ACTIVITY_METRIC_TYPES = {
189464
- totalQuestions: "totalQuestions",
189465
- correctQuestions: "correctQuestions",
189466
- xpEarned: "xpEarned",
189467
- masteredUnits: "masteredUnits"
189468
- };
189469
- var TIME_METRIC_TYPES = {
189470
- active: "active",
189471
- inactive: "inactive",
189472
- waste: "waste",
189473
- unknown: "unknown",
189474
- antiPattern: "anti-pattern"
189475
- };
189476
- var ONEROSTER_STATUS = {
189477
- active: "active",
189478
- toBeDeleted: "tobedeleted"
189479
- };
189480
- var SCORE_STATUS = {
189481
- exempt: "exempt",
189482
- fullyGraded: "fully graded",
189483
- notSubmitted: "not submitted",
189484
- partiallyGraded: "partially graded",
189485
- submitted: "submitted"
189486
- };
189487
- var ENV_VARS = {
189488
- clientId: "TIMEBACK_CLIENT_ID",
189489
- clientSecret: "TIMEBACK_CLIENT_SECRET",
189490
- baseUrl: "TIMEBACK_BASE_URL",
189491
- environment: "TIMEBACK_ENVIRONMENT",
189492
- vendorResourceId: "TIMEBACK_VENDOR_RESOURCE_ID",
189493
- launchBaseUrl: "GAME_URL"
189494
- };
189495
- var HTTP_DEFAULTS = {
189496
- timeout: 30000,
189497
- retries: 3,
189498
- retryBackoffBase: 2
189499
- };
189500
- var AUTH_DEFAULTS = {
189501
- tokenCacheDuration: 50000
189502
- };
189503
- var CACHE_DEFAULTS = {
189504
- defaultTTL: 600000,
189505
- defaultMaxSize: 500,
189506
- defaultName: "TimebackCache",
189507
- studentTTL: 600000,
189508
- studentMaxSize: 500,
189509
- assessmentTTL: 1800000,
189510
- assessmentMaxSize: 200
189511
- };
189512
- var HTTP_STATUS = {
189513
- CLIENT_ERROR_MIN: 400,
189514
- CLIENT_ERROR_MAX: 500,
189515
- SERVER_ERROR_MIN: 500
189516
- };
189517
- var ERROR_NAMES = {
189518
- timebackAuth: "TimebackAuthError",
189519
- timebackApi: "TimebackApiError",
189520
- timebackConfig: "TimebackConfigError",
189521
- timebackSdk: "TimebackSDKError"
189522
- };
189670
+ var init_verify5 = __esm3(() => {
189671
+ init_constants();
189672
+ });
189673
+ init_constants();
189523
189674
 
189524
189675
  class TimebackError extends Error {
189525
189676
  constructor(message22) {
@@ -190030,6 +190181,8 @@ async function updateTimebackResources(client2, courseId, config2) {
190030
190181
  updateComponentResourceLink(client2, config2, sourcedIds)
190031
190182
  ]);
190032
190183
  }
190184
+ init_constants();
190185
+ init_constants();
190033
190186
  if (process.env.DEBUG === "true") {
190034
190187
  process.env.TERM = "dumb";
190035
190188
  }
@@ -190129,6 +190282,7 @@ async function getTimebackTokenResponse(config2) {
190129
190282
  function getAuthUrl(environment = "production") {
190130
190283
  return TIMEBACK_AUTH_URLS[environment];
190131
190284
  }
190285
+ init_constants();
190132
190286
  async function request({
190133
190287
  path: path22,
190134
190288
  baseUrl,
@@ -190234,6 +190388,7 @@ async function requestCaliper(options) {
190234
190388
  baseUrl: caliperBase
190235
190389
  });
190236
190390
  }
190391
+ init_constants();
190237
190392
  function logTimebackError(operation, error2, context) {
190238
190393
  const errorMessage = error2 instanceof Error ? error2.message : String(error2);
190239
190394
  if (error2 instanceof TimebackApiError) {
@@ -190269,6 +190424,17 @@ function createOneRosterNamespace(client2) {
190269
190424
  listByCourse: async (courseSourcedId) => {
190270
190425
  const res = await client2["request"](`${ONEROSTER_ENDPOINTS.courses}/${courseSourcedId}/classes`, "GET");
190271
190426
  return res.classes;
190427
+ },
190428
+ listByStudent: async (userSourcedId, options) => {
190429
+ const queryParams = new URLSearchParams;
190430
+ if (options?.limit)
190431
+ queryParams.set("limit", String(options.limit));
190432
+ if (options?.offset)
190433
+ queryParams.set("offset", String(options.offset));
190434
+ const endpoint = `${ONEROSTER_ENDPOINTS.users}/${userSourcedId}/classes`;
190435
+ const url = queryParams.toString() ? `${endpoint}?${queryParams}` : endpoint;
190436
+ const res = await client2["request"](url, "GET");
190437
+ return res.classes || [];
190272
190438
  }
190273
190439
  },
190274
190440
  organizations: {
@@ -190276,7 +190442,7 @@ function createOneRosterNamespace(client2) {
190276
190442
  return client2["request"](ONEROSTER_ENDPOINTS.organizations, "POST", data);
190277
190443
  },
190278
190444
  get: async (sourcedId) => {
190279
- return client2["request"](`${ONEROSTER_ENDPOINTS.organizations}/${sourcedId}`, "GET");
190445
+ return client2["request"](`${ONEROSTER_ENDPOINTS.organizations}/${sourcedId}`, "GET").then((res) => res.org);
190280
190446
  },
190281
190447
  update: async (sourcedId, data) => {
190282
190448
  return client2["request"](`${ONEROSTER_ENDPOINTS.organizations}/${sourcedId}`, "PUT", data);
@@ -190404,7 +190570,7 @@ function createOneRosterNamespace(client2) {
190404
190570
  return client2["request"](ONEROSTER_ENDPOINTS.users, "POST", { user: data });
190405
190571
  },
190406
190572
  get: async (sourcedId) => {
190407
- return client2["request"](`${ONEROSTER_ENDPOINTS.users}/${sourcedId}`, "GET");
190573
+ return client2["request"](`${ONEROSTER_ENDPOINTS.users}/${sourcedId}`, "GET").then((res) => res.user);
190408
190574
  },
190409
190575
  findByEmail: async (email) => {
190410
190576
  const params = new URLSearchParams({ filter: `email='${email}'` });
@@ -190420,6 +190586,7 @@ function createOneRosterNamespace(client2) {
190420
190586
  }
190421
190587
  };
190422
190588
  }
190589
+ init_constants();
190423
190590
  function createCaliperNamespace(client2) {
190424
190591
  const urls = createOneRosterUrls(client2.getBaseUrl());
190425
190592
  const caliper = {
@@ -190457,7 +190624,7 @@ function createCaliperNamespace(client2) {
190457
190624
  },
190458
190625
  action: TIMEBACK_ACTIONS.completed,
190459
190626
  object: {
190460
- id: urls.componentResource(data.activityId),
190627
+ id: caliper.buildActivityUrl(data),
190461
190628
  type: TIMEBACK_TYPES.activityContext,
190462
190629
  subject: data.subject,
190463
190630
  app: {
@@ -190467,7 +190634,7 @@ function createCaliperNamespace(client2) {
190467
190634
  name: data.activityName
190468
190635
  },
190469
190636
  course: { id: urls.course(data.courseId), name: data.activityName },
190470
- process: true
190637
+ process: false
190471
190638
  },
190472
190639
  generated: {
190473
190640
  id: `urn:timeback:metrics:activity-completion-${crypto.randomUUID()}`,
@@ -190493,7 +190660,8 @@ function createCaliperNamespace(client2) {
190493
190660
  value: data.masteredUnits
190494
190661
  }
190495
190662
  ] : []
190496
- ]
190663
+ ],
190664
+ ...data.extensions ? { extensions: data.extensions } : {}
190497
190665
  }
190498
190666
  };
190499
190667
  return caliper.emit(event, data.sensorUrl);
@@ -190512,7 +190680,7 @@ function createCaliperNamespace(client2) {
190512
190680
  },
190513
190681
  action: TIMEBACK_ACTIONS.spentTime,
190514
190682
  object: {
190515
- id: urls.componentResource(data.activityId),
190683
+ id: caliper.buildActivityUrl(data),
190516
190684
  type: TIMEBACK_TYPES.activityContext,
190517
190685
  subject: data.subject,
190518
190686
  app: {
@@ -190540,10 +190708,33 @@ function createCaliperNamespace(client2) {
190540
190708
  }
190541
190709
  };
190542
190710
  return caliper.emit(event, data.sensorUrl);
190711
+ },
190712
+ buildActivityUrl: (data) => {
190713
+ const base = data.sensorUrl.replace(/\/$/, "");
190714
+ return `${base}/activities/${data.courseId}/${data.activityId}/${crypto.randomUUID()}`;
190543
190715
  }
190544
190716
  };
190545
190717
  return caliper;
190546
190718
  }
190719
+ function createEduBridgeNamespace(client2) {
190720
+ const enrollments = {
190721
+ listByUser: async (userId) => {
190722
+ const response = await client2["request"](`/edubridge/enrollments/user/${userId}`, "GET");
190723
+ return response.data;
190724
+ }
190725
+ };
190726
+ const analytics = {
190727
+ getEnrollmentFacts: async (enrollmentId) => {
190728
+ return client2["request"](`/edubridge/analytics/enrollment/${enrollmentId}`, "GET");
190729
+ }
190730
+ };
190731
+ return {
190732
+ enrollments,
190733
+ analytics
190734
+ };
190735
+ }
190736
+ init_constants();
190737
+ init_constants();
190547
190738
 
190548
190739
  class TimebackCache {
190549
190740
  cache = new Map;
@@ -190649,6 +190840,7 @@ class TimebackCache {
190649
190840
  class TimebackCacheManager {
190650
190841
  studentCache;
190651
190842
  assessmentLineItemCache;
190843
+ resourceMasteryCache;
190652
190844
  constructor() {
190653
190845
  this.studentCache = new TimebackCache({
190654
190846
  defaultTTL: CACHE_DEFAULTS.studentTTL,
@@ -190660,6 +190852,11 @@ class TimebackCacheManager {
190660
190852
  maxSize: CACHE_DEFAULTS.assessmentMaxSize,
190661
190853
  name: "AssessmentLineItemCache"
190662
190854
  });
190855
+ this.resourceMasteryCache = new TimebackCache({
190856
+ defaultTTL: CACHE_DEFAULTS.assessmentTTL,
190857
+ maxSize: CACHE_DEFAULTS.assessmentMaxSize,
190858
+ name: "ResourceMasteryCache"
190859
+ });
190663
190860
  }
190664
190861
  getStudent(key) {
190665
190862
  return this.studentCache.get(key);
@@ -190673,6 +190870,12 @@ class TimebackCacheManager {
190673
190870
  setAssessmentLineItem(key, lineItemId) {
190674
190871
  this.assessmentLineItemCache.set(key, lineItemId);
190675
190872
  }
190873
+ getResourceMasterableUnits(key) {
190874
+ return this.resourceMasteryCache.get(key);
190875
+ }
190876
+ setResourceMasterableUnits(key, value) {
190877
+ this.resourceMasteryCache.set(key, value);
190878
+ }
190676
190879
  clearAll() {
190677
190880
  this.studentCache.clear();
190678
190881
  this.assessmentLineItemCache.clear();
@@ -190681,18 +190884,32 @@ class TimebackCacheManager {
190681
190884
  getStats() {
190682
190885
  return {
190683
190886
  studentCache: this.studentCache.stats(),
190684
- assessmentLineItemCache: this.assessmentLineItemCache.stats()
190887
+ assessmentLineItemCache: this.assessmentLineItemCache.stats(),
190888
+ resourceMasteryCache: this.resourceMasteryCache.stats()
190685
190889
  };
190686
190890
  }
190687
190891
  cleanup() {
190688
190892
  this.studentCache.cleanup();
190689
190893
  this.assessmentLineItemCache.cleanup();
190894
+ this.resourceMasteryCache.cleanup();
190690
190895
  log32.debug("[TimebackCacheManager] Cache cleanup completed");
190691
190896
  }
190692
190897
  }
190693
190898
  function kebabToTitleCase(kebabStr) {
190694
190899
  return kebabStr.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
190695
190900
  }
190901
+ init_constants();
190902
+ init_constants();
190903
+ var isObject2 = (value) => typeof value === "object" && value !== null;
190904
+ function isPlaycademyResourceMetadata(value) {
190905
+ if (!isObject2(value)) {
190906
+ return false;
190907
+ }
190908
+ if (!("mastery" in value) || value.mastery === undefined) {
190909
+ return true;
190910
+ }
190911
+ return isObject2(value.mastery);
190912
+ }
190696
190913
  function validateProgressData(progressData) {
190697
190914
  if (!progressData.subject) {
190698
190915
  throw new ConfigurationError("subject", "Subject is required for Caliper events. Provide it in progressData.subject");
@@ -190746,36 +190963,47 @@ class ProgressRecorder {
190746
190963
  cacheManager;
190747
190964
  onerosterNamespace;
190748
190965
  caliperNamespace;
190749
- constructor(studentResolver, cacheManager, onerosterNamespace, caliperNamespace) {
190966
+ edubridgeNamespace;
190967
+ constructor(studentResolver, cacheManager, onerosterNamespace, caliperNamespace, edubridgeNamespace) {
190750
190968
  this.studentResolver = studentResolver;
190751
190969
  this.cacheManager = cacheManager;
190752
190970
  this.onerosterNamespace = onerosterNamespace;
190753
190971
  this.caliperNamespace = caliperNamespace;
190972
+ this.edubridgeNamespace = edubridgeNamespace;
190754
190973
  }
190755
190974
  async record(courseId, studentIdentifier, progressData) {
190756
190975
  validateProgressData(progressData);
190757
- const ids = deriveSourcedIds(courseId);
190758
- const activityId = progressData.activityId || ids.componentResource || ids.resource || "unknown-activity";
190759
- const activityName = progressData.activityName || kebabToTitleCase(activityId);
190760
- const classId = progressData.classId;
190761
- const courseName = progressData.courseName || "Game Course";
190762
- const student = await this.studentResolver.resolve(studentIdentifier, progressData.studentEmail);
190976
+ const { ids, activityId, activityName, courseName, student } = await this.resolveContext(courseId, studentIdentifier, progressData);
190763
190977
  const { id: studentId, email: studentEmail } = student;
190764
190978
  const { score, totalQuestions, correctQuestions, xpEarned, masteredUnits, attemptNumber } = progressData;
190765
- const lineItemId = `${activityId}-assessment`;
190766
- let actualLineItemId = this.cacheManager.getAssessmentLineItem(lineItemId);
190767
- if (!actualLineItemId) {
190768
- actualLineItemId = await this.getOrCreateLineItem(lineItemId, activityName, classId, ids);
190769
- this.cacheManager.setAssessmentLineItem(lineItemId, actualLineItemId);
190770
- }
190771
- let currentAttemptNumber = attemptNumber || 1;
190979
+ const actualLineItemId = await this.resolveAssessmentLineItem(activityId, activityName, progressData.classId, ids);
190980
+ const currentAttemptNumber = await this.resolveAttemptNumber(attemptNumber, score, studentId, actualLineItemId);
190772
190981
  const isFirstAttempt = currentAttemptNumber === 1;
190773
- if (!attemptNumber && score !== undefined) {
190774
- currentAttemptNumber = await this.determineAttemptNumber(studentId, actualLineItemId);
190775
- }
190776
190982
  const calculatedXp = this.calculateXpForProgress(progressData, totalQuestions, correctQuestions, xpEarned, isFirstAttempt);
190983
+ let extensions = progressData.extensions;
190984
+ const completionProgress = await this.buildCompletionProgress({
190985
+ studentId,
190986
+ courseId,
190987
+ progressData,
190988
+ resourceId: ids.resource
190989
+ });
190990
+ let pctCompleteApp;
190991
+ let masteryAchieved = false;
190992
+ let scoreStatus = SCORE_STATUS.partiallyGraded;
190993
+ const inProgress = "false";
190994
+ if (completionProgress) {
190995
+ masteryAchieved = completionProgress.masteryAchieved;
190996
+ pctCompleteApp = completionProgress.pctCompleteApp;
190997
+ extensions = {
190998
+ ...extensions || {},
190999
+ ...pctCompleteApp !== undefined ? { pctCompleteApp } : {}
191000
+ };
191001
+ if (masteryAchieved) {
191002
+ scoreStatus = SCORE_STATUS.fullyGraded;
191003
+ }
191004
+ }
190777
191005
  if (score !== undefined) {
190778
- await this.createGradebookEntry(actualLineItemId, studentId, currentAttemptNumber, score, totalQuestions, correctQuestions, calculatedXp);
191006
+ await this.createGradebookEntry(actualLineItemId, studentId, currentAttemptNumber, score, totalQuestions, correctQuestions, calculatedXp, masteredUnits, scoreStatus, inProgress, progressData.appName);
190779
191007
  } else {
190780
191008
  log32.warn("[ProgressRecorder] Score not provided, skipping gradebook entry", {
190781
191009
  studentId,
@@ -190783,12 +191011,151 @@ class ProgressRecorder {
190783
191011
  attemptNumber: currentAttemptNumber
190784
191012
  });
190785
191013
  }
190786
- await this.emitCaliperEvent(studentId, studentEmail, activityId, activityName, ids.course, courseName, totalQuestions, correctQuestions, calculatedXp, masteredUnits, currentAttemptNumber, progressData);
191014
+ await this.emitCaliperEvent(studentId, studentEmail, activityId, activityName, ids.course, courseName, totalQuestions, correctQuestions, calculatedXp, masteredUnits, currentAttemptNumber, progressData, extensions);
190787
191015
  return {
190788
191016
  xpAwarded: calculatedXp,
190789
- attemptNumber: currentAttemptNumber
191017
+ attemptNumber: currentAttemptNumber,
191018
+ masteredUnitsApplied: progressData.masteredUnits ?? 0,
191019
+ pctCompleteApp,
191020
+ scoreStatus,
191021
+ inProgress
190790
191022
  };
190791
191023
  }
191024
+ async resolveContext(courseId, studentIdentifier, progressData) {
191025
+ const ids = deriveSourcedIds(courseId);
191026
+ const activityId = progressData.activityId || ids.componentResource || ids.resource || "unknown-activity";
191027
+ const activityName = progressData.activityName || kebabToTitleCase(activityId);
191028
+ const courseName = progressData.courseName || "Game Course";
191029
+ const student = await this.studentResolver.resolve(studentIdentifier, progressData.studentEmail);
191030
+ return { ids, activityId, activityName, courseName, student };
191031
+ }
191032
+ async resolveAssessmentLineItem(activityId, activityName, classId, ids) {
191033
+ const lineItemId = `${ids.course}-${activityId}-assessment`;
191034
+ let actualLineItemId = this.cacheManager.getAssessmentLineItem(lineItemId);
191035
+ if (!actualLineItemId) {
191036
+ actualLineItemId = await this.getOrCreateLineItem(lineItemId, activityName, classId, ids);
191037
+ this.cacheManager.setAssessmentLineItem(lineItemId, actualLineItemId);
191038
+ }
191039
+ return actualLineItemId;
191040
+ }
191041
+ async resolveAttemptNumber(providedAttemptNumber, score, studentId, lineItemId) {
191042
+ if (providedAttemptNumber)
191043
+ return providedAttemptNumber;
191044
+ if (score !== undefined) {
191045
+ return this.determineAttemptNumber(studentId, lineItemId);
191046
+ }
191047
+ return 1;
191048
+ }
191049
+ calculateXpForProgress(progressData, totalQuestions, correctQuestions, xpEarned, isFirstAttempt) {
191050
+ if (xpEarned !== undefined) {
191051
+ log32.debug("[ProgressRecorder] Using provided XP", { xpEarned });
191052
+ return xpEarned;
191053
+ }
191054
+ if (progressData.sessionDurationSeconds && totalQuestions && correctQuestions) {
191055
+ const accuracy = correctQuestions / totalQuestions;
191056
+ const calculatedXp = calculateXp(progressData.sessionDurationSeconds, accuracy, isFirstAttempt);
191057
+ log32.debug("[ProgressRecorder] Calculated XP", {
191058
+ durationSeconds: progressData.sessionDurationSeconds,
191059
+ accuracy,
191060
+ isFirstAttempt,
191061
+ calculatedXp
191062
+ });
191063
+ return calculatedXp;
191064
+ }
191065
+ return 0;
191066
+ }
191067
+ async buildCompletionProgress({
191068
+ studentId,
191069
+ courseId,
191070
+ progressData,
191071
+ resourceId
191072
+ }) {
191073
+ if (typeof progressData.masteredUnits !== "number" || progressData.masteredUnits <= 0) {
191074
+ return;
191075
+ }
191076
+ const masterableUnits = await this.resolveMasterableUnits(resourceId);
191077
+ if (!masterableUnits || masterableUnits <= 0) {
191078
+ log32.warn("[ProgressRecorder] No masterableUnits configured for course", {
191079
+ courseId,
191080
+ resourceId
191081
+ });
191082
+ return;
191083
+ }
191084
+ const facts = await this.fetchEnrollmentAnalyticsFacts(studentId, courseId);
191085
+ if (!facts) {
191086
+ log32.warn("[ProgressRecorder] Unable to retrieve analytics for mastery-based completion", { studentId, courseId });
191087
+ return;
191088
+ }
191089
+ const historicalMasteredUnits = this.sumAnalyticsMetric(facts, "masteredUnits");
191090
+ const totalMastered = historicalMasteredUnits + progressData.masteredUnits;
191091
+ const rawPct = totalMastered / masterableUnits * 100;
191092
+ const pctCompleteApp = Math.min(100, Math.max(0, Math.round(rawPct)));
191093
+ const masteryAchieved = totalMastered >= masterableUnits;
191094
+ return { pctCompleteApp, masteryAchieved };
191095
+ }
191096
+ async fetchEnrollmentAnalyticsFacts(studentId, courseId) {
191097
+ try {
191098
+ const enrollments = await this.edubridgeNamespace.enrollments.listByUser(studentId);
191099
+ const enrollment = enrollments.find((e2) => e2.course.id === courseId);
191100
+ if (!enrollment) {
191101
+ log32.warn("[ProgressRecorder] Enrollment not found for student/course", {
191102
+ studentId,
191103
+ courseId
191104
+ });
191105
+ return;
191106
+ }
191107
+ const analytics = await this.edubridgeNamespace.analytics.getEnrollmentFacts(enrollment.id);
191108
+ return analytics.facts;
191109
+ } catch (error2) {
191110
+ log32.error("[ProgressRecorder] Failed to load enrollment analytics facts", {
191111
+ studentId,
191112
+ courseId,
191113
+ error: error2
191114
+ });
191115
+ return;
191116
+ }
191117
+ }
191118
+ sumAnalyticsMetric(facts, metric) {
191119
+ if (!facts) {
191120
+ return 0;
191121
+ }
191122
+ let total = 0;
191123
+ Object.values(facts).forEach((dateFacts) => {
191124
+ Object.values(dateFacts).forEach((subjectFacts) => {
191125
+ const metrics = subjectFacts.activityMetrics;
191126
+ if (metrics && typeof metrics[metric] === "number") {
191127
+ total += metrics[metric];
191128
+ }
191129
+ });
191130
+ });
191131
+ return total;
191132
+ }
191133
+ async resolveMasterableUnits(resourceId) {
191134
+ if (!resourceId) {
191135
+ return;
191136
+ }
191137
+ const cached = this.cacheManager.getResourceMasterableUnits(resourceId);
191138
+ if (cached !== undefined) {
191139
+ return cached === null ? undefined : cached;
191140
+ }
191141
+ try {
191142
+ const resource = await this.onerosterNamespace.resources.get(resourceId);
191143
+ const playcademyMetadata = resource.metadata?.playcademy;
191144
+ if (!playcademyMetadata) {
191145
+ return;
191146
+ }
191147
+ const masterableUnits = isPlaycademyResourceMetadata(playcademyMetadata) ? playcademyMetadata.mastery?.masterableUnits : undefined;
191148
+ this.cacheManager.setResourceMasterableUnits(resourceId, masterableUnits ?? null);
191149
+ return masterableUnits;
191150
+ } catch (error2) {
191151
+ log32.error("[ProgressRecorder] Failed to fetch resource metadata for mastery config", {
191152
+ resourceId,
191153
+ error: error2
191154
+ });
191155
+ this.cacheManager.setResourceMasterableUnits(resourceId, null);
191156
+ return;
191157
+ }
191158
+ }
190792
191159
  async getOrCreateLineItem(lineItemId, activityName, classId, ids) {
190793
191160
  try {
190794
191161
  const lineItem = await this.onerosterNamespace.assessmentLineItems.findOrCreate(lineItemId, {
@@ -190827,25 +191194,7 @@ class ProgressRecorder {
190827
191194
  }
190828
191195
  return 1;
190829
191196
  }
190830
- calculateXpForProgress(progressData, totalQuestions, correctQuestions, xpEarned, isFirstAttempt) {
190831
- if (xpEarned !== undefined) {
190832
- log32.debug("[ProgressRecorder] Using provided XP", { xpEarned });
190833
- return xpEarned;
190834
- }
190835
- if (progressData.sessionDurationSeconds && totalQuestions && correctQuestions) {
190836
- const accuracy = correctQuestions / totalQuestions;
190837
- const calculatedXp = calculateXp(progressData.sessionDurationSeconds, accuracy, isFirstAttempt);
190838
- log32.debug("[ProgressRecorder] Calculated XP", {
190839
- durationSeconds: progressData.sessionDurationSeconds,
190840
- accuracy,
190841
- isFirstAttempt,
190842
- calculatedXp
190843
- });
190844
- return calculatedXp;
190845
- }
190846
- return 0;
190847
- }
190848
- async createGradebookEntry(lineItemId, studentId, attemptNumber, score, totalQuestions, correctQuestions, xp) {
191197
+ async createGradebookEntry(lineItemId, studentId, attemptNumber, score, totalQuestions, correctQuestions, xp, masteredUnits, scoreStatus, inProgress, appName) {
190849
191198
  const resultId = `${lineItemId}:${studentId}:attempt-${attemptNumber}`;
190850
191199
  await this.onerosterNamespace.assessmentResults.upsert(resultId, {
190851
191200
  sourcedId: resultId,
@@ -190854,18 +191203,21 @@ class ProgressRecorder {
190854
191203
  student: { sourcedId: studentId },
190855
191204
  score,
190856
191205
  scoreDate: new Date().toISOString(),
190857
- scoreStatus: SCORE_STATUS.fullyGraded,
191206
+ scoreStatus,
191207
+ inProgress,
190858
191208
  metadata: {
190859
191209
  xp,
190860
191210
  totalQuestions,
190861
191211
  correctQuestions,
190862
191212
  accuracy: totalQuestions && correctQuestions ? correctQuestions / totalQuestions * 100 : undefined,
190863
191213
  attemptNumber,
190864
- lastUpdated: new Date().toISOString()
191214
+ lastUpdated: new Date().toISOString(),
191215
+ masteredUnits,
191216
+ appName
190865
191217
  }
190866
191218
  });
190867
191219
  }
190868
- async emitCaliperEvent(studentId, studentEmail, activityId, activityName, courseId, courseName, totalQuestions, correctQuestions, xpEarned, masteredUnits, attemptNumber, progressData) {
191220
+ async emitCaliperEvent(studentId, studentEmail, activityId, activityName, courseId, courseName, totalQuestions, correctQuestions, xpEarned, masteredUnits, attemptNumber, progressData, extensions) {
190869
191221
  await this.caliperNamespace.emitActivityEvent({
190870
191222
  studentId,
190871
191223
  studentEmail,
@@ -190880,7 +191232,8 @@ class ProgressRecorder {
190880
191232
  attemptNumber,
190881
191233
  subject: progressData.subject,
190882
191234
  appName: progressData.appName,
190883
- sensorUrl: progressData.sensorUrl
191235
+ sensorUrl: progressData.sensorUrl,
191236
+ extensions: extensions || progressData.extensions
190884
191237
  }).catch((error2) => {
190885
191238
  log32.error("[ProgressRecorder] Failed to emit activity event", { error: error2 });
190886
191239
  });
@@ -194995,9 +195348,10 @@ class TimebackClient {
194995
195348
  };
194996
195349
  this.oneroster = createOneRosterNamespace(this);
194997
195350
  this.caliper = createCaliperNamespace(this);
195351
+ this.edubridge = createEduBridgeNamespace(this);
194998
195352
  this.cacheManager = new TimebackCacheManager;
194999
195353
  this.studentResolver = new StudentResolver(this.cacheManager, this.oneroster);
195000
- this.progressRecorder = new ProgressRecorder(this.studentResolver, this.cacheManager, this.oneroster, this.caliper);
195354
+ this.progressRecorder = new ProgressRecorder(this.studentResolver, this.cacheManager, this.oneroster, this.caliper, this.edubridge);
195001
195355
  this.sessionRecorder = new SessionRecorder(this.studentResolver, this.caliper);
195002
195356
  if (this.credentials) {
195003
195357
  this._ensureAuthenticated().catch((error2) => {
@@ -195106,6 +195460,9 @@ class TimebackClient {
195106
195460
  }
195107
195461
  await this.authenticate();
195108
195462
  }
195463
+ async resolveStudent(studentIdentifier, providedEmail) {
195464
+ return this.studentResolver.resolve(studentIdentifier, providedEmail);
195465
+ }
195109
195466
  async recordProgress(courseId, studentIdentifier, progressData) {
195110
195467
  await this._ensureAuthenticated();
195111
195468
  return this.progressRecorder.record(courseId, studentIdentifier, progressData);
@@ -195114,6 +195471,19 @@ class TimebackClient {
195114
195471
  await this._ensureAuthenticated();
195115
195472
  return this.sessionRecorder.record(courseId, studentIdentifier, sessionData);
195116
195473
  }
195474
+ async getEnrollments(studentId, options) {
195475
+ await this._ensureAuthenticated();
195476
+ const classes = await this.oneroster.classes.listByStudent(studentId, options);
195477
+ return classes.filter((cls) => cls.sourcedId && cls.status && cls.course?.sourcedId).map((cls) => ({
195478
+ sourcedId: cls.sourcedId,
195479
+ title: cls.title,
195480
+ classCode: cls.classCode ?? null,
195481
+ courseId: cls.course.sourcedId,
195482
+ status: cls.status,
195483
+ grades: cls.grades ?? null,
195484
+ subjects: cls.subjects ?? null
195485
+ }));
195486
+ }
195117
195487
  clearCaches() {
195118
195488
  this.cacheManager.clearAll();
195119
195489
  }
@@ -195125,6 +195495,7 @@ class TimebackClient {
195125
195495
  }
195126
195496
  oneroster;
195127
195497
  caliper;
195498
+ edubridge;
195128
195499
  async setup(config2, options) {
195129
195500
  return setupTimebackResources(this, config2, options);
195130
195501
  }
@@ -195141,6 +195512,306 @@ class TimebackClient {
195141
195512
  return deleteTimebackResources(this, courseId);
195142
195513
  }
195143
195514
  }
195515
+ var __esm4 = (fn2, res) => () => (fn2 && (res = fn2(fn2 = 0)), res);
195516
+ var init_auth2 = () => {};
195517
+ var PLAYCADEMY_BASE_URLS2;
195518
+ var init_domains2 = __esm4(() => {
195519
+ PLAYCADEMY_BASE_URLS2 = {
195520
+ production: "https://hub.playcademy.net",
195521
+ staging: "https://hub.dev.playcademy.net"
195522
+ };
195523
+ });
195524
+ var init_env_vars2 = () => {};
195525
+ var ITEM_SLUGS3;
195526
+ var CURRENCIES3;
195527
+ var BADGES3;
195528
+ var init_overworld2 = __esm4(() => {
195529
+ ITEM_SLUGS3 = {
195530
+ PLAYCADEMY_CREDITS: "PLAYCADEMY_CREDITS",
195531
+ PLAYCADEMY_XP: "PLAYCADEMY_XP",
195532
+ FOUNDING_MEMBER_BADGE: "FOUNDING_MEMBER_BADGE",
195533
+ EARLY_ADOPTER_BADGE: "EARLY_ADOPTER_BADGE",
195534
+ FIRST_GAME_BADGE: "FIRST_GAME_BADGE",
195535
+ COMMON_SWORD: "COMMON_SWORD",
195536
+ SMALL_HEALTH_POTION: "SMALL_HEALTH_POTION",
195537
+ SMALL_BACKPACK: "SMALL_BACKPACK",
195538
+ LAVA_LAMP: "LAVA_LAMP",
195539
+ BOOMBOX: "BOOMBOX",
195540
+ CABIN_BED: "CABIN_BED"
195541
+ };
195542
+ CURRENCIES3 = {
195543
+ PRIMARY: ITEM_SLUGS3.PLAYCADEMY_CREDITS,
195544
+ XP: ITEM_SLUGS3.PLAYCADEMY_XP
195545
+ };
195546
+ BADGES3 = {
195547
+ FOUNDING_MEMBER: ITEM_SLUGS3.FOUNDING_MEMBER_BADGE,
195548
+ EARLY_ADOPTER: ITEM_SLUGS3.EARLY_ADOPTER_BADGE,
195549
+ FIRST_GAME: ITEM_SLUGS3.FIRST_GAME_BADGE
195550
+ };
195551
+ });
195552
+ var init_timeback2 = () => {};
195553
+ var init_workers2 = () => {};
195554
+ var init_src3 = __esm4(() => {
195555
+ init_auth2();
195556
+ init_domains2();
195557
+ init_env_vars2();
195558
+ init_overworld2();
195559
+ init_timeback2();
195560
+ init_workers2();
195561
+ });
195562
+ var TIMEBACK_API_URLS2;
195563
+ var TIMEBACK_AUTH_URLS2;
195564
+ var CALIPER_API_URLS2;
195565
+ var ONEROSTER_ENDPOINTS2;
195566
+ var CALIPER_ENDPOINTS2;
195567
+ var CALIPER_CONSTANTS2;
195568
+ var TIMEBACK_EVENT_TYPES2;
195569
+ var TIMEBACK_ACTIONS2;
195570
+ var TIMEBACK_TYPES2;
195571
+ var ACTIVITY_METRIC_TYPES2;
195572
+ var TIME_METRIC_TYPES2;
195573
+ var TIMEBACK_SUBJECTS2;
195574
+ var TIMEBACK_GRADE_LEVELS2;
195575
+ var TIMEBACK_GRADE_LEVEL_LABELS2;
195576
+ var CALIPER_SUBJECTS2;
195577
+ var ONEROSTER_STATUS2;
195578
+ var SCORE_STATUS2;
195579
+ var ENV_VARS2;
195580
+ var HTTP_DEFAULTS2;
195581
+ var AUTH_DEFAULTS2;
195582
+ var CACHE_DEFAULTS2;
195583
+ var CONFIG_DEFAULTS2;
195584
+ var DEFAULT_PLAYCADEMY_ORGANIZATION_ID2;
195585
+ var PLAYCADEMY_DEFAULTS2;
195586
+ var RESOURCE_DEFAULTS2;
195587
+ var HTTP_STATUS2;
195588
+ var ERROR_NAMES2;
195589
+ var init_constants2 = __esm4(() => {
195590
+ init_src3();
195591
+ TIMEBACK_API_URLS2 = {
195592
+ production: "https://api.alpha-1edtech.ai",
195593
+ staging: "https://api.staging.alpha-1edtech.com"
195594
+ };
195595
+ TIMEBACK_AUTH_URLS2 = {
195596
+ production: "https://prod-beyond-timeback-api-2-idp.auth.us-east-1.amazoncognito.com",
195597
+ staging: "https://alpha-auth-development-idp.auth.us-west-2.amazoncognito.com"
195598
+ };
195599
+ CALIPER_API_URLS2 = {
195600
+ production: "https://caliper.alpha-1edtech.ai",
195601
+ staging: "https://caliper-staging.alpha-1edtech.com"
195602
+ };
195603
+ ONEROSTER_ENDPOINTS2 = {
195604
+ organizations: "/ims/oneroster/rostering/v1p2/orgs",
195605
+ courses: "/ims/oneroster/rostering/v1p2/courses",
195606
+ courseComponents: "/ims/oneroster/rostering/v1p2/courses/components",
195607
+ resources: "/ims/oneroster/resources/v1p2/resources",
195608
+ componentResources: "/ims/oneroster/rostering/v1p2/courses/component-resources",
195609
+ classes: "/ims/oneroster/rostering/v1p2/classes",
195610
+ enrollments: "/ims/oneroster/rostering/v1p2/enrollments",
195611
+ assessmentLineItems: "/ims/oneroster/gradebook/v1p2/assessmentLineItems",
195612
+ assessmentResults: "/ims/oneroster/gradebook/v1p2/assessmentResults",
195613
+ users: "/ims/oneroster/rostering/v1p2/users"
195614
+ };
195615
+ CALIPER_ENDPOINTS2 = {
195616
+ events: "/caliper/event",
195617
+ validate: "/caliper/event/validate"
195618
+ };
195619
+ CALIPER_CONSTANTS2 = {
195620
+ context: "http://purl.imsglobal.org/ctx/caliper/v1p2",
195621
+ profile: "TimebackProfile",
195622
+ dataVersion: "http://purl.imsglobal.org/ctx/caliper/v1p2"
195623
+ };
195624
+ TIMEBACK_EVENT_TYPES2 = {
195625
+ activityEvent: "ActivityEvent",
195626
+ timeSpentEvent: "TimeSpentEvent"
195627
+ };
195628
+ TIMEBACK_ACTIONS2 = {
195629
+ completed: "Completed",
195630
+ spentTime: "SpentTime"
195631
+ };
195632
+ TIMEBACK_TYPES2 = {
195633
+ user: "TimebackUser",
195634
+ activityContext: "TimebackActivityContext",
195635
+ activityMetricsCollection: "TimebackActivityMetricsCollection",
195636
+ timeSpentMetricsCollection: "TimebackTimeSpentMetricsCollection"
195637
+ };
195638
+ ACTIVITY_METRIC_TYPES2 = {
195639
+ totalQuestions: "totalQuestions",
195640
+ correctQuestions: "correctQuestions",
195641
+ xpEarned: "xpEarned",
195642
+ masteredUnits: "masteredUnits"
195643
+ };
195644
+ TIME_METRIC_TYPES2 = {
195645
+ active: "active",
195646
+ inactive: "inactive",
195647
+ waste: "waste",
195648
+ unknown: "unknown",
195649
+ antiPattern: "anti-pattern"
195650
+ };
195651
+ TIMEBACK_SUBJECTS2 = [
195652
+ "Math",
195653
+ "FastMath",
195654
+ "Science",
195655
+ "Social Studies",
195656
+ "Language",
195657
+ "Reading",
195658
+ "Vocabulary",
195659
+ "Writing"
195660
+ ];
195661
+ TIMEBACK_GRADE_LEVELS2 = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
195662
+ TIMEBACK_GRADE_LEVEL_LABELS2 = {
195663
+ "-1": "pre-k",
195664
+ "0": "kindergarten",
195665
+ "1": "1st grade",
195666
+ "2": "2nd grade",
195667
+ "3": "3rd grade",
195668
+ "4": "4th grade",
195669
+ "5": "5th grade",
195670
+ "6": "6th grade",
195671
+ "7": "7th grade",
195672
+ "8": "8th grade",
195673
+ "9": "9th grade",
195674
+ "10": "10th grade",
195675
+ "11": "11th grade",
195676
+ "12": "12th grade",
195677
+ "13": "AP"
195678
+ };
195679
+ CALIPER_SUBJECTS2 = {
195680
+ Reading: "Reading",
195681
+ Language: "Language",
195682
+ Vocabulary: "Vocabulary",
195683
+ SocialStudies: "Social Studies",
195684
+ Writing: "Writing",
195685
+ Science: "Science",
195686
+ FastMath: "FastMath",
195687
+ Math: "Math",
195688
+ None: "None"
195689
+ };
195690
+ ONEROSTER_STATUS2 = {
195691
+ active: "active",
195692
+ toBeDeleted: "tobedeleted"
195693
+ };
195694
+ SCORE_STATUS2 = {
195695
+ exempt: "exempt",
195696
+ fullyGraded: "fully graded",
195697
+ notSubmitted: "not submitted",
195698
+ partiallyGraded: "partially graded",
195699
+ submitted: "submitted"
195700
+ };
195701
+ ENV_VARS2 = {
195702
+ clientId: "TIMEBACK_CLIENT_ID",
195703
+ clientSecret: "TIMEBACK_CLIENT_SECRET",
195704
+ baseUrl: "TIMEBACK_BASE_URL",
195705
+ environment: "TIMEBACK_ENVIRONMENT",
195706
+ vendorResourceId: "TIMEBACK_VENDOR_RESOURCE_ID",
195707
+ launchBaseUrl: "GAME_URL"
195708
+ };
195709
+ HTTP_DEFAULTS2 = {
195710
+ timeout: 30000,
195711
+ retries: 3,
195712
+ retryBackoffBase: 2
195713
+ };
195714
+ AUTH_DEFAULTS2 = {
195715
+ tokenCacheDuration: 50000
195716
+ };
195717
+ CACHE_DEFAULTS2 = {
195718
+ defaultTTL: 600000,
195719
+ defaultMaxSize: 500,
195720
+ defaultName: "TimebackCache",
195721
+ studentTTL: 600000,
195722
+ studentMaxSize: 500,
195723
+ assessmentTTL: 1800000,
195724
+ assessmentMaxSize: 200
195725
+ };
195726
+ CONFIG_DEFAULTS2 = {
195727
+ fileNames: ["timeback.config.js", "timeback.config.json"]
195728
+ };
195729
+ DEFAULT_PLAYCADEMY_ORGANIZATION_ID2 = process.env.TIMEBACK_ORG_SOURCE_ID || "PLAYCADEMY";
195730
+ PLAYCADEMY_DEFAULTS2 = {
195731
+ organization: DEFAULT_PLAYCADEMY_ORGANIZATION_ID2,
195732
+ launchBaseUrls: PLAYCADEMY_BASE_URLS2
195733
+ };
195734
+ RESOURCE_DEFAULTS2 = {
195735
+ organization: {
195736
+ name: "Playcademy Studios",
195737
+ type: "department"
195738
+ },
195739
+ course: {
195740
+ gradingScheme: "STANDARD",
195741
+ level: {
195742
+ elementary: "Elementary",
195743
+ middle: "Middle",
195744
+ high: "High",
195745
+ ap: "AP"
195746
+ },
195747
+ metadata: {
195748
+ goals: {
195749
+ dailyXp: 50,
195750
+ dailyLessons: 3
195751
+ },
195752
+ metrics: {
195753
+ totalXp: 1000,
195754
+ totalLessons: 50
195755
+ }
195756
+ }
195757
+ },
195758
+ component: {
195759
+ sortOrder: 1,
195760
+ prerequisiteCriteria: "ALL"
195761
+ },
195762
+ resource: {
195763
+ vendorId: "playcademy",
195764
+ roles: ["primary"],
195765
+ importance: "primary",
195766
+ metadata: {
195767
+ type: "interactive",
195768
+ toolProvider: "Playcademy",
195769
+ instructionalMethod: "exploratory",
195770
+ language: "en-US"
195771
+ }
195772
+ },
195773
+ componentResource: {
195774
+ sortOrder: 1,
195775
+ lessonType: "quiz"
195776
+ }
195777
+ };
195778
+ HTTP_STATUS2 = {
195779
+ CLIENT_ERROR_MIN: 400,
195780
+ CLIENT_ERROR_MAX: 500,
195781
+ SERVER_ERROR_MIN: 500
195782
+ };
195783
+ ERROR_NAMES2 = {
195784
+ timebackAuth: "TimebackAuthError",
195785
+ timebackApi: "TimebackApiError",
195786
+ timebackConfig: "TimebackConfigError",
195787
+ timebackSdk: "TimebackSDKError"
195788
+ };
195789
+ });
195790
+ init_constants2();
195791
+ var isObject3 = (value) => typeof value === "object" && value !== null;
195792
+ var SUBJECT_VALUES = TIMEBACK_SUBJECTS2;
195793
+ var GRADE_VALUES = TIMEBACK_GRADE_LEVELS2;
195794
+ function isCourseMetadata(value) {
195795
+ return isObject3(value);
195796
+ }
195797
+ function isResourceMetadata(value) {
195798
+ return isObject3(value);
195799
+ }
195800
+ function isPlaycademyResourceMetadata2(value) {
195801
+ if (!isObject3(value)) {
195802
+ return false;
195803
+ }
195804
+ if (!("mastery" in value) || value.mastery === undefined) {
195805
+ return true;
195806
+ }
195807
+ return isObject3(value.mastery);
195808
+ }
195809
+ function isTimebackSubject(value) {
195810
+ return typeof value === "string" && SUBJECT_VALUES.includes(value);
195811
+ }
195812
+ function isTimebackGrade(value) {
195813
+ return typeof value === "number" && Number.isInteger(value) && GRADE_VALUES.includes(value);
195814
+ }
195144
195815
  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}$/;
195145
195816
  function isValidUUID(value) {
195146
195817
  if (!value || typeof value !== "string") {
@@ -195417,6 +196088,32 @@ async function getTimebackClient() {
195417
196088
  }
195418
196089
  return timebackClient;
195419
196090
  }
196091
+ function buildResourceMetadata({
196092
+ baseMetadata,
196093
+ subject,
196094
+ grade,
196095
+ totalXp,
196096
+ masterableUnits
196097
+ }) {
196098
+ const normalizedBaseMetadata = isResourceMetadata(baseMetadata) ? baseMetadata : undefined;
196099
+ const metadata2 = {
196100
+ ...normalizedBaseMetadata || {}
196101
+ };
196102
+ metadata2.subject = subject;
196103
+ metadata2.grades = [grade];
196104
+ metadata2.xp = totalXp;
196105
+ if (masterableUnits !== undefined && masterableUnits !== null) {
196106
+ const existingPlaycademy = isPlaycademyResourceMetadata2(metadata2.playcademy) ? metadata2.playcademy : undefined;
196107
+ metadata2.playcademy = {
196108
+ ...existingPlaycademy || {},
196109
+ mastery: {
196110
+ ...existingPlaycademy?.mastery || {},
196111
+ masterableUnits
196112
+ }
196113
+ };
196114
+ }
196115
+ return metadata2;
196116
+ }
195420
196117
  var AchievementCompletionType;
195421
196118
  ((AchievementCompletionType2) => {
195422
196119
  AchievementCompletionType2["TIME_PLAYED_SESSION"] = "time_played_session";
@@ -195940,6 +196637,246 @@ var AchievementService = {
195940
196637
  generateAchievementMessage
195941
196638
  };
195942
196639
 
196640
+ class DiscordEmbedBuilder {
196641
+ embed = {};
196642
+ setTitle(title) {
196643
+ this.embed.title = title;
196644
+ return this;
196645
+ }
196646
+ setDescription(description) {
196647
+ this.embed.description = description;
196648
+ return this;
196649
+ }
196650
+ setUrl(url) {
196651
+ this.embed.url = url;
196652
+ return this;
196653
+ }
196654
+ setColor(color) {
196655
+ this.embed.color = color;
196656
+ return this;
196657
+ }
196658
+ setTimestamp(date4) {
196659
+ this.embed.timestamp = (date4 || new Date).toISOString();
196660
+ return this;
196661
+ }
196662
+ setAuthor(name4, options) {
196663
+ this.embed.author = {
196664
+ name: name4,
196665
+ url: options?.url,
196666
+ icon_url: options?.iconUrl
196667
+ };
196668
+ return this;
196669
+ }
196670
+ setFooter(text5, iconUrl) {
196671
+ this.embed.footer = {
196672
+ text: text5,
196673
+ icon_url: iconUrl
196674
+ };
196675
+ return this;
196676
+ }
196677
+ setThumbnail(url) {
196678
+ this.embed.thumbnail = { url };
196679
+ return this;
196680
+ }
196681
+ setImage(url) {
196682
+ this.embed.image = { url };
196683
+ return this;
196684
+ }
196685
+ addField(name4, value, inline = false) {
196686
+ if (!this.embed.fields) {
196687
+ this.embed.fields = [];
196688
+ }
196689
+ this.embed.fields.push({ name: name4, value, inline });
196690
+ return this;
196691
+ }
196692
+ addFields(...fields) {
196693
+ for (const field of fields) {
196694
+ this.addField(field.name, field.value, field.inline);
196695
+ }
196696
+ return this;
196697
+ }
196698
+ build() {
196699
+ return this.embed;
196700
+ }
196701
+ static create() {
196702
+ return new DiscordEmbedBuilder;
196703
+ }
196704
+ }
196705
+
196706
+ class DiscordClient {
196707
+ config;
196708
+ constructor(config2) {
196709
+ if (!config2.url) {
196710
+ throw new Error("Discord webhook URL is required");
196711
+ }
196712
+ if (!config2.url.startsWith("https://discord.com/api/webhooks/")) {
196713
+ throw new Error("Invalid Discord webhook URL format");
196714
+ }
196715
+ this.config = config2;
196716
+ }
196717
+ async send(message22) {
196718
+ const payload = {
196719
+ content: message22,
196720
+ username: this.config.username,
196721
+ avatar_url: this.config.avatarUrl
196722
+ };
196723
+ await this.sendPayload(payload);
196724
+ }
196725
+ async sendEmbed(embed) {
196726
+ const payload = {
196727
+ embeds: [embed],
196728
+ username: this.config.username,
196729
+ avatar_url: this.config.avatarUrl
196730
+ };
196731
+ await this.sendPayload(payload);
196732
+ }
196733
+ async sendEmbeds(embeds) {
196734
+ if (embeds.length > 10) {
196735
+ throw new Error("Discord allows maximum 10 embeds per message");
196736
+ }
196737
+ const payload = {
196738
+ embeds,
196739
+ username: this.config.username,
196740
+ avatar_url: this.config.avatarUrl
196741
+ };
196742
+ await this.sendPayload(payload);
196743
+ }
196744
+ async sendWithEmbeds(message22, embeds) {
196745
+ const payload = {
196746
+ content: message22,
196747
+ embeds,
196748
+ username: this.config.username,
196749
+ avatar_url: this.config.avatarUrl
196750
+ };
196751
+ await this.sendPayload(payload);
196752
+ }
196753
+ async sendRich(message22) {
196754
+ const builder = new DiscordEmbedBuilder;
196755
+ if (message22.title)
196756
+ builder.setTitle(message22.title);
196757
+ if (message22.description)
196758
+ builder.setDescription(message22.description);
196759
+ if (message22.url)
196760
+ builder.setUrl(message22.url);
196761
+ if (message22.color) {
196762
+ const color = typeof message22.color === "string" ? parseInt(message22.color.replace("#", ""), 16) : message22.color;
196763
+ builder.setColor(color);
196764
+ }
196765
+ if (message22.author) {
196766
+ builder.setAuthor(message22.author.name, {
196767
+ url: message22.author.url,
196768
+ iconUrl: message22.author.iconUrl
196769
+ });
196770
+ }
196771
+ if (message22.fields) {
196772
+ for (const field of message22.fields) {
196773
+ builder.addField(field.name, field.value, field.inline);
196774
+ }
196775
+ }
196776
+ if (message22.footer) {
196777
+ builder.setFooter(message22.footer.text, message22.footer.iconUrl);
196778
+ }
196779
+ if (message22.timestamp) {
196780
+ const date4 = message22.timestamp instanceof Date ? message22.timestamp : new Date(message22.timestamp);
196781
+ builder.setTimestamp(date4);
196782
+ }
196783
+ if (message22.thumbnail) {
196784
+ builder.setThumbnail(message22.thumbnail.url);
196785
+ }
196786
+ if (message22.image) {
196787
+ builder.setImage(message22.image.url);
196788
+ }
196789
+ await this.sendEmbed(builder.build());
196790
+ }
196791
+ async sendPayload(payload) {
196792
+ const response = await fetch(this.config.url, {
196793
+ method: "POST",
196794
+ headers: {
196795
+ "Content-Type": "application/json"
196796
+ },
196797
+ body: JSON.stringify(payload)
196798
+ });
196799
+ if (!response.ok) {
196800
+ const errorText = await response.text().catch(() => "Unknown error");
196801
+ throw new Error(`Discord webhook request failed: ${response.status} ${response.statusText} - ${errorText}`);
196802
+ }
196803
+ }
196804
+ getRedactedUrl() {
196805
+ const parts2 = this.config.url.split("/");
196806
+ const token = parts2[parts2.length - 1];
196807
+ return `${token?.slice(0, 9)}...`;
196808
+ }
196809
+ }
196810
+ var DiscordColors = {
196811
+ DEFAULT: 0,
196812
+ WHITE: 16777215,
196813
+ AQUA: 1752220,
196814
+ GREEN: 5763719,
196815
+ BLUE: 3447003,
196816
+ YELLOW: 16705372,
196817
+ PURPLE: 10181046,
196818
+ LUMINOUS_VIVID_PINK: 15277667,
196819
+ FUCHSIA: 15418782,
196820
+ GOLD: 15844367,
196821
+ ORANGE: 15105570,
196822
+ RED: 15548997,
196823
+ GREY: 9807270,
196824
+ NAVY: 3426654,
196825
+ DARK_AQUA: 1146986,
196826
+ DARK_GREEN: 2067276,
196827
+ DARK_BLUE: 2123412,
196828
+ DARK_PURPLE: 7419530,
196829
+ DARK_VIVID_PINK: 11342935,
196830
+ DARK_GOLD: 12745742,
196831
+ DARK_ORANGE: 11027200,
196832
+ DARK_RED: 10038562,
196833
+ DARK_GREY: 9936031,
196834
+ DARKER_GREY: 8359053,
196835
+ LIGHT_GREY: 12370112,
196836
+ DARK_NAVY: 2899536,
196837
+ BLURPLE: 5793266,
196838
+ GREYPLE: 10070709,
196839
+ DARK_BUT_NOT_BLACK: 2895667,
196840
+ NOT_QUITE_BLACK: 2303786
196841
+ };
196842
+ init_src();
196843
+ function getDiscordClient() {
196844
+ const webhookUrl = process.env.DISCORD_WEBHOOK_PLATFORM;
196845
+ if (!webhookUrl) {
196846
+ log2.debug("[API] Discord webhook not configured, skipping notification");
196847
+ return null;
196848
+ }
196849
+ return new DiscordClient({
196850
+ url: webhookUrl,
196851
+ username: "Playcademy Platform",
196852
+ avatarUrl: process.env.DISCORD_WEBHOOK_AVATAR_URL
196853
+ });
196854
+ }
196855
+ function getEnvironment() {
196856
+ const stage = process.env.SST_STAGE;
196857
+ return stage === "production" ? "production" : stage === "dev" ? "staging" : "test";
196858
+ }
196859
+ async function sendDeveloperApplicationAlert(user) {
196860
+ const discord = getDiscordClient();
196861
+ if (!discord)
196862
+ return;
196863
+ 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();
196864
+ await discord.sendEmbed(embed);
196865
+ log2.debug("[API] Discord notification sent successfully", {
196866
+ userId: user.id
196867
+ });
196868
+ }
196869
+ async function sendGameDeletionAlert(game) {
196870
+ const discord = getDiscordClient();
196871
+ if (!discord)
196872
+ return;
196873
+ 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();
196874
+ await discord.sendEmbed(embed);
196875
+ log2.debug("[API] Discord game deletion notification sent", {
196876
+ slug: game.slug
196877
+ });
196878
+ }
196879
+
195943
196880
  class JwtBaseError extends Error {
195944
196881
  }
195945
196882
 
@@ -197862,7 +198799,6 @@ function normalizeDeploymentOptions(options) {
197862
198799
  assets: options?.bindings?.assets ?? false
197863
198800
  },
197864
198801
  assetsPath: options?.assetsPath,
197865
- assetsZip: options?.assetsZip,
197866
198802
  keepAssets: options?.keepAssets
197867
198803
  };
197868
198804
  }
@@ -197909,34 +198845,6 @@ var CUSTOM_DOMAINS_KV_NAME = "cademy-custom-domains";
197909
198845
  var DEFAULT_COMPATIBILITY_DATE2 = new Date().toISOString().slice(0, 10);
197910
198846
  var GAME_WORKER_DOMAIN_PRODUCTION = GAME_WORKER_DOMAINS.production;
197911
198847
  var GAME_WORKER_DOMAIN_STAGING = GAME_WORKER_DOMAINS.staging;
197912
- var esbuild = __toESM2(require_main(), 1);
197913
- async function bundleWorker(filePath) {
197914
- const result = await esbuild.build({
197915
- entryPoints: [filePath],
197916
- bundle: true,
197917
- format: "esm",
197918
- platform: "browser",
197919
- target: "es2022",
197920
- write: false,
197921
- minify: false,
197922
- sourcemap: false,
197923
- logLevel: "error"
197924
- });
197925
- if (!result.outputFiles?.[0]) {
197926
- throw new Error(`No output generated for worker ${filePath}`);
197927
- }
197928
- return result.outputFiles[0].text;
197929
- }
197930
- var _cachedStubWorker = null;
197931
- async function getStubWorkerCode() {
197932
- if (_cachedStubWorker) {
197933
- return _cachedStubWorker;
197934
- }
197935
- const currentDir = dirname2(fileURLToPath(import.meta.url));
197936
- const workerPath = join4(currentDir, "stub-worker.ts");
197937
- _cachedStubWorker = await bundleWorker(workerPath);
197938
- return _cachedStubWorker;
197939
- }
197940
198848
 
197941
198849
  class CloudflareProvider {
197942
198850
  client;
@@ -197978,7 +198886,7 @@ class CloudflareProvider {
197978
198886
  async function scanDirectory(dir) {
197979
198887
  const entries = await readdir2(dir, { withFileTypes: true });
197980
198888
  for (const entry of entries) {
197981
- const fullPath = join5(dir, entry.name);
198889
+ const fullPath = join4(dir, entry.name);
197982
198890
  if (entry.isDirectory()) {
197983
198891
  if (await scanDirectory(fullPath))
197984
198892
  return true;
@@ -198000,7 +198908,7 @@ class CloudflareProvider {
198000
198908
  async resolveAssetBasePath(dirPath) {
198001
198909
  const entries = await readdir2(dirPath, { withFileTypes: true });
198002
198910
  if (entries.length === 1 && entries[0]?.isDirectory()) {
198003
- const unwrappedPath = join5(dirPath, entries[0].name);
198911
+ const unwrappedPath = join4(dirPath, entries[0].name);
198004
198912
  log2.debug("[CloudflareProvider] Unwrapping wrapper directory", {
198005
198913
  wrapper: entries[0].name
198006
198914
  });
@@ -198011,7 +198919,7 @@ class CloudflareProvider {
198011
198919
  async uploadFilesToR2(dir, baseDir, bucketName) {
198012
198920
  const entries = await readdir2(dir, { withFileTypes: true });
198013
198921
  for (const entry of entries) {
198014
- const fullPath = join5(dir, entry.name);
198922
+ const fullPath = join4(dir, entry.name);
198015
198923
  if (entry.isDirectory()) {
198016
198924
  await this.uploadFilesToR2(fullPath, baseDir, bucketName);
198017
198925
  } else {
@@ -198034,38 +198942,15 @@ class CloudflareProvider {
198034
198942
  async deploy(deploymentId, code, env2, options) {
198035
198943
  const opts = normalizeDeploymentOptions(options);
198036
198944
  const isFirstDeploy = env2.PLAYCADEMY_API_KEY !== undefined;
198037
- const hasAssets = !!(opts.assetsPath || opts.assetsZip);
198038
- let finalCode = code ?? "";
198945
+ const hasAssets = !!opts.assetsPath;
198039
198946
  if (!code) {
198040
- try {
198041
- log2.debug("[CloudflareProvider] Fetching existing script", {
198042
- deploymentId,
198043
- namespace: this.config.dispatchNamespace
198044
- });
198045
- finalCode = await this.client.workers.getScriptContent(this.config.dispatchNamespace, deploymentId);
198046
- } catch {
198047
- log2.debug("[CloudflareProvider] No existing script, using stub worker", {
198048
- deploymentId
198049
- });
198050
- finalCode = await getStubWorkerCode();
198051
- }
198947
+ throw new Error(`No worker code provided for deployment. Frontend-only deployments should include a stub worker from the CLI.`);
198052
198948
  }
198949
+ const finalCode = code;
198053
198950
  if (opts.keepAssets === undefined) {
198054
198951
  opts.keepAssets = true;
198055
198952
  }
198056
- let tempDir = null;
198057
- let finalAssetsPath = opts.assetsPath;
198058
- if (opts.assetsZip && !opts.assetsPath) {
198059
- tempDir = join5(tmpdir(), `playcademy-assets-${Date.now()}`);
198060
- finalAssetsPath = join5(tempDir, "dist");
198061
- await mkdir(finalAssetsPath, { recursive: true });
198062
- await writeFile(join5(tempDir, "temp.zip"), opts.assetsZip);
198063
- execSync(`unzip -q ${join5(tempDir, "temp.zip")} -d ${finalAssetsPath}`);
198064
- log2.debug("[CloudflareProvider] Extracted ZIP to temp directory", {
198065
- tempDir,
198066
- size: opts.assetsZip.length
198067
- });
198068
- }
198953
+ const finalAssetsPath = opts.assetsPath;
198069
198954
  log2.info("[CloudflareProvider] Deploying worker to dispatch namespace", {
198070
198955
  deploymentId,
198071
198956
  namespace: this.config.dispatchNamespace,
@@ -198194,10 +199079,6 @@ class CloudflareProvider {
198194
199079
  error: error2
198195
199080
  });
198196
199081
  throw new Error(`Failed to deploy to Cloudflare Workers for Platforms: ${error2 instanceof Error ? error2.message : String(error2)}`);
198197
- } finally {
198198
- if (tempDir) {
198199
- await rm(tempDir, { recursive: true, force: true }).catch(() => {});
198200
- }
198201
199082
  }
198202
199083
  }
198203
199084
  async delete(deploymentId, options) {
@@ -198277,6 +199158,7 @@ class CloudflareProvider {
198277
199158
  });
198278
199159
  }
198279
199160
  }
199161
+ var esbuild = __toESM2(require_main(), 1);
198280
199162
  init_src();
198281
199163
  var cloudflareProvider = null;
198282
199164
  function getCloudflareProvider() {
@@ -198537,6 +199419,21 @@ var logger3 = {
198537
199419
  warn: (msg) => getLogger().warn(msg),
198538
199420
  error: (msg) => getLogger().error(msg)
198539
199421
  };
199422
+ function detectTimebackCourses() {
199423
+ const coursePattern = /^SANDBOX_TIMEBACK_COURSE_(\d+)_([A-Z_]+)$/i;
199424
+ const courses = [];
199425
+ for (const [key, value] of Object.entries(process.env)) {
199426
+ const match2 = key.match(coursePattern);
199427
+ if (match2 && value) {
199428
+ const gradeStr = match2[1];
199429
+ const subjectStr = match2[2];
199430
+ const grade = parseInt(gradeStr, 10);
199431
+ const subject = subjectStr.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
199432
+ courses.push({ grade, subject, courseId: value });
199433
+ }
199434
+ }
199435
+ return courses;
199436
+ }
198540
199437
  async function seedCoreGames(db) {
198541
199438
  const now2 = new Date;
198542
199439
  const coreGames = [
@@ -198561,18 +199458,26 @@ async function seedCoreGames(db) {
198561
199458
  console.error(`Error seeding core game '${gameData.slug}':`, error2);
198562
199459
  }
198563
199460
  }
198564
- if (config.timeback.courseId && hasTimebackCredentials()) {
198565
- try {
198566
- await db.insert(gameTimebackIntegrations).values({
198567
- id: crypto.randomUUID(),
198568
- gameId: CORE_GAME_UUIDS.PLAYGROUND,
198569
- courseId: config.timeback.courseId,
198570
- lastVerifiedAt: null,
198571
- createdAt: now2,
198572
- updatedAt: now2
198573
- }).onConflictDoNothing();
198574
- } catch (error2) {
198575
- console.error("Error seeding TimeBack integration for playground:", error2);
199461
+ if (hasTimebackCredentials()) {
199462
+ const courses = detectTimebackCourses();
199463
+ if (courses.length > 0) {
199464
+ for (const course of courses) {
199465
+ try {
199466
+ await db.insert(gameTimebackIntegrations).values({
199467
+ id: crypto.randomUUID(),
199468
+ gameId: CORE_GAME_UUIDS.PLAYGROUND,
199469
+ courseId: course.courseId,
199470
+ grade: course.grade,
199471
+ subject: course.subject,
199472
+ totalXp: null,
199473
+ lastVerifiedAt: null,
199474
+ createdAt: now2,
199475
+ updatedAt: now2
199476
+ }).onConflictDoNothing();
199477
+ } catch (error2) {
199478
+ console.error(`Error seeding TimeBack integration for playground (${course.subject} grade ${course.grade}):`, error2);
199479
+ }
199480
+ }
198576
199481
  }
198577
199482
  }
198578
199483
  }
@@ -198603,18 +199508,23 @@ async function seedCurrentProjectGame(db, project) {
198603
199508
  updatedAt: now2
198604
199509
  };
198605
199510
  const [newGame] = await db.insert(games).values(gameRecord).returning();
198606
- if (config.timeback.courseId && hasTimebackCredentials()) {
198607
- await db.insert(gameTimebackIntegrations).values({
198608
- id: crypto.randomUUID(),
198609
- gameId: newGame.id,
198610
- courseId: config.timeback.courseId,
198611
- lastVerifiedAt: null,
198612
- createdAt: now2,
198613
- updatedAt: now2
198614
- }).onConflictDoNothing();
198615
- } else if (hasTimebackCredentials() && !config.timeback.courseId) {
198616
- logger3.info(" ⚠ TimeBack credentials found but SANDBOX_TIMEBACK_COURSE_ID not set");
198617
- logger3.info(" Set SANDBOX_TIMEBACK_COURSE_ID to your real TimeBack course ID");
199511
+ if (hasTimebackCredentials()) {
199512
+ const courses = detectTimebackCourses();
199513
+ if (courses.length > 0) {
199514
+ for (const course of courses) {
199515
+ await db.insert(gameTimebackIntegrations).values({
199516
+ id: crypto.randomUUID(),
199517
+ gameId: newGame.id,
199518
+ courseId: course.courseId,
199519
+ grade: course.grade,
199520
+ subject: course.subject,
199521
+ totalXp: null,
199522
+ lastVerifiedAt: null,
199523
+ createdAt: now2,
199524
+ updatedAt: now2
199525
+ }).onConflictDoNothing();
199526
+ }
199527
+ }
198618
199528
  }
198619
199529
  return newGame;
198620
199530
  } catch (error2) {
@@ -198942,6 +199852,12 @@ async function applyForDeveloperStatus(ctx) {
198942
199852
  userId: user.id,
198943
199853
  newStatus: "pending"
198944
199854
  });
199855
+ sendDeveloperApplicationAlert(fullUser).catch((error2) => {
199856
+ log2.error("[API] Failed to send Discord notification for developer application", {
199857
+ userId: user.id,
199858
+ error: error2 instanceof Error ? error2.message : String(error2)
199859
+ });
199860
+ });
198945
199861
  } catch (error2) {
198946
199862
  log2.error(`Error updating developer status for user ${user.id}:`, {
198947
199863
  error: error2
@@ -203654,6 +204570,67 @@ var NotificationStatsSchema = exports_external2.object({
203654
204570
  startDate: exports_external2.date().optional(),
203655
204571
  endDate: exports_external2.date().optional()
203656
204572
  });
204573
+ var InsertTimebackDailyXpSchema = createInsertSchema(timebackDailyXp, {
204574
+ xp: exports_external2.number().min(0, "XP must be a non-negative number"),
204575
+ date: exports_external2.date()
204576
+ }).omit({ createdAt: true, updatedAt: true });
204577
+ var SelectTimebackDailyXpSchema = createSelectSchema(timebackDailyXp);
204578
+ var UpdateTimebackDailyXpSchema = createUpdateSchema(timebackDailyXp, {
204579
+ xp: exports_external2.number().min(0, "XP must be a non-negative number")
204580
+ }).omit({ userId: true, createdAt: true });
204581
+ var UpdateTimebackXpRequestSchema = exports_external2.object({
204582
+ xp: exports_external2.number().min(0, "XP must be a non-negative number"),
204583
+ userTimestamp: exports_external2.string().datetime().optional()
204584
+ });
204585
+ var TimebackDateQuerySchema = exports_external2.object({
204586
+ date: exports_external2.string().datetime().optional()
204587
+ });
204588
+ var TimebackHistoryQuerySchema = exports_external2.object({
204589
+ startDate: exports_external2.string().date().optional(),
204590
+ endDate: exports_external2.string().date().optional()
204591
+ });
204592
+ var InsertTimebackXpEventSchema = createInsertSchema(timebackXpEvents, {
204593
+ occurredAt: exports_external2.date(),
204594
+ xpDelta: exports_external2.number()
204595
+ }).omit({ createdAt: true, updatedAt: true });
204596
+ var SelectTimebackXpEventSchema = createSelectSchema(timebackXpEvents);
204597
+ var InsertGameTimebackIntegrationSchema = createInsertSchema(gameTimebackIntegrations, {
204598
+ gameId: exports_external2.string().uuid(),
204599
+ courseId: exports_external2.string().min(1),
204600
+ grade: exports_external2.number().int(),
204601
+ subject: exports_external2.string().min(1),
204602
+ totalXp: exports_external2.number().int().positive().optional()
204603
+ }).omit({ id: true, createdAt: true, updatedAt: true });
204604
+ var SelectGameTimebackIntegrationSchema = createSelectSchema(gameTimebackIntegrations);
204605
+ var UpdateGameTimebackIntegrationSchema = createUpdateSchema(gameTimebackIntegrations, {
204606
+ courseId: exports_external2.string().min(1).optional(),
204607
+ totalXp: exports_external2.number().int().positive().optional(),
204608
+ lastVerifiedAt: exports_external2.date().optional()
204609
+ }).omit({ id: true, gameId: true, grade: true, subject: true, createdAt: true });
204610
+ var EndActivityRequestSchema = exports_external2.object({
204611
+ gameId: exports_external2.string().uuid(),
204612
+ studentId: exports_external2.string().min(1),
204613
+ activityData: exports_external2.object({
204614
+ activityId: exports_external2.string().min(1),
204615
+ activityName: exports_external2.string().optional(),
204616
+ grade: exports_external2.number().int(),
204617
+ subject: exports_external2.string().min(1),
204618
+ appName: exports_external2.string().optional(),
204619
+ sensorUrl: exports_external2.string().url().optional(),
204620
+ courseId: exports_external2.string().optional(),
204621
+ courseName: exports_external2.string().optional(),
204622
+ studentEmail: exports_external2.string().email().optional()
204623
+ }),
204624
+ scoreData: exports_external2.object({
204625
+ correctQuestions: exports_external2.number().int().min(0),
204626
+ totalQuestions: exports_external2.number().int().min(0)
204627
+ }),
204628
+ timingData: exports_external2.object({
204629
+ durationSeconds: exports_external2.number().positive()
204630
+ }),
204631
+ xpEarned: exports_external2.number().optional(),
204632
+ masteredUnits: exports_external2.number().nonnegative().optional()
204633
+ });
203657
204634
  init_src();
203658
204635
  async function submitScore(ctx) {
203659
204636
  const { user, params, request: request3 } = ctx;
@@ -203821,6 +204798,26 @@ async function getUserAllScores(ctx) {
203821
204798
  throw ApiError.internal("Failed to fetch user scores");
203822
204799
  }
203823
204800
  }
204801
+ async function getUserScores(ctx) {
204802
+ const user = ctx.user;
204803
+ if (!user) {
204804
+ throw ApiError.unauthorized("Must be logged in to view user scores");
204805
+ }
204806
+ const { params, url } = ctx;
204807
+ const gameId = params.gameId;
204808
+ const userId = params.userId;
204809
+ if (!gameId || !userId) {
204810
+ throw ApiError.badRequest("Game ID and User ID are required");
204811
+ }
204812
+ const limit2 = Math.min(Number(url.searchParams.get("limit") || "10"), 100);
204813
+ const db = getDatabase();
204814
+ try {
204815
+ const scores = await db.select().from(gameScores).where(and(eq(gameScores.gameId, gameId), eq(gameScores.userId, userId))).orderBy(desc(gameScores.achievedAt)).limit(limit2);
204816
+ return scores;
204817
+ } catch {
204818
+ throw ApiError.internal("Failed to fetch user scores");
204819
+ }
204820
+ }
203824
204821
  init_src();
203825
204822
  async function getUserLevel(ctx) {
203826
204823
  const user = ctx.user;
@@ -204464,7 +205461,7 @@ async function deleteGame(ctx) {
204464
205461
  const db = getDatabase();
204465
205462
  const gameToDelete = await db.query.games.findFirst({
204466
205463
  where: eq(games.id, gameId),
204467
- columns: { id: true, slug: true }
205464
+ columns: { id: true, slug: true, displayName: true }
204468
205465
  });
204469
205466
  if (!gameToDelete || !gameToDelete.slug) {
204470
205467
  throw ApiError.notFound("Game not found for deletion");
@@ -204486,6 +205483,16 @@ async function deleteGame(ctx) {
204486
205483
  hadActiveDeployment: !!activeDeployment,
204487
205484
  customDomainsCount: customDomains.length
204488
205485
  });
205486
+ sendGameDeletionAlert({
205487
+ slug: gameToDelete.slug,
205488
+ displayName: gameToDelete.displayName,
205489
+ developer: {
205490
+ id: user.id,
205491
+ email: user.email
205492
+ }
205493
+ }).catch((err2) => {
205494
+ log2.warn("[API] Failed to send Discord game deletion notification", { err: err2 });
205495
+ });
204489
205496
  if (activeDeployment?.provider === "cloudflare") {
204490
205497
  try {
204491
205498
  const cloudflare2 = getCloudflareProvider();
@@ -206049,6 +207056,26 @@ gameScoresRouter.post("/:gameId/scores", async (c3) => {
206049
207056
  return c3.json(createUnknownErrorResponse(error2), 500);
206050
207057
  }
206051
207058
  });
207059
+ gameScoresRouter.get("/:gameId/users/:userId/scores", async (c3) => {
207060
+ const gameId = c3.req.param("gameId");
207061
+ const userId = c3.req.param("userId");
207062
+ const ctx = {
207063
+ user: c3.get("user"),
207064
+ params: { gameId, userId },
207065
+ url: new URL(c3.req.url),
207066
+ request: c3.req.raw
207067
+ };
207068
+ try {
207069
+ const result = await getUserScores(ctx);
207070
+ return c3.json(result, 200);
207071
+ } catch (error2) {
207072
+ if (error2 instanceof ApiError) {
207073
+ return c3.json(createErrorResponse(error2), error2.statusCode);
207074
+ }
207075
+ console.error("Error in getUserScores:", error2);
207076
+ return c3.json(createUnknownErrorResponse(error2), 500);
207077
+ }
207078
+ });
206052
207079
  init_src();
206053
207080
  async function startGameSession(ctx) {
206054
207081
  const user = ctx.user;
@@ -208785,39 +209812,21 @@ async function endActivity(ctx) {
208785
209812
  throw ApiError.unauthorized("Must be logged in to end activity");
208786
209813
  }
208787
209814
  log2.debug("[API] Ending activity", { userId: user.id });
208788
- let request3;
208789
- try {
208790
- const requestBody = await ctx.request.json();
208791
- request3 = requestBody;
208792
- } catch (error2) {
208793
- log2.error("[API] Failed to parse request body:", { error: error2 });
208794
- throw ApiError.badRequest("Invalid JSON body");
208795
- }
208796
- const { gameId, studentId, activityData, scoreData, timingData, xpEarned } = request3;
208797
- if (!activityData?.activityId) {
208798
- throw ApiError.badRequest("activityId is required");
208799
- }
208800
- if (typeof scoreData?.correctQuestions !== "number" || typeof scoreData?.totalQuestions !== "number") {
208801
- throw ApiError.badRequest("correctQuestions and totalQuestions are required");
208802
- }
208803
- if (typeof timingData?.durationSeconds !== "number") {
208804
- throw ApiError.badRequest("durationSeconds is required");
209815
+ const requestBody = await ctx.request.json();
209816
+ const validation2 = EndActivityRequestSchema.safeParse(requestBody);
209817
+ if (!validation2.success) {
209818
+ throw ApiError.badRequest(formatValidationErrors(validation2.error));
208805
209819
  }
209820
+ const { gameId, studentId, activityData, scoreData, timingData, xpEarned, masteredUnits } = validation2.data;
209821
+ await verifyGameAccessById(gameId, user);
208806
209822
  const db = getDatabase();
208807
- const game = await db.query.games.findFirst({
208808
- where: eq(games.id, gameId)
208809
- });
208810
- if (!game) {
208811
- throw ApiError.notFound("Game not found");
208812
- }
208813
- if (user.role !== "admin" && game.developerId !== user.id) {
208814
- throw ApiError.forbidden("You do not own this game");
208815
- }
209823
+ const { grade, subject } = activityData;
208816
209824
  const integration = await db.query.gameTimebackIntegrations.findFirst({
208817
- where: eq(gameTimebackIntegrations.gameId, gameId)
209825
+ where: (table14, { eq: eq3, and: and3 }) => and3(eq3(table14.gameId, gameId), eq3(table14.grade, grade), eq3(table14.subject, subject))
208818
209826
  });
208819
209827
  if (!integration) {
208820
- throw ApiError.notFound("No TimeBack integration found for this game. Run setup first.");
209828
+ log2.error("[API] No TimeBack integration found", { gameId, grade, subject });
209829
+ throw ApiError.notFound(`No TimeBack integration found for this game (grade ${grade}, subject ${subject}). Run setup first.`);
208821
209830
  }
208822
209831
  const client2 = await getTimebackClient();
208823
209832
  try {
@@ -208828,6 +209837,7 @@ async function endActivity(ctx) {
208828
209837
  correctQuestions: scoreData.correctQuestions,
208829
209838
  sessionDurationSeconds: timingData.durationSeconds,
208830
209839
  xpEarned,
209840
+ masteredUnits,
208831
209841
  activityId: activityData.activityId,
208832
209842
  activityName: activityData.activityName,
208833
209843
  subject: activityData.subject,
@@ -208835,8 +209845,11 @@ async function endActivity(ctx) {
208835
209845
  sensorUrl: activityData.sensorUrl,
208836
209846
  courseId: activityData.courseId,
208837
209847
  courseName: activityData.courseName,
208838
- studentEmail: activityData.studentEmail
209848
+ studentEmail: activityData.studentEmail,
209849
+ courseTotalXp: integration.totalXp
208839
209850
  });
209851
+ const studentData = await client2.resolveStudent(studentId);
209852
+ const studentEmail = studentData.email;
208840
209853
  await client2.recordSessionEnd(integration.courseId, studentId, {
208841
209854
  activeTimeSeconds: timingData.durationSeconds,
208842
209855
  activityId: activityData.activityId,
@@ -208852,17 +209865,26 @@ async function endActivity(ctx) {
208852
209865
  gameId,
208853
209866
  courseId: integration.courseId,
208854
209867
  studentId,
209868
+ studentEmail,
208855
209869
  activityId: activityData.activityId,
208856
209870
  score: scorePercentage,
208857
209871
  durationSeconds: timingData.durationSeconds,
208858
209872
  attemptNumber: result.attemptNumber,
208859
209873
  isFirstAttempt: result.attemptNumber === 1,
208860
- xpAwarded: result.xpAwarded
209874
+ xpAwarded: result.xpAwarded,
209875
+ masteredUnits,
209876
+ pctCompleteApp: result.pctCompleteApp,
209877
+ scoreStatus: result.scoreStatus,
209878
+ inProgress: result.inProgress
208861
209879
  });
208862
209880
  return {
208863
209881
  status: "ok",
208864
209882
  courseId: integration.courseId,
208865
- xpAwarded: result.xpAwarded
209883
+ xpAwarded: result.xpAwarded,
209884
+ masteredUnits: result.masteredUnitsApplied,
209885
+ pctCompleteApp: result.pctCompleteApp,
209886
+ scoreStatus: result.scoreStatus,
209887
+ inProgress: result.inProgress
208866
209888
  };
208867
209889
  } catch (error2) {
208868
209890
  logTimebackError2("end activity", error2, {
@@ -208888,68 +209910,160 @@ async function setupTimebackIntegration(ctx) {
208888
209910
  log2.error("Failed to parse request body:", { error: error2 });
208889
209911
  throw ApiError.badRequest("Invalid JSON body");
208890
209912
  }
208891
- const { gameId, config: config2, verbose } = request3;
209913
+ const { gameId, courses, baseConfig, verbose } = request3;
208892
209914
  log2.debug("[API] TimeBack setup request", {
208893
209915
  gameId,
208894
- courseTitle: config2.course.title
209916
+ courseCount: courses.length
208895
209917
  });
209918
+ await verifyGameAccessById(gameId, user);
208896
209919
  const db = getDatabase();
208897
- const game = await db.query.games.findFirst({
208898
- where: eq(games.id, gameId)
208899
- });
208900
- if (!game) {
208901
- throw ApiError.notFound("Game not found");
208902
- }
208903
- if (user.role !== "admin" && game.developerId !== user.id) {
208904
- throw ApiError.forbidden("You do not own this game");
208905
- }
208906
- const existing = await db.query.gameTimebackIntegrations.findFirst({
209920
+ const existing = await db.query.gameTimebackIntegrations.findMany({
208907
209921
  where: eq(gameTimebackIntegrations.gameId, gameId)
208908
209922
  });
208909
209923
  const client2 = await getTimebackClient();
208910
- if (existing) {
208911
- try {
208912
- await client2.update(existing.courseId, config2);
208913
- } catch (error2) {
208914
- logTimebackError2("update", error2, { gameId, courseId: existing.courseId });
208915
- throw error2;
208916
- }
208917
- const [updated] = await db.update(gameTimebackIntegrations).set({
208918
- updatedAt: new Date
208919
- }).where(eq(gameTimebackIntegrations.id, existing.id)).returning();
208920
- log2.info("[API] Updated TimeBack integration", {
208921
- gameId,
208922
- courseId: updated.courseId
208923
- });
208924
- return {
208925
- integration: updated,
208926
- courseId: updated.courseId
209924
+ const integrations = [];
209925
+ const verboseData = [];
209926
+ for (const courseConfig of courses) {
209927
+ const {
209928
+ subject,
209929
+ grade,
209930
+ title,
209931
+ courseCode,
209932
+ level,
209933
+ metadata: metadata2,
209934
+ totalXp: derivedTotalXp,
209935
+ masterableUnits: derivedMasterableUnits
209936
+ } = courseConfig;
209937
+ if (!isTimebackSubject(subject)) {
209938
+ log2.error("[API] Invalid subject in TimeBack request", { subject });
209939
+ throw ApiError.badRequest(`Invalid subject "${subject}" provided for course "${title}".`);
209940
+ }
209941
+ if (!isTimebackGrade(grade)) {
209942
+ log2.error("[API] Invalid grade in TimeBack request", { grade });
209943
+ throw ApiError.badRequest(`Invalid grade "${grade}" provided for course "${title}".`);
209944
+ }
209945
+ const courseSubject = subject;
209946
+ const courseGrade = grade;
209947
+ const courseMetadata = isCourseMetadata(metadata2) ? metadata2 : undefined;
209948
+ const totalXp = derivedTotalXp ?? courseMetadata?.metrics?.totalXp;
209949
+ const masterableUnits = derivedMasterableUnits ?? (isPlaycademyResourceMetadata2(courseMetadata?.playcademy) ? courseMetadata?.playcademy?.mastery?.masterableUnits : undefined);
209950
+ if (typeof totalXp !== "number") {
209951
+ log2.error("[API] Missing totalXp in course metadata", { gameId, grade, subject, title });
209952
+ throw ApiError.badRequest(`Course "${title}" (grade ${grade}, subject ${subject}) is missing \`metadata.metrics.totalXp\``);
209953
+ }
209954
+ const suffix = baseConfig.component.titleSuffix || "";
209955
+ const applySuffix = (text5) => suffix ? `${text5} ${suffix}` : text5;
209956
+ const fullConfig = {
209957
+ organization: baseConfig.organization,
209958
+ course: {
209959
+ title,
209960
+ subjects: [courseSubject],
209961
+ grades: [courseGrade],
209962
+ courseCode,
209963
+ level,
209964
+ gradingScheme: "STANDARD",
209965
+ metadata: metadata2
209966
+ },
209967
+ component: {
209968
+ ...baseConfig.component,
209969
+ title: applySuffix(baseConfig.component.title || `${title} Activities`)
209970
+ },
209971
+ resource: {
209972
+ ...baseConfig.resource,
209973
+ title: applySuffix(baseConfig.resource.title || `${title} Game`),
209974
+ metadata: buildResourceMetadata({
209975
+ baseMetadata: baseConfig.resource.metadata,
209976
+ subject: courseSubject,
209977
+ grade: courseGrade,
209978
+ totalXp,
209979
+ masterableUnits
209980
+ })
209981
+ },
209982
+ componentResource: {
209983
+ ...baseConfig.componentResource,
209984
+ title: applySuffix(baseConfig.componentResource.title || "")
209985
+ }
208927
209986
  };
208928
- }
208929
- let result;
208930
- try {
208931
- result = await client2.setup(config2, { verbose });
208932
- } catch (error2) {
208933
- logTimebackError2("setup", error2, { gameId });
208934
- throw error2;
208935
- }
208936
- const integrationData = {
208937
- gameId,
208938
- courseId: result.courseId
208939
- };
208940
- const [integration] = await db.insert(gameTimebackIntegrations).values(integrationData).returning();
208941
- if (!integration) {
208942
- throw ApiError.internal("Failed to create TimeBack integration record");
209987
+ const existingIntegration = existing.find((i4) => i4.grade === grade && i4.subject === subject);
209988
+ if (existingIntegration) {
209989
+ try {
209990
+ await client2.update(existingIntegration.courseId, fullConfig);
209991
+ } catch (error2) {
209992
+ logTimebackError2("update", error2, {
209993
+ gameId,
209994
+ grade,
209995
+ subject,
209996
+ courseId: existingIntegration.courseId
209997
+ });
209998
+ throw error2;
209999
+ }
210000
+ const [updated] = await db.update(gameTimebackIntegrations).set({
210001
+ totalXp,
210002
+ updatedAt: new Date
210003
+ }).where(eq(gameTimebackIntegrations.id, existingIntegration.id)).returning();
210004
+ if (!updated) {
210005
+ log2.error("[API] Failed to update TimeBack integration record", {
210006
+ gameId,
210007
+ grade,
210008
+ subject,
210009
+ integrationId: existingIntegration.id
210010
+ });
210011
+ throw ApiError.internal("Failed to update TimeBack integration record");
210012
+ }
210013
+ integrations.push(updated);
210014
+ log2.info("[API] Updated TimeBack course", {
210015
+ gameId,
210016
+ courseId: updated.courseId,
210017
+ grade,
210018
+ subject
210019
+ });
210020
+ } else {
210021
+ let result;
210022
+ try {
210023
+ result = await client2.setup(fullConfig, { verbose });
210024
+ } catch (error2) {
210025
+ logTimebackError2("setup", error2, { gameId, grade, subject });
210026
+ throw error2;
210027
+ }
210028
+ const integrationData = {
210029
+ gameId,
210030
+ courseId: result.courseId,
210031
+ grade,
210032
+ subject,
210033
+ totalXp
210034
+ };
210035
+ const [integration] = await db.insert(gameTimebackIntegrations).values(integrationData).returning();
210036
+ if (!integration) {
210037
+ log2.error("[API] Failed to create TimeBack integration record", {
210038
+ gameId,
210039
+ grade,
210040
+ subject,
210041
+ courseId: result.courseId
210042
+ });
210043
+ throw ApiError.internal("Failed to create TimeBack integration record");
210044
+ }
210045
+ integrations.push(integration);
210046
+ if (verbose && result.verboseData) {
210047
+ verboseData.push({
210048
+ integration,
210049
+ config: result.verboseData
210050
+ });
210051
+ }
210052
+ log2.info("[API] Created TimeBack course", {
210053
+ gameId,
210054
+ courseId: integration.courseId,
210055
+ grade,
210056
+ subject
210057
+ });
210058
+ }
208943
210059
  }
208944
210060
  log2.info("[API] Created TimeBack integration", {
208945
210061
  gameId,
208946
- courseId: integration.courseId,
208947
- ...result.verboseData && { verbose: result.verboseData }
210062
+ courseCount: integrations.length
208948
210063
  });
208949
210064
  return {
208950
- integration,
208951
- courseId: integration.courseId,
208952
- ...result.verboseData && { verbose: result.verboseData }
210065
+ integrations,
210066
+ ...verbose && verboseData.length > 0 && { verbose: verboseData }
208953
210067
  };
208954
210068
  }
208955
210069
  async function getTimebackIntegration(ctx) {
@@ -208961,21 +210075,13 @@ async function getTimebackIntegration(ctx) {
208961
210075
  if (!gameId) {
208962
210076
  throw ApiError.badRequest("Missing gameId parameter");
208963
210077
  }
208964
- log2.debug("[API] Getting TimeBack integration", { userId: user.id, gameId });
210078
+ log2.debug("[API] Getting TimeBack integrations", { userId: user.id, gameId });
210079
+ await verifyGameAccessById(gameId, user);
208965
210080
  const db = getDatabase();
208966
- const game = await db.query.games.findFirst({
208967
- where: eq(games.id, gameId)
208968
- });
208969
- if (!game) {
208970
- throw ApiError.notFound("Game not found");
208971
- }
208972
- if (user.role !== "admin" && game.developerId !== user.id) {
208973
- throw ApiError.forbidden("You do not own this game");
208974
- }
208975
- const integration = await db.query.gameTimebackIntegrations.findFirst({
210081
+ const integrations = await db.query.gameTimebackIntegrations.findMany({
208976
210082
  where: eq(gameTimebackIntegrations.gameId, gameId)
208977
210083
  });
208978
- return integration || null;
210084
+ return integrations;
208979
210085
  }
208980
210086
  async function verifyTimebackIntegration(ctx) {
208981
210087
  const user = ctx.user;
@@ -208987,39 +210093,40 @@ async function verifyTimebackIntegration(ctx) {
208987
210093
  throw ApiError.badRequest("Missing gameId parameter");
208988
210094
  }
208989
210095
  log2.debug("[API] Verifying TimeBack integration", { userId: user.id, gameId });
210096
+ await verifyGameAccessById(gameId, user);
208990
210097
  const db = getDatabase();
208991
- const game = await db.query.games.findFirst({
208992
- where: eq(games.id, gameId)
208993
- });
208994
- if (!game) {
208995
- throw ApiError.notFound("Game not found");
208996
- }
208997
- if (user.role !== "admin" && game.developerId !== user.id) {
208998
- log2.error("[API] User does not own game", { userId: user.id, gameId });
208999
- throw ApiError.forbidden("You do not own this game");
209000
- }
209001
- const integration = await db.query.gameTimebackIntegrations.findFirst({
210098
+ const integrations = await db.query.gameTimebackIntegrations.findMany({
209002
210099
  where: eq(gameTimebackIntegrations.gameId, gameId)
209003
210100
  });
209004
- if (!integration) {
210101
+ if (integrations.length === 0) {
210102
+ log2.error("[API] No TimeBack integration found", { gameId });
209005
210103
  throw ApiError.notFound("No TimeBack integration found for this game");
209006
210104
  }
209007
210105
  const client2 = await getTimebackClient();
209008
- const resources = await client2.verify(integration.courseId);
209009
- const resourceValues = Object.values(resources);
209010
- const allFound = resourceValues.every((r3) => r3.found);
209011
- const errors3 = Object.entries(resources).filter(([_5, r3]) => !r3.found).map(([name4]) => `${name4} not found`);
209012
- await db.update(gameTimebackIntegrations).set({ lastVerifiedAt: new Date }).where(eq(gameTimebackIntegrations.id, integration.id));
209013
- log2.info("[API] Verified TimeBack integration", {
210106
+ const now2 = new Date;
210107
+ const results = await Promise.all(integrations.map(async (integration) => {
210108
+ const resources = await client2.verify(integration.courseId);
210109
+ const resourceValues = Object.values(resources);
210110
+ const allFound = resourceValues.every((r3) => r3.found);
210111
+ const errors3 = Object.entries(resources).filter(([_5, r3]) => !r3.found).map(([name4]) => `${name4} not found`);
210112
+ const status = allFound ? "success" : "error";
210113
+ return {
210114
+ integration: { ...integration, lastVerifiedAt: now2 },
210115
+ resources,
210116
+ status,
210117
+ ...errors3.length > 0 && { errors: errors3 }
210118
+ };
210119
+ }));
210120
+ await db.update(gameTimebackIntegrations).set({ lastVerifiedAt: now2 }).where(eq(gameTimebackIntegrations.gameId, gameId));
210121
+ const overallSuccess = results.every((r3) => r3.status === "success");
210122
+ log2.info("[API] Verified TimeBack integrations", {
209014
210123
  gameId,
209015
- courseId: integration.courseId,
209016
- status: allFound ? "success" : "error"
210124
+ courseCount: integrations.length,
210125
+ status: overallSuccess ? "success" : "error"
209017
210126
  });
209018
210127
  return {
209019
- status: allFound ? "success" : "error",
209020
- integration: { ...integration, lastVerifiedAt: new Date },
209021
- resources,
209022
- ...errors3.length > 0 && { errors: errors3 }
210128
+ status: overallSuccess ? "success" : "error",
210129
+ results
209023
210130
  };
209024
210131
  }
209025
210132
  async function getTimebackConfig(ctx) {
@@ -209032,20 +210139,13 @@ async function getTimebackConfig(ctx) {
209032
210139
  throw ApiError.badRequest("Missing gameId parameter");
209033
210140
  }
209034
210141
  log2.debug("[API] Getting TimeBack config", { userId: user.id, gameId });
210142
+ await verifyGameAccessById(gameId, user);
209035
210143
  const db = getDatabase();
209036
- const game = await db.query.games.findFirst({
209037
- where: eq(games.id, gameId)
209038
- });
209039
- if (!game) {
209040
- throw ApiError.notFound("Game not found");
209041
- }
209042
- if (user.role !== "admin" && game.developerId !== user.id) {
209043
- throw ApiError.forbidden("You do not own this game");
209044
- }
209045
210144
  const integration = await db.query.gameTimebackIntegrations.findFirst({
209046
210145
  where: eq(gameTimebackIntegrations.gameId, gameId)
209047
210146
  });
209048
210147
  if (!integration) {
210148
+ log2.error("[API] No TimeBack integration found", { gameId });
209049
210149
  throw ApiError.notFound("No TimeBack integration found for this game");
209050
210150
  }
209051
210151
  const client2 = await getTimebackClient();
@@ -209060,34 +210160,40 @@ async function deleteTimebackIntegration(ctx) {
209060
210160
  if (!gameId) {
209061
210161
  throw ApiError.badRequest("Missing gameId parameter");
209062
210162
  }
209063
- log2.debug("[API] Deleting TimeBack integration", { userId: user.id, gameId });
210163
+ log2.debug("[API] Deleting TimeBack integrations", { userId: user.id, gameId });
210164
+ await verifyGameAccessById(gameId, user);
209064
210165
  const db = getDatabase();
209065
- const game = await db.query.games.findFirst({
209066
- where: eq(games.id, gameId)
209067
- });
209068
- if (!game) {
209069
- throw ApiError.notFound("Game not found");
209070
- }
209071
- if (user.role !== "admin" && game.developerId !== user.id) {
209072
- throw ApiError.forbidden("You do not own this game");
209073
- }
209074
- const integration = await db.query.gameTimebackIntegrations.findFirst({
210166
+ const integrations = await db.query.gameTimebackIntegrations.findMany({
209075
210167
  where: eq(gameTimebackIntegrations.gameId, gameId)
209076
210168
  });
209077
- if (!integration) {
210169
+ if (integrations.length === 0) {
210170
+ log2.error("[API] No TimeBack integrations found", { gameId });
209078
210171
  throw ApiError.notFound("No TimeBack integration found for this game");
209079
210172
  }
209080
210173
  const client2 = await getTimebackClient();
209081
- try {
209082
- await client2.cleanup(integration.courseId);
209083
- } catch (error2) {
209084
- logTimebackError2("cleanup", error2, { gameId, courseId: integration.courseId });
209085
- throw error2;
210174
+ for (const integration of integrations) {
210175
+ try {
210176
+ await client2.cleanup(integration.courseId);
210177
+ log2.info("[API] Cleaned up TimeBack course", {
210178
+ gameId,
210179
+ courseId: integration.courseId,
210180
+ grade: integration.grade,
210181
+ subject: integration.subject
210182
+ });
210183
+ } catch (error2) {
210184
+ logTimebackError2("cleanup", error2, {
210185
+ gameId,
210186
+ courseId: integration.courseId,
210187
+ grade: integration.grade,
210188
+ subject: integration.subject
210189
+ });
210190
+ throw error2;
210191
+ }
209086
210192
  }
209087
- await db.delete(gameTimebackIntegrations).where(eq(gameTimebackIntegrations.id, integration.id));
209088
- log2.info("[API] Deleted TimeBack integration", {
210193
+ await db.delete(gameTimebackIntegrations).where(eq(gameTimebackIntegrations.gameId, gameId));
210194
+ log2.info("[API] Deleted TimeBack integrations", {
209089
210195
  gameId,
209090
- courseId: integration.courseId
210196
+ coursesDeleted: integrations.length
209091
210197
  });
209092
210198
  }
209093
210199
  init_src();
@@ -209218,6 +210324,41 @@ async function getTimeBackXpHistory(ctx) {
209218
210324
  throw ApiError.internal("Failed to get TimeBack XP history", error2);
209219
210325
  }
209220
210326
  }
210327
+ init_src();
210328
+ async function getStudentEnrollments(ctx) {
210329
+ const user = ctx.user;
210330
+ if (!user) {
210331
+ throw ApiError.unauthorized("Must be logged in to get enrollments");
210332
+ }
210333
+ const timebackId = ctx.params.timebackId;
210334
+ if (!timebackId) {
210335
+ throw ApiError.badRequest("Missing timebackId parameter");
210336
+ }
210337
+ log2.debug("[API] Getting student enrollments", { userId: user.id, timebackId });
210338
+ const client2 = await getTimebackClient();
210339
+ const classes = await client2.getEnrollments(timebackId, { limit: 100 });
210340
+ const courseIds = classes.map((cls) => cls.courseId).filter((id) => Boolean(id));
210341
+ if (courseIds.length === 0) {
210342
+ return { enrollments: [] };
210343
+ }
210344
+ const db = getDatabase();
210345
+ const integrations = await db.query.gameTimebackIntegrations.findMany({
210346
+ where: inArray(gameTimebackIntegrations.courseId, courseIds)
210347
+ });
210348
+ const enrollments = integrations.map((integration) => ({
210349
+ gameId: integration.gameId,
210350
+ grade: integration.grade,
210351
+ subject: integration.subject,
210352
+ courseId: integration.courseId
210353
+ }));
210354
+ log2.info("[API] Retrieved student enrollments", {
210355
+ userId: user.id,
210356
+ timebackId,
210357
+ totalClasses: classes.length,
210358
+ mappedEnrollments: enrollments.length
210359
+ });
210360
+ return { enrollments };
210361
+ }
209221
210362
  var timebackRouter = new Hono2;
209222
210363
  timebackRouter.post("/populate-student", async (c3) => {
209223
210364
  return c3.json({
@@ -209408,6 +210549,25 @@ timebackRouter.post("/end-activity", async (c3) => {
209408
210549
  return c3.json(createUnknownErrorResponse(error2), 500);
209409
210550
  }
209410
210551
  });
210552
+ timebackRouter.get("/enrollments/:studentId", async (c3) => {
210553
+ const studentId = c3.req.param("studentId");
210554
+ const ctx = {
210555
+ user: c3.get("user"),
210556
+ params: { studentId },
210557
+ url: new URL(c3.req.url),
210558
+ request: c3.req.raw
210559
+ };
210560
+ try {
210561
+ const result = await getStudentEnrollments(ctx);
210562
+ return c3.json(result);
210563
+ } catch (error2) {
210564
+ if (error2 instanceof ApiError) {
210565
+ return c3.json(createErrorResponse(error2), error2.statusCode);
210566
+ }
210567
+ console.error("Error in getStudentEnrollments:", error2);
210568
+ return c3.json(createUnknownErrorResponse(error2), 500);
210569
+ }
210570
+ });
209411
210571
  init_src();
209412
210572
  async function createLtiSession(userId) {
209413
210573
  const db = getDatabase();
@@ -210378,7 +211538,8 @@ async function configurePlatformMode(server, viteConfig, options) {
210378
211538
  const backend = await setupCliDevServer({
210379
211539
  preferredPort: options.preferredBackendPort,
210380
211540
  viteConfig,
210381
- platformUrl: sandbox.baseUrl
211541
+ platformUrl: sandbox.baseUrl,
211542
+ configPath: options.configPath
210382
211543
  });
210383
211544
  serverState.backend = backend;
210384
211545
  if (sandbox.project) {
@@ -210401,11 +211562,12 @@ async function configurePlatformMode(server, viteConfig, options) {
210401
211562
 
210402
211563
  // src/server/standalone-mode.ts
210403
211564
  var import_picocolors6 = __toESM(require_picocolors(), 1);
210404
- async function configureStandaloneMode(server, viteConfig, preferredPort) {
211565
+ async function configureStandaloneMode(server, viteConfig, options) {
210405
211566
  const backend = await setupCliDevServer({
210406
- preferredPort,
211567
+ preferredPort: options.preferredPort,
210407
211568
  viteConfig,
210408
- platformUrl: undefined
211569
+ platformUrl: undefined,
211570
+ configPath: options.configPath
210409
211571
  });
210410
211572
  if (!backend) {
210411
211573
  viteConfig.logger.error("Failed to start backend server");
@@ -210425,25 +211587,85 @@ async function configureStandaloneMode(server, viteConfig, preferredPort) {
210425
211587
  }
210426
211588
 
210427
211589
  // src/server/mode-switcher.ts
211590
+ var { bold: bold3, cyan: cyan3, dim: dim4, green: green3, red } = import_picocolors7.default;
211591
+ function formatTimestamp2() {
211592
+ const now2 = new Date;
211593
+ const hours = now2.getHours();
211594
+ const minutes = now2.getMinutes().toString().padStart(2, "0");
211595
+ const seconds = now2.getSeconds().toString().padStart(2, "0");
211596
+ const ampm = hours >= 12 ? "PM" : "AM";
211597
+ const displayHours = hours % 12 || 12;
211598
+ return dim4(`${displayHours}:${minutes}:${seconds} ${ampm}`);
211599
+ }
210428
211600
  async function toggleMode(options) {
210429
211601
  const currentMode = getCurrentMode();
210430
211602
  const newMode = currentMode === "platform" ? "standalone" : "platform";
210431
211603
  const viteServer = getViteServerRef();
210432
211604
  if (!viteServer) {
210433
- options.viteConfig.logger.error("[Playcademy] Cannot toggle mode: no Vite server reference");
211605
+ options.viteConfig.logger.error(`${formatTimestamp2()} ${red(bold3("[playcademy]"))} ${red("Cannot toggle mode: no Vite server reference")}`);
210434
211606
  return;
210435
211607
  }
210436
211608
  await cleanupServers();
210437
211609
  await new Promise((resolve2) => setTimeout(resolve2, 100));
210438
211610
  setCurrentMode(newMode);
210439
211611
  if (newMode === "standalone") {
210440
- await configureStandaloneMode(viteServer, options.viteConfig, options.platformModeOptions.preferredBackendPort);
211612
+ await configureStandaloneMode(viteServer, options.viteConfig, {
211613
+ preferredPort: options.platformModeOptions.preferredBackendPort,
211614
+ configPath: options.platformModeOptions.configPath
211615
+ });
210441
211616
  } else {
210442
211617
  await configurePlatformMode(viteServer, options.viteConfig, options.platformModeOptions);
210443
211618
  }
210444
- options.viteConfig.logger.info(`
210445
- ${import_picocolors7.default.green(`✓ Switched to ${import_picocolors7.default.bold(newMode)} mode`)}
210446
- `);
211619
+ options.viteConfig.logger.info(`${formatTimestamp2()} ${cyan3(bold3("[playcademy]"))} ${green3("switched to")} ${green3(bold3(newMode))} ${green3("mode")}`);
211620
+ }
211621
+
211622
+ // src/server/recreate-sandbox-database.ts
211623
+ var import_picocolors8 = __toESM(require_picocolors(), 1);
211624
+ var { bold: bold4, cyan: cyan4, dim: dim5, green: green4, red: red2, yellow } = import_picocolors8.default;
211625
+ function formatTimestamp3() {
211626
+ const now2 = new Date;
211627
+ const hours = now2.getHours();
211628
+ const minutes = now2.getMinutes().toString().padStart(2, "0");
211629
+ const seconds = now2.getSeconds().toString().padStart(2, "0");
211630
+ const ampm = hours >= 12 ? "PM" : "AM";
211631
+ const displayHours = hours % 12 || 12;
211632
+ return dim5(`${displayHours}:${minutes}:${seconds} ${ampm}`);
211633
+ }
211634
+ async function recreateSandboxDatabase(options) {
211635
+ const currentMode = getCurrentMode();
211636
+ const viteServer = getViteServerRef();
211637
+ if (!viteServer) {
211638
+ options.viteConfig.logger.error(`${formatTimestamp3()} ${red2(bold4("[playcademy]"))} ${dim5("(sandbox)")} ${red2("Cannot recreate sandbox database: no Vite server reference")}`);
211639
+ return;
211640
+ }
211641
+ if (currentMode !== "platform") {
211642
+ options.viteConfig.logger.warn(`${formatTimestamp3()} ${yellow(bold4("[playcademy]"))} ${dim5("(sandbox)")} ${yellow("can only recreate sandbox database in platform mode (m + enter)")}`);
211643
+ return;
211644
+ }
211645
+ options.viteConfig.logger.info(`${formatTimestamp3()} ${cyan4(bold4("[playcademy]"))} ${dim5("(sandbox)")} recreating database...`);
211646
+ if (serverState.sandbox) {
211647
+ serverState.sandbox.cleanup();
211648
+ serverState.sandbox = null;
211649
+ }
211650
+ await new Promise((resolve2) => setTimeout(resolve2, 100));
211651
+ const sandbox = await startSandbox(options.viteConfig, options.platformModeOptions.startSandbox, {
211652
+ verbose: options.platformModeOptions.verbose,
211653
+ logLevel: options.platformModeOptions.logLevel,
211654
+ customUrl: options.platformModeOptions.sandboxUrl,
211655
+ quiet: true,
211656
+ recreateDb: true,
211657
+ seed: options.platformModeOptions.seed,
211658
+ memoryOnly: options.platformModeOptions.memoryOnly,
211659
+ databasePath: options.platformModeOptions.databasePath,
211660
+ realtimeEnabled: options.platformModeOptions.realtimeEnabled,
211661
+ realtimePort: options.platformModeOptions.realtimePort
211662
+ });
211663
+ serverState.sandbox = sandbox;
211664
+ if (sandbox.project && serverState.backend) {
211665
+ const gameUrl = `http://localhost:${serverState.backend.port}`;
211666
+ devServerMiddleware(viteServer, sandbox, gameUrl, options.platformModeOptions.showBadge);
211667
+ }
211668
+ options.viteConfig.logger.info(`${formatTimestamp3()} ${cyan4(bold4("[playcademy]"))} ${dim5("(sandbox)")} ${green4("database recreated")}`);
210447
211669
  }
210448
211670
 
210449
211671
  // src/hooks/configure-server.ts
@@ -210471,10 +211693,14 @@ async function configureServerHook(server, context) {
210471
211693
  realtimeEnabled: context.options.realtimeEnabled,
210472
211694
  realtimePort: context.options.realtimePort,
210473
211695
  showBadge: context.options.showBadge,
210474
- preferredBackendPort: preferredPort
211696
+ preferredBackendPort: preferredPort,
211697
+ configPath: context.options.configPath
210475
211698
  };
210476
211699
  if (context.options.mode === "standalone") {
210477
- await configureStandaloneMode(server, context.viteConfig, preferredPort);
211700
+ await configureStandaloneMode(server, context.viteConfig, {
211701
+ preferredPort,
211702
+ configPath: context.options.configPath
211703
+ });
210478
211704
  } else {
210479
211705
  await configurePlatformMode(server, context.viteConfig, platformModeOptions);
210480
211706
  }
@@ -210494,6 +211720,16 @@ async function configureServerHook(server, context) {
210494
211720
  platformModeOptions
210495
211721
  });
210496
211722
  }
211723
+ },
211724
+ {
211725
+ key: "d",
211726
+ description: "recreate sandbox database",
211727
+ action: async () => {
211728
+ await recreateSandboxDatabase({
211729
+ viteConfig: context.viteConfig,
211730
+ platformModeOptions
211731
+ });
211732
+ }
210497
211733
  }
210498
211734
  ]
210499
211735
  });
@@ -210529,6 +211765,7 @@ function resolveOptions(options) {
210529
211765
  const shellOptions = options.shell ?? {};
210530
211766
  const realtimeOptions = sandboxOptions.realtime ?? {};
210531
211767
  return {
211768
+ configPath: options.configPath,
210532
211769
  mode: options.mode ?? "platform",
210533
211770
  autoZip: exportOptions.autoZip ?? true,
210534
211771
  sandboxUrl: sandboxOptions.url ?? `http://localhost:${DEFAULT_PORTS4.SANDBOX}`,