@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
@@ -165,9 +165,9 @@ var init_pouchdb_setup = __esm({
165
165
  PouchDB.plugin(PouchDBFind);
166
166
  PouchDB.plugin(PouchDBAuth);
167
167
  PouchDB.defaults({
168
- ajax: {
169
- timeout: 6e4
170
- }
168
+ // ajax: {
169
+ // timeout: 60000,
170
+ // },
171
171
  });
172
172
  pouchdb_setup_default = PouchDB;
173
173
  }
@@ -320,42 +320,58 @@ var init_updateQueue = __esm({
320
320
  async applyUpdates(id) {
321
321
  logger.debug(`Applying updates on doc: ${id}`);
322
322
  if (this.inprogressUpdates[id]) {
323
- await this.readDB.info();
323
+ while (this.inprogressUpdates[id]) {
324
+ await new Promise((resolve) => setTimeout(resolve, Math.random() * 50));
325
+ }
324
326
  return this.applyUpdates(id);
325
327
  } else {
326
328
  if (this.pendingUpdates[id] && this.pendingUpdates[id].length > 0) {
327
329
  this.inprogressUpdates[id] = true;
328
- try {
329
- let doc = await this.readDB.get(id);
330
- logger.debug(`Retrieved doc: ${id}`);
331
- while (this.pendingUpdates[id].length !== 0) {
332
- const update = this.pendingUpdates[id].splice(0, 1)[0];
333
- if (typeof update === "function") {
334
- doc = { ...doc, ...update(doc) };
330
+ const MAX_RETRIES = 5;
331
+ for (let i = 0; i < MAX_RETRIES; i++) {
332
+ try {
333
+ const doc = await this.readDB.get(id);
334
+ logger.debug(`Retrieved doc: ${id}`);
335
+ let updatedDoc = { ...doc };
336
+ const updatesToApply = [...this.pendingUpdates[id]];
337
+ for (const update of updatesToApply) {
338
+ if (typeof update === "function") {
339
+ updatedDoc = { ...updatedDoc, ...update(updatedDoc) };
340
+ } else {
341
+ updatedDoc = {
342
+ ...updatedDoc,
343
+ ...update
344
+ };
345
+ }
346
+ }
347
+ await this.writeDB.put(updatedDoc);
348
+ logger.debug(`Put doc: ${id}`);
349
+ this.pendingUpdates[id].splice(0, updatesToApply.length);
350
+ if (this.pendingUpdates[id].length === 0) {
351
+ this.inprogressUpdates[id] = false;
352
+ delete this.inprogressUpdates[id];
335
353
  } else {
336
- doc = {
337
- ...doc,
338
- ...update
339
- };
354
+ return this.applyUpdates(id);
355
+ }
356
+ return updatedDoc;
357
+ } catch (e) {
358
+ if (e.name === "conflict" && i < MAX_RETRIES - 1) {
359
+ logger.warn(`Conflict on update for doc ${id}, retry #${i + 1}`);
360
+ await new Promise((res) => setTimeout(res, 50 * Math.random()));
361
+ } else if (e.name === "not_found" && i === 0) {
362
+ logger.warn(`Update failed for ${id} - does not exist. Throwing to caller.`);
363
+ throw e;
364
+ } else {
365
+ delete this.inprogressUpdates[id];
366
+ if (this.pendingUpdates[id]) {
367
+ delete this.pendingUpdates[id];
368
+ }
369
+ logger.error(`Error on attemped update (retry ${i}): ${JSON.stringify(e)}`);
370
+ throw e;
340
371
  }
341
372
  }
342
- await this.writeDB.put(doc);
343
- logger.debug(`Put doc: ${id}`);
344
- if (this.pendingUpdates[id].length === 0) {
345
- this.inprogressUpdates[id] = false;
346
- delete this.inprogressUpdates[id];
347
- } else {
348
- return this.applyUpdates(id);
349
- }
350
- return doc;
351
- } catch (e) {
352
- delete this.inprogressUpdates[id];
353
- if (this.pendingUpdates[id]) {
354
- delete this.pendingUpdates[id];
355
- }
356
- logger.error(`Error on attemped update: ${JSON.stringify(e)}`);
357
- throw e;
358
373
  }
374
+ throw new Error(`UpdateQueue failed for doc ${id} after ${MAX_RETRIES} retries.`);
359
375
  } else {
360
376
  throw new Error(`Empty Updates Queue Triggered`);
361
377
  }
@@ -610,7 +626,7 @@ function getCourseDB(courseID) {
610
626
  const dbName = `coursedb-${courseID}`;
611
627
  return new pouchdb_setup_default(
612
628
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
613
- pouchDBincludeCredentialsConfig
629
+ createPouchDBConfig()
614
630
  );
615
631
  }
616
632
  var AlreadyTaggedErr;
@@ -690,13 +706,16 @@ var init_elo = __esm({
690
706
  }
691
707
  async getNewCards(limit = 99) {
692
708
  const activeCards = await this.user.getActiveCards();
693
- return (await this.course.getCardsCenteredAtELO({ limit, elo: "user" }, (c) => {
694
- if (activeCards.some((ac) => c.includes(ac))) {
695
- return false;
696
- } else {
697
- return true;
709
+ return (await this.course.getCardsCenteredAtELO(
710
+ { limit, elo: "user" },
711
+ (c) => {
712
+ if (activeCards.some((ac) => c.cardID === ac.cardID)) {
713
+ return false;
714
+ } else {
715
+ return true;
716
+ }
698
717
  }
699
- })).map((c) => {
718
+ )).map((c) => {
700
719
  return {
701
720
  ...c,
702
721
  status: "new"
@@ -707,12 +726,74 @@ var init_elo = __esm({
707
726
  }
708
727
  });
709
728
 
729
+ // src/core/navigators/hardcodedOrder.ts
730
+ var hardcodedOrder_exports = {};
731
+ __export(hardcodedOrder_exports, {
732
+ default: () => HardcodedOrderNavigator
733
+ });
734
+ var HardcodedOrderNavigator;
735
+ var init_hardcodedOrder = __esm({
736
+ "src/core/navigators/hardcodedOrder.ts"() {
737
+ "use strict";
738
+ init_navigators();
739
+ init_logger();
740
+ HardcodedOrderNavigator = class extends ContentNavigator {
741
+ orderedCardIds = [];
742
+ user;
743
+ course;
744
+ constructor(user, course, strategyData) {
745
+ super();
746
+ this.user = user;
747
+ this.course = course;
748
+ if (strategyData.serializedData) {
749
+ try {
750
+ this.orderedCardIds = JSON.parse(strategyData.serializedData);
751
+ } catch (e) {
752
+ logger.error("Failed to parse serializedData for HardcodedOrderNavigator", e);
753
+ }
754
+ }
755
+ }
756
+ async getPendingReviews() {
757
+ const reviews = await this.user.getPendingReviews(this.course.getCourseID());
758
+ return reviews.map((r) => {
759
+ return {
760
+ ...r,
761
+ contentSourceType: "course",
762
+ contentSourceID: this.course.getCourseID(),
763
+ cardID: r.cardId,
764
+ courseID: r.courseId,
765
+ reviewID: r._id,
766
+ status: "review"
767
+ };
768
+ });
769
+ }
770
+ async getNewCards(limit = 99) {
771
+ const activeCardIds = (await this.user.getActiveCards()).map((c) => c.cardID);
772
+ const newCardIds = this.orderedCardIds.filter(
773
+ (cardId) => !activeCardIds.includes(cardId)
774
+ );
775
+ const cardsToReturn = newCardIds.slice(0, limit);
776
+ return cardsToReturn.map((cardId) => {
777
+ return {
778
+ cardID: cardId,
779
+ courseID: this.course.getCourseID(),
780
+ contentSourceType: "course",
781
+ contentSourceID: this.course.getCourseID(),
782
+ status: "new"
783
+ };
784
+ });
785
+ }
786
+ };
787
+ }
788
+ });
789
+
710
790
  // import("./**/*") in src/core/navigators/index.ts
711
791
  var globImport;
712
792
  var init_ = __esm({
713
793
  'import("./**/*") in src/core/navigators/index.ts'() {
714
794
  globImport = __glob({
715
795
  "./elo.ts": () => Promise.resolve().then(() => (init_elo(), elo_exports)),
796
+ "./hardcodedOrder.ts": () => Promise.resolve().then(() => (init_hardcodedOrder(), hardcodedOrder_exports)),
716
797
  "./index.ts": () => Promise.resolve().then(() => (init_navigators(), navigators_exports))
717
798
  });
718
799
  }
@@ -732,6 +813,7 @@ var init_navigators = __esm({
732
813
  init_();
733
814
  Navigators = /* @__PURE__ */ ((Navigators2) => {
734
815
  Navigators2["ELO"] = "elo";
816
+ Navigators2["HARDCODED"] = "hardcodedOrder";
735
817
  return Navigators2;
736
818
  })(Navigators || {});
737
819
  ContentNavigator = class {
@@ -744,7 +826,7 @@ var init_navigators = __esm({
744
826
  static async create(user, course, strategyData) {
745
827
  const implementingClass = strategyData.implementingClass;
746
828
  let NavigatorImpl;
747
- const variations = ["", ".js", ".ts"];
829
+ const variations = [".ts", ".js", ""];
748
830
  for (const ext of variations) {
749
831
  try {
750
832
  const module = await globImport(`./${implementingClass}${ext}`);
@@ -961,6 +1043,23 @@ var init_courseDB = __esm({
961
1043
  if (!doc.docType || !(doc.docType === "CARD" /* CARD */)) {
962
1044
  throw new Error(`failed to remove ${id} from course ${this.id}. id does not point to a card`);
963
1045
  }
1046
+ try {
1047
+ const appliedTags = await this.getAppliedTags(id);
1048
+ const results = await Promise.allSettled(
1049
+ appliedTags.rows.map(async (tagRow) => {
1050
+ const tagId = tagRow.id;
1051
+ await this.removeTagFromCard(id, tagId);
1052
+ })
1053
+ );
1054
+ results.forEach((result, index) => {
1055
+ if (result.status === "rejected") {
1056
+ const tagId = appliedTags.rows[index].id;
1057
+ logger.error(`Failed to remove card ${id} from tag ${tagId}: ${result.reason}`);
1058
+ }
1059
+ });
1060
+ } catch (error) {
1061
+ logger.error(`Error removing card ${id} from tags: ${error}`);
1062
+ }
964
1063
  return this.db.remove(doc);
965
1064
  }
966
1065
  async getCardDisplayableDataIDs(id) {
@@ -1008,7 +1107,13 @@ var init_courseDB = __esm({
1008
1107
  } else {
1009
1108
  return s;
1010
1109
  }
1011
- }).map((c) => `${this.id}-${c.id}-${c.key}`);
1110
+ }).map((c) => {
1111
+ return {
1112
+ courseID: this.id,
1113
+ cardID: c.id,
1114
+ elo: c.key
1115
+ };
1116
+ });
1012
1117
  const str = `below:
1013
1118
  ${below.rows.map((r) => ` ${r.id}-${r.key}
1014
1119
  `)}
@@ -1063,7 +1168,13 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
1063
1168
  }
1064
1169
  }
1065
1170
  async addTagToCard(cardId, tagId, updateELO) {
1066
- return await addTagToCard(this.id, cardId, tagId, (await this._getCurrentUser()).getUsername(), updateELO);
1171
+ return await addTagToCard(
1172
+ this.id,
1173
+ cardId,
1174
+ tagId,
1175
+ (await this._getCurrentUser()).getUsername(),
1176
+ updateELO
1177
+ );
1067
1178
  }
1068
1179
  async removeTagFromCard(cardId, tagId) {
1069
1180
  return await removeTagFromCard(this.id, cardId, tagId);
@@ -1132,23 +1243,9 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
1132
1243
  ////////////////////////////////////
1133
1244
  getNavigationStrategy(id) {
1134
1245
  logger.debug(`[courseDB] Getting navigation strategy: ${id}`);
1135
- const strategy = {
1136
- id: "ELO",
1137
- docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
1138
- name: "ELO",
1139
- description: "ELO-based navigation strategy for ordering content by difficulty",
1140
- implementingClass: "elo" /* ELO */,
1141
- course: this.id,
1142
- serializedData: ""
1143
- // serde is a noop for ELO navigator.
1144
- };
1145
- return Promise.resolve(strategy);
1146
- }
1147
- getAllNavigationStrategies() {
1148
- logger.debug("[courseDB] Returning hard-coded navigation strategies");
1149
- const strategies = [
1150
- {
1151
- id: "ELO",
1246
+ if (id == "") {
1247
+ const strategy = {
1248
+ _id: "NAVIGATION_STRATEGY-ELO",
1152
1249
  docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
1153
1250
  name: "ELO",
1154
1251
  description: "ELO-based navigation strategy for ordering content by difficulty",
@@ -1156,14 +1253,25 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
1156
1253
  course: this.id,
1157
1254
  serializedData: ""
1158
1255
  // serde is a noop for ELO navigator.
1159
- }
1160
- ];
1161
- return Promise.resolve(strategies);
1256
+ };
1257
+ return Promise.resolve(strategy);
1258
+ } else {
1259
+ return this.db.get(id);
1260
+ }
1162
1261
  }
1163
- addNavigationStrategy(data) {
1164
- logger.debug(`[courseDB] Adding navigation strategy: ${data.id}`);
1165
- logger.debug(JSON.stringify(data));
1166
- return Promise.resolve();
1262
+ async getAllNavigationStrategies() {
1263
+ const prefix = DocTypePrefixes["NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */];
1264
+ const result = await this.db.allDocs({
1265
+ startkey: prefix,
1266
+ endkey: `${prefix}\uFFF0`,
1267
+ include_docs: true
1268
+ });
1269
+ return result.rows.map((row) => row.doc);
1270
+ }
1271
+ async addNavigationStrategy(data) {
1272
+ logger.debug(`[courseDB] Adding navigation strategy: ${data._id}`);
1273
+ return this.db.put(data).then(() => {
1274
+ });
1167
1275
  }
1168
1276
  updateNavigationStrategy(id, data) {
1169
1277
  logger.debug(`[courseDB] Updating navigation strategy: ${id}`);
@@ -1171,9 +1279,32 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
1171
1279
  return Promise.resolve();
1172
1280
  }
1173
1281
  async surfaceNavigationStrategy() {
1282
+ try {
1283
+ const config = await this.getCourseConfig();
1284
+ if (config.defaultNavigationStrategyId) {
1285
+ try {
1286
+ const strategy = await this.getNavigationStrategy(config.defaultNavigationStrategyId);
1287
+ if (strategy) {
1288
+ logger.debug(`Surfacing strategy ${strategy.name} from course config`);
1289
+ return strategy;
1290
+ }
1291
+ } catch (e) {
1292
+ logger.warn(
1293
+ // @ts-expect-error tmp: defaultNavigationStrategyId property does not yet exist
1294
+ `Failed to load strategy '${config.defaultNavigationStrategyId}' specified in course config. Falling back to ELO.`,
1295
+ e
1296
+ );
1297
+ }
1298
+ }
1299
+ } catch (e) {
1300
+ logger.warn(
1301
+ "Could not retrieve course config to determine navigation strategy. Falling back to ELO.",
1302
+ e
1303
+ );
1304
+ }
1174
1305
  logger.warn(`Returning hard-coded default ELO navigator`);
1175
1306
  const ret = {
1176
- id: "ELO",
1307
+ _id: "NAVIGATION_STRATEGY-ELO",
1177
1308
  docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
1178
1309
  name: "ELO",
1179
1310
  description: "ELO-based navigation strategy",
@@ -1256,17 +1387,93 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
1256
1387
  selectedCards.push(card);
1257
1388
  }
1258
1389
  return selectedCards.map((c) => {
1259
- const split = c.split("-");
1260
1390
  return {
1261
1391
  courseID: this.id,
1262
- cardID: split[1],
1263
- qualifiedID: `${split[0]}-${split[1]}`,
1392
+ cardID: c.cardID,
1264
1393
  contentSourceType: "course",
1265
1394
  contentSourceID: this.id,
1395
+ elo: c.elo,
1266
1396
  status: "new"
1267
1397
  };
1268
1398
  });
1269
1399
  }
1400
+ // Admin search methods
1401
+ async searchCards(query) {
1402
+ logger.log(`[CourseDB ${this.id}] Searching for: "${query}"`);
1403
+ let displayableData;
1404
+ try {
1405
+ displayableData = await this.db.find({
1406
+ selector: {
1407
+ docType: "DISPLAYABLE_DATA",
1408
+ "data.0.data": { $regex: `.*${query}.*` }
1409
+ }
1410
+ });
1411
+ logger.log(`[CourseDB ${this.id}] Regex search on data[0].data successful`);
1412
+ } catch (regexError) {
1413
+ logger.log(
1414
+ `[CourseDB ${this.id}] Regex search failed, falling back to manual search:`,
1415
+ regexError
1416
+ );
1417
+ const allDisplayable = await this.db.find({
1418
+ selector: {
1419
+ docType: "DISPLAYABLE_DATA"
1420
+ }
1421
+ });
1422
+ logger.log(
1423
+ `[CourseDB ${this.id}] Retrieved ${allDisplayable.docs.length} documents for manual filtering`
1424
+ );
1425
+ displayableData = {
1426
+ docs: allDisplayable.docs.filter((doc) => {
1427
+ const docString = JSON.stringify(doc).toLowerCase();
1428
+ const match = docString.includes(query.toLowerCase());
1429
+ if (match) {
1430
+ logger.log(`[CourseDB ${this.id}] Manual match found in document: ${doc._id}`);
1431
+ }
1432
+ return match;
1433
+ })
1434
+ };
1435
+ }
1436
+ logger.log(
1437
+ `[CourseDB ${this.id}] Found ${displayableData.docs.length} displayable data documents`
1438
+ );
1439
+ if (displayableData.docs.length === 0) {
1440
+ const allDisplayableData = await this.db.find({
1441
+ selector: {
1442
+ docType: "DISPLAYABLE_DATA"
1443
+ },
1444
+ limit: 5
1445
+ // Just sample a few
1446
+ });
1447
+ logger.log(
1448
+ `[CourseDB ${this.id}] Sample displayable data:`,
1449
+ allDisplayableData.docs.map((d) => ({
1450
+ id: d._id,
1451
+ docType: d.docType,
1452
+ dataStructure: d.data ? Object.keys(d.data) : "no data field",
1453
+ dataContent: d.data,
1454
+ fullDoc: d
1455
+ }))
1456
+ );
1457
+ }
1458
+ const allResults = [];
1459
+ for (const dd of displayableData.docs) {
1460
+ const cards = await this.db.find({
1461
+ selector: {
1462
+ docType: "CARD",
1463
+ id_displayable_data: { $in: [dd._id] }
1464
+ }
1465
+ });
1466
+ logger.log(
1467
+ `[CourseDB ${this.id}] Displayable data ${dd._id} linked to ${cards.docs.length} cards`
1468
+ );
1469
+ allResults.push(...cards.docs);
1470
+ }
1471
+ logger.log(`[CourseDB ${this.id}] Total cards found: ${allResults.length}`);
1472
+ return allResults;
1473
+ }
1474
+ async find(request) {
1475
+ return this.db.find(request);
1476
+ }
1270
1477
  };
1271
1478
  }
1272
1479
  });
@@ -1331,7 +1538,7 @@ var init_classroomDB2 = __esm({
1331
1538
  const dbName = `classdb-student-${this._id}`;
1332
1539
  this._db = new pouchdb_setup_default(
1333
1540
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
1334
- pouchDBincludeCredentialsConfig
1541
+ createPouchDBConfig()
1335
1542
  );
1336
1543
  try {
1337
1544
  const cfg = await this._db.get(CLASSROOM_CONFIG);
@@ -1400,9 +1607,11 @@ var init_classroomDB2 = __esm({
1400
1607
  ret.push(await getCourseDB2(content.courseID).get(content.cardID));
1401
1608
  }
1402
1609
  }
1403
- logger.info(`New Cards from classroom ${this._cfg.name}: ${ret.map((c) => c.qualifiedID)}`);
1610
+ logger.info(
1611
+ `New Cards from classroom ${this._cfg.name}: ${ret.map((c) => `${c.courseID}-${c.cardID}`)}`
1612
+ );
1404
1613
  return ret.filter((c) => {
1405
- if (activeCards.some((ac) => c.qualifiedID.includes(ac))) {
1614
+ if (activeCards.some((ac) => c.cardID === ac.cardID)) {
1406
1615
  return false;
1407
1616
  } else {
1408
1617
  return true;
@@ -1456,10 +1665,29 @@ var init_CouchDBSyncStrategy = __esm({
1456
1665
  import fetch2 from "cross-fetch";
1457
1666
  import moment4 from "moment";
1458
1667
  import process2 from "process";
1668
+ function createPouchDBConfig() {
1669
+ const hasExplicitCredentials = ENV.COUCHDB_USERNAME && ENV.COUCHDB_PASSWORD;
1670
+ const isNodeEnvironment = typeof window === "undefined";
1671
+ if (hasExplicitCredentials && isNodeEnvironment) {
1672
+ return {
1673
+ fetch(url, opts = {}) {
1674
+ const basicAuth = btoa(`${ENV.COUCHDB_USERNAME}:${ENV.COUCHDB_PASSWORD}`);
1675
+ const headers = new Headers(opts.headers || {});
1676
+ headers.set("Authorization", `Basic ${basicAuth}`);
1677
+ const newOpts = {
1678
+ ...opts,
1679
+ headers
1680
+ };
1681
+ return pouchdb_setup_default.fetch(url, newOpts);
1682
+ }
1683
+ };
1684
+ }
1685
+ return pouchDBincludeCredentialsConfig;
1686
+ }
1459
1687
  function getCourseDB2(courseID) {
1460
1688
  return new pouchdb_setup_default(
1461
1689
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + "coursedb-" + courseID,
1462
- pouchDBincludeCredentialsConfig
1690
+ createPouchDBConfig()
1463
1691
  );
1464
1692
  }
1465
1693
  function getCourseDocs(courseID, docIDs, options = {}) {
@@ -1748,6 +1976,9 @@ Currently logged-in as ${this._username}.`
1748
1976
  await this.init();
1749
1977
  return ret;
1750
1978
  }
1979
+ async get(id) {
1980
+ return this.localDB.get(id);
1981
+ }
1751
1982
  update(id, update) {
1752
1983
  return this.updateQueue.update(id, update);
1753
1984
  }
@@ -1794,7 +2025,12 @@ Currently logged-in as ${this._username}.`
1794
2025
  endkey: keys.endkey,
1795
2026
  include_docs: true
1796
2027
  });
1797
- return reviews.rows.map((r) => `${r.doc.courseId}-${r.doc.cardId}`);
2028
+ return reviews.rows.map((r) => {
2029
+ return {
2030
+ courseID: r.doc.courseId,
2031
+ cardID: r.doc.cardId
2032
+ };
2033
+ });
1798
2034
  }
1799
2035
  async getActivityRecords() {
1800
2036
  try {
@@ -2074,8 +2310,18 @@ Currently logged-in as ${this._username}.`
2074
2310
  }
2075
2311
  this.setDBandQ();
2076
2312
  this.syncStrategy.startSync(this.localDB, this.remoteDB);
2077
- void this.applyDesignDocs();
2078
- void this.deduplicateReviews();
2313
+ this.applyDesignDocs().catch((error) => {
2314
+ log3(`Error in applyDesignDocs background task: ${error}`);
2315
+ if (error && typeof error === "object") {
2316
+ log3(`Full error details in applyDesignDocs: ${JSON.stringify(error)}`);
2317
+ }
2318
+ });
2319
+ this.deduplicateReviews().catch((error) => {
2320
+ log3(`Error in deduplicateReviews background task: ${error}`);
2321
+ if (error && typeof error === "object") {
2322
+ log3(`Full error details in background task: ${JSON.stringify(error)}`);
2323
+ }
2324
+ });
2079
2325
  _BaseUser._initialized = true;
2080
2326
  }
2081
2327
  static designDocs = [
@@ -2093,10 +2339,15 @@ Currently logged-in as ${this._username}.`
2093
2339
  }
2094
2340
  ];
2095
2341
  async applyDesignDocs() {
2342
+ log3(`Starting applyDesignDocs for user: ${this._username}`);
2343
+ log3(`Remote DB name: ${this.remoteDB.name || "unknown"}`);
2096
2344
  if (this._username === "admin") {
2345
+ log3("Skipping design docs for admin user");
2097
2346
  return;
2098
2347
  }
2348
+ log3(`Applying ${_BaseUser.designDocs.length} design docs`);
2099
2349
  for (const doc of _BaseUser.designDocs) {
2350
+ log3(`Applying design doc: ${doc._id}`);
2100
2351
  try {
2101
2352
  try {
2102
2353
  const existingDoc = await this.remoteDB.get(doc._id);
@@ -2173,17 +2424,21 @@ Currently logged-in as ${this._username}.`
2173
2424
  } catch (e) {
2174
2425
  const reason = e;
2175
2426
  if (reason.status === 404) {
2176
- const initCardHistory = {
2177
- _id: cardHistoryID,
2178
- cardID: record.cardID,
2179
- courseID: record.courseID,
2180
- records: [record],
2181
- lapses: 0,
2182
- streak: 0,
2183
- bestInterval: 0
2184
- };
2185
- const putResult = await this.writeDB.put(initCardHistory);
2186
- return { ...initCardHistory, _rev: putResult.rev };
2427
+ try {
2428
+ const initCardHistory = {
2429
+ _id: cardHistoryID,
2430
+ cardID: record.cardID,
2431
+ courseID: record.courseID,
2432
+ records: [record],
2433
+ lapses: 0,
2434
+ streak: 0,
2435
+ bestInterval: 0
2436
+ };
2437
+ const putResult = await this.writeDB.put(initCardHistory);
2438
+ return { ...initCardHistory, _rev: putResult.rev };
2439
+ } catch (creationError) {
2440
+ throw new Error(`Failed to create CardHistory for ${cardHistoryID}. Reason: ${creationError}`);
2441
+ }
2187
2442
  } else {
2188
2443
  throw new Error(`putCardRecord failed because of:
2189
2444
  name:${reason.name}
@@ -2195,8 +2450,13 @@ Currently logged-in as ${this._username}.`
2195
2450
  async deduplicateReviews() {
2196
2451
  try {
2197
2452
  log3("Starting deduplication of scheduled reviews...");
2453
+ log3(`Remote DB name: ${this.remoteDB.name || "unknown"}`);
2454
+ log3(`Write DB name: ${this.writeDB.name || "unknown"}`);
2198
2455
  const reviewsMap = {};
2199
2456
  const duplicateDocIds = [];
2457
+ log3(
2458
+ `Attempting to query remoteDB for reviewCards/reviewCards. Database: ${this.remoteDB.name || "unknown"}`
2459
+ );
2200
2460
  const scheduledReviews = await this.remoteDB.query("reviewCards/reviewCards");
2201
2461
  log3(`Found ${scheduledReviews.rows.length} scheduled reviews to process`);
2202
2462
  scheduledReviews.rows.forEach((r) => {
@@ -2231,6 +2491,17 @@ Currently logged-in as ${this._username}.`
2231
2491
  }
2232
2492
  } catch (error) {
2233
2493
  log3(`Error during review deduplication: ${error}`);
2494
+ if (error && typeof error === "object" && "status" in error && error.status === 404) {
2495
+ log3(
2496
+ `Database not found (404) during review deduplication. Database: ${this.remoteDB.name || "unknown"}`
2497
+ );
2498
+ log3(
2499
+ `This might indicate the user database doesn't exist or the reviewCards view isn't available`
2500
+ );
2501
+ }
2502
+ if (error && typeof error === "object") {
2503
+ log3(`Full error details: ${JSON.stringify(error)}`);
2504
+ }
2234
2505
  }
2235
2506
  }
2236
2507
  /**