@vue-skuilder/db 0.1.11-7 → 0.1.11

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 (68) hide show
  1. package/dist/core/index.d.mts +5 -5
  2. package/dist/core/index.d.ts +5 -5
  3. package/dist/core/index.js +212 -50
  4. package/dist/core/index.js.map +1 -1
  5. package/dist/core/index.mjs +212 -50
  6. package/dist/core/index.mjs.map +1 -1
  7. package/dist/{dataLayerProvider-DqtNroSh.d.ts → dataLayerProvider-VieuAAkV.d.mts} +8 -1
  8. package/dist/{dataLayerProvider-BInqI_RF.d.mts → dataLayerProvider-juuqUHOP.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 +229 -63
  12. package/dist/impl/couch/index.js.map +1 -1
  13. package/dist/impl/couch/index.mjs +228 -62
  14. package/dist/impl/couch/index.mjs.map +1 -1
  15. package/dist/impl/static/index.d.mts +13 -6
  16. package/dist/impl/static/index.d.ts +13 -6
  17. package/dist/impl/static/index.js +142 -46
  18. package/dist/impl/static/index.js.map +1 -1
  19. package/dist/impl/static/index.mjs +142 -46
  20. package/dist/impl/static/index.mjs.map +1 -1
  21. package/dist/{index-CLL31bEy.d.ts → index-CWY6yhkV.d.ts} +1 -1
  22. package/dist/{index-CUNnL38E.d.mts → index-DZyxHCcf.d.mts} +1 -1
  23. package/dist/index.d.mts +28 -20
  24. package/dist/index.d.ts +28 -20
  25. package/dist/index.js +374 -108
  26. package/dist/index.js.map +1 -1
  27. package/dist/index.mjs +378 -108
  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-DC-ckZug.d.mts → types-Che4wTwA.d.mts} +1 -1
  36. package/dist/{types-BefDGkKa.d.ts → types-DtoI27Xh.d.ts} +1 -1
  37. package/dist/{types-legacy-Birv-Jx6.d.mts → types-legacy-B8ahaCbj.d.mts} +5 -1
  38. package/dist/{types-legacy-Birv-Jx6.d.ts → types-legacy-B8ahaCbj.d.ts} +5 -1
  39. package/dist/{userDB-C33Hzjgn.d.mts → userDB-B7zTQ123.d.ts} +88 -55
  40. package/dist/{userDB-DusL7OXe.d.ts → userDB-DJ8HMw83.d.mts} +88 -55
  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/index.ts +1 -1
  50. package/src/core/types/types-legacy.ts +5 -0
  51. package/src/impl/common/BaseUserDB.ts +45 -3
  52. package/src/impl/couch/CouchDBSyncStrategy.ts +2 -2
  53. package/src/impl/couch/PouchDataLayerProvider.ts +21 -0
  54. package/src/impl/couch/adminDB.ts +2 -2
  55. package/src/impl/couch/auth.ts +13 -4
  56. package/src/impl/couch/classroomDB.ts +10 -12
  57. package/src/impl/couch/courseAPI.ts +2 -2
  58. package/src/impl/couch/courseDB.ts +130 -11
  59. package/src/impl/couch/courseLookupDB.ts +4 -3
  60. package/src/impl/couch/index.ts +36 -4
  61. package/src/impl/couch/pouchdb-setup.ts +3 -3
  62. package/src/impl/couch/updateQueue.ts +51 -33
  63. package/src/impl/static/StaticDataLayerProvider.ts +11 -0
  64. package/src/impl/static/courseDB.ts +47 -8
  65. package/src/pouch/index.ts +2 -0
  66. package/src/study/SessionController.ts +168 -51
  67. package/src/study/SpacedRepetition.ts +1 -1
  68. package/tsup.config.ts +1 -0
package/dist/index.js CHANGED
@@ -187,9 +187,9 @@ var init_pouchdb_setup = __esm({
187
187
  import_pouchdb.default.plugin(import_pouchdb_find.default);
188
188
  import_pouchdb.default.plugin(import_pouchdb_authentication.default);
189
189
  import_pouchdb.default.defaults({
190
- ajax: {
191
- timeout: 6e4
192
- }
190
+ // ajax: {
191
+ // timeout: 60000,
192
+ // },
193
193
  });
194
194
  pouchdb_setup_default = import_pouchdb.default;
195
195
  }
@@ -473,37 +473,48 @@ var init_updateQueue = __esm({
473
473
  } else {
474
474
  if (this.pendingUpdates[id] && this.pendingUpdates[id].length > 0) {
475
475
  this.inprogressUpdates[id] = true;
476
- try {
477
- let doc = await this.readDB.get(id);
478
- logger.debug(`Retrieved doc: ${id}`);
479
- while (this.pendingUpdates[id].length !== 0) {
480
- const update = this.pendingUpdates[id].splice(0, 1)[0];
481
- if (typeof update === "function") {
482
- doc = { ...doc, ...update(doc) };
476
+ const MAX_RETRIES = 5;
477
+ for (let i = 0; i < MAX_RETRIES; i++) {
478
+ try {
479
+ const doc = await this.readDB.get(id);
480
+ logger.debug(`Retrieved doc: ${id}`);
481
+ let updatedDoc = { ...doc };
482
+ const updatesToApply = [...this.pendingUpdates[id]];
483
+ for (const update of updatesToApply) {
484
+ if (typeof update === "function") {
485
+ updatedDoc = { ...updatedDoc, ...update(updatedDoc) };
486
+ } else {
487
+ updatedDoc = {
488
+ ...updatedDoc,
489
+ ...update
490
+ };
491
+ }
492
+ }
493
+ await this.writeDB.put(updatedDoc);
494
+ logger.debug(`Put doc: ${id}`);
495
+ this.pendingUpdates[id].splice(0, updatesToApply.length);
496
+ if (this.pendingUpdates[id].length === 0) {
497
+ this.inprogressUpdates[id] = false;
498
+ delete this.inprogressUpdates[id];
483
499
  } else {
484
- doc = {
485
- ...doc,
486
- ...update
487
- };
500
+ return this.applyUpdates(id);
501
+ }
502
+ return updatedDoc;
503
+ } catch (e) {
504
+ if (e.name === "conflict" && i < MAX_RETRIES - 1) {
505
+ logger.warn(`Conflict on update for doc ${id}, retry #${i + 1}`);
506
+ await new Promise((res) => setTimeout(res, 50 * Math.random()));
507
+ } else {
508
+ delete this.inprogressUpdates[id];
509
+ if (this.pendingUpdates[id]) {
510
+ delete this.pendingUpdates[id];
511
+ }
512
+ logger.error(`Error on attemped update (retry ${i}): ${JSON.stringify(e)}`);
513
+ throw e;
488
514
  }
489
515
  }
490
- await this.writeDB.put(doc);
491
- logger.debug(`Put doc: ${id}`);
492
- if (this.pendingUpdates[id].length === 0) {
493
- this.inprogressUpdates[id] = false;
494
- delete this.inprogressUpdates[id];
495
- } else {
496
- return this.applyUpdates(id);
497
- }
498
- return doc;
499
- } catch (e) {
500
- delete this.inprogressUpdates[id];
501
- if (this.pendingUpdates[id]) {
502
- delete this.pendingUpdates[id];
503
- }
504
- logger.error(`Error on attemped update: ${JSON.stringify(e)}`);
505
- throw e;
506
516
  }
517
+ throw new Error(`UpdateQueue failed for doc ${id} after ${MAX_RETRIES} retries.`);
507
518
  } else {
508
519
  throw new Error(`Empty Updates Queue Triggered`);
509
520
  }
@@ -754,7 +765,7 @@ function getCourseDB(courseID) {
754
765
  const dbName = `coursedb-${courseID}`;
755
766
  return new pouchdb_setup_default(
756
767
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
757
- pouchDBincludeCredentialsConfig
768
+ createPouchDBConfig()
758
769
  );
759
770
  }
760
771
  var import_common, import_common2, import_common3, import_uuid, AlreadyTaggedErr;
@@ -880,6 +891,7 @@ var init_courseLookupDB = __esm({
880
891
  const doc = await _CourseLookup._db.get(courseID);
881
892
  return await _CourseLookup._db.remove(doc);
882
893
  }
894
+ // [ ] rename to allCourses()
883
895
  static async allCourseWare() {
884
896
  const resp = await _CourseLookup._db.allDocs({
885
897
  include_docs: true
@@ -950,13 +962,16 @@ var init_elo = __esm({
950
962
  }
951
963
  async getNewCards(limit = 99) {
952
964
  const activeCards = await this.user.getActiveCards();
953
- return (await this.course.getCardsCenteredAtELO({ limit, elo: "user" }, (c) => {
954
- if (activeCards.some((ac) => c.includes(ac))) {
955
- return false;
956
- } else {
957
- return true;
965
+ return (await this.course.getCardsCenteredAtELO(
966
+ { limit, elo: "user" },
967
+ (c) => {
968
+ if (activeCards.some((ac) => c.cardID === ac.cardID)) {
969
+ return false;
970
+ } else {
971
+ return true;
972
+ }
958
973
  }
959
- })).map((c) => {
974
+ )).map((c) => {
960
975
  return {
961
976
  ...c,
962
977
  status: "new"
@@ -1004,7 +1019,7 @@ var init_navigators = __esm({
1004
1019
  static async create(user, course, strategyData) {
1005
1020
  const implementingClass = strategyData.implementingClass;
1006
1021
  let NavigatorImpl;
1007
- const variations = ["", ".js", ".ts"];
1022
+ const variations = [".ts", ".js", ""];
1008
1023
  for (const ext of variations) {
1009
1024
  try {
1010
1025
  const module2 = await globImport(`./${implementingClass}${ext}`);
@@ -1308,7 +1323,13 @@ var init_courseDB = __esm({
1308
1323
  } else {
1309
1324
  return s;
1310
1325
  }
1311
- }).map((c) => `${this.id}-${c.id}-${c.key}`);
1326
+ }).map((c) => {
1327
+ return {
1328
+ courseID: this.id,
1329
+ cardID: c.id,
1330
+ elo: c.key
1331
+ };
1332
+ });
1312
1333
  const str = `below:
1313
1334
  ${below.rows.map((r) => ` ${r.id}-${r.key}
1314
1335
  `)}
@@ -1363,7 +1384,13 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
1363
1384
  }
1364
1385
  }
1365
1386
  async addTagToCard(cardId, tagId, updateELO) {
1366
- return await addTagToCard(this.id, cardId, tagId, (await this._getCurrentUser()).getUsername(), updateELO);
1387
+ return await addTagToCard(
1388
+ this.id,
1389
+ cardId,
1390
+ tagId,
1391
+ (await this._getCurrentUser()).getUsername(),
1392
+ updateELO
1393
+ );
1367
1394
  }
1368
1395
  async removeTagFromCard(cardId, tagId) {
1369
1396
  return await removeTagFromCard(this.id, cardId, tagId);
@@ -1556,17 +1583,93 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
1556
1583
  selectedCards.push(card);
1557
1584
  }
1558
1585
  return selectedCards.map((c) => {
1559
- const split = c.split("-");
1560
1586
  return {
1561
1587
  courseID: this.id,
1562
- cardID: split[1],
1563
- qualifiedID: `${split[0]}-${split[1]}`,
1588
+ cardID: c.cardID,
1564
1589
  contentSourceType: "course",
1565
1590
  contentSourceID: this.id,
1591
+ elo: c.elo,
1566
1592
  status: "new"
1567
1593
  };
1568
1594
  });
1569
1595
  }
1596
+ // Admin search methods
1597
+ async searchCards(query) {
1598
+ logger.log(`[CourseDB ${this.id}] Searching for: "${query}"`);
1599
+ let displayableData;
1600
+ try {
1601
+ displayableData = await this.db.find({
1602
+ selector: {
1603
+ docType: "DISPLAYABLE_DATA",
1604
+ "data.0.data": { $regex: `.*${query}.*` }
1605
+ }
1606
+ });
1607
+ logger.log(`[CourseDB ${this.id}] Regex search on data[0].data successful`);
1608
+ } catch (regexError) {
1609
+ logger.log(
1610
+ `[CourseDB ${this.id}] Regex search failed, falling back to manual search:`,
1611
+ regexError
1612
+ );
1613
+ const allDisplayable = await this.db.find({
1614
+ selector: {
1615
+ docType: "DISPLAYABLE_DATA"
1616
+ }
1617
+ });
1618
+ logger.log(
1619
+ `[CourseDB ${this.id}] Retrieved ${allDisplayable.docs.length} documents for manual filtering`
1620
+ );
1621
+ displayableData = {
1622
+ docs: allDisplayable.docs.filter((doc) => {
1623
+ const docString = JSON.stringify(doc).toLowerCase();
1624
+ const match = docString.includes(query.toLowerCase());
1625
+ if (match) {
1626
+ logger.log(`[CourseDB ${this.id}] Manual match found in document: ${doc._id}`);
1627
+ }
1628
+ return match;
1629
+ })
1630
+ };
1631
+ }
1632
+ logger.log(
1633
+ `[CourseDB ${this.id}] Found ${displayableData.docs.length} displayable data documents`
1634
+ );
1635
+ if (displayableData.docs.length === 0) {
1636
+ const allDisplayableData = await this.db.find({
1637
+ selector: {
1638
+ docType: "DISPLAYABLE_DATA"
1639
+ },
1640
+ limit: 5
1641
+ // Just sample a few
1642
+ });
1643
+ logger.log(
1644
+ `[CourseDB ${this.id}] Sample displayable data:`,
1645
+ allDisplayableData.docs.map((d) => ({
1646
+ id: d._id,
1647
+ docType: d.docType,
1648
+ dataStructure: d.data ? Object.keys(d.data) : "no data field",
1649
+ dataContent: d.data,
1650
+ fullDoc: d
1651
+ }))
1652
+ );
1653
+ }
1654
+ const allResults = [];
1655
+ for (const dd of displayableData.docs) {
1656
+ const cards = await this.db.find({
1657
+ selector: {
1658
+ docType: "CARD",
1659
+ id_displayable_data: { $in: [dd._id] }
1660
+ }
1661
+ });
1662
+ logger.log(
1663
+ `[CourseDB ${this.id}] Displayable data ${dd._id} linked to ${cards.docs.length} cards`
1664
+ );
1665
+ allResults.push(...cards.docs);
1666
+ }
1667
+ logger.log(`[CourseDB ${this.id}] Total cards found: ${allResults.length}`);
1668
+ return allResults;
1669
+ }
1670
+ async find(request) {
1671
+ return this.db.find(request);
1672
+ }
1570
1673
  };
1571
1674
  }
1572
1675
  });
@@ -1632,7 +1735,7 @@ var init_classroomDB2 = __esm({
1632
1735
  const dbName = `classdb-student-${this._id}`;
1633
1736
  this._db = new pouchdb_setup_default(
1634
1737
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
1635
- pouchDBincludeCredentialsConfig
1738
+ createPouchDBConfig()
1636
1739
  );
1637
1740
  try {
1638
1741
  const cfg = await this._db.get(CLASSROOM_CONFIG);
@@ -1701,9 +1804,11 @@ var init_classroomDB2 = __esm({
1701
1804
  ret.push(await getCourseDB2(content.courseID).get(content.cardID));
1702
1805
  }
1703
1806
  }
1704
- logger.info(`New Cards from classroom ${this._cfg.name}: ${ret.map((c) => c.qualifiedID)}`);
1807
+ logger.info(
1808
+ `New Cards from classroom ${this._cfg.name}: ${ret.map((c) => `${c.courseID}-${c.cardID}`)}`
1809
+ );
1705
1810
  return ret.filter((c) => {
1706
- if (activeCards.some((ac) => c.qualifiedID.includes(ac))) {
1811
+ if (activeCards.some((ac) => c.cardID === ac.cardID)) {
1707
1812
  return false;
1708
1813
  } else {
1709
1814
  return true;
@@ -1722,11 +1827,11 @@ var init_classroomDB2 = __esm({
1722
1827
  const stuDbName = `classdb-student-${this._id}`;
1723
1828
  this._db = new pouchdb_setup_default(
1724
1829
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
1725
- pouchDBincludeCredentialsConfig
1830
+ createPouchDBConfig()
1726
1831
  );
1727
1832
  this._stuDb = new pouchdb_setup_default(
1728
1833
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + stuDbName,
1729
- pouchDBincludeCredentialsConfig
1834
+ createPouchDBConfig()
1730
1835
  );
1731
1836
  try {
1732
1837
  return this._db.get(CLASSROOM_CONFIG).then((cfg) => {
@@ -1811,7 +1916,7 @@ var init_adminDB2 = __esm({
1811
1916
  constructor() {
1812
1917
  this.usersDB = new pouchdb_setup_default(
1813
1918
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + "_users",
1814
- pouchDBincludeCredentialsConfig
1919
+ createPouchDBConfig()
1815
1920
  );
1816
1921
  }
1817
1922
  async getUsers() {
@@ -1872,9 +1977,10 @@ var init_adminDB2 = __esm({
1872
1977
  async function getCurrentSession() {
1873
1978
  try {
1874
1979
  if (ENV.COUCHDB_SERVER_URL === NOT_SET || ENV.COUCHDB_SERVER_PROTOCOL === NOT_SET) {
1875
- throw new Error("CouchDB server configuration not properly initialized");
1980
+ throw new Error(`CouchDB server configuration not properly initialized. Protocol: "${ENV.COUCHDB_SERVER_PROTOCOL}", URL: "${ENV.COUCHDB_SERVER_URL}"`);
1876
1981
  }
1877
- const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${ENV.COUCHDB_SERVER_URL}_session`;
1982
+ const baseUrl = ENV.COUCHDB_SERVER_URL.endsWith("/") ? ENV.COUCHDB_SERVER_URL.slice(0, -1) : ENV.COUCHDB_SERVER_URL;
1983
+ const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${baseUrl}/_session`;
1878
1984
  logger.debug(`Attempting session check at: ${url}`);
1879
1985
  const response = await (0, import_cross_fetch.default)(url, {
1880
1986
  method: "GET",
@@ -1886,8 +1992,10 @@ async function getCurrentSession() {
1886
1992
  const resp = await response.json();
1887
1993
  return resp;
1888
1994
  } catch (error) {
1889
- logger.error(`Session check error: ${error}`);
1890
- throw new Error(`Session check failed: ${error}`);
1995
+ const baseUrl = ENV.COUCHDB_SERVER_URL.endsWith("/") ? ENV.COUCHDB_SERVER_URL.slice(0, -1) : ENV.COUCHDB_SERVER_URL;
1996
+ const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${baseUrl}/_session`;
1997
+ logger.error(`Session check error attempting to connect to: ${url} - ${error}`);
1998
+ throw new Error(`Session check failed connecting to ${url}: ${error}`);
1891
1999
  }
1892
2000
  }
1893
2001
  async function getLoggedInUsername() {
@@ -2082,7 +2190,7 @@ var init_CouchDBSyncStrategy = __esm({
2082
2190
  log3(`Fetching user database: ${dbName} (${username})`);
2083
2191
  const ret = new pouchdb_setup_default(
2084
2192
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
2085
- pouchDBincludeCredentialsConfig
2193
+ createPouchDBConfig()
2086
2194
  );
2087
2195
  if (guestAccount) {
2088
2196
  updateGuestAccountExpirationDate(ret);
@@ -2094,10 +2202,29 @@ var init_CouchDBSyncStrategy = __esm({
2094
2202
  });
2095
2203
 
2096
2204
  // src/impl/couch/index.ts
2205
+ function createPouchDBConfig() {
2206
+ const hasExplicitCredentials = ENV.COUCHDB_USERNAME && ENV.COUCHDB_PASSWORD;
2207
+ const isNodeEnvironment2 = typeof window === "undefined";
2208
+ if (hasExplicitCredentials && isNodeEnvironment2) {
2209
+ return {
2210
+ fetch(url, opts = {}) {
2211
+ const basicAuth = btoa(`${ENV.COUCHDB_USERNAME}:${ENV.COUCHDB_PASSWORD}`);
2212
+ const headers = new Headers(opts.headers || {});
2213
+ headers.set("Authorization", `Basic ${basicAuth}`);
2214
+ const newOpts = {
2215
+ ...opts,
2216
+ headers
2217
+ };
2218
+ return pouchdb_setup_default.fetch(url, newOpts);
2219
+ }
2220
+ };
2221
+ }
2222
+ return pouchDBincludeCredentialsConfig;
2223
+ }
2097
2224
  function getCourseDB2(courseID) {
2098
2225
  return new pouchdb_setup_default(
2099
2226
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + "coursedb-" + courseID,
2100
- pouchDBincludeCredentialsConfig
2227
+ createPouchDBConfig()
2101
2228
  );
2102
2229
  }
2103
2230
  function getCourseDocs(courseID, docIDs, options = {}) {
@@ -2389,6 +2516,9 @@ Currently logged-in as ${this._username}.`
2389
2516
  await this.init();
2390
2517
  return ret;
2391
2518
  }
2519
+ async get(id) {
2520
+ return this.localDB.get(id);
2521
+ }
2392
2522
  update(id, update) {
2393
2523
  return this.updateQueue.update(id, update);
2394
2524
  }
@@ -2435,7 +2565,12 @@ Currently logged-in as ${this._username}.`
2435
2565
  endkey: keys.endkey,
2436
2566
  include_docs: true
2437
2567
  });
2438
- return reviews.rows.map((r) => `${r.doc.courseId}-${r.doc.cardId}`);
2568
+ return reviews.rows.map((r) => {
2569
+ return {
2570
+ courseID: r.doc.courseId,
2571
+ cardID: r.doc.cardId
2572
+ };
2573
+ });
2439
2574
  }
2440
2575
  async getActivityRecords() {
2441
2576
  try {
@@ -2715,8 +2850,18 @@ Currently logged-in as ${this._username}.`
2715
2850
  }
2716
2851
  this.setDBandQ();
2717
2852
  this.syncStrategy.startSync(this.localDB, this.remoteDB);
2718
- void this.applyDesignDocs();
2719
- void this.deduplicateReviews();
2853
+ this.applyDesignDocs().catch((error) => {
2854
+ log4(`Error in applyDesignDocs background task: ${error}`);
2855
+ if (error && typeof error === "object") {
2856
+ log4(`Full error details in applyDesignDocs: ${JSON.stringify(error)}`);
2857
+ }
2858
+ });
2859
+ this.deduplicateReviews().catch((error) => {
2860
+ log4(`Error in deduplicateReviews background task: ${error}`);
2861
+ if (error && typeof error === "object") {
2862
+ log4(`Full error details in background task: ${JSON.stringify(error)}`);
2863
+ }
2864
+ });
2720
2865
  _BaseUser._initialized = true;
2721
2866
  }
2722
2867
  static designDocs = [
@@ -2734,10 +2879,15 @@ Currently logged-in as ${this._username}.`
2734
2879
  }
2735
2880
  ];
2736
2881
  async applyDesignDocs() {
2882
+ log4(`Starting applyDesignDocs for user: ${this._username}`);
2883
+ log4(`Remote DB name: ${this.remoteDB.name || "unknown"}`);
2737
2884
  if (this._username === "admin") {
2885
+ log4("Skipping design docs for admin user");
2738
2886
  return;
2739
2887
  }
2888
+ log4(`Applying ${_BaseUser.designDocs.length} design docs`);
2740
2889
  for (const doc of _BaseUser.designDocs) {
2890
+ log4(`Applying design doc: ${doc._id}`);
2741
2891
  try {
2742
2892
  try {
2743
2893
  const existingDoc = await this.remoteDB.get(doc._id);
@@ -2836,8 +2986,13 @@ Currently logged-in as ${this._username}.`
2836
2986
  async deduplicateReviews() {
2837
2987
  try {
2838
2988
  log4("Starting deduplication of scheduled reviews...");
2989
+ log4(`Remote DB name: ${this.remoteDB.name || "unknown"}`);
2990
+ log4(`Write DB name: ${this.writeDB.name || "unknown"}`);
2839
2991
  const reviewsMap = {};
2840
2992
  const duplicateDocIds = [];
2993
+ log4(
2994
+ `Attempting to query remoteDB for reviewCards/reviewCards. Database: ${this.remoteDB.name || "unknown"}`
2995
+ );
2841
2996
  const scheduledReviews = await this.remoteDB.query("reviewCards/reviewCards");
2842
2997
  log4(`Found ${scheduledReviews.rows.length} scheduled reviews to process`);
2843
2998
  scheduledReviews.rows.forEach((r) => {
@@ -2872,6 +3027,17 @@ Currently logged-in as ${this._username}.`
2872
3027
  }
2873
3028
  } catch (error) {
2874
3029
  log4(`Error during review deduplication: ${error}`);
3030
+ if (error && typeof error === "object" && "status" in error && error.status === 404) {
3031
+ log4(
3032
+ `Database not found (404) during review deduplication. Database: ${this.remoteDB.name || "unknown"}`
3033
+ );
3034
+ log4(
3035
+ `This might indicate the user database doesn't exist or the reviewCards view isn't available`
3036
+ );
3037
+ }
3038
+ if (error && typeof error === "object") {
3039
+ log4(`Full error details: ${JSON.stringify(error)}`);
3040
+ }
2875
3041
  }
2876
3042
  }
2877
3043
  /**
@@ -3105,6 +3271,16 @@ var init_PouchDataLayerProvider = __esm({
3105
3271
  getAdminDB() {
3106
3272
  return new AdminDB();
3107
3273
  }
3274
+ async createUserReaderForUser(targetUsername) {
3275
+ const requestingUsername = await getLoggedInUsername();
3276
+ if (requestingUsername !== "admin") {
3277
+ throw new Error("Unauthorized: Only admin users can access other users' data");
3278
+ }
3279
+ logger.info(`Admin user '${requestingUsername}' requesting UserDBReader for '${targetUsername}'`);
3280
+ const syncStrategy = new CouchDBSyncStrategy();
3281
+ const targetUserDB = await BaseUser.instance(syncStrategy, targetUsername);
3282
+ return targetUserDB;
3283
+ }
3108
3284
  isReadOnly() {
3109
3285
  return false;
3110
3286
  }
@@ -3557,7 +3733,10 @@ var init_courseDB2 = __esm({
3557
3733
  };
3558
3734
  }
3559
3735
  async getCardsByELO(elo, limit) {
3560
- return this.unpacker.queryByElo(elo, limit || 25);
3736
+ return (await this.unpacker.queryByElo(elo, limit || 25)).map((card) => {
3737
+ const [courseID, cardID, elo2] = card.split("-");
3738
+ return { courseID, cardID, elo: elo2 ? parseInt(elo2) : void 0 };
3739
+ });
3561
3740
  }
3562
3741
  async getCardEloData(cardIds) {
3563
3742
  const results = await Promise.all(
@@ -3601,14 +3780,19 @@ var init_courseDB2 = __esm({
3601
3780
  } else if (options.elo === "random") {
3602
3781
  targetElo = 800 + Math.random() * 400;
3603
3782
  }
3604
- let cardIds = await this.unpacker.queryByElo(targetElo, options.limit * 2);
3783
+ let cardIds = (await this.unpacker.queryByElo(targetElo, options.limit * 2)).map((c) => {
3784
+ return {
3785
+ cardID: c,
3786
+ courseID: this.courseId
3787
+ };
3788
+ });
3605
3789
  if (filter) {
3606
3790
  cardIds = cardIds.filter(filter);
3607
3791
  }
3608
- return cardIds.slice(0, options.limit).map((cardId) => ({
3792
+ return cardIds.slice(0, options.limit).map((card) => ({
3609
3793
  status: "new",
3610
- qualifiedID: `${this.courseId}-${cardId}`,
3611
- cardID: cardId,
3794
+ // qualifiedID: `${this.courseId}-${cardId}`,
3795
+ cardID: card.cardID,
3612
3796
  contentSourceType: "course",
3613
3797
  contentSourceID: this.courseId,
3614
3798
  courseID: this.courseId
@@ -3800,6 +3984,16 @@ var init_courseDB2 = __esm({
3800
3984
  async getAttachmentBlob(docId, attachmentName) {
3801
3985
  return this.unpacker.getAttachmentBlob(docId, attachmentName);
3802
3986
  }
3987
+ // Admin search methods
3988
+ async searchCards(_query) {
3989
+ return [];
3990
+ }
3991
+ async find(_request) {
3992
+ return {
3993
+ docs: [],
3994
+ warning: "Find operations not supported in static mode"
3995
+ };
3996
+ }
3803
3997
  };
3804
3998
  }
3805
3999
  });
@@ -3955,6 +4149,12 @@ var init_StaticDataLayerProvider = __esm({
3955
4149
  getAdminDB() {
3956
4150
  throw new Error("Admin functions not supported in static mode");
3957
4151
  }
4152
+ async createUserReaderForUser(targetUsername) {
4153
+ logger.warn(`StaticDataLayerProvider: Multi-user access not supported in static mode`);
4154
+ logger.warn(`Request: trying to access data for ${targetUsername}`);
4155
+ logger.warn(`Returning current user's data instead`);
4156
+ return this.getUserDB();
4157
+ }
3958
4158
  isReadOnly() {
3959
4159
  return true;
3960
4160
  }
@@ -5717,6 +5917,7 @@ init_dataDirectory();
5717
5917
  init_tuiLogger();
5718
5918
 
5719
5919
  // src/study/SessionController.ts
5920
+ var import_common15 = require("@vue-skuilder/common");
5720
5921
  function randomInt(min, max) {
5721
5922
  return Math.floor(Math.random() * (max - min + 1)) + min;
5722
5923
  }
@@ -5727,15 +5928,15 @@ var ItemQueue = class {
5727
5928
  get dequeueCount() {
5728
5929
  return this._dequeueCount;
5729
5930
  }
5730
- add(item) {
5731
- if (this.seenCardIds.find((d) => d === item.cardID)) {
5931
+ add(item, cardId) {
5932
+ if (this.seenCardIds.find((d) => d === cardId)) {
5732
5933
  return;
5733
5934
  }
5734
- this.seenCardIds.push(item.cardID);
5935
+ this.seenCardIds.push(cardId);
5735
5936
  this.q.push(item);
5736
5937
  }
5737
- addAll(items) {
5738
- items.forEach((i) => this.add(i));
5938
+ addAll(items, cardIdExtractor) {
5939
+ items.forEach((i) => this.add(i, cardIdExtractor(i)));
5739
5940
  }
5740
5941
  get length() {
5741
5942
  return this.q.length;
@@ -5753,12 +5954,14 @@ var ItemQueue = class {
5753
5954
  }
5754
5955
  get toString() {
5755
5956
  return `${typeof this.q[0]}:
5756
- ` + this.q.map((i) => ` ${i.qualifiedID}: ${i.status}`).join("\n");
5957
+ ` + this.q.map((i) => ` ${i.courseID}+${i.cardID}: ${i.status}`).join("\n");
5757
5958
  }
5758
5959
  };
5759
5960
  var SessionController = class extends Loggable {
5760
5961
  _className = "SessionController";
5761
5962
  sources;
5963
+ dataLayer;
5964
+ getViewComponent;
5762
5965
  _sessionRecord = [];
5763
5966
  set sessionRecord(r) {
5764
5967
  this._sessionRecord = r;
@@ -5766,12 +5969,9 @@ var SessionController = class extends Loggable {
5766
5969
  reviewQ = new ItemQueue();
5767
5970
  newQ = new ItemQueue();
5768
5971
  failedQ = new ItemQueue();
5972
+ hydratedQ = new ItemQueue();
5769
5973
  _currentCard = null;
5770
- /**
5771
- * Indicates whether the session has been initialized - eg, the
5772
- * queues have been populated.
5773
- */
5774
- _isInitialized = false;
5974
+ hydration_in_progress = false;
5775
5975
  startTime;
5776
5976
  endTime;
5777
5977
  _secondsRemaining;
@@ -5789,12 +5989,14 @@ var SessionController = class extends Loggable {
5789
5989
  /**
5790
5990
  *
5791
5991
  */
5792
- constructor(sources, time) {
5992
+ constructor(sources, time, dataLayer, getViewComponent) {
5793
5993
  super();
5794
5994
  this.sources = sources;
5795
5995
  this.startTime = /* @__PURE__ */ new Date();
5796
5996
  this._secondsRemaining = time;
5797
5997
  this.endTime = new Date(this.startTime.valueOf() + 1e3 * this._secondsRemaining);
5998
+ this.dataLayer = dataLayer;
5999
+ this.getViewComponent = getViewComponent;
5798
6000
  this.log(`Session constructed:
5799
6001
  startTime: ${this.startTime}
5800
6002
  endTime: ${this.endTime}`);
@@ -5845,7 +6047,7 @@ var SessionController = class extends Loggable {
5845
6047
  } catch (e) {
5846
6048
  this.error("Error preparing study session:", e);
5847
6049
  }
5848
- this._isInitialized = true;
6050
+ await this._fillHydratedQueue();
5849
6051
  this._intervalHandle = setInterval(() => {
5850
6052
  this.tick();
5851
6053
  }, 1e3);
@@ -5883,12 +6085,8 @@ var SessionController = class extends Loggable {
5883
6085
  }
5884
6086
  }
5885
6087
  let report = "Review session created with:\n";
5886
- for (let i = 0; i < dueCards.length; i++) {
5887
- const card = dueCards[i];
5888
- this.reviewQ.add(card);
5889
- report += ` ${card.qualifiedID}}
5890
- `;
5891
- }
6088
+ this.reviewQ.addAll(dueCards, (c) => c.cardID);
6089
+ report += dueCards.map((card) => `Card ${card.courseID}::${card.cardID} `).join("\n");
5892
6090
  this.log(report);
5893
6091
  }
5894
6092
  async getNewCards(n = 10) {
@@ -5903,36 +6101,32 @@ var SessionController = class extends Loggable {
5903
6101
  for (let i = 0; i < newContent.length; i++) {
5904
6102
  if (newContent[i].length > 0) {
5905
6103
  const item = newContent[i].splice(0, 1)[0];
5906
- this.log(`Adding new card: ${item.qualifiedID}`);
5907
- this.newQ.add(item);
6104
+ this.log(`Adding new card: ${item.courseID}::${item.cardID}`);
6105
+ this.newQ.add(item, item.cardID);
5908
6106
  n--;
5909
6107
  }
5910
6108
  }
5911
6109
  }
5912
6110
  }
5913
- nextNewCard() {
5914
- const item = this.newQ.dequeue();
5915
- if (this._isInitialized && this.newQ.length < 5) {
5916
- void this.getNewCards();
5917
- }
5918
- return item;
5919
- }
5920
- nextCard(action = "dismiss-success") {
5921
- this.dismissCurrentCard(action);
6111
+ _selectNextItemToHydrate(action = "dismiss-success") {
5922
6112
  const choice = Math.random();
5923
6113
  let newBound = 0.1;
5924
6114
  let reviewBound = 0.75;
5925
6115
  if (this.reviewQ.length === 0 && this.failedQ.length === 0 && this.newQ.length === 0) {
5926
- this._currentCard = null;
5927
- return this._currentCard;
6116
+ return null;
5928
6117
  }
5929
6118
  if (this._secondsRemaining < 2 && this.failedQ.length === 0) {
5930
- this._currentCard = null;
5931
- return this._currentCard;
6119
+ return null;
6120
+ }
6121
+ if (this._secondsRemaining <= 0) {
6122
+ if (this.failedQ.length > 0) {
6123
+ return this.failedQ.peek(0);
6124
+ } else {
6125
+ return null;
6126
+ }
5932
6127
  }
5933
6128
  if (this.newQ.dequeueCount < this.sources.length && this.newQ.length) {
5934
- this._currentCard = this.nextNewCard();
5935
- return this._currentCard;
6129
+ return this.newQ.peek(0);
5936
6130
  }
5937
6131
  const cleanupTime = this.estimateCleanupTime();
5938
6132
  const reviewTime = this.estimateReviewTime();
@@ -5957,16 +6151,27 @@ var SessionController = class extends Loggable {
5957
6151
  newBound = reviewBound;
5958
6152
  }
5959
6153
  if (choice < newBound && this.newQ.length) {
5960
- this._currentCard = this.nextNewCard();
6154
+ return this.newQ.peek(0);
5961
6155
  } else if (choice < reviewBound && this.reviewQ.length) {
5962
- this._currentCard = this.reviewQ.dequeue();
6156
+ return this.reviewQ.peek(0);
5963
6157
  } else if (this.failedQ.length) {
5964
- this._currentCard = this.failedQ.dequeue();
6158
+ return this.failedQ.peek(0);
5965
6159
  } else {
5966
6160
  this.log(`No more cards available for the session!`);
5967
- this._currentCard = null;
6161
+ return null;
6162
+ }
6163
+ }
6164
+ async nextCard(action = "dismiss-success") {
6165
+ this.dismissCurrentCard(action);
6166
+ let card = this.hydratedQ.dequeue();
6167
+ if (!card && this.hasAvailableCards()) {
6168
+ void this._fillHydratedQueue();
6169
+ card = await this.nextHydratedCard();
5968
6170
  }
5969
- return this._currentCard;
6171
+ if (this.hydratedQ.length < 3) {
6172
+ void this._fillHydratedQueue();
6173
+ }
6174
+ return card;
5970
6175
  }
5971
6176
  dismissCurrentCard(action = "dismiss-success") {
5972
6177
  if (this._currentCard) {
@@ -5977,7 +6182,6 @@ var SessionController = class extends Loggable {
5977
6182
  failedItem = {
5978
6183
  cardID: this._currentCard.cardID,
5979
6184
  courseID: this._currentCard.courseID,
5980
- qualifiedID: this._currentCard.qualifiedID,
5981
6185
  contentSourceID: this._currentCard.contentSourceID,
5982
6186
  contentSourceType: this._currentCard.contentSourceType,
5983
6187
  status: "failed-review",
@@ -5987,18 +6191,80 @@ var SessionController = class extends Loggable {
5987
6191
  failedItem = {
5988
6192
  cardID: this._currentCard.cardID,
5989
6193
  courseID: this._currentCard.courseID,
5990
- qualifiedID: this._currentCard.qualifiedID,
5991
6194
  contentSourceID: this._currentCard.contentSourceID,
5992
6195
  contentSourceType: this._currentCard.contentSourceType,
5993
6196
  status: "failed-new"
5994
6197
  };
5995
6198
  }
5996
- this.failedQ.add(failedItem);
6199
+ this.failedQ.add(failedItem, failedItem.cardID);
5997
6200
  } else if (action === "dismiss-error") {
5998
6201
  } else if (action === "dismiss-failed") {
5999
6202
  }
6000
6203
  }
6001
6204
  }
6205
+ hasAvailableCards() {
6206
+ return this.reviewQ.length > 0 || this.newQ.length > 0 || this.failedQ.length > 0;
6207
+ }
6208
+ async nextHydratedCard() {
6209
+ while (this.hydratedQ.length === 0 && this.hasAvailableCards()) {
6210
+ await new Promise((resolve) => setTimeout(resolve, 25));
6211
+ }
6212
+ return this.hydratedQ.dequeue();
6213
+ }
6214
+ async _fillHydratedQueue() {
6215
+ if (this.hydration_in_progress) {
6216
+ return;
6217
+ }
6218
+ const BUFFER_SIZE = 5;
6219
+ this.hydration_in_progress = true;
6220
+ while (this.hydratedQ.length < BUFFER_SIZE) {
6221
+ const nextItem = this._selectNextItemToHydrate();
6222
+ if (!nextItem) {
6223
+ return;
6224
+ }
6225
+ try {
6226
+ const cardData = await this.dataLayer.getCourseDB(nextItem.courseID).getCourseDoc(nextItem.cardID);
6227
+ if (!(0, import_common15.isCourseElo)(cardData.elo)) {
6228
+ cardData.elo = (0, import_common15.toCourseElo)(cardData.elo);
6229
+ }
6230
+ const view = this.getViewComponent(cardData.id_view);
6231
+ const dataDocs = await Promise.all(
6232
+ cardData.id_displayable_data.map(
6233
+ (id) => this.dataLayer.getCourseDB(nextItem.courseID).getCourseDoc(id, {
6234
+ attachments: true,
6235
+ binary: true
6236
+ })
6237
+ )
6238
+ );
6239
+ const data = dataDocs.map(import_common15.displayableDataToViewData).reverse();
6240
+ this.hydratedQ.add(
6241
+ {
6242
+ item: nextItem,
6243
+ view,
6244
+ data
6245
+ },
6246
+ nextItem.cardID
6247
+ );
6248
+ if (this.reviewQ.peek(0) === nextItem) {
6249
+ this.reviewQ.dequeue();
6250
+ } else if (this.newQ.peek(0) === nextItem) {
6251
+ this.newQ.dequeue();
6252
+ } else {
6253
+ this.failedQ.dequeue();
6254
+ }
6255
+ } catch (e) {
6256
+ this.error(`Error hydrating card ${nextItem.cardID}:`, e);
6257
+ if (this.reviewQ.peek(0) === nextItem) {
6258
+ this.reviewQ.dequeue();
6259
+ } else if (this.newQ.peek(0) === nextItem) {
6260
+ this.newQ.dequeue();
6261
+ } else {
6262
+ this.failedQ.dequeue();
6263
+ }
6264
+ }
6265
+ }
6266
+ this.hydration_in_progress = false;
6267
+ }
6002
6268
  };
6003
6269
 
6004
6270
  // src/study/SpacedRepetition.ts
@@ -6024,7 +6290,7 @@ function newQuestionInterval(user, cardHistory) {
6024
6290
  });
6025
6291
  }
6026
6292
  if (currentAttempt.isCorrect) {
6027
- const skill = currentAttempt.performance;
6293
+ const skill = Math.min(1, Math.max(0, currentAttempt.performance));
6028
6294
  logger.debug(`Demontrated skill: ${skill}`);
6029
6295
  const interval = lastInterval * (0.75 + skill);
6030
6296
  cardHistory.lapses = getLapses(cardHistory.records);