@lessonkit/core 0.9.3 → 1.0.1

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.js CHANGED
@@ -2,6 +2,11 @@
2
2
  var ID_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/;
3
3
  var ID_MAX_LENGTH = 64;
4
4
 
5
+ // src/assertNever.ts
6
+ function assertNever(value, message = "Unexpected value") {
7
+ throw new Error(`${message}: ${String(value)}`);
8
+ }
9
+
5
10
  // src/validateId.ts
6
11
  function validateId(input, path = "id") {
7
12
  if (typeof input !== "string") {
@@ -30,6 +35,22 @@ function validateId(input, path = "id") {
30
35
  }
31
36
  return { ok: true, id };
32
37
  }
38
+ function parseCourseId(input) {
39
+ const result = validateId(input, "courseId");
40
+ return result.ok ? result.id : null;
41
+ }
42
+ function parseLessonId(input) {
43
+ const result = validateId(input, "lessonId");
44
+ return result.ok ? result.id : null;
45
+ }
46
+ function parseCheckId(input) {
47
+ const result = validateId(input, "checkId");
48
+ return result.ok ? result.id : null;
49
+ }
50
+ function parseBlockId(input) {
51
+ const result = validateId(input, "blockId");
52
+ return result.ok ? result.id : null;
53
+ }
33
54
  function assertValidId(input, path = "id") {
34
55
  const result = validateId(input, path);
35
56
  if (!result.ok) {
@@ -154,18 +175,48 @@ function buildTelemetryCatalog() {
154
175
  }
155
176
 
156
177
  // src/trackingClient.ts
178
+ function isDevEnvironment() {
179
+ const g = globalThis;
180
+ return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
181
+ }
182
+ function invokeTrackingSink(sink, event) {
183
+ let result;
184
+ try {
185
+ result = sink(event);
186
+ } catch (err) {
187
+ if (isDevEnvironment()) {
188
+ console.warn(
189
+ "[lessonkit] tracking sink failed:",
190
+ err instanceof Error ? err.message : err
191
+ );
192
+ }
193
+ throw err;
194
+ }
195
+ if (result != null && typeof result.catch === "function") {
196
+ void result.catch((err) => {
197
+ if (isDevEnvironment()) {
198
+ console.warn(
199
+ "[lessonkit] tracking sink failed:",
200
+ err instanceof Error ? err.message : err
201
+ );
202
+ }
203
+ });
204
+ }
205
+ }
157
206
  function createTrackingClient(opts) {
158
207
  const sink = opts?.sink;
159
208
  const batchSink = opts?.batchSink;
160
209
  const batchEnabled = opts?.batch?.enabled ?? Boolean(batchSink);
161
210
  const flushIntervalMs = opts?.batch?.flushIntervalMs ?? 5e3;
162
211
  const maxBatchSize = opts?.batch?.maxBatchSize ?? 25;
212
+ const maxBufferSize = 1e3;
213
+ let warnedBufferCap = false;
163
214
  if (!batchEnabled) {
164
215
  let disposed2 = false;
165
216
  return {
166
217
  track: (event) => {
167
218
  if (disposed2) return;
168
- void sink?.(event);
219
+ if (sink) invokeTrackingSink(sink, event);
169
220
  },
170
221
  dispose: () => {
171
222
  disposed2 = true;
@@ -224,6 +275,15 @@ function createTrackingClient(opts) {
224
275
  return {
225
276
  track: (event) => {
226
277
  if (disposed || disposing) return;
278
+ if (buffer.length >= maxBufferSize) {
279
+ buffer.shift();
280
+ if (!warnedBufferCap && typeof process !== "undefined" && process.env?.NODE_ENV === "development") {
281
+ warnedBufferCap = true;
282
+ console.warn(
283
+ `[lessonkit] telemetry batch buffer capped at ${maxBufferSize} events; oldest events are dropped while the sink is unavailable.`
284
+ );
285
+ }
286
+ }
227
287
  buffer.push(event);
228
288
  if (buffer.length >= maxBatchSize) void flush();
229
289
  },
@@ -255,16 +315,476 @@ function nowIso() {
255
315
  return (/* @__PURE__ */ new Date()).toISOString();
256
316
  }
257
317
 
258
- // src/plugins.ts
259
- function defineLessonkitPlugin(plugin) {
260
- return plugin;
318
+ // src/telemetryBuilder.ts
319
+ var warnedMissingQuizLesson = false;
320
+ function isDevEnvironment2() {
321
+ const g = globalThis;
322
+ return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
323
+ }
324
+ function resetTelemetryBuilderWarningsForTests() {
325
+ warnedMissingQuizLesson = false;
326
+ }
327
+ function resolveLessonId(opts, eventName) {
328
+ const lessonId = opts.lessonId ?? opts.data?.lessonId;
329
+ if (!lessonId) throw new Error(`${eventName} requires lessonId`);
330
+ return lessonId;
331
+ }
332
+ function buildTelemetryEvent(opts) {
333
+ const base = {
334
+ timestamp: opts.timestamp ?? nowIso(),
335
+ courseId: opts.courseId,
336
+ sessionId: opts.sessionId,
337
+ attemptId: opts.attemptId,
338
+ user: opts.user
339
+ };
340
+ switch (opts.name) {
341
+ case "course_started":
342
+ return { name: "course_started", ...base };
343
+ case "course_completed":
344
+ return { name: "course_completed", ...base };
345
+ case "lesson_started": {
346
+ const lessonId = resolveLessonId(opts, "lesson_started");
347
+ return {
348
+ name: "lesson_started",
349
+ ...base,
350
+ lessonId,
351
+ data: { ...opts.data, lessonId }
352
+ };
353
+ }
354
+ case "lesson_completed":
355
+ case "lesson_time_on_task": {
356
+ const lessonId = resolveLessonId(opts, opts.name);
357
+ return {
358
+ name: opts.name,
359
+ ...base,
360
+ lessonId,
361
+ data: { ...opts.data, lessonId }
362
+ };
363
+ }
364
+ case "quiz_answered": {
365
+ const lessonId = opts.lessonId;
366
+ if (!lessonId) throw new Error("quiz_answered requires active lessonId");
367
+ return { name: "quiz_answered", ...base, lessonId, data: opts.data };
368
+ }
369
+ case "quiz_completed": {
370
+ const lessonId = opts.lessonId;
371
+ if (!lessonId) throw new Error("quiz_completed requires active lessonId");
372
+ return { name: "quiz_completed", ...base, lessonId, data: opts.data };
373
+ }
374
+ case "interaction":
375
+ return {
376
+ name: "interaction",
377
+ ...base,
378
+ lessonId: opts.lessonId,
379
+ data: opts.data
380
+ };
381
+ default:
382
+ return assertNever(opts);
383
+ }
384
+ }
385
+ function tryBuildTelemetryEvent(opts) {
386
+ const isQuiz = opts.name === "quiz_answered" || opts.name === "quiz_completed";
387
+ if (isQuiz && !opts.lessonId) {
388
+ if (isDevEnvironment2() && !warnedMissingQuizLesson) {
389
+ warnedMissingQuizLesson = true;
390
+ console.warn(
391
+ `[lessonkit] ${opts.name} skipped: wrap <Quiz> in <Lesson> so an active lessonId is available`
392
+ );
393
+ }
394
+ return null;
395
+ }
396
+ return buildTelemetryEvent(opts);
397
+ }
398
+
399
+ // src/telemetryPipeline.ts
400
+ function isDevEnvironment3() {
401
+ const g = globalThis;
402
+ return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
403
+ }
404
+ function warnSinkFailure(sinkId, err) {
405
+ if (isDevEnvironment3()) {
406
+ console.warn(
407
+ `[lessonkit] telemetry sink "${sinkId}" failed:`,
408
+ err instanceof Error ? err.message : err
409
+ );
410
+ }
411
+ }
412
+ function invokeSink(sink, event, emitCtx) {
413
+ let result;
414
+ try {
415
+ result = sink.emit(event, emitCtx);
416
+ } catch (err) {
417
+ warnSinkFailure(sink.id, err);
418
+ return;
419
+ }
420
+ if (result != null && typeof result.catch === "function") {
421
+ void result.catch((err) => warnSinkFailure(sink.id, err));
422
+ }
423
+ }
424
+ function createTelemetryPipeline(sinks) {
425
+ const list = [...sinks];
426
+ return {
427
+ sinks: list,
428
+ emit(event, ctx) {
429
+ const emitCtx = ctx ?? {
430
+ courseId: event.courseId,
431
+ sessionId: event.sessionId,
432
+ attemptId: event.attemptId
433
+ };
434
+ for (const sink of list) {
435
+ invokeSink(sink, event, emitCtx);
436
+ }
437
+ }
438
+ };
439
+ }
440
+ function createTrackingPipelineSink(id, track) {
441
+ return {
442
+ id,
443
+ emit(event) {
444
+ track(event);
445
+ }
446
+ };
447
+ }
448
+
449
+ // src/ports.ts
450
+ function createDefaultClock() {
451
+ return {
452
+ nowMs: () => Date.now(),
453
+ nowIso: () => (/* @__PURE__ */ new Date()).toISOString()
454
+ };
455
+ }
456
+ function createNoopStorage() {
457
+ return {
458
+ getItem: () => null,
459
+ setItem: () => {
460
+ }
461
+ };
462
+ }
463
+ function createMemoryBackedSessionStorage(session) {
464
+ const memory = /* @__PURE__ */ new Map();
465
+ let warnedPersistFailure = false;
466
+ const warnPersistFailure = () => {
467
+ if (warnedPersistFailure) return;
468
+ warnedPersistFailure = true;
469
+ if (typeof process !== "undefined" && process.env?.NODE_ENV === "development") {
470
+ console.warn(
471
+ "[lessonkit] sessionStorage is unavailable or failed; using in-memory session dedupe for this tab (may reset on full reload)."
472
+ );
473
+ }
474
+ };
475
+ return {
476
+ getItem: (key) => {
477
+ if (memory.has(key)) return memory.get(key);
478
+ try {
479
+ const value = session.getItem(key);
480
+ if (value !== null) memory.set(key, value);
481
+ return value;
482
+ } catch {
483
+ return memory.get(key) ?? null;
484
+ }
485
+ },
486
+ setItem: (key, value) => {
487
+ memory.set(key, value);
488
+ try {
489
+ session.setItem(key, value);
490
+ } catch {
491
+ warnPersistFailure();
492
+ }
493
+ },
494
+ removeItem: (key) => {
495
+ memory.delete(key);
496
+ try {
497
+ session.removeItem(key);
498
+ } catch {
499
+ warnPersistFailure();
500
+ }
501
+ },
502
+ resetForTests: () => {
503
+ memory.clear();
504
+ }
505
+ };
506
+ }
507
+ function resetStoragePortForTests(storage) {
508
+ storage.resetForTests?.();
509
+ }
510
+ function createSessionStoragePort() {
511
+ if (typeof sessionStorage === "undefined") {
512
+ const memory = /* @__PURE__ */ new Map();
513
+ return {
514
+ getItem: (key) => memory.get(key) ?? null,
515
+ setItem: (key, value) => {
516
+ memory.set(key, value);
517
+ },
518
+ removeItem: (key) => {
519
+ memory.delete(key);
520
+ },
521
+ resetForTests: () => {
522
+ memory.clear();
523
+ }
524
+ };
525
+ }
526
+ return createMemoryBackedSessionStorage(sessionStorage);
527
+ }
528
+ function createGlobalTimer() {
529
+ return {
530
+ setInterval: (fn, ms) => globalThis.setInterval(fn, ms),
531
+ clearInterval: (id) => globalThis.clearInterval(id)
532
+ };
533
+ }
534
+
535
+ // src/progress.ts
536
+ function createProgressController() {
537
+ let activeLessonId;
538
+ let completedLessonIds = /* @__PURE__ */ new Set();
539
+ let courseCompleted = false;
540
+ const lessonStartTimes = /* @__PURE__ */ new Map();
541
+ return {
542
+ getState: () => ({
543
+ activeLessonId,
544
+ completedLessonIds: new Set(completedLessonIds),
545
+ courseCompleted
546
+ }),
547
+ setActiveLesson: (lessonId, startedAtMs) => {
548
+ const previousLessonId = activeLessonId;
549
+ activeLessonId = lessonId;
550
+ lessonStartTimes.set(lessonId, startedAtMs);
551
+ return { previousLessonId };
552
+ },
553
+ completeLesson: (lessonId, completedAtMs) => {
554
+ if (completedLessonIds.has(lessonId)) return { didComplete: false };
555
+ completedLessonIds = new Set(completedLessonIds).add(lessonId);
556
+ if (activeLessonId === lessonId) {
557
+ activeLessonId = void 0;
558
+ }
559
+ const startedAt = lessonStartTimes.get(lessonId);
560
+ lessonStartTimes.delete(lessonId);
561
+ const durationMs = typeof startedAt === "number" ? Math.max(0, completedAtMs - startedAt) : void 0;
562
+ return { durationMs, didComplete: true };
563
+ },
564
+ completeCourse: () => {
565
+ if (courseCompleted) return { didComplete: false };
566
+ courseCompleted = true;
567
+ return { didComplete: true };
568
+ }
569
+ };
570
+ }
571
+
572
+ // src/session.ts
573
+ var SESSION_STORAGE_KEY = "lessonkit:sessionId";
574
+ function getTabSessionId(storage) {
575
+ return storage.getItem(SESSION_STORAGE_KEY);
576
+ }
577
+ var COURSE_STARTED_PREFIX = "lessonkit:course_started:";
578
+ var COURSE_STARTED_TRACKING_PREFIX = "lessonkit:course_started_tracking:";
579
+ var COURSE_STARTED_PIPELINE_PREFIX = "lessonkit:course_started_pipeline:";
580
+ function resolveSessionId(storage, provided) {
581
+ if (provided) return provided;
582
+ const existing = storage.getItem(SESSION_STORAGE_KEY);
583
+ if (existing) return existing;
584
+ const id = createSessionId();
585
+ storage.setItem(SESSION_STORAGE_KEY, id);
586
+ return id;
587
+ }
588
+ function courseStartedStorageKey(sessionId, courseId) {
589
+ return `${COURSE_STARTED_PREFIX}${sessionId}:${courseId ?? ""}`;
590
+ }
591
+ function courseStartedTrackingStorageKey(sessionId, courseId) {
592
+ return `${COURSE_STARTED_TRACKING_PREFIX}${sessionId}:${courseId ?? ""}`;
593
+ }
594
+ function courseStartedPipelineStorageKey(sessionId, courseId) {
595
+ return `${COURSE_STARTED_PIPELINE_PREFIX}${sessionId}:${courseId ?? ""}`;
596
+ }
597
+ function hasCourseStarted(storage, sessionId, courseId) {
598
+ if (!courseId) return false;
599
+ return storage.getItem(courseStartedStorageKey(sessionId, courseId)) === "1";
600
+ }
601
+ function markCourseStarted(storage, sessionId, courseId) {
602
+ if (!courseId) return;
603
+ storage.setItem(courseStartedStorageKey(sessionId, courseId), "1");
604
+ }
605
+ function hasCourseStartedEmittedToTracking(storage, sessionId, courseId) {
606
+ if (!courseId) return false;
607
+ return storage.getItem(courseStartedTrackingStorageKey(sessionId, courseId)) === "1";
608
+ }
609
+ function markCourseStartedEmittedToTracking(storage, sessionId, courseId) {
610
+ if (!courseId) return;
611
+ storage.setItem(courseStartedTrackingStorageKey(sessionId, courseId), "1");
612
+ }
613
+ function hasCourseStartedPipelineDelivered(storage, sessionId, courseId) {
614
+ if (!courseId) return false;
615
+ return storage.getItem(courseStartedPipelineStorageKey(sessionId, courseId)) === "1";
616
+ }
617
+ function markCourseStartedPipelineDelivered(storage, sessionId, courseId) {
618
+ if (!courseId) return;
619
+ storage.setItem(courseStartedPipelineStorageKey(sessionId, courseId), "1");
620
+ }
621
+ function migrateCourseStartedMark(storage, fromSessionId, toSessionId, courseId) {
622
+ if (!courseId || fromSessionId === toSessionId) return;
623
+ if (hasCourseStarted(storage, fromSessionId, courseId)) {
624
+ markCourseStarted(storage, toSessionId, courseId);
625
+ storage.removeItem?.(courseStartedStorageKey(fromSessionId, courseId));
626
+ }
627
+ if (hasCourseStartedEmittedToTracking(storage, fromSessionId, courseId)) {
628
+ markCourseStartedEmittedToTracking(storage, toSessionId, courseId);
629
+ storage.removeItem?.(courseStartedTrackingStorageKey(fromSessionId, courseId));
630
+ }
631
+ if (hasCourseStartedPipelineDelivered(storage, fromSessionId, courseId)) {
632
+ markCourseStartedPipelineDelivered(storage, toSessionId, courseId);
633
+ storage.removeItem?.(courseStartedPipelineStorageKey(fromSessionId, courseId));
634
+ }
635
+ }
636
+
637
+ // src/runtime/courseLifecycle.ts
638
+ function tryEmitCourseStarted(ctx, deps, alreadyEmittedToSink) {
639
+ const marked = hasCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId);
640
+ if (alreadyEmittedToSink) {
641
+ return { emitted: true, marked };
642
+ }
643
+ if (marked) {
644
+ return { emitted: false, marked: true };
645
+ }
646
+ const emitted = deps.emitCourseStartedEvent(ctx);
647
+ if (emitted) {
648
+ markCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId);
649
+ }
650
+ return { emitted, marked: emitted };
651
+ }
652
+ function buildCourseStartedTelemetryEvent(ctx) {
653
+ return buildTelemetryEvent({
654
+ name: "course_started",
655
+ courseId: ctx.courseId,
656
+ sessionId: ctx.sessionId,
657
+ attemptId: ctx.attemptId,
658
+ user: ctx.user
659
+ });
660
+ }
661
+ function completeLessonWithTelemetry(opts) {
662
+ const result = opts.progress.completeLesson(opts.lessonId, opts.nowMs);
663
+ if (!result.didComplete) return false;
664
+ opts.emitLessonCompleted(opts.lessonId, result.durationMs);
665
+ return true;
666
+ }
667
+ function completeCourseWithTelemetry(opts) {
668
+ const current = opts.progress.getState();
669
+ if (current.activeLessonId) {
670
+ completeLessonWithTelemetry({
671
+ progress: opts.progress,
672
+ lessonId: current.activeLessonId,
673
+ nowMs: opts.nowMs,
674
+ emitLessonCompleted: opts.emitLessonCompleted
675
+ });
676
+ }
677
+ const result = opts.progress.completeCourse();
678
+ if (!result.didComplete) return false;
679
+ opts.emitCourseCompleted();
680
+ return true;
261
681
  }
682
+
683
+ // src/runtime/createLessonkitRuntime.ts
684
+ function createLessonkitRuntime(config, ports = {}) {
685
+ const storage = ports.storage ?? createSessionStoragePort();
686
+ const clock = ports.clock ?? createDefaultClock();
687
+ const configSnapshot = { ...config };
688
+ let sessionId = resolveSessionId(storage, configSnapshot.session?.sessionId);
689
+ let attemptId = configSnapshot.session?.attemptId;
690
+ let user = configSnapshot.session?.user;
691
+ let courseId = configSnapshot.courseId;
692
+ let progress = createProgressController();
693
+ const getSession = () => ({ sessionId, attemptId, user });
694
+ const syncSessionFromConfig = (next) => {
695
+ sessionId = resolveSessionId(storage, next.session?.sessionId);
696
+ attemptId = next.session?.attemptId;
697
+ user = next.session?.user;
698
+ courseId = next.courseId;
699
+ };
700
+ syncSessionFromConfig(configSnapshot);
701
+ const track = (name, data, emit, lessonId) => {
702
+ const event = tryBuildTelemetryEvent({
703
+ name,
704
+ courseId,
705
+ lessonId: lessonId ?? progress.getState().activeLessonId,
706
+ sessionId,
707
+ attemptId,
708
+ user,
709
+ data
710
+ });
711
+ if (!event) return;
712
+ emit(event);
713
+ };
714
+ const emitLessonCompleted = (lessonId, durationMs, emitFn) => {
715
+ emitFn("lesson_completed", { lessonId, durationMs }, lessonId);
716
+ if (durationMs !== void 0) {
717
+ emitFn("lesson_time_on_task", { lessonId, durationMs }, lessonId);
718
+ }
719
+ };
720
+ return {
721
+ get config() {
722
+ return configSnapshot;
723
+ },
724
+ get progress() {
725
+ return progress;
726
+ },
727
+ getProgressState: () => progress.getState(),
728
+ getSession,
729
+ updateConfig(next) {
730
+ if (next.courseId !== void 0) configSnapshot.courseId = next.courseId;
731
+ if (next.runtimeVersion !== void 0) configSnapshot.runtimeVersion = next.runtimeVersion;
732
+ if (next.plugins !== void 0) configSnapshot.plugins = next.plugins;
733
+ if (next.session !== void 0) {
734
+ configSnapshot.session = { ...configSnapshot.session, ...next.session };
735
+ }
736
+ syncSessionFromConfig(configSnapshot);
737
+ },
738
+ setActiveLesson(lessonId, emitFn) {
739
+ const current = progress.getState();
740
+ if (current.activeLessonId === lessonId) return;
741
+ if (current.completedLessonIds.has(lessonId)) {
742
+ progress.setActiveLesson(lessonId, clock.nowMs());
743
+ return;
744
+ }
745
+ const previous = current.activeLessonId;
746
+ if (previous && previous !== lessonId) {
747
+ const completed = progress.completeLesson(previous, clock.nowMs());
748
+ if (completed.didComplete) {
749
+ emitLessonCompleted(previous, completed.durationMs, emitFn);
750
+ }
751
+ }
752
+ progress.setActiveLesson(lessonId, clock.nowMs());
753
+ emitFn("lesson_started", { lessonId }, lessonId);
754
+ },
755
+ completeLesson(lessonId, emitFn) {
756
+ const result = progress.completeLesson(lessonId, clock.nowMs());
757
+ if (!result.didComplete) return;
758
+ emitLessonCompleted(lessonId, result.durationMs, emitFn);
759
+ },
760
+ completeCourse(emitFn) {
761
+ const current = progress.getState();
762
+ if (current.activeLessonId) {
763
+ const lessonResult = progress.completeLesson(current.activeLessonId, clock.nowMs());
764
+ if (lessonResult.didComplete) {
765
+ emitLessonCompleted(current.activeLessonId, lessonResult.durationMs, emitFn);
766
+ }
767
+ }
768
+ const result = progress.completeCourse();
769
+ if (!result.didComplete) return;
770
+ emitFn("course_completed");
771
+ },
772
+ track,
773
+ resetForCourseChange(nextCourseId) {
774
+ configSnapshot.courseId = nextCourseId;
775
+ courseId = nextCourseId;
776
+ progress = createProgressController();
777
+ }
778
+ };
779
+ }
780
+
781
+ // src/plugins/registry.ts
262
782
  function warnDuplicatePlugin(id) {
263
783
  const g = globalThis;
264
784
  if (typeof g.process !== "undefined" && g.process.env?.NODE_ENV === "production") return;
265
785
  console.warn(`[lessonkit] plugin id "${id}" was registered more than once; using the latest definition`);
266
786
  }
267
- function createPluginHost(plugins = []) {
787
+ function createPluginRegistry(plugins = []) {
268
788
  const registry = /* @__PURE__ */ new Map();
269
789
  for (const plugin of plugins) {
270
790
  if (registry.has(plugin.id)) warnDuplicatePlugin(plugin.id);
@@ -344,20 +864,64 @@ function createPluginHost(plugins = []) {
344
864
  scoreAssessment
345
865
  };
346
866
  }
867
+
868
+ // src/plugins/define.ts
869
+ function defineTelemetryPlugin(plugin) {
870
+ return plugin;
871
+ }
872
+ function defineAssessmentPlugin(plugin) {
873
+ return plugin;
874
+ }
875
+ function defineLifecyclePlugin(plugin) {
876
+ return plugin;
877
+ }
347
878
  export {
348
879
  ID_MAX_LENGTH,
349
880
  ID_PATTERN,
881
+ SESSION_STORAGE_KEY,
350
882
  TELEMETRY_EVENT_CATALOG,
883
+ assertNever,
351
884
  assertValidId,
885
+ buildCourseStartedTelemetryEvent,
352
886
  buildLessonkitUrn,
353
887
  buildTelemetryCatalog,
354
- createPluginHost,
888
+ buildTelemetryEvent,
889
+ completeCourseWithTelemetry,
890
+ completeLessonWithTelemetry,
891
+ createDefaultClock,
892
+ createGlobalTimer,
893
+ createLessonkitRuntime,
894
+ createNoopStorage,
895
+ createPluginRegistry,
896
+ createProgressController,
355
897
  createSessionId,
898
+ createSessionStoragePort,
899
+ createTelemetryPipeline,
356
900
  createTrackingClient,
357
- defineLessonkitPlugin,
901
+ createTrackingPipelineSink,
902
+ defineAssessmentPlugin,
903
+ defineLifecyclePlugin,
904
+ defineTelemetryPlugin,
358
905
  deriveId,
906
+ getTabSessionId,
907
+ hasCourseStarted,
908
+ hasCourseStartedEmittedToTracking,
909
+ hasCourseStartedPipelineDelivered,
910
+ markCourseStarted,
911
+ markCourseStartedEmittedToTracking,
912
+ markCourseStartedPipelineDelivered,
913
+ migrateCourseStartedMark,
359
914
  nowIso,
915
+ parseBlockId,
916
+ parseCheckId,
917
+ parseCourseId,
918
+ parseLessonId,
919
+ resetStoragePortForTests,
920
+ resetTelemetryBuilderWarningsForTests,
921
+ resolveSessionId,
360
922
  slugifyId,
361
923
  telemetryCatalogVersion,
924
+ tryBuildTelemetryEvent,
925
+ tryEmitCourseStarted,
362
926
  validateId
363
927
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lessonkit/core",
3
- "version": "0.9.3",
3
+ "version": "1.0.1",
4
4
  "private": false,
5
5
  "description": "Shared types and telemetry primitives for LessonKit.",
6
6
  "license": "Apache-2.0",