@vue-skuilder/db 0.1.32-c → 0.1.32-e
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.
- package/dist/{dataLayerProvider-BAn-LRh5.d.ts → contentSource-BMlMwSiG.d.cts} +202 -626
- package/dist/{dataLayerProvider-BJqBlMIl.d.cts → contentSource-Ht3N2f-y.d.ts} +202 -626
- package/dist/core/index.d.cts +23 -84
- package/dist/core/index.d.ts +23 -84
- package/dist/core/index.js +476 -1819
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +456 -1803
- package/dist/core/index.mjs.map +1 -1
- package/dist/dataLayerProvider-BEqB8VBR.d.cts +67 -0
- package/dist/dataLayerProvider-DObSXjnf.d.ts +67 -0
- package/dist/impl/couch/index.d.cts +5 -5
- package/dist/impl/couch/index.d.ts +5 -5
- package/dist/impl/couch/index.js +484 -1827
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +460 -1807
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.cts +5 -4
- package/dist/impl/static/index.d.ts +5 -4
- package/dist/impl/static/index.js +458 -1801
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +437 -1784
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/{index-X6wHrURm.d.ts → index-BWvO-_rJ.d.ts} +1 -1
- package/dist/{index-m8MMGxxR.d.cts → index-Ba7hYbHj.d.cts} +1 -1
- package/dist/index.d.cts +461 -11
- package/dist/index.d.ts +461 -11
- package/dist/index.js +9239 -9159
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +9129 -9049
- package/dist/index.mjs.map +1 -1
- package/dist/{types-DZ5dUqbL.d.ts → types-CJrLM1Ew.d.ts} +1 -1
- package/dist/{types-ZL8tOPQZ.d.cts → types-W8n-B6HG.d.cts} +1 -1
- package/dist/{types-legacy-C7r0T4OV.d.cts → types-legacy-JXDxinpU.d.cts} +1 -1
- package/dist/{types-legacy-C7r0T4OV.d.ts → types-legacy-JXDxinpU.d.ts} +1 -1
- package/dist/util/packer/index.d.cts +3 -3
- package/dist/util/packer/index.d.ts +3 -3
- package/package.json +2 -2
- package/src/core/interfaces/contentSource.ts +2 -3
- package/src/core/navigators/Pipeline.ts +60 -6
- package/src/core/navigators/PipelineDebugger.ts +103 -0
- package/src/core/navigators/filters/hierarchyDefinition.ts +2 -1
- package/src/core/navigators/filters/interferenceMitigator.ts +2 -1
- package/src/core/navigators/filters/relativePriority.ts +2 -1
- package/src/core/navigators/filters/userTagPreference.ts +2 -1
- package/src/core/navigators/generators/CompositeGenerator.ts +58 -5
- package/src/core/navigators/generators/elo.ts +7 -7
- package/src/core/navigators/generators/prescribed.ts +124 -35
- package/src/core/navigators/generators/srs.ts +3 -4
- package/src/core/navigators/generators/types.ts +48 -2
- package/src/core/navigators/index.ts +3 -3
- package/src/impl/couch/classroomDB.ts +4 -3
- package/src/impl/couch/courseDB.ts +3 -3
- package/src/impl/static/courseDB.ts +3 -3
- package/src/study/SessionController.ts +5 -27
- package/src/study/TagFilteredContentSource.ts +4 -3
package/dist/impl/couch/index.js
CHANGED
|
@@ -863,6 +863,81 @@ var init_PipelineDebugger = __esm({
|
|
|
863
863
|
}
|
|
864
864
|
console.groupEnd();
|
|
865
865
|
},
|
|
866
|
+
/**
|
|
867
|
+
* Show prescribed-related cards from the most recent run.
|
|
868
|
+
*
|
|
869
|
+
* Highlights:
|
|
870
|
+
* - cards directly generated by the prescribed strategy
|
|
871
|
+
* - blocked prescribed targets mentioned in provenance
|
|
872
|
+
* - support tags resolved for blocked targets
|
|
873
|
+
*
|
|
874
|
+
* @param groupId - Optional prescribed group ID filter (e.g. 'intro-core')
|
|
875
|
+
*/
|
|
876
|
+
showPrescribed(groupId) {
|
|
877
|
+
if (runHistory.length === 0) {
|
|
878
|
+
logger.info("[Pipeline Debug] No runs captured yet.");
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
const run = runHistory[0];
|
|
882
|
+
const prescribedCards = run.cards.filter(
|
|
883
|
+
(c) => c.provenance.some((p) => p.strategy === "prescribed")
|
|
884
|
+
);
|
|
885
|
+
console.group(`\u{1F9ED} Prescribed Debug (${run.courseId})`);
|
|
886
|
+
if (prescribedCards.length === 0) {
|
|
887
|
+
logger.info("No prescribed-generated cards were present in the most recent run.");
|
|
888
|
+
console.groupEnd();
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
const rows = prescribedCards.map((card) => {
|
|
892
|
+
const prescribedProv = card.provenance.find((p) => p.strategy === "prescribed");
|
|
893
|
+
const reason = prescribedProv?.reason ?? "";
|
|
894
|
+
const parsedGroup = reason.match(/group=([^;]+)/)?.[1] ?? "unknown";
|
|
895
|
+
const mode = reason.match(/mode=([^;]+)/)?.[1] ?? "unknown";
|
|
896
|
+
const blocked = reason.match(/blocked=([^;]+)/)?.[1] ?? "unknown";
|
|
897
|
+
const blockedTargets = reason.match(/blockedTargets=([^;]+)/)?.[1] ?? "none";
|
|
898
|
+
const supportTags = reason.match(/supportTags=([^;]+)/)?.[1] ?? "none";
|
|
899
|
+
const multiplier = reason.match(/multiplier=([^;]+)/)?.[1] ?? "unknown";
|
|
900
|
+
return {
|
|
901
|
+
group: parsedGroup,
|
|
902
|
+
mode,
|
|
903
|
+
cardId: card.cardId,
|
|
904
|
+
selected: card.selected ? "yes" : "no",
|
|
905
|
+
finalScore: card.finalScore.toFixed(3),
|
|
906
|
+
blocked,
|
|
907
|
+
blockedTargets,
|
|
908
|
+
supportTags,
|
|
909
|
+
multiplier
|
|
910
|
+
};
|
|
911
|
+
}).filter((row) => !groupId || row.group === groupId).sort((a, b) => Number(b.finalScore) - Number(a.finalScore));
|
|
912
|
+
if (rows.length === 0) {
|
|
913
|
+
logger.info(
|
|
914
|
+
`[Pipeline Debug] No prescribed cards matched group '${groupId}' in the most recent run.`
|
|
915
|
+
);
|
|
916
|
+
console.groupEnd();
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
console.table(rows);
|
|
920
|
+
const selectedRows = rows.filter((r) => r.selected === "yes");
|
|
921
|
+
const blockedTargetSet = /* @__PURE__ */ new Set();
|
|
922
|
+
const supportTagSet = /* @__PURE__ */ new Set();
|
|
923
|
+
for (const row of rows) {
|
|
924
|
+
if (row.blockedTargets && row.blockedTargets !== "none") {
|
|
925
|
+
row.blockedTargets.split("|").filter(Boolean).forEach((t) => blockedTargetSet.add(t));
|
|
926
|
+
}
|
|
927
|
+
if (row.supportTags && row.supportTags !== "none") {
|
|
928
|
+
row.supportTags.split("|").filter(Boolean).forEach((t) => supportTagSet.add(t));
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
logger.info(`Prescribed cards in run: ${rows.length}`);
|
|
932
|
+
logger.info(`Selected prescribed cards: ${selectedRows.length}`);
|
|
933
|
+
logger.info(
|
|
934
|
+
`Blocked prescribed targets referenced: ${blockedTargetSet.size > 0 ? [...blockedTargetSet].join(", ") : "none"}`
|
|
935
|
+
);
|
|
936
|
+
logger.info(
|
|
937
|
+
`Resolved support tags referenced: ${supportTagSet.size > 0 ? [...supportTagSet].join(", ") : "none"}`
|
|
938
|
+
);
|
|
939
|
+
console.groupEnd();
|
|
940
|
+
},
|
|
866
941
|
/**
|
|
867
942
|
* Show all runs in compact format.
|
|
868
943
|
*/
|
|
@@ -1011,6 +1086,7 @@ Commands:
|
|
|
1011
1086
|
.diagnoseCardSpace() Scan full card space through filters (async)
|
|
1012
1087
|
.showRegistry() Show navigator registry (classes + roles)
|
|
1013
1088
|
.showStrategies() Show registry + strategy mapping from last run
|
|
1089
|
+
.showPrescribed(id?) Show prescribed-generated cards and blocked/support details from last run
|
|
1014
1090
|
.listRuns() List all captured runs in table format
|
|
1015
1091
|
.export() Export run history as JSON for bug reports
|
|
1016
1092
|
.clear() Clear run history
|
|
@@ -1034,6 +1110,44 @@ __export(CompositeGenerator_exports, {
|
|
|
1034
1110
|
AggregationMode: () => AggregationMode,
|
|
1035
1111
|
default: () => CompositeGenerator
|
|
1036
1112
|
});
|
|
1113
|
+
function mergeHints(allHints) {
|
|
1114
|
+
const defined = allHints.filter((h) => h !== void 0);
|
|
1115
|
+
if (defined.length === 0) return void 0;
|
|
1116
|
+
const merged = {};
|
|
1117
|
+
const boostTags = {};
|
|
1118
|
+
for (const hints of defined) {
|
|
1119
|
+
for (const [pattern, factor] of Object.entries(hints.boostTags ?? {})) {
|
|
1120
|
+
boostTags[pattern] = (boostTags[pattern] ?? 1) * factor;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
if (Object.keys(boostTags).length > 0) {
|
|
1124
|
+
merged.boostTags = boostTags;
|
|
1125
|
+
}
|
|
1126
|
+
const boostCards = {};
|
|
1127
|
+
for (const hints of defined) {
|
|
1128
|
+
for (const [pattern, factor] of Object.entries(hints.boostCards ?? {})) {
|
|
1129
|
+
boostCards[pattern] = (boostCards[pattern] ?? 1) * factor;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
if (Object.keys(boostCards).length > 0) {
|
|
1133
|
+
merged.boostCards = boostCards;
|
|
1134
|
+
}
|
|
1135
|
+
const concatUnique = (field) => {
|
|
1136
|
+
const values = defined.flatMap((h) => h[field] ?? []);
|
|
1137
|
+
if (values.length > 0) {
|
|
1138
|
+
merged[field] = [...new Set(values)];
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
concatUnique("requireTags");
|
|
1142
|
+
concatUnique("requireCards");
|
|
1143
|
+
concatUnique("excludeTags");
|
|
1144
|
+
concatUnique("excludeCards");
|
|
1145
|
+
const labels = defined.map((h) => h._label).filter(Boolean);
|
|
1146
|
+
if (labels.length > 0) {
|
|
1147
|
+
merged._label = labels.join("; ");
|
|
1148
|
+
}
|
|
1149
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
1150
|
+
}
|
|
1037
1151
|
var AggregationMode, DEFAULT_AGGREGATION_MODE, FREQUENCY_BOOST_FACTOR, CompositeGenerator;
|
|
1038
1152
|
var init_CompositeGenerator = __esm({
|
|
1039
1153
|
"src/core/navigators/generators/CompositeGenerator.ts"() {
|
|
@@ -1097,17 +1211,18 @@ var init_CompositeGenerator = __esm({
|
|
|
1097
1211
|
this.generators.map((g) => g.getWeightedCards(limit, context))
|
|
1098
1212
|
);
|
|
1099
1213
|
const generatorSummaries = [];
|
|
1100
|
-
results.forEach((
|
|
1214
|
+
results.forEach((result, index) => {
|
|
1215
|
+
const cards2 = result.cards;
|
|
1101
1216
|
const gen = this.generators[index];
|
|
1102
1217
|
const genName = gen.name || `Generator ${index}`;
|
|
1103
|
-
const newCards =
|
|
1104
|
-
const reviewCards =
|
|
1105
|
-
if (
|
|
1106
|
-
const topScore = Math.max(...
|
|
1218
|
+
const newCards = cards2.filter((c) => c.provenance[0]?.reason?.includes("new card"));
|
|
1219
|
+
const reviewCards = cards2.filter((c) => c.provenance[0]?.reason?.includes("review"));
|
|
1220
|
+
if (cards2.length > 0) {
|
|
1221
|
+
const topScore = Math.max(...cards2.map((c) => c.score)).toFixed(2);
|
|
1107
1222
|
const parts = [];
|
|
1108
1223
|
if (newCards.length > 0) parts.push(`${newCards.length} new`);
|
|
1109
1224
|
if (reviewCards.length > 0) parts.push(`${reviewCards.length} reviews`);
|
|
1110
|
-
const breakdown = parts.length > 0 ? parts.join(", ") : `${
|
|
1225
|
+
const breakdown = parts.length > 0 ? parts.join(", ") : `${cards2.length} cards`;
|
|
1111
1226
|
generatorSummaries.push(`${genName}: ${breakdown} (top: ${topScore})`);
|
|
1112
1227
|
} else {
|
|
1113
1228
|
generatorSummaries.push(`${genName}: 0 cards`);
|
|
@@ -1115,7 +1230,8 @@ var init_CompositeGenerator = __esm({
|
|
|
1115
1230
|
});
|
|
1116
1231
|
logger.info(`[Composite] Generator breakdown: ${generatorSummaries.join(" | ")}`);
|
|
1117
1232
|
const byCardId = /* @__PURE__ */ new Map();
|
|
1118
|
-
results.forEach((
|
|
1233
|
+
results.forEach((result, index) => {
|
|
1234
|
+
const cards2 = result.cards;
|
|
1119
1235
|
const gen = this.generators[index];
|
|
1120
1236
|
let weight = gen.learnable?.weight ?? 1;
|
|
1121
1237
|
let deviation;
|
|
@@ -1126,7 +1242,7 @@ var init_CompositeGenerator = __esm({
|
|
|
1126
1242
|
deviation = context.orchestration.getDeviation(strategyId);
|
|
1127
1243
|
}
|
|
1128
1244
|
}
|
|
1129
|
-
for (const card of
|
|
1245
|
+
for (const card of cards2) {
|
|
1130
1246
|
if (card.provenance.length > 0) {
|
|
1131
1247
|
card.provenance[0].effectiveWeight = weight;
|
|
1132
1248
|
card.provenance[0].deviation = deviation;
|
|
@@ -1138,15 +1254,15 @@ var init_CompositeGenerator = __esm({
|
|
|
1138
1254
|
});
|
|
1139
1255
|
const merged = [];
|
|
1140
1256
|
for (const [, items] of byCardId) {
|
|
1141
|
-
const
|
|
1257
|
+
const cards2 = items.map((i) => i.card);
|
|
1142
1258
|
const aggregatedScore = this.aggregateScores(items);
|
|
1143
1259
|
const finalScore = Math.min(1, aggregatedScore);
|
|
1144
|
-
const mergedProvenance =
|
|
1145
|
-
const initialScore =
|
|
1260
|
+
const mergedProvenance = cards2.flatMap((c) => c.provenance);
|
|
1261
|
+
const initialScore = cards2[0].score;
|
|
1146
1262
|
const action = finalScore > initialScore ? "boosted" : finalScore < initialScore ? "penalized" : "passed";
|
|
1147
1263
|
const reason = this.buildAggregationReason(items, finalScore);
|
|
1148
1264
|
merged.push({
|
|
1149
|
-
...
|
|
1265
|
+
...cards2[0],
|
|
1150
1266
|
score: finalScore,
|
|
1151
1267
|
provenance: [
|
|
1152
1268
|
...mergedProvenance,
|
|
@@ -1161,7 +1277,9 @@ var init_CompositeGenerator = __esm({
|
|
|
1161
1277
|
]
|
|
1162
1278
|
});
|
|
1163
1279
|
}
|
|
1164
|
-
|
|
1280
|
+
const cards = merged.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
1281
|
+
const hints = mergeHints(results.map((result) => result.hints));
|
|
1282
|
+
return { cards, hints };
|
|
1165
1283
|
}
|
|
1166
1284
|
/**
|
|
1167
1285
|
* Build human-readable reason for score aggregation.
|
|
@@ -1292,16 +1410,16 @@ var init_elo = __esm({
|
|
|
1292
1410
|
};
|
|
1293
1411
|
});
|
|
1294
1412
|
scored.sort((a, b) => b.score - a.score);
|
|
1295
|
-
const
|
|
1296
|
-
if (
|
|
1297
|
-
const topScores =
|
|
1413
|
+
const cards = scored.slice(0, limit);
|
|
1414
|
+
if (cards.length > 0) {
|
|
1415
|
+
const topScores = cards.slice(0, 3).map((c) => c.score.toFixed(2)).join(", ");
|
|
1298
1416
|
logger.info(
|
|
1299
|
-
`[ELO] Course ${this.course.getCourseID()}: ${
|
|
1417
|
+
`[ELO] Course ${this.course.getCourseID()}: ${cards.length} new cards (top scores: ${topScores})`
|
|
1300
1418
|
);
|
|
1301
1419
|
} else {
|
|
1302
1420
|
logger.info(`[ELO] Course ${this.course.getCourseID()}: No new cards available`);
|
|
1303
1421
|
}
|
|
1304
|
-
return
|
|
1422
|
+
return { cards };
|
|
1305
1423
|
}
|
|
1306
1424
|
};
|
|
1307
1425
|
}
|
|
@@ -1338,7 +1456,7 @@ function matchesTagPattern(tag, pattern) {
|
|
|
1338
1456
|
function pickTopByScore(cards, limit) {
|
|
1339
1457
|
return [...cards].sort((a, b) => b.score - a.score || a.cardId.localeCompare(b.cardId)).slice(0, limit);
|
|
1340
1458
|
}
|
|
1341
|
-
var DEFAULT_FRESHNESS_WINDOW, DEFAULT_MAX_DIRECT_PER_RUN, DEFAULT_MAX_SUPPORT_PER_RUN, DEFAULT_HIERARCHY_DEPTH, DEFAULT_MIN_COUNT, BASE_TARGET_SCORE, BASE_SUPPORT_SCORE, MAX_TARGET_MULTIPLIER, MAX_SUPPORT_MULTIPLIER, LOCKED_TAG_PREFIXES, LESSON_GATE_PENALTY_TAG_HINT, PrescribedCardsGenerator;
|
|
1459
|
+
var DEFAULT_FRESHNESS_WINDOW, DEFAULT_MAX_DIRECT_PER_RUN, DEFAULT_MAX_SUPPORT_PER_RUN, DEFAULT_HIERARCHY_DEPTH, DEFAULT_MIN_COUNT, BASE_TARGET_SCORE, BASE_SUPPORT_SCORE, MAX_TARGET_MULTIPLIER, MAX_SUPPORT_MULTIPLIER, LOCKED_TAG_PREFIXES, LESSON_GATE_PENALTY_TAG_HINT, PRESCRIBED_DEBUG_VERSION, PrescribedCardsGenerator;
|
|
1342
1460
|
var init_prescribed = __esm({
|
|
1343
1461
|
"src/core/navigators/generators/prescribed.ts"() {
|
|
1344
1462
|
"use strict";
|
|
@@ -1355,6 +1473,7 @@ var init_prescribed = __esm({
|
|
|
1355
1473
|
MAX_SUPPORT_MULTIPLIER = 4;
|
|
1356
1474
|
LOCKED_TAG_PREFIXES = ["concept:"];
|
|
1357
1475
|
LESSON_GATE_PENALTY_TAG_HINT = "concept:";
|
|
1476
|
+
PRESCRIBED_DEBUG_VERSION = "testversion-prescribed-v2";
|
|
1358
1477
|
PrescribedCardsGenerator = class extends ContentNavigator {
|
|
1359
1478
|
name;
|
|
1360
1479
|
config;
|
|
@@ -1371,7 +1490,7 @@ var init_prescribed = __esm({
|
|
|
1371
1490
|
}
|
|
1372
1491
|
async getWeightedCards(limit, context) {
|
|
1373
1492
|
if (this.config.groups.length === 0 || limit <= 0) {
|
|
1374
|
-
return [];
|
|
1493
|
+
return { cards: [] };
|
|
1375
1494
|
}
|
|
1376
1495
|
const courseId = this.course.getCourseID();
|
|
1377
1496
|
const activeCards = await this.user.getActiveCards();
|
|
@@ -1396,6 +1515,7 @@ var init_prescribed = __esm({
|
|
|
1396
1515
|
};
|
|
1397
1516
|
const emitted = [];
|
|
1398
1517
|
const emittedIds = /* @__PURE__ */ new Set();
|
|
1518
|
+
const groupRuntimes = [];
|
|
1399
1519
|
for (const group of this.config.groups) {
|
|
1400
1520
|
const runtime = this.buildGroupRuntimeState({
|
|
1401
1521
|
group,
|
|
@@ -1407,6 +1527,7 @@ var init_prescribed = __esm({
|
|
|
1407
1527
|
userTagElo,
|
|
1408
1528
|
userGlobalElo
|
|
1409
1529
|
});
|
|
1530
|
+
groupRuntimes.push(runtime);
|
|
1410
1531
|
nextState.groups[group.id] = this.buildNextGroupState(runtime, progress.groups[group.id]);
|
|
1411
1532
|
const directCards = this.buildDirectTargetCards(
|
|
1412
1533
|
runtime,
|
|
@@ -1420,12 +1541,17 @@ var init_prescribed = __esm({
|
|
|
1420
1541
|
);
|
|
1421
1542
|
emitted.push(...directCards, ...supportCards);
|
|
1422
1543
|
}
|
|
1544
|
+
const hintSummary = this.buildSupportHintSummary(groupRuntimes);
|
|
1545
|
+
const hints = Object.keys(hintSummary.boostTags).length > 0 ? {
|
|
1546
|
+
boostTags: hintSummary.boostTags,
|
|
1547
|
+
_label: `prescribed-support (${hintSummary.supportTags.length} tags; blocked=${hintSummary.blockedTargetIds.length}; testversion=${PRESCRIBED_DEBUG_VERSION})`
|
|
1548
|
+
} : void 0;
|
|
1423
1549
|
if (emitted.length === 0) {
|
|
1424
1550
|
logger.debug("[Prescribed] No prescribed targets/support emitted this run");
|
|
1425
1551
|
await this.putStrategyState(nextState).catch((e) => {
|
|
1426
1552
|
logger.debug(`[Prescribed] Failed to persist empty-state update: ${e}`);
|
|
1427
1553
|
});
|
|
1428
|
-
return [];
|
|
1554
|
+
return hints ? { cards: [], hints } : { cards: [] };
|
|
1429
1555
|
}
|
|
1430
1556
|
const finalCards = pickTopByScore(emitted, limit);
|
|
1431
1557
|
const surfacedByGroup = /* @__PURE__ */ new Map();
|
|
@@ -1456,7 +1582,27 @@ var init_prescribed = __esm({
|
|
|
1456
1582
|
logger.info(
|
|
1457
1583
|
`[Prescribed] Emitting ${finalCards.length} cards (${finalCards.filter((c) => c.provenance[0]?.reason.includes("mode=target")).length} target, ${finalCards.filter((c) => c.provenance[0]?.reason.includes("mode=support")).length} support)`
|
|
1458
1584
|
);
|
|
1459
|
-
return finalCards;
|
|
1585
|
+
return hints ? { cards: finalCards, hints } : { cards: finalCards };
|
|
1586
|
+
}
|
|
1587
|
+
buildSupportHintSummary(groupRuntimes) {
|
|
1588
|
+
const boostTags = {};
|
|
1589
|
+
const blockedTargetIds = /* @__PURE__ */ new Set();
|
|
1590
|
+
const supportTags = /* @__PURE__ */ new Set();
|
|
1591
|
+
for (const runtime of groupRuntimes) {
|
|
1592
|
+
if (runtime.blockedTargets.length === 0 || runtime.supportTags.length === 0) {
|
|
1593
|
+
continue;
|
|
1594
|
+
}
|
|
1595
|
+
runtime.blockedTargets.forEach((cardId) => blockedTargetIds.add(cardId));
|
|
1596
|
+
for (const tag of runtime.supportTags) {
|
|
1597
|
+
supportTags.add(tag);
|
|
1598
|
+
boostTags[tag] = (boostTags[tag] ?? 1) * runtime.supportMultiplier;
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
return {
|
|
1602
|
+
boostTags,
|
|
1603
|
+
blockedTargetIds: [...blockedTargetIds].sort(),
|
|
1604
|
+
supportTags: [...supportTags].sort()
|
|
1605
|
+
};
|
|
1460
1606
|
}
|
|
1461
1607
|
parseConfig(serializedData) {
|
|
1462
1608
|
try {
|
|
@@ -1539,7 +1685,22 @@ var init_prescribed = __esm({
|
|
|
1539
1685
|
group.hierarchyWalk?.enabled !== false,
|
|
1540
1686
|
group.hierarchyWalk?.maxDepth ?? DEFAULT_HIERARCHY_DEPTH
|
|
1541
1687
|
);
|
|
1542
|
-
|
|
1688
|
+
const introTags = tags.filter((tag) => tag.startsWith("gpc:intro:"));
|
|
1689
|
+
const exposeTags = new Set(tags.filter((tag) => tag.startsWith("gpc:expose:")));
|
|
1690
|
+
for (const introTag of introTags) {
|
|
1691
|
+
const suffix = introTag.slice("gpc:intro:".length);
|
|
1692
|
+
if (suffix) {
|
|
1693
|
+
exposeTags.add(`gpc:expose:${suffix}`);
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
const unmetExposeTags = [...exposeTags].filter((tag) => {
|
|
1697
|
+
const tagElo = userTagElo[tag];
|
|
1698
|
+
return !tagElo || tagElo.count < DEFAULT_MIN_COUNT;
|
|
1699
|
+
});
|
|
1700
|
+
if (unmetExposeTags.length > 0) {
|
|
1701
|
+
unmetExposeTags.forEach((tag) => supportTags.add(tag));
|
|
1702
|
+
}
|
|
1703
|
+
if (resolution.blocked || unmetExposeTags.length > 0) {
|
|
1543
1704
|
blockedTargets.push(cardId);
|
|
1544
1705
|
resolution.supportTags.forEach((t) => supportTags.add(t));
|
|
1545
1706
|
} else {
|
|
@@ -1569,7 +1730,8 @@ var init_prescribed = __esm({
|
|
|
1569
1730
|
supportCandidates,
|
|
1570
1731
|
supportTags: [...supportTags],
|
|
1571
1732
|
pressureMultiplier,
|
|
1572
|
-
supportMultiplier
|
|
1733
|
+
supportMultiplier,
|
|
1734
|
+
debugVersion: PRESCRIBED_DEBUG_VERSION
|
|
1573
1735
|
};
|
|
1574
1736
|
}
|
|
1575
1737
|
buildNextGroupState(runtime, prior) {
|
|
@@ -1577,6 +1739,7 @@ var init_prescribed = __esm({
|
|
|
1577
1739
|
const surfacedThisRun = false;
|
|
1578
1740
|
return {
|
|
1579
1741
|
encounteredCardIds: [...runtime.encounteredTargets].sort(),
|
|
1742
|
+
pendingTargetIds: [...runtime.pendingTargets].sort(),
|
|
1580
1743
|
lastSurfacedAt: prior?.lastSurfacedAt ?? null,
|
|
1581
1744
|
sessionsSinceSurfaced: surfacedThisRun ? 0 : carriedSessions + 1,
|
|
1582
1745
|
lastSupportAt: prior?.lastSupportAt ?? null,
|
|
@@ -1601,7 +1764,7 @@ var init_prescribed = __esm({
|
|
|
1601
1764
|
strategyId: this.strategyId || "NAVIGATION_STRATEGY-prescribed",
|
|
1602
1765
|
action: "generated",
|
|
1603
1766
|
score: BASE_TARGET_SCORE * runtime.pressureMultiplier,
|
|
1604
|
-
reason: `mode=target;group=${runtime.group.id};pending=${runtime.pendingTargets.length};surfaceable=${runtime.surfaceableTargets.length};blocked=${runtime.blockedTargets.length};multiplier=${runtime.pressureMultiplier.toFixed(2)}`
|
|
1767
|
+
reason: `mode=target;group=${runtime.group.id};pending=${runtime.pendingTargets.length};surfaceable=${runtime.surfaceableTargets.length};blocked=${runtime.blockedTargets.length};blockedTargets=${runtime.blockedTargets.join("|") || "none"};supportTags=${runtime.supportTags.join("|") || "none"};multiplier=${runtime.pressureMultiplier.toFixed(2)};testversion=${runtime.debugVersion}`
|
|
1605
1768
|
}
|
|
1606
1769
|
]
|
|
1607
1770
|
});
|
|
@@ -1628,7 +1791,7 @@ var init_prescribed = __esm({
|
|
|
1628
1791
|
strategyId: this.strategyId || "NAVIGATION_STRATEGY-prescribed",
|
|
1629
1792
|
action: "generated",
|
|
1630
1793
|
score: BASE_SUPPORT_SCORE * runtime.supportMultiplier,
|
|
1631
|
-
reason: `mode=support;group=${runtime.group.id};blocked=${runtime.blockedTargets.length};supportTags=${runtime.supportTags.join("|") || "none"};multiplier=${runtime.supportMultiplier.toFixed(2)}`
|
|
1794
|
+
reason: `mode=support;group=${runtime.group.id};pending=${runtime.pendingTargets.length};blocked=${runtime.blockedTargets.length};blockedTargets=${runtime.blockedTargets.join("|") || "none"};supportCard=${cardId};supportTags=${runtime.supportTags.join("|") || "none"};multiplier=${runtime.supportMultiplier.toFixed(2)};testversion=${runtime.debugVersion}`
|
|
1632
1795
|
}
|
|
1633
1796
|
]
|
|
1634
1797
|
});
|
|
@@ -1658,35 +1821,43 @@ var init_prescribed = __esm({
|
|
|
1658
1821
|
return [...candidates];
|
|
1659
1822
|
}
|
|
1660
1823
|
resolveBlockedSupportTags(targetTags, hierarchyConfigs, userTagElo, userGlobalElo, hierarchyWalkEnabled, maxDepth) {
|
|
1661
|
-
if (!hierarchyWalkEnabled || targetTags.length === 0 || hierarchyConfigs.length === 0) {
|
|
1662
|
-
return {
|
|
1663
|
-
blocked: false,
|
|
1664
|
-
supportTags: []
|
|
1665
|
-
};
|
|
1666
|
-
}
|
|
1667
1824
|
const supportTags = /* @__PURE__ */ new Set();
|
|
1668
1825
|
let blocked = false;
|
|
1669
1826
|
for (const targetTag of targetTags) {
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
)
|
|
1676
|
-
|
|
1677
|
-
|
|
1827
|
+
const prereqSets = hierarchyConfigs.map((hierarchy) => hierarchy.prerequisites[targetTag]).filter((prereqs) => Array.isArray(prereqs) && prereqs.length > 0);
|
|
1828
|
+
if (prereqSets.length === 0) {
|
|
1829
|
+
continue;
|
|
1830
|
+
}
|
|
1831
|
+
const tagBlocked = prereqSets.some(
|
|
1832
|
+
(prereqs) => prereqs.some((pr) => !this.isPrerequisiteMet(pr, userTagElo[pr.tag], userGlobalElo))
|
|
1833
|
+
);
|
|
1834
|
+
if (!tagBlocked) {
|
|
1835
|
+
continue;
|
|
1836
|
+
}
|
|
1837
|
+
blocked = true;
|
|
1838
|
+
if (!hierarchyWalkEnabled) {
|
|
1839
|
+
for (const prereqs of prereqSets) {
|
|
1840
|
+
for (const prereq of prereqs) {
|
|
1841
|
+
if (!this.isPrerequisiteMet(prereq, userTagElo[prereq.tag], userGlobalElo)) {
|
|
1842
|
+
supportTags.add(prereq.tag);
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1678
1845
|
}
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1846
|
+
continue;
|
|
1847
|
+
}
|
|
1848
|
+
for (const prereqs of prereqSets) {
|
|
1849
|
+
for (const prereq of prereqs) {
|
|
1850
|
+
if (!this.isPrerequisiteMet(prereq, userTagElo[prereq.tag], userGlobalElo)) {
|
|
1851
|
+
this.collectSupportTagsRecursive(
|
|
1852
|
+
prereq.tag,
|
|
1853
|
+
hierarchyConfigs,
|
|
1854
|
+
userTagElo,
|
|
1855
|
+
userGlobalElo,
|
|
1856
|
+
maxDepth,
|
|
1857
|
+
/* @__PURE__ */ new Set(),
|
|
1858
|
+
supportTags
|
|
1859
|
+
);
|
|
1860
|
+
}
|
|
1690
1861
|
}
|
|
1691
1862
|
}
|
|
1692
1863
|
}
|
|
@@ -1841,7 +2012,7 @@ var init_srs = __esm({
|
|
|
1841
2012
|
]
|
|
1842
2013
|
};
|
|
1843
2014
|
});
|
|
1844
|
-
return scored.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
2015
|
+
return { cards: scored.sort((a, b) => b.score - a.score).slice(0, limit) };
|
|
1845
2016
|
}
|
|
1846
2017
|
/**
|
|
1847
2018
|
* Compute backlog pressure based on number of due reviews.
|
|
@@ -2894,1725 +3065,138 @@ __export(userGoalStub_exports, {
|
|
|
2894
3065
|
var USER_GOAL_NAVIGATOR_STUB;
|
|
2895
3066
|
var init_userGoalStub = __esm({
|
|
2896
3067
|
"src/core/navigators/filters/userGoalStub.ts"() {
|
|
2897
|
-
"use strict";
|
|
2898
|
-
USER_GOAL_NAVIGATOR_STUB = true;
|
|
2899
|
-
}
|
|
2900
|
-
});
|
|
2901
|
-
|
|
2902
|
-
// import("./filters/**/*") in src/core/navigators/index.ts
|
|
2903
|
-
var globImport_filters;
|
|
2904
|
-
var init_2 = __esm({
|
|
2905
|
-
'import("./filters/**/*") in src/core/navigators/index.ts'() {
|
|
2906
|
-
globImport_filters = __glob({
|
|
2907
|
-
"./filters/WeightedFilter.ts": () => Promise.resolve().then(() => (init_WeightedFilter(), WeightedFilter_exports)),
|
|
2908
|
-
"./filters/eloDistance.ts": () => Promise.resolve().then(() => (init_eloDistance(), eloDistance_exports)),
|
|
2909
|
-
"./filters/hierarchyDefinition.ts": () => Promise.resolve().then(() => (init_hierarchyDefinition(), hierarchyDefinition_exports)),
|
|
2910
|
-
"./filters/index.ts": () => Promise.resolve().then(() => (init_filters(), filters_exports)),
|
|
2911
|
-
"./filters/inferredPreferenceStub.ts": () => Promise.resolve().then(() => (init_inferredPreferenceStub(), inferredPreferenceStub_exports)),
|
|
2912
|
-
"./filters/interferenceMitigator.ts": () => Promise.resolve().then(() => (init_interferenceMitigator(), interferenceMitigator_exports)),
|
|
2913
|
-
"./filters/relativePriority.ts": () => Promise.resolve().then(() => (init_relativePriority(), relativePriority_exports)),
|
|
2914
|
-
"./filters/types.ts": () => Promise.resolve().then(() => (init_types2(), types_exports2)),
|
|
2915
|
-
"./filters/userGoalStub.ts": () => Promise.resolve().then(() => (init_userGoalStub(), userGoalStub_exports)),
|
|
2916
|
-
"./filters/userTagPreference.ts": () => Promise.resolve().then(() => (init_userTagPreference(), userTagPreference_exports))
|
|
2917
|
-
});
|
|
2918
|
-
}
|
|
2919
|
-
});
|
|
2920
|
-
|
|
2921
|
-
// src/core/orchestration/gradient.ts
|
|
2922
|
-
var init_gradient = __esm({
|
|
2923
|
-
"src/core/orchestration/gradient.ts"() {
|
|
2924
|
-
"use strict";
|
|
2925
|
-
init_logger();
|
|
2926
|
-
}
|
|
2927
|
-
});
|
|
2928
|
-
|
|
2929
|
-
// src/core/orchestration/learning.ts
|
|
2930
|
-
var init_learning = __esm({
|
|
2931
|
-
"src/core/orchestration/learning.ts"() {
|
|
2932
|
-
"use strict";
|
|
2933
|
-
init_contentNavigationStrategy();
|
|
2934
|
-
init_types_legacy();
|
|
2935
|
-
init_logger();
|
|
2936
|
-
}
|
|
2937
|
-
});
|
|
2938
|
-
|
|
2939
|
-
// src/core/orchestration/signal.ts
|
|
2940
|
-
var init_signal = __esm({
|
|
2941
|
-
"src/core/orchestration/signal.ts"() {
|
|
2942
|
-
"use strict";
|
|
2943
|
-
}
|
|
2944
|
-
});
|
|
2945
|
-
|
|
2946
|
-
// src/core/orchestration/recording.ts
|
|
2947
|
-
var init_recording = __esm({
|
|
2948
|
-
"src/core/orchestration/recording.ts"() {
|
|
2949
|
-
"use strict";
|
|
2950
|
-
init_signal();
|
|
2951
|
-
init_types_legacy();
|
|
2952
|
-
init_logger();
|
|
2953
|
-
}
|
|
2954
|
-
});
|
|
2955
|
-
|
|
2956
|
-
// src/core/orchestration/index.ts
|
|
2957
|
-
function fnv1a(str) {
|
|
2958
|
-
let hash = 2166136261;
|
|
2959
|
-
for (let i = 0; i < str.length; i++) {
|
|
2960
|
-
hash ^= str.charCodeAt(i);
|
|
2961
|
-
hash = Math.imul(hash, 16777619);
|
|
2962
|
-
}
|
|
2963
|
-
return hash >>> 0;
|
|
2964
|
-
}
|
|
2965
|
-
function computeDeviation(userId, strategyId, salt) {
|
|
2966
|
-
const input = `${userId}:${strategyId}:${salt}`;
|
|
2967
|
-
const hash = fnv1a(input);
|
|
2968
|
-
const normalized = hash / 4294967296;
|
|
2969
|
-
return normalized * 2 - 1;
|
|
2970
|
-
}
|
|
2971
|
-
function computeSpread(confidence) {
|
|
2972
|
-
const clampedConfidence = Math.max(0, Math.min(1, confidence));
|
|
2973
|
-
return MAX_SPREAD - clampedConfidence * (MAX_SPREAD - MIN_SPREAD);
|
|
2974
|
-
}
|
|
2975
|
-
function computeEffectiveWeight(learnable, userId, strategyId, salt) {
|
|
2976
|
-
const deviation = computeDeviation(userId, strategyId, salt);
|
|
2977
|
-
const spread = computeSpread(learnable.confidence);
|
|
2978
|
-
const adjustment = deviation * spread * learnable.weight;
|
|
2979
|
-
const effective = learnable.weight + adjustment;
|
|
2980
|
-
return Math.max(MIN_WEIGHT, Math.min(MAX_WEIGHT, effective));
|
|
2981
|
-
}
|
|
2982
|
-
async function createOrchestrationContext(user, course) {
|
|
2983
|
-
let courseConfig;
|
|
2984
|
-
try {
|
|
2985
|
-
courseConfig = await course.getCourseConfig();
|
|
2986
|
-
} catch (e) {
|
|
2987
|
-
logger.error(`[Orchestration] Failed to load course config: ${e}`);
|
|
2988
|
-
courseConfig = {
|
|
2989
|
-
name: "Unknown",
|
|
2990
|
-
description: "",
|
|
2991
|
-
public: false,
|
|
2992
|
-
deleted: false,
|
|
2993
|
-
creator: "",
|
|
2994
|
-
admins: [],
|
|
2995
|
-
moderators: [],
|
|
2996
|
-
dataShapes: [],
|
|
2997
|
-
questionTypes: [],
|
|
2998
|
-
orchestration: { salt: "default" }
|
|
2999
|
-
};
|
|
3000
|
-
}
|
|
3001
|
-
const userId = user.getUsername();
|
|
3002
|
-
const salt = courseConfig.orchestration?.salt || "default_salt";
|
|
3003
|
-
return {
|
|
3004
|
-
user,
|
|
3005
|
-
course,
|
|
3006
|
-
userId,
|
|
3007
|
-
courseConfig,
|
|
3008
|
-
getEffectiveWeight(strategyId, learnable) {
|
|
3009
|
-
return computeEffectiveWeight(learnable, userId, strategyId, salt);
|
|
3010
|
-
},
|
|
3011
|
-
getDeviation(strategyId) {
|
|
3012
|
-
return computeDeviation(userId, strategyId, salt);
|
|
3013
|
-
}
|
|
3014
|
-
};
|
|
3015
|
-
}
|
|
3016
|
-
var MIN_SPREAD, MAX_SPREAD, MIN_WEIGHT, MAX_WEIGHT;
|
|
3017
|
-
var init_orchestration = __esm({
|
|
3018
|
-
"src/core/orchestration/index.ts"() {
|
|
3019
|
-
"use strict";
|
|
3020
|
-
init_logger();
|
|
3021
|
-
init_gradient();
|
|
3022
|
-
init_learning();
|
|
3023
|
-
init_signal();
|
|
3024
|
-
init_recording();
|
|
3025
|
-
MIN_SPREAD = 0.1;
|
|
3026
|
-
MAX_SPREAD = 0.5;
|
|
3027
|
-
MIN_WEIGHT = 0.1;
|
|
3028
|
-
MAX_WEIGHT = 3;
|
|
3029
|
-
}
|
|
3030
|
-
});
|
|
3031
|
-
|
|
3032
|
-
// src/core/util/index.ts
|
|
3033
|
-
function getCardHistoryID(courseID, cardID) {
|
|
3034
|
-
return `${DocTypePrefixes["CARDRECORD" /* CARDRECORD */]}-${courseID}-${cardID}`;
|
|
3035
|
-
}
|
|
3036
|
-
var init_util = __esm({
|
|
3037
|
-
"src/core/util/index.ts"() {
|
|
3038
|
-
"use strict";
|
|
3039
|
-
init_types_legacy();
|
|
3040
|
-
}
|
|
3041
|
-
});
|
|
3042
|
-
|
|
3043
|
-
// src/study/SpacedRepetition.ts
|
|
3044
|
-
var import_moment2, import_common8, duration;
|
|
3045
|
-
var init_SpacedRepetition = __esm({
|
|
3046
|
-
"src/study/SpacedRepetition.ts"() {
|
|
3047
|
-
"use strict";
|
|
3048
|
-
init_util();
|
|
3049
|
-
import_moment2 = __toESM(require("moment"), 1);
|
|
3050
|
-
import_common8 = require("@vue-skuilder/common");
|
|
3051
|
-
init_logger();
|
|
3052
|
-
duration = import_moment2.default.duration;
|
|
3053
|
-
}
|
|
3054
|
-
});
|
|
3055
|
-
|
|
3056
|
-
// src/study/services/SrsService.ts
|
|
3057
|
-
var import_moment3;
|
|
3058
|
-
var init_SrsService = __esm({
|
|
3059
|
-
"src/study/services/SrsService.ts"() {
|
|
3060
|
-
"use strict";
|
|
3061
|
-
import_moment3 = __toESM(require("moment"), 1);
|
|
3062
|
-
init_couch();
|
|
3063
|
-
init_SpacedRepetition();
|
|
3064
|
-
init_logger();
|
|
3065
|
-
}
|
|
3066
|
-
});
|
|
3067
|
-
|
|
3068
|
-
// src/study/services/EloService.ts
|
|
3069
|
-
var import_common9;
|
|
3070
|
-
var init_EloService = __esm({
|
|
3071
|
-
"src/study/services/EloService.ts"() {
|
|
3072
|
-
"use strict";
|
|
3073
|
-
import_common9 = require("@vue-skuilder/common");
|
|
3074
|
-
init_logger();
|
|
3075
|
-
}
|
|
3076
|
-
});
|
|
3077
|
-
|
|
3078
|
-
// src/study/services/ResponseProcessor.ts
|
|
3079
|
-
var import_common10;
|
|
3080
|
-
var init_ResponseProcessor = __esm({
|
|
3081
|
-
"src/study/services/ResponseProcessor.ts"() {
|
|
3082
|
-
"use strict";
|
|
3083
|
-
init_core();
|
|
3084
|
-
init_logger();
|
|
3085
|
-
import_common10 = require("@vue-skuilder/common");
|
|
3086
|
-
}
|
|
3087
|
-
});
|
|
3088
|
-
|
|
3089
|
-
// src/study/services/CardHydrationService.ts
|
|
3090
|
-
var import_common11;
|
|
3091
|
-
var init_CardHydrationService = __esm({
|
|
3092
|
-
"src/study/services/CardHydrationService.ts"() {
|
|
3093
|
-
"use strict";
|
|
3094
|
-
import_common11 = require("@vue-skuilder/common");
|
|
3095
|
-
init_logger();
|
|
3096
|
-
}
|
|
3097
|
-
});
|
|
3098
|
-
|
|
3099
|
-
// src/study/ItemQueue.ts
|
|
3100
|
-
var init_ItemQueue = __esm({
|
|
3101
|
-
"src/study/ItemQueue.ts"() {
|
|
3102
|
-
"use strict";
|
|
3103
|
-
}
|
|
3104
|
-
});
|
|
3105
|
-
|
|
3106
|
-
// src/util/packer/types.ts
|
|
3107
|
-
var init_types3 = __esm({
|
|
3108
|
-
"src/util/packer/types.ts"() {
|
|
3109
|
-
"use strict";
|
|
3110
|
-
}
|
|
3111
|
-
});
|
|
3112
|
-
|
|
3113
|
-
// src/util/packer/CouchDBToStaticPacker.ts
|
|
3114
|
-
var init_CouchDBToStaticPacker = __esm({
|
|
3115
|
-
"src/util/packer/CouchDBToStaticPacker.ts"() {
|
|
3116
|
-
"use strict";
|
|
3117
|
-
init_types_legacy();
|
|
3118
|
-
init_logger();
|
|
3119
|
-
}
|
|
3120
|
-
});
|
|
3121
|
-
|
|
3122
|
-
// src/util/packer/index.ts
|
|
3123
|
-
var init_packer = __esm({
|
|
3124
|
-
"src/util/packer/index.ts"() {
|
|
3125
|
-
"use strict";
|
|
3126
|
-
init_types3();
|
|
3127
|
-
init_CouchDBToStaticPacker();
|
|
3128
|
-
}
|
|
3129
|
-
});
|
|
3130
|
-
|
|
3131
|
-
// src/util/migrator/types.ts
|
|
3132
|
-
var DEFAULT_MIGRATION_OPTIONS;
|
|
3133
|
-
var init_types4 = __esm({
|
|
3134
|
-
"src/util/migrator/types.ts"() {
|
|
3135
|
-
"use strict";
|
|
3136
|
-
DEFAULT_MIGRATION_OPTIONS = {
|
|
3137
|
-
chunkBatchSize: 100,
|
|
3138
|
-
validateRoundTrip: false,
|
|
3139
|
-
cleanupOnFailure: true,
|
|
3140
|
-
timeout: 3e5
|
|
3141
|
-
// 5 minutes
|
|
3142
|
-
};
|
|
3143
|
-
}
|
|
3144
|
-
});
|
|
3145
|
-
|
|
3146
|
-
// src/util/migrator/FileSystemAdapter.ts
|
|
3147
|
-
var FileSystemError;
|
|
3148
|
-
var init_FileSystemAdapter = __esm({
|
|
3149
|
-
"src/util/migrator/FileSystemAdapter.ts"() {
|
|
3150
|
-
"use strict";
|
|
3151
|
-
FileSystemError = class extends Error {
|
|
3152
|
-
constructor(message, operation, filePath, cause) {
|
|
3153
|
-
super(message);
|
|
3154
|
-
this.operation = operation;
|
|
3155
|
-
this.filePath = filePath;
|
|
3156
|
-
this.cause = cause;
|
|
3157
|
-
this.name = "FileSystemError";
|
|
3158
|
-
}
|
|
3159
|
-
};
|
|
3160
|
-
}
|
|
3161
|
-
});
|
|
3162
|
-
|
|
3163
|
-
// src/util/migrator/validation.ts
|
|
3164
|
-
async function validateStaticCourse(staticPath, fs) {
|
|
3165
|
-
const validation = {
|
|
3166
|
-
valid: true,
|
|
3167
|
-
manifestExists: false,
|
|
3168
|
-
chunksExist: false,
|
|
3169
|
-
attachmentsExist: false,
|
|
3170
|
-
errors: [],
|
|
3171
|
-
warnings: []
|
|
3172
|
-
};
|
|
3173
|
-
try {
|
|
3174
|
-
if (fs) {
|
|
3175
|
-
const stats = await fs.stat(staticPath);
|
|
3176
|
-
if (!stats.isDirectory()) {
|
|
3177
|
-
validation.errors.push(`Path is not a directory: ${staticPath}`);
|
|
3178
|
-
validation.valid = false;
|
|
3179
|
-
return validation;
|
|
3180
|
-
}
|
|
3181
|
-
} else if (!nodeFS) {
|
|
3182
|
-
validation.errors.push("File system access not available - validation skipped");
|
|
3183
|
-
validation.valid = false;
|
|
3184
|
-
return validation;
|
|
3185
|
-
} else {
|
|
3186
|
-
const stats = await nodeFS.promises.stat(staticPath);
|
|
3187
|
-
if (!stats.isDirectory()) {
|
|
3188
|
-
validation.errors.push(`Path is not a directory: ${staticPath}`);
|
|
3189
|
-
validation.valid = false;
|
|
3190
|
-
return validation;
|
|
3191
|
-
}
|
|
3192
|
-
}
|
|
3193
|
-
let manifestPath = `${staticPath}/manifest.json`;
|
|
3194
|
-
try {
|
|
3195
|
-
if (fs) {
|
|
3196
|
-
manifestPath = fs.joinPath(staticPath, "manifest.json");
|
|
3197
|
-
if (await fs.exists(manifestPath)) {
|
|
3198
|
-
validation.manifestExists = true;
|
|
3199
|
-
const manifestContent = await fs.readFile(manifestPath);
|
|
3200
|
-
const manifest = JSON.parse(manifestContent);
|
|
3201
|
-
validation.courseId = manifest.courseId;
|
|
3202
|
-
validation.courseName = manifest.courseName;
|
|
3203
|
-
if (!manifest.version || !manifest.courseId || !manifest.chunks || !Array.isArray(manifest.chunks)) {
|
|
3204
|
-
validation.errors.push("Invalid manifest structure");
|
|
3205
|
-
validation.valid = false;
|
|
3206
|
-
}
|
|
3207
|
-
} else {
|
|
3208
|
-
validation.errors.push(`Manifest not found: ${manifestPath}`);
|
|
3209
|
-
validation.valid = false;
|
|
3210
|
-
}
|
|
3211
|
-
} else {
|
|
3212
|
-
manifestPath = `${staticPath}/manifest.json`;
|
|
3213
|
-
await nodeFS.promises.access(manifestPath);
|
|
3214
|
-
validation.manifestExists = true;
|
|
3215
|
-
const manifestContent = await nodeFS.promises.readFile(manifestPath, "utf8");
|
|
3216
|
-
const manifest = JSON.parse(manifestContent);
|
|
3217
|
-
validation.courseId = manifest.courseId;
|
|
3218
|
-
validation.courseName = manifest.courseName;
|
|
3219
|
-
if (!manifest.version || !manifest.courseId || !manifest.chunks || !Array.isArray(manifest.chunks)) {
|
|
3220
|
-
validation.errors.push("Invalid manifest structure");
|
|
3221
|
-
validation.valid = false;
|
|
3222
|
-
}
|
|
3223
|
-
}
|
|
3224
|
-
} catch (error) {
|
|
3225
|
-
const errorMessage = error instanceof FileSystemError ? error.message : `Manifest not found or invalid: ${manifestPath}`;
|
|
3226
|
-
validation.errors.push(errorMessage);
|
|
3227
|
-
validation.valid = false;
|
|
3228
|
-
}
|
|
3229
|
-
let chunksPath = `${staticPath}/chunks`;
|
|
3230
|
-
try {
|
|
3231
|
-
if (fs) {
|
|
3232
|
-
chunksPath = fs.joinPath(staticPath, "chunks");
|
|
3233
|
-
if (await fs.exists(chunksPath)) {
|
|
3234
|
-
const chunksStats = await fs.stat(chunksPath);
|
|
3235
|
-
if (chunksStats.isDirectory()) {
|
|
3236
|
-
validation.chunksExist = true;
|
|
3237
|
-
} else {
|
|
3238
|
-
validation.errors.push(`Chunks path is not a directory: ${chunksPath}`);
|
|
3239
|
-
validation.valid = false;
|
|
3240
|
-
}
|
|
3241
|
-
} else {
|
|
3242
|
-
validation.errors.push(`Chunks directory not found: ${chunksPath}`);
|
|
3243
|
-
validation.valid = false;
|
|
3244
|
-
}
|
|
3245
|
-
} else {
|
|
3246
|
-
chunksPath = `${staticPath}/chunks`;
|
|
3247
|
-
const chunksStats = await nodeFS.promises.stat(chunksPath);
|
|
3248
|
-
if (chunksStats.isDirectory()) {
|
|
3249
|
-
validation.chunksExist = true;
|
|
3250
|
-
} else {
|
|
3251
|
-
validation.errors.push(`Chunks path is not a directory: ${chunksPath}`);
|
|
3252
|
-
validation.valid = false;
|
|
3253
|
-
}
|
|
3254
|
-
}
|
|
3255
|
-
} catch (error) {
|
|
3256
|
-
const errorMessage = error instanceof FileSystemError ? error.message : `Chunks directory not found: ${chunksPath}`;
|
|
3257
|
-
validation.errors.push(errorMessage);
|
|
3258
|
-
validation.valid = false;
|
|
3259
|
-
}
|
|
3260
|
-
let attachmentsPath;
|
|
3261
|
-
try {
|
|
3262
|
-
if (fs) {
|
|
3263
|
-
attachmentsPath = fs.joinPath(staticPath, "attachments");
|
|
3264
|
-
if (await fs.exists(attachmentsPath)) {
|
|
3265
|
-
const attachmentsStats = await fs.stat(attachmentsPath);
|
|
3266
|
-
if (attachmentsStats.isDirectory()) {
|
|
3267
|
-
validation.attachmentsExist = true;
|
|
3268
|
-
}
|
|
3269
|
-
} else {
|
|
3270
|
-
validation.warnings.push(
|
|
3271
|
-
`Attachments directory not found: ${attachmentsPath} (this is OK if course has no attachments)`
|
|
3272
|
-
);
|
|
3273
|
-
}
|
|
3274
|
-
} else {
|
|
3275
|
-
attachmentsPath = `${staticPath}/attachments`;
|
|
3276
|
-
const attachmentsStats = await nodeFS.promises.stat(attachmentsPath);
|
|
3277
|
-
if (attachmentsStats.isDirectory()) {
|
|
3278
|
-
validation.attachmentsExist = true;
|
|
3279
|
-
}
|
|
3280
|
-
}
|
|
3281
|
-
} catch (error) {
|
|
3282
|
-
attachmentsPath = attachmentsPath || `${staticPath}/attachments`;
|
|
3283
|
-
const warningMessage = error instanceof FileSystemError ? error.message : `Attachments directory not found: ${attachmentsPath} (this is OK if course has no attachments)`;
|
|
3284
|
-
validation.warnings.push(warningMessage);
|
|
3285
|
-
}
|
|
3286
|
-
} catch (error) {
|
|
3287
|
-
validation.errors.push(
|
|
3288
|
-
`Failed to validate static course: ${error instanceof Error ? error.message : String(error)}`
|
|
3289
|
-
);
|
|
3290
|
-
validation.valid = false;
|
|
3291
|
-
}
|
|
3292
|
-
return validation;
|
|
3293
|
-
}
|
|
3294
|
-
async function validateMigration(targetDB, expectedCounts, manifest) {
|
|
3295
|
-
const validation = {
|
|
3296
|
-
valid: true,
|
|
3297
|
-
documentCountMatch: false,
|
|
3298
|
-
attachmentIntegrity: false,
|
|
3299
|
-
viewFunctionality: false,
|
|
3300
|
-
issues: []
|
|
3301
|
-
};
|
|
3302
|
-
try {
|
|
3303
|
-
logger.info("Starting migration validation...");
|
|
3304
|
-
const actualCounts = await getActualDocumentCounts(targetDB);
|
|
3305
|
-
validation.documentCountMatch = compareDocumentCounts(
|
|
3306
|
-
expectedCounts,
|
|
3307
|
-
actualCounts,
|
|
3308
|
-
validation.issues
|
|
3309
|
-
);
|
|
3310
|
-
await validateCourseConfig(targetDB, manifest, validation.issues);
|
|
3311
|
-
validation.viewFunctionality = await validateViews(targetDB, manifest, validation.issues);
|
|
3312
|
-
validation.attachmentIntegrity = await validateAttachmentIntegrity(targetDB, validation.issues);
|
|
3313
|
-
validation.valid = validation.documentCountMatch && validation.viewFunctionality && validation.attachmentIntegrity;
|
|
3314
|
-
logger.info(`Migration validation completed. Valid: ${validation.valid}`);
|
|
3315
|
-
if (validation.issues.length > 0) {
|
|
3316
|
-
logger.info(`Validation issues: ${validation.issues.length}`);
|
|
3317
|
-
validation.issues.forEach((issue) => {
|
|
3318
|
-
if (issue.type === "error") {
|
|
3319
|
-
logger.error(`${issue.category}: ${issue.message}`);
|
|
3320
|
-
} else {
|
|
3321
|
-
logger.warn(`${issue.category}: ${issue.message}`);
|
|
3322
|
-
}
|
|
3323
|
-
});
|
|
3324
|
-
}
|
|
3325
|
-
} catch (error) {
|
|
3326
|
-
validation.valid = false;
|
|
3327
|
-
validation.issues.push({
|
|
3328
|
-
type: "error",
|
|
3329
|
-
category: "metadata",
|
|
3330
|
-
message: `Validation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
3331
|
-
});
|
|
3332
|
-
}
|
|
3333
|
-
return validation;
|
|
3334
|
-
}
|
|
3335
|
-
async function getActualDocumentCounts(db) {
|
|
3336
|
-
const counts = {};
|
|
3337
|
-
try {
|
|
3338
|
-
const allDocs = await db.allDocs({ include_docs: true });
|
|
3339
|
-
for (const row of allDocs.rows) {
|
|
3340
|
-
if (row.id.startsWith("_design/")) {
|
|
3341
|
-
counts["_design"] = (counts["_design"] || 0) + 1;
|
|
3342
|
-
continue;
|
|
3343
|
-
}
|
|
3344
|
-
const doc = row.doc;
|
|
3345
|
-
if (doc && doc.docType) {
|
|
3346
|
-
counts[doc.docType] = (counts[doc.docType] || 0) + 1;
|
|
3347
|
-
} else {
|
|
3348
|
-
counts["unknown"] = (counts["unknown"] || 0) + 1;
|
|
3349
|
-
}
|
|
3350
|
-
}
|
|
3351
|
-
} catch (error) {
|
|
3352
|
-
logger.error("Failed to get actual document counts:", error);
|
|
3353
|
-
}
|
|
3354
|
-
return counts;
|
|
3355
|
-
}
|
|
3356
|
-
function compareDocumentCounts(expected, actual, issues) {
|
|
3357
|
-
let countsMatch = true;
|
|
3358
|
-
for (const [docType, expectedCount] of Object.entries(expected)) {
|
|
3359
|
-
const actualCount = actual[docType] || 0;
|
|
3360
|
-
if (actualCount !== expectedCount) {
|
|
3361
|
-
countsMatch = false;
|
|
3362
|
-
issues.push({
|
|
3363
|
-
type: "error",
|
|
3364
|
-
category: "documents",
|
|
3365
|
-
message: `Document count mismatch for ${docType}: expected ${expectedCount}, got ${actualCount}`
|
|
3366
|
-
});
|
|
3367
|
-
}
|
|
3368
|
-
}
|
|
3369
|
-
for (const [docType, actualCount] of Object.entries(actual)) {
|
|
3370
|
-
if (!expected[docType] && docType !== "_design") {
|
|
3371
|
-
issues.push({
|
|
3372
|
-
type: "warning",
|
|
3373
|
-
category: "documents",
|
|
3374
|
-
message: `Unexpected document type found: ${docType} (${actualCount} documents)`
|
|
3375
|
-
});
|
|
3376
|
-
}
|
|
3377
|
-
}
|
|
3378
|
-
return countsMatch;
|
|
3379
|
-
}
|
|
3380
|
-
async function validateCourseConfig(db, manifest, issues) {
|
|
3381
|
-
try {
|
|
3382
|
-
const courseConfig = await db.get("CourseConfig");
|
|
3383
|
-
if (!courseConfig) {
|
|
3384
|
-
issues.push({
|
|
3385
|
-
type: "error",
|
|
3386
|
-
category: "course_config",
|
|
3387
|
-
message: "CourseConfig document not found after migration"
|
|
3388
|
-
});
|
|
3389
|
-
return;
|
|
3390
|
-
}
|
|
3391
|
-
if (!courseConfig.courseID) {
|
|
3392
|
-
issues.push({
|
|
3393
|
-
type: "warning",
|
|
3394
|
-
category: "course_config",
|
|
3395
|
-
message: "CourseConfig document missing courseID field"
|
|
3396
|
-
});
|
|
3397
|
-
}
|
|
3398
|
-
if (courseConfig.courseID !== manifest.courseId) {
|
|
3399
|
-
issues.push({
|
|
3400
|
-
type: "warning",
|
|
3401
|
-
category: "course_config",
|
|
3402
|
-
message: `CourseConfig courseID mismatch: expected ${manifest.courseId}, got ${courseConfig.courseID}`
|
|
3403
|
-
});
|
|
3404
|
-
}
|
|
3405
|
-
logger.debug("CourseConfig document validation passed");
|
|
3406
|
-
} catch (error) {
|
|
3407
|
-
if (error.status === 404) {
|
|
3408
|
-
issues.push({
|
|
3409
|
-
type: "error",
|
|
3410
|
-
category: "course_config",
|
|
3411
|
-
message: "CourseConfig document not found in database"
|
|
3412
|
-
});
|
|
3413
|
-
} else {
|
|
3414
|
-
issues.push({
|
|
3415
|
-
type: "error",
|
|
3416
|
-
category: "course_config",
|
|
3417
|
-
message: `Failed to validate CourseConfig document: ${error instanceof Error ? error.message : String(error)}`
|
|
3418
|
-
});
|
|
3419
|
-
}
|
|
3420
|
-
}
|
|
3421
|
-
}
|
|
3422
|
-
async function validateViews(db, manifest, issues) {
|
|
3423
|
-
let viewsValid = true;
|
|
3424
|
-
try {
|
|
3425
|
-
for (const designDoc of manifest.designDocs) {
|
|
3426
|
-
try {
|
|
3427
|
-
const doc = await db.get(designDoc._id);
|
|
3428
|
-
if (!doc) {
|
|
3429
|
-
viewsValid = false;
|
|
3430
|
-
issues.push({
|
|
3431
|
-
type: "error",
|
|
3432
|
-
category: "views",
|
|
3433
|
-
message: `Design document not found: ${designDoc._id}`
|
|
3434
|
-
});
|
|
3435
|
-
continue;
|
|
3436
|
-
}
|
|
3437
|
-
for (const viewName of Object.keys(designDoc.views)) {
|
|
3438
|
-
try {
|
|
3439
|
-
const viewPath = `${designDoc._id}/${viewName}`;
|
|
3440
|
-
await db.query(viewPath, { limit: 1 });
|
|
3441
|
-
} catch (viewError) {
|
|
3442
|
-
viewsValid = false;
|
|
3443
|
-
issues.push({
|
|
3444
|
-
type: "error",
|
|
3445
|
-
category: "views",
|
|
3446
|
-
message: `View not accessible: ${designDoc._id}/${viewName} - ${viewError}`
|
|
3447
|
-
});
|
|
3448
|
-
}
|
|
3449
|
-
}
|
|
3450
|
-
} catch (error) {
|
|
3451
|
-
viewsValid = false;
|
|
3452
|
-
issues.push({
|
|
3453
|
-
type: "error",
|
|
3454
|
-
category: "views",
|
|
3455
|
-
message: `Failed to validate design document ${designDoc._id}: ${error}`
|
|
3456
|
-
});
|
|
3457
|
-
}
|
|
3458
|
-
}
|
|
3459
|
-
} catch (error) {
|
|
3460
|
-
viewsValid = false;
|
|
3461
|
-
issues.push({
|
|
3462
|
-
type: "error",
|
|
3463
|
-
category: "views",
|
|
3464
|
-
message: `View validation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
3465
|
-
});
|
|
3466
|
-
}
|
|
3467
|
-
return viewsValid;
|
|
3468
|
-
}
|
|
3469
|
-
async function validateAttachmentIntegrity(db, issues) {
|
|
3470
|
-
let attachmentsValid = true;
|
|
3471
|
-
try {
|
|
3472
|
-
const allDocs = await db.allDocs({
|
|
3473
|
-
include_docs: true,
|
|
3474
|
-
limit: 10
|
|
3475
|
-
// Sample first 10 documents for performance
|
|
3476
|
-
});
|
|
3477
|
-
let attachmentCount = 0;
|
|
3478
|
-
let validAttachments = 0;
|
|
3479
|
-
for (const row of allDocs.rows) {
|
|
3480
|
-
const doc = row.doc;
|
|
3481
|
-
if (doc && doc._attachments) {
|
|
3482
|
-
for (const [attachmentName, _attachmentMeta] of Object.entries(doc._attachments)) {
|
|
3483
|
-
attachmentCount++;
|
|
3484
|
-
try {
|
|
3485
|
-
const attachment = await db.getAttachment(doc._id, attachmentName);
|
|
3486
|
-
if (attachment) {
|
|
3487
|
-
validAttachments++;
|
|
3488
|
-
}
|
|
3489
|
-
} catch (attachmentError) {
|
|
3490
|
-
attachmentsValid = false;
|
|
3491
|
-
issues.push({
|
|
3492
|
-
type: "error",
|
|
3493
|
-
category: "attachments",
|
|
3494
|
-
message: `Attachment not accessible: ${doc._id}/${attachmentName} - ${attachmentError}`
|
|
3495
|
-
});
|
|
3496
|
-
}
|
|
3497
|
-
}
|
|
3498
|
-
}
|
|
3499
|
-
}
|
|
3500
|
-
if (attachmentCount === 0) {
|
|
3501
|
-
issues.push({
|
|
3502
|
-
type: "warning",
|
|
3503
|
-
category: "attachments",
|
|
3504
|
-
message: "No attachments found in sampled documents"
|
|
3505
|
-
});
|
|
3506
|
-
} else {
|
|
3507
|
-
logger.info(`Validated ${validAttachments}/${attachmentCount} sampled attachments`);
|
|
3508
|
-
}
|
|
3509
|
-
} catch (error) {
|
|
3510
|
-
attachmentsValid = false;
|
|
3511
|
-
issues.push({
|
|
3512
|
-
type: "error",
|
|
3513
|
-
category: "attachments",
|
|
3514
|
-
message: `Attachment validation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
3515
|
-
});
|
|
3516
|
-
}
|
|
3517
|
-
return attachmentsValid;
|
|
3518
|
-
}
|
|
3519
|
-
var nodeFS;
|
|
3520
|
-
var init_validation = __esm({
|
|
3521
|
-
"src/util/migrator/validation.ts"() {
|
|
3522
|
-
"use strict";
|
|
3523
|
-
init_logger();
|
|
3524
|
-
init_FileSystemAdapter();
|
|
3525
|
-
nodeFS = null;
|
|
3526
|
-
try {
|
|
3527
|
-
if (typeof window === "undefined" && typeof process !== "undefined" && process.versions?.node) {
|
|
3528
|
-
nodeFS = eval("require")("fs");
|
|
3529
|
-
nodeFS.promises = nodeFS.promises || eval("require")("fs").promises;
|
|
3530
|
-
}
|
|
3531
|
-
} catch {
|
|
3532
|
-
}
|
|
3533
|
-
}
|
|
3534
|
-
});
|
|
3535
|
-
|
|
3536
|
-
// src/util/migrator/StaticToCouchDBMigrator.ts
|
|
3537
|
-
var nodeFS2, nodePath, StaticToCouchDBMigrator;
|
|
3538
|
-
var init_StaticToCouchDBMigrator = __esm({
|
|
3539
|
-
"src/util/migrator/StaticToCouchDBMigrator.ts"() {
|
|
3540
|
-
"use strict";
|
|
3541
|
-
init_logger();
|
|
3542
|
-
init_types4();
|
|
3543
|
-
init_validation();
|
|
3544
|
-
init_FileSystemAdapter();
|
|
3545
|
-
nodeFS2 = null;
|
|
3546
|
-
nodePath = null;
|
|
3547
|
-
try {
|
|
3548
|
-
if (typeof window === "undefined" && typeof process !== "undefined" && process.versions?.node) {
|
|
3549
|
-
nodeFS2 = eval("require")("fs");
|
|
3550
|
-
nodePath = eval("require")("path");
|
|
3551
|
-
nodeFS2.promises = nodeFS2.promises || eval("require")("fs").promises;
|
|
3552
|
-
}
|
|
3553
|
-
} catch {
|
|
3554
|
-
}
|
|
3555
|
-
StaticToCouchDBMigrator = class {
|
|
3556
|
-
options;
|
|
3557
|
-
progressCallback;
|
|
3558
|
-
fs;
|
|
3559
|
-
constructor(options = {}, fileSystemAdapter) {
|
|
3560
|
-
this.options = {
|
|
3561
|
-
...DEFAULT_MIGRATION_OPTIONS,
|
|
3562
|
-
...options
|
|
3563
|
-
};
|
|
3564
|
-
this.fs = fileSystemAdapter;
|
|
3565
|
-
}
|
|
3566
|
-
/**
|
|
3567
|
-
* Set a progress callback to receive updates during migration
|
|
3568
|
-
*/
|
|
3569
|
-
setProgressCallback(callback) {
|
|
3570
|
-
this.progressCallback = callback;
|
|
3571
|
-
}
|
|
3572
|
-
/**
|
|
3573
|
-
* Migrate a static course to CouchDB
|
|
3574
|
-
*/
|
|
3575
|
-
async migrateCourse(staticPath, targetDB) {
|
|
3576
|
-
const startTime = Date.now();
|
|
3577
|
-
const result = {
|
|
3578
|
-
success: false,
|
|
3579
|
-
documentsRestored: 0,
|
|
3580
|
-
attachmentsRestored: 0,
|
|
3581
|
-
designDocsRestored: 0,
|
|
3582
|
-
courseConfigRestored: 0,
|
|
3583
|
-
errors: [],
|
|
3584
|
-
warnings: [],
|
|
3585
|
-
migrationTime: 0
|
|
3586
|
-
};
|
|
3587
|
-
try {
|
|
3588
|
-
logger.info(`Starting migration from ${staticPath} to CouchDB`);
|
|
3589
|
-
this.reportProgress("manifest", 0, 1, "Validating static course...");
|
|
3590
|
-
const validation = await validateStaticCourse(staticPath, this.fs);
|
|
3591
|
-
if (!validation.valid) {
|
|
3592
|
-
result.errors.push(...validation.errors);
|
|
3593
|
-
throw new Error(`Static course validation failed: ${validation.errors.join(", ")}`);
|
|
3594
|
-
}
|
|
3595
|
-
result.warnings.push(...validation.warnings);
|
|
3596
|
-
this.reportProgress("manifest", 1, 1, "Loading course manifest...");
|
|
3597
|
-
const manifest = await this.loadManifest(staticPath);
|
|
3598
|
-
logger.info(`Loaded manifest for course: ${manifest.courseId} (${manifest.courseName})`);
|
|
3599
|
-
this.reportProgress(
|
|
3600
|
-
"design_docs",
|
|
3601
|
-
0,
|
|
3602
|
-
manifest.designDocs.length,
|
|
3603
|
-
"Restoring design documents..."
|
|
3604
|
-
);
|
|
3605
|
-
const designDocResults = await this.restoreDesignDocuments(manifest.designDocs, targetDB);
|
|
3606
|
-
result.designDocsRestored = designDocResults.restored;
|
|
3607
|
-
result.errors.push(...designDocResults.errors);
|
|
3608
|
-
result.warnings.push(...designDocResults.warnings);
|
|
3609
|
-
this.reportProgress("course_config", 0, 1, "Restoring CourseConfig document...");
|
|
3610
|
-
const courseConfigResults = await this.restoreCourseConfig(manifest, targetDB);
|
|
3611
|
-
result.courseConfigRestored = courseConfigResults.restored;
|
|
3612
|
-
result.errors.push(...courseConfigResults.errors);
|
|
3613
|
-
result.warnings.push(...courseConfigResults.warnings);
|
|
3614
|
-
this.reportProgress("course_config", 1, 1, "CourseConfig document restored");
|
|
3615
|
-
const expectedCounts = this.calculateExpectedCounts(manifest);
|
|
3616
|
-
this.reportProgress(
|
|
3617
|
-
"documents",
|
|
3618
|
-
0,
|
|
3619
|
-
manifest.documentCount,
|
|
3620
|
-
"Aggregating documents from chunks..."
|
|
3621
|
-
);
|
|
3622
|
-
const documents = await this.aggregateDocuments(staticPath, manifest);
|
|
3623
|
-
const filteredDocuments = documents.filter((doc) => doc._id !== "CourseConfig");
|
|
3624
|
-
if (documents.length !== filteredDocuments.length) {
|
|
3625
|
-
result.warnings.push(
|
|
3626
|
-
`Filtered out ${documents.length - filteredDocuments.length} CourseConfig document(s) from chunks to prevent conflicts`
|
|
3627
|
-
);
|
|
3628
|
-
}
|
|
3629
|
-
this.reportProgress(
|
|
3630
|
-
"documents",
|
|
3631
|
-
filteredDocuments.length,
|
|
3632
|
-
manifest.documentCount,
|
|
3633
|
-
"Uploading documents to CouchDB..."
|
|
3634
|
-
);
|
|
3635
|
-
const docResults = await this.uploadDocuments(filteredDocuments, targetDB);
|
|
3636
|
-
result.documentsRestored = docResults.restored;
|
|
3637
|
-
result.errors.push(...docResults.errors);
|
|
3638
|
-
result.warnings.push(...docResults.warnings);
|
|
3639
|
-
const docsWithAttachments = documents.filter(
|
|
3640
|
-
(doc) => doc._attachments && Object.keys(doc._attachments).length > 0
|
|
3641
|
-
);
|
|
3642
|
-
this.reportProgress("attachments", 0, docsWithAttachments.length, "Uploading attachments...");
|
|
3643
|
-
const attachmentResults = await this.uploadAttachments(
|
|
3644
|
-
staticPath,
|
|
3645
|
-
docsWithAttachments,
|
|
3646
|
-
targetDB
|
|
3647
|
-
);
|
|
3648
|
-
result.attachmentsRestored = attachmentResults.restored;
|
|
3649
|
-
result.errors.push(...attachmentResults.errors);
|
|
3650
|
-
result.warnings.push(...attachmentResults.warnings);
|
|
3651
|
-
if (this.options.validateRoundTrip) {
|
|
3652
|
-
this.reportProgress("validation", 0, 1, "Validating migration...");
|
|
3653
|
-
const validationResult = await validateMigration(targetDB, expectedCounts, manifest);
|
|
3654
|
-
if (!validationResult.valid) {
|
|
3655
|
-
result.warnings.push("Migration validation found issues");
|
|
3656
|
-
validationResult.issues.forEach((issue) => {
|
|
3657
|
-
if (issue.type === "error") {
|
|
3658
|
-
result.errors.push(`Validation: ${issue.message}`);
|
|
3659
|
-
} else {
|
|
3660
|
-
result.warnings.push(`Validation: ${issue.message}`);
|
|
3661
|
-
}
|
|
3662
|
-
});
|
|
3663
|
-
}
|
|
3664
|
-
this.reportProgress("validation", 1, 1, "Migration validation completed");
|
|
3665
|
-
}
|
|
3666
|
-
result.success = result.errors.length === 0;
|
|
3667
|
-
result.migrationTime = Date.now() - startTime;
|
|
3668
|
-
logger.info(`Migration completed in ${result.migrationTime}ms`);
|
|
3669
|
-
logger.info(`Documents restored: ${result.documentsRestored}`);
|
|
3670
|
-
logger.info(`Attachments restored: ${result.attachmentsRestored}`);
|
|
3671
|
-
logger.info(`Design docs restored: ${result.designDocsRestored}`);
|
|
3672
|
-
logger.info(`CourseConfig restored: ${result.courseConfigRestored}`);
|
|
3673
|
-
if (result.errors.length > 0) {
|
|
3674
|
-
logger.error(`Migration completed with ${result.errors.length} errors`);
|
|
3675
|
-
}
|
|
3676
|
-
if (result.warnings.length > 0) {
|
|
3677
|
-
logger.warn(`Migration completed with ${result.warnings.length} warnings`);
|
|
3678
|
-
}
|
|
3679
|
-
} catch (error) {
|
|
3680
|
-
result.success = false;
|
|
3681
|
-
result.migrationTime = Date.now() - startTime;
|
|
3682
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3683
|
-
result.errors.push(`Migration failed: ${errorMessage}`);
|
|
3684
|
-
logger.error("Migration failed:", error);
|
|
3685
|
-
if (this.options.cleanupOnFailure) {
|
|
3686
|
-
try {
|
|
3687
|
-
await this.cleanupFailedMigration(targetDB);
|
|
3688
|
-
} catch (cleanupError) {
|
|
3689
|
-
logger.error("Failed to cleanup after migration failure:", cleanupError);
|
|
3690
|
-
result.warnings.push("Failed to cleanup after migration failure");
|
|
3691
|
-
}
|
|
3692
|
-
}
|
|
3693
|
-
}
|
|
3694
|
-
return result;
|
|
3695
|
-
}
|
|
3696
|
-
/**
|
|
3697
|
-
* Load and parse the manifest file
|
|
3698
|
-
*/
|
|
3699
|
-
async loadManifest(staticPath) {
|
|
3700
|
-
try {
|
|
3701
|
-
let manifestContent;
|
|
3702
|
-
let manifestPath;
|
|
3703
|
-
if (this.fs) {
|
|
3704
|
-
manifestPath = this.fs.joinPath(staticPath, "manifest.json");
|
|
3705
|
-
manifestContent = await this.fs.readFile(manifestPath);
|
|
3706
|
-
} else {
|
|
3707
|
-
manifestPath = nodeFS2 && nodePath ? nodePath.join(staticPath, "manifest.json") : `${staticPath}/manifest.json`;
|
|
3708
|
-
if (nodeFS2 && this.isLocalPath(staticPath)) {
|
|
3709
|
-
manifestContent = await nodeFS2.promises.readFile(manifestPath, "utf8");
|
|
3710
|
-
} else {
|
|
3711
|
-
const response = await fetch(manifestPath);
|
|
3712
|
-
if (!response.ok) {
|
|
3713
|
-
throw new Error(`Failed to fetch manifest: ${response.status} ${response.statusText}`);
|
|
3714
|
-
}
|
|
3715
|
-
manifestContent = await response.text();
|
|
3716
|
-
}
|
|
3717
|
-
}
|
|
3718
|
-
const manifest = JSON.parse(manifestContent);
|
|
3719
|
-
if (!manifest.version || !manifest.courseId || !manifest.chunks) {
|
|
3720
|
-
throw new Error("Invalid manifest structure");
|
|
3721
|
-
}
|
|
3722
|
-
return manifest;
|
|
3723
|
-
} catch (error) {
|
|
3724
|
-
const errorMessage = error instanceof FileSystemError ? error.message : `Failed to load manifest: ${error instanceof Error ? error.message : String(error)}`;
|
|
3725
|
-
throw new Error(errorMessage);
|
|
3726
|
-
}
|
|
3727
|
-
}
|
|
3728
|
-
/**
|
|
3729
|
-
* Restore design documents to CouchDB
|
|
3730
|
-
*/
|
|
3731
|
-
async restoreDesignDocuments(designDocs, db) {
|
|
3732
|
-
const result = { restored: 0, errors: [], warnings: [] };
|
|
3733
|
-
for (let i = 0; i < designDocs.length; i++) {
|
|
3734
|
-
const designDoc = designDocs[i];
|
|
3735
|
-
this.reportProgress("design_docs", i, designDocs.length, `Restoring ${designDoc._id}...`);
|
|
3736
|
-
try {
|
|
3737
|
-
let existingDoc;
|
|
3738
|
-
try {
|
|
3739
|
-
existingDoc = await db.get(designDoc._id);
|
|
3740
|
-
} catch {
|
|
3741
|
-
}
|
|
3742
|
-
const docToInsert = {
|
|
3743
|
-
_id: designDoc._id,
|
|
3744
|
-
views: designDoc.views
|
|
3745
|
-
};
|
|
3746
|
-
if (existingDoc) {
|
|
3747
|
-
docToInsert._rev = existingDoc._rev;
|
|
3748
|
-
logger.debug(`Updating existing design document: ${designDoc._id}`);
|
|
3749
|
-
} else {
|
|
3750
|
-
logger.debug(`Creating new design document: ${designDoc._id}`);
|
|
3751
|
-
}
|
|
3752
|
-
await db.put(docToInsert);
|
|
3753
|
-
result.restored++;
|
|
3754
|
-
} catch (error) {
|
|
3755
|
-
const errorMessage = `Failed to restore design document ${designDoc._id}: ${error instanceof Error ? error.message : String(error)}`;
|
|
3756
|
-
result.errors.push(errorMessage);
|
|
3757
|
-
logger.error(errorMessage);
|
|
3758
|
-
}
|
|
3759
|
-
}
|
|
3760
|
-
this.reportProgress(
|
|
3761
|
-
"design_docs",
|
|
3762
|
-
designDocs.length,
|
|
3763
|
-
designDocs.length,
|
|
3764
|
-
`Restored ${result.restored} design documents`
|
|
3765
|
-
);
|
|
3766
|
-
return result;
|
|
3767
|
-
}
|
|
3768
|
-
/**
|
|
3769
|
-
* Aggregate documents from all chunks
|
|
3770
|
-
*/
|
|
3771
|
-
async aggregateDocuments(staticPath, manifest) {
|
|
3772
|
-
const allDocuments = [];
|
|
3773
|
-
const documentMap = /* @__PURE__ */ new Map();
|
|
3774
|
-
for (let i = 0; i < manifest.chunks.length; i++) {
|
|
3775
|
-
const chunk = manifest.chunks[i];
|
|
3776
|
-
this.reportProgress(
|
|
3777
|
-
"documents",
|
|
3778
|
-
allDocuments.length,
|
|
3779
|
-
manifest.documentCount,
|
|
3780
|
-
`Loading chunk ${chunk.id}...`
|
|
3781
|
-
);
|
|
3782
|
-
try {
|
|
3783
|
-
const documents = await this.loadChunk(staticPath, chunk);
|
|
3784
|
-
for (const doc of documents) {
|
|
3785
|
-
if (!doc._id) {
|
|
3786
|
-
logger.warn(`Document without _id found in chunk ${chunk.id}, skipping`);
|
|
3787
|
-
continue;
|
|
3788
|
-
}
|
|
3789
|
-
if (documentMap.has(doc._id)) {
|
|
3790
|
-
logger.warn(`Duplicate document ID found: ${doc._id}, using latest version`);
|
|
3791
|
-
}
|
|
3792
|
-
documentMap.set(doc._id, doc);
|
|
3793
|
-
}
|
|
3794
|
-
} catch (error) {
|
|
3795
|
-
throw new Error(
|
|
3796
|
-
`Failed to load chunk ${chunk.id}: ${error instanceof Error ? error.message : String(error)}`
|
|
3797
|
-
);
|
|
3798
|
-
}
|
|
3799
|
-
}
|
|
3800
|
-
allDocuments.push(...documentMap.values());
|
|
3801
|
-
logger.info(
|
|
3802
|
-
`Aggregated ${allDocuments.length} unique documents from ${manifest.chunks.length} chunks`
|
|
3803
|
-
);
|
|
3804
|
-
return allDocuments;
|
|
3805
|
-
}
|
|
3806
|
-
/**
|
|
3807
|
-
* Load documents from a single chunk file
|
|
3808
|
-
*/
|
|
3809
|
-
async loadChunk(staticPath, chunk) {
|
|
3810
|
-
try {
|
|
3811
|
-
let chunkContent;
|
|
3812
|
-
let chunkPath;
|
|
3813
|
-
if (this.fs) {
|
|
3814
|
-
chunkPath = this.fs.joinPath(staticPath, chunk.path);
|
|
3815
|
-
chunkContent = await this.fs.readFile(chunkPath);
|
|
3816
|
-
} else {
|
|
3817
|
-
chunkPath = nodeFS2 && nodePath ? nodePath.join(staticPath, chunk.path) : `${staticPath}/${chunk.path}`;
|
|
3818
|
-
if (nodeFS2 && this.isLocalPath(staticPath)) {
|
|
3819
|
-
chunkContent = await nodeFS2.promises.readFile(chunkPath, "utf8");
|
|
3820
|
-
} else {
|
|
3821
|
-
const response = await fetch(chunkPath);
|
|
3822
|
-
if (!response.ok) {
|
|
3823
|
-
throw new Error(`Failed to fetch chunk: ${response.status} ${response.statusText}`);
|
|
3824
|
-
}
|
|
3825
|
-
chunkContent = await response.text();
|
|
3826
|
-
}
|
|
3827
|
-
}
|
|
3828
|
-
const documents = JSON.parse(chunkContent);
|
|
3829
|
-
if (!Array.isArray(documents)) {
|
|
3830
|
-
throw new Error("Chunk file does not contain an array of documents");
|
|
3831
|
-
}
|
|
3832
|
-
return documents;
|
|
3833
|
-
} catch (error) {
|
|
3834
|
-
const errorMessage = error instanceof FileSystemError ? error.message : `Failed to load chunk: ${error instanceof Error ? error.message : String(error)}`;
|
|
3835
|
-
throw new Error(errorMessage);
|
|
3836
|
-
}
|
|
3837
|
-
}
|
|
3838
|
-
/**
|
|
3839
|
-
* Upload documents to CouchDB in batches
|
|
3840
|
-
*/
|
|
3841
|
-
async uploadDocuments(documents, db) {
|
|
3842
|
-
const result = { restored: 0, errors: [], warnings: [] };
|
|
3843
|
-
const batchSize = this.options.chunkBatchSize;
|
|
3844
|
-
for (let i = 0; i < documents.length; i += batchSize) {
|
|
3845
|
-
const batch = documents.slice(i, i + batchSize);
|
|
3846
|
-
this.reportProgress(
|
|
3847
|
-
"documents",
|
|
3848
|
-
i,
|
|
3849
|
-
documents.length,
|
|
3850
|
-
`Uploading batch ${Math.floor(i / batchSize) + 1}...`
|
|
3851
|
-
);
|
|
3852
|
-
try {
|
|
3853
|
-
const docsToInsert = batch.map((doc) => {
|
|
3854
|
-
const cleanDoc = { ...doc };
|
|
3855
|
-
delete cleanDoc._rev;
|
|
3856
|
-
delete cleanDoc._attachments;
|
|
3857
|
-
return cleanDoc;
|
|
3858
|
-
});
|
|
3859
|
-
const bulkResult = await db.bulkDocs(docsToInsert);
|
|
3860
|
-
for (let j = 0; j < bulkResult.length; j++) {
|
|
3861
|
-
const docResult = bulkResult[j];
|
|
3862
|
-
const originalDoc = batch[j];
|
|
3863
|
-
if ("error" in docResult) {
|
|
3864
|
-
const errorMessage = `Failed to upload document ${originalDoc._id}: ${docResult.error} - ${docResult.reason}`;
|
|
3865
|
-
result.errors.push(errorMessage);
|
|
3866
|
-
logger.error(errorMessage);
|
|
3867
|
-
} else {
|
|
3868
|
-
result.restored++;
|
|
3869
|
-
}
|
|
3870
|
-
}
|
|
3871
|
-
} catch (error) {
|
|
3872
|
-
let errorMessage;
|
|
3873
|
-
if (error instanceof Error) {
|
|
3874
|
-
errorMessage = `Failed to upload document batch starting at index ${i}: ${error.message}`;
|
|
3875
|
-
} else if (error && typeof error === "object" && "message" in error) {
|
|
3876
|
-
errorMessage = `Failed to upload document batch starting at index ${i}: ${error.message}`;
|
|
3877
|
-
} else {
|
|
3878
|
-
errorMessage = `Failed to upload document batch starting at index ${i}: ${JSON.stringify(error)}`;
|
|
3879
|
-
}
|
|
3880
|
-
result.errors.push(errorMessage);
|
|
3881
|
-
logger.error(errorMessage);
|
|
3882
|
-
}
|
|
3883
|
-
}
|
|
3884
|
-
this.reportProgress(
|
|
3885
|
-
"documents",
|
|
3886
|
-
documents.length,
|
|
3887
|
-
documents.length,
|
|
3888
|
-
`Uploaded ${result.restored} documents`
|
|
3889
|
-
);
|
|
3890
|
-
return result;
|
|
3891
|
-
}
|
|
3892
|
-
/**
|
|
3893
|
-
* Upload attachments from filesystem to CouchDB
|
|
3894
|
-
*/
|
|
3895
|
-
async uploadAttachments(staticPath, documents, db) {
|
|
3896
|
-
const result = { restored: 0, errors: [], warnings: [] };
|
|
3897
|
-
let processedDocs = 0;
|
|
3898
|
-
for (const doc of documents) {
|
|
3899
|
-
this.reportProgress(
|
|
3900
|
-
"attachments",
|
|
3901
|
-
processedDocs,
|
|
3902
|
-
documents.length,
|
|
3903
|
-
`Processing attachments for ${doc._id}...`
|
|
3904
|
-
);
|
|
3905
|
-
processedDocs++;
|
|
3906
|
-
if (!doc._attachments) {
|
|
3907
|
-
continue;
|
|
3908
|
-
}
|
|
3909
|
-
for (const [attachmentName, attachmentMeta] of Object.entries(doc._attachments)) {
|
|
3910
|
-
try {
|
|
3911
|
-
const uploadResult = await this.uploadSingleAttachment(
|
|
3912
|
-
staticPath,
|
|
3913
|
-
doc._id,
|
|
3914
|
-
attachmentName,
|
|
3915
|
-
attachmentMeta,
|
|
3916
|
-
db
|
|
3917
|
-
);
|
|
3918
|
-
if (uploadResult.success) {
|
|
3919
|
-
result.restored++;
|
|
3920
|
-
} else {
|
|
3921
|
-
result.errors.push(uploadResult.error || "Unknown attachment upload error");
|
|
3922
|
-
}
|
|
3923
|
-
} catch (error) {
|
|
3924
|
-
const errorMessage = `Failed to upload attachment ${doc._id}/${attachmentName}: ${error instanceof Error ? error.message : String(error)}`;
|
|
3925
|
-
result.errors.push(errorMessage);
|
|
3926
|
-
logger.error(errorMessage);
|
|
3927
|
-
}
|
|
3928
|
-
}
|
|
3929
|
-
}
|
|
3930
|
-
this.reportProgress(
|
|
3931
|
-
"attachments",
|
|
3932
|
-
documents.length,
|
|
3933
|
-
documents.length,
|
|
3934
|
-
`Uploaded ${result.restored} attachments`
|
|
3935
|
-
);
|
|
3936
|
-
return result;
|
|
3937
|
-
}
|
|
3938
|
-
/**
|
|
3939
|
-
* Upload a single attachment file
|
|
3940
|
-
*/
|
|
3941
|
-
async uploadSingleAttachment(staticPath, docId, attachmentName, attachmentMeta, db) {
|
|
3942
|
-
const result = {
|
|
3943
|
-
success: false,
|
|
3944
|
-
attachmentName,
|
|
3945
|
-
docId
|
|
3946
|
-
};
|
|
3947
|
-
try {
|
|
3948
|
-
if (!attachmentMeta.path) {
|
|
3949
|
-
result.error = "Attachment metadata missing file path";
|
|
3950
|
-
return result;
|
|
3951
|
-
}
|
|
3952
|
-
let attachmentData;
|
|
3953
|
-
let attachmentPath;
|
|
3954
|
-
if (this.fs) {
|
|
3955
|
-
attachmentPath = this.fs.joinPath(staticPath, attachmentMeta.path);
|
|
3956
|
-
attachmentData = await this.fs.readBinary(attachmentPath);
|
|
3957
|
-
} else {
|
|
3958
|
-
attachmentPath = nodeFS2 && nodePath ? nodePath.join(staticPath, attachmentMeta.path) : `${staticPath}/${attachmentMeta.path}`;
|
|
3959
|
-
if (nodeFS2 && this.isLocalPath(staticPath)) {
|
|
3960
|
-
attachmentData = await nodeFS2.promises.readFile(attachmentPath);
|
|
3961
|
-
} else {
|
|
3962
|
-
const response = await fetch(attachmentPath);
|
|
3963
|
-
if (!response.ok) {
|
|
3964
|
-
result.error = `Failed to fetch attachment: ${response.status} ${response.statusText}`;
|
|
3965
|
-
return result;
|
|
3966
|
-
}
|
|
3967
|
-
attachmentData = await response.arrayBuffer();
|
|
3968
|
-
}
|
|
3969
|
-
}
|
|
3970
|
-
const doc = await db.get(docId);
|
|
3971
|
-
await db.putAttachment(
|
|
3972
|
-
docId,
|
|
3973
|
-
attachmentName,
|
|
3974
|
-
doc._rev,
|
|
3975
|
-
attachmentData,
|
|
3976
|
-
// PouchDB accepts both ArrayBuffer and Buffer
|
|
3977
|
-
attachmentMeta.content_type
|
|
3978
|
-
);
|
|
3979
|
-
result.success = true;
|
|
3980
|
-
} catch (error) {
|
|
3981
|
-
result.error = error instanceof Error ? error.message : String(error);
|
|
3982
|
-
}
|
|
3983
|
-
return result;
|
|
3984
|
-
}
|
|
3985
|
-
/**
|
|
3986
|
-
* Restore CourseConfig document from manifest
|
|
3987
|
-
*/
|
|
3988
|
-
async restoreCourseConfig(manifest, targetDB) {
|
|
3989
|
-
const results = {
|
|
3990
|
-
restored: 0,
|
|
3991
|
-
errors: [],
|
|
3992
|
-
warnings: []
|
|
3993
|
-
};
|
|
3994
|
-
try {
|
|
3995
|
-
if (!manifest.courseConfig) {
|
|
3996
|
-
results.warnings.push(
|
|
3997
|
-
"No courseConfig found in manifest, skipping CourseConfig document creation"
|
|
3998
|
-
);
|
|
3999
|
-
return results;
|
|
4000
|
-
}
|
|
4001
|
-
const courseConfigDoc = {
|
|
4002
|
-
_id: "CourseConfig",
|
|
4003
|
-
...manifest.courseConfig,
|
|
4004
|
-
courseID: manifest.courseId
|
|
4005
|
-
};
|
|
4006
|
-
delete courseConfigDoc._rev;
|
|
4007
|
-
await targetDB.put(courseConfigDoc);
|
|
4008
|
-
results.restored = 1;
|
|
4009
|
-
logger.info(`CourseConfig document created for course: ${manifest.courseId}`);
|
|
4010
|
-
} catch (error) {
|
|
4011
|
-
const errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
|
|
4012
|
-
results.errors.push(`Failed to restore CourseConfig: ${errorMessage}`);
|
|
4013
|
-
logger.error("CourseConfig restoration failed:", error);
|
|
4014
|
-
}
|
|
4015
|
-
return results;
|
|
4016
|
-
}
|
|
4017
|
-
/**
|
|
4018
|
-
* Calculate expected document counts from manifest
|
|
4019
|
-
*/
|
|
4020
|
-
calculateExpectedCounts(manifest) {
|
|
4021
|
-
const counts = {};
|
|
4022
|
-
for (const chunk of manifest.chunks) {
|
|
4023
|
-
counts[chunk.docType] = (counts[chunk.docType] || 0) + chunk.documentCount;
|
|
4024
|
-
}
|
|
4025
|
-
if (manifest.designDocs.length > 0) {
|
|
4026
|
-
counts["_design"] = manifest.designDocs.length;
|
|
4027
|
-
}
|
|
4028
|
-
return counts;
|
|
4029
|
-
}
|
|
4030
|
-
/**
|
|
4031
|
-
* Clean up database after failed migration
|
|
4032
|
-
*/
|
|
4033
|
-
async cleanupFailedMigration(db) {
|
|
4034
|
-
logger.info("Cleaning up failed migration...");
|
|
4035
|
-
try {
|
|
4036
|
-
const allDocs = await db.allDocs();
|
|
4037
|
-
const docsToDelete = allDocs.rows.map((row) => ({
|
|
4038
|
-
_id: row.id,
|
|
4039
|
-
_rev: row.value.rev,
|
|
4040
|
-
_deleted: true
|
|
4041
|
-
}));
|
|
4042
|
-
if (docsToDelete.length > 0) {
|
|
4043
|
-
await db.bulkDocs(docsToDelete);
|
|
4044
|
-
logger.info(`Cleaned up ${docsToDelete.length} documents from failed migration`);
|
|
4045
|
-
}
|
|
4046
|
-
} catch (error) {
|
|
4047
|
-
logger.error("Failed to cleanup documents:", error);
|
|
4048
|
-
throw error;
|
|
4049
|
-
}
|
|
4050
|
-
}
|
|
4051
|
-
/**
|
|
4052
|
-
* Report progress to callback if available
|
|
4053
|
-
*/
|
|
4054
|
-
reportProgress(phase, current, total, message) {
|
|
4055
|
-
if (this.progressCallback) {
|
|
4056
|
-
this.progressCallback({
|
|
4057
|
-
phase,
|
|
4058
|
-
current,
|
|
4059
|
-
total,
|
|
4060
|
-
message
|
|
4061
|
-
});
|
|
4062
|
-
}
|
|
4063
|
-
}
|
|
4064
|
-
/**
|
|
4065
|
-
* Check if a path is a local file path (vs URL)
|
|
4066
|
-
*/
|
|
4067
|
-
isLocalPath(path2) {
|
|
4068
|
-
return !path2.startsWith("http://") && !path2.startsWith("https://");
|
|
4069
|
-
}
|
|
4070
|
-
};
|
|
4071
|
-
}
|
|
4072
|
-
});
|
|
4073
|
-
|
|
4074
|
-
// src/util/migrator/index.ts
|
|
4075
|
-
var init_migrator = __esm({
|
|
4076
|
-
"src/util/migrator/index.ts"() {
|
|
4077
|
-
"use strict";
|
|
4078
|
-
init_StaticToCouchDBMigrator();
|
|
4079
|
-
init_validation();
|
|
4080
|
-
init_FileSystemAdapter();
|
|
4081
|
-
}
|
|
4082
|
-
});
|
|
4083
|
-
|
|
4084
|
-
// src/util/dataDirectory.ts
|
|
4085
|
-
function getAppDataDirectory() {
|
|
4086
|
-
if (ENV.LOCAL_STORAGE_PREFIX) {
|
|
4087
|
-
return path.join(os.homedir(), `.tuilder`, ENV.LOCAL_STORAGE_PREFIX);
|
|
4088
|
-
} else {
|
|
4089
|
-
return path.join(os.homedir(), ".tuilder");
|
|
4090
|
-
}
|
|
4091
|
-
}
|
|
4092
|
-
function getDbPath(dbName) {
|
|
4093
|
-
return path.join(getAppDataDirectory(), dbName);
|
|
4094
|
-
}
|
|
4095
|
-
var path, os;
|
|
4096
|
-
var init_dataDirectory = __esm({
|
|
4097
|
-
"src/util/dataDirectory.ts"() {
|
|
4098
|
-
"use strict";
|
|
4099
|
-
path = __toESM(require("path"), 1);
|
|
4100
|
-
os = __toESM(require("os"), 1);
|
|
4101
|
-
init_logger();
|
|
4102
|
-
init_factory();
|
|
4103
|
-
}
|
|
4104
|
-
});
|
|
4105
|
-
|
|
4106
|
-
// src/util/index.ts
|
|
4107
|
-
var init_util2 = __esm({
|
|
4108
|
-
"src/util/index.ts"() {
|
|
4109
|
-
"use strict";
|
|
4110
|
-
init_Loggable();
|
|
4111
|
-
init_packer();
|
|
4112
|
-
init_migrator();
|
|
4113
|
-
init_dataDirectory();
|
|
4114
|
-
}
|
|
4115
|
-
});
|
|
4116
|
-
|
|
4117
|
-
// src/study/SourceMixer.ts
|
|
4118
|
-
var init_SourceMixer = __esm({
|
|
4119
|
-
"src/study/SourceMixer.ts"() {
|
|
4120
|
-
"use strict";
|
|
4121
|
-
}
|
|
4122
|
-
});
|
|
4123
|
-
|
|
4124
|
-
// src/study/MixerDebugger.ts
|
|
4125
|
-
function printMixerSummary(run) {
|
|
4126
|
-
console.group(`\u{1F3A8} Mixer Run: ${run.mixerType}`);
|
|
4127
|
-
logger.info(`Run ID: ${run.runId}`);
|
|
4128
|
-
logger.info(`Time: ${run.timestamp.toISOString()}`);
|
|
4129
|
-
logger.info(
|
|
4130
|
-
`Config: limit=${run.requestedLimit}${run.quotaPerSource ? `, quota/source=${run.quotaPerSource}` : ""}`
|
|
4131
|
-
);
|
|
4132
|
-
console.group(`\u{1F4E5} Input: ${run.sourceSummaries.length} sources`);
|
|
4133
|
-
for (const src of run.sourceSummaries) {
|
|
4134
|
-
logger.info(
|
|
4135
|
-
` ${src.sourceName || src.sourceId}: ${src.totalCards} cards (${src.reviewCount} reviews, ${src.newCount} new)`
|
|
4136
|
-
);
|
|
4137
|
-
logger.info(` Score range: [${src.scoreRange[0].toFixed(2)}, ${src.scoreRange[1].toFixed(2)}], avg: ${src.avgScore.toFixed(2)}`);
|
|
4138
|
-
}
|
|
4139
|
-
console.groupEnd();
|
|
4140
|
-
console.group(`\u{1F4E4} Output: ${run.finalCount} cards selected (${run.reviewsSelected} reviews, ${run.newSelected} new)`);
|
|
4141
|
-
for (const breakdown of run.sourceBreakdowns) {
|
|
4142
|
-
const name = breakdown.sourceName || breakdown.sourceId;
|
|
4143
|
-
logger.info(
|
|
4144
|
-
` ${name}: ${breakdown.totalSelected} selected (${breakdown.reviewsSelected} reviews, ${breakdown.newSelected} new) - ${breakdown.selectionRate.toFixed(1)}% selection rate`
|
|
4145
|
-
);
|
|
4146
|
-
}
|
|
4147
|
-
console.groupEnd();
|
|
4148
|
-
console.groupEnd();
|
|
4149
|
-
}
|
|
4150
|
-
function mountMixerDebugger() {
|
|
4151
|
-
if (typeof window === "undefined") return;
|
|
4152
|
-
const win = window;
|
|
4153
|
-
win.skuilder = win.skuilder || {};
|
|
4154
|
-
win.skuilder.mixer = mixerDebugAPI;
|
|
4155
|
-
}
|
|
4156
|
-
var runHistory2, mixerDebugAPI;
|
|
4157
|
-
var init_MixerDebugger = __esm({
|
|
4158
|
-
"src/study/MixerDebugger.ts"() {
|
|
4159
|
-
"use strict";
|
|
4160
|
-
init_logger();
|
|
4161
|
-
init_navigators();
|
|
4162
|
-
runHistory2 = [];
|
|
4163
|
-
mixerDebugAPI = {
|
|
4164
|
-
/**
|
|
4165
|
-
* Get raw run history for programmatic access.
|
|
4166
|
-
*/
|
|
4167
|
-
get runs() {
|
|
4168
|
-
return [...runHistory2];
|
|
4169
|
-
},
|
|
4170
|
-
/**
|
|
4171
|
-
* Show summary of a specific mixer run.
|
|
4172
|
-
*/
|
|
4173
|
-
showRun(idOrIndex = 0) {
|
|
4174
|
-
if (runHistory2.length === 0) {
|
|
4175
|
-
logger.info("[Mixer Debug] No runs captured yet.");
|
|
4176
|
-
return;
|
|
4177
|
-
}
|
|
4178
|
-
let run;
|
|
4179
|
-
if (typeof idOrIndex === "number") {
|
|
4180
|
-
run = runHistory2[idOrIndex];
|
|
4181
|
-
if (!run) {
|
|
4182
|
-
logger.info(`[Mixer Debug] No run found at index ${idOrIndex}. History length: ${runHistory2.length}`);
|
|
4183
|
-
return;
|
|
4184
|
-
}
|
|
4185
|
-
} else {
|
|
4186
|
-
run = runHistory2.find((r) => r.runId.endsWith(idOrIndex));
|
|
4187
|
-
if (!run) {
|
|
4188
|
-
logger.info(`[Mixer Debug] No run found matching ID '${idOrIndex}'.`);
|
|
4189
|
-
return;
|
|
4190
|
-
}
|
|
4191
|
-
}
|
|
4192
|
-
printMixerSummary(run);
|
|
4193
|
-
},
|
|
4194
|
-
/**
|
|
4195
|
-
* Show summary of the last mixer run.
|
|
4196
|
-
*/
|
|
4197
|
-
showLastMix() {
|
|
4198
|
-
this.showRun(0);
|
|
4199
|
-
},
|
|
4200
|
-
/**
|
|
4201
|
-
* Explain source balance in the last run.
|
|
4202
|
-
*/
|
|
4203
|
-
explainSourceBalance() {
|
|
4204
|
-
if (runHistory2.length === 0) {
|
|
4205
|
-
logger.info("[Mixer Debug] No runs captured yet.");
|
|
4206
|
-
return;
|
|
4207
|
-
}
|
|
4208
|
-
const run = runHistory2[0];
|
|
4209
|
-
console.group("\u2696\uFE0F Source Balance Analysis");
|
|
4210
|
-
logger.info(`Mixer: ${run.mixerType}`);
|
|
4211
|
-
logger.info(`Requested limit: ${run.requestedLimit}`);
|
|
4212
|
-
if (run.quotaPerSource) {
|
|
4213
|
-
logger.info(`Quota per source: ${run.quotaPerSource}`);
|
|
4214
|
-
}
|
|
4215
|
-
console.group("Input Distribution:");
|
|
4216
|
-
for (const src of run.sourceSummaries) {
|
|
4217
|
-
const name = src.sourceName || src.sourceId;
|
|
4218
|
-
logger.info(`${name}:`);
|
|
4219
|
-
logger.info(` Provided: ${src.totalCards} cards (${src.reviewCount} reviews, ${src.newCount} new)`);
|
|
4220
|
-
logger.info(` Score range: [${src.scoreRange[0].toFixed(2)}, ${src.scoreRange[1].toFixed(2)}]`);
|
|
4221
|
-
}
|
|
4222
|
-
console.groupEnd();
|
|
4223
|
-
console.group("Selection Results:");
|
|
4224
|
-
for (const breakdown of run.sourceBreakdowns) {
|
|
4225
|
-
const name = breakdown.sourceName || breakdown.sourceId;
|
|
4226
|
-
logger.info(`${name}:`);
|
|
4227
|
-
logger.info(
|
|
4228
|
-
` Selected: ${breakdown.totalSelected}/${breakdown.reviewsProvided + breakdown.newProvided} (${breakdown.selectionRate.toFixed(1)}%)`
|
|
4229
|
-
);
|
|
4230
|
-
logger.info(` Reviews: ${breakdown.reviewsSelected}/${breakdown.reviewsProvided}`);
|
|
4231
|
-
logger.info(` New: ${breakdown.newSelected}/${breakdown.newProvided}`);
|
|
4232
|
-
if (breakdown.reviewsProvided > 0 && breakdown.reviewsSelected === 0) {
|
|
4233
|
-
logger.info(` \u26A0\uFE0F Had reviews but none selected!`);
|
|
4234
|
-
}
|
|
4235
|
-
if (breakdown.totalSelected === 0 && breakdown.reviewsProvided + breakdown.newProvided > 0) {
|
|
4236
|
-
logger.info(` \u26A0\uFE0F Had cards but none selected!`);
|
|
4237
|
-
}
|
|
4238
|
-
}
|
|
4239
|
-
console.groupEnd();
|
|
4240
|
-
const selectionRates = run.sourceBreakdowns.map((b) => b.selectionRate);
|
|
4241
|
-
const avgRate = selectionRates.reduce((a, b) => a + b, 0) / selectionRates.length;
|
|
4242
|
-
const maxDeviation = Math.max(...selectionRates.map((r) => Math.abs(r - avgRate)));
|
|
4243
|
-
if (maxDeviation > 20) {
|
|
4244
|
-
logger.info(`
|
|
4245
|
-
\u26A0\uFE0F Significant imbalance detected (max deviation: ${maxDeviation.toFixed(1)}%)`);
|
|
4246
|
-
logger.info("Possible causes:");
|
|
4247
|
-
logger.info(" - Score range differences between sources");
|
|
4248
|
-
logger.info(" - One source has much better quality cards");
|
|
4249
|
-
logger.info(" - Different card availability (reviews vs new)");
|
|
4250
|
-
}
|
|
4251
|
-
console.groupEnd();
|
|
4252
|
-
},
|
|
4253
|
-
/**
|
|
4254
|
-
* Compare score distributions across sources.
|
|
4255
|
-
*/
|
|
4256
|
-
compareScores() {
|
|
4257
|
-
if (runHistory2.length === 0) {
|
|
4258
|
-
logger.info("[Mixer Debug] No runs captured yet.");
|
|
4259
|
-
return;
|
|
4260
|
-
}
|
|
4261
|
-
const run = runHistory2[0];
|
|
4262
|
-
console.group("\u{1F4CA} Score Distribution Comparison");
|
|
4263
|
-
console.table(
|
|
4264
|
-
run.sourceSummaries.map((src) => ({
|
|
4265
|
-
source: src.sourceName || src.sourceId,
|
|
4266
|
-
cards: src.totalCards,
|
|
4267
|
-
min: src.bottomScore.toFixed(3),
|
|
4268
|
-
max: src.topScore.toFixed(3),
|
|
4269
|
-
avg: src.avgScore.toFixed(3),
|
|
4270
|
-
range: (src.topScore - src.bottomScore).toFixed(3)
|
|
4271
|
-
}))
|
|
4272
|
-
);
|
|
4273
|
-
const ranges = run.sourceSummaries.map((s) => s.topScore - s.bottomScore);
|
|
4274
|
-
const avgScores = run.sourceSummaries.map((s) => s.avgScore);
|
|
4275
|
-
const rangeDiff = Math.max(...ranges) - Math.min(...ranges);
|
|
4276
|
-
const avgDiff = Math.max(...avgScores) - Math.min(...avgScores);
|
|
4277
|
-
if (rangeDiff > 0.3 || avgDiff > 0.2) {
|
|
4278
|
-
logger.info("\n\u26A0\uFE0F Significant score distribution differences detected");
|
|
4279
|
-
logger.info(
|
|
4280
|
-
"This may cause one source to dominate selection if using global sorting (not quota-based)"
|
|
4281
|
-
);
|
|
4282
|
-
}
|
|
4283
|
-
console.groupEnd();
|
|
4284
|
-
},
|
|
4285
|
-
/**
|
|
4286
|
-
* Show detailed information for a specific card.
|
|
4287
|
-
*/
|
|
4288
|
-
showCard(cardId) {
|
|
4289
|
-
for (const run of runHistory2) {
|
|
4290
|
-
const card = run.cards.find((c) => c.cardId === cardId);
|
|
4291
|
-
if (card) {
|
|
4292
|
-
const source = run.sourceSummaries.find((s) => s.sourceIndex === card.sourceIndex);
|
|
4293
|
-
console.group(`\u{1F3B4} Card: ${cardId}`);
|
|
4294
|
-
logger.info(`Course: ${card.courseId}`);
|
|
4295
|
-
logger.info(`Source: ${source?.sourceName || source?.sourceId || "unknown"}`);
|
|
4296
|
-
logger.info(`Origin: ${card.origin}`);
|
|
4297
|
-
logger.info(`Score: ${card.score.toFixed(3)}`);
|
|
4298
|
-
if (card.rankInSource) {
|
|
4299
|
-
logger.info(`Rank in source: #${card.rankInSource}`);
|
|
4300
|
-
}
|
|
4301
|
-
if (card.rankInMix) {
|
|
4302
|
-
logger.info(`Rank in mixed results: #${card.rankInMix}`);
|
|
4303
|
-
}
|
|
4304
|
-
logger.info(`Selected: ${card.selected ? "Yes \u2705" : "No \u274C"}`);
|
|
4305
|
-
if (!card.selected && card.rankInSource) {
|
|
4306
|
-
logger.info("\nWhy not selected:");
|
|
4307
|
-
if (run.quotaPerSource && card.rankInSource > run.quotaPerSource) {
|
|
4308
|
-
logger.info(` - Ranked #${card.rankInSource} in source, but quota was ${run.quotaPerSource}`);
|
|
4309
|
-
}
|
|
4310
|
-
logger.info(" - Check score compared to selected cards using .showRun()");
|
|
4311
|
-
}
|
|
4312
|
-
console.groupEnd();
|
|
4313
|
-
return;
|
|
4314
|
-
}
|
|
4315
|
-
}
|
|
4316
|
-
logger.info(`[Mixer Debug] Card '${cardId}' not found in recent runs.`);
|
|
4317
|
-
},
|
|
4318
|
-
/**
|
|
4319
|
-
* Show all runs in compact format.
|
|
4320
|
-
*/
|
|
4321
|
-
listRuns() {
|
|
4322
|
-
if (runHistory2.length === 0) {
|
|
4323
|
-
logger.info("[Mixer Debug] No runs captured yet.");
|
|
4324
|
-
return;
|
|
4325
|
-
}
|
|
4326
|
-
console.table(
|
|
4327
|
-
runHistory2.map((r) => ({
|
|
4328
|
-
id: r.runId.slice(-8),
|
|
4329
|
-
time: r.timestamp.toLocaleTimeString(),
|
|
4330
|
-
mixer: r.mixerType,
|
|
4331
|
-
sources: r.sourceSummaries.length,
|
|
4332
|
-
selected: r.finalCount,
|
|
4333
|
-
reviews: r.reviewsSelected,
|
|
4334
|
-
new: r.newSelected
|
|
4335
|
-
}))
|
|
4336
|
-
);
|
|
4337
|
-
},
|
|
4338
|
-
/**
|
|
4339
|
-
* Export run history as JSON for bug reports.
|
|
4340
|
-
*/
|
|
4341
|
-
export() {
|
|
4342
|
-
const json = JSON.stringify(runHistory2, null, 2);
|
|
4343
|
-
logger.info("[Mixer Debug] Run history exported. Copy the returned string or use:");
|
|
4344
|
-
logger.info(" copy(window.skuilder.mixer.export())");
|
|
4345
|
-
return json;
|
|
4346
|
-
},
|
|
4347
|
-
/**
|
|
4348
|
-
* Clear run history.
|
|
4349
|
-
*/
|
|
4350
|
-
clear() {
|
|
4351
|
-
runHistory2.length = 0;
|
|
4352
|
-
logger.info("[Mixer Debug] Run history cleared.");
|
|
4353
|
-
},
|
|
4354
|
-
/**
|
|
4355
|
-
* Show help.
|
|
4356
|
-
*/
|
|
4357
|
-
help() {
|
|
4358
|
-
logger.info(`
|
|
4359
|
-
\u{1F3A8} Mixer Debug API
|
|
4360
|
-
|
|
4361
|
-
Commands:
|
|
4362
|
-
.showLastMix() Show summary of most recent mixer run
|
|
4363
|
-
.showRun(id|index) Show summary of a specific run (by index or ID suffix)
|
|
4364
|
-
.explainSourceBalance() Analyze source balance and selection patterns
|
|
4365
|
-
.compareScores() Compare score distributions across sources
|
|
4366
|
-
.showCard(cardId) Show mixer decisions for a specific card
|
|
4367
|
-
.listRuns() List all captured runs in table format
|
|
4368
|
-
.export() Export run history as JSON for bug reports
|
|
4369
|
-
.clear() Clear run history
|
|
4370
|
-
.runs Access raw run history array
|
|
4371
|
-
.help() Show this help message
|
|
4372
|
-
|
|
4373
|
-
Example:
|
|
4374
|
-
window.skuilder.mixer.showLastMix()
|
|
4375
|
-
window.skuilder.mixer.explainSourceBalance()
|
|
4376
|
-
window.skuilder.mixer.compareScores()
|
|
4377
|
-
`);
|
|
4378
|
-
}
|
|
4379
|
-
};
|
|
4380
|
-
mountMixerDebugger();
|
|
3068
|
+
"use strict";
|
|
3069
|
+
USER_GOAL_NAVIGATOR_STUB = true;
|
|
4381
3070
|
}
|
|
4382
3071
|
});
|
|
4383
3072
|
|
|
4384
|
-
// src/
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
3073
|
+
// import("./filters/**/*") in src/core/navigators/index.ts
|
|
3074
|
+
var globImport_filters;
|
|
3075
|
+
var init_2 = __esm({
|
|
3076
|
+
'import("./filters/**/*") in src/core/navigators/index.ts'() {
|
|
3077
|
+
globImport_filters = __glob({
|
|
3078
|
+
"./filters/WeightedFilter.ts": () => Promise.resolve().then(() => (init_WeightedFilter(), WeightedFilter_exports)),
|
|
3079
|
+
"./filters/eloDistance.ts": () => Promise.resolve().then(() => (init_eloDistance(), eloDistance_exports)),
|
|
3080
|
+
"./filters/hierarchyDefinition.ts": () => Promise.resolve().then(() => (init_hierarchyDefinition(), hierarchyDefinition_exports)),
|
|
3081
|
+
"./filters/index.ts": () => Promise.resolve().then(() => (init_filters(), filters_exports)),
|
|
3082
|
+
"./filters/inferredPreferenceStub.ts": () => Promise.resolve().then(() => (init_inferredPreferenceStub(), inferredPreferenceStub_exports)),
|
|
3083
|
+
"./filters/interferenceMitigator.ts": () => Promise.resolve().then(() => (init_interferenceMitigator(), interferenceMitigator_exports)),
|
|
3084
|
+
"./filters/relativePriority.ts": () => Promise.resolve().then(() => (init_relativePriority(), relativePriority_exports)),
|
|
3085
|
+
"./filters/types.ts": () => Promise.resolve().then(() => (init_types2(), types_exports2)),
|
|
3086
|
+
"./filters/userGoalStub.ts": () => Promise.resolve().then(() => (init_userGoalStub(), userGoalStub_exports)),
|
|
3087
|
+
"./filters/userTagPreference.ts": () => Promise.resolve().then(() => (init_userTagPreference(), userTagPreference_exports))
|
|
3088
|
+
});
|
|
4389
3089
|
}
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
3090
|
+
});
|
|
3091
|
+
|
|
3092
|
+
// src/core/orchestration/gradient.ts
|
|
3093
|
+
var init_gradient = __esm({
|
|
3094
|
+
"src/core/orchestration/gradient.ts"() {
|
|
3095
|
+
"use strict";
|
|
3096
|
+
init_logger();
|
|
4395
3097
|
}
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
3098
|
+
});
|
|
3099
|
+
|
|
3100
|
+
// src/core/orchestration/learning.ts
|
|
3101
|
+
var init_learning = __esm({
|
|
3102
|
+
"src/core/orchestration/learning.ts"() {
|
|
3103
|
+
"use strict";
|
|
3104
|
+
init_contentNavigationStrategy();
|
|
3105
|
+
init_types_legacy();
|
|
3106
|
+
init_logger();
|
|
4399
3107
|
}
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
logger.info(`[Session Debug] No session found at index ${sessionIndex}`);
|
|
4407
|
-
return;
|
|
3108
|
+
});
|
|
3109
|
+
|
|
3110
|
+
// src/core/orchestration/signal.ts
|
|
3111
|
+
var init_signal = __esm({
|
|
3112
|
+
"src/core/orchestration/signal.ts"() {
|
|
3113
|
+
"use strict";
|
|
4408
3114
|
}
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
3115
|
+
});
|
|
3116
|
+
|
|
3117
|
+
// src/core/orchestration/recording.ts
|
|
3118
|
+
var init_recording = __esm({
|
|
3119
|
+
"src/core/orchestration/recording.ts"() {
|
|
3120
|
+
"use strict";
|
|
3121
|
+
init_signal();
|
|
3122
|
+
init_types_legacy();
|
|
3123
|
+
init_logger();
|
|
4413
3124
|
}
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
score: p.score?.toFixed(3) || "-",
|
|
4423
|
-
time: p.timestamp.toLocaleTimeString()
|
|
4424
|
-
}))
|
|
4425
|
-
);
|
|
3125
|
+
});
|
|
3126
|
+
|
|
3127
|
+
// src/core/orchestration/index.ts
|
|
3128
|
+
function fnv1a(str) {
|
|
3129
|
+
let hash = 2166136261;
|
|
3130
|
+
for (let i = 0; i < str.length; i++) {
|
|
3131
|
+
hash ^= str.charCodeAt(i);
|
|
3132
|
+
hash = Math.imul(hash, 16777619);
|
|
4426
3133
|
}
|
|
4427
|
-
|
|
3134
|
+
return hash >>> 0;
|
|
4428
3135
|
}
|
|
4429
|
-
function
|
|
4430
|
-
const
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
}
|
|
4435
|
-
console.group("\u{1F500} Interleaving Analysis");
|
|
4436
|
-
const courseCounts = /* @__PURE__ */ new Map();
|
|
4437
|
-
const courseOrigins = /* @__PURE__ */ new Map();
|
|
4438
|
-
session.presentations.forEach((p) => {
|
|
4439
|
-
const name = p.courseName || p.courseId;
|
|
4440
|
-
courseCounts.set(name, (courseCounts.get(name) || 0) + 1);
|
|
4441
|
-
if (!courseOrigins.has(name)) {
|
|
4442
|
-
courseOrigins.set(name, { review: 0, new: 0, failed: 0 });
|
|
4443
|
-
}
|
|
4444
|
-
const origins = courseOrigins.get(name);
|
|
4445
|
-
origins[p.origin]++;
|
|
4446
|
-
});
|
|
4447
|
-
logger.info("Course distribution:");
|
|
4448
|
-
console.table(
|
|
4449
|
-
Array.from(courseCounts.entries()).map(([course, count]) => {
|
|
4450
|
-
const origins = courseOrigins.get(course);
|
|
4451
|
-
return {
|
|
4452
|
-
course,
|
|
4453
|
-
total: count,
|
|
4454
|
-
reviews: origins.review,
|
|
4455
|
-
new: origins.new,
|
|
4456
|
-
failed: origins.failed,
|
|
4457
|
-
percentage: (count / session.presentations.length * 100).toFixed(1) + "%"
|
|
4458
|
-
};
|
|
4459
|
-
})
|
|
4460
|
-
);
|
|
4461
|
-
if (session.presentations.length > 0) {
|
|
4462
|
-
logger.info("\nPresentation sequence (first 20):");
|
|
4463
|
-
const sequence = session.presentations.slice(0, 20).map((p, idx) => `${idx + 1}. ${p.courseName || p.courseId.slice(0, 8)} (${p.origin})`).join("\n");
|
|
4464
|
-
logger.info(sequence);
|
|
4465
|
-
}
|
|
4466
|
-
let maxCluster = 0;
|
|
4467
|
-
let currentCluster = 1;
|
|
4468
|
-
let currentCourse = session.presentations[0]?.courseId;
|
|
4469
|
-
for (let i = 1; i < session.presentations.length; i++) {
|
|
4470
|
-
if (session.presentations[i].courseId === currentCourse) {
|
|
4471
|
-
currentCluster++;
|
|
4472
|
-
maxCluster = Math.max(maxCluster, currentCluster);
|
|
4473
|
-
} else {
|
|
4474
|
-
currentCourse = session.presentations[i].courseId;
|
|
4475
|
-
currentCluster = 1;
|
|
4476
|
-
}
|
|
4477
|
-
}
|
|
4478
|
-
if (maxCluster > 3) {
|
|
4479
|
-
logger.info(`
|
|
4480
|
-
\u26A0\uFE0F Detected clustering: max ${maxCluster} cards from same course in a row`);
|
|
4481
|
-
logger.info("This suggests cards are sorted by score rather than round-robin by course.");
|
|
4482
|
-
}
|
|
4483
|
-
console.groupEnd();
|
|
3136
|
+
function computeDeviation(userId, strategyId, salt) {
|
|
3137
|
+
const input = `${userId}:${strategyId}:${salt}`;
|
|
3138
|
+
const hash = fnv1a(input);
|
|
3139
|
+
const normalized = hash / 4294967296;
|
|
3140
|
+
return normalized * 2 - 1;
|
|
4484
3141
|
}
|
|
4485
|
-
function
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
win.skuilder = win.skuilder || {};
|
|
4489
|
-
win.skuilder.session = sessionDebugAPI;
|
|
3142
|
+
function computeSpread(confidence) {
|
|
3143
|
+
const clampedConfidence = Math.max(0, Math.min(1, confidence));
|
|
3144
|
+
return MAX_SPREAD - clampedConfidence * (MAX_SPREAD - MIN_SPREAD);
|
|
4490
3145
|
}
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
showCurrentQueue();
|
|
4516
|
-
},
|
|
4517
|
-
/**
|
|
4518
|
-
* Show presentation history for current or past session.
|
|
4519
|
-
*/
|
|
4520
|
-
showHistory(sessionIndex = 0) {
|
|
4521
|
-
showPresentationHistory(sessionIndex);
|
|
4522
|
-
},
|
|
4523
|
-
/**
|
|
4524
|
-
* Analyze course interleaving pattern.
|
|
4525
|
-
*/
|
|
4526
|
-
showInterleaving(sessionIndex = 0) {
|
|
4527
|
-
showInterleaving(sessionIndex);
|
|
4528
|
-
},
|
|
4529
|
-
/**
|
|
4530
|
-
* List all tracked sessions.
|
|
4531
|
-
*/
|
|
4532
|
-
listSessions() {
|
|
4533
|
-
if (activeSession) {
|
|
4534
|
-
logger.info(`Active session: ${activeSession.sessionId} (${activeSession.presentations.length} cards presented)`);
|
|
4535
|
-
}
|
|
4536
|
-
if (sessionHistory.length === 0) {
|
|
4537
|
-
logger.info("[Session Debug] No completed sessions in history.");
|
|
4538
|
-
return;
|
|
4539
|
-
}
|
|
4540
|
-
console.table(
|
|
4541
|
-
sessionHistory.map((s, idx) => ({
|
|
4542
|
-
index: idx,
|
|
4543
|
-
id: s.sessionId.slice(-8),
|
|
4544
|
-
started: s.startTime.toLocaleTimeString(),
|
|
4545
|
-
ended: s.endTime?.toLocaleTimeString() || "incomplete",
|
|
4546
|
-
cards: s.presentations.length
|
|
4547
|
-
}))
|
|
4548
|
-
);
|
|
4549
|
-
},
|
|
4550
|
-
/**
|
|
4551
|
-
* Export session history as JSON for bug reports.
|
|
4552
|
-
*/
|
|
4553
|
-
export() {
|
|
4554
|
-
const data = {
|
|
4555
|
-
active: activeSession,
|
|
4556
|
-
history: sessionHistory
|
|
4557
|
-
};
|
|
4558
|
-
const json = JSON.stringify(data, null, 2);
|
|
4559
|
-
logger.info("[Session Debug] Session data exported. Copy the returned string or use:");
|
|
4560
|
-
logger.info(" copy(window.skuilder.session.export())");
|
|
4561
|
-
return json;
|
|
4562
|
-
},
|
|
4563
|
-
/**
|
|
4564
|
-
* Clear session history.
|
|
4565
|
-
*/
|
|
4566
|
-
clear() {
|
|
4567
|
-
sessionHistory.length = 0;
|
|
4568
|
-
logger.info("[Session Debug] Session history cleared.");
|
|
4569
|
-
},
|
|
4570
|
-
/**
|
|
4571
|
-
* Show help.
|
|
4572
|
-
*/
|
|
4573
|
-
help() {
|
|
4574
|
-
logger.info(`
|
|
4575
|
-
\u{1F3AF} Session Debug API
|
|
4576
|
-
|
|
4577
|
-
Commands:
|
|
4578
|
-
.showQueue() Show current queue state (active session only)
|
|
4579
|
-
.showHistory(index?) Show presentation history (0=current/last, 1=previous, etc)
|
|
4580
|
-
.showInterleaving(index?) Analyze course interleaving pattern
|
|
4581
|
-
.listSessions() List all tracked sessions
|
|
4582
|
-
.export() Export session data as JSON for bug reports
|
|
4583
|
-
.clear() Clear session history
|
|
4584
|
-
.sessions Access raw session history array
|
|
4585
|
-
.active Access active session (if any)
|
|
4586
|
-
.help() Show this help message
|
|
4587
|
-
|
|
4588
|
-
Example:
|
|
4589
|
-
window.skuilder.session.showHistory()
|
|
4590
|
-
window.skuilder.session.showInterleaving()
|
|
4591
|
-
window.skuilder.session.showQueue()
|
|
4592
|
-
`);
|
|
4593
|
-
}
|
|
3146
|
+
function computeEffectiveWeight(learnable, userId, strategyId, salt) {
|
|
3147
|
+
const deviation = computeDeviation(userId, strategyId, salt);
|
|
3148
|
+
const spread = computeSpread(learnable.confidence);
|
|
3149
|
+
const adjustment = deviation * spread * learnable.weight;
|
|
3150
|
+
const effective = learnable.weight + adjustment;
|
|
3151
|
+
return Math.max(MIN_WEIGHT, Math.min(MAX_WEIGHT, effective));
|
|
3152
|
+
}
|
|
3153
|
+
async function createOrchestrationContext(user, course) {
|
|
3154
|
+
let courseConfig;
|
|
3155
|
+
try {
|
|
3156
|
+
courseConfig = await course.getCourseConfig();
|
|
3157
|
+
} catch (e) {
|
|
3158
|
+
logger.error(`[Orchestration] Failed to load course config: ${e}`);
|
|
3159
|
+
courseConfig = {
|
|
3160
|
+
name: "Unknown",
|
|
3161
|
+
description: "",
|
|
3162
|
+
public: false,
|
|
3163
|
+
deleted: false,
|
|
3164
|
+
creator: "",
|
|
3165
|
+
admins: [],
|
|
3166
|
+
moderators: [],
|
|
3167
|
+
dataShapes: [],
|
|
3168
|
+
questionTypes: [],
|
|
3169
|
+
orchestration: { salt: "default" }
|
|
4594
3170
|
};
|
|
4595
|
-
mountSessionDebugger();
|
|
4596
3171
|
}
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
3172
|
+
const userId = user.getUsername();
|
|
3173
|
+
const salt = courseConfig.orchestration?.salt || "default_salt";
|
|
3174
|
+
return {
|
|
3175
|
+
user,
|
|
3176
|
+
course,
|
|
3177
|
+
userId,
|
|
3178
|
+
courseConfig,
|
|
3179
|
+
getEffectiveWeight(strategyId, learnable) {
|
|
3180
|
+
return computeEffectiveWeight(learnable, userId, strategyId, salt);
|
|
3181
|
+
},
|
|
3182
|
+
getDeviation(strategyId) {
|
|
3183
|
+
return computeDeviation(userId, strategyId, salt);
|
|
3184
|
+
}
|
|
3185
|
+
};
|
|
3186
|
+
}
|
|
3187
|
+
var MIN_SPREAD, MAX_SPREAD, MIN_WEIGHT, MAX_WEIGHT;
|
|
3188
|
+
var init_orchestration = __esm({
|
|
3189
|
+
"src/core/orchestration/index.ts"() {
|
|
4602
3190
|
"use strict";
|
|
4603
|
-
init_SrsService();
|
|
4604
|
-
init_EloService();
|
|
4605
|
-
init_ResponseProcessor();
|
|
4606
|
-
init_CardHydrationService();
|
|
4607
|
-
init_ItemQueue();
|
|
4608
|
-
init_couch();
|
|
4609
|
-
init_recording();
|
|
4610
|
-
init_util2();
|
|
4611
|
-
init_navigators();
|
|
4612
|
-
init_SourceMixer();
|
|
4613
|
-
init_MixerDebugger();
|
|
4614
|
-
init_SessionDebugger();
|
|
4615
3191
|
init_logger();
|
|
3192
|
+
init_gradient();
|
|
3193
|
+
init_learning();
|
|
3194
|
+
init_signal();
|
|
3195
|
+
init_recording();
|
|
3196
|
+
MIN_SPREAD = 0.1;
|
|
3197
|
+
MAX_SPREAD = 0.5;
|
|
3198
|
+
MIN_WEIGHT = 0.1;
|
|
3199
|
+
MAX_WEIGHT = 3;
|
|
4616
3200
|
}
|
|
4617
3201
|
});
|
|
4618
3202
|
|
|
@@ -4633,6 +3217,44 @@ function globMatch(value, pattern) {
|
|
|
4633
3217
|
function cardMatchesTagPattern(card, pattern) {
|
|
4634
3218
|
return (card.tags ?? []).some((tag) => globMatch(tag, pattern));
|
|
4635
3219
|
}
|
|
3220
|
+
function mergeHints2(allHints) {
|
|
3221
|
+
const defined = allHints.filter((h) => h !== null && h !== void 0);
|
|
3222
|
+
if (defined.length === 0) return void 0;
|
|
3223
|
+
const merged = {};
|
|
3224
|
+
const boostTags = {};
|
|
3225
|
+
for (const hints of defined) {
|
|
3226
|
+
for (const [pattern, factor] of Object.entries(hints.boostTags ?? {})) {
|
|
3227
|
+
boostTags[pattern] = (boostTags[pattern] ?? 1) * factor;
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
if (Object.keys(boostTags).length > 0) {
|
|
3231
|
+
merged.boostTags = boostTags;
|
|
3232
|
+
}
|
|
3233
|
+
const boostCards = {};
|
|
3234
|
+
for (const hints of defined) {
|
|
3235
|
+
for (const [pattern, factor] of Object.entries(hints.boostCards ?? {})) {
|
|
3236
|
+
boostCards[pattern] = (boostCards[pattern] ?? 1) * factor;
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
if (Object.keys(boostCards).length > 0) {
|
|
3240
|
+
merged.boostCards = boostCards;
|
|
3241
|
+
}
|
|
3242
|
+
const concatUnique = (field) => {
|
|
3243
|
+
const values = defined.flatMap((h) => h[field] ?? []);
|
|
3244
|
+
if (values.length > 0) {
|
|
3245
|
+
merged[field] = [...new Set(values)];
|
|
3246
|
+
}
|
|
3247
|
+
};
|
|
3248
|
+
concatUnique("requireTags");
|
|
3249
|
+
concatUnique("requireCards");
|
|
3250
|
+
concatUnique("excludeTags");
|
|
3251
|
+
concatUnique("excludeCards");
|
|
3252
|
+
const labels = defined.map((h) => h._label).filter(Boolean);
|
|
3253
|
+
if (labels.length > 0) {
|
|
3254
|
+
merged._label = labels.join("; ");
|
|
3255
|
+
}
|
|
3256
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
3257
|
+
}
|
|
4636
3258
|
function logPipelineConfig(generator, filters) {
|
|
4637
3259
|
const filterList = filters.length > 0 ? "\n - " + filters.map((f) => f.name).join("\n - ") : " none";
|
|
4638
3260
|
logger.info(
|
|
@@ -4696,16 +3318,15 @@ function logCardProvenance(cards, maxCards = 3) {
|
|
|
4696
3318
|
}
|
|
4697
3319
|
}
|
|
4698
3320
|
}
|
|
4699
|
-
var
|
|
3321
|
+
var import_common8, VERBOSE_RESULTS, Pipeline;
|
|
4700
3322
|
var init_Pipeline = __esm({
|
|
4701
3323
|
"src/core/navigators/Pipeline.ts"() {
|
|
4702
3324
|
"use strict";
|
|
4703
|
-
|
|
3325
|
+
import_common8 = require("@vue-skuilder/common");
|
|
4704
3326
|
init_navigators();
|
|
4705
3327
|
init_logger();
|
|
4706
3328
|
init_orchestration();
|
|
4707
3329
|
init_PipelineDebugger();
|
|
4708
|
-
init_SessionController();
|
|
4709
3330
|
VERBOSE_RESULTS = true;
|
|
4710
3331
|
Pipeline = class extends ContentNavigator {
|
|
4711
3332
|
generator;
|
|
@@ -4785,9 +3406,12 @@ var init_Pipeline = __esm({
|
|
|
4785
3406
|
logger.debug(
|
|
4786
3407
|
`[Pipeline] Fetching ${fetchLimit} candidates from generator '${this.generator.name}'`
|
|
4787
3408
|
);
|
|
4788
|
-
|
|
3409
|
+
const generatorResult = await this.generator.getWeightedCards(fetchLimit, context);
|
|
3410
|
+
let cards = generatorResult.cards;
|
|
4789
3411
|
const tGenerate = performance.now();
|
|
4790
3412
|
const generatedCount = cards.length;
|
|
3413
|
+
const mergedHints = mergeHints2([this._ephemeralHints, generatorResult.hints]);
|
|
3414
|
+
this._ephemeralHints = mergedHints ?? null;
|
|
4791
3415
|
let generatorSummaries;
|
|
4792
3416
|
if (this.generator.generators) {
|
|
4793
3417
|
const genMap = /* @__PURE__ */ new Map();
|
|
@@ -4873,7 +3497,7 @@ var init_Pipeline = __esm({
|
|
|
4873
3497
|
} catch (e) {
|
|
4874
3498
|
logger.debug(`[Pipeline] Failed to capture debug run: ${e}`);
|
|
4875
3499
|
}
|
|
4876
|
-
return result;
|
|
3500
|
+
return { cards: result };
|
|
4877
3501
|
}
|
|
4878
3502
|
/**
|
|
4879
3503
|
* Batch hydrate tags for all cards.
|
|
@@ -5028,7 +3652,7 @@ var init_Pipeline = __esm({
|
|
|
5028
3652
|
let userElo = 1e3;
|
|
5029
3653
|
try {
|
|
5030
3654
|
const courseReg = await this.user.getCourseRegDoc(this.course.getCourseID());
|
|
5031
|
-
const courseElo = (0,
|
|
3655
|
+
const courseElo = (0, import_common8.toCourseElo)(courseReg.elo);
|
|
5032
3656
|
userElo = courseElo.global.score;
|
|
5033
3657
|
} catch (e) {
|
|
5034
3658
|
logger.debug(`[Pipeline] Could not get user ELO, using default: ${e}`);
|
|
@@ -5089,7 +3713,7 @@ var init_Pipeline = __esm({
|
|
|
5089
3713
|
*/
|
|
5090
3714
|
async getTagEloStatus(tagFilter) {
|
|
5091
3715
|
const courseReg = await this.user.getCourseRegDoc(this.course.getCourseID());
|
|
5092
|
-
const courseElo = (0,
|
|
3716
|
+
const courseElo = (0, import_common8.toCourseElo)(courseReg.elo);
|
|
5093
3717
|
const result = {};
|
|
5094
3718
|
if (!tagFilter) {
|
|
5095
3719
|
for (const [tag, data] of Object.entries(courseElo.tags)) {
|
|
@@ -5804,11 +4428,11 @@ ${JSON.stringify(config)}
|
|
|
5804
4428
|
function isSuccessRow(row) {
|
|
5805
4429
|
return "doc" in row && row.doc !== null && row.doc !== void 0;
|
|
5806
4430
|
}
|
|
5807
|
-
var
|
|
4431
|
+
var import_common9, CoursesDB, CourseDB;
|
|
5808
4432
|
var init_courseDB = __esm({
|
|
5809
4433
|
"src/impl/couch/courseDB.ts"() {
|
|
5810
4434
|
"use strict";
|
|
5811
|
-
|
|
4435
|
+
import_common9 = require("@vue-skuilder/common");
|
|
5812
4436
|
init_couch();
|
|
5813
4437
|
init_updateQueue();
|
|
5814
4438
|
init_types_legacy();
|
|
@@ -5957,14 +4581,14 @@ var init_courseDB = __esm({
|
|
|
5957
4581
|
docs.rows.forEach((r) => {
|
|
5958
4582
|
if (isSuccessRow(r)) {
|
|
5959
4583
|
if (r.doc && r.doc.elo) {
|
|
5960
|
-
ret.push((0,
|
|
4584
|
+
ret.push((0, import_common9.toCourseElo)(r.doc.elo));
|
|
5961
4585
|
} else {
|
|
5962
4586
|
logger.warn("no elo data for card: " + r.id);
|
|
5963
|
-
ret.push((0,
|
|
4587
|
+
ret.push((0, import_common9.blankCourseElo)());
|
|
5964
4588
|
}
|
|
5965
4589
|
} else {
|
|
5966
4590
|
logger.warn("no elo data for card: " + JSON.stringify(r));
|
|
5967
|
-
ret.push((0,
|
|
4591
|
+
ret.push((0, import_common9.blankCourseElo)());
|
|
5968
4592
|
}
|
|
5969
4593
|
});
|
|
5970
4594
|
return ret;
|
|
@@ -6166,7 +4790,7 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
6166
4790
|
async getCourseTagStubs() {
|
|
6167
4791
|
return getCourseTagStubs(this.id);
|
|
6168
4792
|
}
|
|
6169
|
-
async addNote(codeCourse, shape, data, author, tags, uploads, elo = (0,
|
|
4793
|
+
async addNote(codeCourse, shape, data, author, tags, uploads, elo = (0, import_common9.blankCourseElo)()) {
|
|
6170
4794
|
try {
|
|
6171
4795
|
const resp = await addNote55(this.id, codeCourse, shape, data, author, tags, uploads, elo);
|
|
6172
4796
|
if (resp.ok) {
|
|
@@ -6175,19 +4799,19 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
6175
4799
|
`[courseDB.addNote] Note added but card creation failed: ${resp.cardCreationError}`
|
|
6176
4800
|
);
|
|
6177
4801
|
return {
|
|
6178
|
-
status:
|
|
4802
|
+
status: import_common9.Status.error,
|
|
6179
4803
|
message: `Note was added but no cards were created: ${resp.cardCreationError}`,
|
|
6180
4804
|
id: resp.id
|
|
6181
4805
|
};
|
|
6182
4806
|
}
|
|
6183
4807
|
return {
|
|
6184
|
-
status:
|
|
4808
|
+
status: import_common9.Status.ok,
|
|
6185
4809
|
message: "",
|
|
6186
4810
|
id: resp.id
|
|
6187
4811
|
};
|
|
6188
4812
|
} else {
|
|
6189
4813
|
return {
|
|
6190
|
-
status:
|
|
4814
|
+
status: import_common9.Status.error,
|
|
6191
4815
|
message: "Unexpected error adding note"
|
|
6192
4816
|
};
|
|
6193
4817
|
}
|
|
@@ -6199,7 +4823,7 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
6199
4823
|
message: ${err.message}`
|
|
6200
4824
|
);
|
|
6201
4825
|
return {
|
|
6202
|
-
status:
|
|
4826
|
+
status: import_common9.Status.error,
|
|
6203
4827
|
message: `Error adding note to course. ${e.reason || err.message}`
|
|
6204
4828
|
};
|
|
6205
4829
|
}
|
|
@@ -6338,7 +4962,7 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
6338
4962
|
const courseDoc = (await u.getCourseRegistrationsDoc()).courses.find((c) => {
|
|
6339
4963
|
return c.courseID === this.id;
|
|
6340
4964
|
});
|
|
6341
|
-
targetElo = (0,
|
|
4965
|
+
targetElo = (0, import_common9.EloToNumber)(courseDoc.elo);
|
|
6342
4966
|
} catch {
|
|
6343
4967
|
targetElo = 1e3;
|
|
6344
4968
|
}
|
|
@@ -6473,13 +5097,13 @@ function getClassroomDB(classID, version) {
|
|
|
6473
5097
|
async function getClassroomConfig(classID) {
|
|
6474
5098
|
return await getClassroomDB(classID, "student").get(CLASSROOM_CONFIG);
|
|
6475
5099
|
}
|
|
6476
|
-
var
|
|
5100
|
+
var import_moment2, classroomLookupDBTitle, CLASSROOM_CONFIG, ClassroomDBBase, StudentClassroomDB, TeacherClassroomDB, ClassroomLookupDB;
|
|
6477
5101
|
var init_classroomDB2 = __esm({
|
|
6478
5102
|
"src/impl/couch/classroomDB.ts"() {
|
|
6479
5103
|
"use strict";
|
|
6480
5104
|
init_factory();
|
|
6481
5105
|
init_logger();
|
|
6482
|
-
|
|
5106
|
+
import_moment2 = __toESM(require("moment"), 1);
|
|
6483
5107
|
init_pouchdb_setup();
|
|
6484
5108
|
init_couch();
|
|
6485
5109
|
init_courseDB();
|
|
@@ -6592,14 +5216,14 @@ var init_classroomDB2 = __esm({
|
|
|
6592
5216
|
}
|
|
6593
5217
|
const activeCards = await this._user.getActiveCards();
|
|
6594
5218
|
const activeCardIds = new Set(activeCards.map((ac) => ac.cardID));
|
|
6595
|
-
const now =
|
|
5219
|
+
const now = import_moment2.default.utc();
|
|
6596
5220
|
const assigned = await this.getAssignedContent();
|
|
6597
|
-
const due = assigned.filter((c) => now.isAfter(
|
|
5221
|
+
const due = assigned.filter((c) => now.isAfter(import_moment2.default.utc(c.activeOn, REVIEW_TIME_FORMAT)));
|
|
6598
5222
|
logger.info(`[StudentClassroomDB] Due content: ${JSON.stringify(due)}`);
|
|
6599
5223
|
for (const content of due) {
|
|
6600
5224
|
if (content.type === "course") {
|
|
6601
5225
|
const db = new CourseDB(content.courseID, async () => this._user);
|
|
6602
|
-
const courseCards = await db.getWeightedCards(limit);
|
|
5226
|
+
const { cards: courseCards } = await db.getWeightedCards(limit);
|
|
6603
5227
|
for (const card of courseCards) {
|
|
6604
5228
|
if (!activeCardIds.has(card.cardId)) {
|
|
6605
5229
|
weighted.push({
|
|
@@ -6662,7 +5286,7 @@ var init_classroomDB2 = __esm({
|
|
|
6662
5286
|
logger.info(
|
|
6663
5287
|
`[StudentClassroomDB] New cards from classroom ${this._cfg.name}: ${weighted.length} total (reviews + new)`
|
|
6664
5288
|
);
|
|
6665
|
-
return weighted.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
5289
|
+
return { cards: weighted.sort((a, b) => b.score - a.score).slice(0, limit) };
|
|
6666
5290
|
}
|
|
6667
5291
|
};
|
|
6668
5292
|
TeacherClassroomDB = class _TeacherClassroomDB extends ClassroomDBBase {
|
|
@@ -6720,8 +5344,8 @@ var init_classroomDB2 = __esm({
|
|
|
6720
5344
|
type: "tag",
|
|
6721
5345
|
_id: id,
|
|
6722
5346
|
assignedBy: content.assignedBy,
|
|
6723
|
-
assignedOn:
|
|
6724
|
-
activeOn: content.activeOn ||
|
|
5347
|
+
assignedOn: import_moment2.default.utc(),
|
|
5348
|
+
activeOn: content.activeOn || import_moment2.default.utc()
|
|
6725
5349
|
});
|
|
6726
5350
|
} else {
|
|
6727
5351
|
put = await this._db.put({
|
|
@@ -6729,8 +5353,8 @@ var init_classroomDB2 = __esm({
|
|
|
6729
5353
|
type: "course",
|
|
6730
5354
|
_id: id,
|
|
6731
5355
|
assignedBy: content.assignedBy,
|
|
6732
|
-
assignedOn:
|
|
6733
|
-
activeOn: content.activeOn ||
|
|
5356
|
+
assignedOn: import_moment2.default.utc(),
|
|
5357
|
+
activeOn: content.activeOn || import_moment2.default.utc()
|
|
6734
5358
|
});
|
|
6735
5359
|
}
|
|
6736
5360
|
if (put.ok) {
|
|
@@ -6750,11 +5374,11 @@ var init_classroomDB2 = __esm({
|
|
|
6750
5374
|
});
|
|
6751
5375
|
|
|
6752
5376
|
// src/study/TagFilteredContentSource.ts
|
|
6753
|
-
var
|
|
5377
|
+
var import_common10, TagFilteredContentSource;
|
|
6754
5378
|
var init_TagFilteredContentSource = __esm({
|
|
6755
5379
|
"src/study/TagFilteredContentSource.ts"() {
|
|
6756
5380
|
"use strict";
|
|
6757
|
-
|
|
5381
|
+
import_common10 = require("@vue-skuilder/common");
|
|
6758
5382
|
init_courseDB();
|
|
6759
5383
|
init_logger();
|
|
6760
5384
|
TagFilteredContentSource = class {
|
|
@@ -6840,9 +5464,9 @@ var init_TagFilteredContentSource = __esm({
|
|
|
6840
5464
|
* @returns Cards sorted by score descending (all scores = 1.0)
|
|
6841
5465
|
*/
|
|
6842
5466
|
async getWeightedCards(limit) {
|
|
6843
|
-
if (!(0,
|
|
5467
|
+
if (!(0, import_common10.hasActiveFilter)(this.filter)) {
|
|
6844
5468
|
logger.warn("[TagFilteredContentSource] getWeightedCards called with no active filter");
|
|
6845
|
-
return [];
|
|
5469
|
+
return { cards: [] };
|
|
6846
5470
|
}
|
|
6847
5471
|
const eligibleCardIds = await this.resolveFilteredCardIds();
|
|
6848
5472
|
const activeCards = await this.user.getActiveCards();
|
|
@@ -6894,7 +5518,7 @@ var init_TagFilteredContentSource = __esm({
|
|
|
6894
5518
|
}
|
|
6895
5519
|
]
|
|
6896
5520
|
}));
|
|
6897
|
-
return [...reviewWeighted, ...newCardWeighted].slice(0, limit);
|
|
5521
|
+
return { cards: [...reviewWeighted, ...newCardWeighted].slice(0, limit) };
|
|
6898
5522
|
}
|
|
6899
5523
|
/**
|
|
6900
5524
|
* Clears the cached resolved card IDs.
|
|
@@ -6928,19 +5552,19 @@ async function getStudySource(source, user) {
|
|
|
6928
5552
|
if (source.type === "classroom") {
|
|
6929
5553
|
return await StudentClassroomDB.factory(source.id, user);
|
|
6930
5554
|
} else {
|
|
6931
|
-
if ((0,
|
|
5555
|
+
if ((0, import_common11.hasActiveFilter)(source.tagFilter)) {
|
|
6932
5556
|
return new TagFilteredContentSource(source.id, source.tagFilter, user);
|
|
6933
5557
|
}
|
|
6934
5558
|
return getDataLayer().getCourseDB(source.id);
|
|
6935
5559
|
}
|
|
6936
5560
|
}
|
|
6937
|
-
var
|
|
5561
|
+
var import_common11;
|
|
6938
5562
|
var init_contentSource = __esm({
|
|
6939
5563
|
"src/core/interfaces/contentSource.ts"() {
|
|
6940
5564
|
"use strict";
|
|
6941
5565
|
init_factory();
|
|
6942
5566
|
init_classroomDB2();
|
|
6943
|
-
|
|
5567
|
+
import_common11 = require("@vue-skuilder/common");
|
|
6944
5568
|
init_TagFilteredContentSource();
|
|
6945
5569
|
}
|
|
6946
5570
|
});
|
|
@@ -7003,18 +5627,29 @@ var init_userOutcome = __esm({
|
|
|
7003
5627
|
}
|
|
7004
5628
|
});
|
|
7005
5629
|
|
|
5630
|
+
// src/core/util/index.ts
|
|
5631
|
+
function getCardHistoryID(courseID, cardID) {
|
|
5632
|
+
return `${DocTypePrefixes["CARDRECORD" /* CARDRECORD */]}-${courseID}-${cardID}`;
|
|
5633
|
+
}
|
|
5634
|
+
var init_util = __esm({
|
|
5635
|
+
"src/core/util/index.ts"() {
|
|
5636
|
+
"use strict";
|
|
5637
|
+
init_types_legacy();
|
|
5638
|
+
}
|
|
5639
|
+
});
|
|
5640
|
+
|
|
7006
5641
|
// src/core/bulkImport/cardProcessor.ts
|
|
7007
|
-
var
|
|
5642
|
+
var import_common12;
|
|
7008
5643
|
var init_cardProcessor = __esm({
|
|
7009
5644
|
"src/core/bulkImport/cardProcessor.ts"() {
|
|
7010
5645
|
"use strict";
|
|
7011
|
-
|
|
5646
|
+
import_common12 = require("@vue-skuilder/common");
|
|
7012
5647
|
init_logger();
|
|
7013
5648
|
}
|
|
7014
5649
|
});
|
|
7015
5650
|
|
|
7016
5651
|
// src/core/bulkImport/types.ts
|
|
7017
|
-
var
|
|
5652
|
+
var init_types3 = __esm({
|
|
7018
5653
|
"src/core/bulkImport/types.ts"() {
|
|
7019
5654
|
"use strict";
|
|
7020
5655
|
}
|
|
@@ -7025,7 +5660,29 @@ var init_bulkImport = __esm({
|
|
|
7025
5660
|
"src/core/bulkImport/index.ts"() {
|
|
7026
5661
|
"use strict";
|
|
7027
5662
|
init_cardProcessor();
|
|
7028
|
-
|
|
5663
|
+
init_types3();
|
|
5664
|
+
}
|
|
5665
|
+
});
|
|
5666
|
+
|
|
5667
|
+
// src/util/dataDirectory.ts
|
|
5668
|
+
function getAppDataDirectory() {
|
|
5669
|
+
if (ENV.LOCAL_STORAGE_PREFIX) {
|
|
5670
|
+
return path.join(os.homedir(), `.tuilder`, ENV.LOCAL_STORAGE_PREFIX);
|
|
5671
|
+
} else {
|
|
5672
|
+
return path.join(os.homedir(), ".tuilder");
|
|
5673
|
+
}
|
|
5674
|
+
}
|
|
5675
|
+
function getDbPath(dbName) {
|
|
5676
|
+
return path.join(getAppDataDirectory(), dbName);
|
|
5677
|
+
}
|
|
5678
|
+
var path, os;
|
|
5679
|
+
var init_dataDirectory = __esm({
|
|
5680
|
+
"src/util/dataDirectory.ts"() {
|
|
5681
|
+
"use strict";
|
|
5682
|
+
path = __toESM(require("path"), 1);
|
|
5683
|
+
os = __toESM(require("os"), 1);
|
|
5684
|
+
init_logger();
|
|
5685
|
+
init_factory();
|
|
7029
5686
|
}
|
|
7030
5687
|
});
|
|
7031
5688
|
|
|
@@ -7057,7 +5714,7 @@ function getStartAndEndKeys2(key) {
|
|
|
7057
5714
|
};
|
|
7058
5715
|
}
|
|
7059
5716
|
function updateGuestAccountExpirationDate(guestDB) {
|
|
7060
|
-
const currentTime =
|
|
5717
|
+
const currentTime = import_moment3.default.utc();
|
|
7061
5718
|
const expirationDate = currentTime.add(2, "months").toISOString();
|
|
7062
5719
|
const expiryDocID2 = "GuestAccountExpirationDate";
|
|
7063
5720
|
void guestDB.get(expiryDocID2).then((doc) => {
|
|
@@ -7082,7 +5739,7 @@ function getLocalUserDB(username) {
|
|
|
7082
5739
|
}
|
|
7083
5740
|
}
|
|
7084
5741
|
function scheduleCardReviewLocal(userDB, review) {
|
|
7085
|
-
const now =
|
|
5742
|
+
const now = import_moment3.default.utc();
|
|
7086
5743
|
logger.info(`Scheduling for review in: ${review.time.diff(now, "h") / 24} days`);
|
|
7087
5744
|
void userDB.put({
|
|
7088
5745
|
_id: DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */] + review.time.format(REVIEW_TIME_FORMAT2),
|
|
@@ -7105,11 +5762,11 @@ async function removeScheduledCardReviewLocal(userDB, reviewDocID) {
|
|
|
7105
5762
|
${JSON.stringify(err)}`);
|
|
7106
5763
|
});
|
|
7107
5764
|
}
|
|
7108
|
-
var
|
|
5765
|
+
var import_moment3, REVIEW_TIME_FORMAT2, log2;
|
|
7109
5766
|
var init_userDBHelpers = __esm({
|
|
7110
5767
|
"src/impl/common/userDBHelpers.ts"() {
|
|
7111
5768
|
"use strict";
|
|
7112
|
-
|
|
5769
|
+
import_moment3 = __toESM(require("moment"), 1);
|
|
7113
5770
|
init_core();
|
|
7114
5771
|
init_logger();
|
|
7115
5772
|
init_pouchdb_setup();
|
|
@@ -7469,11 +6126,11 @@ var init_core = __esm({
|
|
|
7469
6126
|
});
|
|
7470
6127
|
|
|
7471
6128
|
// src/impl/couch/user-course-relDB.ts
|
|
7472
|
-
var
|
|
6129
|
+
var import_moment4, UsrCrsData;
|
|
7473
6130
|
var init_user_course_relDB = __esm({
|
|
7474
6131
|
"src/impl/couch/user-course-relDB.ts"() {
|
|
7475
6132
|
"use strict";
|
|
7476
|
-
|
|
6133
|
+
import_moment4 = __toESM(require("moment"), 1);
|
|
7477
6134
|
init_logger();
|
|
7478
6135
|
UsrCrsData = class {
|
|
7479
6136
|
user;
|
|
@@ -7483,11 +6140,11 @@ var init_user_course_relDB = __esm({
|
|
|
7483
6140
|
this._courseId = courseId;
|
|
7484
6141
|
}
|
|
7485
6142
|
async getReviewsForcast(daysCount) {
|
|
7486
|
-
const time =
|
|
6143
|
+
const time = import_moment4.default.utc().add(daysCount, "days");
|
|
7487
6144
|
return this.getReviewstoDate(time);
|
|
7488
6145
|
}
|
|
7489
6146
|
async getPendingReviews() {
|
|
7490
|
-
const now =
|
|
6147
|
+
const now = import_moment4.default.utc();
|
|
7491
6148
|
return this.getReviewstoDate(now);
|
|
7492
6149
|
}
|
|
7493
6150
|
async getScheduledReviewCount() {
|
|
@@ -7523,7 +6180,7 @@ var init_user_course_relDB = __esm({
|
|
|
7523
6180
|
`Fetching ${this.user.getUsername()}'s scheduled reviews for course ${this._courseId}.`
|
|
7524
6181
|
);
|
|
7525
6182
|
return allReviews.filter((review) => {
|
|
7526
|
-
const reviewTime =
|
|
6183
|
+
const reviewTime = import_moment4.default.utc(review.reviewTime);
|
|
7527
6184
|
return targetDate.isAfter(reviewTime);
|
|
7528
6185
|
});
|
|
7529
6186
|
}
|
|
@@ -7705,14 +6362,14 @@ async function dropUserFromClassroom(user, classID) {
|
|
|
7705
6362
|
async function getUserClassrooms(user) {
|
|
7706
6363
|
return getOrCreateClassroomRegistrationsDoc(user);
|
|
7707
6364
|
}
|
|
7708
|
-
var
|
|
6365
|
+
var import_common13, import_moment5, log3, BaseUser, userCoursesDoc, userClassroomsDoc;
|
|
7709
6366
|
var init_BaseUserDB = __esm({
|
|
7710
6367
|
"src/impl/common/BaseUserDB.ts"() {
|
|
7711
6368
|
"use strict";
|
|
7712
6369
|
init_core();
|
|
7713
6370
|
init_util();
|
|
7714
|
-
|
|
7715
|
-
|
|
6371
|
+
import_common13 = require("@vue-skuilder/common");
|
|
6372
|
+
import_moment5 = __toESM(require("moment"), 1);
|
|
7716
6373
|
init_types_legacy();
|
|
7717
6374
|
init_logger();
|
|
7718
6375
|
init_userDBHelpers();
|
|
@@ -7761,7 +6418,7 @@ Currently logged-in as ${this._username}.`
|
|
|
7761
6418
|
);
|
|
7762
6419
|
}
|
|
7763
6420
|
const result = await this.syncStrategy.createAccount(username, password);
|
|
7764
|
-
if (result.status ===
|
|
6421
|
+
if (result.status === import_common13.Status.ok) {
|
|
7765
6422
|
log3(`Account created successfully, updating username to ${username}`);
|
|
7766
6423
|
this._username = username;
|
|
7767
6424
|
try {
|
|
@@ -7803,7 +6460,7 @@ Currently logged-in as ${this._username}.`
|
|
|
7803
6460
|
async resetUserData() {
|
|
7804
6461
|
if (this.syncStrategy.canAuthenticate()) {
|
|
7805
6462
|
return {
|
|
7806
|
-
status:
|
|
6463
|
+
status: import_common13.Status.error,
|
|
7807
6464
|
error: "Reset user data is only available for local-only mode. Use logout instead for remote sync."
|
|
7808
6465
|
};
|
|
7809
6466
|
}
|
|
@@ -7825,11 +6482,11 @@ Currently logged-in as ${this._username}.`
|
|
|
7825
6482
|
await localDB.bulkDocs(docsToDelete);
|
|
7826
6483
|
}
|
|
7827
6484
|
await this.init();
|
|
7828
|
-
return { status:
|
|
6485
|
+
return { status: import_common13.Status.ok };
|
|
7829
6486
|
} catch (error) {
|
|
7830
6487
|
logger.error("Failed to reset user data:", error);
|
|
7831
6488
|
return {
|
|
7832
|
-
status:
|
|
6489
|
+
status: import_common13.Status.error,
|
|
7833
6490
|
error: error instanceof Error ? error.message : "Unknown error during reset"
|
|
7834
6491
|
};
|
|
7835
6492
|
}
|
|
@@ -7976,7 +6633,7 @@ Currently logged-in as ${this._username}.`
|
|
|
7976
6633
|
);
|
|
7977
6634
|
return reviews.rows.filter((r) => {
|
|
7978
6635
|
if (r.id.startsWith(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */])) {
|
|
7979
|
-
const date =
|
|
6636
|
+
const date = import_moment5.default.utc(
|
|
7980
6637
|
r.id.substr(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */].length),
|
|
7981
6638
|
REVIEW_TIME_FORMAT2
|
|
7982
6639
|
);
|
|
@@ -7989,11 +6646,11 @@ Currently logged-in as ${this._username}.`
|
|
|
7989
6646
|
}).map((r) => r.doc);
|
|
7990
6647
|
}
|
|
7991
6648
|
async getReviewsForcast(daysCount) {
|
|
7992
|
-
const time =
|
|
6649
|
+
const time = import_moment5.default.utc().add(daysCount, "days");
|
|
7993
6650
|
return this.getReviewstoDate(time);
|
|
7994
6651
|
}
|
|
7995
6652
|
async getPendingReviews(course_id) {
|
|
7996
|
-
const now =
|
|
6653
|
+
const now = import_moment5.default.utc();
|
|
7997
6654
|
return this.getReviewstoDate(now, course_id);
|
|
7998
6655
|
}
|
|
7999
6656
|
async getScheduledReviewCount(course_id) {
|
|
@@ -8280,7 +6937,7 @@ Currently logged-in as ${this._username}.`
|
|
|
8280
6937
|
*/
|
|
8281
6938
|
async putCardRecord(record) {
|
|
8282
6939
|
const cardHistoryID = getCardHistoryID(record.courseID, record.cardID);
|
|
8283
|
-
record.timeStamp =
|
|
6940
|
+
record.timeStamp = import_moment5.default.utc(record.timeStamp).toString();
|
|
8284
6941
|
try {
|
|
8285
6942
|
const cardHistory = await this.update(
|
|
8286
6943
|
cardHistoryID,
|
|
@@ -8296,7 +6953,7 @@ Currently logged-in as ${this._username}.`
|
|
|
8296
6953
|
const ret = {
|
|
8297
6954
|
...record2
|
|
8298
6955
|
};
|
|
8299
|
-
ret.timeStamp =
|
|
6956
|
+
ret.timeStamp = import_moment5.default.utc(record2.timeStamp);
|
|
8300
6957
|
return ret;
|
|
8301
6958
|
});
|
|
8302
6959
|
return cardHistory;
|
|
@@ -9012,14 +7669,14 @@ var init_auth = __esm({
|
|
|
9012
7669
|
});
|
|
9013
7670
|
|
|
9014
7671
|
// src/impl/couch/CouchDBSyncStrategy.ts
|
|
9015
|
-
var
|
|
7672
|
+
var import_common15, log4, CouchDBSyncStrategy;
|
|
9016
7673
|
var init_CouchDBSyncStrategy = __esm({
|
|
9017
7674
|
"src/impl/couch/CouchDBSyncStrategy.ts"() {
|
|
9018
7675
|
"use strict";
|
|
9019
7676
|
init_factory();
|
|
9020
7677
|
init_types_legacy();
|
|
9021
7678
|
init_logger();
|
|
9022
|
-
|
|
7679
|
+
import_common15 = require("@vue-skuilder/common");
|
|
9023
7680
|
init_common();
|
|
9024
7681
|
init_pouchdb_setup();
|
|
9025
7682
|
init_couch();
|
|
@@ -9090,32 +7747,32 @@ var init_CouchDBSyncStrategy = __esm({
|
|
|
9090
7747
|
}
|
|
9091
7748
|
}
|
|
9092
7749
|
return {
|
|
9093
|
-
status:
|
|
7750
|
+
status: import_common15.Status.ok,
|
|
9094
7751
|
error: void 0
|
|
9095
7752
|
};
|
|
9096
7753
|
} else {
|
|
9097
7754
|
return {
|
|
9098
|
-
status:
|
|
7755
|
+
status: import_common15.Status.error,
|
|
9099
7756
|
error: "Failed to log in after account creation"
|
|
9100
7757
|
};
|
|
9101
7758
|
}
|
|
9102
7759
|
} else {
|
|
9103
7760
|
logger.warn(`Signup not OK: ${JSON.stringify(signupRequest)}`);
|
|
9104
7761
|
return {
|
|
9105
|
-
status:
|
|
7762
|
+
status: import_common15.Status.error,
|
|
9106
7763
|
error: "Account creation failed"
|
|
9107
7764
|
};
|
|
9108
7765
|
}
|
|
9109
7766
|
} catch (e) {
|
|
9110
7767
|
if (e.reason === "Document update conflict.") {
|
|
9111
7768
|
return {
|
|
9112
|
-
status:
|
|
7769
|
+
status: import_common15.Status.error,
|
|
9113
7770
|
error: "This username is taken!"
|
|
9114
7771
|
};
|
|
9115
7772
|
}
|
|
9116
7773
|
logger.error(`Error on signup: ${JSON.stringify(e)}`);
|
|
9117
7774
|
return {
|
|
9118
|
-
status:
|
|
7775
|
+
status: import_common15.Status.error,
|
|
9119
7776
|
error: e.message || "Unknown error during account creation"
|
|
9120
7777
|
};
|
|
9121
7778
|
}
|
|
@@ -9355,7 +8012,7 @@ async function usernameIsAvailable(username) {
|
|
|
9355
8012
|
}
|
|
9356
8013
|
}
|
|
9357
8014
|
function updateGuestAccountExpirationDate2(guestDB) {
|
|
9358
|
-
const currentTime =
|
|
8015
|
+
const currentTime = import_moment6.default.utc();
|
|
9359
8016
|
const expirationDate = currentTime.add(2, "months").toISOString();
|
|
9360
8017
|
void guestDB.get(expiryDocID).then((doc) => {
|
|
9361
8018
|
return guestDB.put({
|
|
@@ -9418,7 +8075,7 @@ function getCouchUserDB(username) {
|
|
|
9418
8075
|
return ret;
|
|
9419
8076
|
}
|
|
9420
8077
|
function scheduleCardReview(review) {
|
|
9421
|
-
const now =
|
|
8078
|
+
const now = import_moment6.default.utc();
|
|
9422
8079
|
logger.info(`Scheduling for review in: ${review.time.diff(now, "h") / 24} days`);
|
|
9423
8080
|
void getCouchUserDB(review.user).put({
|
|
9424
8081
|
_id: DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */] + review.time.format(REVIEW_TIME_FORMAT),
|
|
@@ -9447,13 +8104,13 @@ function getStartAndEndKeys(key) {
|
|
|
9447
8104
|
endkey: key + "\uFFF0"
|
|
9448
8105
|
};
|
|
9449
8106
|
}
|
|
9450
|
-
var import_cross_fetch2,
|
|
8107
|
+
var import_cross_fetch2, import_moment6, import_process, isBrowser, expiryDocID, GUEST_LOCAL_DB, localUserDB, pouchDBincludeCredentialsConfig, REVIEW_TIME_FORMAT;
|
|
9451
8108
|
var init_couch = __esm({
|
|
9452
8109
|
"src/impl/couch/index.ts"() {
|
|
9453
8110
|
init_factory();
|
|
9454
8111
|
init_types_legacy();
|
|
9455
8112
|
import_cross_fetch2 = __toESM(require("cross-fetch"), 1);
|
|
9456
|
-
|
|
8113
|
+
import_moment6 = __toESM(require("moment"), 1);
|
|
9457
8114
|
init_logger();
|
|
9458
8115
|
init_pouchdb_setup();
|
|
9459
8116
|
import_process = __toESM(require("process"), 1);
|