@lessonkit/core 1.3.1 → 1.4.0

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/index.cjs CHANGED
@@ -22,11 +22,13 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  ACCORDION_FORBIDDEN_CHILD_TYPES: () => ACCORDION_FORBIDDEN_CHILD_TYPES,
24
24
  ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES: () => ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES,
25
+ BLOCKS_14_PAGE_SLIDE: () => BLOCKS_14_PAGE_SLIDE,
25
26
  COMPOUND_MAX_NESTING_DEPTH: () => COMPOUND_MAX_NESTING_DEPTH,
26
27
  COMPOUND_RESUME_SCHEMA_VERSION: () => COMPOUND_RESUME_SCHEMA_VERSION,
27
28
  ID_MAX_LENGTH: () => ID_MAX_LENGTH,
28
29
  ID_PATTERN: () => ID_PATTERN,
29
30
  INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES: () => INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES,
31
+ INTERACTIVE_VIDEO_ALLOWED_CHILD_TYPES: () => INTERACTIVE_VIDEO_ALLOWED_CHILD_TYPES,
30
32
  PAGE_ALLOWED_CHILD_TYPES: () => PAGE_ALLOWED_CHILD_TYPES,
31
33
  SESSION_STORAGE_KEY: () => SESSION_STORAGE_KEY,
32
34
  SLIDE_ALLOWED_CHILD_TYPES: () => SLIDE_ALLOWED_CHILD_TYPES,
@@ -34,6 +36,7 @@ __export(index_exports, {
34
36
  TELEMETRY_EVENT_CATALOG: () => TELEMETRY_EVENT_CATALOG,
35
37
  TELEMETRY_EVENT_CATALOG_V2: () => TELEMETRY_EVENT_CATALOG_V2,
36
38
  TELEMETRY_EVENT_CATALOG_V3: () => TELEMETRY_EVENT_CATALOG_V3,
39
+ TIMED_CUE_ALLOWED_CHILD_TYPES: () => TIMED_CUE_ALLOWED_CHILD_TYPES,
37
40
  assertNever: () => assertNever,
38
41
  assertValidId: () => assertValidId,
39
42
  buildCourseStartedTelemetryEvent: () => buildCourseStartedTelemetryEvent,
@@ -322,10 +325,23 @@ function clearCompoundState(storage, courseId, compoundId) {
322
325
  }
323
326
 
324
327
  // src/compoundAllowlists.ts
328
+ var PAGE_AND_SLIDE_14_BLOCKS = [
329
+ "Video",
330
+ "Summary",
331
+ "ImagePairing",
332
+ "ImageSequencing",
333
+ "MemoryGame",
334
+ "InformationWall",
335
+ "ParallaxSlideshow",
336
+ "Questionnaire",
337
+ "Essay",
338
+ "ArithmeticQuiz"
339
+ ];
325
340
  var PAGE_ALLOWED_CHILD_TYPES = [
326
341
  "Text",
327
342
  "Heading",
328
343
  "Image",
344
+ "Video",
329
345
  "Scenario",
330
346
  "Reflection",
331
347
  "Quiz",
@@ -335,6 +351,15 @@ var PAGE_ALLOWED_CHILD_TYPES = [
335
351
  "DragAndDrop",
336
352
  "DragTheWords",
337
353
  "MarkTheWords",
354
+ "Summary",
355
+ "ImagePairing",
356
+ "ImageSequencing",
357
+ "MemoryGame",
358
+ "InformationWall",
359
+ "ParallaxSlideshow",
360
+ "Questionnaire",
361
+ "Essay",
362
+ "ArithmeticQuiz",
338
363
  "Accordion",
339
364
  "DialogCards",
340
365
  "Flashcards",
@@ -349,6 +374,7 @@ var SLIDE_ALLOWED_CHILD_TYPES = [
349
374
  "Text",
350
375
  "Heading",
351
376
  "Image",
377
+ "Video",
352
378
  "Scenario",
353
379
  "Reflection",
354
380
  "Quiz",
@@ -358,6 +384,15 @@ var SLIDE_ALLOWED_CHILD_TYPES = [
358
384
  "DragAndDrop",
359
385
  "DragTheWords",
360
386
  "MarkTheWords",
387
+ "Summary",
388
+ "ImagePairing",
389
+ "ImageSequencing",
390
+ "MemoryGame",
391
+ "InformationWall",
392
+ "ParallaxSlideshow",
393
+ "Questionnaire",
394
+ "Essay",
395
+ "ArithmeticQuiz",
361
396
  "Accordion",
362
397
  "DialogCards",
363
398
  "Flashcards",
@@ -367,6 +402,22 @@ var SLIDE_ALLOWED_CHILD_TYPES = [
367
402
  "ImageSlider"
368
403
  ];
369
404
  var SLIDE_DECK_ALLOWED_CHILD_TYPES = ["Slide"];
405
+ var TIMED_CUE_ALLOWED_CHILD_TYPES = [
406
+ "Text",
407
+ "Heading",
408
+ "Image",
409
+ "Quiz",
410
+ "TrueFalse",
411
+ "FillInTheBlanks",
412
+ "Summary",
413
+ "ImagePairing",
414
+ "ImageSequencing",
415
+ "MemoryGame",
416
+ "Questionnaire",
417
+ "Essay",
418
+ "ArithmeticQuiz"
419
+ ];
420
+ var INTERACTIVE_VIDEO_ALLOWED_CHILD_TYPES = ["TimedCue"];
370
421
  var ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES = [
371
422
  "TrueFalse",
372
423
  "FillInTheBlanks",
@@ -376,13 +427,20 @@ var ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES = [
376
427
  "Quiz",
377
428
  "KnowledgeCheck",
378
429
  "FindHotspot",
379
- "FindMultipleHotspots"
430
+ "FindMultipleHotspots",
431
+ "Summary",
432
+ "ImagePairing",
433
+ "ImageSequencing",
434
+ "ArithmeticQuiz",
435
+ "Essay"
380
436
  ];
381
437
  var ALLOWLISTS = {
382
438
  Page: PAGE_ALLOWED_CHILD_TYPES,
383
439
  InteractiveBook: INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES,
384
440
  Slide: SLIDE_ALLOWED_CHILD_TYPES,
385
441
  SlideDeck: SLIDE_DECK_ALLOWED_CHILD_TYPES,
442
+ TimedCue: TIMED_CUE_ALLOWED_CHILD_TYPES,
443
+ InteractiveVideo: INTERACTIVE_VIDEO_ALLOWED_CHILD_TYPES,
386
444
  AssessmentSequence: ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES
387
445
  };
388
446
  var COMPOUND_MAX_NESTING_DEPTH = {
@@ -390,6 +448,8 @@ var COMPOUND_MAX_NESTING_DEPTH = {
390
448
  InteractiveBook: 2,
391
449
  Slide: 1,
392
450
  SlideDeck: 2,
451
+ TimedCue: 1,
452
+ InteractiveVideo: 2,
393
453
  AssessmentSequence: 1
394
454
  };
395
455
  function getAllowedChildTypes(parent) {
@@ -399,6 +459,7 @@ function isChildTypeAllowed(parent, childType) {
399
459
  return ALLOWLISTS[parent].includes(childType);
400
460
  }
401
461
  var ACCORDION_FORBIDDEN_CHILD_TYPES = ["Accordion"];
462
+ var BLOCKS_14_PAGE_SLIDE = PAGE_AND_SLIDE_14_BLOCKS;
402
463
 
403
464
  // src/telemetryCatalog.ts
404
465
  var telemetryCatalogVersion = 1;
@@ -554,6 +615,54 @@ var TELEMETRY_EVENT_CATALOG_V3 = [
554
615
  dataFields: ["blockId", "slideIndex"],
555
616
  xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
556
617
  urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
618
+ },
619
+ {
620
+ name: "video_cue_reached",
621
+ description: "Learner reached a timed cue in an Interactive Video",
622
+ requiredFields: ["courseId", "lessonId", "sessionId", "timestamp"],
623
+ dataFields: ["blockId", "cueIndex", "atSeconds", "cueLabel"],
624
+ xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
625
+ urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
626
+ },
627
+ {
628
+ name: "video_segment_completed",
629
+ description: "Learner completed a timed segment in an Interactive Video",
630
+ requiredFields: ["courseId", "lessonId", "sessionId", "timestamp"],
631
+ dataFields: ["blockId", "segmentIndex", "atSeconds", "segmentLabel"],
632
+ xapiVerb: "http://adlnet.gov/expapi/verbs/completed",
633
+ urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
634
+ },
635
+ {
636
+ name: "memory_card_flipped",
637
+ description: "Learner flipped a memory game card",
638
+ requiredFields: ["courseId", "sessionId", "timestamp"],
639
+ dataFields: ["blockId", "cardIndex", "face"],
640
+ xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
641
+ urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
642
+ },
643
+ {
644
+ name: "information_wall_search",
645
+ description: "Learner searched an information wall",
646
+ requiredFields: ["courseId", "sessionId", "timestamp"],
647
+ dataFields: ["blockId", "query", "resultCount"],
648
+ xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
649
+ urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
650
+ },
651
+ {
652
+ name: "parallax_slide_viewed",
653
+ description: "Learner viewed a slide in a parallax slideshow",
654
+ requiredFields: ["courseId", "sessionId", "timestamp"],
655
+ dataFields: ["blockId", "slideIndex"],
656
+ xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
657
+ urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
658
+ },
659
+ {
660
+ name: "questionnaire_submitted",
661
+ description: "Learner submitted an unscored questionnaire",
662
+ requiredFields: ["courseId", "lessonId", "sessionId", "timestamp"],
663
+ dataFields: ["blockId", "fieldCount"],
664
+ xapiVerb: "http://adlnet.gov/expapi/verbs/completed",
665
+ urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
557
666
  }
558
667
  ];
559
668
  function buildTelemetryCatalogV3() {
@@ -561,6 +670,18 @@ function buildTelemetryCatalogV3() {
561
670
  }
562
671
 
563
672
  // src/internal/sinkInvoke.ts
673
+ async function invokeTrackingSinkWithResult(sink, event) {
674
+ try {
675
+ const result = sink(event);
676
+ if (result != null && typeof result.then === "function") {
677
+ await result;
678
+ }
679
+ return true;
680
+ } catch (err) {
681
+ warnDev("[lessonkit] tracking sink failed:", err);
682
+ return false;
683
+ }
684
+ }
564
685
  function invokeTrackingSink(sink, event) {
565
686
  let result;
566
687
  try {
@@ -607,7 +728,17 @@ function createTrackingClient(opts) {
607
728
  return {
608
729
  track: (event) => {
609
730
  if (disposed2) return;
610
- if (sink) invokeTrackingSink(sink, event);
731
+ if (sink) {
732
+ try {
733
+ invokeTrackingSink(sink, event);
734
+ } catch {
735
+ }
736
+ }
737
+ },
738
+ deliver: async (event) => {
739
+ if (disposed2) return false;
740
+ if (!sink) return true;
741
+ return invokeTrackingSinkWithResult(sink, event);
611
742
  },
612
743
  dispose: () => {
613
744
  disposed2 = true;
@@ -684,21 +815,26 @@ function createTrackingClient(opts) {
684
815
  };
685
816
  intervalId = flushIntervalMs > 0 ? globalThis.setInterval(() => void flush(), flushIntervalMs) : void 0;
686
817
  intervalId?.unref?.();
687
- return {
688
- track: (event) => {
689
- if (disposed || disposing) return;
690
- if (buffer.length >= maxBufferSize) {
691
- opts?.onBufferDrop?.();
692
- if (!warnedBufferCap && isDevEnvironment()) {
693
- warnedBufferCap = true;
694
- console.warn(
695
- `[lessonkit] telemetry batch buffer capped at ${maxBufferSize} events; new events are dropped until the buffer drains.`
696
- );
697
- }
698
- return;
818
+ const track = (event) => {
819
+ if (disposed || disposing) return;
820
+ if (buffer.length >= maxBufferSize) {
821
+ opts?.onBufferDrop?.();
822
+ if (!warnedBufferCap && isDevEnvironment()) {
823
+ warnedBufferCap = true;
824
+ console.warn(
825
+ `[lessonkit] telemetry batch buffer capped at ${maxBufferSize} events; new events are dropped until the buffer drains.`
826
+ );
699
827
  }
700
- buffer.push(event);
701
- if (buffer.length >= maxBatchSize) void flush();
828
+ return;
829
+ }
830
+ buffer.push(event);
831
+ if (buffer.length >= maxBatchSize) void flush();
832
+ };
833
+ return {
834
+ track,
835
+ deliver: async (event) => {
836
+ track(event);
837
+ return flush();
702
838
  },
703
839
  flush,
704
840
  flushOnExit: opts?.exitBatchSink ? () => {
@@ -940,6 +1076,81 @@ var TELEMETRY_EVENT_REGISTRY = {
940
1076
  data: opts.data
941
1077
  };
942
1078
  }
1079
+ },
1080
+ video_cue_reached: {
1081
+ requiresLessonId: true,
1082
+ build: (opts, base) => {
1083
+ if (opts.name !== "video_cue_reached") throw new Error("unexpected event");
1084
+ const lessonId = opts.lessonId;
1085
+ if (!lessonId) throw new Error("video_cue_reached requires active lessonId");
1086
+ return {
1087
+ name: "video_cue_reached",
1088
+ ...base,
1089
+ lessonId,
1090
+ data: opts.data
1091
+ };
1092
+ }
1093
+ },
1094
+ video_segment_completed: {
1095
+ requiresLessonId: true,
1096
+ build: (opts, base) => {
1097
+ if (opts.name !== "video_segment_completed") throw new Error("unexpected event");
1098
+ const lessonId = opts.lessonId;
1099
+ if (!lessonId) throw new Error("video_segment_completed requires active lessonId");
1100
+ return {
1101
+ name: "video_segment_completed",
1102
+ ...base,
1103
+ lessonId,
1104
+ data: opts.data
1105
+ };
1106
+ }
1107
+ },
1108
+ memory_card_flipped: {
1109
+ build: (opts, base) => {
1110
+ if (opts.name !== "memory_card_flipped") throw new Error("unexpected event");
1111
+ return {
1112
+ name: "memory_card_flipped",
1113
+ ...base,
1114
+ lessonId: opts.lessonId,
1115
+ data: opts.data
1116
+ };
1117
+ }
1118
+ },
1119
+ information_wall_search: {
1120
+ build: (opts, base) => {
1121
+ if (opts.name !== "information_wall_search") throw new Error("unexpected event");
1122
+ return {
1123
+ name: "information_wall_search",
1124
+ ...base,
1125
+ lessonId: opts.lessonId,
1126
+ data: opts.data
1127
+ };
1128
+ }
1129
+ },
1130
+ parallax_slide_viewed: {
1131
+ build: (opts, base) => {
1132
+ if (opts.name !== "parallax_slide_viewed") throw new Error("unexpected event");
1133
+ return {
1134
+ name: "parallax_slide_viewed",
1135
+ ...base,
1136
+ lessonId: opts.lessonId,
1137
+ data: opts.data
1138
+ };
1139
+ }
1140
+ },
1141
+ questionnaire_submitted: {
1142
+ requiresLessonId: true,
1143
+ build: (opts, base) => {
1144
+ if (opts.name !== "questionnaire_submitted") throw new Error("unexpected event");
1145
+ const lessonId = opts.lessonId;
1146
+ if (!lessonId) throw new Error("questionnaire_submitted requires active lessonId");
1147
+ return {
1148
+ name: "questionnaire_submitted",
1149
+ ...base,
1150
+ lessonId,
1151
+ data: opts.data
1152
+ };
1153
+ }
943
1154
  }
944
1155
  };
945
1156
  function buildTelemetryEventFromRegistry(opts) {
@@ -1518,11 +1729,13 @@ function createLessonkitRuntime(config, ports = {}) {
1518
1729
  if (next.courseId !== void 0 && next.courseId !== previousCourseId) {
1519
1730
  progress = createProgressController();
1520
1731
  }
1521
- if (next.plugins !== void 0 && next.plugins !== pluginHost) {
1732
+ if (next.plugins !== void 0 && next.plugins !== configSnapshot.plugins) {
1522
1733
  pluginHost?.disposeAll();
1523
1734
  configSnapshot.plugins = next.plugins;
1524
1735
  pluginHost = resolvePluginHost(configSnapshot.plugins);
1525
- pluginHost?.setupAll(getPluginCtx());
1736
+ if (!configSnapshot.deferPluginSetup) {
1737
+ pluginHost?.setupAll(getPluginCtx());
1738
+ }
1526
1739
  } else if (next.session !== void 0 && sessionKeyBefore !== sessionKeyAfter && pluginHost && !configSnapshot.deferPluginSetup) {
1527
1740
  pluginHost.disposeAll();
1528
1741
  pluginHost.setupAll(getPluginCtx());
@@ -1532,17 +1745,17 @@ function createLessonkitRuntime(config, ports = {}) {
1532
1745
  const wrapped = wrapEmitFn(emitFn);
1533
1746
  const current = progress.getState();
1534
1747
  if (current.activeLessonId === lessonId) return;
1535
- if (current.completedLessonIds.has(lessonId)) {
1536
- progress.setActiveLesson(lessonId, clock.nowMs());
1537
- return;
1538
- }
1539
1748
  const previous = current.activeLessonId;
1540
- if (previous && previous !== lessonId) {
1749
+ if (previous && previous !== lessonId && !current.completedLessonIds.has(previous)) {
1541
1750
  const completed = progress.completeLesson(previous, clock.nowMs());
1542
1751
  if (completed.didComplete) {
1543
1752
  emitLessonCompletedEvents(previous, completed.durationMs, wrapped);
1544
1753
  }
1545
1754
  }
1755
+ if (current.completedLessonIds.has(lessonId)) {
1756
+ progress.setActiveLesson(lessonId, clock.nowMs());
1757
+ return;
1758
+ }
1546
1759
  progress.setActiveLesson(lessonId, clock.nowMs());
1547
1760
  wrapped("lesson_started", { lessonId }, lessonId);
1548
1761
  },
@@ -1563,9 +1776,12 @@ function createLessonkitRuntime(config, ports = {}) {
1563
1776
  });
1564
1777
  },
1565
1778
  track,
1566
- scoreAssessment(input, _lessonId) {
1779
+ scoreAssessment(input, lessonId) {
1567
1780
  if (!pluginHost) return null;
1568
- return pluginHost.scoreAssessment(input, getPluginCtx());
1781
+ return pluginHost.scoreAssessment(
1782
+ { ...input, lessonId: input.lessonId ?? lessonId },
1783
+ getPluginCtx()
1784
+ );
1569
1785
  },
1570
1786
  resetForCourseChange(nextCourseId) {
1571
1787
  configSnapshot.courseId = nextCourseId;
@@ -1592,11 +1808,13 @@ function defineLifecyclePlugin(plugin) {
1592
1808
  0 && (module.exports = {
1593
1809
  ACCORDION_FORBIDDEN_CHILD_TYPES,
1594
1810
  ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES,
1811
+ BLOCKS_14_PAGE_SLIDE,
1595
1812
  COMPOUND_MAX_NESTING_DEPTH,
1596
1813
  COMPOUND_RESUME_SCHEMA_VERSION,
1597
1814
  ID_MAX_LENGTH,
1598
1815
  ID_PATTERN,
1599
1816
  INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES,
1817
+ INTERACTIVE_VIDEO_ALLOWED_CHILD_TYPES,
1600
1818
  PAGE_ALLOWED_CHILD_TYPES,
1601
1819
  SESSION_STORAGE_KEY,
1602
1820
  SLIDE_ALLOWED_CHILD_TYPES,
@@ -1604,6 +1822,7 @@ function defineLifecyclePlugin(plugin) {
1604
1822
  TELEMETRY_EVENT_CATALOG,
1605
1823
  TELEMETRY_EVENT_CATALOG_V2,
1606
1824
  TELEMETRY_EVENT_CATALOG_V3,
1825
+ TIMED_CUE_ALLOWED_CHILD_TYPES,
1607
1826
  assertNever,
1608
1827
  assertValidId,
1609
1828
  buildCourseStartedTelemetryEvent,