@vue-skuilder/db 0.1.11-9 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/core/index.d.mts +7 -6
  2. package/dist/core/index.d.ts +7 -6
  3. package/dist/core/index.js +358 -87
  4. package/dist/core/index.js.map +1 -1
  5. package/dist/core/index.mjs +358 -87
  6. package/dist/core/index.mjs.map +1 -1
  7. package/dist/{dataLayerProvider-DqtNroSh.d.ts → dataLayerProvider-BiP3kWix.d.mts} +8 -1
  8. package/dist/{dataLayerProvider-BInqI_RF.d.mts → dataLayerProvider-DSdeyRT3.d.ts} +8 -1
  9. package/dist/impl/couch/index.d.mts +19 -7
  10. package/dist/impl/couch/index.d.ts +19 -7
  11. package/dist/impl/couch/index.js +375 -100
  12. package/dist/impl/couch/index.js.map +1 -1
  13. package/dist/impl/couch/index.mjs +374 -99
  14. package/dist/impl/couch/index.mjs.map +1 -1
  15. package/dist/impl/static/index.d.mts +23 -8
  16. package/dist/impl/static/index.d.ts +23 -8
  17. package/dist/impl/static/index.js +289 -85
  18. package/dist/impl/static/index.js.map +1 -1
  19. package/dist/impl/static/index.mjs +289 -85
  20. package/dist/impl/static/index.mjs.map +1 -1
  21. package/dist/{index-CUNnL38E.d.mts → index-Bmll7Xse.d.mts} +1 -1
  22. package/dist/{index-CLL31bEy.d.ts → index-CD8BZz2k.d.ts} +1 -1
  23. package/dist/index.d.mts +123 -20
  24. package/dist/index.d.ts +123 -20
  25. package/dist/index.js +1133 -343
  26. package/dist/index.js.map +1 -1
  27. package/dist/index.mjs +1137 -343
  28. package/dist/index.mjs.map +1 -1
  29. package/dist/pouch/index.d.mts +1 -0
  30. package/dist/pouch/index.d.ts +1 -0
  31. package/dist/pouch/index.js +49 -0
  32. package/dist/pouch/index.js.map +1 -0
  33. package/dist/pouch/index.mjs +16 -0
  34. package/dist/pouch/index.mjs.map +1 -0
  35. package/dist/{types-BefDGkKa.d.ts → types-CewsN87z.d.ts} +1 -1
  36. package/dist/{types-DC-ckZug.d.mts → types-Dbp5DaRR.d.mts} +1 -1
  37. package/dist/{types-legacy-Birv-Jx6.d.mts → types-legacy-6ettoclI.d.mts} +17 -2
  38. package/dist/{types-legacy-Birv-Jx6.d.ts → types-legacy-6ettoclI.d.ts} +17 -2
  39. package/dist/{userDB-DusL7OXe.d.ts → userDB-C4yyAnpp.d.mts} +89 -56
  40. package/dist/{userDB-C33Hzjgn.d.mts → userDB-CD6s6ZCp.d.ts} +89 -56
  41. package/dist/util/packer/index.d.mts +3 -3
  42. package/dist/util/packer/index.d.ts +3 -3
  43. package/package.json +3 -3
  44. package/src/core/interfaces/contentSource.ts +3 -2
  45. package/src/core/interfaces/courseDB.ts +26 -3
  46. package/src/core/interfaces/dataLayerProvider.ts +9 -1
  47. package/src/core/interfaces/userDB.ts +80 -64
  48. package/src/core/navigators/elo.ts +10 -7
  49. package/src/core/navigators/hardcodedOrder.ts +64 -0
  50. package/src/core/navigators/index.ts +2 -1
  51. package/src/core/types/contentNavigationStrategy.ts +2 -1
  52. package/src/core/types/types-legacy.ts +7 -2
  53. package/src/impl/common/BaseUserDB.ts +60 -14
  54. package/src/impl/couch/CouchDBSyncStrategy.ts +2 -2
  55. package/src/impl/couch/PouchDataLayerProvider.ts +21 -0
  56. package/src/impl/couch/adminDB.ts +2 -2
  57. package/src/impl/couch/auth.ts +13 -4
  58. package/src/impl/couch/classroomDB.ts +10 -12
  59. package/src/impl/couch/courseAPI.ts +2 -2
  60. package/src/impl/couch/courseDB.ts +204 -38
  61. package/src/impl/couch/courseLookupDB.ts +4 -3
  62. package/src/impl/couch/index.ts +36 -4
  63. package/src/impl/couch/pouchdb-setup.ts +3 -3
  64. package/src/impl/couch/updateQueue.ts +59 -36
  65. package/src/impl/static/StaticDataLayerProvider.ts +68 -17
  66. package/src/impl/static/courseDB.ts +64 -20
  67. package/src/impl/static/coursesDB.ts +10 -6
  68. package/src/pouch/index.ts +2 -0
  69. package/src/study/ItemQueue.ts +58 -0
  70. package/src/study/SessionController.ts +182 -111
  71. package/src/study/SpacedRepetition.ts +1 -1
  72. package/src/study/services/CardHydrationService.ts +153 -0
  73. package/src/study/services/EloService.ts +85 -0
  74. package/src/study/services/ResponseProcessor.ts +224 -0
  75. package/src/study/services/SrsService.ts +44 -0
  76. package/tsup.config.ts +1 -0
@@ -146,9 +146,9 @@ var init_pouchdb_setup = __esm({
146
146
  import_pouchdb.default.plugin(import_pouchdb_find.default);
147
147
  import_pouchdb.default.plugin(import_pouchdb_authentication.default);
148
148
  import_pouchdb.default.defaults({
149
- ajax: {
150
- timeout: 6e4
151
- }
149
+ // ajax: {
150
+ // timeout: 60000,
151
+ // },
152
152
  });
153
153
  pouchdb_setup_default = import_pouchdb.default;
154
154
  }
@@ -302,42 +302,58 @@ var init_updateQueue = __esm({
302
302
  async applyUpdates(id) {
303
303
  logger.debug(`Applying updates on doc: ${id}`);
304
304
  if (this.inprogressUpdates[id]) {
305
- await this.readDB.info();
305
+ while (this.inprogressUpdates[id]) {
306
+ await new Promise((resolve) => setTimeout(resolve, Math.random() * 50));
307
+ }
306
308
  return this.applyUpdates(id);
307
309
  } else {
308
310
  if (this.pendingUpdates[id] && this.pendingUpdates[id].length > 0) {
309
311
  this.inprogressUpdates[id] = true;
310
- try {
311
- let doc = await this.readDB.get(id);
312
- logger.debug(`Retrieved doc: ${id}`);
313
- while (this.pendingUpdates[id].length !== 0) {
314
- const update = this.pendingUpdates[id].splice(0, 1)[0];
315
- if (typeof update === "function") {
316
- doc = { ...doc, ...update(doc) };
312
+ const MAX_RETRIES = 5;
313
+ for (let i = 0; i < MAX_RETRIES; i++) {
314
+ try {
315
+ const doc = await this.readDB.get(id);
316
+ logger.debug(`Retrieved doc: ${id}`);
317
+ let updatedDoc = { ...doc };
318
+ const updatesToApply = [...this.pendingUpdates[id]];
319
+ for (const update of updatesToApply) {
320
+ if (typeof update === "function") {
321
+ updatedDoc = { ...updatedDoc, ...update(updatedDoc) };
322
+ } else {
323
+ updatedDoc = {
324
+ ...updatedDoc,
325
+ ...update
326
+ };
327
+ }
328
+ }
329
+ await this.writeDB.put(updatedDoc);
330
+ logger.debug(`Put doc: ${id}`);
331
+ this.pendingUpdates[id].splice(0, updatesToApply.length);
332
+ if (this.pendingUpdates[id].length === 0) {
333
+ this.inprogressUpdates[id] = false;
334
+ delete this.inprogressUpdates[id];
317
335
  } else {
318
- doc = {
319
- ...doc,
320
- ...update
321
- };
336
+ return this.applyUpdates(id);
337
+ }
338
+ return updatedDoc;
339
+ } catch (e) {
340
+ if (e.name === "conflict" && i < MAX_RETRIES - 1) {
341
+ logger.warn(`Conflict on update for doc ${id}, retry #${i + 1}`);
342
+ await new Promise((res) => setTimeout(res, 50 * Math.random()));
343
+ } else if (e.name === "not_found" && i === 0) {
344
+ logger.warn(`Update failed for ${id} - does not exist. Throwing to caller.`);
345
+ throw e;
346
+ } else {
347
+ delete this.inprogressUpdates[id];
348
+ if (this.pendingUpdates[id]) {
349
+ delete this.pendingUpdates[id];
350
+ }
351
+ logger.error(`Error on attemped update (retry ${i}): ${JSON.stringify(e)}`);
352
+ throw e;
322
353
  }
323
354
  }
324
- await this.writeDB.put(doc);
325
- logger.debug(`Put doc: ${id}`);
326
- if (this.pendingUpdates[id].length === 0) {
327
- this.inprogressUpdates[id] = false;
328
- delete this.inprogressUpdates[id];
329
- } else {
330
- return this.applyUpdates(id);
331
- }
332
- return doc;
333
- } catch (e) {
334
- delete this.inprogressUpdates[id];
335
- if (this.pendingUpdates[id]) {
336
- delete this.pendingUpdates[id];
337
- }
338
- logger.error(`Error on attemped update: ${JSON.stringify(e)}`);
339
- throw e;
340
355
  }
356
+ throw new Error(`UpdateQueue failed for doc ${id} after ${MAX_RETRIES} retries.`);
341
357
  } else {
342
358
  throw new Error(`Empty Updates Queue Triggered`);
343
359
  }
@@ -425,7 +441,7 @@ function getCourseDB(courseID) {
425
441
  const dbName = `coursedb-${courseID}`;
426
442
  return new pouchdb_setup_default(
427
443
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
428
- pouchDBincludeCredentialsConfig
444
+ createPouchDBConfig()
429
445
  );
430
446
  }
431
447
  var import_common, import_common2, import_common3, import_uuid;
@@ -503,13 +519,16 @@ var init_elo = __esm({
503
519
  }
504
520
  async getNewCards(limit = 99) {
505
521
  const activeCards = await this.user.getActiveCards();
506
- return (await this.course.getCardsCenteredAtELO({ limit, elo: "user" }, (c) => {
507
- if (activeCards.some((ac) => c.includes(ac))) {
508
- return false;
509
- } else {
510
- return true;
522
+ return (await this.course.getCardsCenteredAtELO(
523
+ { limit, elo: "user" },
524
+ (c) => {
525
+ if (activeCards.some((ac) => c.cardID === ac.cardID)) {
526
+ return false;
527
+ } else {
528
+ return true;
529
+ }
511
530
  }
512
- })).map((c) => {
531
+ )).map((c) => {
513
532
  return {
514
533
  ...c,
515
534
  status: "new"
@@ -520,12 +539,74 @@ var init_elo = __esm({
520
539
  }
521
540
  });
522
541
 
542
+ // src/core/navigators/hardcodedOrder.ts
543
+ var hardcodedOrder_exports = {};
544
+ __export(hardcodedOrder_exports, {
545
+ default: () => HardcodedOrderNavigator
546
+ });
547
+ var HardcodedOrderNavigator;
548
+ var init_hardcodedOrder = __esm({
549
+ "src/core/navigators/hardcodedOrder.ts"() {
550
+ "use strict";
551
+ init_navigators();
552
+ init_logger();
553
+ HardcodedOrderNavigator = class extends ContentNavigator {
554
+ orderedCardIds = [];
555
+ user;
556
+ course;
557
+ constructor(user, course, strategyData) {
558
+ super();
559
+ this.user = user;
560
+ this.course = course;
561
+ if (strategyData.serializedData) {
562
+ try {
563
+ this.orderedCardIds = JSON.parse(strategyData.serializedData);
564
+ } catch (e) {
565
+ logger.error("Failed to parse serializedData for HardcodedOrderNavigator", e);
566
+ }
567
+ }
568
+ }
569
+ async getPendingReviews() {
570
+ const reviews = await this.user.getPendingReviews(this.course.getCourseID());
571
+ return reviews.map((r) => {
572
+ return {
573
+ ...r,
574
+ contentSourceType: "course",
575
+ contentSourceID: this.course.getCourseID(),
576
+ cardID: r.cardId,
577
+ courseID: r.courseId,
578
+ reviewID: r._id,
579
+ status: "review"
580
+ };
581
+ });
582
+ }
583
+ async getNewCards(limit = 99) {
584
+ const activeCardIds = (await this.user.getActiveCards()).map((c) => c.cardID);
585
+ const newCardIds = this.orderedCardIds.filter(
586
+ (cardId) => !activeCardIds.includes(cardId)
587
+ );
588
+ const cardsToReturn = newCardIds.slice(0, limit);
589
+ return cardsToReturn.map((cardId) => {
590
+ return {
591
+ cardID: cardId,
592
+ courseID: this.course.getCourseID(),
593
+ contentSourceType: "course",
594
+ contentSourceID: this.course.getCourseID(),
595
+ status: "new"
596
+ };
597
+ });
598
+ }
599
+ };
600
+ }
601
+ });
602
+
523
603
  // import("./**/*") in src/core/navigators/index.ts
524
604
  var globImport;
525
605
  var init_ = __esm({
526
606
  'import("./**/*") in src/core/navigators/index.ts'() {
527
607
  globImport = __glob({
528
608
  "./elo.ts": () => Promise.resolve().then(() => (init_elo(), elo_exports)),
609
+ "./hardcodedOrder.ts": () => Promise.resolve().then(() => (init_hardcodedOrder(), hardcodedOrder_exports)),
529
610
  "./index.ts": () => Promise.resolve().then(() => (init_navigators(), navigators_exports))
530
611
  });
531
612
  }
@@ -545,6 +626,7 @@ var init_navigators = __esm({
545
626
  init_();
546
627
  Navigators = /* @__PURE__ */ ((Navigators2) => {
547
628
  Navigators2["ELO"] = "elo";
629
+ Navigators2["HARDCODED"] = "hardcodedOrder";
548
630
  return Navigators2;
549
631
  })(Navigators || {});
550
632
  ContentNavigator = class {
@@ -557,7 +639,7 @@ var init_navigators = __esm({
557
639
  static async create(user, course, strategyData) {
558
640
  const implementingClass = strategyData.implementingClass;
559
641
  let NavigatorImpl;
560
- const variations = ["", ".js", ".ts"];
642
+ const variations = [".ts", ".js", ""];
561
643
  for (const ext of variations) {
562
644
  try {
563
645
  const module2 = await globImport(`./${implementingClass}${ext}`);
@@ -649,6 +731,25 @@ var init_CouchDBSyncStrategy = __esm({
649
731
  });
650
732
 
651
733
  // src/impl/couch/index.ts
734
+ function createPouchDBConfig() {
735
+ const hasExplicitCredentials = ENV.COUCHDB_USERNAME && ENV.COUCHDB_PASSWORD;
736
+ const isNodeEnvironment = typeof window === "undefined";
737
+ if (hasExplicitCredentials && isNodeEnvironment) {
738
+ return {
739
+ fetch(url, opts = {}) {
740
+ const basicAuth = btoa(`${ENV.COUCHDB_USERNAME}:${ENV.COUCHDB_PASSWORD}`);
741
+ const headers = new Headers(opts.headers || {});
742
+ headers.set("Authorization", `Basic ${basicAuth}`);
743
+ const newOpts = {
744
+ ...opts,
745
+ headers
746
+ };
747
+ return pouchdb_setup_default.fetch(url, newOpts);
748
+ }
749
+ };
750
+ }
751
+ return pouchDBincludeCredentialsConfig;
752
+ }
652
753
  var import_cross_fetch2, import_moment4, import_process, isBrowser, GUEST_LOCAL_DB, localUserDB, pouchDBincludeCredentialsConfig;
653
754
  var init_couch = __esm({
654
755
  "src/impl/couch/index.ts"() {
@@ -911,6 +1012,9 @@ Currently logged-in as ${this._username}.`
911
1012
  await this.init();
912
1013
  return ret;
913
1014
  }
1015
+ async get(id) {
1016
+ return this.localDB.get(id);
1017
+ }
914
1018
  update(id, update) {
915
1019
  return this.updateQueue.update(id, update);
916
1020
  }
@@ -957,7 +1061,12 @@ Currently logged-in as ${this._username}.`
957
1061
  endkey: keys.endkey,
958
1062
  include_docs: true
959
1063
  });
960
- return reviews.rows.map((r) => `${r.doc.courseId}-${r.doc.cardId}`);
1064
+ return reviews.rows.map((r) => {
1065
+ return {
1066
+ courseID: r.doc.courseId,
1067
+ cardID: r.doc.cardId
1068
+ };
1069
+ });
961
1070
  }
962
1071
  async getActivityRecords() {
963
1072
  try {
@@ -1237,8 +1346,18 @@ Currently logged-in as ${this._username}.`
1237
1346
  }
1238
1347
  this.setDBandQ();
1239
1348
  this.syncStrategy.startSync(this.localDB, this.remoteDB);
1240
- void this.applyDesignDocs();
1241
- void this.deduplicateReviews();
1349
+ this.applyDesignDocs().catch((error) => {
1350
+ log3(`Error in applyDesignDocs background task: ${error}`);
1351
+ if (error && typeof error === "object") {
1352
+ log3(`Full error details in applyDesignDocs: ${JSON.stringify(error)}`);
1353
+ }
1354
+ });
1355
+ this.deduplicateReviews().catch((error) => {
1356
+ log3(`Error in deduplicateReviews background task: ${error}`);
1357
+ if (error && typeof error === "object") {
1358
+ log3(`Full error details in background task: ${JSON.stringify(error)}`);
1359
+ }
1360
+ });
1242
1361
  _BaseUser._initialized = true;
1243
1362
  }
1244
1363
  static designDocs = [
@@ -1256,10 +1375,15 @@ Currently logged-in as ${this._username}.`
1256
1375
  }
1257
1376
  ];
1258
1377
  async applyDesignDocs() {
1378
+ log3(`Starting applyDesignDocs for user: ${this._username}`);
1379
+ log3(`Remote DB name: ${this.remoteDB.name || "unknown"}`);
1259
1380
  if (this._username === "admin") {
1381
+ log3("Skipping design docs for admin user");
1260
1382
  return;
1261
1383
  }
1384
+ log3(`Applying ${_BaseUser.designDocs.length} design docs`);
1262
1385
  for (const doc of _BaseUser.designDocs) {
1386
+ log3(`Applying design doc: ${doc._id}`);
1263
1387
  try {
1264
1388
  try {
1265
1389
  const existingDoc = await this.remoteDB.get(doc._id);
@@ -1336,17 +1460,21 @@ Currently logged-in as ${this._username}.`
1336
1460
  } catch (e) {
1337
1461
  const reason = e;
1338
1462
  if (reason.status === 404) {
1339
- const initCardHistory = {
1340
- _id: cardHistoryID,
1341
- cardID: record.cardID,
1342
- courseID: record.courseID,
1343
- records: [record],
1344
- lapses: 0,
1345
- streak: 0,
1346
- bestInterval: 0
1347
- };
1348
- const putResult = await this.writeDB.put(initCardHistory);
1349
- return { ...initCardHistory, _rev: putResult.rev };
1463
+ try {
1464
+ const initCardHistory = {
1465
+ _id: cardHistoryID,
1466
+ cardID: record.cardID,
1467
+ courseID: record.courseID,
1468
+ records: [record],
1469
+ lapses: 0,
1470
+ streak: 0,
1471
+ bestInterval: 0
1472
+ };
1473
+ const putResult = await this.writeDB.put(initCardHistory);
1474
+ return { ...initCardHistory, _rev: putResult.rev };
1475
+ } catch (creationError) {
1476
+ throw new Error(`Failed to create CardHistory for ${cardHistoryID}. Reason: ${creationError}`);
1477
+ }
1350
1478
  } else {
1351
1479
  throw new Error(`putCardRecord failed because of:
1352
1480
  name:${reason.name}
@@ -1358,8 +1486,13 @@ Currently logged-in as ${this._username}.`
1358
1486
  async deduplicateReviews() {
1359
1487
  try {
1360
1488
  log3("Starting deduplication of scheduled reviews...");
1489
+ log3(`Remote DB name: ${this.remoteDB.name || "unknown"}`);
1490
+ log3(`Write DB name: ${this.writeDB.name || "unknown"}`);
1361
1491
  const reviewsMap = {};
1362
1492
  const duplicateDocIds = [];
1493
+ log3(
1494
+ `Attempting to query remoteDB for reviewCards/reviewCards. Database: ${this.remoteDB.name || "unknown"}`
1495
+ );
1363
1496
  const scheduledReviews = await this.remoteDB.query("reviewCards/reviewCards");
1364
1497
  log3(`Found ${scheduledReviews.rows.length} scheduled reviews to process`);
1365
1498
  scheduledReviews.rows.forEach((r) => {
@@ -1394,6 +1527,17 @@ Currently logged-in as ${this._username}.`
1394
1527
  }
1395
1528
  } catch (error) {
1396
1529
  log3(`Error during review deduplication: ${error}`);
1530
+ if (error && typeof error === "object" && "status" in error && error.status === 404) {
1531
+ log3(
1532
+ `Database not found (404) during review deduplication. Database: ${this.remoteDB.name || "unknown"}`
1533
+ );
1534
+ log3(
1535
+ `This might indicate the user database doesn't exist or the reviewCards view isn't available`
1536
+ );
1537
+ }
1538
+ if (error && typeof error === "object") {
1539
+ log3(`Full error details: ${JSON.stringify(error)}`);
1540
+ }
1397
1541
  }
1398
1542
  }
1399
1543
  /**
@@ -2098,7 +2242,10 @@ var init_courseDB3 = __esm({
2098
2242
  };
2099
2243
  }
2100
2244
  async getCardsByELO(elo, limit) {
2101
- return this.unpacker.queryByElo(elo, limit || 25);
2245
+ return (await this.unpacker.queryByElo(elo, limit || 25)).map((card) => {
2246
+ const [courseID, cardID, elo2] = card.split("-");
2247
+ return { courseID, cardID, elo: elo2 ? parseInt(elo2) : void 0 };
2248
+ });
2102
2249
  }
2103
2250
  async getCardEloData(cardIds) {
2104
2251
  const results = await Promise.all(
@@ -2116,16 +2263,20 @@ var init_courseDB3 = __esm({
2116
2263
  async updateCardElo(cardId, _elo) {
2117
2264
  return { ok: true, id: cardId, rev: "1-static" };
2118
2265
  }
2119
- async getNewCards(limit) {
2120
- const cardIds = await this.unpacker.queryByElo(1e3, limit || 10);
2121
- return cardIds.map((cardId) => ({
2122
- status: "new",
2123
- qualifiedID: `${this.courseId}-${cardId}`,
2124
- cardID: cardId,
2125
- contentSourceType: "course",
2126
- contentSourceID: this.courseId,
2127
- courseID: this.courseId
2128
- }));
2266
+ async getNewCards(limit = 99) {
2267
+ const activeCards = await this.userDB.getActiveCards();
2268
+ return (await this.getCardsCenteredAtELO({ limit, elo: "user" }, (c) => {
2269
+ if (activeCards.some((ac) => c.cardID === ac.cardID)) {
2270
+ return false;
2271
+ } else {
2272
+ return true;
2273
+ }
2274
+ })).map((c) => {
2275
+ return {
2276
+ ...c,
2277
+ status: "new"
2278
+ };
2279
+ });
2129
2280
  }
2130
2281
  async getCardsCenteredAtELO(options, filter) {
2131
2282
  let targetElo = typeof options.elo === "number" ? options.elo : 1e3;
@@ -2142,14 +2293,19 @@ var init_courseDB3 = __esm({
2142
2293
  } else if (options.elo === "random") {
2143
2294
  targetElo = 800 + Math.random() * 400;
2144
2295
  }
2145
- let cardIds = await this.unpacker.queryByElo(targetElo, options.limit * 2);
2296
+ let cardIds = (await this.unpacker.queryByElo(targetElo, options.limit * 2)).map((c) => {
2297
+ return {
2298
+ cardID: c,
2299
+ courseID: this.courseId
2300
+ };
2301
+ });
2146
2302
  if (filter) {
2147
2303
  cardIds = cardIds.filter(filter);
2148
2304
  }
2149
- return cardIds.slice(0, options.limit).map((cardId) => ({
2305
+ return cardIds.slice(0, options.limit).map((card) => ({
2150
2306
  status: "new",
2151
- qualifiedID: `${this.courseId}-${cardId}`,
2152
- cardID: cardId,
2307
+ // qualifiedID: `${this.courseId}-${cardId}`,
2308
+ cardID: card.cardID,
2153
2309
  contentSourceType: "course",
2154
2310
  contentSourceID: this.courseId,
2155
2311
  courseID: this.courseId
@@ -2301,7 +2457,7 @@ var init_courseDB3 = __esm({
2301
2457
  // Navigation Strategy Manager implementation
2302
2458
  async getNavigationStrategy(_id) {
2303
2459
  return {
2304
- id: "ELO",
2460
+ _id: "NAVIGATION_STRATEGY-ELO",
2305
2461
  docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
2306
2462
  name: "ELO",
2307
2463
  description: "ELO-based navigation strategy",
@@ -2341,6 +2497,16 @@ var init_courseDB3 = __esm({
2341
2497
  async getAttachmentBlob(docId, attachmentName) {
2342
2498
  return this.unpacker.getAttachmentBlob(docId, attachmentName);
2343
2499
  }
2500
+ // Admin search methods
2501
+ async searchCards(_query) {
2502
+ return [];
2503
+ }
2504
+ async find(_request) {
2505
+ return {
2506
+ docs: [],
2507
+ warning: "Find operations not supported in static mode"
2508
+ };
2509
+ }
2344
2510
  };
2345
2511
  }
2346
2512
  });
@@ -2356,11 +2522,17 @@ var init_coursesDB = __esm({
2356
2522
  this.manifests = manifests;
2357
2523
  }
2358
2524
  async getCourseConfig(courseId) {
2359
- if (!this.manifests[courseId]) {
2360
- logger.warn(`Course ${courseId} not found`);
2361
- return {};
2525
+ const manifest = this.manifests[courseId];
2526
+ if (!manifest) {
2527
+ logger.warn(`Course manifest for ${courseId} not found`);
2528
+ throw new Error(`Course ${courseId} not found`);
2529
+ }
2530
+ if (manifest.courseConfig) {
2531
+ return manifest.courseConfig;
2532
+ } else {
2533
+ logger.warn(`Course config not found in manifest for course ${courseId}`);
2534
+ throw new Error(`Course config not found for course ${courseId}`);
2362
2535
  }
2363
- return {};
2364
2536
  }
2365
2537
  async getCourseList() {
2366
2538
  return Object.keys(this.manifests).map(
@@ -2448,23 +2620,49 @@ var init_StaticDataLayerProvider = __esm({
2448
2620
  config;
2449
2621
  initialized = false;
2450
2622
  courseUnpackers = /* @__PURE__ */ new Map();
2623
+ manifests = {};
2451
2624
  constructor(config) {
2452
2625
  this.config = {
2453
- staticContentPath: config.staticContentPath || "/static-courses",
2454
2626
  localStoragePrefix: config.localStoragePrefix || "skuilder-static",
2455
- manifests: config.manifests || {}
2627
+ rootManifest: config.rootManifest || { dependencies: {} },
2628
+ rootManifestUrl: config.rootManifestUrl || "/"
2456
2629
  };
2457
2630
  }
2631
+ async resolveCourseDependencies() {
2632
+ logger.info("[StaticDataLayerProvider] Starting course dependency resolution...");
2633
+ const rootManifest = this.config.rootManifest;
2634
+ for (const [courseName, courseUrl] of Object.entries(rootManifest.dependencies || {})) {
2635
+ try {
2636
+ logger.debug(`[StaticDataLayerProvider] Resolving dependency: ${courseName} from ${courseUrl}`);
2637
+ const courseManifestUrl = new URL(courseUrl, this.config.rootManifestUrl).href;
2638
+ const courseJsonResponse = await fetch(courseManifestUrl);
2639
+ if (!courseJsonResponse.ok) {
2640
+ throw new Error(`Failed to fetch course manifest for ${courseName}`);
2641
+ }
2642
+ const courseJson = await courseJsonResponse.json();
2643
+ if (courseJson.content && courseJson.content.manifest) {
2644
+ const baseUrl = new URL(".", courseManifestUrl).href;
2645
+ const finalManifestUrl = new URL(courseJson.content.manifest, courseManifestUrl).href;
2646
+ const finalManifestResponse = await fetch(finalManifestUrl);
2647
+ if (!finalManifestResponse.ok) {
2648
+ throw new Error(`Failed to fetch final content manifest for ${courseName} at ${finalManifestUrl}`);
2649
+ }
2650
+ const finalManifest = await finalManifestResponse.json();
2651
+ this.manifests[courseName] = finalManifest;
2652
+ const unpacker = new StaticDataUnpacker(finalManifest, baseUrl);
2653
+ this.courseUnpackers.set(courseName, unpacker);
2654
+ logger.info(`[StaticDataLayerProvider] Successfully resolved and prepared course: ${courseName}`);
2655
+ }
2656
+ } catch (e) {
2657
+ logger.error(`[StaticDataLayerProvider] Failed to resolve dependency ${courseName}:`, e);
2658
+ }
2659
+ }
2660
+ logger.info("[StaticDataLayerProvider] Course dependency resolution complete.");
2661
+ }
2458
2662
  async initialize() {
2459
2663
  if (this.initialized) return;
2460
2664
  logger.info("Initializing static data layer provider");
2461
- for (const [courseId, manifest] of Object.entries(this.config.manifests)) {
2462
- const unpacker = new StaticDataUnpacker(
2463
- manifest,
2464
- `${this.config.staticContentPath}/${courseId}`
2465
- );
2466
- this.courseUnpackers.set(courseId, unpacker);
2467
- }
2665
+ await this.resolveCourseDependencies();
2468
2666
  this.initialized = true;
2469
2667
  }
2470
2668
  async teardown() {
@@ -2478,13 +2676,13 @@ var init_StaticDataLayerProvider = __esm({
2478
2676
  getCourseDB(courseId) {
2479
2677
  const unpacker = this.courseUnpackers.get(courseId);
2480
2678
  if (!unpacker) {
2481
- throw new Error(`Course ${courseId} not found in static data`);
2679
+ throw new Error(`Course ${courseId} not found or failed to initialize in static data layer.`);
2482
2680
  }
2483
- const manifest = this.config.manifests[courseId];
2681
+ const manifest = this.manifests[courseId];
2484
2682
  return new StaticCourseDB(courseId, unpacker, this.getUserDB(), manifest);
2485
2683
  }
2486
2684
  getCoursesDB() {
2487
- return new StaticCoursesDB(this.config.manifests);
2685
+ return new StaticCoursesDB(this.manifests);
2488
2686
  }
2489
2687
  async getClassroomDB(_classId, _type) {
2490
2688
  throw new Error("Classrooms not supported in static mode");
@@ -2492,6 +2690,12 @@ var init_StaticDataLayerProvider = __esm({
2492
2690
  getAdminDB() {
2493
2691
  throw new Error("Admin functions not supported in static mode");
2494
2692
  }
2693
+ async createUserReaderForUser(targetUsername) {
2694
+ logger.warn(`StaticDataLayerProvider: Multi-user access not supported in static mode`);
2695
+ logger.warn(`Request: trying to access data for ${targetUsername}`);
2696
+ logger.warn(`Returning current user's data instead`);
2697
+ return this.getUserDB();
2698
+ }
2495
2699
  isReadOnly() {
2496
2700
  return true;
2497
2701
  }