tessera-learn 0.0.5 → 0.0.7
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/plugin/cli.d.ts +1 -0
- package/dist/plugin/cli.js +18 -0
- package/dist/plugin/cli.js.map +1 -0
- package/dist/plugin/index.js +9 -730
- package/dist/plugin/index.js.map +1 -1
- package/dist/validation-B4UhCY5y.js +911 -0
- package/dist/validation-B4UhCY5y.js.map +1 -0
- package/package.json +4 -2
- package/src/plugin/cli.ts +30 -0
- package/src/plugin/export.ts +12 -1
- package/src/plugin/validation.ts +336 -62
- package/src/runtime/adapters/index.ts +1 -1
- package/src/runtime/adapters/retry.ts +86 -15
- package/src/runtime/adapters/scorm-base.ts +90 -46
- package/src/runtime/adapters/scorm12.ts +36 -11
- package/src/runtime/adapters/scorm2004.ts +129 -26
- package/src/runtime/hooks.svelte.ts +22 -1
- package/src/runtime/interaction-format.ts +83 -48
- package/AGENTS.md +0 -1362
|
@@ -1,79 +1,125 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* reuses the same encoding for `cmi.interaction` activity statements.
|
|
5
|
-
*
|
|
6
|
-
* ITEM delimiter [,]
|
|
7
|
-
* PAIR delimiter [.]
|
|
8
|
-
* RANGE delimiter [:]
|
|
2
|
+
* SCORM 1.2 RTE §3.4.7 vs SCORM 2004 4E RTE §4.2.7 differ in delimiter
|
|
3
|
+
* encoding and identifier rules; cmi5 (xAPI) reuses the 2004 encoding.
|
|
9
4
|
*/
|
|
10
5
|
|
|
11
6
|
import type { Interaction } from './interaction.js';
|
|
12
7
|
|
|
8
|
+
export interface InteractionFormat {
|
|
9
|
+
itemDelim: string;
|
|
10
|
+
pairDelim: string;
|
|
11
|
+
rangeDelim: string;
|
|
12
|
+
/**
|
|
13
|
+
* SCORM 1.2 has no numeric range syntax — `correct_responses.n.pattern`
|
|
14
|
+
* is a single CMIDecimal. SCORM 2004 supports `min[:]max`.
|
|
15
|
+
*/
|
|
16
|
+
supportsNumericRange: boolean;
|
|
17
|
+
formatBoolean(value: boolean): string;
|
|
18
|
+
identifier(value: string): string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const SCORM12_INTERACTION_FORMAT: InteractionFormat = {
|
|
22
|
+
itemDelim: ',',
|
|
23
|
+
pairDelim: '.',
|
|
24
|
+
rangeDelim: ':',
|
|
25
|
+
supportsNumericRange: false,
|
|
26
|
+
formatBoolean: (v) => (v ? 't' : 'f'),
|
|
27
|
+
identifier: shortIdentifier,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Bracketed delimiters are literal text, not regex. xAPI parses them the
|
|
32
|
+
* same way.
|
|
33
|
+
*/
|
|
34
|
+
export const SCORM2004_INTERACTION_FORMAT: InteractionFormat = {
|
|
35
|
+
itemDelim: '[,]',
|
|
36
|
+
pairDelim: '[.]',
|
|
37
|
+
rangeDelim: '[:]',
|
|
38
|
+
supportsNumericRange: true,
|
|
39
|
+
formatBoolean: (v) => (v ? 'true' : 'false'),
|
|
40
|
+
identifier: shortIdentifier,
|
|
41
|
+
};
|
|
42
|
+
|
|
13
43
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
44
|
+
* SCORM `short_identifier_type` / `CMIIdentifier`: alphanumerics +
|
|
45
|
+
* underscore, max 250 chars. Strict validators (SCORM Cloud) reject raw
|
|
46
|
+
* option labels with spaces or punctuation with error 405/406.
|
|
16
47
|
*/
|
|
17
|
-
|
|
48
|
+
function shortIdentifier(value: string): string {
|
|
49
|
+
const cleaned = value.replace(/[^A-Za-z0-9]+/g, '_').replace(/^_+|_+$/g, '');
|
|
50
|
+
const trimmed = cleaned.slice(0, 250);
|
|
51
|
+
return trimmed || '_';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function formatResponse(
|
|
55
|
+
i: Interaction,
|
|
56
|
+
fmt: InteractionFormat = SCORM2004_INTERACTION_FORMAT
|
|
57
|
+
): string {
|
|
18
58
|
switch (i.type) {
|
|
19
59
|
case 'choice':
|
|
20
60
|
case 'sequencing':
|
|
21
|
-
return i.response.join(
|
|
61
|
+
return i.response.map(fmt.identifier).join(fmt.itemDelim);
|
|
22
62
|
case 'true-false':
|
|
23
|
-
return i.response
|
|
63
|
+
return fmt.formatBoolean(i.response);
|
|
24
64
|
case 'fill-in':
|
|
25
65
|
case 'long-fill-in':
|
|
26
66
|
case 'likert':
|
|
27
67
|
case 'other':
|
|
28
68
|
return i.response;
|
|
29
69
|
case 'matching':
|
|
30
|
-
return i.response
|
|
70
|
+
return i.response
|
|
71
|
+
.map(([l, r]) => `${fmt.identifier(l)}${fmt.pairDelim}${fmt.identifier(r)}`)
|
|
72
|
+
.join(fmt.itemDelim);
|
|
31
73
|
case 'numeric':
|
|
32
74
|
return String(i.response);
|
|
33
75
|
case 'performance':
|
|
34
|
-
return i.response
|
|
76
|
+
return i.response
|
|
77
|
+
.map(([s, v]) => `${fmt.identifier(s)}${fmt.pairDelim}${fmt.identifier(String(v))}`)
|
|
78
|
+
.join(fmt.itemDelim);
|
|
35
79
|
}
|
|
36
80
|
}
|
|
37
81
|
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
82
|
+
/** Returns null when no correct pattern was provided. */
|
|
83
|
+
export function formatCorrectPattern(
|
|
84
|
+
i: Interaction,
|
|
85
|
+
fmt: InteractionFormat = SCORM2004_INTERACTION_FORMAT
|
|
86
|
+
): string | null {
|
|
43
87
|
if (i.correct === undefined) return null;
|
|
44
88
|
switch (i.type) {
|
|
45
89
|
case 'choice':
|
|
46
90
|
case 'sequencing':
|
|
47
|
-
return (i.correct as string[]).join(
|
|
91
|
+
return (i.correct as string[]).map(fmt.identifier).join(fmt.itemDelim);
|
|
48
92
|
case 'true-false':
|
|
49
|
-
return (i.correct as boolean)
|
|
93
|
+
return fmt.formatBoolean(i.correct as boolean);
|
|
50
94
|
case 'fill-in':
|
|
51
95
|
case 'long-fill-in':
|
|
52
|
-
|
|
53
|
-
return (i.correct as string[]).join('[,]');
|
|
96
|
+
return (i.correct as string[]).join(fmt.itemDelim);
|
|
54
97
|
case 'matching':
|
|
55
|
-
return (i.correct as Array<[string, string]>)
|
|
98
|
+
return (i.correct as Array<[string, string]>)
|
|
99
|
+
.map(([l, r]) => `${fmt.identifier(l)}${fmt.pairDelim}${fmt.identifier(r)}`)
|
|
100
|
+
.join(fmt.itemDelim);
|
|
56
101
|
case 'numeric': {
|
|
57
102
|
const c = i.correct as { min?: number; max?: number };
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
103
|
+
if (c.min !== undefined && c.max !== undefined && c.min === c.max) {
|
|
104
|
+
return String(c.min);
|
|
105
|
+
}
|
|
106
|
+
if (c.min !== undefined && c.max === undefined) return String(c.min);
|
|
107
|
+
if (c.min === undefined && c.max !== undefined) return String(c.max);
|
|
108
|
+
// True range — drop the pattern in 1.2 (rely on `result` for pass/fail).
|
|
109
|
+
if (!fmt.supportsNumericRange) return null;
|
|
110
|
+
return `${c.min ?? ''}${fmt.rangeDelim}${c.max ?? ''}`;
|
|
61
111
|
}
|
|
62
112
|
case 'likert':
|
|
63
113
|
case 'other':
|
|
64
114
|
return i.correct as string;
|
|
65
115
|
case 'performance':
|
|
66
116
|
return (i.correct as Array<[string, string | number]>)
|
|
67
|
-
.map(([s, v]) => `${s}
|
|
68
|
-
.join(
|
|
117
|
+
.map(([s, v]) => `${fmt.identifier(s)}${fmt.pairDelim}${fmt.identifier(String(v))}`)
|
|
118
|
+
.join(fmt.itemDelim);
|
|
69
119
|
}
|
|
70
120
|
}
|
|
71
121
|
|
|
72
|
-
/**
|
|
73
|
-
* Map Tessera interaction types to SCORM 1.2's narrower vocabulary. SCORM 1.2
|
|
74
|
-
* does not define `long-fill-in`; fall back to `fill-in`. `other` is not in
|
|
75
|
-
* the spec either — fall back to `fill-in` (free text).
|
|
76
|
-
*/
|
|
122
|
+
/** SCORM 1.2 has no `long-fill-in` or `other` — both fall back to `fill-in`. */
|
|
77
123
|
export function scorm12Type(type: Interaction['type']): string {
|
|
78
124
|
switch (type) {
|
|
79
125
|
case 'long-fill-in':
|
|
@@ -85,26 +131,15 @@ export function scorm12Type(type: Interaction['type']): string {
|
|
|
85
131
|
}
|
|
86
132
|
}
|
|
87
133
|
|
|
88
|
-
/**
|
|
89
|
-
* Per-standard differences in how `cmi.interactions.n.*` is written. The
|
|
90
|
-
* SCORM 1.2 vs 2004 deltas are: response field name, result vocabulary,
|
|
91
|
-
* timestamp field name+format, and the type vocabulary mapping.
|
|
92
|
-
*/
|
|
93
134
|
export interface ScormInteractionSpec {
|
|
94
135
|
responseField: 'student_response' | 'learner_response';
|
|
95
136
|
timestampField: 'time' | 'timestamp';
|
|
96
|
-
/** Wall-clock value formatted to whichever style the standard expects. */
|
|
97
137
|
timestamp: string;
|
|
98
|
-
/** Mapped interaction type — already narrowed for SCORM 1.2 callers. */
|
|
99
138
|
typeValue: string;
|
|
100
139
|
resultLabels: { correct: string; incorrect: string };
|
|
140
|
+
format: InteractionFormat;
|
|
101
141
|
}
|
|
102
142
|
|
|
103
|
-
/**
|
|
104
|
-
* Build the ordered list of `cmi.interactions.n.*` writes that SCORM 1.2 and
|
|
105
|
-
* SCORM 2004 adapters share. Caller wires each pair through its own LMS
|
|
106
|
-
* SetValue queue (the queueing semantics differ between adapters).
|
|
107
|
-
*/
|
|
108
143
|
export function buildScormInteractionFields(
|
|
109
144
|
prefix: string,
|
|
110
145
|
questionId: string,
|
|
@@ -115,9 +150,9 @@ export function buildScormInteractionFields(
|
|
|
115
150
|
const fields: Array<[string, string]> = [
|
|
116
151
|
[`${prefix}.id`, questionId],
|
|
117
152
|
[`${prefix}.type`, spec.typeValue],
|
|
118
|
-
[`${prefix}.${spec.responseField}`, formatResponse(interaction)],
|
|
153
|
+
[`${prefix}.${spec.responseField}`, formatResponse(interaction, spec.format)],
|
|
119
154
|
];
|
|
120
|
-
const pattern = formatCorrectPattern(interaction);
|
|
155
|
+
const pattern = formatCorrectPattern(interaction, spec.format);
|
|
121
156
|
if (pattern !== null) {
|
|
122
157
|
fields.push([`${prefix}.correct_responses.0.pattern`, pattern]);
|
|
123
158
|
}
|