@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
|
@@ -742,6 +742,81 @@ var init_PipelineDebugger = __esm({
|
|
|
742
742
|
}
|
|
743
743
|
console.groupEnd();
|
|
744
744
|
},
|
|
745
|
+
/**
|
|
746
|
+
* Show prescribed-related cards from the most recent run.
|
|
747
|
+
*
|
|
748
|
+
* Highlights:
|
|
749
|
+
* - cards directly generated by the prescribed strategy
|
|
750
|
+
* - blocked prescribed targets mentioned in provenance
|
|
751
|
+
* - support tags resolved for blocked targets
|
|
752
|
+
*
|
|
753
|
+
* @param groupId - Optional prescribed group ID filter (e.g. 'intro-core')
|
|
754
|
+
*/
|
|
755
|
+
showPrescribed(groupId) {
|
|
756
|
+
if (runHistory.length === 0) {
|
|
757
|
+
logger.info("[Pipeline Debug] No runs captured yet.");
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const run = runHistory[0];
|
|
761
|
+
const prescribedCards = run.cards.filter(
|
|
762
|
+
(c) => c.provenance.some((p) => p.strategy === "prescribed")
|
|
763
|
+
);
|
|
764
|
+
console.group(`\u{1F9ED} Prescribed Debug (${run.courseId})`);
|
|
765
|
+
if (prescribedCards.length === 0) {
|
|
766
|
+
logger.info("No prescribed-generated cards were present in the most recent run.");
|
|
767
|
+
console.groupEnd();
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
const rows = prescribedCards.map((card) => {
|
|
771
|
+
const prescribedProv = card.provenance.find((p) => p.strategy === "prescribed");
|
|
772
|
+
const reason = prescribedProv?.reason ?? "";
|
|
773
|
+
const parsedGroup = reason.match(/group=([^;]+)/)?.[1] ?? "unknown";
|
|
774
|
+
const mode = reason.match(/mode=([^;]+)/)?.[1] ?? "unknown";
|
|
775
|
+
const blocked = reason.match(/blocked=([^;]+)/)?.[1] ?? "unknown";
|
|
776
|
+
const blockedTargets = reason.match(/blockedTargets=([^;]+)/)?.[1] ?? "none";
|
|
777
|
+
const supportTags = reason.match(/supportTags=([^;]+)/)?.[1] ?? "none";
|
|
778
|
+
const multiplier = reason.match(/multiplier=([^;]+)/)?.[1] ?? "unknown";
|
|
779
|
+
return {
|
|
780
|
+
group: parsedGroup,
|
|
781
|
+
mode,
|
|
782
|
+
cardId: card.cardId,
|
|
783
|
+
selected: card.selected ? "yes" : "no",
|
|
784
|
+
finalScore: card.finalScore.toFixed(3),
|
|
785
|
+
blocked,
|
|
786
|
+
blockedTargets,
|
|
787
|
+
supportTags,
|
|
788
|
+
multiplier
|
|
789
|
+
};
|
|
790
|
+
}).filter((row) => !groupId || row.group === groupId).sort((a, b) => Number(b.finalScore) - Number(a.finalScore));
|
|
791
|
+
if (rows.length === 0) {
|
|
792
|
+
logger.info(
|
|
793
|
+
`[Pipeline Debug] No prescribed cards matched group '${groupId}' in the most recent run.`
|
|
794
|
+
);
|
|
795
|
+
console.groupEnd();
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
console.table(rows);
|
|
799
|
+
const selectedRows = rows.filter((r) => r.selected === "yes");
|
|
800
|
+
const blockedTargetSet = /* @__PURE__ */ new Set();
|
|
801
|
+
const supportTagSet = /* @__PURE__ */ new Set();
|
|
802
|
+
for (const row of rows) {
|
|
803
|
+
if (row.blockedTargets && row.blockedTargets !== "none") {
|
|
804
|
+
row.blockedTargets.split("|").filter(Boolean).forEach((t) => blockedTargetSet.add(t));
|
|
805
|
+
}
|
|
806
|
+
if (row.supportTags && row.supportTags !== "none") {
|
|
807
|
+
row.supportTags.split("|").filter(Boolean).forEach((t) => supportTagSet.add(t));
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
logger.info(`Prescribed cards in run: ${rows.length}`);
|
|
811
|
+
logger.info(`Selected prescribed cards: ${selectedRows.length}`);
|
|
812
|
+
logger.info(
|
|
813
|
+
`Blocked prescribed targets referenced: ${blockedTargetSet.size > 0 ? [...blockedTargetSet].join(", ") : "none"}`
|
|
814
|
+
);
|
|
815
|
+
logger.info(
|
|
816
|
+
`Resolved support tags referenced: ${supportTagSet.size > 0 ? [...supportTagSet].join(", ") : "none"}`
|
|
817
|
+
);
|
|
818
|
+
console.groupEnd();
|
|
819
|
+
},
|
|
745
820
|
/**
|
|
746
821
|
* Show all runs in compact format.
|
|
747
822
|
*/
|
|
@@ -890,6 +965,7 @@ Commands:
|
|
|
890
965
|
.diagnoseCardSpace() Scan full card space through filters (async)
|
|
891
966
|
.showRegistry() Show navigator registry (classes + roles)
|
|
892
967
|
.showStrategies() Show registry + strategy mapping from last run
|
|
968
|
+
.showPrescribed(id?) Show prescribed-generated cards and blocked/support details from last run
|
|
893
969
|
.listRuns() List all captured runs in table format
|
|
894
970
|
.export() Export run history as JSON for bug reports
|
|
895
971
|
.clear() Clear run history
|
|
@@ -913,6 +989,44 @@ __export(CompositeGenerator_exports, {
|
|
|
913
989
|
AggregationMode: () => AggregationMode,
|
|
914
990
|
default: () => CompositeGenerator
|
|
915
991
|
});
|
|
992
|
+
function mergeHints(allHints) {
|
|
993
|
+
const defined = allHints.filter((h) => h !== void 0);
|
|
994
|
+
if (defined.length === 0) return void 0;
|
|
995
|
+
const merged = {};
|
|
996
|
+
const boostTags = {};
|
|
997
|
+
for (const hints of defined) {
|
|
998
|
+
for (const [pattern, factor] of Object.entries(hints.boostTags ?? {})) {
|
|
999
|
+
boostTags[pattern] = (boostTags[pattern] ?? 1) * factor;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
if (Object.keys(boostTags).length > 0) {
|
|
1003
|
+
merged.boostTags = boostTags;
|
|
1004
|
+
}
|
|
1005
|
+
const boostCards = {};
|
|
1006
|
+
for (const hints of defined) {
|
|
1007
|
+
for (const [pattern, factor] of Object.entries(hints.boostCards ?? {})) {
|
|
1008
|
+
boostCards[pattern] = (boostCards[pattern] ?? 1) * factor;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
if (Object.keys(boostCards).length > 0) {
|
|
1012
|
+
merged.boostCards = boostCards;
|
|
1013
|
+
}
|
|
1014
|
+
const concatUnique = (field) => {
|
|
1015
|
+
const values = defined.flatMap((h) => h[field] ?? []);
|
|
1016
|
+
if (values.length > 0) {
|
|
1017
|
+
merged[field] = [...new Set(values)];
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
concatUnique("requireTags");
|
|
1021
|
+
concatUnique("requireCards");
|
|
1022
|
+
concatUnique("excludeTags");
|
|
1023
|
+
concatUnique("excludeCards");
|
|
1024
|
+
const labels = defined.map((h) => h._label).filter(Boolean);
|
|
1025
|
+
if (labels.length > 0) {
|
|
1026
|
+
merged._label = labels.join("; ");
|
|
1027
|
+
}
|
|
1028
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
1029
|
+
}
|
|
916
1030
|
var AggregationMode, DEFAULT_AGGREGATION_MODE, FREQUENCY_BOOST_FACTOR, CompositeGenerator;
|
|
917
1031
|
var init_CompositeGenerator = __esm({
|
|
918
1032
|
"src/core/navigators/generators/CompositeGenerator.ts"() {
|
|
@@ -976,17 +1090,18 @@ var init_CompositeGenerator = __esm({
|
|
|
976
1090
|
this.generators.map((g) => g.getWeightedCards(limit, context))
|
|
977
1091
|
);
|
|
978
1092
|
const generatorSummaries = [];
|
|
979
|
-
results.forEach((
|
|
1093
|
+
results.forEach((result, index) => {
|
|
1094
|
+
const cards2 = result.cards;
|
|
980
1095
|
const gen = this.generators[index];
|
|
981
1096
|
const genName = gen.name || `Generator ${index}`;
|
|
982
|
-
const newCards =
|
|
983
|
-
const reviewCards =
|
|
984
|
-
if (
|
|
985
|
-
const topScore = Math.max(...
|
|
1097
|
+
const newCards = cards2.filter((c) => c.provenance[0]?.reason?.includes("new card"));
|
|
1098
|
+
const reviewCards = cards2.filter((c) => c.provenance[0]?.reason?.includes("review"));
|
|
1099
|
+
if (cards2.length > 0) {
|
|
1100
|
+
const topScore = Math.max(...cards2.map((c) => c.score)).toFixed(2);
|
|
986
1101
|
const parts = [];
|
|
987
1102
|
if (newCards.length > 0) parts.push(`${newCards.length} new`);
|
|
988
1103
|
if (reviewCards.length > 0) parts.push(`${reviewCards.length} reviews`);
|
|
989
|
-
const breakdown = parts.length > 0 ? parts.join(", ") : `${
|
|
1104
|
+
const breakdown = parts.length > 0 ? parts.join(", ") : `${cards2.length} cards`;
|
|
990
1105
|
generatorSummaries.push(`${genName}: ${breakdown} (top: ${topScore})`);
|
|
991
1106
|
} else {
|
|
992
1107
|
generatorSummaries.push(`${genName}: 0 cards`);
|
|
@@ -994,7 +1109,8 @@ var init_CompositeGenerator = __esm({
|
|
|
994
1109
|
});
|
|
995
1110
|
logger.info(`[Composite] Generator breakdown: ${generatorSummaries.join(" | ")}`);
|
|
996
1111
|
const byCardId = /* @__PURE__ */ new Map();
|
|
997
|
-
results.forEach((
|
|
1112
|
+
results.forEach((result, index) => {
|
|
1113
|
+
const cards2 = result.cards;
|
|
998
1114
|
const gen = this.generators[index];
|
|
999
1115
|
let weight = gen.learnable?.weight ?? 1;
|
|
1000
1116
|
let deviation;
|
|
@@ -1005,7 +1121,7 @@ var init_CompositeGenerator = __esm({
|
|
|
1005
1121
|
deviation = context.orchestration.getDeviation(strategyId);
|
|
1006
1122
|
}
|
|
1007
1123
|
}
|
|
1008
|
-
for (const card of
|
|
1124
|
+
for (const card of cards2) {
|
|
1009
1125
|
if (card.provenance.length > 0) {
|
|
1010
1126
|
card.provenance[0].effectiveWeight = weight;
|
|
1011
1127
|
card.provenance[0].deviation = deviation;
|
|
@@ -1017,15 +1133,15 @@ var init_CompositeGenerator = __esm({
|
|
|
1017
1133
|
});
|
|
1018
1134
|
const merged = [];
|
|
1019
1135
|
for (const [, items] of byCardId) {
|
|
1020
|
-
const
|
|
1136
|
+
const cards2 = items.map((i) => i.card);
|
|
1021
1137
|
const aggregatedScore = this.aggregateScores(items);
|
|
1022
1138
|
const finalScore = Math.min(1, aggregatedScore);
|
|
1023
|
-
const mergedProvenance =
|
|
1024
|
-
const initialScore =
|
|
1139
|
+
const mergedProvenance = cards2.flatMap((c) => c.provenance);
|
|
1140
|
+
const initialScore = cards2[0].score;
|
|
1025
1141
|
const action = finalScore > initialScore ? "boosted" : finalScore < initialScore ? "penalized" : "passed";
|
|
1026
1142
|
const reason = this.buildAggregationReason(items, finalScore);
|
|
1027
1143
|
merged.push({
|
|
1028
|
-
...
|
|
1144
|
+
...cards2[0],
|
|
1029
1145
|
score: finalScore,
|
|
1030
1146
|
provenance: [
|
|
1031
1147
|
...mergedProvenance,
|
|
@@ -1040,7 +1156,9 @@ var init_CompositeGenerator = __esm({
|
|
|
1040
1156
|
]
|
|
1041
1157
|
});
|
|
1042
1158
|
}
|
|
1043
|
-
|
|
1159
|
+
const cards = merged.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
1160
|
+
const hints = mergeHints(results.map((result) => result.hints));
|
|
1161
|
+
return { cards, hints };
|
|
1044
1162
|
}
|
|
1045
1163
|
/**
|
|
1046
1164
|
* Build human-readable reason for score aggregation.
|
|
@@ -1171,16 +1289,16 @@ var init_elo = __esm({
|
|
|
1171
1289
|
};
|
|
1172
1290
|
});
|
|
1173
1291
|
scored.sort((a, b) => b.score - a.score);
|
|
1174
|
-
const
|
|
1175
|
-
if (
|
|
1176
|
-
const topScores =
|
|
1292
|
+
const cards = scored.slice(0, limit);
|
|
1293
|
+
if (cards.length > 0) {
|
|
1294
|
+
const topScores = cards.slice(0, 3).map((c) => c.score.toFixed(2)).join(", ");
|
|
1177
1295
|
logger.info(
|
|
1178
|
-
`[ELO] Course ${this.course.getCourseID()}: ${
|
|
1296
|
+
`[ELO] Course ${this.course.getCourseID()}: ${cards.length} new cards (top scores: ${topScores})`
|
|
1179
1297
|
);
|
|
1180
1298
|
} else {
|
|
1181
1299
|
logger.info(`[ELO] Course ${this.course.getCourseID()}: No new cards available`);
|
|
1182
1300
|
}
|
|
1183
|
-
return
|
|
1301
|
+
return { cards };
|
|
1184
1302
|
}
|
|
1185
1303
|
};
|
|
1186
1304
|
}
|
|
@@ -1217,7 +1335,7 @@ function matchesTagPattern(tag, pattern) {
|
|
|
1217
1335
|
function pickTopByScore(cards, limit) {
|
|
1218
1336
|
return [...cards].sort((a, b) => b.score - a.score || a.cardId.localeCompare(b.cardId)).slice(0, limit);
|
|
1219
1337
|
}
|
|
1220
|
-
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;
|
|
1338
|
+
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;
|
|
1221
1339
|
var init_prescribed = __esm({
|
|
1222
1340
|
"src/core/navigators/generators/prescribed.ts"() {
|
|
1223
1341
|
"use strict";
|
|
@@ -1234,6 +1352,7 @@ var init_prescribed = __esm({
|
|
|
1234
1352
|
MAX_SUPPORT_MULTIPLIER = 4;
|
|
1235
1353
|
LOCKED_TAG_PREFIXES = ["concept:"];
|
|
1236
1354
|
LESSON_GATE_PENALTY_TAG_HINT = "concept:";
|
|
1355
|
+
PRESCRIBED_DEBUG_VERSION = "testversion-prescribed-v2";
|
|
1237
1356
|
PrescribedCardsGenerator = class extends ContentNavigator {
|
|
1238
1357
|
name;
|
|
1239
1358
|
config;
|
|
@@ -1250,7 +1369,7 @@ var init_prescribed = __esm({
|
|
|
1250
1369
|
}
|
|
1251
1370
|
async getWeightedCards(limit, context) {
|
|
1252
1371
|
if (this.config.groups.length === 0 || limit <= 0) {
|
|
1253
|
-
return [];
|
|
1372
|
+
return { cards: [] };
|
|
1254
1373
|
}
|
|
1255
1374
|
const courseId = this.course.getCourseID();
|
|
1256
1375
|
const activeCards = await this.user.getActiveCards();
|
|
@@ -1275,6 +1394,7 @@ var init_prescribed = __esm({
|
|
|
1275
1394
|
};
|
|
1276
1395
|
const emitted = [];
|
|
1277
1396
|
const emittedIds = /* @__PURE__ */ new Set();
|
|
1397
|
+
const groupRuntimes = [];
|
|
1278
1398
|
for (const group of this.config.groups) {
|
|
1279
1399
|
const runtime = this.buildGroupRuntimeState({
|
|
1280
1400
|
group,
|
|
@@ -1286,6 +1406,7 @@ var init_prescribed = __esm({
|
|
|
1286
1406
|
userTagElo,
|
|
1287
1407
|
userGlobalElo
|
|
1288
1408
|
});
|
|
1409
|
+
groupRuntimes.push(runtime);
|
|
1289
1410
|
nextState.groups[group.id] = this.buildNextGroupState(runtime, progress.groups[group.id]);
|
|
1290
1411
|
const directCards = this.buildDirectTargetCards(
|
|
1291
1412
|
runtime,
|
|
@@ -1299,12 +1420,17 @@ var init_prescribed = __esm({
|
|
|
1299
1420
|
);
|
|
1300
1421
|
emitted.push(...directCards, ...supportCards);
|
|
1301
1422
|
}
|
|
1423
|
+
const hintSummary = this.buildSupportHintSummary(groupRuntimes);
|
|
1424
|
+
const hints = Object.keys(hintSummary.boostTags).length > 0 ? {
|
|
1425
|
+
boostTags: hintSummary.boostTags,
|
|
1426
|
+
_label: `prescribed-support (${hintSummary.supportTags.length} tags; blocked=${hintSummary.blockedTargetIds.length}; testversion=${PRESCRIBED_DEBUG_VERSION})`
|
|
1427
|
+
} : void 0;
|
|
1302
1428
|
if (emitted.length === 0) {
|
|
1303
1429
|
logger.debug("[Prescribed] No prescribed targets/support emitted this run");
|
|
1304
1430
|
await this.putStrategyState(nextState).catch((e) => {
|
|
1305
1431
|
logger.debug(`[Prescribed] Failed to persist empty-state update: ${e}`);
|
|
1306
1432
|
});
|
|
1307
|
-
return [];
|
|
1433
|
+
return hints ? { cards: [], hints } : { cards: [] };
|
|
1308
1434
|
}
|
|
1309
1435
|
const finalCards = pickTopByScore(emitted, limit);
|
|
1310
1436
|
const surfacedByGroup = /* @__PURE__ */ new Map();
|
|
@@ -1335,7 +1461,27 @@ var init_prescribed = __esm({
|
|
|
1335
1461
|
logger.info(
|
|
1336
1462
|
`[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)`
|
|
1337
1463
|
);
|
|
1338
|
-
return finalCards;
|
|
1464
|
+
return hints ? { cards: finalCards, hints } : { cards: finalCards };
|
|
1465
|
+
}
|
|
1466
|
+
buildSupportHintSummary(groupRuntimes) {
|
|
1467
|
+
const boostTags = {};
|
|
1468
|
+
const blockedTargetIds = /* @__PURE__ */ new Set();
|
|
1469
|
+
const supportTags = /* @__PURE__ */ new Set();
|
|
1470
|
+
for (const runtime of groupRuntimes) {
|
|
1471
|
+
if (runtime.blockedTargets.length === 0 || runtime.supportTags.length === 0) {
|
|
1472
|
+
continue;
|
|
1473
|
+
}
|
|
1474
|
+
runtime.blockedTargets.forEach((cardId) => blockedTargetIds.add(cardId));
|
|
1475
|
+
for (const tag of runtime.supportTags) {
|
|
1476
|
+
supportTags.add(tag);
|
|
1477
|
+
boostTags[tag] = (boostTags[tag] ?? 1) * runtime.supportMultiplier;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
return {
|
|
1481
|
+
boostTags,
|
|
1482
|
+
blockedTargetIds: [...blockedTargetIds].sort(),
|
|
1483
|
+
supportTags: [...supportTags].sort()
|
|
1484
|
+
};
|
|
1339
1485
|
}
|
|
1340
1486
|
parseConfig(serializedData) {
|
|
1341
1487
|
try {
|
|
@@ -1418,7 +1564,22 @@ var init_prescribed = __esm({
|
|
|
1418
1564
|
group.hierarchyWalk?.enabled !== false,
|
|
1419
1565
|
group.hierarchyWalk?.maxDepth ?? DEFAULT_HIERARCHY_DEPTH
|
|
1420
1566
|
);
|
|
1421
|
-
|
|
1567
|
+
const introTags = tags.filter((tag) => tag.startsWith("gpc:intro:"));
|
|
1568
|
+
const exposeTags = new Set(tags.filter((tag) => tag.startsWith("gpc:expose:")));
|
|
1569
|
+
for (const introTag of introTags) {
|
|
1570
|
+
const suffix = introTag.slice("gpc:intro:".length);
|
|
1571
|
+
if (suffix) {
|
|
1572
|
+
exposeTags.add(`gpc:expose:${suffix}`);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
const unmetExposeTags = [...exposeTags].filter((tag) => {
|
|
1576
|
+
const tagElo = userTagElo[tag];
|
|
1577
|
+
return !tagElo || tagElo.count < DEFAULT_MIN_COUNT;
|
|
1578
|
+
});
|
|
1579
|
+
if (unmetExposeTags.length > 0) {
|
|
1580
|
+
unmetExposeTags.forEach((tag) => supportTags.add(tag));
|
|
1581
|
+
}
|
|
1582
|
+
if (resolution.blocked || unmetExposeTags.length > 0) {
|
|
1422
1583
|
blockedTargets.push(cardId);
|
|
1423
1584
|
resolution.supportTags.forEach((t) => supportTags.add(t));
|
|
1424
1585
|
} else {
|
|
@@ -1448,7 +1609,8 @@ var init_prescribed = __esm({
|
|
|
1448
1609
|
supportCandidates,
|
|
1449
1610
|
supportTags: [...supportTags],
|
|
1450
1611
|
pressureMultiplier,
|
|
1451
|
-
supportMultiplier
|
|
1612
|
+
supportMultiplier,
|
|
1613
|
+
debugVersion: PRESCRIBED_DEBUG_VERSION
|
|
1452
1614
|
};
|
|
1453
1615
|
}
|
|
1454
1616
|
buildNextGroupState(runtime, prior) {
|
|
@@ -1456,6 +1618,7 @@ var init_prescribed = __esm({
|
|
|
1456
1618
|
const surfacedThisRun = false;
|
|
1457
1619
|
return {
|
|
1458
1620
|
encounteredCardIds: [...runtime.encounteredTargets].sort(),
|
|
1621
|
+
pendingTargetIds: [...runtime.pendingTargets].sort(),
|
|
1459
1622
|
lastSurfacedAt: prior?.lastSurfacedAt ?? null,
|
|
1460
1623
|
sessionsSinceSurfaced: surfacedThisRun ? 0 : carriedSessions + 1,
|
|
1461
1624
|
lastSupportAt: prior?.lastSupportAt ?? null,
|
|
@@ -1480,7 +1643,7 @@ var init_prescribed = __esm({
|
|
|
1480
1643
|
strategyId: this.strategyId || "NAVIGATION_STRATEGY-prescribed",
|
|
1481
1644
|
action: "generated",
|
|
1482
1645
|
score: BASE_TARGET_SCORE * runtime.pressureMultiplier,
|
|
1483
|
-
reason: `mode=target;group=${runtime.group.id};pending=${runtime.pendingTargets.length};surfaceable=${runtime.surfaceableTargets.length};blocked=${runtime.blockedTargets.length};multiplier=${runtime.pressureMultiplier.toFixed(2)}`
|
|
1646
|
+
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}`
|
|
1484
1647
|
}
|
|
1485
1648
|
]
|
|
1486
1649
|
});
|
|
@@ -1507,7 +1670,7 @@ var init_prescribed = __esm({
|
|
|
1507
1670
|
strategyId: this.strategyId || "NAVIGATION_STRATEGY-prescribed",
|
|
1508
1671
|
action: "generated",
|
|
1509
1672
|
score: BASE_SUPPORT_SCORE * runtime.supportMultiplier,
|
|
1510
|
-
reason: `mode=support;group=${runtime.group.id};blocked=${runtime.blockedTargets.length};supportTags=${runtime.supportTags.join("|") || "none"};multiplier=${runtime.supportMultiplier.toFixed(2)}`
|
|
1673
|
+
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}`
|
|
1511
1674
|
}
|
|
1512
1675
|
]
|
|
1513
1676
|
});
|
|
@@ -1537,35 +1700,43 @@ var init_prescribed = __esm({
|
|
|
1537
1700
|
return [...candidates];
|
|
1538
1701
|
}
|
|
1539
1702
|
resolveBlockedSupportTags(targetTags, hierarchyConfigs, userTagElo, userGlobalElo, hierarchyWalkEnabled, maxDepth) {
|
|
1540
|
-
if (!hierarchyWalkEnabled || targetTags.length === 0 || hierarchyConfigs.length === 0) {
|
|
1541
|
-
return {
|
|
1542
|
-
blocked: false,
|
|
1543
|
-
supportTags: []
|
|
1544
|
-
};
|
|
1545
|
-
}
|
|
1546
1703
|
const supportTags = /* @__PURE__ */ new Set();
|
|
1547
1704
|
let blocked = false;
|
|
1548
1705
|
for (const targetTag of targetTags) {
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
)
|
|
1555
|
-
|
|
1556
|
-
|
|
1706
|
+
const prereqSets = hierarchyConfigs.map((hierarchy) => hierarchy.prerequisites[targetTag]).filter((prereqs) => Array.isArray(prereqs) && prereqs.length > 0);
|
|
1707
|
+
if (prereqSets.length === 0) {
|
|
1708
|
+
continue;
|
|
1709
|
+
}
|
|
1710
|
+
const tagBlocked = prereqSets.some(
|
|
1711
|
+
(prereqs) => prereqs.some((pr) => !this.isPrerequisiteMet(pr, userTagElo[pr.tag], userGlobalElo))
|
|
1712
|
+
);
|
|
1713
|
+
if (!tagBlocked) {
|
|
1714
|
+
continue;
|
|
1715
|
+
}
|
|
1716
|
+
blocked = true;
|
|
1717
|
+
if (!hierarchyWalkEnabled) {
|
|
1718
|
+
for (const prereqs of prereqSets) {
|
|
1719
|
+
for (const prereq of prereqs) {
|
|
1720
|
+
if (!this.isPrerequisiteMet(prereq, userTagElo[prereq.tag], userGlobalElo)) {
|
|
1721
|
+
supportTags.add(prereq.tag);
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1557
1724
|
}
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1725
|
+
continue;
|
|
1726
|
+
}
|
|
1727
|
+
for (const prereqs of prereqSets) {
|
|
1728
|
+
for (const prereq of prereqs) {
|
|
1729
|
+
if (!this.isPrerequisiteMet(prereq, userTagElo[prereq.tag], userGlobalElo)) {
|
|
1730
|
+
this.collectSupportTagsRecursive(
|
|
1731
|
+
prereq.tag,
|
|
1732
|
+
hierarchyConfigs,
|
|
1733
|
+
userTagElo,
|
|
1734
|
+
userGlobalElo,
|
|
1735
|
+
maxDepth,
|
|
1736
|
+
/* @__PURE__ */ new Set(),
|
|
1737
|
+
supportTags
|
|
1738
|
+
);
|
|
1739
|
+
}
|
|
1569
1740
|
}
|
|
1570
1741
|
}
|
|
1571
1742
|
}
|
|
@@ -1720,7 +1891,7 @@ var init_srs = __esm({
|
|
|
1720
1891
|
]
|
|
1721
1892
|
};
|
|
1722
1893
|
});
|
|
1723
|
-
return scored.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
1894
|
+
return { cards: scored.sort((a, b) => b.score - a.score).slice(0, limit) };
|
|
1724
1895
|
}
|
|
1725
1896
|
/**
|
|
1726
1897
|
* Compute backlog pressure based on number of due reviews.
|
|
@@ -2736,1729 +2907,175 @@ var init_relativePriority = __esm({
|
|
|
2736
2907
|
action,
|
|
2737
2908
|
score: finalScore,
|
|
2738
2909
|
reason
|
|
2739
|
-
}
|
|
2740
|
-
]
|
|
2741
|
-
};
|
|
2742
|
-
})
|
|
2743
|
-
);
|
|
2744
|
-
return adjusted;
|
|
2745
|
-
}
|
|
2746
|
-
/**
|
|
2747
|
-
* Legacy getWeightedCards - now throws as filters should not be used as generators.
|
|
2748
|
-
*
|
|
2749
|
-
* Use transform() via Pipeline instead.
|
|
2750
|
-
*/
|
|
2751
|
-
async getWeightedCards(_limit) {
|
|
2752
|
-
throw new Error(
|
|
2753
|
-
"RelativePriorityNavigator is a filter and should not be used as a generator. Use Pipeline with a generator and this filter via transform()."
|
|
2754
|
-
);
|
|
2755
|
-
}
|
|
2756
|
-
};
|
|
2757
|
-
}
|
|
2758
|
-
});
|
|
2759
|
-
|
|
2760
|
-
// src/core/navigators/filters/types.ts
|
|
2761
|
-
var types_exports2 = {};
|
|
2762
|
-
var init_types2 = __esm({
|
|
2763
|
-
"src/core/navigators/filters/types.ts"() {
|
|
2764
|
-
"use strict";
|
|
2765
|
-
}
|
|
2766
|
-
});
|
|
2767
|
-
|
|
2768
|
-
// src/core/navigators/filters/userGoalStub.ts
|
|
2769
|
-
var userGoalStub_exports = {};
|
|
2770
|
-
__export(userGoalStub_exports, {
|
|
2771
|
-
USER_GOAL_NAVIGATOR_STUB: () => USER_GOAL_NAVIGATOR_STUB
|
|
2772
|
-
});
|
|
2773
|
-
var USER_GOAL_NAVIGATOR_STUB;
|
|
2774
|
-
var init_userGoalStub = __esm({
|
|
2775
|
-
"src/core/navigators/filters/userGoalStub.ts"() {
|
|
2776
|
-
"use strict";
|
|
2777
|
-
USER_GOAL_NAVIGATOR_STUB = true;
|
|
2778
|
-
}
|
|
2779
|
-
});
|
|
2780
|
-
|
|
2781
|
-
// import("./filters/**/*") in src/core/navigators/index.ts
|
|
2782
|
-
var globImport_filters;
|
|
2783
|
-
var init_2 = __esm({
|
|
2784
|
-
'import("./filters/**/*") in src/core/navigators/index.ts'() {
|
|
2785
|
-
globImport_filters = __glob({
|
|
2786
|
-
"./filters/WeightedFilter.ts": () => Promise.resolve().then(() => (init_WeightedFilter(), WeightedFilter_exports)),
|
|
2787
|
-
"./filters/eloDistance.ts": () => Promise.resolve().then(() => (init_eloDistance(), eloDistance_exports)),
|
|
2788
|
-
"./filters/hierarchyDefinition.ts": () => Promise.resolve().then(() => (init_hierarchyDefinition(), hierarchyDefinition_exports)),
|
|
2789
|
-
"./filters/index.ts": () => Promise.resolve().then(() => (init_filters(), filters_exports)),
|
|
2790
|
-
"./filters/inferredPreferenceStub.ts": () => Promise.resolve().then(() => (init_inferredPreferenceStub(), inferredPreferenceStub_exports)),
|
|
2791
|
-
"./filters/interferenceMitigator.ts": () => Promise.resolve().then(() => (init_interferenceMitigator(), interferenceMitigator_exports)),
|
|
2792
|
-
"./filters/relativePriority.ts": () => Promise.resolve().then(() => (init_relativePriority(), relativePriority_exports)),
|
|
2793
|
-
"./filters/types.ts": () => Promise.resolve().then(() => (init_types2(), types_exports2)),
|
|
2794
|
-
"./filters/userGoalStub.ts": () => Promise.resolve().then(() => (init_userGoalStub(), userGoalStub_exports)),
|
|
2795
|
-
"./filters/userTagPreference.ts": () => Promise.resolve().then(() => (init_userTagPreference(), userTagPreference_exports))
|
|
2796
|
-
});
|
|
2797
|
-
}
|
|
2798
|
-
});
|
|
2799
|
-
|
|
2800
|
-
// src/core/orchestration/gradient.ts
|
|
2801
|
-
var init_gradient = __esm({
|
|
2802
|
-
"src/core/orchestration/gradient.ts"() {
|
|
2803
|
-
"use strict";
|
|
2804
|
-
init_logger();
|
|
2805
|
-
}
|
|
2806
|
-
});
|
|
2807
|
-
|
|
2808
|
-
// src/core/orchestration/learning.ts
|
|
2809
|
-
var init_learning = __esm({
|
|
2810
|
-
"src/core/orchestration/learning.ts"() {
|
|
2811
|
-
"use strict";
|
|
2812
|
-
init_contentNavigationStrategy();
|
|
2813
|
-
init_types_legacy();
|
|
2814
|
-
init_logger();
|
|
2815
|
-
}
|
|
2816
|
-
});
|
|
2817
|
-
|
|
2818
|
-
// src/core/orchestration/signal.ts
|
|
2819
|
-
var init_signal = __esm({
|
|
2820
|
-
"src/core/orchestration/signal.ts"() {
|
|
2821
|
-
"use strict";
|
|
2822
|
-
}
|
|
2823
|
-
});
|
|
2824
|
-
|
|
2825
|
-
// src/core/orchestration/recording.ts
|
|
2826
|
-
var init_recording = __esm({
|
|
2827
|
-
"src/core/orchestration/recording.ts"() {
|
|
2828
|
-
"use strict";
|
|
2829
|
-
init_signal();
|
|
2830
|
-
init_types_legacy();
|
|
2831
|
-
init_logger();
|
|
2832
|
-
}
|
|
2833
|
-
});
|
|
2834
|
-
|
|
2835
|
-
// src/core/orchestration/index.ts
|
|
2836
|
-
function fnv1a(str) {
|
|
2837
|
-
let hash = 2166136261;
|
|
2838
|
-
for (let i = 0; i < str.length; i++) {
|
|
2839
|
-
hash ^= str.charCodeAt(i);
|
|
2840
|
-
hash = Math.imul(hash, 16777619);
|
|
2841
|
-
}
|
|
2842
|
-
return hash >>> 0;
|
|
2843
|
-
}
|
|
2844
|
-
function computeDeviation(userId, strategyId, salt) {
|
|
2845
|
-
const input = `${userId}:${strategyId}:${salt}`;
|
|
2846
|
-
const hash = fnv1a(input);
|
|
2847
|
-
const normalized = hash / 4294967296;
|
|
2848
|
-
return normalized * 2 - 1;
|
|
2849
|
-
}
|
|
2850
|
-
function computeSpread(confidence) {
|
|
2851
|
-
const clampedConfidence = Math.max(0, Math.min(1, confidence));
|
|
2852
|
-
return MAX_SPREAD - clampedConfidence * (MAX_SPREAD - MIN_SPREAD);
|
|
2853
|
-
}
|
|
2854
|
-
function computeEffectiveWeight(learnable, userId, strategyId, salt) {
|
|
2855
|
-
const deviation = computeDeviation(userId, strategyId, salt);
|
|
2856
|
-
const spread = computeSpread(learnable.confidence);
|
|
2857
|
-
const adjustment = deviation * spread * learnable.weight;
|
|
2858
|
-
const effective = learnable.weight + adjustment;
|
|
2859
|
-
return Math.max(MIN_WEIGHT, Math.min(MAX_WEIGHT, effective));
|
|
2860
|
-
}
|
|
2861
|
-
async function createOrchestrationContext(user, course) {
|
|
2862
|
-
let courseConfig;
|
|
2863
|
-
try {
|
|
2864
|
-
courseConfig = await course.getCourseConfig();
|
|
2865
|
-
} catch (e) {
|
|
2866
|
-
logger.error(`[Orchestration] Failed to load course config: ${e}`);
|
|
2867
|
-
courseConfig = {
|
|
2868
|
-
name: "Unknown",
|
|
2869
|
-
description: "",
|
|
2870
|
-
public: false,
|
|
2871
|
-
deleted: false,
|
|
2872
|
-
creator: "",
|
|
2873
|
-
admins: [],
|
|
2874
|
-
moderators: [],
|
|
2875
|
-
dataShapes: [],
|
|
2876
|
-
questionTypes: [],
|
|
2877
|
-
orchestration: { salt: "default" }
|
|
2878
|
-
};
|
|
2879
|
-
}
|
|
2880
|
-
const userId = user.getUsername();
|
|
2881
|
-
const salt = courseConfig.orchestration?.salt || "default_salt";
|
|
2882
|
-
return {
|
|
2883
|
-
user,
|
|
2884
|
-
course,
|
|
2885
|
-
userId,
|
|
2886
|
-
courseConfig,
|
|
2887
|
-
getEffectiveWeight(strategyId, learnable) {
|
|
2888
|
-
return computeEffectiveWeight(learnable, userId, strategyId, salt);
|
|
2889
|
-
},
|
|
2890
|
-
getDeviation(strategyId) {
|
|
2891
|
-
return computeDeviation(userId, strategyId, salt);
|
|
2892
|
-
}
|
|
2893
|
-
};
|
|
2894
|
-
}
|
|
2895
|
-
var MIN_SPREAD, MAX_SPREAD, MIN_WEIGHT, MAX_WEIGHT;
|
|
2896
|
-
var init_orchestration = __esm({
|
|
2897
|
-
"src/core/orchestration/index.ts"() {
|
|
2898
|
-
"use strict";
|
|
2899
|
-
init_logger();
|
|
2900
|
-
init_gradient();
|
|
2901
|
-
init_learning();
|
|
2902
|
-
init_signal();
|
|
2903
|
-
init_recording();
|
|
2904
|
-
MIN_SPREAD = 0.1;
|
|
2905
|
-
MAX_SPREAD = 0.5;
|
|
2906
|
-
MIN_WEIGHT = 0.1;
|
|
2907
|
-
MAX_WEIGHT = 3;
|
|
2908
|
-
}
|
|
2909
|
-
});
|
|
2910
|
-
|
|
2911
|
-
// src/study/SpacedRepetition.ts
|
|
2912
|
-
var import_moment4, import_common8, duration;
|
|
2913
|
-
var init_SpacedRepetition = __esm({
|
|
2914
|
-
"src/study/SpacedRepetition.ts"() {
|
|
2915
|
-
"use strict";
|
|
2916
|
-
init_util();
|
|
2917
|
-
import_moment4 = __toESM(require("moment"), 1);
|
|
2918
|
-
import_common8 = require("@vue-skuilder/common");
|
|
2919
|
-
init_logger();
|
|
2920
|
-
duration = import_moment4.default.duration;
|
|
2921
|
-
}
|
|
2922
|
-
});
|
|
2923
|
-
|
|
2924
|
-
// src/study/services/SrsService.ts
|
|
2925
|
-
var import_moment5;
|
|
2926
|
-
var init_SrsService = __esm({
|
|
2927
|
-
"src/study/services/SrsService.ts"() {
|
|
2928
|
-
"use strict";
|
|
2929
|
-
import_moment5 = __toESM(require("moment"), 1);
|
|
2930
|
-
init_couch();
|
|
2931
|
-
init_SpacedRepetition();
|
|
2932
|
-
init_logger();
|
|
2933
|
-
}
|
|
2934
|
-
});
|
|
2935
|
-
|
|
2936
|
-
// src/study/services/EloService.ts
|
|
2937
|
-
var import_common9;
|
|
2938
|
-
var init_EloService = __esm({
|
|
2939
|
-
"src/study/services/EloService.ts"() {
|
|
2940
|
-
"use strict";
|
|
2941
|
-
import_common9 = require("@vue-skuilder/common");
|
|
2942
|
-
init_logger();
|
|
2943
|
-
}
|
|
2944
|
-
});
|
|
2945
|
-
|
|
2946
|
-
// src/study/services/ResponseProcessor.ts
|
|
2947
|
-
var import_common10;
|
|
2948
|
-
var init_ResponseProcessor = __esm({
|
|
2949
|
-
"src/study/services/ResponseProcessor.ts"() {
|
|
2950
|
-
"use strict";
|
|
2951
|
-
init_core();
|
|
2952
|
-
init_logger();
|
|
2953
|
-
import_common10 = require("@vue-skuilder/common");
|
|
2954
|
-
}
|
|
2955
|
-
});
|
|
2956
|
-
|
|
2957
|
-
// src/study/services/CardHydrationService.ts
|
|
2958
|
-
var import_common11;
|
|
2959
|
-
var init_CardHydrationService = __esm({
|
|
2960
|
-
"src/study/services/CardHydrationService.ts"() {
|
|
2961
|
-
"use strict";
|
|
2962
|
-
import_common11 = require("@vue-skuilder/common");
|
|
2963
|
-
init_logger();
|
|
2964
|
-
}
|
|
2965
|
-
});
|
|
2966
|
-
|
|
2967
|
-
// src/study/ItemQueue.ts
|
|
2968
|
-
var init_ItemQueue = __esm({
|
|
2969
|
-
"src/study/ItemQueue.ts"() {
|
|
2970
|
-
"use strict";
|
|
2971
|
-
}
|
|
2972
|
-
});
|
|
2973
|
-
|
|
2974
|
-
// src/util/packer/types.ts
|
|
2975
|
-
var init_types3 = __esm({
|
|
2976
|
-
"src/util/packer/types.ts"() {
|
|
2977
|
-
"use strict";
|
|
2978
|
-
}
|
|
2979
|
-
});
|
|
2980
|
-
|
|
2981
|
-
// src/util/packer/CouchDBToStaticPacker.ts
|
|
2982
|
-
var init_CouchDBToStaticPacker = __esm({
|
|
2983
|
-
"src/util/packer/CouchDBToStaticPacker.ts"() {
|
|
2984
|
-
"use strict";
|
|
2985
|
-
init_types_legacy();
|
|
2986
|
-
init_logger();
|
|
2987
|
-
}
|
|
2988
|
-
});
|
|
2989
|
-
|
|
2990
|
-
// src/util/packer/index.ts
|
|
2991
|
-
var init_packer = __esm({
|
|
2992
|
-
"src/util/packer/index.ts"() {
|
|
2993
|
-
"use strict";
|
|
2994
|
-
init_types3();
|
|
2995
|
-
init_CouchDBToStaticPacker();
|
|
2996
|
-
}
|
|
2997
|
-
});
|
|
2998
|
-
|
|
2999
|
-
// src/util/migrator/types.ts
|
|
3000
|
-
var DEFAULT_MIGRATION_OPTIONS;
|
|
3001
|
-
var init_types4 = __esm({
|
|
3002
|
-
"src/util/migrator/types.ts"() {
|
|
3003
|
-
"use strict";
|
|
3004
|
-
DEFAULT_MIGRATION_OPTIONS = {
|
|
3005
|
-
chunkBatchSize: 100,
|
|
3006
|
-
validateRoundTrip: false,
|
|
3007
|
-
cleanupOnFailure: true,
|
|
3008
|
-
timeout: 3e5
|
|
3009
|
-
// 5 minutes
|
|
3010
|
-
};
|
|
3011
|
-
}
|
|
3012
|
-
});
|
|
3013
|
-
|
|
3014
|
-
// src/util/migrator/FileSystemAdapter.ts
|
|
3015
|
-
var FileSystemError;
|
|
3016
|
-
var init_FileSystemAdapter = __esm({
|
|
3017
|
-
"src/util/migrator/FileSystemAdapter.ts"() {
|
|
3018
|
-
"use strict";
|
|
3019
|
-
FileSystemError = class extends Error {
|
|
3020
|
-
constructor(message, operation, filePath, cause) {
|
|
3021
|
-
super(message);
|
|
3022
|
-
this.operation = operation;
|
|
3023
|
-
this.filePath = filePath;
|
|
3024
|
-
this.cause = cause;
|
|
3025
|
-
this.name = "FileSystemError";
|
|
3026
|
-
}
|
|
3027
|
-
};
|
|
3028
|
-
}
|
|
3029
|
-
});
|
|
3030
|
-
|
|
3031
|
-
// src/util/migrator/validation.ts
|
|
3032
|
-
async function validateStaticCourse(staticPath, fs) {
|
|
3033
|
-
const validation = {
|
|
3034
|
-
valid: true,
|
|
3035
|
-
manifestExists: false,
|
|
3036
|
-
chunksExist: false,
|
|
3037
|
-
attachmentsExist: false,
|
|
3038
|
-
errors: [],
|
|
3039
|
-
warnings: []
|
|
3040
|
-
};
|
|
3041
|
-
try {
|
|
3042
|
-
if (fs) {
|
|
3043
|
-
const stats = await fs.stat(staticPath);
|
|
3044
|
-
if (!stats.isDirectory()) {
|
|
3045
|
-
validation.errors.push(`Path is not a directory: ${staticPath}`);
|
|
3046
|
-
validation.valid = false;
|
|
3047
|
-
return validation;
|
|
3048
|
-
}
|
|
3049
|
-
} else if (!nodeFS) {
|
|
3050
|
-
validation.errors.push("File system access not available - validation skipped");
|
|
3051
|
-
validation.valid = false;
|
|
3052
|
-
return validation;
|
|
3053
|
-
} else {
|
|
3054
|
-
const stats = await nodeFS.promises.stat(staticPath);
|
|
3055
|
-
if (!stats.isDirectory()) {
|
|
3056
|
-
validation.errors.push(`Path is not a directory: ${staticPath}`);
|
|
3057
|
-
validation.valid = false;
|
|
3058
|
-
return validation;
|
|
3059
|
-
}
|
|
3060
|
-
}
|
|
3061
|
-
let manifestPath = `${staticPath}/manifest.json`;
|
|
3062
|
-
try {
|
|
3063
|
-
if (fs) {
|
|
3064
|
-
manifestPath = fs.joinPath(staticPath, "manifest.json");
|
|
3065
|
-
if (await fs.exists(manifestPath)) {
|
|
3066
|
-
validation.manifestExists = true;
|
|
3067
|
-
const manifestContent = await fs.readFile(manifestPath);
|
|
3068
|
-
const manifest = JSON.parse(manifestContent);
|
|
3069
|
-
validation.courseId = manifest.courseId;
|
|
3070
|
-
validation.courseName = manifest.courseName;
|
|
3071
|
-
if (!manifest.version || !manifest.courseId || !manifest.chunks || !Array.isArray(manifest.chunks)) {
|
|
3072
|
-
validation.errors.push("Invalid manifest structure");
|
|
3073
|
-
validation.valid = false;
|
|
3074
|
-
}
|
|
3075
|
-
} else {
|
|
3076
|
-
validation.errors.push(`Manifest not found: ${manifestPath}`);
|
|
3077
|
-
validation.valid = false;
|
|
3078
|
-
}
|
|
3079
|
-
} else {
|
|
3080
|
-
manifestPath = `${staticPath}/manifest.json`;
|
|
3081
|
-
await nodeFS.promises.access(manifestPath);
|
|
3082
|
-
validation.manifestExists = true;
|
|
3083
|
-
const manifestContent = await nodeFS.promises.readFile(manifestPath, "utf8");
|
|
3084
|
-
const manifest = JSON.parse(manifestContent);
|
|
3085
|
-
validation.courseId = manifest.courseId;
|
|
3086
|
-
validation.courseName = manifest.courseName;
|
|
3087
|
-
if (!manifest.version || !manifest.courseId || !manifest.chunks || !Array.isArray(manifest.chunks)) {
|
|
3088
|
-
validation.errors.push("Invalid manifest structure");
|
|
3089
|
-
validation.valid = false;
|
|
3090
|
-
}
|
|
3091
|
-
}
|
|
3092
|
-
} catch (error) {
|
|
3093
|
-
const errorMessage = error instanceof FileSystemError ? error.message : `Manifest not found or invalid: ${manifestPath}`;
|
|
3094
|
-
validation.errors.push(errorMessage);
|
|
3095
|
-
validation.valid = false;
|
|
3096
|
-
}
|
|
3097
|
-
let chunksPath = `${staticPath}/chunks`;
|
|
3098
|
-
try {
|
|
3099
|
-
if (fs) {
|
|
3100
|
-
chunksPath = fs.joinPath(staticPath, "chunks");
|
|
3101
|
-
if (await fs.exists(chunksPath)) {
|
|
3102
|
-
const chunksStats = await fs.stat(chunksPath);
|
|
3103
|
-
if (chunksStats.isDirectory()) {
|
|
3104
|
-
validation.chunksExist = true;
|
|
3105
|
-
} else {
|
|
3106
|
-
validation.errors.push(`Chunks path is not a directory: ${chunksPath}`);
|
|
3107
|
-
validation.valid = false;
|
|
3108
|
-
}
|
|
3109
|
-
} else {
|
|
3110
|
-
validation.errors.push(`Chunks directory not found: ${chunksPath}`);
|
|
3111
|
-
validation.valid = false;
|
|
3112
|
-
}
|
|
3113
|
-
} else {
|
|
3114
|
-
chunksPath = `${staticPath}/chunks`;
|
|
3115
|
-
const chunksStats = await nodeFS.promises.stat(chunksPath);
|
|
3116
|
-
if (chunksStats.isDirectory()) {
|
|
3117
|
-
validation.chunksExist = true;
|
|
3118
|
-
} else {
|
|
3119
|
-
validation.errors.push(`Chunks path is not a directory: ${chunksPath}`);
|
|
3120
|
-
validation.valid = false;
|
|
3121
|
-
}
|
|
3122
|
-
}
|
|
3123
|
-
} catch (error) {
|
|
3124
|
-
const errorMessage = error instanceof FileSystemError ? error.message : `Chunks directory not found: ${chunksPath}`;
|
|
3125
|
-
validation.errors.push(errorMessage);
|
|
3126
|
-
validation.valid = false;
|
|
3127
|
-
}
|
|
3128
|
-
let attachmentsPath;
|
|
3129
|
-
try {
|
|
3130
|
-
if (fs) {
|
|
3131
|
-
attachmentsPath = fs.joinPath(staticPath, "attachments");
|
|
3132
|
-
if (await fs.exists(attachmentsPath)) {
|
|
3133
|
-
const attachmentsStats = await fs.stat(attachmentsPath);
|
|
3134
|
-
if (attachmentsStats.isDirectory()) {
|
|
3135
|
-
validation.attachmentsExist = true;
|
|
3136
|
-
}
|
|
3137
|
-
} else {
|
|
3138
|
-
validation.warnings.push(
|
|
3139
|
-
`Attachments directory not found: ${attachmentsPath} (this is OK if course has no attachments)`
|
|
3140
|
-
);
|
|
3141
|
-
}
|
|
3142
|
-
} else {
|
|
3143
|
-
attachmentsPath = `${staticPath}/attachments`;
|
|
3144
|
-
const attachmentsStats = await nodeFS.promises.stat(attachmentsPath);
|
|
3145
|
-
if (attachmentsStats.isDirectory()) {
|
|
3146
|
-
validation.attachmentsExist = true;
|
|
3147
|
-
}
|
|
3148
|
-
}
|
|
3149
|
-
} catch (error) {
|
|
3150
|
-
attachmentsPath = attachmentsPath || `${staticPath}/attachments`;
|
|
3151
|
-
const warningMessage = error instanceof FileSystemError ? error.message : `Attachments directory not found: ${attachmentsPath} (this is OK if course has no attachments)`;
|
|
3152
|
-
validation.warnings.push(warningMessage);
|
|
3153
|
-
}
|
|
3154
|
-
} catch (error) {
|
|
3155
|
-
validation.errors.push(
|
|
3156
|
-
`Failed to validate static course: ${error instanceof Error ? error.message : String(error)}`
|
|
3157
|
-
);
|
|
3158
|
-
validation.valid = false;
|
|
3159
|
-
}
|
|
3160
|
-
return validation;
|
|
3161
|
-
}
|
|
3162
|
-
async function validateMigration(targetDB, expectedCounts, manifest) {
|
|
3163
|
-
const validation = {
|
|
3164
|
-
valid: true,
|
|
3165
|
-
documentCountMatch: false,
|
|
3166
|
-
attachmentIntegrity: false,
|
|
3167
|
-
viewFunctionality: false,
|
|
3168
|
-
issues: []
|
|
3169
|
-
};
|
|
3170
|
-
try {
|
|
3171
|
-
logger.info("Starting migration validation...");
|
|
3172
|
-
const actualCounts = await getActualDocumentCounts(targetDB);
|
|
3173
|
-
validation.documentCountMatch = compareDocumentCounts(
|
|
3174
|
-
expectedCounts,
|
|
3175
|
-
actualCounts,
|
|
3176
|
-
validation.issues
|
|
3177
|
-
);
|
|
3178
|
-
await validateCourseConfig(targetDB, manifest, validation.issues);
|
|
3179
|
-
validation.viewFunctionality = await validateViews(targetDB, manifest, validation.issues);
|
|
3180
|
-
validation.attachmentIntegrity = await validateAttachmentIntegrity(targetDB, validation.issues);
|
|
3181
|
-
validation.valid = validation.documentCountMatch && validation.viewFunctionality && validation.attachmentIntegrity;
|
|
3182
|
-
logger.info(`Migration validation completed. Valid: ${validation.valid}`);
|
|
3183
|
-
if (validation.issues.length > 0) {
|
|
3184
|
-
logger.info(`Validation issues: ${validation.issues.length}`);
|
|
3185
|
-
validation.issues.forEach((issue) => {
|
|
3186
|
-
if (issue.type === "error") {
|
|
3187
|
-
logger.error(`${issue.category}: ${issue.message}`);
|
|
3188
|
-
} else {
|
|
3189
|
-
logger.warn(`${issue.category}: ${issue.message}`);
|
|
3190
|
-
}
|
|
3191
|
-
});
|
|
3192
|
-
}
|
|
3193
|
-
} catch (error) {
|
|
3194
|
-
validation.valid = false;
|
|
3195
|
-
validation.issues.push({
|
|
3196
|
-
type: "error",
|
|
3197
|
-
category: "metadata",
|
|
3198
|
-
message: `Validation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
3199
|
-
});
|
|
3200
|
-
}
|
|
3201
|
-
return validation;
|
|
3202
|
-
}
|
|
3203
|
-
async function getActualDocumentCounts(db) {
|
|
3204
|
-
const counts = {};
|
|
3205
|
-
try {
|
|
3206
|
-
const allDocs = await db.allDocs({ include_docs: true });
|
|
3207
|
-
for (const row of allDocs.rows) {
|
|
3208
|
-
if (row.id.startsWith("_design/")) {
|
|
3209
|
-
counts["_design"] = (counts["_design"] || 0) + 1;
|
|
3210
|
-
continue;
|
|
3211
|
-
}
|
|
3212
|
-
const doc = row.doc;
|
|
3213
|
-
if (doc && doc.docType) {
|
|
3214
|
-
counts[doc.docType] = (counts[doc.docType] || 0) + 1;
|
|
3215
|
-
} else {
|
|
3216
|
-
counts["unknown"] = (counts["unknown"] || 0) + 1;
|
|
3217
|
-
}
|
|
3218
|
-
}
|
|
3219
|
-
} catch (error) {
|
|
3220
|
-
logger.error("Failed to get actual document counts:", error);
|
|
3221
|
-
}
|
|
3222
|
-
return counts;
|
|
3223
|
-
}
|
|
3224
|
-
function compareDocumentCounts(expected, actual, issues) {
|
|
3225
|
-
let countsMatch = true;
|
|
3226
|
-
for (const [docType, expectedCount] of Object.entries(expected)) {
|
|
3227
|
-
const actualCount = actual[docType] || 0;
|
|
3228
|
-
if (actualCount !== expectedCount) {
|
|
3229
|
-
countsMatch = false;
|
|
3230
|
-
issues.push({
|
|
3231
|
-
type: "error",
|
|
3232
|
-
category: "documents",
|
|
3233
|
-
message: `Document count mismatch for ${docType}: expected ${expectedCount}, got ${actualCount}`
|
|
3234
|
-
});
|
|
3235
|
-
}
|
|
3236
|
-
}
|
|
3237
|
-
for (const [docType, actualCount] of Object.entries(actual)) {
|
|
3238
|
-
if (!expected[docType] && docType !== "_design") {
|
|
3239
|
-
issues.push({
|
|
3240
|
-
type: "warning",
|
|
3241
|
-
category: "documents",
|
|
3242
|
-
message: `Unexpected document type found: ${docType} (${actualCount} documents)`
|
|
3243
|
-
});
|
|
3244
|
-
}
|
|
3245
|
-
}
|
|
3246
|
-
return countsMatch;
|
|
3247
|
-
}
|
|
3248
|
-
async function validateCourseConfig(db, manifest, issues) {
|
|
3249
|
-
try {
|
|
3250
|
-
const courseConfig = await db.get("CourseConfig");
|
|
3251
|
-
if (!courseConfig) {
|
|
3252
|
-
issues.push({
|
|
3253
|
-
type: "error",
|
|
3254
|
-
category: "course_config",
|
|
3255
|
-
message: "CourseConfig document not found after migration"
|
|
3256
|
-
});
|
|
3257
|
-
return;
|
|
3258
|
-
}
|
|
3259
|
-
if (!courseConfig.courseID) {
|
|
3260
|
-
issues.push({
|
|
3261
|
-
type: "warning",
|
|
3262
|
-
category: "course_config",
|
|
3263
|
-
message: "CourseConfig document missing courseID field"
|
|
3264
|
-
});
|
|
3265
|
-
}
|
|
3266
|
-
if (courseConfig.courseID !== manifest.courseId) {
|
|
3267
|
-
issues.push({
|
|
3268
|
-
type: "warning",
|
|
3269
|
-
category: "course_config",
|
|
3270
|
-
message: `CourseConfig courseID mismatch: expected ${manifest.courseId}, got ${courseConfig.courseID}`
|
|
3271
|
-
});
|
|
3272
|
-
}
|
|
3273
|
-
logger.debug("CourseConfig document validation passed");
|
|
3274
|
-
} catch (error) {
|
|
3275
|
-
if (error.status === 404) {
|
|
3276
|
-
issues.push({
|
|
3277
|
-
type: "error",
|
|
3278
|
-
category: "course_config",
|
|
3279
|
-
message: "CourseConfig document not found in database"
|
|
3280
|
-
});
|
|
3281
|
-
} else {
|
|
3282
|
-
issues.push({
|
|
3283
|
-
type: "error",
|
|
3284
|
-
category: "course_config",
|
|
3285
|
-
message: `Failed to validate CourseConfig document: ${error instanceof Error ? error.message : String(error)}`
|
|
3286
|
-
});
|
|
3287
|
-
}
|
|
3288
|
-
}
|
|
3289
|
-
}
|
|
3290
|
-
async function validateViews(db, manifest, issues) {
|
|
3291
|
-
let viewsValid = true;
|
|
3292
|
-
try {
|
|
3293
|
-
for (const designDoc of manifest.designDocs) {
|
|
3294
|
-
try {
|
|
3295
|
-
const doc = await db.get(designDoc._id);
|
|
3296
|
-
if (!doc) {
|
|
3297
|
-
viewsValid = false;
|
|
3298
|
-
issues.push({
|
|
3299
|
-
type: "error",
|
|
3300
|
-
category: "views",
|
|
3301
|
-
message: `Design document not found: ${designDoc._id}`
|
|
3302
|
-
});
|
|
3303
|
-
continue;
|
|
3304
|
-
}
|
|
3305
|
-
for (const viewName of Object.keys(designDoc.views)) {
|
|
3306
|
-
try {
|
|
3307
|
-
const viewPath = `${designDoc._id}/${viewName}`;
|
|
3308
|
-
await db.query(viewPath, { limit: 1 });
|
|
3309
|
-
} catch (viewError) {
|
|
3310
|
-
viewsValid = false;
|
|
3311
|
-
issues.push({
|
|
3312
|
-
type: "error",
|
|
3313
|
-
category: "views",
|
|
3314
|
-
message: `View not accessible: ${designDoc._id}/${viewName} - ${viewError}`
|
|
3315
|
-
});
|
|
3316
|
-
}
|
|
3317
|
-
}
|
|
3318
|
-
} catch (error) {
|
|
3319
|
-
viewsValid = false;
|
|
3320
|
-
issues.push({
|
|
3321
|
-
type: "error",
|
|
3322
|
-
category: "views",
|
|
3323
|
-
message: `Failed to validate design document ${designDoc._id}: ${error}`
|
|
3324
|
-
});
|
|
3325
|
-
}
|
|
3326
|
-
}
|
|
3327
|
-
} catch (error) {
|
|
3328
|
-
viewsValid = false;
|
|
3329
|
-
issues.push({
|
|
3330
|
-
type: "error",
|
|
3331
|
-
category: "views",
|
|
3332
|
-
message: `View validation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
3333
|
-
});
|
|
3334
|
-
}
|
|
3335
|
-
return viewsValid;
|
|
3336
|
-
}
|
|
3337
|
-
async function validateAttachmentIntegrity(db, issues) {
|
|
3338
|
-
let attachmentsValid = true;
|
|
3339
|
-
try {
|
|
3340
|
-
const allDocs = await db.allDocs({
|
|
3341
|
-
include_docs: true,
|
|
3342
|
-
limit: 10
|
|
3343
|
-
// Sample first 10 documents for performance
|
|
3344
|
-
});
|
|
3345
|
-
let attachmentCount = 0;
|
|
3346
|
-
let validAttachments = 0;
|
|
3347
|
-
for (const row of allDocs.rows) {
|
|
3348
|
-
const doc = row.doc;
|
|
3349
|
-
if (doc && doc._attachments) {
|
|
3350
|
-
for (const [attachmentName, _attachmentMeta] of Object.entries(doc._attachments)) {
|
|
3351
|
-
attachmentCount++;
|
|
3352
|
-
try {
|
|
3353
|
-
const attachment = await db.getAttachment(doc._id, attachmentName);
|
|
3354
|
-
if (attachment) {
|
|
3355
|
-
validAttachments++;
|
|
3356
|
-
}
|
|
3357
|
-
} catch (attachmentError) {
|
|
3358
|
-
attachmentsValid = false;
|
|
3359
|
-
issues.push({
|
|
3360
|
-
type: "error",
|
|
3361
|
-
category: "attachments",
|
|
3362
|
-
message: `Attachment not accessible: ${doc._id}/${attachmentName} - ${attachmentError}`
|
|
3363
|
-
});
|
|
3364
|
-
}
|
|
3365
|
-
}
|
|
3366
|
-
}
|
|
3367
|
-
}
|
|
3368
|
-
if (attachmentCount === 0) {
|
|
3369
|
-
issues.push({
|
|
3370
|
-
type: "warning",
|
|
3371
|
-
category: "attachments",
|
|
3372
|
-
message: "No attachments found in sampled documents"
|
|
3373
|
-
});
|
|
3374
|
-
} else {
|
|
3375
|
-
logger.info(`Validated ${validAttachments}/${attachmentCount} sampled attachments`);
|
|
3376
|
-
}
|
|
3377
|
-
} catch (error) {
|
|
3378
|
-
attachmentsValid = false;
|
|
3379
|
-
issues.push({
|
|
3380
|
-
type: "error",
|
|
3381
|
-
category: "attachments",
|
|
3382
|
-
message: `Attachment validation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
3383
|
-
});
|
|
3384
|
-
}
|
|
3385
|
-
return attachmentsValid;
|
|
3386
|
-
}
|
|
3387
|
-
var nodeFS;
|
|
3388
|
-
var init_validation = __esm({
|
|
3389
|
-
"src/util/migrator/validation.ts"() {
|
|
3390
|
-
"use strict";
|
|
3391
|
-
init_logger();
|
|
3392
|
-
init_FileSystemAdapter();
|
|
3393
|
-
nodeFS = null;
|
|
3394
|
-
try {
|
|
3395
|
-
if (typeof window === "undefined" && typeof process !== "undefined" && process.versions?.node) {
|
|
3396
|
-
nodeFS = eval("require")("fs");
|
|
3397
|
-
nodeFS.promises = nodeFS.promises || eval("require")("fs").promises;
|
|
3398
|
-
}
|
|
3399
|
-
} catch {
|
|
3400
|
-
}
|
|
3401
|
-
}
|
|
3402
|
-
});
|
|
3403
|
-
|
|
3404
|
-
// src/util/migrator/StaticToCouchDBMigrator.ts
|
|
3405
|
-
var nodeFS2, nodePath, StaticToCouchDBMigrator;
|
|
3406
|
-
var init_StaticToCouchDBMigrator = __esm({
|
|
3407
|
-
"src/util/migrator/StaticToCouchDBMigrator.ts"() {
|
|
3408
|
-
"use strict";
|
|
3409
|
-
init_logger();
|
|
3410
|
-
init_types4();
|
|
3411
|
-
init_validation();
|
|
3412
|
-
init_FileSystemAdapter();
|
|
3413
|
-
nodeFS2 = null;
|
|
3414
|
-
nodePath = null;
|
|
3415
|
-
try {
|
|
3416
|
-
if (typeof window === "undefined" && typeof process !== "undefined" && process.versions?.node) {
|
|
3417
|
-
nodeFS2 = eval("require")("fs");
|
|
3418
|
-
nodePath = eval("require")("path");
|
|
3419
|
-
nodeFS2.promises = nodeFS2.promises || eval("require")("fs").promises;
|
|
3420
|
-
}
|
|
3421
|
-
} catch {
|
|
3422
|
-
}
|
|
3423
|
-
StaticToCouchDBMigrator = class {
|
|
3424
|
-
options;
|
|
3425
|
-
progressCallback;
|
|
3426
|
-
fs;
|
|
3427
|
-
constructor(options = {}, fileSystemAdapter) {
|
|
3428
|
-
this.options = {
|
|
3429
|
-
...DEFAULT_MIGRATION_OPTIONS,
|
|
3430
|
-
...options
|
|
3431
|
-
};
|
|
3432
|
-
this.fs = fileSystemAdapter;
|
|
3433
|
-
}
|
|
3434
|
-
/**
|
|
3435
|
-
* Set a progress callback to receive updates during migration
|
|
3436
|
-
*/
|
|
3437
|
-
setProgressCallback(callback) {
|
|
3438
|
-
this.progressCallback = callback;
|
|
3439
|
-
}
|
|
3440
|
-
/**
|
|
3441
|
-
* Migrate a static course to CouchDB
|
|
3442
|
-
*/
|
|
3443
|
-
async migrateCourse(staticPath, targetDB) {
|
|
3444
|
-
const startTime = Date.now();
|
|
3445
|
-
const result = {
|
|
3446
|
-
success: false,
|
|
3447
|
-
documentsRestored: 0,
|
|
3448
|
-
attachmentsRestored: 0,
|
|
3449
|
-
designDocsRestored: 0,
|
|
3450
|
-
courseConfigRestored: 0,
|
|
3451
|
-
errors: [],
|
|
3452
|
-
warnings: [],
|
|
3453
|
-
migrationTime: 0
|
|
3454
|
-
};
|
|
3455
|
-
try {
|
|
3456
|
-
logger.info(`Starting migration from ${staticPath} to CouchDB`);
|
|
3457
|
-
this.reportProgress("manifest", 0, 1, "Validating static course...");
|
|
3458
|
-
const validation = await validateStaticCourse(staticPath, this.fs);
|
|
3459
|
-
if (!validation.valid) {
|
|
3460
|
-
result.errors.push(...validation.errors);
|
|
3461
|
-
throw new Error(`Static course validation failed: ${validation.errors.join(", ")}`);
|
|
3462
|
-
}
|
|
3463
|
-
result.warnings.push(...validation.warnings);
|
|
3464
|
-
this.reportProgress("manifest", 1, 1, "Loading course manifest...");
|
|
3465
|
-
const manifest = await this.loadManifest(staticPath);
|
|
3466
|
-
logger.info(`Loaded manifest for course: ${manifest.courseId} (${manifest.courseName})`);
|
|
3467
|
-
this.reportProgress(
|
|
3468
|
-
"design_docs",
|
|
3469
|
-
0,
|
|
3470
|
-
manifest.designDocs.length,
|
|
3471
|
-
"Restoring design documents..."
|
|
3472
|
-
);
|
|
3473
|
-
const designDocResults = await this.restoreDesignDocuments(manifest.designDocs, targetDB);
|
|
3474
|
-
result.designDocsRestored = designDocResults.restored;
|
|
3475
|
-
result.errors.push(...designDocResults.errors);
|
|
3476
|
-
result.warnings.push(...designDocResults.warnings);
|
|
3477
|
-
this.reportProgress("course_config", 0, 1, "Restoring CourseConfig document...");
|
|
3478
|
-
const courseConfigResults = await this.restoreCourseConfig(manifest, targetDB);
|
|
3479
|
-
result.courseConfigRestored = courseConfigResults.restored;
|
|
3480
|
-
result.errors.push(...courseConfigResults.errors);
|
|
3481
|
-
result.warnings.push(...courseConfigResults.warnings);
|
|
3482
|
-
this.reportProgress("course_config", 1, 1, "CourseConfig document restored");
|
|
3483
|
-
const expectedCounts = this.calculateExpectedCounts(manifest);
|
|
3484
|
-
this.reportProgress(
|
|
3485
|
-
"documents",
|
|
3486
|
-
0,
|
|
3487
|
-
manifest.documentCount,
|
|
3488
|
-
"Aggregating documents from chunks..."
|
|
3489
|
-
);
|
|
3490
|
-
const documents = await this.aggregateDocuments(staticPath, manifest);
|
|
3491
|
-
const filteredDocuments = documents.filter((doc) => doc._id !== "CourseConfig");
|
|
3492
|
-
if (documents.length !== filteredDocuments.length) {
|
|
3493
|
-
result.warnings.push(
|
|
3494
|
-
`Filtered out ${documents.length - filteredDocuments.length} CourseConfig document(s) from chunks to prevent conflicts`
|
|
3495
|
-
);
|
|
3496
|
-
}
|
|
3497
|
-
this.reportProgress(
|
|
3498
|
-
"documents",
|
|
3499
|
-
filteredDocuments.length,
|
|
3500
|
-
manifest.documentCount,
|
|
3501
|
-
"Uploading documents to CouchDB..."
|
|
3502
|
-
);
|
|
3503
|
-
const docResults = await this.uploadDocuments(filteredDocuments, targetDB);
|
|
3504
|
-
result.documentsRestored = docResults.restored;
|
|
3505
|
-
result.errors.push(...docResults.errors);
|
|
3506
|
-
result.warnings.push(...docResults.warnings);
|
|
3507
|
-
const docsWithAttachments = documents.filter(
|
|
3508
|
-
(doc) => doc._attachments && Object.keys(doc._attachments).length > 0
|
|
3509
|
-
);
|
|
3510
|
-
this.reportProgress("attachments", 0, docsWithAttachments.length, "Uploading attachments...");
|
|
3511
|
-
const attachmentResults = await this.uploadAttachments(
|
|
3512
|
-
staticPath,
|
|
3513
|
-
docsWithAttachments,
|
|
3514
|
-
targetDB
|
|
3515
|
-
);
|
|
3516
|
-
result.attachmentsRestored = attachmentResults.restored;
|
|
3517
|
-
result.errors.push(...attachmentResults.errors);
|
|
3518
|
-
result.warnings.push(...attachmentResults.warnings);
|
|
3519
|
-
if (this.options.validateRoundTrip) {
|
|
3520
|
-
this.reportProgress("validation", 0, 1, "Validating migration...");
|
|
3521
|
-
const validationResult = await validateMigration(targetDB, expectedCounts, manifest);
|
|
3522
|
-
if (!validationResult.valid) {
|
|
3523
|
-
result.warnings.push("Migration validation found issues");
|
|
3524
|
-
validationResult.issues.forEach((issue) => {
|
|
3525
|
-
if (issue.type === "error") {
|
|
3526
|
-
result.errors.push(`Validation: ${issue.message}`);
|
|
3527
|
-
} else {
|
|
3528
|
-
result.warnings.push(`Validation: ${issue.message}`);
|
|
3529
|
-
}
|
|
3530
|
-
});
|
|
3531
|
-
}
|
|
3532
|
-
this.reportProgress("validation", 1, 1, "Migration validation completed");
|
|
3533
|
-
}
|
|
3534
|
-
result.success = result.errors.length === 0;
|
|
3535
|
-
result.migrationTime = Date.now() - startTime;
|
|
3536
|
-
logger.info(`Migration completed in ${result.migrationTime}ms`);
|
|
3537
|
-
logger.info(`Documents restored: ${result.documentsRestored}`);
|
|
3538
|
-
logger.info(`Attachments restored: ${result.attachmentsRestored}`);
|
|
3539
|
-
logger.info(`Design docs restored: ${result.designDocsRestored}`);
|
|
3540
|
-
logger.info(`CourseConfig restored: ${result.courseConfigRestored}`);
|
|
3541
|
-
if (result.errors.length > 0) {
|
|
3542
|
-
logger.error(`Migration completed with ${result.errors.length} errors`);
|
|
3543
|
-
}
|
|
3544
|
-
if (result.warnings.length > 0) {
|
|
3545
|
-
logger.warn(`Migration completed with ${result.warnings.length} warnings`);
|
|
3546
|
-
}
|
|
3547
|
-
} catch (error) {
|
|
3548
|
-
result.success = false;
|
|
3549
|
-
result.migrationTime = Date.now() - startTime;
|
|
3550
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3551
|
-
result.errors.push(`Migration failed: ${errorMessage}`);
|
|
3552
|
-
logger.error("Migration failed:", error);
|
|
3553
|
-
if (this.options.cleanupOnFailure) {
|
|
3554
|
-
try {
|
|
3555
|
-
await this.cleanupFailedMigration(targetDB);
|
|
3556
|
-
} catch (cleanupError) {
|
|
3557
|
-
logger.error("Failed to cleanup after migration failure:", cleanupError);
|
|
3558
|
-
result.warnings.push("Failed to cleanup after migration failure");
|
|
3559
|
-
}
|
|
3560
|
-
}
|
|
3561
|
-
}
|
|
3562
|
-
return result;
|
|
3563
|
-
}
|
|
3564
|
-
/**
|
|
3565
|
-
* Load and parse the manifest file
|
|
3566
|
-
*/
|
|
3567
|
-
async loadManifest(staticPath) {
|
|
3568
|
-
try {
|
|
3569
|
-
let manifestContent;
|
|
3570
|
-
let manifestPath;
|
|
3571
|
-
if (this.fs) {
|
|
3572
|
-
manifestPath = this.fs.joinPath(staticPath, "manifest.json");
|
|
3573
|
-
manifestContent = await this.fs.readFile(manifestPath);
|
|
3574
|
-
} else {
|
|
3575
|
-
manifestPath = nodeFS2 && nodePath ? nodePath.join(staticPath, "manifest.json") : `${staticPath}/manifest.json`;
|
|
3576
|
-
if (nodeFS2 && this.isLocalPath(staticPath)) {
|
|
3577
|
-
manifestContent = await nodeFS2.promises.readFile(manifestPath, "utf8");
|
|
3578
|
-
} else {
|
|
3579
|
-
const response = await fetch(manifestPath);
|
|
3580
|
-
if (!response.ok) {
|
|
3581
|
-
throw new Error(`Failed to fetch manifest: ${response.status} ${response.statusText}`);
|
|
3582
|
-
}
|
|
3583
|
-
manifestContent = await response.text();
|
|
3584
|
-
}
|
|
3585
|
-
}
|
|
3586
|
-
const manifest = JSON.parse(manifestContent);
|
|
3587
|
-
if (!manifest.version || !manifest.courseId || !manifest.chunks) {
|
|
3588
|
-
throw new Error("Invalid manifest structure");
|
|
3589
|
-
}
|
|
3590
|
-
return manifest;
|
|
3591
|
-
} catch (error) {
|
|
3592
|
-
const errorMessage = error instanceof FileSystemError ? error.message : `Failed to load manifest: ${error instanceof Error ? error.message : String(error)}`;
|
|
3593
|
-
throw new Error(errorMessage);
|
|
3594
|
-
}
|
|
3595
|
-
}
|
|
3596
|
-
/**
|
|
3597
|
-
* Restore design documents to CouchDB
|
|
3598
|
-
*/
|
|
3599
|
-
async restoreDesignDocuments(designDocs, db) {
|
|
3600
|
-
const result = { restored: 0, errors: [], warnings: [] };
|
|
3601
|
-
for (let i = 0; i < designDocs.length; i++) {
|
|
3602
|
-
const designDoc = designDocs[i];
|
|
3603
|
-
this.reportProgress("design_docs", i, designDocs.length, `Restoring ${designDoc._id}...`);
|
|
3604
|
-
try {
|
|
3605
|
-
let existingDoc;
|
|
3606
|
-
try {
|
|
3607
|
-
existingDoc = await db.get(designDoc._id);
|
|
3608
|
-
} catch {
|
|
3609
|
-
}
|
|
3610
|
-
const docToInsert = {
|
|
3611
|
-
_id: designDoc._id,
|
|
3612
|
-
views: designDoc.views
|
|
3613
|
-
};
|
|
3614
|
-
if (existingDoc) {
|
|
3615
|
-
docToInsert._rev = existingDoc._rev;
|
|
3616
|
-
logger.debug(`Updating existing design document: ${designDoc._id}`);
|
|
3617
|
-
} else {
|
|
3618
|
-
logger.debug(`Creating new design document: ${designDoc._id}`);
|
|
3619
|
-
}
|
|
3620
|
-
await db.put(docToInsert);
|
|
3621
|
-
result.restored++;
|
|
3622
|
-
} catch (error) {
|
|
3623
|
-
const errorMessage = `Failed to restore design document ${designDoc._id}: ${error instanceof Error ? error.message : String(error)}`;
|
|
3624
|
-
result.errors.push(errorMessage);
|
|
3625
|
-
logger.error(errorMessage);
|
|
3626
|
-
}
|
|
3627
|
-
}
|
|
3628
|
-
this.reportProgress(
|
|
3629
|
-
"design_docs",
|
|
3630
|
-
designDocs.length,
|
|
3631
|
-
designDocs.length,
|
|
3632
|
-
`Restored ${result.restored} design documents`
|
|
3633
|
-
);
|
|
3634
|
-
return result;
|
|
3635
|
-
}
|
|
3636
|
-
/**
|
|
3637
|
-
* Aggregate documents from all chunks
|
|
3638
|
-
*/
|
|
3639
|
-
async aggregateDocuments(staticPath, manifest) {
|
|
3640
|
-
const allDocuments = [];
|
|
3641
|
-
const documentMap = /* @__PURE__ */ new Map();
|
|
3642
|
-
for (let i = 0; i < manifest.chunks.length; i++) {
|
|
3643
|
-
const chunk = manifest.chunks[i];
|
|
3644
|
-
this.reportProgress(
|
|
3645
|
-
"documents",
|
|
3646
|
-
allDocuments.length,
|
|
3647
|
-
manifest.documentCount,
|
|
3648
|
-
`Loading chunk ${chunk.id}...`
|
|
3649
|
-
);
|
|
3650
|
-
try {
|
|
3651
|
-
const documents = await this.loadChunk(staticPath, chunk);
|
|
3652
|
-
for (const doc of documents) {
|
|
3653
|
-
if (!doc._id) {
|
|
3654
|
-
logger.warn(`Document without _id found in chunk ${chunk.id}, skipping`);
|
|
3655
|
-
continue;
|
|
3656
|
-
}
|
|
3657
|
-
if (documentMap.has(doc._id)) {
|
|
3658
|
-
logger.warn(`Duplicate document ID found: ${doc._id}, using latest version`);
|
|
3659
|
-
}
|
|
3660
|
-
documentMap.set(doc._id, doc);
|
|
3661
|
-
}
|
|
3662
|
-
} catch (error) {
|
|
3663
|
-
throw new Error(
|
|
3664
|
-
`Failed to load chunk ${chunk.id}: ${error instanceof Error ? error.message : String(error)}`
|
|
3665
|
-
);
|
|
3666
|
-
}
|
|
3667
|
-
}
|
|
3668
|
-
allDocuments.push(...documentMap.values());
|
|
3669
|
-
logger.info(
|
|
3670
|
-
`Aggregated ${allDocuments.length} unique documents from ${manifest.chunks.length} chunks`
|
|
3671
|
-
);
|
|
3672
|
-
return allDocuments;
|
|
3673
|
-
}
|
|
3674
|
-
/**
|
|
3675
|
-
* Load documents from a single chunk file
|
|
3676
|
-
*/
|
|
3677
|
-
async loadChunk(staticPath, chunk) {
|
|
3678
|
-
try {
|
|
3679
|
-
let chunkContent;
|
|
3680
|
-
let chunkPath;
|
|
3681
|
-
if (this.fs) {
|
|
3682
|
-
chunkPath = this.fs.joinPath(staticPath, chunk.path);
|
|
3683
|
-
chunkContent = await this.fs.readFile(chunkPath);
|
|
3684
|
-
} else {
|
|
3685
|
-
chunkPath = nodeFS2 && nodePath ? nodePath.join(staticPath, chunk.path) : `${staticPath}/${chunk.path}`;
|
|
3686
|
-
if (nodeFS2 && this.isLocalPath(staticPath)) {
|
|
3687
|
-
chunkContent = await nodeFS2.promises.readFile(chunkPath, "utf8");
|
|
3688
|
-
} else {
|
|
3689
|
-
const response = await fetch(chunkPath);
|
|
3690
|
-
if (!response.ok) {
|
|
3691
|
-
throw new Error(`Failed to fetch chunk: ${response.status} ${response.statusText}`);
|
|
3692
|
-
}
|
|
3693
|
-
chunkContent = await response.text();
|
|
3694
|
-
}
|
|
3695
|
-
}
|
|
3696
|
-
const documents = JSON.parse(chunkContent);
|
|
3697
|
-
if (!Array.isArray(documents)) {
|
|
3698
|
-
throw new Error("Chunk file does not contain an array of documents");
|
|
3699
|
-
}
|
|
3700
|
-
return documents;
|
|
3701
|
-
} catch (error) {
|
|
3702
|
-
const errorMessage = error instanceof FileSystemError ? error.message : `Failed to load chunk: ${error instanceof Error ? error.message : String(error)}`;
|
|
3703
|
-
throw new Error(errorMessage);
|
|
3704
|
-
}
|
|
3705
|
-
}
|
|
3706
|
-
/**
|
|
3707
|
-
* Upload documents to CouchDB in batches
|
|
3708
|
-
*/
|
|
3709
|
-
async uploadDocuments(documents, db) {
|
|
3710
|
-
const result = { restored: 0, errors: [], warnings: [] };
|
|
3711
|
-
const batchSize = this.options.chunkBatchSize;
|
|
3712
|
-
for (let i = 0; i < documents.length; i += batchSize) {
|
|
3713
|
-
const batch = documents.slice(i, i + batchSize);
|
|
3714
|
-
this.reportProgress(
|
|
3715
|
-
"documents",
|
|
3716
|
-
i,
|
|
3717
|
-
documents.length,
|
|
3718
|
-
`Uploading batch ${Math.floor(i / batchSize) + 1}...`
|
|
3719
|
-
);
|
|
3720
|
-
try {
|
|
3721
|
-
const docsToInsert = batch.map((doc) => {
|
|
3722
|
-
const cleanDoc = { ...doc };
|
|
3723
|
-
delete cleanDoc._rev;
|
|
3724
|
-
delete cleanDoc._attachments;
|
|
3725
|
-
return cleanDoc;
|
|
3726
|
-
});
|
|
3727
|
-
const bulkResult = await db.bulkDocs(docsToInsert);
|
|
3728
|
-
for (let j = 0; j < bulkResult.length; j++) {
|
|
3729
|
-
const docResult = bulkResult[j];
|
|
3730
|
-
const originalDoc = batch[j];
|
|
3731
|
-
if ("error" in docResult) {
|
|
3732
|
-
const errorMessage = `Failed to upload document ${originalDoc._id}: ${docResult.error} - ${docResult.reason}`;
|
|
3733
|
-
result.errors.push(errorMessage);
|
|
3734
|
-
logger.error(errorMessage);
|
|
3735
|
-
} else {
|
|
3736
|
-
result.restored++;
|
|
3737
|
-
}
|
|
3738
|
-
}
|
|
3739
|
-
} catch (error) {
|
|
3740
|
-
let errorMessage;
|
|
3741
|
-
if (error instanceof Error) {
|
|
3742
|
-
errorMessage = `Failed to upload document batch starting at index ${i}: ${error.message}`;
|
|
3743
|
-
} else if (error && typeof error === "object" && "message" in error) {
|
|
3744
|
-
errorMessage = `Failed to upload document batch starting at index ${i}: ${error.message}`;
|
|
3745
|
-
} else {
|
|
3746
|
-
errorMessage = `Failed to upload document batch starting at index ${i}: ${JSON.stringify(error)}`;
|
|
3747
|
-
}
|
|
3748
|
-
result.errors.push(errorMessage);
|
|
3749
|
-
logger.error(errorMessage);
|
|
3750
|
-
}
|
|
3751
|
-
}
|
|
3752
|
-
this.reportProgress(
|
|
3753
|
-
"documents",
|
|
3754
|
-
documents.length,
|
|
3755
|
-
documents.length,
|
|
3756
|
-
`Uploaded ${result.restored} documents`
|
|
3757
|
-
);
|
|
3758
|
-
return result;
|
|
3759
|
-
}
|
|
3760
|
-
/**
|
|
3761
|
-
* Upload attachments from filesystem to CouchDB
|
|
3762
|
-
*/
|
|
3763
|
-
async uploadAttachments(staticPath, documents, db) {
|
|
3764
|
-
const result = { restored: 0, errors: [], warnings: [] };
|
|
3765
|
-
let processedDocs = 0;
|
|
3766
|
-
for (const doc of documents) {
|
|
3767
|
-
this.reportProgress(
|
|
3768
|
-
"attachments",
|
|
3769
|
-
processedDocs,
|
|
3770
|
-
documents.length,
|
|
3771
|
-
`Processing attachments for ${doc._id}...`
|
|
3772
|
-
);
|
|
3773
|
-
processedDocs++;
|
|
3774
|
-
if (!doc._attachments) {
|
|
3775
|
-
continue;
|
|
3776
|
-
}
|
|
3777
|
-
for (const [attachmentName, attachmentMeta] of Object.entries(doc._attachments)) {
|
|
3778
|
-
try {
|
|
3779
|
-
const uploadResult = await this.uploadSingleAttachment(
|
|
3780
|
-
staticPath,
|
|
3781
|
-
doc._id,
|
|
3782
|
-
attachmentName,
|
|
3783
|
-
attachmentMeta,
|
|
3784
|
-
db
|
|
3785
|
-
);
|
|
3786
|
-
if (uploadResult.success) {
|
|
3787
|
-
result.restored++;
|
|
3788
|
-
} else {
|
|
3789
|
-
result.errors.push(uploadResult.error || "Unknown attachment upload error");
|
|
3790
|
-
}
|
|
3791
|
-
} catch (error) {
|
|
3792
|
-
const errorMessage = `Failed to upload attachment ${doc._id}/${attachmentName}: ${error instanceof Error ? error.message : String(error)}`;
|
|
3793
|
-
result.errors.push(errorMessage);
|
|
3794
|
-
logger.error(errorMessage);
|
|
3795
|
-
}
|
|
3796
|
-
}
|
|
3797
|
-
}
|
|
3798
|
-
this.reportProgress(
|
|
3799
|
-
"attachments",
|
|
3800
|
-
documents.length,
|
|
3801
|
-
documents.length,
|
|
3802
|
-
`Uploaded ${result.restored} attachments`
|
|
3803
|
-
);
|
|
3804
|
-
return result;
|
|
3805
|
-
}
|
|
3806
|
-
/**
|
|
3807
|
-
* Upload a single attachment file
|
|
3808
|
-
*/
|
|
3809
|
-
async uploadSingleAttachment(staticPath, docId, attachmentName, attachmentMeta, db) {
|
|
3810
|
-
const result = {
|
|
3811
|
-
success: false,
|
|
3812
|
-
attachmentName,
|
|
3813
|
-
docId
|
|
3814
|
-
};
|
|
3815
|
-
try {
|
|
3816
|
-
if (!attachmentMeta.path) {
|
|
3817
|
-
result.error = "Attachment metadata missing file path";
|
|
3818
|
-
return result;
|
|
3819
|
-
}
|
|
3820
|
-
let attachmentData;
|
|
3821
|
-
let attachmentPath;
|
|
3822
|
-
if (this.fs) {
|
|
3823
|
-
attachmentPath = this.fs.joinPath(staticPath, attachmentMeta.path);
|
|
3824
|
-
attachmentData = await this.fs.readBinary(attachmentPath);
|
|
3825
|
-
} else {
|
|
3826
|
-
attachmentPath = nodeFS2 && nodePath ? nodePath.join(staticPath, attachmentMeta.path) : `${staticPath}/${attachmentMeta.path}`;
|
|
3827
|
-
if (nodeFS2 && this.isLocalPath(staticPath)) {
|
|
3828
|
-
attachmentData = await nodeFS2.promises.readFile(attachmentPath);
|
|
3829
|
-
} else {
|
|
3830
|
-
const response = await fetch(attachmentPath);
|
|
3831
|
-
if (!response.ok) {
|
|
3832
|
-
result.error = `Failed to fetch attachment: ${response.status} ${response.statusText}`;
|
|
3833
|
-
return result;
|
|
3834
|
-
}
|
|
3835
|
-
attachmentData = await response.arrayBuffer();
|
|
3836
|
-
}
|
|
3837
|
-
}
|
|
3838
|
-
const doc = await db.get(docId);
|
|
3839
|
-
await db.putAttachment(
|
|
3840
|
-
docId,
|
|
3841
|
-
attachmentName,
|
|
3842
|
-
doc._rev,
|
|
3843
|
-
attachmentData,
|
|
3844
|
-
// PouchDB accepts both ArrayBuffer and Buffer
|
|
3845
|
-
attachmentMeta.content_type
|
|
3846
|
-
);
|
|
3847
|
-
result.success = true;
|
|
3848
|
-
} catch (error) {
|
|
3849
|
-
result.error = error instanceof Error ? error.message : String(error);
|
|
3850
|
-
}
|
|
3851
|
-
return result;
|
|
3852
|
-
}
|
|
3853
|
-
/**
|
|
3854
|
-
* Restore CourseConfig document from manifest
|
|
3855
|
-
*/
|
|
3856
|
-
async restoreCourseConfig(manifest, targetDB) {
|
|
3857
|
-
const results = {
|
|
3858
|
-
restored: 0,
|
|
3859
|
-
errors: [],
|
|
3860
|
-
warnings: []
|
|
3861
|
-
};
|
|
3862
|
-
try {
|
|
3863
|
-
if (!manifest.courseConfig) {
|
|
3864
|
-
results.warnings.push(
|
|
3865
|
-
"No courseConfig found in manifest, skipping CourseConfig document creation"
|
|
3866
|
-
);
|
|
3867
|
-
return results;
|
|
3868
|
-
}
|
|
3869
|
-
const courseConfigDoc = {
|
|
3870
|
-
_id: "CourseConfig",
|
|
3871
|
-
...manifest.courseConfig,
|
|
3872
|
-
courseID: manifest.courseId
|
|
3873
|
-
};
|
|
3874
|
-
delete courseConfigDoc._rev;
|
|
3875
|
-
await targetDB.put(courseConfigDoc);
|
|
3876
|
-
results.restored = 1;
|
|
3877
|
-
logger.info(`CourseConfig document created for course: ${manifest.courseId}`);
|
|
3878
|
-
} catch (error) {
|
|
3879
|
-
const errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
|
|
3880
|
-
results.errors.push(`Failed to restore CourseConfig: ${errorMessage}`);
|
|
3881
|
-
logger.error("CourseConfig restoration failed:", error);
|
|
3882
|
-
}
|
|
3883
|
-
return results;
|
|
3884
|
-
}
|
|
3885
|
-
/**
|
|
3886
|
-
* Calculate expected document counts from manifest
|
|
3887
|
-
*/
|
|
3888
|
-
calculateExpectedCounts(manifest) {
|
|
3889
|
-
const counts = {};
|
|
3890
|
-
for (const chunk of manifest.chunks) {
|
|
3891
|
-
counts[chunk.docType] = (counts[chunk.docType] || 0) + chunk.documentCount;
|
|
3892
|
-
}
|
|
3893
|
-
if (manifest.designDocs.length > 0) {
|
|
3894
|
-
counts["_design"] = manifest.designDocs.length;
|
|
3895
|
-
}
|
|
3896
|
-
return counts;
|
|
3897
|
-
}
|
|
3898
|
-
/**
|
|
3899
|
-
* Clean up database after failed migration
|
|
3900
|
-
*/
|
|
3901
|
-
async cleanupFailedMigration(db) {
|
|
3902
|
-
logger.info("Cleaning up failed migration...");
|
|
3903
|
-
try {
|
|
3904
|
-
const allDocs = await db.allDocs();
|
|
3905
|
-
const docsToDelete = allDocs.rows.map((row) => ({
|
|
3906
|
-
_id: row.id,
|
|
3907
|
-
_rev: row.value.rev,
|
|
3908
|
-
_deleted: true
|
|
3909
|
-
}));
|
|
3910
|
-
if (docsToDelete.length > 0) {
|
|
3911
|
-
await db.bulkDocs(docsToDelete);
|
|
3912
|
-
logger.info(`Cleaned up ${docsToDelete.length} documents from failed migration`);
|
|
3913
|
-
}
|
|
3914
|
-
} catch (error) {
|
|
3915
|
-
logger.error("Failed to cleanup documents:", error);
|
|
3916
|
-
throw error;
|
|
3917
|
-
}
|
|
3918
|
-
}
|
|
3919
|
-
/**
|
|
3920
|
-
* Report progress to callback if available
|
|
3921
|
-
*/
|
|
3922
|
-
reportProgress(phase, current, total, message) {
|
|
3923
|
-
if (this.progressCallback) {
|
|
3924
|
-
this.progressCallback({
|
|
3925
|
-
phase,
|
|
3926
|
-
current,
|
|
3927
|
-
total,
|
|
3928
|
-
message
|
|
3929
|
-
});
|
|
3930
|
-
}
|
|
3931
|
-
}
|
|
3932
|
-
/**
|
|
3933
|
-
* Check if a path is a local file path (vs URL)
|
|
3934
|
-
*/
|
|
3935
|
-
isLocalPath(path2) {
|
|
3936
|
-
return !path2.startsWith("http://") && !path2.startsWith("https://");
|
|
3937
|
-
}
|
|
3938
|
-
};
|
|
3939
|
-
}
|
|
3940
|
-
});
|
|
3941
|
-
|
|
3942
|
-
// src/util/migrator/index.ts
|
|
3943
|
-
var init_migrator = __esm({
|
|
3944
|
-
"src/util/migrator/index.ts"() {
|
|
3945
|
-
"use strict";
|
|
3946
|
-
init_StaticToCouchDBMigrator();
|
|
3947
|
-
init_validation();
|
|
3948
|
-
init_FileSystemAdapter();
|
|
3949
|
-
}
|
|
3950
|
-
});
|
|
3951
|
-
|
|
3952
|
-
// src/util/index.ts
|
|
3953
|
-
var init_util2 = __esm({
|
|
3954
|
-
"src/util/index.ts"() {
|
|
3955
|
-
"use strict";
|
|
3956
|
-
init_Loggable();
|
|
3957
|
-
init_packer();
|
|
3958
|
-
init_migrator();
|
|
3959
|
-
init_dataDirectory();
|
|
3960
|
-
}
|
|
3961
|
-
});
|
|
3962
|
-
|
|
3963
|
-
// src/study/SourceMixer.ts
|
|
3964
|
-
var init_SourceMixer = __esm({
|
|
3965
|
-
"src/study/SourceMixer.ts"() {
|
|
3966
|
-
"use strict";
|
|
3967
|
-
}
|
|
3968
|
-
});
|
|
3969
|
-
|
|
3970
|
-
// src/study/MixerDebugger.ts
|
|
3971
|
-
function printMixerSummary(run) {
|
|
3972
|
-
console.group(`\u{1F3A8} Mixer Run: ${run.mixerType}`);
|
|
3973
|
-
logger.info(`Run ID: ${run.runId}`);
|
|
3974
|
-
logger.info(`Time: ${run.timestamp.toISOString()}`);
|
|
3975
|
-
logger.info(
|
|
3976
|
-
`Config: limit=${run.requestedLimit}${run.quotaPerSource ? `, quota/source=${run.quotaPerSource}` : ""}`
|
|
3977
|
-
);
|
|
3978
|
-
console.group(`\u{1F4E5} Input: ${run.sourceSummaries.length} sources`);
|
|
3979
|
-
for (const src of run.sourceSummaries) {
|
|
3980
|
-
logger.info(
|
|
3981
|
-
` ${src.sourceName || src.sourceId}: ${src.totalCards} cards (${src.reviewCount} reviews, ${src.newCount} new)`
|
|
3982
|
-
);
|
|
3983
|
-
logger.info(` Score range: [${src.scoreRange[0].toFixed(2)}, ${src.scoreRange[1].toFixed(2)}], avg: ${src.avgScore.toFixed(2)}`);
|
|
3984
|
-
}
|
|
3985
|
-
console.groupEnd();
|
|
3986
|
-
console.group(`\u{1F4E4} Output: ${run.finalCount} cards selected (${run.reviewsSelected} reviews, ${run.newSelected} new)`);
|
|
3987
|
-
for (const breakdown of run.sourceBreakdowns) {
|
|
3988
|
-
const name = breakdown.sourceName || breakdown.sourceId;
|
|
3989
|
-
logger.info(
|
|
3990
|
-
` ${name}: ${breakdown.totalSelected} selected (${breakdown.reviewsSelected} reviews, ${breakdown.newSelected} new) - ${breakdown.selectionRate.toFixed(1)}% selection rate`
|
|
3991
|
-
);
|
|
3992
|
-
}
|
|
3993
|
-
console.groupEnd();
|
|
3994
|
-
console.groupEnd();
|
|
3995
|
-
}
|
|
3996
|
-
function mountMixerDebugger() {
|
|
3997
|
-
if (typeof window === "undefined") return;
|
|
3998
|
-
const win = window;
|
|
3999
|
-
win.skuilder = win.skuilder || {};
|
|
4000
|
-
win.skuilder.mixer = mixerDebugAPI;
|
|
4001
|
-
}
|
|
4002
|
-
var runHistory2, mixerDebugAPI;
|
|
4003
|
-
var init_MixerDebugger = __esm({
|
|
4004
|
-
"src/study/MixerDebugger.ts"() {
|
|
4005
|
-
"use strict";
|
|
4006
|
-
init_logger();
|
|
4007
|
-
init_navigators();
|
|
4008
|
-
runHistory2 = [];
|
|
4009
|
-
mixerDebugAPI = {
|
|
4010
|
-
/**
|
|
4011
|
-
* Get raw run history for programmatic access.
|
|
4012
|
-
*/
|
|
4013
|
-
get runs() {
|
|
4014
|
-
return [...runHistory2];
|
|
4015
|
-
},
|
|
4016
|
-
/**
|
|
4017
|
-
* Show summary of a specific mixer run.
|
|
4018
|
-
*/
|
|
4019
|
-
showRun(idOrIndex = 0) {
|
|
4020
|
-
if (runHistory2.length === 0) {
|
|
4021
|
-
logger.info("[Mixer Debug] No runs captured yet.");
|
|
4022
|
-
return;
|
|
4023
|
-
}
|
|
4024
|
-
let run;
|
|
4025
|
-
if (typeof idOrIndex === "number") {
|
|
4026
|
-
run = runHistory2[idOrIndex];
|
|
4027
|
-
if (!run) {
|
|
4028
|
-
logger.info(`[Mixer Debug] No run found at index ${idOrIndex}. History length: ${runHistory2.length}`);
|
|
4029
|
-
return;
|
|
4030
|
-
}
|
|
4031
|
-
} else {
|
|
4032
|
-
run = runHistory2.find((r) => r.runId.endsWith(idOrIndex));
|
|
4033
|
-
if (!run) {
|
|
4034
|
-
logger.info(`[Mixer Debug] No run found matching ID '${idOrIndex}'.`);
|
|
4035
|
-
return;
|
|
4036
|
-
}
|
|
4037
|
-
}
|
|
4038
|
-
printMixerSummary(run);
|
|
4039
|
-
},
|
|
4040
|
-
/**
|
|
4041
|
-
* Show summary of the last mixer run.
|
|
4042
|
-
*/
|
|
4043
|
-
showLastMix() {
|
|
4044
|
-
this.showRun(0);
|
|
4045
|
-
},
|
|
4046
|
-
/**
|
|
4047
|
-
* Explain source balance in the last run.
|
|
4048
|
-
*/
|
|
4049
|
-
explainSourceBalance() {
|
|
4050
|
-
if (runHistory2.length === 0) {
|
|
4051
|
-
logger.info("[Mixer Debug] No runs captured yet.");
|
|
4052
|
-
return;
|
|
4053
|
-
}
|
|
4054
|
-
const run = runHistory2[0];
|
|
4055
|
-
console.group("\u2696\uFE0F Source Balance Analysis");
|
|
4056
|
-
logger.info(`Mixer: ${run.mixerType}`);
|
|
4057
|
-
logger.info(`Requested limit: ${run.requestedLimit}`);
|
|
4058
|
-
if (run.quotaPerSource) {
|
|
4059
|
-
logger.info(`Quota per source: ${run.quotaPerSource}`);
|
|
4060
|
-
}
|
|
4061
|
-
console.group("Input Distribution:");
|
|
4062
|
-
for (const src of run.sourceSummaries) {
|
|
4063
|
-
const name = src.sourceName || src.sourceId;
|
|
4064
|
-
logger.info(`${name}:`);
|
|
4065
|
-
logger.info(` Provided: ${src.totalCards} cards (${src.reviewCount} reviews, ${src.newCount} new)`);
|
|
4066
|
-
logger.info(` Score range: [${src.scoreRange[0].toFixed(2)}, ${src.scoreRange[1].toFixed(2)}]`);
|
|
4067
|
-
}
|
|
4068
|
-
console.groupEnd();
|
|
4069
|
-
console.group("Selection Results:");
|
|
4070
|
-
for (const breakdown of run.sourceBreakdowns) {
|
|
4071
|
-
const name = breakdown.sourceName || breakdown.sourceId;
|
|
4072
|
-
logger.info(`${name}:`);
|
|
4073
|
-
logger.info(
|
|
4074
|
-
` Selected: ${breakdown.totalSelected}/${breakdown.reviewsProvided + breakdown.newProvided} (${breakdown.selectionRate.toFixed(1)}%)`
|
|
4075
|
-
);
|
|
4076
|
-
logger.info(` Reviews: ${breakdown.reviewsSelected}/${breakdown.reviewsProvided}`);
|
|
4077
|
-
logger.info(` New: ${breakdown.newSelected}/${breakdown.newProvided}`);
|
|
4078
|
-
if (breakdown.reviewsProvided > 0 && breakdown.reviewsSelected === 0) {
|
|
4079
|
-
logger.info(` \u26A0\uFE0F Had reviews but none selected!`);
|
|
4080
|
-
}
|
|
4081
|
-
if (breakdown.totalSelected === 0 && breakdown.reviewsProvided + breakdown.newProvided > 0) {
|
|
4082
|
-
logger.info(` \u26A0\uFE0F Had cards but none selected!`);
|
|
4083
|
-
}
|
|
4084
|
-
}
|
|
4085
|
-
console.groupEnd();
|
|
4086
|
-
const selectionRates = run.sourceBreakdowns.map((b) => b.selectionRate);
|
|
4087
|
-
const avgRate = selectionRates.reduce((a, b) => a + b, 0) / selectionRates.length;
|
|
4088
|
-
const maxDeviation = Math.max(...selectionRates.map((r) => Math.abs(r - avgRate)));
|
|
4089
|
-
if (maxDeviation > 20) {
|
|
4090
|
-
logger.info(`
|
|
4091
|
-
\u26A0\uFE0F Significant imbalance detected (max deviation: ${maxDeviation.toFixed(1)}%)`);
|
|
4092
|
-
logger.info("Possible causes:");
|
|
4093
|
-
logger.info(" - Score range differences between sources");
|
|
4094
|
-
logger.info(" - One source has much better quality cards");
|
|
4095
|
-
logger.info(" - Different card availability (reviews vs new)");
|
|
4096
|
-
}
|
|
4097
|
-
console.groupEnd();
|
|
4098
|
-
},
|
|
4099
|
-
/**
|
|
4100
|
-
* Compare score distributions across sources.
|
|
4101
|
-
*/
|
|
4102
|
-
compareScores() {
|
|
4103
|
-
if (runHistory2.length === 0) {
|
|
4104
|
-
logger.info("[Mixer Debug] No runs captured yet.");
|
|
4105
|
-
return;
|
|
4106
|
-
}
|
|
4107
|
-
const run = runHistory2[0];
|
|
4108
|
-
console.group("\u{1F4CA} Score Distribution Comparison");
|
|
4109
|
-
console.table(
|
|
4110
|
-
run.sourceSummaries.map((src) => ({
|
|
4111
|
-
source: src.sourceName || src.sourceId,
|
|
4112
|
-
cards: src.totalCards,
|
|
4113
|
-
min: src.bottomScore.toFixed(3),
|
|
4114
|
-
max: src.topScore.toFixed(3),
|
|
4115
|
-
avg: src.avgScore.toFixed(3),
|
|
4116
|
-
range: (src.topScore - src.bottomScore).toFixed(3)
|
|
4117
|
-
}))
|
|
4118
|
-
);
|
|
4119
|
-
const ranges = run.sourceSummaries.map((s) => s.topScore - s.bottomScore);
|
|
4120
|
-
const avgScores = run.sourceSummaries.map((s) => s.avgScore);
|
|
4121
|
-
const rangeDiff = Math.max(...ranges) - Math.min(...ranges);
|
|
4122
|
-
const avgDiff = Math.max(...avgScores) - Math.min(...avgScores);
|
|
4123
|
-
if (rangeDiff > 0.3 || avgDiff > 0.2) {
|
|
4124
|
-
logger.info("\n\u26A0\uFE0F Significant score distribution differences detected");
|
|
4125
|
-
logger.info(
|
|
4126
|
-
"This may cause one source to dominate selection if using global sorting (not quota-based)"
|
|
4127
|
-
);
|
|
4128
|
-
}
|
|
4129
|
-
console.groupEnd();
|
|
4130
|
-
},
|
|
4131
|
-
/**
|
|
4132
|
-
* Show detailed information for a specific card.
|
|
4133
|
-
*/
|
|
4134
|
-
showCard(cardId) {
|
|
4135
|
-
for (const run of runHistory2) {
|
|
4136
|
-
const card = run.cards.find((c) => c.cardId === cardId);
|
|
4137
|
-
if (card) {
|
|
4138
|
-
const source = run.sourceSummaries.find((s) => s.sourceIndex === card.sourceIndex);
|
|
4139
|
-
console.group(`\u{1F3B4} Card: ${cardId}`);
|
|
4140
|
-
logger.info(`Course: ${card.courseId}`);
|
|
4141
|
-
logger.info(`Source: ${source?.sourceName || source?.sourceId || "unknown"}`);
|
|
4142
|
-
logger.info(`Origin: ${card.origin}`);
|
|
4143
|
-
logger.info(`Score: ${card.score.toFixed(3)}`);
|
|
4144
|
-
if (card.rankInSource) {
|
|
4145
|
-
logger.info(`Rank in source: #${card.rankInSource}`);
|
|
4146
|
-
}
|
|
4147
|
-
if (card.rankInMix) {
|
|
4148
|
-
logger.info(`Rank in mixed results: #${card.rankInMix}`);
|
|
4149
|
-
}
|
|
4150
|
-
logger.info(`Selected: ${card.selected ? "Yes \u2705" : "No \u274C"}`);
|
|
4151
|
-
if (!card.selected && card.rankInSource) {
|
|
4152
|
-
logger.info("\nWhy not selected:");
|
|
4153
|
-
if (run.quotaPerSource && card.rankInSource > run.quotaPerSource) {
|
|
4154
|
-
logger.info(` - Ranked #${card.rankInSource} in source, but quota was ${run.quotaPerSource}`);
|
|
4155
|
-
}
|
|
4156
|
-
logger.info(" - Check score compared to selected cards using .showRun()");
|
|
4157
|
-
}
|
|
4158
|
-
console.groupEnd();
|
|
4159
|
-
return;
|
|
4160
|
-
}
|
|
4161
|
-
}
|
|
4162
|
-
logger.info(`[Mixer Debug] Card '${cardId}' not found in recent runs.`);
|
|
4163
|
-
},
|
|
4164
|
-
/**
|
|
4165
|
-
* Show all runs in compact format.
|
|
4166
|
-
*/
|
|
4167
|
-
listRuns() {
|
|
4168
|
-
if (runHistory2.length === 0) {
|
|
4169
|
-
logger.info("[Mixer Debug] No runs captured yet.");
|
|
4170
|
-
return;
|
|
4171
|
-
}
|
|
4172
|
-
console.table(
|
|
4173
|
-
runHistory2.map((r) => ({
|
|
4174
|
-
id: r.runId.slice(-8),
|
|
4175
|
-
time: r.timestamp.toLocaleTimeString(),
|
|
4176
|
-
mixer: r.mixerType,
|
|
4177
|
-
sources: r.sourceSummaries.length,
|
|
4178
|
-
selected: r.finalCount,
|
|
4179
|
-
reviews: r.reviewsSelected,
|
|
4180
|
-
new: r.newSelected
|
|
4181
|
-
}))
|
|
4182
|
-
);
|
|
4183
|
-
},
|
|
4184
|
-
/**
|
|
4185
|
-
* Export run history as JSON for bug reports.
|
|
4186
|
-
*/
|
|
4187
|
-
export() {
|
|
4188
|
-
const json = JSON.stringify(runHistory2, null, 2);
|
|
4189
|
-
logger.info("[Mixer Debug] Run history exported. Copy the returned string or use:");
|
|
4190
|
-
logger.info(" copy(window.skuilder.mixer.export())");
|
|
4191
|
-
return json;
|
|
4192
|
-
},
|
|
4193
|
-
/**
|
|
4194
|
-
* Clear run history.
|
|
4195
|
-
*/
|
|
4196
|
-
clear() {
|
|
4197
|
-
runHistory2.length = 0;
|
|
4198
|
-
logger.info("[Mixer Debug] Run history cleared.");
|
|
4199
|
-
},
|
|
2910
|
+
}
|
|
2911
|
+
]
|
|
2912
|
+
};
|
|
2913
|
+
})
|
|
2914
|
+
);
|
|
2915
|
+
return adjusted;
|
|
2916
|
+
}
|
|
4200
2917
|
/**
|
|
4201
|
-
*
|
|
2918
|
+
* Legacy getWeightedCards - now throws as filters should not be used as generators.
|
|
2919
|
+
*
|
|
2920
|
+
* Use transform() via Pipeline instead.
|
|
4202
2921
|
*/
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
Commands:
|
|
4208
|
-
.showLastMix() Show summary of most recent mixer run
|
|
4209
|
-
.showRun(id|index) Show summary of a specific run (by index or ID suffix)
|
|
4210
|
-
.explainSourceBalance() Analyze source balance and selection patterns
|
|
4211
|
-
.compareScores() Compare score distributions across sources
|
|
4212
|
-
.showCard(cardId) Show mixer decisions for a specific card
|
|
4213
|
-
.listRuns() List all captured runs in table format
|
|
4214
|
-
.export() Export run history as JSON for bug reports
|
|
4215
|
-
.clear() Clear run history
|
|
4216
|
-
.runs Access raw run history array
|
|
4217
|
-
.help() Show this help message
|
|
4218
|
-
|
|
4219
|
-
Example:
|
|
4220
|
-
window.skuilder.mixer.showLastMix()
|
|
4221
|
-
window.skuilder.mixer.explainSourceBalance()
|
|
4222
|
-
window.skuilder.mixer.compareScores()
|
|
4223
|
-
`);
|
|
2922
|
+
async getWeightedCards(_limit) {
|
|
2923
|
+
throw new Error(
|
|
2924
|
+
"RelativePriorityNavigator is a filter and should not be used as a generator. Use Pipeline with a generator and this filter via transform()."
|
|
2925
|
+
);
|
|
4224
2926
|
}
|
|
4225
2927
|
};
|
|
4226
|
-
mountMixerDebugger();
|
|
4227
2928
|
}
|
|
4228
2929
|
});
|
|
4229
2930
|
|
|
4230
|
-
// src/
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
}
|
|
4236
|
-
const latest = activeSession.queueSnapshots[activeSession.queueSnapshots.length - 1] || activeSession.initialQueues;
|
|
4237
|
-
console.group("\u{1F4CA} Current Queue State");
|
|
4238
|
-
logger.info(`Review Queue: ${latest.reviewQLength} cards`);
|
|
4239
|
-
if (latest.reviewQNext3 && latest.reviewQNext3.length > 0) {
|
|
4240
|
-
logger.info(` Next: ${latest.reviewQNext3.join(", ")}`);
|
|
4241
|
-
}
|
|
4242
|
-
logger.info(`New Queue: ${latest.newQLength} cards`);
|
|
4243
|
-
if (latest.newQNext3 && latest.newQNext3.length > 0) {
|
|
4244
|
-
logger.info(` Next: ${latest.newQNext3.join(", ")}`);
|
|
2931
|
+
// src/core/navigators/filters/types.ts
|
|
2932
|
+
var types_exports2 = {};
|
|
2933
|
+
var init_types2 = __esm({
|
|
2934
|
+
"src/core/navigators/filters/types.ts"() {
|
|
2935
|
+
"use strict";
|
|
4245
2936
|
}
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
2937
|
+
});
|
|
2938
|
+
|
|
2939
|
+
// src/core/navigators/filters/userGoalStub.ts
|
|
2940
|
+
var userGoalStub_exports = {};
|
|
2941
|
+
__export(userGoalStub_exports, {
|
|
2942
|
+
USER_GOAL_NAVIGATOR_STUB: () => USER_GOAL_NAVIGATOR_STUB
|
|
2943
|
+
});
|
|
2944
|
+
var USER_GOAL_NAVIGATOR_STUB;
|
|
2945
|
+
var init_userGoalStub = __esm({
|
|
2946
|
+
"src/core/navigators/filters/userGoalStub.ts"() {
|
|
2947
|
+
"use strict";
|
|
2948
|
+
USER_GOAL_NAVIGATOR_STUB = true;
|
|
4254
2949
|
}
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
2950
|
+
});
|
|
2951
|
+
|
|
2952
|
+
// import("./filters/**/*") in src/core/navigators/index.ts
|
|
2953
|
+
var globImport_filters;
|
|
2954
|
+
var init_2 = __esm({
|
|
2955
|
+
'import("./filters/**/*") in src/core/navigators/index.ts'() {
|
|
2956
|
+
globImport_filters = __glob({
|
|
2957
|
+
"./filters/WeightedFilter.ts": () => Promise.resolve().then(() => (init_WeightedFilter(), WeightedFilter_exports)),
|
|
2958
|
+
"./filters/eloDistance.ts": () => Promise.resolve().then(() => (init_eloDistance(), eloDistance_exports)),
|
|
2959
|
+
"./filters/hierarchyDefinition.ts": () => Promise.resolve().then(() => (init_hierarchyDefinition(), hierarchyDefinition_exports)),
|
|
2960
|
+
"./filters/index.ts": () => Promise.resolve().then(() => (init_filters(), filters_exports)),
|
|
2961
|
+
"./filters/inferredPreferenceStub.ts": () => Promise.resolve().then(() => (init_inferredPreferenceStub(), inferredPreferenceStub_exports)),
|
|
2962
|
+
"./filters/interferenceMitigator.ts": () => Promise.resolve().then(() => (init_interferenceMitigator(), interferenceMitigator_exports)),
|
|
2963
|
+
"./filters/relativePriority.ts": () => Promise.resolve().then(() => (init_relativePriority(), relativePriority_exports)),
|
|
2964
|
+
"./filters/types.ts": () => Promise.resolve().then(() => (init_types2(), types_exports2)),
|
|
2965
|
+
"./filters/userGoalStub.ts": () => Promise.resolve().then(() => (init_userGoalStub(), userGoalStub_exports)),
|
|
2966
|
+
"./filters/userTagPreference.ts": () => Promise.resolve().then(() => (init_userTagPreference(), userTagPreference_exports))
|
|
2967
|
+
});
|
|
4259
2968
|
}
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
queue: p.queueSource,
|
|
4268
|
-
score: p.score?.toFixed(3) || "-",
|
|
4269
|
-
time: p.timestamp.toLocaleTimeString()
|
|
4270
|
-
}))
|
|
4271
|
-
);
|
|
2969
|
+
});
|
|
2970
|
+
|
|
2971
|
+
// src/core/orchestration/gradient.ts
|
|
2972
|
+
var init_gradient = __esm({
|
|
2973
|
+
"src/core/orchestration/gradient.ts"() {
|
|
2974
|
+
"use strict";
|
|
2975
|
+
init_logger();
|
|
4272
2976
|
}
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
2977
|
+
});
|
|
2978
|
+
|
|
2979
|
+
// src/core/orchestration/learning.ts
|
|
2980
|
+
var init_learning = __esm({
|
|
2981
|
+
"src/core/orchestration/learning.ts"() {
|
|
2982
|
+
"use strict";
|
|
2983
|
+
init_contentNavigationStrategy();
|
|
2984
|
+
init_types_legacy();
|
|
2985
|
+
init_logger();
|
|
4280
2986
|
}
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
if (!courseOrigins.has(name)) {
|
|
4288
|
-
courseOrigins.set(name, { review: 0, new: 0, failed: 0 });
|
|
4289
|
-
}
|
|
4290
|
-
const origins = courseOrigins.get(name);
|
|
4291
|
-
origins[p.origin]++;
|
|
4292
|
-
});
|
|
4293
|
-
logger.info("Course distribution:");
|
|
4294
|
-
console.table(
|
|
4295
|
-
Array.from(courseCounts.entries()).map(([course, count]) => {
|
|
4296
|
-
const origins = courseOrigins.get(course);
|
|
4297
|
-
return {
|
|
4298
|
-
course,
|
|
4299
|
-
total: count,
|
|
4300
|
-
reviews: origins.review,
|
|
4301
|
-
new: origins.new,
|
|
4302
|
-
failed: origins.failed,
|
|
4303
|
-
percentage: (count / session.presentations.length * 100).toFixed(1) + "%"
|
|
4304
|
-
};
|
|
4305
|
-
})
|
|
4306
|
-
);
|
|
4307
|
-
if (session.presentations.length > 0) {
|
|
4308
|
-
logger.info("\nPresentation sequence (first 20):");
|
|
4309
|
-
const sequence = session.presentations.slice(0, 20).map((p, idx) => `${idx + 1}. ${p.courseName || p.courseId.slice(0, 8)} (${p.origin})`).join("\n");
|
|
4310
|
-
logger.info(sequence);
|
|
2987
|
+
});
|
|
2988
|
+
|
|
2989
|
+
// src/core/orchestration/signal.ts
|
|
2990
|
+
var init_signal = __esm({
|
|
2991
|
+
"src/core/orchestration/signal.ts"() {
|
|
2992
|
+
"use strict";
|
|
4311
2993
|
}
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
currentCluster = 1;
|
|
4322
|
-
}
|
|
2994
|
+
});
|
|
2995
|
+
|
|
2996
|
+
// src/core/orchestration/recording.ts
|
|
2997
|
+
var init_recording = __esm({
|
|
2998
|
+
"src/core/orchestration/recording.ts"() {
|
|
2999
|
+
"use strict";
|
|
3000
|
+
init_signal();
|
|
3001
|
+
init_types_legacy();
|
|
3002
|
+
init_logger();
|
|
4323
3003
|
}
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
3004
|
+
});
|
|
3005
|
+
|
|
3006
|
+
// src/core/orchestration/index.ts
|
|
3007
|
+
function fnv1a(str) {
|
|
3008
|
+
let hash = 2166136261;
|
|
3009
|
+
for (let i = 0; i < str.length; i++) {
|
|
3010
|
+
hash ^= str.charCodeAt(i);
|
|
3011
|
+
hash = Math.imul(hash, 16777619);
|
|
4328
3012
|
}
|
|
4329
|
-
|
|
3013
|
+
return hash >>> 0;
|
|
4330
3014
|
}
|
|
4331
|
-
function
|
|
4332
|
-
|
|
4333
|
-
const
|
|
4334
|
-
|
|
4335
|
-
|
|
3015
|
+
function computeDeviation(userId, strategyId, salt) {
|
|
3016
|
+
const input = `${userId}:${strategyId}:${salt}`;
|
|
3017
|
+
const hash = fnv1a(input);
|
|
3018
|
+
const normalized = hash / 4294967296;
|
|
3019
|
+
return normalized * 2 - 1;
|
|
4336
3020
|
}
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
*/
|
|
4366
|
-
showHistory(sessionIndex = 0) {
|
|
4367
|
-
showPresentationHistory(sessionIndex);
|
|
4368
|
-
},
|
|
4369
|
-
/**
|
|
4370
|
-
* Analyze course interleaving pattern.
|
|
4371
|
-
*/
|
|
4372
|
-
showInterleaving(sessionIndex = 0) {
|
|
4373
|
-
showInterleaving(sessionIndex);
|
|
4374
|
-
},
|
|
4375
|
-
/**
|
|
4376
|
-
* List all tracked sessions.
|
|
4377
|
-
*/
|
|
4378
|
-
listSessions() {
|
|
4379
|
-
if (activeSession) {
|
|
4380
|
-
logger.info(`Active session: ${activeSession.sessionId} (${activeSession.presentations.length} cards presented)`);
|
|
4381
|
-
}
|
|
4382
|
-
if (sessionHistory.length === 0) {
|
|
4383
|
-
logger.info("[Session Debug] No completed sessions in history.");
|
|
4384
|
-
return;
|
|
4385
|
-
}
|
|
4386
|
-
console.table(
|
|
4387
|
-
sessionHistory.map((s, idx) => ({
|
|
4388
|
-
index: idx,
|
|
4389
|
-
id: s.sessionId.slice(-8),
|
|
4390
|
-
started: s.startTime.toLocaleTimeString(),
|
|
4391
|
-
ended: s.endTime?.toLocaleTimeString() || "incomplete",
|
|
4392
|
-
cards: s.presentations.length
|
|
4393
|
-
}))
|
|
4394
|
-
);
|
|
4395
|
-
},
|
|
4396
|
-
/**
|
|
4397
|
-
* Export session history as JSON for bug reports.
|
|
4398
|
-
*/
|
|
4399
|
-
export() {
|
|
4400
|
-
const data = {
|
|
4401
|
-
active: activeSession,
|
|
4402
|
-
history: sessionHistory
|
|
4403
|
-
};
|
|
4404
|
-
const json = JSON.stringify(data, null, 2);
|
|
4405
|
-
logger.info("[Session Debug] Session data exported. Copy the returned string or use:");
|
|
4406
|
-
logger.info(" copy(window.skuilder.session.export())");
|
|
4407
|
-
return json;
|
|
4408
|
-
},
|
|
4409
|
-
/**
|
|
4410
|
-
* Clear session history.
|
|
4411
|
-
*/
|
|
4412
|
-
clear() {
|
|
4413
|
-
sessionHistory.length = 0;
|
|
4414
|
-
logger.info("[Session Debug] Session history cleared.");
|
|
4415
|
-
},
|
|
4416
|
-
/**
|
|
4417
|
-
* Show help.
|
|
4418
|
-
*/
|
|
4419
|
-
help() {
|
|
4420
|
-
logger.info(`
|
|
4421
|
-
\u{1F3AF} Session Debug API
|
|
4422
|
-
|
|
4423
|
-
Commands:
|
|
4424
|
-
.showQueue() Show current queue state (active session only)
|
|
4425
|
-
.showHistory(index?) Show presentation history (0=current/last, 1=previous, etc)
|
|
4426
|
-
.showInterleaving(index?) Analyze course interleaving pattern
|
|
4427
|
-
.listSessions() List all tracked sessions
|
|
4428
|
-
.export() Export session data as JSON for bug reports
|
|
4429
|
-
.clear() Clear session history
|
|
4430
|
-
.sessions Access raw session history array
|
|
4431
|
-
.active Access active session (if any)
|
|
4432
|
-
.help() Show this help message
|
|
4433
|
-
|
|
4434
|
-
Example:
|
|
4435
|
-
window.skuilder.session.showHistory()
|
|
4436
|
-
window.skuilder.session.showInterleaving()
|
|
4437
|
-
window.skuilder.session.showQueue()
|
|
4438
|
-
`);
|
|
4439
|
-
}
|
|
3021
|
+
function computeSpread(confidence) {
|
|
3022
|
+
const clampedConfidence = Math.max(0, Math.min(1, confidence));
|
|
3023
|
+
return MAX_SPREAD - clampedConfidence * (MAX_SPREAD - MIN_SPREAD);
|
|
3024
|
+
}
|
|
3025
|
+
function computeEffectiveWeight(learnable, userId, strategyId, salt) {
|
|
3026
|
+
const deviation = computeDeviation(userId, strategyId, salt);
|
|
3027
|
+
const spread = computeSpread(learnable.confidence);
|
|
3028
|
+
const adjustment = deviation * spread * learnable.weight;
|
|
3029
|
+
const effective = learnable.weight + adjustment;
|
|
3030
|
+
return Math.max(MIN_WEIGHT, Math.min(MAX_WEIGHT, effective));
|
|
3031
|
+
}
|
|
3032
|
+
async function createOrchestrationContext(user, course) {
|
|
3033
|
+
let courseConfig;
|
|
3034
|
+
try {
|
|
3035
|
+
courseConfig = await course.getCourseConfig();
|
|
3036
|
+
} catch (e) {
|
|
3037
|
+
logger.error(`[Orchestration] Failed to load course config: ${e}`);
|
|
3038
|
+
courseConfig = {
|
|
3039
|
+
name: "Unknown",
|
|
3040
|
+
description: "",
|
|
3041
|
+
public: false,
|
|
3042
|
+
deleted: false,
|
|
3043
|
+
creator: "",
|
|
3044
|
+
admins: [],
|
|
3045
|
+
moderators: [],
|
|
3046
|
+
dataShapes: [],
|
|
3047
|
+
questionTypes: [],
|
|
3048
|
+
orchestration: { salt: "default" }
|
|
4440
3049
|
};
|
|
4441
|
-
mountSessionDebugger();
|
|
4442
3050
|
}
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
3051
|
+
const userId = user.getUsername();
|
|
3052
|
+
const salt = courseConfig.orchestration?.salt || "default_salt";
|
|
3053
|
+
return {
|
|
3054
|
+
user,
|
|
3055
|
+
course,
|
|
3056
|
+
userId,
|
|
3057
|
+
courseConfig,
|
|
3058
|
+
getEffectiveWeight(strategyId, learnable) {
|
|
3059
|
+
return computeEffectiveWeight(learnable, userId, strategyId, salt);
|
|
3060
|
+
},
|
|
3061
|
+
getDeviation(strategyId) {
|
|
3062
|
+
return computeDeviation(userId, strategyId, salt);
|
|
3063
|
+
}
|
|
3064
|
+
};
|
|
3065
|
+
}
|
|
3066
|
+
var MIN_SPREAD, MAX_SPREAD, MIN_WEIGHT, MAX_WEIGHT;
|
|
3067
|
+
var init_orchestration = __esm({
|
|
3068
|
+
"src/core/orchestration/index.ts"() {
|
|
4448
3069
|
"use strict";
|
|
4449
|
-
init_SrsService();
|
|
4450
|
-
init_EloService();
|
|
4451
|
-
init_ResponseProcessor();
|
|
4452
|
-
init_CardHydrationService();
|
|
4453
|
-
init_ItemQueue();
|
|
4454
|
-
init_couch();
|
|
4455
|
-
init_recording();
|
|
4456
|
-
init_util2();
|
|
4457
|
-
init_navigators();
|
|
4458
|
-
init_SourceMixer();
|
|
4459
|
-
init_MixerDebugger();
|
|
4460
|
-
init_SessionDebugger();
|
|
4461
3070
|
init_logger();
|
|
3071
|
+
init_gradient();
|
|
3072
|
+
init_learning();
|
|
3073
|
+
init_signal();
|
|
3074
|
+
init_recording();
|
|
3075
|
+
MIN_SPREAD = 0.1;
|
|
3076
|
+
MAX_SPREAD = 0.5;
|
|
3077
|
+
MIN_WEIGHT = 0.1;
|
|
3078
|
+
MAX_WEIGHT = 3;
|
|
4462
3079
|
}
|
|
4463
3080
|
});
|
|
4464
3081
|
|
|
@@ -4479,6 +3096,44 @@ function globMatch(value, pattern) {
|
|
|
4479
3096
|
function cardMatchesTagPattern(card, pattern) {
|
|
4480
3097
|
return (card.tags ?? []).some((tag) => globMatch(tag, pattern));
|
|
4481
3098
|
}
|
|
3099
|
+
function mergeHints2(allHints) {
|
|
3100
|
+
const defined = allHints.filter((h) => h !== null && h !== void 0);
|
|
3101
|
+
if (defined.length === 0) return void 0;
|
|
3102
|
+
const merged = {};
|
|
3103
|
+
const boostTags = {};
|
|
3104
|
+
for (const hints of defined) {
|
|
3105
|
+
for (const [pattern, factor] of Object.entries(hints.boostTags ?? {})) {
|
|
3106
|
+
boostTags[pattern] = (boostTags[pattern] ?? 1) * factor;
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
if (Object.keys(boostTags).length > 0) {
|
|
3110
|
+
merged.boostTags = boostTags;
|
|
3111
|
+
}
|
|
3112
|
+
const boostCards = {};
|
|
3113
|
+
for (const hints of defined) {
|
|
3114
|
+
for (const [pattern, factor] of Object.entries(hints.boostCards ?? {})) {
|
|
3115
|
+
boostCards[pattern] = (boostCards[pattern] ?? 1) * factor;
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
if (Object.keys(boostCards).length > 0) {
|
|
3119
|
+
merged.boostCards = boostCards;
|
|
3120
|
+
}
|
|
3121
|
+
const concatUnique = (field) => {
|
|
3122
|
+
const values = defined.flatMap((h) => h[field] ?? []);
|
|
3123
|
+
if (values.length > 0) {
|
|
3124
|
+
merged[field] = [...new Set(values)];
|
|
3125
|
+
}
|
|
3126
|
+
};
|
|
3127
|
+
concatUnique("requireTags");
|
|
3128
|
+
concatUnique("requireCards");
|
|
3129
|
+
concatUnique("excludeTags");
|
|
3130
|
+
concatUnique("excludeCards");
|
|
3131
|
+
const labels = defined.map((h) => h._label).filter(Boolean);
|
|
3132
|
+
if (labels.length > 0) {
|
|
3133
|
+
merged._label = labels.join("; ");
|
|
3134
|
+
}
|
|
3135
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
3136
|
+
}
|
|
4482
3137
|
function logPipelineConfig(generator, filters) {
|
|
4483
3138
|
const filterList = filters.length > 0 ? "\n - " + filters.map((f) => f.name).join("\n - ") : " none";
|
|
4484
3139
|
logger.info(
|
|
@@ -4542,16 +3197,15 @@ function logCardProvenance(cards, maxCards = 3) {
|
|
|
4542
3197
|
}
|
|
4543
3198
|
}
|
|
4544
3199
|
}
|
|
4545
|
-
var
|
|
3200
|
+
var import_common8, VERBOSE_RESULTS, Pipeline;
|
|
4546
3201
|
var init_Pipeline = __esm({
|
|
4547
3202
|
"src/core/navigators/Pipeline.ts"() {
|
|
4548
3203
|
"use strict";
|
|
4549
|
-
|
|
3204
|
+
import_common8 = require("@vue-skuilder/common");
|
|
4550
3205
|
init_navigators();
|
|
4551
3206
|
init_logger();
|
|
4552
3207
|
init_orchestration();
|
|
4553
3208
|
init_PipelineDebugger();
|
|
4554
|
-
init_SessionController();
|
|
4555
3209
|
VERBOSE_RESULTS = true;
|
|
4556
3210
|
Pipeline = class extends ContentNavigator {
|
|
4557
3211
|
generator;
|
|
@@ -4631,9 +3285,12 @@ var init_Pipeline = __esm({
|
|
|
4631
3285
|
logger.debug(
|
|
4632
3286
|
`[Pipeline] Fetching ${fetchLimit} candidates from generator '${this.generator.name}'`
|
|
4633
3287
|
);
|
|
4634
|
-
|
|
3288
|
+
const generatorResult = await this.generator.getWeightedCards(fetchLimit, context);
|
|
3289
|
+
let cards = generatorResult.cards;
|
|
4635
3290
|
const tGenerate = performance.now();
|
|
4636
3291
|
const generatedCount = cards.length;
|
|
3292
|
+
const mergedHints = mergeHints2([this._ephemeralHints, generatorResult.hints]);
|
|
3293
|
+
this._ephemeralHints = mergedHints ?? null;
|
|
4637
3294
|
let generatorSummaries;
|
|
4638
3295
|
if (this.generator.generators) {
|
|
4639
3296
|
const genMap = /* @__PURE__ */ new Map();
|
|
@@ -4719,7 +3376,7 @@ var init_Pipeline = __esm({
|
|
|
4719
3376
|
} catch (e) {
|
|
4720
3377
|
logger.debug(`[Pipeline] Failed to capture debug run: ${e}`);
|
|
4721
3378
|
}
|
|
4722
|
-
return result;
|
|
3379
|
+
return { cards: result };
|
|
4723
3380
|
}
|
|
4724
3381
|
/**
|
|
4725
3382
|
* Batch hydrate tags for all cards.
|
|
@@ -4874,7 +3531,7 @@ var init_Pipeline = __esm({
|
|
|
4874
3531
|
let userElo = 1e3;
|
|
4875
3532
|
try {
|
|
4876
3533
|
const courseReg = await this.user.getCourseRegDoc(this.course.getCourseID());
|
|
4877
|
-
const courseElo = (0,
|
|
3534
|
+
const courseElo = (0, import_common8.toCourseElo)(courseReg.elo);
|
|
4878
3535
|
userElo = courseElo.global.score;
|
|
4879
3536
|
} catch (e) {
|
|
4880
3537
|
logger.debug(`[Pipeline] Could not get user ELO, using default: ${e}`);
|
|
@@ -4935,7 +3592,7 @@ var init_Pipeline = __esm({
|
|
|
4935
3592
|
*/
|
|
4936
3593
|
async getTagEloStatus(tagFilter) {
|
|
4937
3594
|
const courseReg = await this.user.getCourseRegDoc(this.course.getCourseID());
|
|
4938
|
-
const courseElo = (0,
|
|
3595
|
+
const courseElo = (0, import_common8.toCourseElo)(courseReg.elo);
|
|
4939
3596
|
const result = {};
|
|
4940
3597
|
if (!tagFilter) {
|
|
4941
3598
|
for (const [tag, data] of Object.entries(courseElo.tags)) {
|
|
@@ -5533,11 +4190,11 @@ var init_navigators = __esm({
|
|
|
5533
4190
|
});
|
|
5534
4191
|
|
|
5535
4192
|
// src/impl/couch/courseDB.ts
|
|
5536
|
-
var
|
|
4193
|
+
var import_common9;
|
|
5537
4194
|
var init_courseDB = __esm({
|
|
5538
4195
|
"src/impl/couch/courseDB.ts"() {
|
|
5539
4196
|
"use strict";
|
|
5540
|
-
|
|
4197
|
+
import_common9 = require("@vue-skuilder/common");
|
|
5541
4198
|
init_couch();
|
|
5542
4199
|
init_updateQueue();
|
|
5543
4200
|
init_types_legacy();
|
|
@@ -5552,13 +4209,13 @@ var init_courseDB = __esm({
|
|
|
5552
4209
|
});
|
|
5553
4210
|
|
|
5554
4211
|
// src/impl/couch/classroomDB.ts
|
|
5555
|
-
var
|
|
4212
|
+
var import_moment4;
|
|
5556
4213
|
var init_classroomDB2 = __esm({
|
|
5557
4214
|
"src/impl/couch/classroomDB.ts"() {
|
|
5558
4215
|
"use strict";
|
|
5559
4216
|
init_factory();
|
|
5560
4217
|
init_logger();
|
|
5561
|
-
|
|
4218
|
+
import_moment4 = __toESM(require("moment"), 1);
|
|
5562
4219
|
init_pouchdb_setup();
|
|
5563
4220
|
init_couch();
|
|
5564
4221
|
init_courseDB();
|
|
@@ -5600,14 +4257,14 @@ var init_auth = __esm({
|
|
|
5600
4257
|
});
|
|
5601
4258
|
|
|
5602
4259
|
// src/impl/couch/CouchDBSyncStrategy.ts
|
|
5603
|
-
var
|
|
4260
|
+
var import_common10;
|
|
5604
4261
|
var init_CouchDBSyncStrategy = __esm({
|
|
5605
4262
|
"src/impl/couch/CouchDBSyncStrategy.ts"() {
|
|
5606
4263
|
"use strict";
|
|
5607
4264
|
init_factory();
|
|
5608
4265
|
init_types_legacy();
|
|
5609
4266
|
init_logger();
|
|
5610
|
-
|
|
4267
|
+
import_common10 = require("@vue-skuilder/common");
|
|
5611
4268
|
init_common();
|
|
5612
4269
|
init_pouchdb_setup();
|
|
5613
4270
|
init_couch();
|
|
@@ -5635,14 +4292,14 @@ function createPouchDBConfig() {
|
|
|
5635
4292
|
}
|
|
5636
4293
|
return pouchDBincludeCredentialsConfig;
|
|
5637
4294
|
}
|
|
5638
|
-
var import_cross_fetch2,
|
|
4295
|
+
var import_cross_fetch2, import_moment5, import_process, isBrowser, GUEST_LOCAL_DB, localUserDB, pouchDBincludeCredentialsConfig;
|
|
5639
4296
|
var init_couch = __esm({
|
|
5640
4297
|
"src/impl/couch/index.ts"() {
|
|
5641
4298
|
"use strict";
|
|
5642
4299
|
init_factory();
|
|
5643
4300
|
init_types_legacy();
|
|
5644
4301
|
import_cross_fetch2 = __toESM(require("cross-fetch"), 1);
|
|
5645
|
-
|
|
4302
|
+
import_moment5 = __toESM(require("moment"), 1);
|
|
5646
4303
|
init_logger();
|
|
5647
4304
|
init_pouchdb_setup();
|
|
5648
4305
|
import_process = __toESM(require("process"), 1);
|
|
@@ -5842,14 +4499,14 @@ async function dropUserFromClassroom(user, classID) {
|
|
|
5842
4499
|
async function getUserClassrooms(user) {
|
|
5843
4500
|
return getOrCreateClassroomRegistrationsDoc(user);
|
|
5844
4501
|
}
|
|
5845
|
-
var
|
|
4502
|
+
var import_common12, import_moment6, log3, BaseUser, userCoursesDoc, userClassroomsDoc;
|
|
5846
4503
|
var init_BaseUserDB = __esm({
|
|
5847
4504
|
"src/impl/common/BaseUserDB.ts"() {
|
|
5848
4505
|
"use strict";
|
|
5849
4506
|
init_core();
|
|
5850
4507
|
init_util();
|
|
5851
|
-
|
|
5852
|
-
|
|
4508
|
+
import_common12 = require("@vue-skuilder/common");
|
|
4509
|
+
import_moment6 = __toESM(require("moment"), 1);
|
|
5853
4510
|
init_types_legacy();
|
|
5854
4511
|
init_logger();
|
|
5855
4512
|
init_userDBHelpers();
|
|
@@ -5898,7 +4555,7 @@ Currently logged-in as ${this._username}.`
|
|
|
5898
4555
|
);
|
|
5899
4556
|
}
|
|
5900
4557
|
const result = await this.syncStrategy.createAccount(username, password);
|
|
5901
|
-
if (result.status ===
|
|
4558
|
+
if (result.status === import_common12.Status.ok) {
|
|
5902
4559
|
log3(`Account created successfully, updating username to ${username}`);
|
|
5903
4560
|
this._username = username;
|
|
5904
4561
|
try {
|
|
@@ -5940,7 +4597,7 @@ Currently logged-in as ${this._username}.`
|
|
|
5940
4597
|
async resetUserData() {
|
|
5941
4598
|
if (this.syncStrategy.canAuthenticate()) {
|
|
5942
4599
|
return {
|
|
5943
|
-
status:
|
|
4600
|
+
status: import_common12.Status.error,
|
|
5944
4601
|
error: "Reset user data is only available for local-only mode. Use logout instead for remote sync."
|
|
5945
4602
|
};
|
|
5946
4603
|
}
|
|
@@ -5962,11 +4619,11 @@ Currently logged-in as ${this._username}.`
|
|
|
5962
4619
|
await localDB.bulkDocs(docsToDelete);
|
|
5963
4620
|
}
|
|
5964
4621
|
await this.init();
|
|
5965
|
-
return { status:
|
|
4622
|
+
return { status: import_common12.Status.ok };
|
|
5966
4623
|
} catch (error) {
|
|
5967
4624
|
logger.error("Failed to reset user data:", error);
|
|
5968
4625
|
return {
|
|
5969
|
-
status:
|
|
4626
|
+
status: import_common12.Status.error,
|
|
5970
4627
|
error: error instanceof Error ? error.message : "Unknown error during reset"
|
|
5971
4628
|
};
|
|
5972
4629
|
}
|
|
@@ -6113,7 +4770,7 @@ Currently logged-in as ${this._username}.`
|
|
|
6113
4770
|
);
|
|
6114
4771
|
return reviews.rows.filter((r) => {
|
|
6115
4772
|
if (r.id.startsWith(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */])) {
|
|
6116
|
-
const date =
|
|
4773
|
+
const date = import_moment6.default.utc(
|
|
6117
4774
|
r.id.substr(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */].length),
|
|
6118
4775
|
REVIEW_TIME_FORMAT
|
|
6119
4776
|
);
|
|
@@ -6126,11 +4783,11 @@ Currently logged-in as ${this._username}.`
|
|
|
6126
4783
|
}).map((r) => r.doc);
|
|
6127
4784
|
}
|
|
6128
4785
|
async getReviewsForcast(daysCount) {
|
|
6129
|
-
const time =
|
|
4786
|
+
const time = import_moment6.default.utc().add(daysCount, "days");
|
|
6130
4787
|
return this.getReviewstoDate(time);
|
|
6131
4788
|
}
|
|
6132
4789
|
async getPendingReviews(course_id) {
|
|
6133
|
-
const now =
|
|
4790
|
+
const now = import_moment6.default.utc();
|
|
6134
4791
|
return this.getReviewstoDate(now, course_id);
|
|
6135
4792
|
}
|
|
6136
4793
|
async getScheduledReviewCount(course_id) {
|
|
@@ -6417,7 +5074,7 @@ Currently logged-in as ${this._username}.`
|
|
|
6417
5074
|
*/
|
|
6418
5075
|
async putCardRecord(record) {
|
|
6419
5076
|
const cardHistoryID = getCardHistoryID(record.courseID, record.cardID);
|
|
6420
|
-
record.timeStamp =
|
|
5077
|
+
record.timeStamp = import_moment6.default.utc(record.timeStamp).toString();
|
|
6421
5078
|
try {
|
|
6422
5079
|
const cardHistory = await this.update(
|
|
6423
5080
|
cardHistoryID,
|
|
@@ -6433,7 +5090,7 @@ Currently logged-in as ${this._username}.`
|
|
|
6433
5090
|
const ret = {
|
|
6434
5091
|
...record2
|
|
6435
5092
|
};
|
|
6436
|
-
ret.timeStamp =
|
|
5093
|
+
ret.timeStamp = import_moment6.default.utc(record2.timeStamp);
|
|
6437
5094
|
return ret;
|
|
6438
5095
|
});
|
|
6439
5096
|
return cardHistory;
|
|
@@ -6760,24 +5417,24 @@ var init_factory = __esm({
|
|
|
6760
5417
|
});
|
|
6761
5418
|
|
|
6762
5419
|
// src/study/TagFilteredContentSource.ts
|
|
6763
|
-
var
|
|
5420
|
+
var import_common14;
|
|
6764
5421
|
var init_TagFilteredContentSource = __esm({
|
|
6765
5422
|
"src/study/TagFilteredContentSource.ts"() {
|
|
6766
5423
|
"use strict";
|
|
6767
|
-
|
|
5424
|
+
import_common14 = require("@vue-skuilder/common");
|
|
6768
5425
|
init_courseDB();
|
|
6769
5426
|
init_logger();
|
|
6770
5427
|
}
|
|
6771
5428
|
});
|
|
6772
5429
|
|
|
6773
5430
|
// src/core/interfaces/contentSource.ts
|
|
6774
|
-
var
|
|
5431
|
+
var import_common15;
|
|
6775
5432
|
var init_contentSource = __esm({
|
|
6776
5433
|
"src/core/interfaces/contentSource.ts"() {
|
|
6777
5434
|
"use strict";
|
|
6778
5435
|
init_factory();
|
|
6779
5436
|
init_classroomDB2();
|
|
6780
|
-
|
|
5437
|
+
import_common15 = require("@vue-skuilder/common");
|
|
6781
5438
|
init_TagFilteredContentSource();
|
|
6782
5439
|
}
|
|
6783
5440
|
});
|
|
@@ -6841,17 +5498,17 @@ var init_userOutcome = __esm({
|
|
|
6841
5498
|
});
|
|
6842
5499
|
|
|
6843
5500
|
// src/core/bulkImport/cardProcessor.ts
|
|
6844
|
-
var
|
|
5501
|
+
var import_common16;
|
|
6845
5502
|
var init_cardProcessor = __esm({
|
|
6846
5503
|
"src/core/bulkImport/cardProcessor.ts"() {
|
|
6847
5504
|
"use strict";
|
|
6848
|
-
|
|
5505
|
+
import_common16 = require("@vue-skuilder/common");
|
|
6849
5506
|
init_logger();
|
|
6850
5507
|
}
|
|
6851
5508
|
});
|
|
6852
5509
|
|
|
6853
5510
|
// src/core/bulkImport/types.ts
|
|
6854
|
-
var
|
|
5511
|
+
var init_types3 = __esm({
|
|
6855
5512
|
"src/core/bulkImport/types.ts"() {
|
|
6856
5513
|
"use strict";
|
|
6857
5514
|
}
|
|
@@ -6862,7 +5519,7 @@ var init_bulkImport = __esm({
|
|
|
6862
5519
|
"src/core/bulkImport/index.ts"() {
|
|
6863
5520
|
"use strict";
|
|
6864
5521
|
init_cardProcessor();
|
|
6865
|
-
|
|
5522
|
+
init_types3();
|
|
6866
5523
|
}
|
|
6867
5524
|
});
|
|
6868
5525
|
|
|
@@ -7214,7 +5871,7 @@ var init_core = __esm({
|
|
|
7214
5871
|
});
|
|
7215
5872
|
|
|
7216
5873
|
// src/impl/static/StaticDataUnpacker.ts
|
|
7217
|
-
var pathUtils,
|
|
5874
|
+
var pathUtils, nodeFS, StaticDataUnpacker;
|
|
7218
5875
|
var init_StaticDataUnpacker = __esm({
|
|
7219
5876
|
"src/impl/static/StaticDataUnpacker.ts"() {
|
|
7220
5877
|
"use strict";
|
|
@@ -7231,10 +5888,10 @@ var init_StaticDataUnpacker = __esm({
|
|
|
7231
5888
|
return false;
|
|
7232
5889
|
}
|
|
7233
5890
|
};
|
|
7234
|
-
|
|
5891
|
+
nodeFS = null;
|
|
7235
5892
|
try {
|
|
7236
5893
|
if (typeof window === "undefined" && typeof process !== "undefined" && process.versions?.node) {
|
|
7237
|
-
|
|
5894
|
+
nodeFS = eval("require")("fs");
|
|
7238
5895
|
}
|
|
7239
5896
|
} catch {
|
|
7240
5897
|
}
|
|
@@ -7421,8 +6078,8 @@ var init_StaticDataUnpacker = __esm({
|
|
|
7421
6078
|
const chunkPath = `${this.basePath}/${chunk.path}`;
|
|
7422
6079
|
logger.debug(`Loading chunk from ${chunkPath}`);
|
|
7423
6080
|
let documents;
|
|
7424
|
-
if (this.isLocalPath(chunkPath) &&
|
|
7425
|
-
const fileContent = await
|
|
6081
|
+
if (this.isLocalPath(chunkPath) && nodeFS) {
|
|
6082
|
+
const fileContent = await nodeFS.promises.readFile(chunkPath, "utf8");
|
|
7426
6083
|
documents = JSON.parse(fileContent);
|
|
7427
6084
|
} else {
|
|
7428
6085
|
const response = await fetch(chunkPath);
|
|
@@ -7460,8 +6117,8 @@ var init_StaticDataUnpacker = __esm({
|
|
|
7460
6117
|
const indexPath = `${this.basePath}/${indexMeta.path}`;
|
|
7461
6118
|
logger.debug(`Loading index from ${indexPath}`);
|
|
7462
6119
|
let indexData;
|
|
7463
|
-
if (this.isLocalPath(indexPath) &&
|
|
7464
|
-
const fileContent = await
|
|
6120
|
+
if (this.isLocalPath(indexPath) && nodeFS) {
|
|
6121
|
+
const fileContent = await nodeFS.promises.readFile(indexPath, "utf8");
|
|
7465
6122
|
indexData = JSON.parse(fileContent);
|
|
7466
6123
|
} else {
|
|
7467
6124
|
const response = await fetch(indexPath);
|
|
@@ -7536,8 +6193,8 @@ var init_StaticDataUnpacker = __esm({
|
|
|
7536
6193
|
return null;
|
|
7537
6194
|
}
|
|
7538
6195
|
try {
|
|
7539
|
-
if (this.isLocalPath(attachmentPath) &&
|
|
7540
|
-
const buffer = await
|
|
6196
|
+
if (this.isLocalPath(attachmentPath) && nodeFS) {
|
|
6197
|
+
const buffer = await nodeFS.promises.readFile(attachmentPath);
|
|
7541
6198
|
return buffer;
|
|
7542
6199
|
} else {
|
|
7543
6200
|
const response = await fetch(attachmentPath);
|
|
@@ -7630,11 +6287,11 @@ var init_StaticDataUnpacker = __esm({
|
|
|
7630
6287
|
});
|
|
7631
6288
|
|
|
7632
6289
|
// src/impl/static/courseDB.ts
|
|
7633
|
-
var
|
|
6290
|
+
var import_common17, StaticCourseDB;
|
|
7634
6291
|
var init_courseDB3 = __esm({
|
|
7635
6292
|
"src/impl/static/courseDB.ts"() {
|
|
7636
6293
|
"use strict";
|
|
7637
|
-
|
|
6294
|
+
import_common17 = require("@vue-skuilder/common");
|
|
7638
6295
|
init_types_legacy();
|
|
7639
6296
|
init_logger();
|
|
7640
6297
|
init_defaults();
|
|
@@ -7895,7 +6552,7 @@ var init_courseDB3 = __esm({
|
|
|
7895
6552
|
}
|
|
7896
6553
|
async addNote(_codeCourse, _shape, _data, _author, _tags, _uploads, _elo) {
|
|
7897
6554
|
return {
|
|
7898
|
-
status:
|
|
6555
|
+
status: import_common17.Status.error,
|
|
7899
6556
|
message: "Cannot add notes in static mode"
|
|
7900
6557
|
};
|
|
7901
6558
|
}
|