@things-factory/learning 10.1.261 → 10.1.262
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-client/pages/learning-course-list.d.ts +0 -4
- package/dist-client/pages/learning-course-list.js +48 -14
- package/dist-client/pages/learning-course-list.js.map +1 -1
- package/dist-client/pages/learning-enrollment-status.d.ts +3 -0
- package/dist-client/pages/learning-enrollment-status.js +13 -9
- package/dist-client/pages/learning-enrollment-status.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/course/course-query.d.ts +1 -0
- package/dist-server/service/course/course-query.js +13 -0
- package/dist-server/service/course/course-query.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-mutation.d.ts +1 -1
- package/dist-server/service/enrollment/enrollment-mutation.js +6 -6
- package/dist-server/service/enrollment/enrollment-mutation.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-query.d.ts +4 -1
- package/dist-server/service/enrollment/enrollment-query.js +21 -1
- package/dist-server/service/enrollment/enrollment-query.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-service.d.ts +4 -3
- package/dist-server/service/enrollment/enrollment-service.js +28 -15
- package/dist-server/service/enrollment/enrollment-service.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-status.d.ts +11 -0
- package/dist-server/service/enrollment/enrollment-status.js +42 -0
- package/dist-server/service/enrollment/enrollment-status.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-type.d.ts +7 -0
- package/dist-server/service/enrollment/enrollment-type.js +27 -1
- package/dist-server/service/enrollment/enrollment-type.js.map +1 -1
- package/dist-server/service/enrollment/index.d.ts +3 -3
- package/dist-server/service/enrollment/index.js +3 -3
- package/dist-server/service/enrollment/index.js.map +1 -1
- package/dist-server/service/enrollment/progress-rules.d.ts +4 -0
- package/dist-server/service/enrollment/progress-rules.js +7 -1
- package/dist-server/service/enrollment/progress-rules.js.map +1 -1
- package/dist-server/service/index.d.ts +2 -2
- package/dist-server/service/index.js +2 -2
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +13 -13
- package/translations/en.json +13 -3
- package/translations/es.json +13 -3
- package/translations/ja.json +13 -3
- package/translations/ko.json +13 -3
- package/translations/ms.json +13 -3
- package/translations/zh.json +13 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.subscribers = exports.resolvers = exports.entities = exports.LEARNING_REGISTER_KIND = exports.LEARNING_CERTIFICATE_KIND = exports.withdraw = exports.learningAddressOf = exports.enroll = exports.DIRECT_SOURCE_KIND = exports.
|
|
3
|
+
exports.subscribers = exports.resolvers = exports.entities = exports.LEARNING_REGISTER_KIND = exports.LEARNING_CERTIFICATE_KIND = exports.withdraw = exports.learningAddressOf = exports.enroll = exports.DIRECT_SOURCE_KIND = exports.cancelEnrollments = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const enrollment_1 = require("./enrollment");
|
|
6
6
|
const quiz_attempt_1 = require("./quiz-attempt");
|
|
@@ -12,7 +12,7 @@ tslib_1.__exportStar(require("./quiz-attempt"), exports);
|
|
|
12
12
|
tslib_1.__exportStar(require("./unit-progress"), exports);
|
|
13
13
|
tslib_1.__exportStar(require("./learning-outcome"), exports);
|
|
14
14
|
var enrollment_service_1 = require("./enrollment-service");
|
|
15
|
-
Object.defineProperty(exports, "
|
|
15
|
+
Object.defineProperty(exports, "cancelEnrollments", { enumerable: true, get: function () { return enrollment_service_1.cancelEnrollments; } });
|
|
16
16
|
Object.defineProperty(exports, "DIRECT_SOURCE_KIND", { enumerable: true, get: function () { return enrollment_service_1.DIRECT_SOURCE_KIND; } });
|
|
17
17
|
Object.defineProperty(exports, "enroll", { enumerable: true, get: function () { return enrollment_service_1.enroll; } });
|
|
18
18
|
Object.defineProperty(exports, "learningAddressOf", { enumerable: true, get: function () { return enrollment_service_1.learningAddressOf; } });
|
|
@@ -21,6 +21,6 @@ var learning_documents_1 = require("./learning-documents");
|
|
|
21
21
|
Object.defineProperty(exports, "LEARNING_CERTIFICATE_KIND", { enumerable: true, get: function () { return learning_documents_1.LEARNING_CERTIFICATE_KIND; } });
|
|
22
22
|
Object.defineProperty(exports, "LEARNING_REGISTER_KIND", { enumerable: true, get: function () { return learning_documents_1.LEARNING_REGISTER_KIND; } });
|
|
23
23
|
exports.entities = [enrollment_1.Enrollment, unit_progress_1.UnitProgress, quiz_attempt_1.QuizAttempt];
|
|
24
|
-
exports.resolvers = [enrollment_query_1.EnrollmentQuery, enrollment_mutation_1.EnrollmentMutation];
|
|
24
|
+
exports.resolvers = [enrollment_query_1.EnrollmentQuery, enrollment_query_1.CourseEnrollmentFields, enrollment_mutation_1.EnrollmentMutation];
|
|
25
25
|
exports.subscribers = [];
|
|
26
26
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/enrollment/index.ts"],"names":[],"mappings":";;;;AAAA,6CAAyC;AACzC,iDAA4C;AAC5C,mDAA8C;AAC9C,+DAA0D;AAC1D,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/enrollment/index.ts"],"names":[],"mappings":";;;;AAAA,6CAAyC;AACzC,iDAA4C;AAC5C,mDAA8C;AAC9C,+DAA0D;AAC1D,yDAA4E;AAE5E,uDAA4B;AAC5B,yDAA8B;AAC9B,0DAA+B;AAC/B,6DAAkC;AAClC,2DAAiH;AAAxG,uHAAA,iBAAiB,OAAA;AAAE,wHAAA,kBAAkB,OAAA;AAAE,4GAAA,MAAM,OAAA;AAAE,uHAAA,iBAAiB,OAAA;AAAE,8GAAA,QAAQ,OAAA;AAEnF,2DAAwF;AAA/E,+HAAA,yBAAyB,OAAA;AAAE,4HAAA,sBAAsB,OAAA;AAE7C,QAAA,QAAQ,GAAG,CAAC,uBAAU,EAAE,4BAAY,EAAE,0BAAW,CAAC,CAAA;AAClD,QAAA,SAAS,GAAG,CAAC,kCAAe,EAAE,yCAAsB,EAAE,wCAAkB,CAAC,CAAA;AACzE,QAAA,WAAW,GAAG,EAAE,CAAA","sourcesContent":["import { Enrollment } from './enrollment'\nimport { QuizAttempt } from './quiz-attempt'\nimport { UnitProgress } from './unit-progress'\nimport { EnrollmentMutation } from './enrollment-mutation'\nimport { CourseEnrollmentFields, EnrollmentQuery } from './enrollment-query'\n\nexport * from './enrollment'\nexport * from './quiz-attempt'\nexport * from './unit-progress'\nexport * from './learning-outcome'\nexport { cancelEnrollments, DIRECT_SOURCE_KIND, enroll, learningAddressOf, withdraw } from './enrollment-service'\nexport type { LearningSource } from './enrollment-service'\nexport { LEARNING_CERTIFICATE_KIND, LEARNING_REGISTER_KIND } from './learning-documents'\n\nexport const entities = [Enrollment, UnitProgress, QuizAttempt]\nexport const resolvers = [EnrollmentQuery, CourseEnrollmentFields, EnrollmentMutation]\nexport const subscribers = []\n"]}
|
|
@@ -70,6 +70,10 @@ export declare function unitDone(unit: UnitShape, progress: {
|
|
|
70
70
|
watchedSeconds: number;
|
|
71
71
|
pagesRead?: number | null;
|
|
72
72
|
}): boolean;
|
|
73
|
+
/** The seconds a unit takes to be done, by `unitDone`'s own rule: a video its length, a PDF or text its least time, a link none. */
|
|
74
|
+
export declare const unitSeconds: (unit: UnitShape) => number;
|
|
75
|
+
/** The seconds a version takes at least - every unit done, and the version's least time: the longer of the two. */
|
|
76
|
+
export declare const expectedSecondsOf: (units: UnitShape[], minSeconds: number | null | undefined) => number;
|
|
73
77
|
export interface CompletionFacts {
|
|
74
78
|
/** The units that count, and the ones of them done. */
|
|
75
79
|
units: number;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* time a tick adds, where a video may stand, when a unit is done, whether an enrolment is complete, and a quiz's score.
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.overdue = exports.counts = exports.DEFAULT_UNIT_SECONDS = exports.POSITION_SLACK_SECONDS = exports.TICK_SECONDS = void 0;
|
|
7
|
+
exports.overdue = exports.expectedSecondsOf = exports.unitSeconds = exports.counts = exports.DEFAULT_UNIT_SECONDS = exports.POSITION_SLACK_SECONDS = exports.TICK_SECONDS = void 0;
|
|
8
8
|
exports.secondsPerPage = secondsPerPage;
|
|
9
9
|
exports.tickVerdict = tickVerdict;
|
|
10
10
|
exports.unitDone = unitDone;
|
|
@@ -83,6 +83,12 @@ function unitDone(unit, progress) {
|
|
|
83
83
|
return progress.learnedSeconds >= (unit.minSeconds ?? exports.DEFAULT_UNIT_SECONDS);
|
|
84
84
|
return false;
|
|
85
85
|
}
|
|
86
|
+
/** The seconds a unit takes to be done, by `unitDone`'s own rule: a video its length, a PDF or text its least time, a link none. */
|
|
87
|
+
const unitSeconds = (unit) => unit.kind === 'VIDEO' ? (unit.durationSeconds ?? 0) : (0, exports.counts)(unit) ? (unit.minSeconds ?? exports.DEFAULT_UNIT_SECONDS) : 0;
|
|
88
|
+
exports.unitSeconds = unitSeconds;
|
|
89
|
+
/** The seconds a version takes at least - every unit done, and the version's least time: the longer of the two. */
|
|
90
|
+
const expectedSecondsOf = (units, minSeconds) => Math.max(minSeconds ?? 0, units.reduce((sum, unit) => sum + (0, exports.unitSeconds)(unit), 0));
|
|
91
|
+
exports.expectedSecondsOf = expectedSecondsOf;
|
|
86
92
|
/**
|
|
87
93
|
* What still stands between an enrolment and its completion (decision 8) - every unit done, the least time, the quiz
|
|
88
94
|
* passed when there is one, the acknowledgement confirmed when one is asked. Complete when nothing is missing.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"progress-rules.js","sourceRoot":"","sources":["../../../server/service/enrollment/progress-rules.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * What the server decides of learning by itself (ADR-0194 decisions 4, 6, 8) - data in, verdict out, no database: the\n * time a tick adds, where a video may stand, when a unit is done, whether an enrolment is complete, and a quiz's score.\n */\n\n/** The longest a tick is counted for, in seconds - the screen sends one every 15 seconds looked at. */\nexport const TICK_SECONDS = 15\n/** How far ahead of what was watched a position still reads as watching - the video's own step between two updates. */\nexport const POSITION_SLACK_SECONDS = 2\n/** How long a PDF or a text is looked at to be done when its unit says nothing. */\nexport const DEFAULT_UNIT_SECONDS = 60\n\nexport interface ProgressNow {\n learnedSeconds: number\n watchedSeconds: number\n /** The furthest page of a PDF arrived at. */\n pagesSeen?: number | null\n /** The furthest page of a PDF read through - looked at for its seconds. */\n pagesRead?: number | null\n /** The seconds looked at on the furthest page arrived at, so far. */\n pageSeconds?: number | null\n /** The page it was on until this tick. */\n pageAt?: number | null\n lastTickAt?: Date | null\n}\n\nexport interface Tick {\n /** What the screen says it looked at since its last tick - it may lower what is counted, never raise it. */\n seconds: number\n /** Where it stands - a video's second, a PDF's page (from 1); 0 for a text. */\n position: number\n}\n\nexport interface UnitShape {\n kind: string\n durationSeconds?: number | null\n pageCount?: number | null\n minSeconds?: number | null\n}\n\nexport type TickVerdict =\n | { refusal: 'position-ahead'; allowed: number }\n | { refusal: 'page-ahead'; allowed: number }\n | { add: number; watchedSeconds: number; positionSeconds: number; pagesSeen: number; pagesRead: number; pageSeconds: number; pageAt: number }\n\n/** The fewest seconds a PDF page is looked at to count as read - its unit's time shared by its pages, 3 at least. */\nexport function secondsPerPage(unit: UnitShape): number {\n return Math.max(3, Math.ceil((unit.minSeconds ?? DEFAULT_UNIT_SECONDS) / Math.max(1, unit.pageCount ?? 1)))\n}\n\n/**\n * What one tick does (decision 6). The time added is the gap since the last tick, at most 15 seconds and at most what\n * the screen says - a tick with no tick before it starts the clock and adds nothing.\n *\n * - A video's position may not be ahead of what was watched by more than the time that passed: a move ahead is refused,\n * a move back is free.\n * - A PDF is read page by page (2026-11-08, the user). The seconds since the last tick were spent on the page it was on;\n * the furthest page arrived at is read once its own seconds reach `secondsPerPage`. A tick may turn only to a page\n * read or the one after the furthest read - two turns at the same moment do not read the page between them; going\n * back is free.\n */\nexport function tickVerdict(progress: ProgressNow, tick: Tick, now: Date, unit: UnitShape): TickVerdict {\n const gap = progress.lastTickAt ? Math.max(0, (now.getTime() - new Date(progress.lastTickAt).getTime()) / 1000) : 0\n const said = Math.max(0, Number(tick.seconds) || 0)\n const add = progress.lastTickAt ? Math.floor(Math.min(TICK_SECONDS, gap, said)) : 0\n const position = Math.max(0, Math.floor(Number(tick.position) || 0))\n let seen = progress.pagesSeen ?? 0\n let read = progress.pagesRead ?? 0\n let onPage = progress.pageSeconds ?? 0\n const kept = { pagesSeen: seen, pagesRead: read, pageSeconds: onPage }\n if (unit.kind === 'VIDEO') {\n const allowed = progress.watchedSeconds + Math.min(TICK_SECONDS, gap) + POSITION_SLACK_SECONDS\n if (position > allowed) return { refusal: 'position-ahead', allowed: Math.floor(progress.watchedSeconds) }\n return { add, watchedSeconds: Math.max(progress.watchedSeconds, position), positionSeconds: position, ...kept, pageAt: 0 }\n }\n if (unit.kind === 'PDF') {\n /* The time since the last tick goes to the page it was on - it reads the furthest page only when it was there. */\n const was = progress.pageAt ?? 0\n if (was > 0 && was === seen) {\n onPage += add\n if (onPage >= secondsPerPage(unit)) read = Math.max(read, seen)\n }\n const page = Math.max(1, position)\n if (page > read + 1) return { refusal: 'page-ahead', allowed: read }\n if (page > seen) {\n seen = page\n onPage = 0\n }\n return { add, watchedSeconds: progress.watchedSeconds, positionSeconds: 0, pagesSeen: seen, pagesRead: read, pageSeconds: onPage, pageAt: page }\n }\n return { add, watchedSeconds: progress.watchedSeconds, positionSeconds: 0, ...kept, pageAt: 0 }\n}\n\n/** Whether a unit counts toward completion - a link does not: the server cannot know what was watched elsewhere. */\nexport const counts = (unit: UnitShape) => unit.kind !== 'LINK'\n\n/**\n * Whether a unit is done (decision 3): a video watched to its end (its length known) **and** its running time counted\n * by the server - a skip or a faster playback reaches neither; a PDF with every page read through - the last one too (its\n * page count known) - and its time; a text looked at for its time. A link is never done - it does not count.\n */\nexport function unitDone(unit: UnitShape, progress: { learnedSeconds: number; watchedSeconds: number; pagesRead?: number | null }): boolean {\n if (unit.kind === 'VIDEO')\n return !!unit.durationSeconds && progress.watchedSeconds >= unit.durationSeconds - POSITION_SLACK_SECONDS && progress.learnedSeconds >= unit.durationSeconds - TICK_SECONDS\n if (unit.kind === 'PDF') return !!unit.pageCount && (progress.pagesRead ?? 0) >= unit.pageCount && progress.learnedSeconds >= (unit.minSeconds ?? DEFAULT_UNIT_SECONDS)\n if (unit.kind === 'MARKDOWN') return progress.learnedSeconds >= (unit.minSeconds ?? DEFAULT_UNIT_SECONDS)\n return false\n}\n\nexport interface CompletionFacts {\n /** The units that count, and the ones of them done. */\n units: number\n unitsDone: number\n learnedSeconds: number\n minSeconds: number\n /** The version has a quiz - questions and a pass score. */\n quiz: boolean\n passed: boolean\n /** The version asks for an acknowledgement. */\n acknowledgement: boolean\n acknowledged: boolean\n}\n\nexport type Missing = 'units' | 'time' | 'quiz' | 'acknowledgement'\n\n/**\n * What still stands between an enrolment and its completion (decision 8) - every unit done, the least time, the quiz\n * passed when there is one, the acknowledgement confirmed when one is asked. Complete when nothing is missing.\n */\nexport function missingOf(facts: CompletionFacts): Missing[] {\n const missing: Missing[] = []\n if (facts.unitsDone < facts.units) missing.push('units')\n if (facts.learnedSeconds < facts.minSeconds) missing.push('time')\n if (facts.quiz && !facts.passed) missing.push('quiz')\n if (facts.acknowledgement && !facts.acknowledged) missing.push('acknowledgement')\n return missing\n}\n\nexport interface QuestionKey {\n id: string\n kind: string\n choices: { id: string; correct: boolean }[]\n}\n\n/**\n * A quiz's score, out of 100 (decision 4): each question is right when the choices picked are exactly the right ones -\n * one for SINGLE and TRUE_FALSE, every right one and no other for MULTIPLE.\n */\nexport function scoreOf(questions: QuestionKey[], answers: Record<string, string[]>): number {\n if (!questions.length) return 0\n const right = questions.filter(question => {\n const picked = new Set(answers?.[question.id] ?? [])\n const correct = question.choices.filter(one => one.correct).map(one => one.id)\n return picked.size === correct.length && correct.every(id => picked.has(id))\n }).length\n return Math.round((right / questions.length) * 100)\n}\n\n/** The questions an attempt draws - `count` of them (all when empty), in a shuffled order, each with its choices shuffled. */\nexport function drawQuestions<Q extends { id: string; choices: { id: string }[] }>(questions: Q[], count: number | null | undefined, random: () => number = Math.random) {\n const shuffle = <T>(items: T[]): T[] => {\n const out = [...items]\n for (let i = out.length - 1; i > 0; i--) {\n const j = Math.floor(random() * (i + 1))\n ;[out[i], out[j]] = [out[j], out[i]]\n }\n return out\n }\n const picked = shuffle(questions).slice(0, count && count > 0 ? count : questions.length)\n return picked.map(question => ({ questionId: question.id, choiceIds: shuffle(question.choices.map(one => one.id)) }))\n}\n\n/** The day an enrolment reads as overdue: open, and its due day before today. */\nexport const overdue = (enrollment: { status: string; dueOn?: string | null }, today: string) =>\n (enrollment.status === 'ASSIGNED' || enrollment.status === 'IN_PROGRESS') && !!enrollment.dueOn && enrollment.dueOn < today\n"]}
|
|
1
|
+
{"version":3,"file":"progress-rules.js","sourceRoot":"","sources":["../../../server/service/enrollment/progress-rules.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AA2CH,wCAEC;AAaD,kCA8BC;AAUD,4BAMC;AA8BD,8BAOC;AAYD,0BAQC;AAGD,sCAWC;AA7KD,uGAAuG;AAC1F,QAAA,YAAY,GAAG,EAAE,CAAA;AAC9B,uHAAuH;AAC1G,QAAA,sBAAsB,GAAG,CAAC,CAAA;AACvC,mFAAmF;AACtE,QAAA,oBAAoB,GAAG,EAAE,CAAA;AAmCtC,qHAAqH;AACrH,SAAgB,cAAc,CAAC,IAAe;IAC5C,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,IAAI,4BAAoB,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;AAC7G,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,WAAW,CAAC,QAAqB,EAAE,IAAU,EAAE,GAAS,EAAE,IAAe;IACvF,MAAM,GAAG,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACnH,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;IACnD,MAAM,GAAG,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,oBAAY,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACnF,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACpE,IAAI,IAAI,GAAG,QAAQ,CAAC,SAAS,IAAI,CAAC,CAAA;IAClC,IAAI,IAAI,GAAG,QAAQ,CAAC,SAAS,IAAI,CAAC,CAAA;IAClC,IAAI,MAAM,GAAG,QAAQ,CAAC,WAAW,IAAI,CAAC,CAAA;IACtC,MAAM,IAAI,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,CAAA;IACtE,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,QAAQ,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,oBAAY,EAAE,GAAG,CAAC,GAAG,8BAAsB,CAAA;QAC9F,IAAI,QAAQ,GAAG,OAAO;YAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAA;QAC1G,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,cAAc,EAAE,QAAQ,CAAC,EAAE,eAAe,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAA;IAC5H,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QACxB,kHAAkH;QAClH,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAA;QAChC,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,CAAA;YACb,IAAI,MAAM,IAAI,cAAc,CAAC,IAAI,CAAC;gBAAE,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;QACjE,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAA;QAClC,IAAI,IAAI,GAAG,IAAI,GAAG,CAAC;YAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;QACpE,IAAI,IAAI,GAAG,IAAI,EAAE,CAAC;YAChB,IAAI,GAAG,IAAI,CAAA;YACX,MAAM,GAAG,CAAC,CAAA;QACZ,CAAC;QACD,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,QAAQ,CAAC,cAAc,EAAE,eAAe,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;IAClJ,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,QAAQ,CAAC,cAAc,EAAE,eAAe,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAA;AACjG,CAAC;AAED,oHAAoH;AAC7G,MAAM,MAAM,GAAG,CAAC,IAAe,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAA;AAAlD,QAAA,MAAM,UAA4C;AAE/D;;;;GAIG;AACH,SAAgB,QAAQ,CAAC,IAAe,EAAE,QAAuF;IAC/H,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;QACvB,OAAO,CAAC,CAAC,IAAI,CAAC,eAAe,IAAI,QAAQ,CAAC,cAAc,IAAI,IAAI,CAAC,eAAe,GAAG,8BAAsB,IAAI,QAAQ,CAAC,cAAc,IAAI,IAAI,CAAC,eAAe,GAAG,oBAAY,CAAA;IAC7K,IAAI,IAAI,CAAC,IAAI,KAAK,KAAK;QAAE,OAAO,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,QAAQ,CAAC,SAAS,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,IAAI,QAAQ,CAAC,cAAc,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,4BAAoB,CAAC,CAAA;IACvK,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU;QAAE,OAAO,QAAQ,CAAC,cAAc,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,4BAAoB,CAAC,CAAA;IACzG,OAAO,KAAK,CAAA;AACd,CAAC;AAED,oIAAoI;AAC7H,MAAM,WAAW,GAAG,CAAC,IAAe,EAAU,EAAE,CACrD,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAA,cAAM,EAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,IAAI,4BAAoB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AADvG,QAAA,WAAW,eAC4F;AAEpH,mHAAmH;AAC5G,MAAM,iBAAiB,GAAG,CAAC,KAAkB,EAAE,UAAqC,EAAU,EAAE,CACrG,IAAI,CAAC,GAAG,CAAC,UAAU,IAAI,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,IAAA,mBAAW,EAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;AADvE,QAAA,iBAAiB,qBACsD;AAkBpF;;;GAGG;AACH,SAAgB,SAAS,CAAC,KAAsB;IAC9C,MAAM,OAAO,GAAc,EAAE,CAAA;IAC7B,IAAI,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,KAAK;QAAE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACxD,IAAI,KAAK,CAAC,cAAc,GAAG,KAAK,CAAC,UAAU;QAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACjE,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACrD,IAAI,KAAK,CAAC,eAAe,IAAI,CAAC,KAAK,CAAC,YAAY;QAAE,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAA;IACjF,OAAO,OAAO,CAAA;AAChB,CAAC;AAQD;;;GAGG;AACH,SAAgB,OAAO,CAAC,SAAwB,EAAE,OAAiC;IACjF,IAAI,CAAC,SAAS,CAAC,MAAM;QAAE,OAAO,CAAC,CAAA;IAC/B,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE;QACxC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAA;QACpD,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAC9E,OAAO,MAAM,CAAC,IAAI,KAAK,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;IAC9E,CAAC,CAAC,CAAC,MAAM,CAAA;IACT,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAA;AACrD,CAAC;AAED,8HAA8H;AAC9H,SAAgB,aAAa,CAAsD,SAAc,EAAE,KAAgC,EAAE,SAAuB,IAAI,CAAC,MAAM;IACrK,MAAM,OAAO,GAAG,CAAI,KAAU,EAAO,EAAE;QACrC,MAAM,GAAG,GAAG,CAAC,GAAG,KAAK,CAAC,CAAA;QACtB,KAAK,IAAI,CAAC,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YACxC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CACvC;YAAA,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;QACtC,CAAC;QACD,OAAO,GAAG,CAAA;IACZ,CAAC,CAAA;IACD,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IACzF,OAAO,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AACvH,CAAC;AAED,iFAAiF;AAC1E,MAAM,OAAO,GAAG,CAAC,UAAqD,EAAE,KAAa,EAAE,EAAE,CAC9F,CAAC,UAAU,CAAC,MAAM,KAAK,UAAU,IAAI,UAAU,CAAC,MAAM,KAAK,aAAa,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,GAAG,KAAK,CAAA;AADhH,QAAA,OAAO,WACyG","sourcesContent":["/*\n * What the server decides of learning by itself (ADR-0194 decisions 4, 6, 8) - data in, verdict out, no database: the\n * time a tick adds, where a video may stand, when a unit is done, whether an enrolment is complete, and a quiz's score.\n */\n\n/** The longest a tick is counted for, in seconds - the screen sends one every 15 seconds looked at. */\nexport const TICK_SECONDS = 15\n/** How far ahead of what was watched a position still reads as watching - the video's own step between two updates. */\nexport const POSITION_SLACK_SECONDS = 2\n/** How long a PDF or a text is looked at to be done when its unit says nothing. */\nexport const DEFAULT_UNIT_SECONDS = 60\n\nexport interface ProgressNow {\n learnedSeconds: number\n watchedSeconds: number\n /** The furthest page of a PDF arrived at. */\n pagesSeen?: number | null\n /** The furthest page of a PDF read through - looked at for its seconds. */\n pagesRead?: number | null\n /** The seconds looked at on the furthest page arrived at, so far. */\n pageSeconds?: number | null\n /** The page it was on until this tick. */\n pageAt?: number | null\n lastTickAt?: Date | null\n}\n\nexport interface Tick {\n /** What the screen says it looked at since its last tick - it may lower what is counted, never raise it. */\n seconds: number\n /** Where it stands - a video's second, a PDF's page (from 1); 0 for a text. */\n position: number\n}\n\nexport interface UnitShape {\n kind: string\n durationSeconds?: number | null\n pageCount?: number | null\n minSeconds?: number | null\n}\n\nexport type TickVerdict =\n | { refusal: 'position-ahead'; allowed: number }\n | { refusal: 'page-ahead'; allowed: number }\n | { add: number; watchedSeconds: number; positionSeconds: number; pagesSeen: number; pagesRead: number; pageSeconds: number; pageAt: number }\n\n/** The fewest seconds a PDF page is looked at to count as read - its unit's time shared by its pages, 3 at least. */\nexport function secondsPerPage(unit: UnitShape): number {\n return Math.max(3, Math.ceil((unit.minSeconds ?? DEFAULT_UNIT_SECONDS) / Math.max(1, unit.pageCount ?? 1)))\n}\n\n/**\n * What one tick does (decision 6). The time added is the gap since the last tick, at most 15 seconds and at most what\n * the screen says - a tick with no tick before it starts the clock and adds nothing.\n *\n * - A video's position may not be ahead of what was watched by more than the time that passed: a move ahead is refused,\n * a move back is free.\n * - A PDF is read page by page (2026-11-08, the user). The seconds since the last tick were spent on the page it was on;\n * the furthest page arrived at is read once its own seconds reach `secondsPerPage`. A tick may turn only to a page\n * read or the one after the furthest read - two turns at the same moment do not read the page between them; going\n * back is free.\n */\nexport function tickVerdict(progress: ProgressNow, tick: Tick, now: Date, unit: UnitShape): TickVerdict {\n const gap = progress.lastTickAt ? Math.max(0, (now.getTime() - new Date(progress.lastTickAt).getTime()) / 1000) : 0\n const said = Math.max(0, Number(tick.seconds) || 0)\n const add = progress.lastTickAt ? Math.floor(Math.min(TICK_SECONDS, gap, said)) : 0\n const position = Math.max(0, Math.floor(Number(tick.position) || 0))\n let seen = progress.pagesSeen ?? 0\n let read = progress.pagesRead ?? 0\n let onPage = progress.pageSeconds ?? 0\n const kept = { pagesSeen: seen, pagesRead: read, pageSeconds: onPage }\n if (unit.kind === 'VIDEO') {\n const allowed = progress.watchedSeconds + Math.min(TICK_SECONDS, gap) + POSITION_SLACK_SECONDS\n if (position > allowed) return { refusal: 'position-ahead', allowed: Math.floor(progress.watchedSeconds) }\n return { add, watchedSeconds: Math.max(progress.watchedSeconds, position), positionSeconds: position, ...kept, pageAt: 0 }\n }\n if (unit.kind === 'PDF') {\n /* The time since the last tick goes to the page it was on - it reads the furthest page only when it was there. */\n const was = progress.pageAt ?? 0\n if (was > 0 && was === seen) {\n onPage += add\n if (onPage >= secondsPerPage(unit)) read = Math.max(read, seen)\n }\n const page = Math.max(1, position)\n if (page > read + 1) return { refusal: 'page-ahead', allowed: read }\n if (page > seen) {\n seen = page\n onPage = 0\n }\n return { add, watchedSeconds: progress.watchedSeconds, positionSeconds: 0, pagesSeen: seen, pagesRead: read, pageSeconds: onPage, pageAt: page }\n }\n return { add, watchedSeconds: progress.watchedSeconds, positionSeconds: 0, ...kept, pageAt: 0 }\n}\n\n/** Whether a unit counts toward completion - a link does not: the server cannot know what was watched elsewhere. */\nexport const counts = (unit: UnitShape) => unit.kind !== 'LINK'\n\n/**\n * Whether a unit is done (decision 3): a video watched to its end (its length known) **and** its running time counted\n * by the server - a skip or a faster playback reaches neither; a PDF with every page read through - the last one too (its\n * page count known) - and its time; a text looked at for its time. A link is never done - it does not count.\n */\nexport function unitDone(unit: UnitShape, progress: { learnedSeconds: number; watchedSeconds: number; pagesRead?: number | null }): boolean {\n if (unit.kind === 'VIDEO')\n return !!unit.durationSeconds && progress.watchedSeconds >= unit.durationSeconds - POSITION_SLACK_SECONDS && progress.learnedSeconds >= unit.durationSeconds - TICK_SECONDS\n if (unit.kind === 'PDF') return !!unit.pageCount && (progress.pagesRead ?? 0) >= unit.pageCount && progress.learnedSeconds >= (unit.minSeconds ?? DEFAULT_UNIT_SECONDS)\n if (unit.kind === 'MARKDOWN') return progress.learnedSeconds >= (unit.minSeconds ?? DEFAULT_UNIT_SECONDS)\n return false\n}\n\n/** The seconds a unit takes to be done, by `unitDone`'s own rule: a video its length, a PDF or text its least time, a link none. */\nexport const unitSeconds = (unit: UnitShape): number =>\n unit.kind === 'VIDEO' ? (unit.durationSeconds ?? 0) : counts(unit) ? (unit.minSeconds ?? DEFAULT_UNIT_SECONDS) : 0\n\n/** The seconds a version takes at least - every unit done, and the version's least time: the longer of the two. */\nexport const expectedSecondsOf = (units: UnitShape[], minSeconds: number | null | undefined): number =>\n Math.max(minSeconds ?? 0, units.reduce((sum, unit) => sum + unitSeconds(unit), 0))\n\nexport interface CompletionFacts {\n /** The units that count, and the ones of them done. */\n units: number\n unitsDone: number\n learnedSeconds: number\n minSeconds: number\n /** The version has a quiz - questions and a pass score. */\n quiz: boolean\n passed: boolean\n /** The version asks for an acknowledgement. */\n acknowledgement: boolean\n acknowledged: boolean\n}\n\nexport type Missing = 'units' | 'time' | 'quiz' | 'acknowledgement'\n\n/**\n * What still stands between an enrolment and its completion (decision 8) - every unit done, the least time, the quiz\n * passed when there is one, the acknowledgement confirmed when one is asked. Complete when nothing is missing.\n */\nexport function missingOf(facts: CompletionFacts): Missing[] {\n const missing: Missing[] = []\n if (facts.unitsDone < facts.units) missing.push('units')\n if (facts.learnedSeconds < facts.minSeconds) missing.push('time')\n if (facts.quiz && !facts.passed) missing.push('quiz')\n if (facts.acknowledgement && !facts.acknowledged) missing.push('acknowledgement')\n return missing\n}\n\nexport interface QuestionKey {\n id: string\n kind: string\n choices: { id: string; correct: boolean }[]\n}\n\n/**\n * A quiz's score, out of 100 (decision 4): each question is right when the choices picked are exactly the right ones -\n * one for SINGLE and TRUE_FALSE, every right one and no other for MULTIPLE.\n */\nexport function scoreOf(questions: QuestionKey[], answers: Record<string, string[]>): number {\n if (!questions.length) return 0\n const right = questions.filter(question => {\n const picked = new Set(answers?.[question.id] ?? [])\n const correct = question.choices.filter(one => one.correct).map(one => one.id)\n return picked.size === correct.length && correct.every(id => picked.has(id))\n }).length\n return Math.round((right / questions.length) * 100)\n}\n\n/** The questions an attempt draws - `count` of them (all when empty), in a shuffled order, each with its choices shuffled. */\nexport function drawQuestions<Q extends { id: string; choices: { id: string }[] }>(questions: Q[], count: number | null | undefined, random: () => number = Math.random) {\n const shuffle = <T>(items: T[]): T[] => {\n const out = [...items]\n for (let i = out.length - 1; i > 0; i--) {\n const j = Math.floor(random() * (i + 1))\n ;[out[i], out[j]] = [out[j], out[i]]\n }\n return out\n }\n const picked = shuffle(questions).slice(0, count && count > 0 ? count : questions.length)\n return picked.map(question => ({ questionId: question.id, choiceIds: shuffle(question.choices.map(one => one.id)) }))\n}\n\n/** The day an enrolment reads as overdue: open, and its due day before today. */\nexport const overdue = (enrollment: { status: string; dueOn?: string | null }, today: string) =>\n (enrollment.status === 'ASSIGNED' || enrollment.status === 'IN_PROGRESS') && !!enrollment.dueOn && enrollment.dueOn < today\n"]}
|
|
@@ -7,8 +7,8 @@ export * from './enrollment/enrollment';
|
|
|
7
7
|
export * from './enrollment/quiz-attempt';
|
|
8
8
|
export * from './enrollment/unit-progress';
|
|
9
9
|
export * from './enrollment/learning-outcome';
|
|
10
|
-
export {
|
|
10
|
+
export { cancelEnrollments, DIRECT_SOURCE_KIND, enroll, learningAddressOf, withdraw, type LearningSource, LEARNING_CERTIFICATE_KIND, LEARNING_REGISTER_KIND } from './enrollment';
|
|
11
11
|
export declare const entities: (typeof import("./course").Course | typeof import("./course").CourseVersion | typeof import("./course").CourseUnit | typeof import("./course").CourseQuestion | typeof import("./enrollment").Enrollment | typeof import("./enrollment").QuizAttempt | typeof import("./enrollment").UnitProgress)[];
|
|
12
12
|
export declare const schema: {
|
|
13
|
-
resolverClasses: (typeof import("./course/material-draft-mutation").MaterialDraftMutation | typeof import("./course/course-mutation").CourseMutation | typeof import("./course/course-query").CourseQuery | typeof import("./course/course-query").CourseVersionFields | typeof import("./course/course-query").CourseUnitFields | typeof import("./course/course-query").CourseQuestionFields | typeof import("./enrollment/enrollment-mutation").EnrollmentMutation | typeof import("./enrollment/enrollment-query").EnrollmentQuery)[];
|
|
13
|
+
resolverClasses: (typeof import("./course/material-draft-mutation").MaterialDraftMutation | typeof import("./course/course-mutation").CourseMutation | typeof import("./course/course-query").CourseQuery | typeof import("./course/course-query").CourseVersionFields | typeof import("./course/course-query").CourseUnitFields | typeof import("./course/course-query").CourseQuestionFields | typeof import("./enrollment/enrollment-mutation").EnrollmentMutation | typeof import("./enrollment/enrollment-query").EnrollmentQuery | typeof import("./enrollment/enrollment-query").CourseEnrollmentFields)[];
|
|
14
14
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.schema = exports.entities = exports.LEARNING_REGISTER_KIND = exports.LEARNING_CERTIFICATE_KIND = exports.withdraw = exports.learningAddressOf = exports.enroll = exports.DIRECT_SOURCE_KIND = exports.
|
|
3
|
+
exports.schema = exports.entities = exports.LEARNING_REGISTER_KIND = exports.LEARNING_CERTIFICATE_KIND = exports.withdraw = exports.learningAddressOf = exports.enroll = exports.DIRECT_SOURCE_KIND = exports.cancelEnrollments = exports.addDraftedQuestions = exports.questionsOf = exports.unitsOf = exports.publishedVersionOf = exports.COURSE_UNIT_REF_TYPE = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
/* EXPORT ENTITY TYPES */
|
|
6
6
|
tslib_1.__exportStar(require("./course/course"), exports);
|
|
@@ -18,7 +18,7 @@ tslib_1.__exportStar(require("./enrollment/quiz-attempt"), exports);
|
|
|
18
18
|
tslib_1.__exportStar(require("./enrollment/unit-progress"), exports);
|
|
19
19
|
tslib_1.__exportStar(require("./enrollment/learning-outcome"), exports);
|
|
20
20
|
var enrollment_1 = require("./enrollment");
|
|
21
|
-
Object.defineProperty(exports, "
|
|
21
|
+
Object.defineProperty(exports, "cancelEnrollments", { enumerable: true, get: function () { return enrollment_1.cancelEnrollments; } });
|
|
22
22
|
Object.defineProperty(exports, "DIRECT_SOURCE_KIND", { enumerable: true, get: function () { return enrollment_1.DIRECT_SOURCE_KIND; } });
|
|
23
23
|
Object.defineProperty(exports, "enroll", { enumerable: true, get: function () { return enrollment_1.enroll; } });
|
|
24
24
|
Object.defineProperty(exports, "learningAddressOf", { enumerable: true, get: function () { return enrollment_1.learningAddressOf; } });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yBAAyB;AACzB,0DAA+B;AAC/B,mEAAwC;AACxC,+DAAoC;AACpC,kEAAuC;AACvC,mCAA8G;AAArG,8GAAA,oBAAoB,OAAA;AAAE,4GAAA,kBAAkB,OAAA;AAAE,iGAAA,OAAO,OAAA;AAAE,qGAAA,WAAW,OAAA;AAAE,6GAAA,mBAAmB,OAAA;AAC5F,kEAAuC;AACvC,oEAAyC;AACzC,qEAA0C;AAC1C,wEAA6C;AAC7C,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yBAAyB;AACzB,0DAA+B;AAC/B,mEAAwC;AACxC,+DAAoC;AACpC,kEAAuC;AACvC,mCAA8G;AAArG,8GAAA,oBAAoB,OAAA;AAAE,4GAAA,kBAAkB,OAAA;AAAE,iGAAA,OAAO,OAAA;AAAE,qGAAA,WAAW,OAAA;AAAE,6GAAA,mBAAmB,OAAA;AAC5F,kEAAuC;AACvC,oEAAyC;AACzC,qEAA0C;AAC1C,wEAA6C;AAC7C,2CAAiL;AAAxK,+GAAA,iBAAiB,OAAA;AAAE,gHAAA,kBAAkB,OAAA;AAAE,oGAAA,MAAM,OAAA;AAAE,+GAAA,iBAAiB,OAAA;AAAE,sGAAA,QAAQ,OAAA;AAAuB,uHAAA,yBAAyB,OAAA;AAAE,oHAAA,sBAAsB,OAAA;AAE3J,mCAAmC;AACnC,qCAAmF;AACnF,6CAA+F;AAElF,QAAA,QAAQ,GAAG;IACtB,cAAc;IACd,GAAG,iBAAc;IACjB,GAAG,qBAAkB;CACtB,CAAA;AAEY,QAAA,MAAM,GAAG;IACpB,eAAe,EAAE;QACf,sBAAsB;QACtB,GAAG,kBAAe;QAClB,GAAG,sBAAmB;KACvB;CACF,CAAA","sourcesContent":["/* EXPORT ENTITY TYPES */\nexport * from './course/course'\nexport * from './course/course-question'\nexport * from './course/course-unit'\nexport * from './course/course-version'\nexport { COURSE_UNIT_REF_TYPE, publishedVersionOf, unitsOf, questionsOf, addDraftedQuestions } from './course'\nexport * from './enrollment/enrollment'\nexport * from './enrollment/quiz-attempt'\nexport * from './enrollment/unit-progress'\nexport * from './enrollment/learning-outcome'\nexport { cancelEnrollments, DIRECT_SOURCE_KIND, enroll, learningAddressOf, withdraw, type LearningSource, LEARNING_CERTIFICATE_KIND, LEARNING_REGISTER_KIND } from './enrollment'\n\n/* IMPORT ENTITIES AND RESOLVERS */\nimport { entities as CourseEntities, resolvers as CourseResolvers } from './course'\nimport { entities as EnrollmentEntities, resolvers as EnrollmentResolvers } from './enrollment'\n\nexport const entities = [\n /* ENTITIES */\n ...CourseEntities,\n ...EnrollmentEntities\n]\n\nexport const schema = {\n resolverClasses: [\n /* RESOLVER CLASSES */\n ...CourseResolvers,\n ...EnrollmentResolvers\n ]\n}\n"]}
|