@lessonkit/core 1.3.0 → 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/chunk-PEWFPVQ6.js +628 -0
- package/dist/index.cjs +285 -36
- package/dist/index.d.cts +16 -481
- package/dist/index.d.ts +16 -481
- package/dist/index.js +216 -535
- package/dist/testing-BhVGckZ5.d.cts +569 -0
- package/dist/testing-BhVGckZ5.d.ts +569 -0
- package/dist/testing.cjs +60 -0
- package/dist/testing.d.cts +1 -0
- package/dist/testing.d.ts +1 -0
- package/dist/testing.js +12 -0
- package/package.json +9 -4
- package/telemetry-catalog.v3.json +170 -14
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
type CourseId = string;
|
|
2
|
+
type LessonId = string;
|
|
3
|
+
type CheckId = string;
|
|
4
|
+
type BlockId = string;
|
|
5
|
+
/** Stable URN string returned by {@link buildLessonkitUrn}. */
|
|
6
|
+
type LessonkitUrn = string;
|
|
7
|
+
type IdentityValidationIssue = {
|
|
8
|
+
path: string;
|
|
9
|
+
message: string;
|
|
10
|
+
};
|
|
11
|
+
type IdentityValidationResult = {
|
|
12
|
+
ok: true;
|
|
13
|
+
id: string;
|
|
14
|
+
} | {
|
|
15
|
+
ok: false;
|
|
16
|
+
issues: IdentityValidationIssue[];
|
|
17
|
+
};
|
|
18
|
+
type IdentityIdPath = "courseId" | "lessonId" | "checkId" | "blockId" | "id";
|
|
19
|
+
/** LessonKit id format: letter first, then alphanumeric, `_`, `-`; length 1–64. */
|
|
20
|
+
declare const ID_PATTERN: RegExp;
|
|
21
|
+
declare const ID_MAX_LENGTH = 64;
|
|
22
|
+
|
|
23
|
+
/** H5P-aligned interaction kinds for assessment telemetry and xAPI. */
|
|
24
|
+
type AssessmentInteractionType = "mcq" | "trueFalse" | "fillInBlanks" | "markTheWords" | "dragTheWords" | "dragAndDrop" | "assessmentSequence" | "findHotspot" | "findMultipleHotspots" | "summary" | "imagePairing" | "imageSequencing" | "essay" | "arithmeticQuiz" | "memoryGame";
|
|
25
|
+
/** Serializable resume blob for a single assessment block. */
|
|
26
|
+
type AssessmentResumeState = Record<string, unknown>;
|
|
27
|
+
/** Behaviour flags aligned with H5P question types. */
|
|
28
|
+
type AssessmentBehaviour = {
|
|
29
|
+
enableRetry?: boolean;
|
|
30
|
+
enableSolutionsButton?: boolean;
|
|
31
|
+
autoCheck?: boolean;
|
|
32
|
+
};
|
|
33
|
+
/** Payload for xAPI mapping from assessment components. */
|
|
34
|
+
type AssessmentXAPIData = {
|
|
35
|
+
checkId: CheckId;
|
|
36
|
+
interactionType: AssessmentInteractionType;
|
|
37
|
+
response?: string | string[] | boolean | Record<string, unknown>;
|
|
38
|
+
correct?: boolean;
|
|
39
|
+
score?: number;
|
|
40
|
+
maxScore?: number;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Imperative handle for scored blocks (H5P question-type contract analogue).
|
|
44
|
+
* Parent containers (`AssessmentSequence`, future compounds) may call these methods.
|
|
45
|
+
*/
|
|
46
|
+
type AssessmentHandle = {
|
|
47
|
+
getScore: () => number;
|
|
48
|
+
getMaxScore: () => number;
|
|
49
|
+
getAnswerGiven: () => boolean;
|
|
50
|
+
resetTask: () => void;
|
|
51
|
+
showSolutions: () => void;
|
|
52
|
+
getXAPIData: () => AssessmentXAPIData;
|
|
53
|
+
getCurrentState?: () => AssessmentResumeState;
|
|
54
|
+
resume?: (state: AssessmentResumeState) => void;
|
|
55
|
+
};
|
|
56
|
+
type AssessmentBaseProps = AssessmentBehaviour & {
|
|
57
|
+
checkId: CheckId;
|
|
58
|
+
passingScore?: number;
|
|
59
|
+
};
|
|
60
|
+
/** MCQ assessment props shared by React components and LMS packaging descriptors. */
|
|
61
|
+
type McqAssessmentProps = AssessmentBaseProps & {
|
|
62
|
+
kind?: "mcq";
|
|
63
|
+
question: string;
|
|
64
|
+
choices: string[];
|
|
65
|
+
answer: string;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
type TelemetryEventName = "course_started" | "course_completed" | "lesson_started" | "lesson_completed" | "lesson_time_on_task" | "quiz_answered" | "quiz_completed" | "assessment_answered" | "assessment_completed" | "interaction" | "book_page_viewed" | "slide_viewed" | "compound_page_viewed" | "hotspot_opened" | "accordion_section_toggled" | "flashcard_flipped" | "image_slider_changed" | "video_cue_reached" | "video_segment_completed" | "memory_card_flipped" | "information_wall_search" | "parallax_slide_viewed" | "questionnaire_submitted";
|
|
69
|
+
type TelemetryUser = {
|
|
70
|
+
id?: string;
|
|
71
|
+
email?: string;
|
|
72
|
+
name?: string;
|
|
73
|
+
[key: string]: unknown;
|
|
74
|
+
};
|
|
75
|
+
type TelemetryEventBase = {
|
|
76
|
+
timestamp: string;
|
|
77
|
+
courseId: CourseId;
|
|
78
|
+
sessionId?: string;
|
|
79
|
+
attemptId?: string;
|
|
80
|
+
user?: TelemetryUser;
|
|
81
|
+
};
|
|
82
|
+
type LessonLifecycleData = {
|
|
83
|
+
lessonId: LessonId;
|
|
84
|
+
durationMs?: number;
|
|
85
|
+
success?: boolean;
|
|
86
|
+
score?: number;
|
|
87
|
+
maxScore?: number;
|
|
88
|
+
};
|
|
89
|
+
type QuizAnsweredData = {
|
|
90
|
+
checkId: CheckId;
|
|
91
|
+
question: string;
|
|
92
|
+
choice: string;
|
|
93
|
+
correct: boolean;
|
|
94
|
+
};
|
|
95
|
+
type QuizCompletedData = {
|
|
96
|
+
checkId: CheckId;
|
|
97
|
+
score?: number;
|
|
98
|
+
maxScore?: number;
|
|
99
|
+
passingScore?: number;
|
|
100
|
+
};
|
|
101
|
+
type AssessmentAnsweredData = {
|
|
102
|
+
checkId: CheckId;
|
|
103
|
+
interactionType: AssessmentInteractionType;
|
|
104
|
+
question?: string;
|
|
105
|
+
response?: string | string[] | boolean | Record<string, unknown>;
|
|
106
|
+
correct?: boolean;
|
|
107
|
+
};
|
|
108
|
+
type AssessmentCompletedData = {
|
|
109
|
+
checkId: CheckId;
|
|
110
|
+
interactionType: AssessmentInteractionType;
|
|
111
|
+
score?: number;
|
|
112
|
+
maxScore?: number;
|
|
113
|
+
passingScore?: number;
|
|
114
|
+
};
|
|
115
|
+
type InteractionData = {
|
|
116
|
+
kind?: string;
|
|
117
|
+
blockId?: BlockId;
|
|
118
|
+
payload?: Record<string, unknown>;
|
|
119
|
+
[key: string]: unknown;
|
|
120
|
+
};
|
|
121
|
+
type BookPageViewedData = {
|
|
122
|
+
blockId: BlockId;
|
|
123
|
+
pageIndex: number;
|
|
124
|
+
pageTitle?: string;
|
|
125
|
+
};
|
|
126
|
+
type SlideViewedData = {
|
|
127
|
+
blockId: BlockId;
|
|
128
|
+
slideIndex: number;
|
|
129
|
+
slideTitle?: string;
|
|
130
|
+
};
|
|
131
|
+
type CompoundPageViewedData = {
|
|
132
|
+
blockId: BlockId;
|
|
133
|
+
pageIndex: number;
|
|
134
|
+
parentType?: string;
|
|
135
|
+
};
|
|
136
|
+
type HotspotOpenedData = {
|
|
137
|
+
blockId: BlockId;
|
|
138
|
+
hotspotId: string;
|
|
139
|
+
};
|
|
140
|
+
type AccordionSectionToggledData = {
|
|
141
|
+
blockId: BlockId;
|
|
142
|
+
sectionId: string;
|
|
143
|
+
expanded: boolean;
|
|
144
|
+
};
|
|
145
|
+
type FlashcardFlippedData = {
|
|
146
|
+
blockId: BlockId;
|
|
147
|
+
cardIndex: number;
|
|
148
|
+
face: "front" | "back";
|
|
149
|
+
};
|
|
150
|
+
type ImageSliderChangedData = {
|
|
151
|
+
blockId: BlockId;
|
|
152
|
+
slideIndex: number;
|
|
153
|
+
};
|
|
154
|
+
type VideoCueReachedData = {
|
|
155
|
+
blockId: BlockId;
|
|
156
|
+
cueIndex: number;
|
|
157
|
+
atSeconds: number;
|
|
158
|
+
cueLabel?: string;
|
|
159
|
+
};
|
|
160
|
+
type VideoSegmentCompletedData = {
|
|
161
|
+
blockId: BlockId;
|
|
162
|
+
segmentIndex: number;
|
|
163
|
+
atSeconds: number;
|
|
164
|
+
segmentLabel?: string;
|
|
165
|
+
};
|
|
166
|
+
type MemoryCardFlippedData = {
|
|
167
|
+
blockId: BlockId;
|
|
168
|
+
cardIndex: number;
|
|
169
|
+
face: "front" | "back";
|
|
170
|
+
};
|
|
171
|
+
type InformationWallSearchData = {
|
|
172
|
+
blockId: BlockId;
|
|
173
|
+
query: string;
|
|
174
|
+
resultCount: number;
|
|
175
|
+
};
|
|
176
|
+
type ParallaxSlideViewedData = {
|
|
177
|
+
blockId: BlockId;
|
|
178
|
+
slideIndex: number;
|
|
179
|
+
};
|
|
180
|
+
type QuestionnaireSubmittedData = {
|
|
181
|
+
blockId: BlockId;
|
|
182
|
+
fieldCount: number;
|
|
183
|
+
};
|
|
184
|
+
type TelemetryEvent = (TelemetryEventBase & {
|
|
185
|
+
name: "course_started";
|
|
186
|
+
lessonId?: LessonId;
|
|
187
|
+
data?: undefined;
|
|
188
|
+
}) | (TelemetryEventBase & {
|
|
189
|
+
name: "course_completed";
|
|
190
|
+
lessonId?: LessonId;
|
|
191
|
+
data?: undefined;
|
|
192
|
+
}) | (TelemetryEventBase & {
|
|
193
|
+
name: "lesson_started";
|
|
194
|
+
lessonId: LessonId;
|
|
195
|
+
data: LessonLifecycleData;
|
|
196
|
+
}) | (TelemetryEventBase & {
|
|
197
|
+
name: "lesson_completed";
|
|
198
|
+
lessonId: LessonId;
|
|
199
|
+
data: LessonLifecycleData;
|
|
200
|
+
}) | (TelemetryEventBase & {
|
|
201
|
+
name: "lesson_time_on_task";
|
|
202
|
+
lessonId: LessonId;
|
|
203
|
+
data: LessonLifecycleData;
|
|
204
|
+
}) | (TelemetryEventBase & {
|
|
205
|
+
name: "quiz_answered";
|
|
206
|
+
lessonId: LessonId;
|
|
207
|
+
data: QuizAnsweredData;
|
|
208
|
+
}) | (TelemetryEventBase & {
|
|
209
|
+
name: "quiz_completed";
|
|
210
|
+
lessonId: LessonId;
|
|
211
|
+
data: QuizCompletedData;
|
|
212
|
+
}) | (TelemetryEventBase & {
|
|
213
|
+
name: "assessment_answered";
|
|
214
|
+
lessonId: LessonId;
|
|
215
|
+
data: AssessmentAnsweredData;
|
|
216
|
+
}) | (TelemetryEventBase & {
|
|
217
|
+
name: "assessment_completed";
|
|
218
|
+
lessonId: LessonId;
|
|
219
|
+
data: AssessmentCompletedData;
|
|
220
|
+
}) | (TelemetryEventBase & {
|
|
221
|
+
name: "interaction";
|
|
222
|
+
lessonId?: LessonId;
|
|
223
|
+
data?: InteractionData;
|
|
224
|
+
}) | (TelemetryEventBase & {
|
|
225
|
+
name: "book_page_viewed";
|
|
226
|
+
lessonId: LessonId;
|
|
227
|
+
data: BookPageViewedData;
|
|
228
|
+
}) | (TelemetryEventBase & {
|
|
229
|
+
name: "slide_viewed";
|
|
230
|
+
lessonId: LessonId;
|
|
231
|
+
data: SlideViewedData;
|
|
232
|
+
}) | (TelemetryEventBase & {
|
|
233
|
+
name: "compound_page_viewed";
|
|
234
|
+
lessonId: LessonId;
|
|
235
|
+
data: CompoundPageViewedData;
|
|
236
|
+
}) | (TelemetryEventBase & {
|
|
237
|
+
name: "hotspot_opened";
|
|
238
|
+
lessonId?: LessonId;
|
|
239
|
+
data: HotspotOpenedData;
|
|
240
|
+
}) | (TelemetryEventBase & {
|
|
241
|
+
name: "accordion_section_toggled";
|
|
242
|
+
lessonId?: LessonId;
|
|
243
|
+
data: AccordionSectionToggledData;
|
|
244
|
+
}) | (TelemetryEventBase & {
|
|
245
|
+
name: "flashcard_flipped";
|
|
246
|
+
lessonId?: LessonId;
|
|
247
|
+
data: FlashcardFlippedData;
|
|
248
|
+
}) | (TelemetryEventBase & {
|
|
249
|
+
name: "image_slider_changed";
|
|
250
|
+
lessonId?: LessonId;
|
|
251
|
+
data: ImageSliderChangedData;
|
|
252
|
+
}) | (TelemetryEventBase & {
|
|
253
|
+
name: "video_cue_reached";
|
|
254
|
+
lessonId: LessonId;
|
|
255
|
+
data: VideoCueReachedData;
|
|
256
|
+
}) | (TelemetryEventBase & {
|
|
257
|
+
name: "video_segment_completed";
|
|
258
|
+
lessonId: LessonId;
|
|
259
|
+
data: VideoSegmentCompletedData;
|
|
260
|
+
}) | (TelemetryEventBase & {
|
|
261
|
+
name: "memory_card_flipped";
|
|
262
|
+
lessonId?: LessonId;
|
|
263
|
+
data: MemoryCardFlippedData;
|
|
264
|
+
}) | (TelemetryEventBase & {
|
|
265
|
+
name: "information_wall_search";
|
|
266
|
+
lessonId?: LessonId;
|
|
267
|
+
data: InformationWallSearchData;
|
|
268
|
+
}) | (TelemetryEventBase & {
|
|
269
|
+
name: "parallax_slide_viewed";
|
|
270
|
+
lessonId?: LessonId;
|
|
271
|
+
data: ParallaxSlideViewedData;
|
|
272
|
+
}) | (TelemetryEventBase & {
|
|
273
|
+
name: "questionnaire_submitted";
|
|
274
|
+
lessonId: LessonId;
|
|
275
|
+
data: QuestionnaireSubmittedData;
|
|
276
|
+
});
|
|
277
|
+
/** Payload shape for a telemetry event name. */
|
|
278
|
+
type TelemetryDataFor<N extends TelemetryEventName> = Extract<TelemetryEvent, {
|
|
279
|
+
name: N;
|
|
280
|
+
}> extends {
|
|
281
|
+
data?: infer D;
|
|
282
|
+
} ? D : never;
|
|
283
|
+
type TelemetrySink = (event: TelemetryEvent) => void | Promise<void>;
|
|
284
|
+
type TelemetryBatchSink = (events: TelemetryEvent[]) => void | Promise<void>;
|
|
285
|
+
type TrackingClient = {
|
|
286
|
+
track: (event: TelemetryEvent) => void;
|
|
287
|
+
/** Delivers one event and resolves to true only when the sink accepted it (batch: includes flush). */
|
|
288
|
+
deliver?: (event: TelemetryEvent) => Promise<boolean>;
|
|
289
|
+
/** Resolves to true when all buffered events were delivered; false when a sink failure re-queued events. */
|
|
290
|
+
flush?: () => void | Promise<boolean>;
|
|
291
|
+
/** Best-effort synchronous flush for pagehide (keepalive batch sink when configured). */
|
|
292
|
+
flushOnExit?: () => void;
|
|
293
|
+
dispose?: () => void | Promise<void>;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
type StoragePort = {
|
|
297
|
+
getItem: (key: string) => string | null;
|
|
298
|
+
/** Returns false when the value could not be durably persisted (e.g. sessionStorage quota). */
|
|
299
|
+
setItem: (key: string, value: string) => boolean;
|
|
300
|
+
removeItem?: (key: string) => void;
|
|
301
|
+
/** @internal Test helper to clear in-memory fallback state. */
|
|
302
|
+
resetForTests?: () => void;
|
|
303
|
+
};
|
|
304
|
+
type ClockPort = {
|
|
305
|
+
nowMs: () => number;
|
|
306
|
+
nowIso: () => string;
|
|
307
|
+
};
|
|
308
|
+
type TimerPort = {
|
|
309
|
+
setInterval: (fn: () => void, ms: number) => ReturnType<typeof globalThis.setInterval>;
|
|
310
|
+
clearInterval: (id: ReturnType<typeof globalThis.setInterval>) => void;
|
|
311
|
+
};
|
|
312
|
+
declare function createDefaultClock(): ClockPort;
|
|
313
|
+
declare function createNoopStorage(): StoragePort;
|
|
314
|
+
declare function resetStoragePortForTests(storage: StoragePort): void;
|
|
315
|
+
declare function createSessionStoragePort(): StoragePort;
|
|
316
|
+
declare function createGlobalTimer(): TimerPort;
|
|
317
|
+
|
|
318
|
+
type BuildTelemetryEventContext = {
|
|
319
|
+
courseId: CourseId;
|
|
320
|
+
sessionId?: string;
|
|
321
|
+
attemptId?: string;
|
|
322
|
+
user?: TelemetryUser;
|
|
323
|
+
timestamp?: string;
|
|
324
|
+
};
|
|
325
|
+
type BuildTelemetryEventInput = (BuildTelemetryEventContext & {
|
|
326
|
+
name: "course_started";
|
|
327
|
+
lessonId?: LessonId;
|
|
328
|
+
data?: undefined;
|
|
329
|
+
}) | (BuildTelemetryEventContext & {
|
|
330
|
+
name: "course_completed";
|
|
331
|
+
lessonId?: LessonId;
|
|
332
|
+
data?: undefined;
|
|
333
|
+
}) | (BuildTelemetryEventContext & {
|
|
334
|
+
name: "lesson_started";
|
|
335
|
+
lessonId?: LessonId;
|
|
336
|
+
data?: LessonLifecycleData;
|
|
337
|
+
}) | (BuildTelemetryEventContext & {
|
|
338
|
+
name: "lesson_completed";
|
|
339
|
+
lessonId?: LessonId;
|
|
340
|
+
data?: LessonLifecycleData;
|
|
341
|
+
}) | (BuildTelemetryEventContext & {
|
|
342
|
+
name: "lesson_time_on_task";
|
|
343
|
+
lessonId?: LessonId;
|
|
344
|
+
data?: LessonLifecycleData;
|
|
345
|
+
}) | (BuildTelemetryEventContext & {
|
|
346
|
+
name: "quiz_answered";
|
|
347
|
+
lessonId?: LessonId;
|
|
348
|
+
data: QuizAnsweredData;
|
|
349
|
+
}) | (BuildTelemetryEventContext & {
|
|
350
|
+
name: "quiz_completed";
|
|
351
|
+
lessonId?: LessonId;
|
|
352
|
+
data: QuizCompletedData;
|
|
353
|
+
}) | (BuildTelemetryEventContext & {
|
|
354
|
+
name: "assessment_answered";
|
|
355
|
+
lessonId?: LessonId;
|
|
356
|
+
data: AssessmentAnsweredData;
|
|
357
|
+
}) | (BuildTelemetryEventContext & {
|
|
358
|
+
name: "assessment_completed";
|
|
359
|
+
lessonId?: LessonId;
|
|
360
|
+
data: AssessmentCompletedData;
|
|
361
|
+
}) | (BuildTelemetryEventContext & {
|
|
362
|
+
name: "interaction";
|
|
363
|
+
lessonId?: LessonId;
|
|
364
|
+
data?: InteractionData;
|
|
365
|
+
}) | (BuildTelemetryEventContext & {
|
|
366
|
+
name: "book_page_viewed";
|
|
367
|
+
lessonId?: LessonId;
|
|
368
|
+
data: BookPageViewedData;
|
|
369
|
+
}) | (BuildTelemetryEventContext & {
|
|
370
|
+
name: "slide_viewed";
|
|
371
|
+
lessonId?: LessonId;
|
|
372
|
+
data: SlideViewedData;
|
|
373
|
+
}) | (BuildTelemetryEventContext & {
|
|
374
|
+
name: "compound_page_viewed";
|
|
375
|
+
lessonId?: LessonId;
|
|
376
|
+
data: CompoundPageViewedData;
|
|
377
|
+
}) | (BuildTelemetryEventContext & {
|
|
378
|
+
name: "hotspot_opened";
|
|
379
|
+
lessonId?: LessonId;
|
|
380
|
+
data: HotspotOpenedData;
|
|
381
|
+
}) | (BuildTelemetryEventContext & {
|
|
382
|
+
name: "accordion_section_toggled";
|
|
383
|
+
lessonId?: LessonId;
|
|
384
|
+
data: AccordionSectionToggledData;
|
|
385
|
+
}) | (BuildTelemetryEventContext & {
|
|
386
|
+
name: "flashcard_flipped";
|
|
387
|
+
lessonId?: LessonId;
|
|
388
|
+
data: FlashcardFlippedData;
|
|
389
|
+
}) | (BuildTelemetryEventContext & {
|
|
390
|
+
name: "image_slider_changed";
|
|
391
|
+
lessonId?: LessonId;
|
|
392
|
+
data: ImageSliderChangedData;
|
|
393
|
+
}) | (BuildTelemetryEventContext & {
|
|
394
|
+
name: "video_cue_reached";
|
|
395
|
+
lessonId?: LessonId;
|
|
396
|
+
data: VideoCueReachedData;
|
|
397
|
+
}) | (BuildTelemetryEventContext & {
|
|
398
|
+
name: "video_segment_completed";
|
|
399
|
+
lessonId?: LessonId;
|
|
400
|
+
data: VideoSegmentCompletedData;
|
|
401
|
+
}) | (BuildTelemetryEventContext & {
|
|
402
|
+
name: "memory_card_flipped";
|
|
403
|
+
lessonId?: LessonId;
|
|
404
|
+
data: MemoryCardFlippedData;
|
|
405
|
+
}) | (BuildTelemetryEventContext & {
|
|
406
|
+
name: "information_wall_search";
|
|
407
|
+
lessonId?: LessonId;
|
|
408
|
+
data: InformationWallSearchData;
|
|
409
|
+
}) | (BuildTelemetryEventContext & {
|
|
410
|
+
name: "parallax_slide_viewed";
|
|
411
|
+
lessonId?: LessonId;
|
|
412
|
+
data: ParallaxSlideViewedData;
|
|
413
|
+
}) | (BuildTelemetryEventContext & {
|
|
414
|
+
name: "questionnaire_submitted";
|
|
415
|
+
lessonId?: LessonId;
|
|
416
|
+
data: QuestionnaireSubmittedData;
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
/** Reset dev-warning state (tests only). */
|
|
420
|
+
declare function resetTelemetryBuilderWarningsForTests(): void;
|
|
421
|
+
/**
|
|
422
|
+
* Build a typed telemetry event from a catalog event name and context.
|
|
423
|
+
* Validates lesson-scoped events require `lessonId`.
|
|
424
|
+
*/
|
|
425
|
+
declare function buildTelemetryEvent(opts: BuildTelemetryEventInput): TelemetryEvent;
|
|
426
|
+
/**
|
|
427
|
+
* Like `buildTelemetryEvent`, but returns null (with a dev warning) when quiz events lack an active lesson.
|
|
428
|
+
*/
|
|
429
|
+
declare function tryBuildTelemetryEvent(opts: BuildTelemetryEventInput): TelemetryEvent | null;
|
|
430
|
+
|
|
431
|
+
type ProgressState = {
|
|
432
|
+
activeLessonId?: LessonId;
|
|
433
|
+
completedLessonIds: ReadonlySet<LessonId>;
|
|
434
|
+
courseCompleted: boolean;
|
|
435
|
+
};
|
|
436
|
+
type ProgressController = {
|
|
437
|
+
getState: () => ProgressState;
|
|
438
|
+
setActiveLesson: (lessonId: LessonId, startedAtMs: number) => {
|
|
439
|
+
previousLessonId?: LessonId;
|
|
440
|
+
};
|
|
441
|
+
completeLesson: (lessonId: LessonId, completedAtMs: number) => {
|
|
442
|
+
durationMs?: number;
|
|
443
|
+
didComplete: boolean;
|
|
444
|
+
};
|
|
445
|
+
completeCourse: () => {
|
|
446
|
+
didComplete: boolean;
|
|
447
|
+
};
|
|
448
|
+
};
|
|
449
|
+
declare function createProgressController(): ProgressController;
|
|
450
|
+
|
|
451
|
+
declare const SESSION_STORAGE_KEY = "lessonkit:sessionId";
|
|
452
|
+
declare function getTabSessionId(storage: StoragePort): string | null;
|
|
453
|
+
declare function resolveSessionId(storage: StoragePort, provided?: string): string;
|
|
454
|
+
declare function hasCourseStarted(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
|
|
455
|
+
declare function markCourseStarted(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
|
|
456
|
+
declare function hasCourseStartedEmittedToTracking(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
|
|
457
|
+
declare function markCourseStartedEmittedToTracking(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
|
|
458
|
+
declare function hasCourseStartedPipelineDelivered(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
|
|
459
|
+
declare function markCourseStartedPipelineDelivered(storage: StoragePort, sessionId: string, courseId?: CourseId): boolean;
|
|
460
|
+
/** @internal Reset shared volatile session id between tests. */
|
|
461
|
+
declare function resetSharedVolatileSessionIdForTests(): void;
|
|
462
|
+
declare function migrateCourseStartedMark(storage: StoragePort, fromSessionId: string, toSessionId: string, courseId?: CourseId): void;
|
|
463
|
+
|
|
464
|
+
/** Plugin category — aligns with roadmap extension areas. */
|
|
465
|
+
type LessonkitPluginKind = "analytics" | "lms" | "assessment" | "interaction" | "ai";
|
|
466
|
+
type LessonkitPluginContext = {
|
|
467
|
+
courseId: CourseId;
|
|
468
|
+
sessionId?: string;
|
|
469
|
+
attemptId?: string;
|
|
470
|
+
user?: TelemetryUser;
|
|
471
|
+
};
|
|
472
|
+
type AssessmentScoreInput = {
|
|
473
|
+
checkId: CheckId;
|
|
474
|
+
lessonId?: LessonId;
|
|
475
|
+
response: unknown;
|
|
476
|
+
};
|
|
477
|
+
type AssessmentScoreResult = {
|
|
478
|
+
score: number;
|
|
479
|
+
maxScore?: number;
|
|
480
|
+
passed?: boolean;
|
|
481
|
+
feedback?: string;
|
|
482
|
+
};
|
|
483
|
+
/** Metadata for custom interaction blocks (renderer wiring stays in app code until 1.0). */
|
|
484
|
+
type InteractionBlockRegistration = {
|
|
485
|
+
blockType: string;
|
|
486
|
+
catalogVersion?: string;
|
|
487
|
+
description?: string;
|
|
488
|
+
};
|
|
489
|
+
type PluginIdentity = {
|
|
490
|
+
id: string;
|
|
491
|
+
version: string;
|
|
492
|
+
kind: LessonkitPluginKind;
|
|
493
|
+
name?: string;
|
|
494
|
+
};
|
|
495
|
+
/** Narrow telemetry plugin contract (ISP). */
|
|
496
|
+
type TelemetryPlugin = PluginIdentity & {
|
|
497
|
+
onTelemetry?: (event: TelemetryEvent, ctx: LessonkitPluginContext) => TelemetryEvent | null;
|
|
498
|
+
onTelemetryBatch?: (events: TelemetryEvent[], ctx: LessonkitPluginContext) => void;
|
|
499
|
+
wrapTrackingSink?: (sink: TelemetrySink, ctx: LessonkitPluginContext) => TelemetrySink;
|
|
500
|
+
};
|
|
501
|
+
/** Narrow lifecycle plugin contract (ISP). */
|
|
502
|
+
type LifecyclePlugin = PluginIdentity & {
|
|
503
|
+
setup?: (ctx: LessonkitPluginContext) => void;
|
|
504
|
+
dispose?: () => void;
|
|
505
|
+
};
|
|
506
|
+
/** Narrow assessment plugin contract (ISP). */
|
|
507
|
+
type AssessmentPlugin = PluginIdentity & {
|
|
508
|
+
kind: "assessment";
|
|
509
|
+
scoreAssessment?: (input: AssessmentScoreInput, ctx: LessonkitPluginContext) => AssessmentScoreResult | null;
|
|
510
|
+
};
|
|
511
|
+
/**
|
|
512
|
+
* Narrow interaction metadata plugin (ISP).
|
|
513
|
+
* @experimental Not wired into PluginHost; reserved for a future release.
|
|
514
|
+
*/
|
|
515
|
+
type InteractionPlugin = PluginIdentity & {
|
|
516
|
+
interactionBlocks?: InteractionBlockRegistration[];
|
|
517
|
+
};
|
|
518
|
+
/**
|
|
519
|
+
* Combined plugin contract (v1). Prefer segregated types for new plugins.
|
|
520
|
+
* @deprecated Prefer `TelemetryPlugin`, `AssessmentPlugin`, or `LifecyclePlugin` via `define*Plugin`.
|
|
521
|
+
*/
|
|
522
|
+
type LessonkitPlugin = PluginIdentity & Partial<Pick<TelemetryPlugin, "onTelemetry" | "onTelemetryBatch" | "wrapTrackingSink"> & Pick<LifecyclePlugin, "setup" | "dispose"> & Pick<AssessmentPlugin, "scoreAssessment"> & Pick<InteractionPlugin, "interactionBlocks">>;
|
|
523
|
+
type PluginHost = {
|
|
524
|
+
readonly plugins: readonly LessonkitPlugin[];
|
|
525
|
+
setupAll: (ctx: LessonkitPluginContext) => void;
|
|
526
|
+
disposeAll: () => void;
|
|
527
|
+
runTelemetry: (event: TelemetryEvent, ctx: LessonkitPluginContext) => TelemetryEvent | null;
|
|
528
|
+
runTelemetryBatch: (events: TelemetryEvent[], ctx: LessonkitPluginContext) => TelemetryEvent[];
|
|
529
|
+
deliverTelemetryBatch: (events: TelemetryEvent[], ctx: LessonkitPluginContext) => TelemetryEvent[];
|
|
530
|
+
composeTrackingSink: (sink: TelemetrySink | undefined, ctx: LessonkitPluginContext | (() => LessonkitPluginContext)) => TelemetrySink | undefined;
|
|
531
|
+
scoreAssessment: (input: AssessmentScoreInput, ctx: LessonkitPluginContext) => AssessmentScoreResult | null;
|
|
532
|
+
};
|
|
533
|
+
/** Segregated plugin registry (ISP + SRP). */
|
|
534
|
+
type PluginRegistry = PluginHost;
|
|
535
|
+
|
|
536
|
+
/** @internal Reset in-flight course_started guard between tests. */
|
|
537
|
+
declare function resetCourseStartedEmitFlightForTests(): void;
|
|
538
|
+
type CourseLifecycleContext = {
|
|
539
|
+
courseId: CourseId;
|
|
540
|
+
sessionId: string;
|
|
541
|
+
attemptId?: string;
|
|
542
|
+
user?: TelemetryUser;
|
|
543
|
+
storage: StoragePort;
|
|
544
|
+
pluginHost: PluginRegistry | null;
|
|
545
|
+
lxpackBridge: "auto" | "off";
|
|
546
|
+
};
|
|
547
|
+
type CourseLifecycleDeps = {
|
|
548
|
+
emitCourseStartedEvent: (ctx: CourseLifecycleContext) => boolean;
|
|
549
|
+
};
|
|
550
|
+
declare function tryEmitCourseStarted(ctx: CourseLifecycleContext, deps: CourseLifecycleDeps, alreadyEmittedToSink: boolean): {
|
|
551
|
+
emitted: boolean;
|
|
552
|
+
marked: boolean;
|
|
553
|
+
};
|
|
554
|
+
declare function buildCourseStartedTelemetryEvent(ctx: CourseLifecycleContext): TelemetryEvent;
|
|
555
|
+
type LessonCompletionEmitter = (lessonId: LessonId, durationMs?: number) => void;
|
|
556
|
+
declare function completeLessonWithTelemetry(opts: {
|
|
557
|
+
progress: ProgressController;
|
|
558
|
+
lessonId: LessonId;
|
|
559
|
+
nowMs: number;
|
|
560
|
+
emitLessonCompleted: LessonCompletionEmitter;
|
|
561
|
+
}): boolean;
|
|
562
|
+
declare function completeCourseWithTelemetry(opts: {
|
|
563
|
+
progress: ProgressController;
|
|
564
|
+
nowMs: number;
|
|
565
|
+
emitLessonCompleted: LessonCompletionEmitter;
|
|
566
|
+
emitCourseCompleted: () => void;
|
|
567
|
+
}): boolean;
|
|
568
|
+
|
|
569
|
+
export { type SlideViewedData as $, type AccordionSectionToggledData as A, type BlockId as B, type CheckId as C, type InteractionPlugin as D, type LessonId as E, type FlashcardFlippedData as F, type LessonLifecycleData as G, type HotspotOpenedData as H, ID_MAX_LENGTH as I, type LessonkitPlugin as J, type LessonkitPluginContext as K, type LessonCompletionEmitter as L, type LessonkitPluginKind as M, type LessonkitUrn as N, type LifecyclePlugin as O, type McqAssessmentProps as P, type MemoryCardFlippedData as Q, type ParallaxSlideViewedData as R, type PluginHost as S, type PluginIdentity as T, type PluginRegistry as U, type ProgressController as V, type ProgressState as W, type QuestionnaireSubmittedData as X, type QuizAnsweredData as Y, type QuizCompletedData as Z, SESSION_STORAGE_KEY as _, type AssessmentAnsweredData as a, type StoragePort as a0, type TelemetryBatchSink as a1, type TelemetryDataFor as a2, type TelemetryEvent as a3, type TelemetryEventBase as a4, type TelemetryEventName as a5, type TelemetryPlugin as a6, type TelemetrySink as a7, type TelemetryUser as a8, type TimerPort as a9, tryEmitCourseStarted as aA, type TrackingClient as aa, type VideoCueReachedData as ab, type VideoSegmentCompletedData as ac, buildCourseStartedTelemetryEvent as ad, buildTelemetryEvent as ae, completeCourseWithTelemetry as af, completeLessonWithTelemetry as ag, createDefaultClock as ah, createGlobalTimer as ai, createNoopStorage as aj, createProgressController as ak, createSessionStoragePort as al, getTabSessionId as am, hasCourseStarted as an, hasCourseStartedEmittedToTracking as ao, hasCourseStartedPipelineDelivered as ap, markCourseStarted as aq, markCourseStartedEmittedToTracking as ar, markCourseStartedPipelineDelivered as as, migrateCourseStartedMark as at, resetCourseStartedEmitFlightForTests as au, resetSharedVolatileSessionIdForTests as av, resetStoragePortForTests as aw, resetTelemetryBuilderWarningsForTests as ax, resolveSessionId as ay, tryBuildTelemetryEvent as az, type AssessmentBaseProps as b, type AssessmentBehaviour as c, type AssessmentCompletedData as d, type AssessmentHandle as e, type AssessmentInteractionType as f, type AssessmentPlugin as g, type AssessmentResumeState as h, type AssessmentScoreInput as i, type AssessmentScoreResult as j, type AssessmentXAPIData as k, type BookPageViewedData as l, type BuildTelemetryEventInput as m, type ClockPort as n, type CompoundPageViewedData as o, type CourseId as p, type CourseLifecycleContext as q, type CourseLifecycleDeps as r, ID_PATTERN as s, type IdentityIdPath as t, type IdentityValidationIssue as u, type IdentityValidationResult as v, type ImageSliderChangedData as w, type InformationWallSearchData as x, type InteractionBlockRegistration as y, type InteractionData as z };
|
package/dist/testing.cjs
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/testing.ts
|
|
21
|
+
var testing_exports = {};
|
|
22
|
+
__export(testing_exports, {
|
|
23
|
+
resetCourseStartedEmitFlightForTests: () => resetCourseStartedEmitFlightForTests,
|
|
24
|
+
resetSharedVolatileSessionIdForTests: () => resetSharedVolatileSessionIdForTests,
|
|
25
|
+
resetStoragePortForTests: () => resetStoragePortForTests,
|
|
26
|
+
resetTelemetryBuilderWarningsForTests: () => resetTelemetryBuilderWarningsForTests
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(testing_exports);
|
|
29
|
+
|
|
30
|
+
// src/telemetryBuilder.ts
|
|
31
|
+
var warnedMissingQuizLesson = false;
|
|
32
|
+
var warnedMissingAssessmentLesson = false;
|
|
33
|
+
function resetTelemetryBuilderWarningsForTests() {
|
|
34
|
+
warnedMissingQuizLesson = false;
|
|
35
|
+
warnedMissingAssessmentLesson = false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/ports.ts
|
|
39
|
+
function resetStoragePortForTests(storage) {
|
|
40
|
+
storage.resetForTests?.();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/session.ts
|
|
44
|
+
var sharedVolatileSessionId = null;
|
|
45
|
+
function resetSharedVolatileSessionIdForTests() {
|
|
46
|
+
sharedVolatileSessionId = null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/runtime/courseLifecycle.ts
|
|
50
|
+
var courseStartedEmitFlights = /* @__PURE__ */ new Set();
|
|
51
|
+
function resetCourseStartedEmitFlightForTests() {
|
|
52
|
+
courseStartedEmitFlights.clear();
|
|
53
|
+
}
|
|
54
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
55
|
+
0 && (module.exports = {
|
|
56
|
+
resetCourseStartedEmitFlightForTests,
|
|
57
|
+
resetSharedVolatileSessionIdForTests,
|
|
58
|
+
resetStoragePortForTests,
|
|
59
|
+
resetTelemetryBuilderWarningsForTests
|
|
60
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { au as resetCourseStartedEmitFlightForTests, av as resetSharedVolatileSessionIdForTests, aw as resetStoragePortForTests, ax as resetTelemetryBuilderWarningsForTests } from './testing-BhVGckZ5.cjs';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { au as resetCourseStartedEmitFlightForTests, av as resetSharedVolatileSessionIdForTests, aw as resetStoragePortForTests, ax as resetTelemetryBuilderWarningsForTests } from './testing-BhVGckZ5.js';
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resetCourseStartedEmitFlightForTests,
|
|
3
|
+
resetSharedVolatileSessionIdForTests,
|
|
4
|
+
resetStoragePortForTests,
|
|
5
|
+
resetTelemetryBuilderWarningsForTests
|
|
6
|
+
} from "./chunk-PEWFPVQ6.js";
|
|
7
|
+
export {
|
|
8
|
+
resetCourseStartedEmitFlightForTests,
|
|
9
|
+
resetSharedVolatileSessionIdForTests,
|
|
10
|
+
resetStoragePortForTests,
|
|
11
|
+
resetTelemetryBuilderWarningsForTests
|
|
12
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lessonkit/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Shared types and telemetry primitives for LessonKit.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -30,6 +30,11 @@
|
|
|
30
30
|
"import": "./dist/index.js",
|
|
31
31
|
"require": "./dist/index.cjs"
|
|
32
32
|
},
|
|
33
|
+
"./testing": {
|
|
34
|
+
"types": "./dist/testing.d.ts",
|
|
35
|
+
"import": "./dist/testing.js",
|
|
36
|
+
"require": "./dist/testing.cjs"
|
|
37
|
+
},
|
|
33
38
|
"./telemetry-catalog.v1.json": "./telemetry-catalog.v1.json",
|
|
34
39
|
"./telemetry-catalog.v2.json": "./telemetry-catalog.v2.json",
|
|
35
40
|
"./telemetry-catalog.v3.json": "./telemetry-catalog.v3.json",
|
|
@@ -43,8 +48,8 @@
|
|
|
43
48
|
"identity-contract.v1.json"
|
|
44
49
|
],
|
|
45
50
|
"scripts": {
|
|
46
|
-
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
47
|
-
"dev": "tsup src/index.ts --format esm,cjs --dts --watch",
|
|
51
|
+
"build": "tsup src/index.ts src/testing.ts --format esm,cjs --dts",
|
|
52
|
+
"dev": "tsup src/index.ts src/testing.ts --format esm,cjs --dts --watch",
|
|
48
53
|
"prepublishOnly": "npm run build",
|
|
49
54
|
"typecheck": "tsc -p tsconfig.json",
|
|
50
55
|
"test": "vitest run --passWithNoTests",
|
|
@@ -52,7 +57,7 @@
|
|
|
52
57
|
"lint": "echo \"(no lint configured yet)\""
|
|
53
58
|
},
|
|
54
59
|
"devDependencies": {
|
|
55
|
-
"@types/node": "^
|
|
60
|
+
"@types/node": "^25.9.2",
|
|
56
61
|
"tsup": "^8.5.0",
|
|
57
62
|
"typescript": "^5.8.3",
|
|
58
63
|
"vitest": "^4.1.8"
|