@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
@@ -90,9 +90,9 @@ var init_pouchdb_setup = __esm({
90
90
  PouchDB.plugin(PouchDBFind);
91
91
  PouchDB.plugin(PouchDBAuth);
92
92
  PouchDB.defaults({
93
- ajax: {
94
- timeout: 6e4
95
- }
93
+ // ajax: {
94
+ // timeout: 60000,
95
+ // },
96
96
  });
97
97
  pouchdb_setup_default = PouchDB;
98
98
  }
@@ -150,42 +150,58 @@ var init_updateQueue = __esm({
150
150
  async applyUpdates(id) {
151
151
  logger.debug(`Applying updates on doc: ${id}`);
152
152
  if (this.inprogressUpdates[id]) {
153
- await this.readDB.info();
153
+ while (this.inprogressUpdates[id]) {
154
+ await new Promise((resolve) => setTimeout(resolve, Math.random() * 50));
155
+ }
154
156
  return this.applyUpdates(id);
155
157
  } else {
156
158
  if (this.pendingUpdates[id] && this.pendingUpdates[id].length > 0) {
157
159
  this.inprogressUpdates[id] = true;
158
- try {
159
- let doc = await this.readDB.get(id);
160
- logger.debug(`Retrieved doc: ${id}`);
161
- while (this.pendingUpdates[id].length !== 0) {
162
- const update = this.pendingUpdates[id].splice(0, 1)[0];
163
- if (typeof update === "function") {
164
- doc = { ...doc, ...update(doc) };
160
+ const MAX_RETRIES = 5;
161
+ for (let i = 0; i < MAX_RETRIES; i++) {
162
+ try {
163
+ const doc = await this.readDB.get(id);
164
+ logger.debug(`Retrieved doc: ${id}`);
165
+ let updatedDoc = { ...doc };
166
+ const updatesToApply = [...this.pendingUpdates[id]];
167
+ for (const update of updatesToApply) {
168
+ if (typeof update === "function") {
169
+ updatedDoc = { ...updatedDoc, ...update(updatedDoc) };
170
+ } else {
171
+ updatedDoc = {
172
+ ...updatedDoc,
173
+ ...update
174
+ };
175
+ }
176
+ }
177
+ await this.writeDB.put(updatedDoc);
178
+ logger.debug(`Put doc: ${id}`);
179
+ this.pendingUpdates[id].splice(0, updatesToApply.length);
180
+ if (this.pendingUpdates[id].length === 0) {
181
+ this.inprogressUpdates[id] = false;
182
+ delete this.inprogressUpdates[id];
165
183
  } else {
166
- doc = {
167
- ...doc,
168
- ...update
169
- };
184
+ return this.applyUpdates(id);
185
+ }
186
+ return updatedDoc;
187
+ } catch (e) {
188
+ if (e.name === "conflict" && i < MAX_RETRIES - 1) {
189
+ logger.warn(`Conflict on update for doc ${id}, retry #${i + 1}`);
190
+ await new Promise((res) => setTimeout(res, 50 * Math.random()));
191
+ } else if (e.name === "not_found" && i === 0) {
192
+ logger.warn(`Update failed for ${id} - does not exist. Throwing to caller.`);
193
+ throw e;
194
+ } else {
195
+ delete this.inprogressUpdates[id];
196
+ if (this.pendingUpdates[id]) {
197
+ delete this.pendingUpdates[id];
198
+ }
199
+ logger.error(`Error on attemped update (retry ${i}): ${JSON.stringify(e)}`);
200
+ throw e;
170
201
  }
171
202
  }
172
- await this.writeDB.put(doc);
173
- logger.debug(`Put doc: ${id}`);
174
- if (this.pendingUpdates[id].length === 0) {
175
- this.inprogressUpdates[id] = false;
176
- delete this.inprogressUpdates[id];
177
- } else {
178
- return this.applyUpdates(id);
179
- }
180
- return doc;
181
- } catch (e) {
182
- delete this.inprogressUpdates[id];
183
- if (this.pendingUpdates[id]) {
184
- delete this.pendingUpdates[id];
185
- }
186
- logger.error(`Error on attemped update: ${JSON.stringify(e)}`);
187
- throw e;
188
203
  }
204
+ throw new Error(`UpdateQueue failed for doc ${id} after ${MAX_RETRIES} retries.`);
189
205
  } else {
190
206
  throw new Error(`Empty Updates Queue Triggered`);
191
207
  }
@@ -412,7 +428,7 @@ function getCourseDB(courseID) {
412
428
  const dbName = `coursedb-${courseID}`;
413
429
  return new pouchdb_setup_default(
414
430
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
415
- pouchDBincludeCredentialsConfig
431
+ createPouchDBConfig()
416
432
  );
417
433
  }
418
434
  var AlreadyTaggedErr;
@@ -534,6 +550,7 @@ var init_courseLookupDB = __esm({
534
550
  const doc = await _CourseLookup._db.get(courseID);
535
551
  return await _CourseLookup._db.remove(doc);
536
552
  }
553
+ // [ ] rename to allCourses()
537
554
  static async allCourseWare() {
538
555
  const resp = await _CourseLookup._db.allDocs({
539
556
  include_docs: true
@@ -604,13 +621,16 @@ var init_elo = __esm({
604
621
  }
605
622
  async getNewCards(limit = 99) {
606
623
  const activeCards = await this.user.getActiveCards();
607
- return (await this.course.getCardsCenteredAtELO({ limit, elo: "user" }, (c) => {
608
- if (activeCards.some((ac) => c.includes(ac))) {
609
- return false;
610
- } else {
611
- return true;
624
+ return (await this.course.getCardsCenteredAtELO(
625
+ { limit, elo: "user" },
626
+ (c) => {
627
+ if (activeCards.some((ac) => c.cardID === ac.cardID)) {
628
+ return false;
629
+ } else {
630
+ return true;
631
+ }
612
632
  }
613
- })).map((c) => {
633
+ )).map((c) => {
614
634
  return {
615
635
  ...c,
616
636
  status: "new"
@@ -621,12 +641,74 @@ var init_elo = __esm({
621
641
  }
622
642
  });
623
643
 
644
+ // src/core/navigators/hardcodedOrder.ts
645
+ var hardcodedOrder_exports = {};
646
+ __export(hardcodedOrder_exports, {
647
+ default: () => HardcodedOrderNavigator
648
+ });
649
+ var HardcodedOrderNavigator;
650
+ var init_hardcodedOrder = __esm({
651
+ "src/core/navigators/hardcodedOrder.ts"() {
652
+ "use strict";
653
+ init_navigators();
654
+ init_logger();
655
+ HardcodedOrderNavigator = class extends ContentNavigator {
656
+ orderedCardIds = [];
657
+ user;
658
+ course;
659
+ constructor(user, course, strategyData) {
660
+ super();
661
+ this.user = user;
662
+ this.course = course;
663
+ if (strategyData.serializedData) {
664
+ try {
665
+ this.orderedCardIds = JSON.parse(strategyData.serializedData);
666
+ } catch (e) {
667
+ logger.error("Failed to parse serializedData for HardcodedOrderNavigator", e);
668
+ }
669
+ }
670
+ }
671
+ async getPendingReviews() {
672
+ const reviews = await this.user.getPendingReviews(this.course.getCourseID());
673
+ return reviews.map((r) => {
674
+ return {
675
+ ...r,
676
+ contentSourceType: "course",
677
+ contentSourceID: this.course.getCourseID(),
678
+ cardID: r.cardId,
679
+ courseID: r.courseId,
680
+ reviewID: r._id,
681
+ status: "review"
682
+ };
683
+ });
684
+ }
685
+ async getNewCards(limit = 99) {
686
+ const activeCardIds = (await this.user.getActiveCards()).map((c) => c.cardID);
687
+ const newCardIds = this.orderedCardIds.filter(
688
+ (cardId) => !activeCardIds.includes(cardId)
689
+ );
690
+ const cardsToReturn = newCardIds.slice(0, limit);
691
+ return cardsToReturn.map((cardId) => {
692
+ return {
693
+ cardID: cardId,
694
+ courseID: this.course.getCourseID(),
695
+ contentSourceType: "course",
696
+ contentSourceID: this.course.getCourseID(),
697
+ status: "new"
698
+ };
699
+ });
700
+ }
701
+ };
702
+ }
703
+ });
704
+
624
705
  // import("./**/*") in src/core/navigators/index.ts
625
706
  var globImport;
626
707
  var init_ = __esm({
627
708
  'import("./**/*") in src/core/navigators/index.ts'() {
628
709
  globImport = __glob({
629
710
  "./elo.ts": () => Promise.resolve().then(() => (init_elo(), elo_exports)),
711
+ "./hardcodedOrder.ts": () => Promise.resolve().then(() => (init_hardcodedOrder(), hardcodedOrder_exports)),
630
712
  "./index.ts": () => Promise.resolve().then(() => (init_navigators(), navigators_exports))
631
713
  });
632
714
  }
@@ -646,6 +728,7 @@ var init_navigators = __esm({
646
728
  init_();
647
729
  Navigators = /* @__PURE__ */ ((Navigators2) => {
648
730
  Navigators2["ELO"] = "elo";
731
+ Navigators2["HARDCODED"] = "hardcodedOrder";
649
732
  return Navigators2;
650
733
  })(Navigators || {});
651
734
  ContentNavigator = class {
@@ -658,7 +741,7 @@ var init_navigators = __esm({
658
741
  static async create(user, course, strategyData) {
659
742
  const implementingClass = strategyData.implementingClass;
660
743
  let NavigatorImpl;
661
- const variations = ["", ".js", ".ts"];
744
+ const variations = [".ts", ".js", ""];
662
745
  for (const ext of variations) {
663
746
  try {
664
747
  const module = await globImport(`./${implementingClass}${ext}`);
@@ -962,6 +1045,23 @@ var init_courseDB = __esm({
962
1045
  if (!doc.docType || !(doc.docType === "CARD" /* CARD */)) {
963
1046
  throw new Error(`failed to remove ${id} from course ${this.id}. id does not point to a card`);
964
1047
  }
1048
+ try {
1049
+ const appliedTags = await this.getAppliedTags(id);
1050
+ const results = await Promise.allSettled(
1051
+ appliedTags.rows.map(async (tagRow) => {
1052
+ const tagId = tagRow.id;
1053
+ await this.removeTagFromCard(id, tagId);
1054
+ })
1055
+ );
1056
+ results.forEach((result, index) => {
1057
+ if (result.status === "rejected") {
1058
+ const tagId = appliedTags.rows[index].id;
1059
+ logger.error(`Failed to remove card ${id} from tag ${tagId}: ${result.reason}`);
1060
+ }
1061
+ });
1062
+ } catch (error) {
1063
+ logger.error(`Error removing card ${id} from tags: ${error}`);
1064
+ }
965
1065
  return this.db.remove(doc);
966
1066
  }
967
1067
  async getCardDisplayableDataIDs(id) {
@@ -1009,7 +1109,13 @@ var init_courseDB = __esm({
1009
1109
  } else {
1010
1110
  return s;
1011
1111
  }
1012
- }).map((c) => `${this.id}-${c.id}-${c.key}`);
1112
+ }).map((c) => {
1113
+ return {
1114
+ courseID: this.id,
1115
+ cardID: c.id,
1116
+ elo: c.key
1117
+ };
1118
+ });
1013
1119
  const str = `below:
1014
1120
  ${below.rows.map((r) => ` ${r.id}-${r.key}
1015
1121
  `)}
@@ -1064,7 +1170,13 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
1064
1170
  }
1065
1171
  }
1066
1172
  async addTagToCard(cardId, tagId, updateELO) {
1067
- return await addTagToCard(this.id, cardId, tagId, (await this._getCurrentUser()).getUsername(), updateELO);
1173
+ return await addTagToCard(
1174
+ this.id,
1175
+ cardId,
1176
+ tagId,
1177
+ (await this._getCurrentUser()).getUsername(),
1178
+ updateELO
1179
+ );
1068
1180
  }
1069
1181
  async removeTagFromCard(cardId, tagId) {
1070
1182
  return await removeTagFromCard(this.id, cardId, tagId);
@@ -1133,23 +1245,9 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
1133
1245
  ////////////////////////////////////
1134
1246
  getNavigationStrategy(id) {
1135
1247
  logger.debug(`[courseDB] Getting navigation strategy: ${id}`);
1136
- const strategy = {
1137
- id: "ELO",
1138
- docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
1139
- name: "ELO",
1140
- description: "ELO-based navigation strategy for ordering content by difficulty",
1141
- implementingClass: "elo" /* ELO */,
1142
- course: this.id,
1143
- serializedData: ""
1144
- // serde is a noop for ELO navigator.
1145
- };
1146
- return Promise.resolve(strategy);
1147
- }
1148
- getAllNavigationStrategies() {
1149
- logger.debug("[courseDB] Returning hard-coded navigation strategies");
1150
- const strategies = [
1151
- {
1152
- id: "ELO",
1248
+ if (id == "") {
1249
+ const strategy = {
1250
+ _id: "NAVIGATION_STRATEGY-ELO",
1153
1251
  docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
1154
1252
  name: "ELO",
1155
1253
  description: "ELO-based navigation strategy for ordering content by difficulty",
@@ -1157,14 +1255,25 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
1157
1255
  course: this.id,
1158
1256
  serializedData: ""
1159
1257
  // serde is a noop for ELO navigator.
1160
- }
1161
- ];
1162
- return Promise.resolve(strategies);
1258
+ };
1259
+ return Promise.resolve(strategy);
1260
+ } else {
1261
+ return this.db.get(id);
1262
+ }
1163
1263
  }
1164
- addNavigationStrategy(data) {
1165
- logger.debug(`[courseDB] Adding navigation strategy: ${data.id}`);
1166
- logger.debug(JSON.stringify(data));
1167
- return Promise.resolve();
1264
+ async getAllNavigationStrategies() {
1265
+ const prefix = DocTypePrefixes["NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */];
1266
+ const result = await this.db.allDocs({
1267
+ startkey: prefix,
1268
+ endkey: `${prefix}\uFFF0`,
1269
+ include_docs: true
1270
+ });
1271
+ return result.rows.map((row) => row.doc);
1272
+ }
1273
+ async addNavigationStrategy(data) {
1274
+ logger.debug(`[courseDB] Adding navigation strategy: ${data._id}`);
1275
+ return this.db.put(data).then(() => {
1276
+ });
1168
1277
  }
1169
1278
  updateNavigationStrategy(id, data) {
1170
1279
  logger.debug(`[courseDB] Updating navigation strategy: ${id}`);
@@ -1172,9 +1281,32 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
1172
1281
  return Promise.resolve();
1173
1282
  }
1174
1283
  async surfaceNavigationStrategy() {
1284
+ try {
1285
+ const config = await this.getCourseConfig();
1286
+ if (config.defaultNavigationStrategyId) {
1287
+ try {
1288
+ const strategy = await this.getNavigationStrategy(config.defaultNavigationStrategyId);
1289
+ if (strategy) {
1290
+ logger.debug(`Surfacing strategy ${strategy.name} from course config`);
1291
+ return strategy;
1292
+ }
1293
+ } catch (e) {
1294
+ logger.warn(
1295
+ // @ts-expect-error tmp: defaultNavigationStrategyId property does not yet exist
1296
+ `Failed to load strategy '${config.defaultNavigationStrategyId}' specified in course config. Falling back to ELO.`,
1297
+ e
1298
+ );
1299
+ }
1300
+ }
1301
+ } catch (e) {
1302
+ logger.warn(
1303
+ "Could not retrieve course config to determine navigation strategy. Falling back to ELO.",
1304
+ e
1305
+ );
1306
+ }
1175
1307
  logger.warn(`Returning hard-coded default ELO navigator`);
1176
1308
  const ret = {
1177
- id: "ELO",
1309
+ _id: "NAVIGATION_STRATEGY-ELO",
1178
1310
  docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
1179
1311
  name: "ELO",
1180
1312
  description: "ELO-based navigation strategy",
@@ -1257,17 +1389,93 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
1257
1389
  selectedCards.push(card);
1258
1390
  }
1259
1391
  return selectedCards.map((c) => {
1260
- const split = c.split("-");
1261
1392
  return {
1262
1393
  courseID: this.id,
1263
- cardID: split[1],
1264
- qualifiedID: `${split[0]}-${split[1]}`,
1394
+ cardID: c.cardID,
1265
1395
  contentSourceType: "course",
1266
1396
  contentSourceID: this.id,
1397
+ elo: c.elo,
1267
1398
  status: "new"
1268
1399
  };
1269
1400
  });
1270
1401
  }
1402
+ // Admin search methods
1403
+ async searchCards(query) {
1404
+ logger.log(`[CourseDB ${this.id}] Searching for: "${query}"`);
1405
+ let displayableData;
1406
+ try {
1407
+ displayableData = await this.db.find({
1408
+ selector: {
1409
+ docType: "DISPLAYABLE_DATA",
1410
+ "data.0.data": { $regex: `.*${query}.*` }
1411
+ }
1412
+ });
1413
+ logger.log(`[CourseDB ${this.id}] Regex search on data[0].data successful`);
1414
+ } catch (regexError) {
1415
+ logger.log(
1416
+ `[CourseDB ${this.id}] Regex search failed, falling back to manual search:`,
1417
+ regexError
1418
+ );
1419
+ const allDisplayable = await this.db.find({
1420
+ selector: {
1421
+ docType: "DISPLAYABLE_DATA"
1422
+ }
1423
+ });
1424
+ logger.log(
1425
+ `[CourseDB ${this.id}] Retrieved ${allDisplayable.docs.length} documents for manual filtering`
1426
+ );
1427
+ displayableData = {
1428
+ docs: allDisplayable.docs.filter((doc) => {
1429
+ const docString = JSON.stringify(doc).toLowerCase();
1430
+ const match = docString.includes(query.toLowerCase());
1431
+ if (match) {
1432
+ logger.log(`[CourseDB ${this.id}] Manual match found in document: ${doc._id}`);
1433
+ }
1434
+ return match;
1435
+ })
1436
+ };
1437
+ }
1438
+ logger.log(
1439
+ `[CourseDB ${this.id}] Found ${displayableData.docs.length} displayable data documents`
1440
+ );
1441
+ if (displayableData.docs.length === 0) {
1442
+ const allDisplayableData = await this.db.find({
1443
+ selector: {
1444
+ docType: "DISPLAYABLE_DATA"
1445
+ },
1446
+ limit: 5
1447
+ // Just sample a few
1448
+ });
1449
+ logger.log(
1450
+ `[CourseDB ${this.id}] Sample displayable data:`,
1451
+ allDisplayableData.docs.map((d) => ({
1452
+ id: d._id,
1453
+ docType: d.docType,
1454
+ dataStructure: d.data ? Object.keys(d.data) : "no data field",
1455
+ dataContent: d.data,
1456
+ fullDoc: d
1457
+ }))
1458
+ );
1459
+ }
1460
+ const allResults = [];
1461
+ for (const dd of displayableData.docs) {
1462
+ const cards = await this.db.find({
1463
+ selector: {
1464
+ docType: "CARD",
1465
+ id_displayable_data: { $in: [dd._id] }
1466
+ }
1467
+ });
1468
+ logger.log(
1469
+ `[CourseDB ${this.id}] Displayable data ${dd._id} linked to ${cards.docs.length} cards`
1470
+ );
1471
+ allResults.push(...cards.docs);
1472
+ }
1473
+ logger.log(`[CourseDB ${this.id}] Total cards found: ${allResults.length}`);
1474
+ return allResults;
1475
+ }
1476
+ async find(request) {
1477
+ return this.db.find(request);
1478
+ }
1271
1479
  };
1272
1480
  }
1273
1481
  });
@@ -1279,7 +1487,7 @@ function getClassroomDB(classID, version) {
1279
1487
  logger.info(`Retrieving classroom db: ${dbName}`);
1280
1488
  return new pouchdb_setup_default(
1281
1489
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
1282
- pouchDBincludeCredentialsConfig
1490
+ createPouchDBConfig()
1283
1491
  );
1284
1492
  }
1285
1493
  async function getClassroomConfig(classID) {
@@ -1344,7 +1552,7 @@ var init_classroomDB2 = __esm({
1344
1552
  const dbName = `classdb-student-${this._id}`;
1345
1553
  this._db = new pouchdb_setup_default(
1346
1554
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
1347
- pouchDBincludeCredentialsConfig
1555
+ createPouchDBConfig()
1348
1556
  );
1349
1557
  try {
1350
1558
  const cfg = await this._db.get(CLASSROOM_CONFIG);
@@ -1413,9 +1621,11 @@ var init_classroomDB2 = __esm({
1413
1621
  ret.push(await getCourseDB2(content.courseID).get(content.cardID));
1414
1622
  }
1415
1623
  }
1416
- logger.info(`New Cards from classroom ${this._cfg.name}: ${ret.map((c) => c.qualifiedID)}`);
1624
+ logger.info(
1625
+ `New Cards from classroom ${this._cfg.name}: ${ret.map((c) => `${c.courseID}-${c.cardID}`)}`
1626
+ );
1417
1627
  return ret.filter((c) => {
1418
- if (activeCards.some((ac) => c.qualifiedID.includes(ac))) {
1628
+ if (activeCards.some((ac) => c.cardID === ac.cardID)) {
1419
1629
  return false;
1420
1630
  } else {
1421
1631
  return true;
@@ -1434,11 +1644,11 @@ var init_classroomDB2 = __esm({
1434
1644
  const stuDbName = `classdb-student-${this._id}`;
1435
1645
  this._db = new pouchdb_setup_default(
1436
1646
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
1437
- pouchDBincludeCredentialsConfig
1647
+ createPouchDBConfig()
1438
1648
  );
1439
1649
  this._stuDb = new pouchdb_setup_default(
1440
1650
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + stuDbName,
1441
- pouchDBincludeCredentialsConfig
1651
+ createPouchDBConfig()
1442
1652
  );
1443
1653
  try {
1444
1654
  return this._db.get(CLASSROOM_CONFIG).then((cfg) => {
@@ -2023,6 +2233,9 @@ Currently logged-in as ${this._username}.`
2023
2233
  await this.init();
2024
2234
  return ret;
2025
2235
  }
2236
+ async get(id) {
2237
+ return this.localDB.get(id);
2238
+ }
2026
2239
  update(id, update) {
2027
2240
  return this.updateQueue.update(id, update);
2028
2241
  }
@@ -2069,7 +2282,12 @@ Currently logged-in as ${this._username}.`
2069
2282
  endkey: keys.endkey,
2070
2283
  include_docs: true
2071
2284
  });
2072
- return reviews.rows.map((r) => `${r.doc.courseId}-${r.doc.cardId}`);
2285
+ return reviews.rows.map((r) => {
2286
+ return {
2287
+ courseID: r.doc.courseId,
2288
+ cardID: r.doc.cardId
2289
+ };
2290
+ });
2073
2291
  }
2074
2292
  async getActivityRecords() {
2075
2293
  try {
@@ -2349,8 +2567,18 @@ Currently logged-in as ${this._username}.`
2349
2567
  }
2350
2568
  this.setDBandQ();
2351
2569
  this.syncStrategy.startSync(this.localDB, this.remoteDB);
2352
- void this.applyDesignDocs();
2353
- void this.deduplicateReviews();
2570
+ this.applyDesignDocs().catch((error) => {
2571
+ log3(`Error in applyDesignDocs background task: ${error}`);
2572
+ if (error && typeof error === "object") {
2573
+ log3(`Full error details in applyDesignDocs: ${JSON.stringify(error)}`);
2574
+ }
2575
+ });
2576
+ this.deduplicateReviews().catch((error) => {
2577
+ log3(`Error in deduplicateReviews background task: ${error}`);
2578
+ if (error && typeof error === "object") {
2579
+ log3(`Full error details in background task: ${JSON.stringify(error)}`);
2580
+ }
2581
+ });
2354
2582
  _BaseUser._initialized = true;
2355
2583
  }
2356
2584
  static designDocs = [
@@ -2368,10 +2596,15 @@ Currently logged-in as ${this._username}.`
2368
2596
  }
2369
2597
  ];
2370
2598
  async applyDesignDocs() {
2599
+ log3(`Starting applyDesignDocs for user: ${this._username}`);
2600
+ log3(`Remote DB name: ${this.remoteDB.name || "unknown"}`);
2371
2601
  if (this._username === "admin") {
2602
+ log3("Skipping design docs for admin user");
2372
2603
  return;
2373
2604
  }
2605
+ log3(`Applying ${_BaseUser.designDocs.length} design docs`);
2374
2606
  for (const doc of _BaseUser.designDocs) {
2607
+ log3(`Applying design doc: ${doc._id}`);
2375
2608
  try {
2376
2609
  try {
2377
2610
  const existingDoc = await this.remoteDB.get(doc._id);
@@ -2448,17 +2681,21 @@ Currently logged-in as ${this._username}.`
2448
2681
  } catch (e) {
2449
2682
  const reason = e;
2450
2683
  if (reason.status === 404) {
2451
- const initCardHistory = {
2452
- _id: cardHistoryID,
2453
- cardID: record.cardID,
2454
- courseID: record.courseID,
2455
- records: [record],
2456
- lapses: 0,
2457
- streak: 0,
2458
- bestInterval: 0
2459
- };
2460
- const putResult = await this.writeDB.put(initCardHistory);
2461
- return { ...initCardHistory, _rev: putResult.rev };
2684
+ try {
2685
+ const initCardHistory = {
2686
+ _id: cardHistoryID,
2687
+ cardID: record.cardID,
2688
+ courseID: record.courseID,
2689
+ records: [record],
2690
+ lapses: 0,
2691
+ streak: 0,
2692
+ bestInterval: 0
2693
+ };
2694
+ const putResult = await this.writeDB.put(initCardHistory);
2695
+ return { ...initCardHistory, _rev: putResult.rev };
2696
+ } catch (creationError) {
2697
+ throw new Error(`Failed to create CardHistory for ${cardHistoryID}. Reason: ${creationError}`);
2698
+ }
2462
2699
  } else {
2463
2700
  throw new Error(`putCardRecord failed because of:
2464
2701
  name:${reason.name}
@@ -2470,8 +2707,13 @@ Currently logged-in as ${this._username}.`
2470
2707
  async deduplicateReviews() {
2471
2708
  try {
2472
2709
  log3("Starting deduplication of scheduled reviews...");
2710
+ log3(`Remote DB name: ${this.remoteDB.name || "unknown"}`);
2711
+ log3(`Write DB name: ${this.writeDB.name || "unknown"}`);
2473
2712
  const reviewsMap = {};
2474
2713
  const duplicateDocIds = [];
2714
+ log3(
2715
+ `Attempting to query remoteDB for reviewCards/reviewCards. Database: ${this.remoteDB.name || "unknown"}`
2716
+ );
2475
2717
  const scheduledReviews = await this.remoteDB.query("reviewCards/reviewCards");
2476
2718
  log3(`Found ${scheduledReviews.rows.length} scheduled reviews to process`);
2477
2719
  scheduledReviews.rows.forEach((r) => {
@@ -2506,6 +2748,17 @@ Currently logged-in as ${this._username}.`
2506
2748
  }
2507
2749
  } catch (error) {
2508
2750
  log3(`Error during review deduplication: ${error}`);
2751
+ if (error && typeof error === "object" && "status" in error && error.status === 404) {
2752
+ log3(
2753
+ `Database not found (404) during review deduplication. Database: ${this.remoteDB.name || "unknown"}`
2754
+ );
2755
+ log3(
2756
+ `This might indicate the user database doesn't exist or the reviewCards view isn't available`
2757
+ );
2758
+ }
2759
+ if (error && typeof error === "object") {
2760
+ log3(`Full error details: ${JSON.stringify(error)}`);
2761
+ }
2509
2762
  }
2510
2763
  }
2511
2764
  /**
@@ -2698,7 +2951,7 @@ var init_adminDB2 = __esm({
2698
2951
  constructor() {
2699
2952
  this.usersDB = new pouchdb_setup_default(
2700
2953
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + "_users",
2701
- pouchDBincludeCredentialsConfig
2954
+ createPouchDBConfig()
2702
2955
  );
2703
2956
  }
2704
2957
  async getUsers() {
@@ -2760,9 +3013,10 @@ import fetch from "cross-fetch";
2760
3013
  async function getCurrentSession() {
2761
3014
  try {
2762
3015
  if (ENV.COUCHDB_SERVER_URL === NOT_SET || ENV.COUCHDB_SERVER_PROTOCOL === NOT_SET) {
2763
- throw new Error("CouchDB server configuration not properly initialized");
3016
+ throw new Error(`CouchDB server configuration not properly initialized. Protocol: "${ENV.COUCHDB_SERVER_PROTOCOL}", URL: "${ENV.COUCHDB_SERVER_URL}"`);
2764
3017
  }
2765
- const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${ENV.COUCHDB_SERVER_URL}_session`;
3018
+ const baseUrl = ENV.COUCHDB_SERVER_URL.endsWith("/") ? ENV.COUCHDB_SERVER_URL.slice(0, -1) : ENV.COUCHDB_SERVER_URL;
3019
+ const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${baseUrl}/_session`;
2766
3020
  logger.debug(`Attempting session check at: ${url}`);
2767
3021
  const response = await fetch(url, {
2768
3022
  method: "GET",
@@ -2774,8 +3028,10 @@ async function getCurrentSession() {
2774
3028
  const resp = await response.json();
2775
3029
  return resp;
2776
3030
  } catch (error) {
2777
- logger.error(`Session check error: ${error}`);
2778
- throw new Error(`Session check failed: ${error}`);
3031
+ const baseUrl = ENV.COUCHDB_SERVER_URL.endsWith("/") ? ENV.COUCHDB_SERVER_URL.slice(0, -1) : ENV.COUCHDB_SERVER_URL;
3032
+ const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${baseUrl}/_session`;
3033
+ logger.error(`Session check error attempting to connect to: ${url} - ${error}`);
3034
+ throw new Error(`Session check failed connecting to ${url}: ${error}`);
2779
3035
  }
2780
3036
  }
2781
3037
  async function getLoggedInUsername() {
@@ -2964,7 +3220,7 @@ var init_CouchDBSyncStrategy = __esm({
2964
3220
  log4(`Fetching user database: ${dbName} (${username})`);
2965
3221
  const ret = new pouchdb_setup_default(
2966
3222
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
2967
- pouchDBincludeCredentialsConfig
3223
+ createPouchDBConfig()
2968
3224
  );
2969
3225
  if (guestAccount) {
2970
3226
  updateGuestAccountExpirationDate(ret);
@@ -2988,16 +3244,35 @@ function hexEncode2(str) {
2988
3244
  }
2989
3245
  return returnStr;
2990
3246
  }
3247
+ function createPouchDBConfig() {
3248
+ const hasExplicitCredentials = ENV.COUCHDB_USERNAME && ENV.COUCHDB_PASSWORD;
3249
+ const isNodeEnvironment = typeof window === "undefined";
3250
+ if (hasExplicitCredentials && isNodeEnvironment) {
3251
+ return {
3252
+ fetch(url, opts = {}) {
3253
+ const basicAuth = btoa(`${ENV.COUCHDB_USERNAME}:${ENV.COUCHDB_PASSWORD}`);
3254
+ const headers = new Headers(opts.headers || {});
3255
+ headers.set("Authorization", `Basic ${basicAuth}`);
3256
+ const newOpts = {
3257
+ ...opts,
3258
+ headers
3259
+ };
3260
+ return pouchdb_setup_default.fetch(url, newOpts);
3261
+ }
3262
+ };
3263
+ }
3264
+ return pouchDBincludeCredentialsConfig;
3265
+ }
2991
3266
  function getCouchDB(dbName) {
2992
3267
  return new pouchdb_setup_default(
2993
3268
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
2994
- pouchDBincludeCredentialsConfig
3269
+ createPouchDBConfig()
2995
3270
  );
2996
3271
  }
2997
3272
  function getCourseDB2(courseID) {
2998
3273
  return new pouchdb_setup_default(
2999
3274
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + "coursedb-" + courseID,
3000
- pouchDBincludeCredentialsConfig
3275
+ createPouchDBConfig()
3001
3276
  );
3002
3277
  }
3003
3278
  async function getLatestVersion() {
@@ -3082,7 +3357,7 @@ function getCouchUserDB(username) {
3082
3357
  log(`Fetching user database: ${dbName} (${username})`);
3083
3358
  const ret = new pouchdb_setup_default(
3084
3359
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
3085
- pouchDBincludeCredentialsConfig
3360
+ createPouchDBConfig()
3086
3361
  );
3087
3362
  if (guestAccount) {
3088
3363
  updateGuestAccountExpirationDate2(ret);
@@ -3161,6 +3436,7 @@ export {
3161
3436
  TeacherClassroomDB,
3162
3437
  addNote55,
3163
3438
  addTagToCard,
3439
+ createPouchDBConfig,
3164
3440
  createTag,
3165
3441
  deleteTag,
3166
3442
  filterAllDocsByPrefix,
@@ -3187,7 +3463,6 @@ export {
3187
3463
  hexEncode2 as hexEncode,
3188
3464
  isReview,
3189
3465
  localUserDB,
3190
- pouchDBincludeCredentialsConfig,
3191
3466
  removeTagFromCard,
3192
3467
  scheduleCardReview,
3193
3468
  updateCardElo2 as updateCardElo,