@intellectif/lk-core 0.2.1 → 0.3.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.
Files changed (59) hide show
  1. package/dist/activity-zdcAMtFB.d.cts +626 -0
  2. package/dist/activity-zdcAMtFB.d.ts +626 -0
  3. package/dist/chunk-3YAVDV5F.js +47 -0
  4. package/dist/chunk-3YAVDV5F.js.map +1 -0
  5. package/dist/chunk-55O4M45K.js +57 -0
  6. package/dist/chunk-55O4M45K.js.map +1 -0
  7. package/dist/chunk-5GJJHGY5.js +576 -0
  8. package/dist/chunk-5GJJHGY5.js.map +1 -0
  9. package/dist/{chunk-ZHOX6TO4.js → chunk-HS7BYCGE.js} +65 -7
  10. package/dist/chunk-HS7BYCGE.js.map +1 -0
  11. package/dist/chunk-PIMX4B4D.cjs +47 -0
  12. package/dist/chunk-PIMX4B4D.cjs.map +1 -0
  13. package/dist/chunk-QOAORTA4.cjs +576 -0
  14. package/dist/chunk-QOAORTA4.cjs.map +1 -0
  15. package/dist/chunk-QSVBYTNM.js +94 -0
  16. package/dist/chunk-QSVBYTNM.js.map +1 -0
  17. package/dist/{chunk-PJFDAP54.cjs → chunk-RUGIOQZY.cjs} +66 -8
  18. package/dist/chunk-RUGIOQZY.cjs.map +1 -0
  19. package/dist/chunk-THSZMZND.cjs +94 -0
  20. package/dist/chunk-THSZMZND.cjs.map +1 -0
  21. package/dist/chunk-YNTYWHZI.cjs +57 -0
  22. package/dist/chunk-YNTYWHZI.cjs.map +1 -0
  23. package/dist/index.cjs +185 -6
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +253 -7
  26. package/dist/index.d.ts +253 -7
  27. package/dist/index.js +188 -9
  28. package/dist/index.js.map +1 -1
  29. package/dist/schemas.cjs +29 -2
  30. package/dist/schemas.cjs.map +1 -1
  31. package/dist/schemas.d.cts +310 -29
  32. package/dist/schemas.d.ts +310 -29
  33. package/dist/schemas.js +32 -5
  34. package/dist/scoring.cjs +13 -3
  35. package/dist/scoring.cjs.map +1 -1
  36. package/dist/scoring.d.cts +29 -5
  37. package/dist/scoring.d.ts +29 -5
  38. package/dist/scoring.js +12 -2
  39. package/dist/xapi.cjs +3 -3
  40. package/dist/xapi.d.cts +40 -2
  41. package/dist/xapi.d.ts +40 -2
  42. package/dist/xapi.js +2 -2
  43. package/package.json +7 -2
  44. package/dist/activity-D5tqgx8A.d.cts +0 -330
  45. package/dist/activity-D5tqgx8A.d.ts +0 -330
  46. package/dist/chunk-2T3IL7VL.js +0 -147
  47. package/dist/chunk-2T3IL7VL.js.map +0 -1
  48. package/dist/chunk-3JMLUUFS.cjs +0 -124
  49. package/dist/chunk-3JMLUUFS.cjs.map +0 -1
  50. package/dist/chunk-6VOCV4EX.js +0 -25
  51. package/dist/chunk-6VOCV4EX.js.map +0 -1
  52. package/dist/chunk-DCDOTQNH.cjs +0 -147
  53. package/dist/chunk-DCDOTQNH.cjs.map +0 -1
  54. package/dist/chunk-DV466SBJ.cjs +0 -25
  55. package/dist/chunk-DV466SBJ.cjs.map +0 -1
  56. package/dist/chunk-GZ73G2TS.js +0 -124
  57. package/dist/chunk-GZ73G2TS.js.map +0 -1
  58. package/dist/chunk-PJFDAP54.cjs.map +0 -1
  59. package/dist/chunk-ZHOX6TO4.js.map +0 -1
@@ -0,0 +1,626 @@
1
+ /**
2
+ * Configurable free-text answer matching (R3.3).
3
+ *
4
+ * Grade-stability contract: the DEFAULTS reproduce the v1 fill-in-the-blanks
5
+ * semantics exactly (trim, then locale-insensitive lowercase, strict `===`).
6
+ * Every tolerance below is opt-in, because changing a default here changes
7
+ * historical grades — and this SDK powers real summative exams.
8
+ */
9
+ interface TextMatchPolicy {
10
+ /** Compare case-sensitively. Default `false` (v1 behaviour). */
11
+ caseSensitive?: boolean;
12
+ /** Strip leading/trailing whitespace before comparing. Default `true` (v1 behaviour). */
13
+ trim?: boolean;
14
+ /**
15
+ * Unicode normalization applied to both sides before comparing. Default
16
+ * `'none'` (v1 behaviour). `'NFC'` makes a decomposed `está` (combining
17
+ * U+0301) equal its composed form — the fix for accent-input mismatches in
18
+ * EN/ES/PT content.
19
+ */
20
+ normalize?: 'none' | 'NFC' | 'NFKC';
21
+ /** Treat diacritics as equal to their base letters (`está` ≡ `esta`). Default `false`. */
22
+ foldDiacritics?: boolean;
23
+ /** Collapse runs of inner whitespace to a single space. Default `false`. */
24
+ collapseInnerWhitespace?: boolean;
25
+ /** Ignore Unicode punctuation on both sides. Default `false`. */
26
+ ignorePunctuation?: boolean;
27
+ /** Maximum Levenshtein edit distance still accepted as a match. Default `0`. */
28
+ levenshtein?: number;
29
+ /**
30
+ * BCP 47 tag for locale-aware case folding (e.g. `'tr'` for Turkish dotted /
31
+ * dotless I). Default: locale-insensitive `toLowerCase()` (v1 behaviour).
32
+ */
33
+ locale?: string;
34
+ }
35
+ /**
36
+ * How a match was achieved. Lets a consumer award full credit for an `exact`
37
+ * match and partial credit for a `folded` or `fuzzy` one (e.g. diacritic
38
+ * tolerance in a listening gap-fill vs. a spelling test).
39
+ * - `exact` — equal under the baseline trim/case rules alone.
40
+ * - `normalized` — required Unicode normalization, whitespace collapse, or punctuation tolerance.
41
+ * - `folded` — required diacritic folding.
42
+ * - `fuzzy` — required Levenshtein tolerance.
43
+ * - `none` — no accepted answer matched.
44
+ */
45
+ interface TextMatchResult {
46
+ matched: boolean;
47
+ via: 'exact' | 'normalized' | 'folded' | 'fuzzy' | 'none';
48
+ }
49
+ /**
50
+ * Levenshtein distance with an early-exit bound: returns `max + 1` as soon as
51
+ * the distance provably exceeds `max`.
52
+ */
53
+ declare function levenshteinDistance(a: string, b: string, max: number): number;
54
+ /**
55
+ * Matches a learner's input against one or more accepted answers under a
56
+ * {@link TextMatchPolicy}. Tolerances are evaluated in stages — baseline
57
+ * (trim/case), then normalization, then diacritic folding, then Levenshtein —
58
+ * and {@link TextMatchResult.via} reports the first stage that produced the
59
+ * match, so graders can award credit by match quality.
60
+ *
61
+ * With no policy (or an empty one) this is byte-for-byte the v1 matching
62
+ * semantics: `trim` + locale-insensitive `toLowerCase` + strict equality.
63
+ */
64
+ declare function matchText(input: string, accepted: string | readonly string[], policy?: TextMatchPolicy): TextMatchResult;
65
+
66
+ /**
67
+ * An xAPI Activity object — the thing a statement is about.
68
+ * This SDK only builds Activity-type objects (xAPI 1.0.3 §4.1.4.1).
69
+ */
70
+ interface XAPIObject {
71
+ objectType: 'Activity';
72
+ /** IRI uniquely identifying the activity. */
73
+ id: string;
74
+ definition?: {
75
+ /** Language-map of human-readable names for the activity. */
76
+ name?: Record<string, string>;
77
+ /** Language-map of human-readable descriptions. */
78
+ description?: Record<string, string>;
79
+ /** IRI identifying the activity type. */
80
+ type?: string;
81
+ /** xAPI 1.0.3 cmi.interaction type (e.g. `choice`, `fill-in`, `long-fill-in`). */
82
+ interactionType?: string;
83
+ /** Patterns describing the correct response(s), per the xAPI CRP format. */
84
+ correctResponsesPattern?: string[];
85
+ /** For `choice`-family interactions: the available components. */
86
+ choices?: Array<{
87
+ id: string;
88
+ description?: Record<string, string>;
89
+ }>;
90
+ extensions?: Record<string, unknown>;
91
+ };
92
+ }
93
+ /**
94
+ * An xAPI Agent (person or system) that performed the statement's verb.
95
+ * Exactly one of `mbox` or `account` must be present.
96
+ */
97
+ interface XAPIActor {
98
+ objectType: 'Agent';
99
+ /** Display name for the actor. */
100
+ name?: string;
101
+ /** Mailto IRI uniquely identifying the actor (e.g. `mailto:user@example.com`). */
102
+ mbox?: string;
103
+ /** Account-based actor identifier. */
104
+ account?: {
105
+ /** IRI of the home page of the account's service provider. */
106
+ homePage: string;
107
+ /** The actor's unique name on that service. */
108
+ name: string;
109
+ };
110
+ }
111
+ /** An xAPI Verb — the action performed in the statement. */
112
+ interface XAPIVerbObject {
113
+ /** IRI uniquely identifying this verb (e.g. from adlnet.gov/expapi/verbs). */
114
+ id: string;
115
+ /** Language-map of human-readable verb labels. */
116
+ display: Record<string, string>;
117
+ }
118
+ /** Score sub-object within an xAPI Result. */
119
+ interface XAPIScore {
120
+ /** Normalized score in the range [0, 1]. */
121
+ scaled: number;
122
+ /** Raw score in the instrument's native scale. */
123
+ raw?: number;
124
+ /** Minimum possible raw score. */
125
+ min?: number;
126
+ /** Maximum possible raw score. */
127
+ max?: number;
128
+ }
129
+ /** Result information attached to an xAPI Statement. */
130
+ interface XAPIResult {
131
+ score?: XAPIScore;
132
+ /** Whether the actor's performance was successful. */
133
+ success?: boolean;
134
+ /** Whether the activity was completed. */
135
+ completion?: boolean;
136
+ /** ISO 8601 duration string representing time spent (e.g. `PT5M30S`). */
137
+ duration?: string;
138
+ /** A string representation of the learner's response. */
139
+ response?: string;
140
+ extensions?: Record<string, unknown>;
141
+ }
142
+ /** Related-activity references attached to a statement's context. */
143
+ interface XAPIContextActivities {
144
+ /** Activities this statement's activity is directly part of (e.g. the exam section). */
145
+ parent?: XAPIObject[];
146
+ /** Broader groupings (e.g. the course). */
147
+ grouping?: XAPIObject[];
148
+ /** Categorisations (e.g. a profile IRI). */
149
+ category?: XAPIObject[];
150
+ /** Other contextually related activities. */
151
+ other?: XAPIObject[];
152
+ }
153
+ /** Context information attached to an xAPI Statement. */
154
+ interface XAPIContext {
155
+ /** Name of the software used to record the statement. */
156
+ platform?: string;
157
+ /** BCP 47 language tag for the activity content. */
158
+ language?: string;
159
+ /** Related activities (parent/grouping/category/other). */
160
+ contextActivities?: XAPIContextActivities;
161
+ extensions?: Record<string, unknown>;
162
+ }
163
+ /** A complete xAPI 1.0.3 Statement. */
164
+ interface XAPIStatement {
165
+ /** UUID v4 uniquely identifying this statement. */
166
+ id: string;
167
+ actor: XAPIActor;
168
+ verb: XAPIVerbObject;
169
+ object: XAPIObject;
170
+ result?: XAPIResult;
171
+ context?: XAPIContext;
172
+ /** ISO 8601 timestamp of when the statement occurred. */
173
+ timestamp: string;
174
+ version: '1.0.3';
175
+ }
176
+ /**
177
+ * Configuration passed to `useXAPI` to connect to an LRS endpoint.
178
+ * The `auth` field supports both HTTP Basic and Bearer token authentication.
179
+ */
180
+ interface XAPIConfig {
181
+ /** Full URL of the LRS statements endpoint. */
182
+ endpoint: string;
183
+ /** Authentication credentials for the LRS. */
184
+ auth: {
185
+ type: 'basic';
186
+ username: string;
187
+ password: string;
188
+ } | {
189
+ type: 'bearer';
190
+ token: string;
191
+ };
192
+ /**
193
+ * Replaces the SDK's placeholder object id (`urn:learning-kit:activity:*`)
194
+ * before a statement is sent.
195
+ *
196
+ * - `string` — the IRI for THE activity this hook instance serves. A hook
197
+ * instance is per-activity by contract; with several activities sharing
198
+ * one hook, a plain string would collapse their statements onto one IRI —
199
+ * use the function form instead.
200
+ * - `(sdkObjectId) => string` — maps each placeholder id (which embeds the
201
+ * activity's `data.id`) to its deployment IRI, for pages that send many
202
+ * activities through one hook.
203
+ *
204
+ * Object ids the consumer already rewrote (non-`urn:learning-kit:` ids) are
205
+ * never touched.
206
+ */
207
+ activityId: string | ((sdkObjectId: string) => string);
208
+ actor: XAPIActor;
209
+ /** Called after all retry attempts are exhausted. */
210
+ onError?: (err: XAPIError) => void;
211
+ }
212
+ /** Error payload delivered to `XAPIConfig.onError` after retries are exhausted. */
213
+ interface XAPIError {
214
+ /** The statement that failed to send. */
215
+ statement: XAPIStatement;
216
+ /** The 1-based attempt number on which the final failure occurred. */
217
+ attempt: number;
218
+ /** HTTP status code from the LRS, if a response was received. */
219
+ statusCode?: number;
220
+ /** Human-readable error description. */
221
+ message: string;
222
+ }
223
+
224
+ /**
225
+ * Maps each ActivityType string to its corresponding data shape.
226
+ *
227
+ * Open for extension via TypeScript module augmentation: a consumer that
228
+ * registers a custom activity type (`registerActivityType`) can augment this
229
+ * interface so `ActivityType`, `ActivityData`, `validateActivity`, and
230
+ * `evaluate` pick the new type up without an SDK release:
231
+ *
232
+ * ```ts
233
+ * declare module '@intellectif/lk-core' {
234
+ * interface ActivityDataMap { 'my-type': MyTypeData }
235
+ * interface LearnerResponseMap { 'my-type': MyTypeLearnerResponse }
236
+ * }
237
+ * ```
238
+ */
239
+ interface ActivityDataMap {
240
+ 'multiple-choice': MultipleChoiceData;
241
+ 'fill-in-the-blanks': FillInTheBlanksData;
242
+ 'written-response': WrittenResponseData;
243
+ }
244
+ /**
245
+ * The set of activity types supported by learning-kit. Derived from
246
+ * {@link ActivityDataMap}, so module augmentation widens it automatically.
247
+ */
248
+ type ActivityType = keyof ActivityDataMap;
249
+ /** Union of all valid activity data shapes. */
250
+ type ActivityData = ActivityDataMap[ActivityType];
251
+ /**
252
+ * Optional media (image/audio/video) shown above a question or passage.
253
+ * URL-only: hosting/delivery is the consuming application's responsibility.
254
+ */
255
+ interface ActivityMedia {
256
+ /** The kind of media; selects the rendered element. `embed` → sandboxed iframe. */
257
+ type: 'image' | 'audio' | 'video' | 'embed';
258
+ /**
259
+ * Source URL. For `embed` this MUST be the provider's embeddable URL
260
+ * (e.g. `https://www.youtube.com/embed/<id>`), not the watch page.
261
+ */
262
+ url: string;
263
+ /** Alternative text. Required for `image` and `embed`; optional label for audio/video. */
264
+ alt?: string;
265
+ /** Optional WebVTT captions track URL for `audio`/`video`. */
266
+ captionsUrl?: string;
267
+ }
268
+ /**
269
+ * Optional authored "overall feedback" shown after submission, chosen by
270
+ * whether the learner passed (h5p-style overall feedback). Distinct from
271
+ * per-option feedback; either field may be omitted.
272
+ */
273
+ interface ActivityFeedback {
274
+ /** Shown when the learner passes (score ≥ pass threshold). */
275
+ correct?: string;
276
+ /** Shown when the learner does not pass. */
277
+ incorrect?: string;
278
+ }
279
+ /** A single selectable option within a Multiple Choice activity. */
280
+ interface MultipleChoiceOption {
281
+ /** Unique identifier for this option within the activity. */
282
+ id: string;
283
+ /** Display text shown to the learner. */
284
+ text: string;
285
+ /** Whether this option is part of the correct answer. */
286
+ isCorrect: boolean;
287
+ /** Optional per-option feedback shown after submission. */
288
+ feedback?: string;
289
+ }
290
+ /** Data contract for a Multiple Choice activity. */
291
+ interface MultipleChoiceData {
292
+ schemaVersion: '1.0';
293
+ type: 'multiple-choice';
294
+ /** Unique identifier for this activity. */
295
+ id: string;
296
+ /** Human-readable title used in xAPI statements and error boundaries. */
297
+ title: string;
298
+ /** The question stem presented to the learner, as plain text. */
299
+ question: string;
300
+ /**
301
+ * Optional sanitised rich-HTML rendering of the question, parallel to
302
+ * `WrittenResponseData.promptHtml`. The SDK carries and redacts it as
303
+ * learner-visible content but does not render it yet (components escape
304
+ * text); sanitisation is the application's responsibility.
305
+ */
306
+ questionHtml?: string;
307
+ /** `single` allows one selection; `multi` allows multiple. */
308
+ mode: 'single' | 'multi';
309
+ /** Ordered list of answer options. */
310
+ options: MultipleChoiceOption[];
311
+ /** Scoring algorithm applied when the learner submits. */
312
+ scoringStrategy: 'all-or-nothing' | 'partial';
313
+ /** Optional media shown above the question. */
314
+ media?: ActivityMedia;
315
+ /** Optional authored overall feedback shown after submission. */
316
+ feedback?: ActivityFeedback;
317
+ /** Minimum scaled score [0–1] required to pass. Defaults to {@link DEFAULT_PASS_THRESHOLD} (0.7) when absent. */
318
+ passThreshold?: number;
319
+ /** When true, options are shuffled deterministically per session. */
320
+ shuffle?: boolean;
321
+ /** BCP 47 language tag for the activity content. */
322
+ locale?: string;
323
+ /** IRI references to learning objectives addressed by this activity. */
324
+ learningObjectives?: string[];
325
+ /** Subjective difficulty on a 1–5 scale. */
326
+ difficultyLevel?: 1 | 2 | 3 | 4 | 5;
327
+ }
328
+ /** Configuration for a single fill-in-the-blank slot. */
329
+ interface BlankConfig {
330
+ /** Unique identifier matching the `{{blank_id}}` placeholder in the passage. */
331
+ id: string;
332
+ /** List of strings accepted as correct answers for this blank. */
333
+ acceptedAnswers: string[];
334
+ /** Whether answer matching is case-sensitive. Defaults to false. */
335
+ caseSensitive?: boolean;
336
+ /** Whether leading/trailing whitespace is stripped before matching. Defaults to true. */
337
+ trimWhitespace?: boolean;
338
+ /**
339
+ * Optional matching-tolerance policy for this blank (Unicode normalization,
340
+ * diacritic folding, whitespace collapse, punctuation tolerance, typo
341
+ * tolerance). Every tolerance is opt-in; when absent, matching reproduces
342
+ * the v1 trim + case-fold semantics exactly. Fields set here take
343
+ * precedence over the legacy `caseSensitive` / `trimWhitespace` flags.
344
+ */
345
+ match?: TextMatchPolicy;
346
+ /** Optional hint text revealed on learner request. */
347
+ hint?: string;
348
+ /** Optional feedback shown inline next to this blank after submission. */
349
+ feedback?: string;
350
+ }
351
+ /** Data contract for a Fill-in-the-Blanks activity. */
352
+ interface FillInTheBlanksData {
353
+ schemaVersion: '1.0';
354
+ type: 'fill-in-the-blanks';
355
+ /** Unique identifier for this activity. */
356
+ id: string;
357
+ /** Human-readable title used in xAPI statements and error boundaries. */
358
+ title: string;
359
+ /** Passage text containing `{{blank_id}}` placeholders. */
360
+ passage: string;
361
+ /**
362
+ * Optional sanitised rich-HTML rendering of the passage, parallel to
363
+ * `WrittenResponseData.promptHtml`. Carried and redacted as learner-visible
364
+ * content; not rendered by the SDK yet. Sanitisation is the application's.
365
+ */
366
+ passageHtml?: string;
367
+ /** Configuration for each blank in the passage. */
368
+ blanks: BlankConfig[];
369
+ /** Scoring algorithm applied when the learner submits. */
370
+ scoringStrategy: 'all-or-nothing' | 'partial';
371
+ /** Optional media shown above the passage. */
372
+ media?: ActivityMedia;
373
+ /** Optional authored overall feedback shown after submission. */
374
+ feedback?: ActivityFeedback;
375
+ /** Minimum scaled score [0–1] required to pass. Defaults to {@link DEFAULT_PASS_THRESHOLD} (0.7) when absent. */
376
+ passThreshold?: number;
377
+ /** BCP 47 language tag for the activity content. */
378
+ locale?: string;
379
+ /** IRI references to learning objectives addressed by this activity. */
380
+ learningObjectives?: string[];
381
+ /** Subjective difficulty on a 1–5 scale. */
382
+ difficultyLevel?: 1 | 2 | 3 | 4 | 5;
383
+ }
384
+ /** A single criterion within a written-response grading rubric. */
385
+ interface WrittenResponseRubricCriterion {
386
+ /** Criterion name (e.g. "Task achievement", "Grammar range"). */
387
+ name: string;
388
+ /** Optional longer description of what the criterion assesses. */
389
+ description?: string;
390
+ /** Non-negative weight of this criterion in the overall grade. */
391
+ weight: number;
392
+ }
393
+ /** Grading rubric attached to a written-response activity. */
394
+ interface WrittenResponseRubric {
395
+ /** Optional display label for the rubric as a whole. */
396
+ label?: string;
397
+ /** The criteria the response is graded against. Non-empty when present. */
398
+ criteria: WrittenResponseRubricCriterion[];
399
+ }
400
+ /**
401
+ * Data contract for a Written Response activity (free-text writing graded
402
+ * asynchronously — by an AI or human grader — after submission).
403
+ *
404
+ * Wire-format note (Req 22.9): field names and casing are locked for
405
+ * byte-compatibility with consumer-stored JSONB rows. `feedback` and
406
+ * `passThreshold` are SDK-side optional additions for component parity
407
+ * (Req 22.8 / Req 3.9) — being optional, their absence keeps stored payloads
408
+ * byte-identical.
409
+ */
410
+ interface WrittenResponseData {
411
+ schemaVersion: '1.0';
412
+ type: 'written-response';
413
+ /** Unique identifier for this activity. */
414
+ id: string;
415
+ /** Human-readable title used in xAPI statements and error boundaries. */
416
+ title: string;
417
+ /** The writing prompt, as plain text. */
418
+ prompt: string;
419
+ /** Optional sanitised rich-HTML sidecar of the prompt (not rendered by the SDK yet). */
420
+ promptHtml?: string;
421
+ /** Minimum acceptable word count (≥ 0). */
422
+ minWords: number;
423
+ /** Maximum acceptable word count (≥ 1, and ≥ `minWords`). */
424
+ maxWords: number;
425
+ /** Optional grading rubric consumed by the asynchronous grader. */
426
+ rubric?: WrittenResponseRubric;
427
+ /** Optional target language/level for the response (e.g. `"en-A2"`, `"es-B1"`). */
428
+ languageTarget?: string;
429
+ /** Optional media shown above the prompt. */
430
+ media?: ActivityMedia;
431
+ /** Optional authored overall feedback (surfaced once the deferred grade exists). */
432
+ feedback?: ActivityFeedback;
433
+ /** Minimum scaled score [0–1] required to pass once graded. Defaults to {@link DEFAULT_PASS_THRESHOLD} (0.7) when absent. */
434
+ passThreshold?: number;
435
+ /** BCP 47 language tag for the activity content. */
436
+ locale?: string;
437
+ /** IRI references to learning objectives addressed by this activity. */
438
+ learningObjectives?: string[];
439
+ /** Subjective difficulty on a 1–5 scale. */
440
+ difficultyLevel?: 1 | 2 | 3 | 4 | 5;
441
+ }
442
+ /**
443
+ * Maps each ActivityType string to its learner-response shape. Open for
444
+ * extension via module augmentation, mirroring {@link ActivityDataMap}.
445
+ */
446
+ interface LearnerResponseMap {
447
+ 'multiple-choice': MultipleChoiceLearnerResponse;
448
+ 'fill-in-the-blanks': FillInTheBlanksLearnerResponse;
449
+ 'written-response': WrittenResponseLearnerResponse;
450
+ }
451
+ /** Union of all learner response shapes. */
452
+ type LearnerResponse = LearnerResponseMap[keyof LearnerResponseMap];
453
+ /** Learner response for a Multiple Choice activity. */
454
+ interface MultipleChoiceLearnerResponse {
455
+ type: 'multiple-choice';
456
+ /** IDs of the options the learner selected. */
457
+ selectedOptionIds: string[];
458
+ }
459
+ /** Learner response for a Fill-in-the-Blanks activity. */
460
+ interface FillInTheBlanksLearnerResponse {
461
+ type: 'fill-in-the-blanks';
462
+ /** Map of blank ID to the learner's typed answer. */
463
+ answers: Record<string, string>;
464
+ }
465
+ /** Learner response for a Written Response activity. */
466
+ interface WrittenResponseLearnerResponse {
467
+ type: 'written-response';
468
+ /** The learner's free-text response. */
469
+ text: string;
470
+ /** Word count of `text`, computed with the canonical `countWords()` helper. */
471
+ wordCount: number;
472
+ }
473
+ /**
474
+ * Fine-grained outcome of the learner's action on a single item, replacing the
475
+ * ambiguous {@link ScoringDetail.correct}:
476
+ * - `correct` — the learner selected/entered the right answer.
477
+ * - `incorrect` — the learner selected/entered a wrong answer.
478
+ * - `correct-omission` — the learner correctly left a non-answer unselected (multiple-choice only).
479
+ * - `incorrect-omission` — the learner failed to select a correct answer (multiple-choice only).
480
+ */
481
+ type ScoringOutcome = 'correct' | 'incorrect' | 'correct-omission' | 'incorrect-omission';
482
+ /** Per-item scoring breakdown returned by the scoring engine. */
483
+ interface ScoringDetail {
484
+ /** ID of the option or blank this detail refers to. */
485
+ itemId: string;
486
+ /**
487
+ * @deprecated Ambiguous: for multiple-choice this means "the learner acted
488
+ * correctly on this option" (`wasSelected === option.isCorrect`), NOT "this
489
+ * option is the answer" — an unselected wrong option reads `correct: true`.
490
+ * Read {@link ScoringDetail.outcome} instead; `correct` remains written for
491
+ * backward compatibility and will be removed in v1.0.
492
+ */
493
+ correct: boolean;
494
+ /**
495
+ * Unambiguous outcome of the learner's action on this item. Optional in the
496
+ * type so 0.2-era consumer-constructed literals keep compiling, but ALWAYS
497
+ * written by both built-in scorers since 0.3.0; becomes required in v1.0
498
+ * when the deprecated `correct` is removed.
499
+ */
500
+ outcome?: ScoringOutcome;
501
+ /** The learner's actual response for this item. */
502
+ learnerResponse: string | string[];
503
+ /** The expected correct response(s) for this item. */
504
+ correctResponse: string | string[];
505
+ /**
506
+ * Weight applied to this item's contribution to the overall score. Both
507
+ * built-in scorers currently weight every item equally and write `1`.
508
+ */
509
+ weight?: number;
510
+ }
511
+ /** Full scoring result returned by the scoring engine. */
512
+ interface ScoringResult {
513
+ /** Scaled score in the range [0, 1]. */
514
+ score: number;
515
+ /** Maximum possible scaled score (always 1). */
516
+ maxScore: number;
517
+ /** Whether the score meets or exceeds the activity's pass threshold. */
518
+ passed: boolean;
519
+ /**
520
+ * The authored overall feedback selected for this result: `feedback.correct`
521
+ * when the learner passed, `feedback.incorrect` otherwise; `null` when the
522
+ * activity authored no matching feedback.
523
+ */
524
+ feedback: string | null;
525
+ /** Per-item scoring breakdown. */
526
+ details: ScoringDetail[];
527
+ }
528
+ /**
529
+ * Progress facts about a deferred (asynchronously graded) submission that are
530
+ * computable synchronously at submit time. For written-response, both fields
531
+ * are always present and `wordCount` is recomputed from the submitted text
532
+ * with the canonical `countWords()` (the client-supplied count is not trusted).
533
+ */
534
+ interface DeferredScoringPartial {
535
+ /** Whether the recomputed word count falls within `[minWords, maxWords]`. */
536
+ withinWordBounds?: boolean;
537
+ /** Recomputed word count of the submitted text. */
538
+ wordCount?: number;
539
+ [key: string]: unknown;
540
+ }
541
+ /**
542
+ * The outcome of evaluating a learner response against an activity — the
543
+ * union `evaluate()` returns. Unlike {@link ScoringResult}, it can express
544
+ * "not gradable yet" (`deferred`) and "not gradable at all" (`unscorable`),
545
+ * so an ungraded written response is never conflated with a score of 0.
546
+ */
547
+ type ItemOutcome = {
548
+ status: 'scored';
549
+ /** Scaled score in the range [0, 1]. */
550
+ score: number;
551
+ /** Maximum possible scaled score. */
552
+ maxScore: number;
553
+ /** Whether the score meets or exceeds the activity's pass threshold. */
554
+ passed: boolean;
555
+ /** Authored overall feedback selected by pass state, or null. */
556
+ feedback: string | null;
557
+ /** Per-item scoring breakdown. */
558
+ details: ScoringDetail[];
559
+ } | {
560
+ status: 'deferred';
561
+ /** Why the grade is deferred (asynchronous AI/human grading). */
562
+ reason: 'requires_async_grading';
563
+ /** Maximum possible scaled score once graded. */
564
+ maxScore: number;
565
+ /** Synchronously computable progress facts (word bounds, counts). */
566
+ partial?: DeferredScoringPartial;
567
+ } | {
568
+ status: 'unscorable';
569
+ /** Why no grade can be produced (e.g. unregistered activity type). */
570
+ reason: string;
571
+ /** Maximum possible scaled score, when known. */
572
+ maxScore: number;
573
+ };
574
+ /** A single validation error produced by `validateActivity`. */
575
+ interface ValidationError {
576
+ /** JSON-path-style location of the invalid field. */
577
+ path: string[];
578
+ /** Human-readable description of the validation failure. */
579
+ message: string;
580
+ /** Machine-readable error code. */
581
+ code: string;
582
+ }
583
+ /**
584
+ * Result of a `validateActivity` call.
585
+ * On success, `data` is the validated and typed activity data.
586
+ * On failure, `errors` contains one entry per violated constraint.
587
+ */
588
+ type ValidationResult<T> = {
589
+ success: true;
590
+ data: T;
591
+ } | {
592
+ success: false;
593
+ errors: ValidationError[];
594
+ };
595
+ /** The payload delivered to an activity's `onComplete` callback. */
596
+ interface ActivityResult {
597
+ /** Scaled score in the range [0, 1]. */
598
+ score: number;
599
+ /** Maximum possible scaled score (always 1). */
600
+ maxScore: number;
601
+ /** Whether the learner passed based on the activity's pass threshold. */
602
+ passed: boolean;
603
+ /** Time in milliseconds from first interaction to submission. */
604
+ timeSpent: number;
605
+ /** The xAPI statement built and (optionally) sent for this attempt. */
606
+ xapiStatement: XAPIStatement;
607
+ }
608
+ /**
609
+ * The kinds of interaction the built-in components emit. Custom activity types
610
+ * registered by consumers may emit their own kinds, so any string is accepted;
611
+ * the named literals are kept for autocompletion.
612
+ */
613
+ type InteractionKind = 'option-selected' | 'option-deselected' | 'blank-filled' | 'hint-requested' | 'text-changed' | 'submitted' | (string & {});
614
+ /** Fired by activity components on every discrete learner interaction. */
615
+ interface InteractionEvent {
616
+ /** The kind of interaction that occurred. */
617
+ type: InteractionKind;
618
+ /** ID of the activity that produced the event. */
619
+ activityId: string;
620
+ /** Unix timestamp (ms) of when the interaction occurred. */
621
+ timestamp: number;
622
+ /** Additional event-specific data. */
623
+ payload: Record<string, unknown>;
624
+ }
625
+
626
+ export { type ActivityData as A, type BlankConfig as B, type XAPIVerbObject as C, type DeferredScoringPartial as D, levenshteinDistance as E, type FillInTheBlanksData as F, matchText as G, type InteractionEvent as I, type LearnerResponse as L, type MultipleChoiceData as M, type ScoringDetail as S, type TextMatchPolicy as T, type ValidationError as V, type WrittenResponseData as W, type XAPIActor as X, type ActivityDataMap as a, type ActivityFeedback as b, type ActivityMedia as c, type ActivityResult as d, type ActivityType as e, type FillInTheBlanksLearnerResponse as f, type InteractionKind as g, type ItemOutcome as h, type LearnerResponseMap as i, type MultipleChoiceLearnerResponse as j, type MultipleChoiceOption as k, type ScoringOutcome as l, type ScoringResult as m, type TextMatchResult as n, type ValidationResult as o, type WrittenResponseLearnerResponse as p, type WrittenResponseRubric as q, type WrittenResponseRubricCriterion as r, type XAPIConfig as s, type XAPIContext as t, type XAPIContextActivities as u, type XAPIError as v, type XAPIObject as w, type XAPIResult as x, type XAPIScore as y, type XAPIStatement as z };
@@ -0,0 +1,47 @@
1
+ // src/errors.ts
2
+ var ActivitySchemaError = class extends Error {
3
+ constructor(activityType, errors) {
4
+ super(`Invalid activity data for type "${activityType}"`);
5
+ this.activityType = activityType;
6
+ this.errors = errors;
7
+ this.name = "ActivitySchemaError";
8
+ }
9
+ activityType;
10
+ errors;
11
+ };
12
+ var UnknownActivityTypeError = class extends Error {
13
+ constructor(activityType) {
14
+ super(`Activity type "${activityType}" is not registered`);
15
+ this.activityType = activityType;
16
+ this.name = "UnknownActivityTypeError";
17
+ }
18
+ activityType;
19
+ };
20
+ var RedactedScoringError = class extends Error {
21
+ constructor(activityType) {
22
+ super(
23
+ `Activity data for "${activityType}" carries no answer key (it looks redacted), so it cannot be scored. Score against the full activity data server-side, or use evaluate() which returns { status: "unscorable" }.`
24
+ );
25
+ this.activityType = activityType;
26
+ this.name = "RedactedScoringError";
27
+ }
28
+ activityType;
29
+ };
30
+ var DeferredScoringError = class extends Error {
31
+ constructor(activityType) {
32
+ super(
33
+ `Activity type "${activityType}" is graded asynchronously and has no synchronous score. Use evaluate() \u2014 it returns { status: 'deferred' } for this type.`
34
+ );
35
+ this.activityType = activityType;
36
+ this.name = "DeferredScoringError";
37
+ }
38
+ activityType;
39
+ };
40
+
41
+ export {
42
+ ActivitySchemaError,
43
+ UnknownActivityTypeError,
44
+ RedactedScoringError,
45
+ DeferredScoringError
46
+ };
47
+ //# sourceMappingURL=chunk-3YAVDV5F.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts"],"sourcesContent":["import type { ValidationError } from './types/activity.js';\n\n/** Thrown when activity data fails schema validation at a component boundary. */\nexport class ActivitySchemaError extends Error {\n constructor(\n public readonly activityType: string,\n public readonly errors: ValidationError[],\n ) {\n super(`Invalid activity data for type \"${activityType}\"`);\n this.name = 'ActivitySchemaError';\n }\n}\n\n/** Thrown when an unrecognised activity type is passed to the scoring engine. */\nexport class UnknownActivityTypeError extends Error {\n constructor(public readonly activityType: string) {\n super(`Activity type \"${activityType}\" is not registered`);\n this.name = 'UnknownActivityTypeError';\n }\n}\n\n/**\n * Thrown when `score()` is asked to grade a `redact()` projection (or any\n * activity data whose answer key is missing, yielding a non-finite score).\n * Redacted data is learner-safe precisely because the key was removed, so a\n * score derived from it is meaningless — previously this produced a silent\n * `NaN` that serialized to `null` in a grade column. `evaluate()` returns\n * `{ status: 'unscorable' }` for the same input instead of throwing.\n */\nexport class RedactedScoringError extends Error {\n constructor(public readonly activityType: string) {\n super(\n `Activity data for \"${activityType}\" carries no answer key (it looks redacted), so it cannot be scored. ` +\n 'Score against the full activity data server-side, or use evaluate() which returns { status: \"unscorable\" }.',\n );\n this.name = 'RedactedScoringError';\n }\n}\n\n/**\n * Thrown when `score()` is called for an activity type whose grading is\n * deferred (asynchronous AI/human grading, e.g. `written-response`). A\n * deferred submission has no synchronous score — treating it as 0 would show\n * a learner a failing grade for work that simply has not been graded yet.\n * Call `evaluate()` instead, which returns `{ status: 'deferred', ... }`.\n */\nexport class DeferredScoringError extends Error {\n constructor(public readonly activityType: string) {\n super(\n `Activity type \"${activityType}\" is graded asynchronously and has no synchronous score. ` +\n `Use evaluate() — it returns { status: 'deferred' } for this type.`,\n );\n this.name = 'DeferredScoringError';\n }\n}\n"],"mappings":";AAGO,IAAM,sBAAN,cAAkC,MAAM;AAAA,EAC7C,YACkB,cACA,QAChB;AACA,UAAM,mCAAmC,YAAY,GAAG;AAHxC;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA,EALkB;AAAA,EACA;AAKpB;AAGO,IAAM,2BAAN,cAAuC,MAAM;AAAA,EAClD,YAA4B,cAAsB;AAChD,UAAM,kBAAkB,YAAY,qBAAqB;AAD/B;AAE1B,SAAK,OAAO;AAAA,EACd;AAAA,EAH4B;AAI9B;AAUO,IAAM,uBAAN,cAAmC,MAAM;AAAA,EAC9C,YAA4B,cAAsB;AAChD;AAAA,MACE,sBAAsB,YAAY;AAAA,IAEpC;AAJ0B;AAK1B,SAAK,OAAO;AAAA,EACd;AAAA,EAN4B;AAO9B;AASO,IAAM,uBAAN,cAAmC,MAAM;AAAA,EAC9C,YAA4B,cAAsB;AAChD;AAAA,MACE,kBAAkB,YAAY;AAAA,IAEhC;AAJ0B;AAK1B,SAAK,OAAO;AAAA,EACd;AAAA,EAN4B;AAO9B;","names":[]}