@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
@@ -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
  }
@@ -343,42 +343,58 @@ var init_updateQueue = __esm({
343
343
  async applyUpdates(id) {
344
344
  logger.debug(`Applying updates on doc: ${id}`);
345
345
  if (this.inprogressUpdates[id]) {
346
- await this.readDB.info();
346
+ while (this.inprogressUpdates[id]) {
347
+ await new Promise((resolve) => setTimeout(resolve, Math.random() * 50));
348
+ }
347
349
  return this.applyUpdates(id);
348
350
  } else {
349
351
  if (this.pendingUpdates[id] && this.pendingUpdates[id].length > 0) {
350
352
  this.inprogressUpdates[id] = true;
351
- try {
352
- let doc = await this.readDB.get(id);
353
- logger.debug(`Retrieved doc: ${id}`);
354
- while (this.pendingUpdates[id].length !== 0) {
355
- const update = this.pendingUpdates[id].splice(0, 1)[0];
356
- if (typeof update === "function") {
357
- doc = { ...doc, ...update(doc) };
353
+ const MAX_RETRIES = 5;
354
+ for (let i = 0; i < MAX_RETRIES; i++) {
355
+ try {
356
+ const doc = await this.readDB.get(id);
357
+ logger.debug(`Retrieved doc: ${id}`);
358
+ let updatedDoc = { ...doc };
359
+ const updatesToApply = [...this.pendingUpdates[id]];
360
+ for (const update of updatesToApply) {
361
+ if (typeof update === "function") {
362
+ updatedDoc = { ...updatedDoc, ...update(updatedDoc) };
363
+ } else {
364
+ updatedDoc = {
365
+ ...updatedDoc,
366
+ ...update
367
+ };
368
+ }
369
+ }
370
+ await this.writeDB.put(updatedDoc);
371
+ logger.debug(`Put doc: ${id}`);
372
+ this.pendingUpdates[id].splice(0, updatesToApply.length);
373
+ if (this.pendingUpdates[id].length === 0) {
374
+ this.inprogressUpdates[id] = false;
375
+ delete this.inprogressUpdates[id];
358
376
  } else {
359
- doc = {
360
- ...doc,
361
- ...update
362
- };
377
+ return this.applyUpdates(id);
378
+ }
379
+ return updatedDoc;
380
+ } catch (e) {
381
+ if (e.name === "conflict" && i < MAX_RETRIES - 1) {
382
+ logger.warn(`Conflict on update for doc ${id}, retry #${i + 1}`);
383
+ await new Promise((res) => setTimeout(res, 50 * Math.random()));
384
+ } else if (e.name === "not_found" && i === 0) {
385
+ logger.warn(`Update failed for ${id} - does not exist. Throwing to caller.`);
386
+ throw e;
387
+ } else {
388
+ delete this.inprogressUpdates[id];
389
+ if (this.pendingUpdates[id]) {
390
+ delete this.pendingUpdates[id];
391
+ }
392
+ logger.error(`Error on attemped update (retry ${i}): ${JSON.stringify(e)}`);
393
+ throw e;
363
394
  }
364
395
  }
365
- await this.writeDB.put(doc);
366
- logger.debug(`Put doc: ${id}`);
367
- if (this.pendingUpdates[id].length === 0) {
368
- this.inprogressUpdates[id] = false;
369
- delete this.inprogressUpdates[id];
370
- } else {
371
- return this.applyUpdates(id);
372
- }
373
- return doc;
374
- } catch (e) {
375
- delete this.inprogressUpdates[id];
376
- if (this.pendingUpdates[id]) {
377
- delete this.pendingUpdates[id];
378
- }
379
- logger.error(`Error on attemped update: ${JSON.stringify(e)}`);
380
- throw e;
381
396
  }
397
+ throw new Error(`UpdateQueue failed for doc ${id} after ${MAX_RETRIES} retries.`);
382
398
  } else {
383
399
  throw new Error(`Empty Updates Queue Triggered`);
384
400
  }
@@ -629,7 +645,7 @@ function getCourseDB(courseID) {
629
645
  const dbName = `coursedb-${courseID}`;
630
646
  return new pouchdb_setup_default(
631
647
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
632
- pouchDBincludeCredentialsConfig
648
+ createPouchDBConfig()
633
649
  );
634
650
  }
635
651
  var import_common, import_common2, import_common3, import_uuid, AlreadyTaggedErr;
@@ -713,13 +729,16 @@ var init_elo = __esm({
713
729
  }
714
730
  async getNewCards(limit = 99) {
715
731
  const activeCards = await this.user.getActiveCards();
716
- return (await this.course.getCardsCenteredAtELO({ limit, elo: "user" }, (c) => {
717
- if (activeCards.some((ac) => c.includes(ac))) {
718
- return false;
719
- } else {
720
- return true;
732
+ return (await this.course.getCardsCenteredAtELO(
733
+ { limit, elo: "user" },
734
+ (c) => {
735
+ if (activeCards.some((ac) => c.cardID === ac.cardID)) {
736
+ return false;
737
+ } else {
738
+ return true;
739
+ }
721
740
  }
722
- })).map((c) => {
741
+ )).map((c) => {
723
742
  return {
724
743
  ...c,
725
744
  status: "new"
@@ -730,12 +749,74 @@ var init_elo = __esm({
730
749
  }
731
750
  });
732
751
 
752
+ // src/core/navigators/hardcodedOrder.ts
753
+ var hardcodedOrder_exports = {};
754
+ __export(hardcodedOrder_exports, {
755
+ default: () => HardcodedOrderNavigator
756
+ });
757
+ var HardcodedOrderNavigator;
758
+ var init_hardcodedOrder = __esm({
759
+ "src/core/navigators/hardcodedOrder.ts"() {
760
+ "use strict";
761
+ init_navigators();
762
+ init_logger();
763
+ HardcodedOrderNavigator = class extends ContentNavigator {
764
+ orderedCardIds = [];
765
+ user;
766
+ course;
767
+ constructor(user, course, strategyData) {
768
+ super();
769
+ this.user = user;
770
+ this.course = course;
771
+ if (strategyData.serializedData) {
772
+ try {
773
+ this.orderedCardIds = JSON.parse(strategyData.serializedData);
774
+ } catch (e) {
775
+ logger.error("Failed to parse serializedData for HardcodedOrderNavigator", e);
776
+ }
777
+ }
778
+ }
779
+ async getPendingReviews() {
780
+ const reviews = await this.user.getPendingReviews(this.course.getCourseID());
781
+ return reviews.map((r) => {
782
+ return {
783
+ ...r,
784
+ contentSourceType: "course",
785
+ contentSourceID: this.course.getCourseID(),
786
+ cardID: r.cardId,
787
+ courseID: r.courseId,
788
+ reviewID: r._id,
789
+ status: "review"
790
+ };
791
+ });
792
+ }
793
+ async getNewCards(limit = 99) {
794
+ const activeCardIds = (await this.user.getActiveCards()).map((c) => c.cardID);
795
+ const newCardIds = this.orderedCardIds.filter(
796
+ (cardId) => !activeCardIds.includes(cardId)
797
+ );
798
+ const cardsToReturn = newCardIds.slice(0, limit);
799
+ return cardsToReturn.map((cardId) => {
800
+ return {
801
+ cardID: cardId,
802
+ courseID: this.course.getCourseID(),
803
+ contentSourceType: "course",
804
+ contentSourceID: this.course.getCourseID(),
805
+ status: "new"
806
+ };
807
+ });
808
+ }
809
+ };
810
+ }
811
+ });
812
+
733
813
  // import("./**/*") in src/core/navigators/index.ts
734
814
  var globImport;
735
815
  var init_ = __esm({
736
816
  'import("./**/*") in src/core/navigators/index.ts'() {
737
817
  globImport = __glob({
738
818
  "./elo.ts": () => Promise.resolve().then(() => (init_elo(), elo_exports)),
819
+ "./hardcodedOrder.ts": () => Promise.resolve().then(() => (init_hardcodedOrder(), hardcodedOrder_exports)),
739
820
  "./index.ts": () => Promise.resolve().then(() => (init_navigators(), navigators_exports))
740
821
  });
741
822
  }
@@ -755,6 +836,7 @@ var init_navigators = __esm({
755
836
  init_();
756
837
  Navigators = /* @__PURE__ */ ((Navigators2) => {
757
838
  Navigators2["ELO"] = "elo";
839
+ Navigators2["HARDCODED"] = "hardcodedOrder";
758
840
  return Navigators2;
759
841
  })(Navigators || {});
760
842
  ContentNavigator = class {
@@ -767,7 +849,7 @@ var init_navigators = __esm({
767
849
  static async create(user, course, strategyData) {
768
850
  const implementingClass = strategyData.implementingClass;
769
851
  let NavigatorImpl;
770
- const variations = ["", ".js", ".ts"];
852
+ const variations = [".ts", ".js", ""];
771
853
  for (const ext of variations) {
772
854
  try {
773
855
  const module2 = await globImport(`./${implementingClass}${ext}`);
@@ -979,6 +1061,23 @@ var init_courseDB = __esm({
979
1061
  if (!doc.docType || !(doc.docType === "CARD" /* CARD */)) {
980
1062
  throw new Error(`failed to remove ${id} from course ${this.id}. id does not point to a card`);
981
1063
  }
1064
+ try {
1065
+ const appliedTags = await this.getAppliedTags(id);
1066
+ const results = await Promise.allSettled(
1067
+ appliedTags.rows.map(async (tagRow) => {
1068
+ const tagId = tagRow.id;
1069
+ await this.removeTagFromCard(id, tagId);
1070
+ })
1071
+ );
1072
+ results.forEach((result, index) => {
1073
+ if (result.status === "rejected") {
1074
+ const tagId = appliedTags.rows[index].id;
1075
+ logger.error(`Failed to remove card ${id} from tag ${tagId}: ${result.reason}`);
1076
+ }
1077
+ });
1078
+ } catch (error) {
1079
+ logger.error(`Error removing card ${id} from tags: ${error}`);
1080
+ }
982
1081
  return this.db.remove(doc);
983
1082
  }
984
1083
  async getCardDisplayableDataIDs(id) {
@@ -1026,7 +1125,13 @@ var init_courseDB = __esm({
1026
1125
  } else {
1027
1126
  return s;
1028
1127
  }
1029
- }).map((c) => `${this.id}-${c.id}-${c.key}`);
1128
+ }).map((c) => {
1129
+ return {
1130
+ courseID: this.id,
1131
+ cardID: c.id,
1132
+ elo: c.key
1133
+ };
1134
+ });
1030
1135
  const str = `below:
1031
1136
  ${below.rows.map((r) => ` ${r.id}-${r.key}
1032
1137
  `)}
@@ -1081,7 +1186,13 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
1081
1186
  }
1082
1187
  }
1083
1188
  async addTagToCard(cardId, tagId, updateELO) {
1084
- return await addTagToCard(this.id, cardId, tagId, (await this._getCurrentUser()).getUsername(), updateELO);
1189
+ return await addTagToCard(
1190
+ this.id,
1191
+ cardId,
1192
+ tagId,
1193
+ (await this._getCurrentUser()).getUsername(),
1194
+ updateELO
1195
+ );
1085
1196
  }
1086
1197
  async removeTagFromCard(cardId, tagId) {
1087
1198
  return await removeTagFromCard(this.id, cardId, tagId);
@@ -1150,23 +1261,9 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
1150
1261
  ////////////////////////////////////
1151
1262
  getNavigationStrategy(id) {
1152
1263
  logger.debug(`[courseDB] Getting navigation strategy: ${id}`);
1153
- const strategy = {
1154
- id: "ELO",
1155
- docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
1156
- name: "ELO",
1157
- description: "ELO-based navigation strategy for ordering content by difficulty",
1158
- implementingClass: "elo" /* ELO */,
1159
- course: this.id,
1160
- serializedData: ""
1161
- // serde is a noop for ELO navigator.
1162
- };
1163
- return Promise.resolve(strategy);
1164
- }
1165
- getAllNavigationStrategies() {
1166
- logger.debug("[courseDB] Returning hard-coded navigation strategies");
1167
- const strategies = [
1168
- {
1169
- id: "ELO",
1264
+ if (id == "") {
1265
+ const strategy = {
1266
+ _id: "NAVIGATION_STRATEGY-ELO",
1170
1267
  docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
1171
1268
  name: "ELO",
1172
1269
  description: "ELO-based navigation strategy for ordering content by difficulty",
@@ -1174,14 +1271,25 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
1174
1271
  course: this.id,
1175
1272
  serializedData: ""
1176
1273
  // serde is a noop for ELO navigator.
1177
- }
1178
- ];
1179
- return Promise.resolve(strategies);
1274
+ };
1275
+ return Promise.resolve(strategy);
1276
+ } else {
1277
+ return this.db.get(id);
1278
+ }
1180
1279
  }
1181
- addNavigationStrategy(data) {
1182
- logger.debug(`[courseDB] Adding navigation strategy: ${data.id}`);
1183
- logger.debug(JSON.stringify(data));
1184
- return Promise.resolve();
1280
+ async getAllNavigationStrategies() {
1281
+ const prefix = DocTypePrefixes["NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */];
1282
+ const result = await this.db.allDocs({
1283
+ startkey: prefix,
1284
+ endkey: `${prefix}\uFFF0`,
1285
+ include_docs: true
1286
+ });
1287
+ return result.rows.map((row) => row.doc);
1288
+ }
1289
+ async addNavigationStrategy(data) {
1290
+ logger.debug(`[courseDB] Adding navigation strategy: ${data._id}`);
1291
+ return this.db.put(data).then(() => {
1292
+ });
1185
1293
  }
1186
1294
  updateNavigationStrategy(id, data) {
1187
1295
  logger.debug(`[courseDB] Updating navigation strategy: ${id}`);
@@ -1189,9 +1297,32 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
1189
1297
  return Promise.resolve();
1190
1298
  }
1191
1299
  async surfaceNavigationStrategy() {
1300
+ try {
1301
+ const config = await this.getCourseConfig();
1302
+ if (config.defaultNavigationStrategyId) {
1303
+ try {
1304
+ const strategy = await this.getNavigationStrategy(config.defaultNavigationStrategyId);
1305
+ if (strategy) {
1306
+ logger.debug(`Surfacing strategy ${strategy.name} from course config`);
1307
+ return strategy;
1308
+ }
1309
+ } catch (e) {
1310
+ logger.warn(
1311
+ // @ts-expect-error tmp: defaultNavigationStrategyId property does not yet exist
1312
+ `Failed to load strategy '${config.defaultNavigationStrategyId}' specified in course config. Falling back to ELO.`,
1313
+ e
1314
+ );
1315
+ }
1316
+ }
1317
+ } catch (e) {
1318
+ logger.warn(
1319
+ "Could not retrieve course config to determine navigation strategy. Falling back to ELO.",
1320
+ e
1321
+ );
1322
+ }
1192
1323
  logger.warn(`Returning hard-coded default ELO navigator`);
1193
1324
  const ret = {
1194
- id: "ELO",
1325
+ _id: "NAVIGATION_STRATEGY-ELO",
1195
1326
  docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
1196
1327
  name: "ELO",
1197
1328
  description: "ELO-based navigation strategy",
@@ -1274,17 +1405,93 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
1274
1405
  selectedCards.push(card);
1275
1406
  }
1276
1407
  return selectedCards.map((c) => {
1277
- const split = c.split("-");
1278
1408
  return {
1279
1409
  courseID: this.id,
1280
- cardID: split[1],
1281
- qualifiedID: `${split[0]}-${split[1]}`,
1410
+ cardID: c.cardID,
1282
1411
  contentSourceType: "course",
1283
1412
  contentSourceID: this.id,
1413
+ elo: c.elo,
1284
1414
  status: "new"
1285
1415
  };
1286
1416
  });
1287
1417
  }
1418
+ // Admin search methods
1419
+ async searchCards(query) {
1420
+ logger.log(`[CourseDB ${this.id}] Searching for: "${query}"`);
1421
+ let displayableData;
1422
+ try {
1423
+ displayableData = await this.db.find({
1424
+ selector: {
1425
+ docType: "DISPLAYABLE_DATA",
1426
+ "data.0.data": { $regex: `.*${query}.*` }
1427
+ }
1428
+ });
1429
+ logger.log(`[CourseDB ${this.id}] Regex search on data[0].data successful`);
1430
+ } catch (regexError) {
1431
+ logger.log(
1432
+ `[CourseDB ${this.id}] Regex search failed, falling back to manual search:`,
1433
+ regexError
1434
+ );
1435
+ const allDisplayable = await this.db.find({
1436
+ selector: {
1437
+ docType: "DISPLAYABLE_DATA"
1438
+ }
1439
+ });
1440
+ logger.log(
1441
+ `[CourseDB ${this.id}] Retrieved ${allDisplayable.docs.length} documents for manual filtering`
1442
+ );
1443
+ displayableData = {
1444
+ docs: allDisplayable.docs.filter((doc) => {
1445
+ const docString = JSON.stringify(doc).toLowerCase();
1446
+ const match = docString.includes(query.toLowerCase());
1447
+ if (match) {
1448
+ logger.log(`[CourseDB ${this.id}] Manual match found in document: ${doc._id}`);
1449
+ }
1450
+ return match;
1451
+ })
1452
+ };
1453
+ }
1454
+ logger.log(
1455
+ `[CourseDB ${this.id}] Found ${displayableData.docs.length} displayable data documents`
1456
+ );
1457
+ if (displayableData.docs.length === 0) {
1458
+ const allDisplayableData = await this.db.find({
1459
+ selector: {
1460
+ docType: "DISPLAYABLE_DATA"
1461
+ },
1462
+ limit: 5
1463
+ // Just sample a few
1464
+ });
1465
+ logger.log(
1466
+ `[CourseDB ${this.id}] Sample displayable data:`,
1467
+ allDisplayableData.docs.map((d) => ({
1468
+ id: d._id,
1469
+ docType: d.docType,
1470
+ dataStructure: d.data ? Object.keys(d.data) : "no data field",
1471
+ dataContent: d.data,
1472
+ fullDoc: d
1473
+ }))
1474
+ );
1475
+ }
1476
+ const allResults = [];
1477
+ for (const dd of displayableData.docs) {
1478
+ const cards = await this.db.find({
1479
+ selector: {
1480
+ docType: "CARD",
1481
+ id_displayable_data: { $in: [dd._id] }
1482
+ }
1483
+ });
1484
+ logger.log(
1485
+ `[CourseDB ${this.id}] Displayable data ${dd._id} linked to ${cards.docs.length} cards`
1486
+ );
1487
+ allResults.push(...cards.docs);
1488
+ }
1489
+ logger.log(`[CourseDB ${this.id}] Total cards found: ${allResults.length}`);
1490
+ return allResults;
1491
+ }
1492
+ async find(request) {
1493
+ return this.db.find(request);
1494
+ }
1288
1495
  };
1289
1496
  }
1290
1497
  });
@@ -1349,7 +1556,7 @@ var init_classroomDB2 = __esm({
1349
1556
  const dbName = `classdb-student-${this._id}`;
1350
1557
  this._db = new pouchdb_setup_default(
1351
1558
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + dbName,
1352
- pouchDBincludeCredentialsConfig
1559
+ createPouchDBConfig()
1353
1560
  );
1354
1561
  try {
1355
1562
  const cfg = await this._db.get(CLASSROOM_CONFIG);
@@ -1418,9 +1625,11 @@ var init_classroomDB2 = __esm({
1418
1625
  ret.push(await getCourseDB2(content.courseID).get(content.cardID));
1419
1626
  }
1420
1627
  }
1421
- logger.info(`New Cards from classroom ${this._cfg.name}: ${ret.map((c) => c.qualifiedID)}`);
1628
+ logger.info(
1629
+ `New Cards from classroom ${this._cfg.name}: ${ret.map((c) => `${c.courseID}-${c.cardID}`)}`
1630
+ );
1422
1631
  return ret.filter((c) => {
1423
- if (activeCards.some((ac) => c.qualifiedID.includes(ac))) {
1632
+ if (activeCards.some((ac) => c.cardID === ac.cardID)) {
1424
1633
  return false;
1425
1634
  } else {
1426
1635
  return true;
@@ -1473,10 +1682,29 @@ var init_CouchDBSyncStrategy = __esm({
1473
1682
  });
1474
1683
 
1475
1684
  // src/impl/couch/index.ts
1685
+ function createPouchDBConfig() {
1686
+ const hasExplicitCredentials = ENV.COUCHDB_USERNAME && ENV.COUCHDB_PASSWORD;
1687
+ const isNodeEnvironment = typeof window === "undefined";
1688
+ if (hasExplicitCredentials && isNodeEnvironment) {
1689
+ return {
1690
+ fetch(url, opts = {}) {
1691
+ const basicAuth = btoa(`${ENV.COUCHDB_USERNAME}:${ENV.COUCHDB_PASSWORD}`);
1692
+ const headers = new Headers(opts.headers || {});
1693
+ headers.set("Authorization", `Basic ${basicAuth}`);
1694
+ const newOpts = {
1695
+ ...opts,
1696
+ headers
1697
+ };
1698
+ return pouchdb_setup_default.fetch(url, newOpts);
1699
+ }
1700
+ };
1701
+ }
1702
+ return pouchDBincludeCredentialsConfig;
1703
+ }
1476
1704
  function getCourseDB2(courseID) {
1477
1705
  return new pouchdb_setup_default(
1478
1706
  ENV.COUCHDB_SERVER_PROTOCOL + "://" + ENV.COUCHDB_SERVER_URL + "coursedb-" + courseID,
1479
- pouchDBincludeCredentialsConfig
1707
+ createPouchDBConfig()
1480
1708
  );
1481
1709
  }
1482
1710
  function getCourseDocs(courseID, docIDs, options = {}) {
@@ -1768,6 +1996,9 @@ Currently logged-in as ${this._username}.`
1768
1996
  await this.init();
1769
1997
  return ret;
1770
1998
  }
1999
+ async get(id) {
2000
+ return this.localDB.get(id);
2001
+ }
1771
2002
  update(id, update) {
1772
2003
  return this.updateQueue.update(id, update);
1773
2004
  }
@@ -1814,7 +2045,12 @@ Currently logged-in as ${this._username}.`
1814
2045
  endkey: keys.endkey,
1815
2046
  include_docs: true
1816
2047
  });
1817
- return reviews.rows.map((r) => `${r.doc.courseId}-${r.doc.cardId}`);
2048
+ return reviews.rows.map((r) => {
2049
+ return {
2050
+ courseID: r.doc.courseId,
2051
+ cardID: r.doc.cardId
2052
+ };
2053
+ });
1818
2054
  }
1819
2055
  async getActivityRecords() {
1820
2056
  try {
@@ -2094,8 +2330,18 @@ Currently logged-in as ${this._username}.`
2094
2330
  }
2095
2331
  this.setDBandQ();
2096
2332
  this.syncStrategy.startSync(this.localDB, this.remoteDB);
2097
- void this.applyDesignDocs();
2098
- void this.deduplicateReviews();
2333
+ this.applyDesignDocs().catch((error) => {
2334
+ log3(`Error in applyDesignDocs background task: ${error}`);
2335
+ if (error && typeof error === "object") {
2336
+ log3(`Full error details in applyDesignDocs: ${JSON.stringify(error)}`);
2337
+ }
2338
+ });
2339
+ this.deduplicateReviews().catch((error) => {
2340
+ log3(`Error in deduplicateReviews background task: ${error}`);
2341
+ if (error && typeof error === "object") {
2342
+ log3(`Full error details in background task: ${JSON.stringify(error)}`);
2343
+ }
2344
+ });
2099
2345
  _BaseUser._initialized = true;
2100
2346
  }
2101
2347
  static designDocs = [
@@ -2113,10 +2359,15 @@ Currently logged-in as ${this._username}.`
2113
2359
  }
2114
2360
  ];
2115
2361
  async applyDesignDocs() {
2362
+ log3(`Starting applyDesignDocs for user: ${this._username}`);
2363
+ log3(`Remote DB name: ${this.remoteDB.name || "unknown"}`);
2116
2364
  if (this._username === "admin") {
2365
+ log3("Skipping design docs for admin user");
2117
2366
  return;
2118
2367
  }
2368
+ log3(`Applying ${_BaseUser.designDocs.length} design docs`);
2119
2369
  for (const doc of _BaseUser.designDocs) {
2370
+ log3(`Applying design doc: ${doc._id}`);
2120
2371
  try {
2121
2372
  try {
2122
2373
  const existingDoc = await this.remoteDB.get(doc._id);
@@ -2193,17 +2444,21 @@ Currently logged-in as ${this._username}.`
2193
2444
  } catch (e) {
2194
2445
  const reason = e;
2195
2446
  if (reason.status === 404) {
2196
- const initCardHistory = {
2197
- _id: cardHistoryID,
2198
- cardID: record.cardID,
2199
- courseID: record.courseID,
2200
- records: [record],
2201
- lapses: 0,
2202
- streak: 0,
2203
- bestInterval: 0
2204
- };
2205
- const putResult = await this.writeDB.put(initCardHistory);
2206
- return { ...initCardHistory, _rev: putResult.rev };
2447
+ try {
2448
+ const initCardHistory = {
2449
+ _id: cardHistoryID,
2450
+ cardID: record.cardID,
2451
+ courseID: record.courseID,
2452
+ records: [record],
2453
+ lapses: 0,
2454
+ streak: 0,
2455
+ bestInterval: 0
2456
+ };
2457
+ const putResult = await this.writeDB.put(initCardHistory);
2458
+ return { ...initCardHistory, _rev: putResult.rev };
2459
+ } catch (creationError) {
2460
+ throw new Error(`Failed to create CardHistory for ${cardHistoryID}. Reason: ${creationError}`);
2461
+ }
2207
2462
  } else {
2208
2463
  throw new Error(`putCardRecord failed because of:
2209
2464
  name:${reason.name}
@@ -2215,8 +2470,13 @@ Currently logged-in as ${this._username}.`
2215
2470
  async deduplicateReviews() {
2216
2471
  try {
2217
2472
  log3("Starting deduplication of scheduled reviews...");
2473
+ log3(`Remote DB name: ${this.remoteDB.name || "unknown"}`);
2474
+ log3(`Write DB name: ${this.writeDB.name || "unknown"}`);
2218
2475
  const reviewsMap = {};
2219
2476
  const duplicateDocIds = [];
2477
+ log3(
2478
+ `Attempting to query remoteDB for reviewCards/reviewCards. Database: ${this.remoteDB.name || "unknown"}`
2479
+ );
2220
2480
  const scheduledReviews = await this.remoteDB.query("reviewCards/reviewCards");
2221
2481
  log3(`Found ${scheduledReviews.rows.length} scheduled reviews to process`);
2222
2482
  scheduledReviews.rows.forEach((r) => {
@@ -2251,6 +2511,17 @@ Currently logged-in as ${this._username}.`
2251
2511
  }
2252
2512
  } catch (error) {
2253
2513
  log3(`Error during review deduplication: ${error}`);
2514
+ if (error && typeof error === "object" && "status" in error && error.status === 404) {
2515
+ log3(
2516
+ `Database not found (404) during review deduplication. Database: ${this.remoteDB.name || "unknown"}`
2517
+ );
2518
+ log3(
2519
+ `This might indicate the user database doesn't exist or the reviewCards view isn't available`
2520
+ );
2521
+ }
2522
+ if (error && typeof error === "object") {
2523
+ log3(`Full error details: ${JSON.stringify(error)}`);
2524
+ }
2254
2525
  }
2255
2526
  }
2256
2527
  /**