@intellectif/lk-core 0.5.0 → 0.7.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/README.md +12 -4
- package/dist/{activity-wkzRemHx.d.cts → activity-gelWNZ6V.d.cts} +49 -10
- package/dist/{activity-wkzRemHx.d.ts → activity-gelWNZ6V.d.ts} +49 -10
- package/dist/{chunk-WV5WQ3SZ.js → chunk-2SQ75JTE.js} +2 -2
- package/dist/{chunk-4JR3UXX6.cjs → chunk-5RE3ZRSA.cjs} +8 -1
- package/dist/chunk-5RE3ZRSA.cjs.map +1 -0
- package/dist/{chunk-FCM5VJBS.cjs → chunk-7NIH5IL3.cjs} +16 -13
- package/dist/chunk-7NIH5IL3.cjs.map +1 -0
- package/dist/{chunk-R7PDJBRX.cjs → chunk-AFIQQADJ.cjs} +4 -4
- package/dist/{chunk-R7PDJBRX.cjs.map → chunk-AFIQQADJ.cjs.map} +1 -1
- package/dist/{chunk-SM5HUGYU.cjs → chunk-MVZKEWZN.cjs} +21 -11
- package/dist/chunk-MVZKEWZN.cjs.map +1 -0
- package/dist/{chunk-DUQVGLQ3.js → chunk-NOBJYDOB.js} +15 -5
- package/dist/chunk-NOBJYDOB.js.map +1 -0
- package/dist/{chunk-LSHDNA2T.js → chunk-XL75ZZVM.js} +6 -3
- package/dist/chunk-XL75ZZVM.js.map +1 -0
- package/dist/{chunk-NUCEUU4P.js → chunk-YCXIHFLG.js} +8 -1
- package/dist/chunk-YCXIHFLG.js.map +1 -0
- package/dist/{index-BKyZrd94.d.cts → index-B_RLyeEj.d.ts} +101 -14
- package/dist/{index-CLmXzBhB.d.ts → index-CDWH2WIg.d.cts} +101 -14
- package/dist/index.cjs +336 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +360 -6
- package/dist/index.d.ts +360 -6
- package/dist/index.js +317 -6
- package/dist/index.js.map +1 -1
- package/dist/schemas.cjs +3 -3
- package/dist/schemas.d.cts +2 -2
- package/dist/schemas.d.ts +2 -2
- package/dist/schemas.js +2 -2
- package/dist/scoring.cjs +3 -3
- package/dist/scoring.d.cts +2 -2
- package/dist/scoring.d.ts +2 -2
- package/dist/scoring.js +2 -2
- package/dist/xapi.cjs +3 -3
- package/dist/xapi.d.cts +1 -1
- package/dist/xapi.d.ts +1 -1
- package/dist/xapi.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-4JR3UXX6.cjs.map +0 -1
- package/dist/chunk-DUQVGLQ3.js.map +0 -1
- package/dist/chunk-FCM5VJBS.cjs.map +0 -1
- package/dist/chunk-LSHDNA2T.js.map +0 -1
- package/dist/chunk-NUCEUU4P.js.map +0 -1
- package/dist/chunk-SM5HUGYU.cjs.map +0 -1
- /package/dist/{chunk-WV5WQ3SZ.js.map → chunk-2SQ75JTE.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
var
|
|
13
|
+
var _chunkMVZKEWZNcjs = require('./chunk-MVZKEWZN.cjs');
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
var
|
|
20
|
+
var _chunkAFIQQADJcjs = require('./chunk-AFIQQADJ.cjs');
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
|
|
@@ -31,7 +31,7 @@ var _chunkR7PDJBRXcjs = require('./chunk-R7PDJBRX.cjs');
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
var
|
|
34
|
+
var _chunk7NIH5IL3cjs = require('./chunk-7NIH5IL3.cjs');
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
|
|
@@ -63,7 +63,63 @@ var _chunkFCM5VJBScjs = require('./chunk-FCM5VJBS.cjs');
|
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
|
|
66
|
-
var
|
|
66
|
+
var _chunk5RE3ZRSAcjs = require('./chunk-5RE3ZRSA.cjs');
|
|
67
|
+
|
|
68
|
+
// src/content-hash.ts
|
|
69
|
+
function canonicalJson(value, seen = /* @__PURE__ */ new Set()) {
|
|
70
|
+
if (value === null) {
|
|
71
|
+
return "null";
|
|
72
|
+
}
|
|
73
|
+
if (typeof value === "number") {
|
|
74
|
+
return Number.isFinite(value) ? JSON.stringify(value === 0 ? 0 : value) : `"#${String(value)}"`;
|
|
75
|
+
}
|
|
76
|
+
if (typeof value === "string" || typeof value === "boolean") {
|
|
77
|
+
return JSON.stringify(value);
|
|
78
|
+
}
|
|
79
|
+
if (typeof value === "bigint") {
|
|
80
|
+
return `"#${value.toString()}n"`;
|
|
81
|
+
}
|
|
82
|
+
if (typeof value === "object") {
|
|
83
|
+
if (seen.has(value)) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
"canonicalJson: the value contains a circular reference and cannot be fingerprinted."
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
seen.add(value);
|
|
89
|
+
try {
|
|
90
|
+
if (Array.isArray(value)) {
|
|
91
|
+
return `[${value.map((element) => element === void 0 ? "null" : canonicalJson(element, seen)).join(",")}]`;
|
|
92
|
+
}
|
|
93
|
+
const source = value;
|
|
94
|
+
const parts = [];
|
|
95
|
+
for (const key of Object.keys(source).sort()) {
|
|
96
|
+
const entry = source[key];
|
|
97
|
+
if (entry === void 0) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
parts.push(`${JSON.stringify(key)}:${canonicalJson(entry, seen)}`);
|
|
101
|
+
}
|
|
102
|
+
return `{${parts.join(",")}}`;
|
|
103
|
+
} finally {
|
|
104
|
+
seen.delete(value);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return "null";
|
|
108
|
+
}
|
|
109
|
+
var FNV_OFFSET = 14695981039346656037n;
|
|
110
|
+
var FNV_PRIME = 1099511628211n;
|
|
111
|
+
var MASK64 = (1n << 64n) - 1n;
|
|
112
|
+
function fingerprint(input) {
|
|
113
|
+
const bytes = new TextEncoder().encode(input);
|
|
114
|
+
let hash = FNV_OFFSET;
|
|
115
|
+
for (const byte of bytes) {
|
|
116
|
+
hash = (hash ^ BigInt(byte)) * FNV_PRIME & MASK64;
|
|
117
|
+
}
|
|
118
|
+
return hash.toString(16).padStart(16, "0");
|
|
119
|
+
}
|
|
120
|
+
function contentHash(value) {
|
|
121
|
+
return fingerprint(canonicalJson(value));
|
|
122
|
+
}
|
|
67
123
|
|
|
68
124
|
// src/shuffle.ts
|
|
69
125
|
function hashSeed(input) {
|
|
@@ -92,6 +148,23 @@ function seededShuffle(items, seed, options = {}) {
|
|
|
92
148
|
function isItemGroup(entry) {
|
|
93
149
|
return entry.type === "item-group";
|
|
94
150
|
}
|
|
151
|
+
function keyOf(entry) {
|
|
152
|
+
const key = entry.slotKey;
|
|
153
|
+
if (key === void 0) {
|
|
154
|
+
return void 0;
|
|
155
|
+
}
|
|
156
|
+
if (typeof key !== "string" || key.length === 0) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`flattenSequence: slotKey must be a non-empty string, received ${String(key)}.`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
if (key.includes(".")) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
`flattenSequence: slotKey "${key}" contains a "." , which separates a group from its item in a slot id. Choose a key without it, or the two become indistinguishable.`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return key;
|
|
167
|
+
}
|
|
95
168
|
function flattenSequence(entries, options = {}) {
|
|
96
169
|
const shuffleEntries = options.shuffleEntries === true;
|
|
97
170
|
const needsSeed = shuffleEntries || entries.some((entry) => isItemGroup(entry) && entry.shuffle === "within-group");
|
|
@@ -105,8 +178,9 @@ function flattenSequence(entries, options = {}) {
|
|
|
105
178
|
const ordered = shuffleEntries ? seededShuffle(authored, `${seed}:entries`) : authored;
|
|
106
179
|
const slots = [];
|
|
107
180
|
for (const { entry, entryIndex } of ordered) {
|
|
181
|
+
const entryKey = _nullishCoalesce(keyOf(entry), () => ( String(entryIndex)));
|
|
108
182
|
if (!isItemGroup(entry)) {
|
|
109
|
-
slots.push({ slotId:
|
|
183
|
+
slots.push({ slotId: entryKey, index: slots.length, activity: entry });
|
|
110
184
|
continue;
|
|
111
185
|
}
|
|
112
186
|
if (entry.items.length === 0) {
|
|
@@ -119,7 +193,7 @@ function flattenSequence(entries, options = {}) {
|
|
|
119
193
|
const size = presented.length;
|
|
120
194
|
presented.forEach(({ item, itemIndex }, position) => {
|
|
121
195
|
slots.push({
|
|
122
|
-
slotId: `${
|
|
196
|
+
slotId: `${entryKey}.${_nullishCoalesce(keyOf(item), () => ( String(itemIndex)))}`,
|
|
123
197
|
index: slots.length,
|
|
124
198
|
activity: item,
|
|
125
199
|
group: {
|
|
@@ -132,9 +206,234 @@ function flattenSequence(entries, options = {}) {
|
|
|
132
206
|
});
|
|
133
207
|
});
|
|
134
208
|
}
|
|
209
|
+
const seenSlotIds = /* @__PURE__ */ new Set();
|
|
210
|
+
const seenEntryKeys = /* @__PURE__ */ new Set();
|
|
211
|
+
for (const slot of slots) {
|
|
212
|
+
if (seenSlotIds.has(slot.slotId)) {
|
|
213
|
+
throw new Error(
|
|
214
|
+
`flattenSequence: duplicate slot id "${slot.slotId}". Two entries declare the same slotKey, so responses stored against them could not be told apart.`
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
seenSlotIds.add(slot.slotId);
|
|
218
|
+
}
|
|
219
|
+
for (const { entry, entryIndex } of ordered) {
|
|
220
|
+
const entryKey = _nullishCoalesce(keyOf(entry), () => ( String(entryIndex)));
|
|
221
|
+
if (seenEntryKeys.has(entryKey)) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
`flattenSequence: duplicate entry key "${entryKey}". A loose activity and an item group cannot share a slotKey \u2014 their slot ids would not collide, but everything that reads the entry they belong to would treat them as one entry.`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
seenEntryKeys.add(entryKey);
|
|
227
|
+
}
|
|
135
228
|
return slots;
|
|
136
229
|
}
|
|
137
230
|
|
|
231
|
+
// src/attempt-plan.ts
|
|
232
|
+
function contentOf(activity) {
|
|
233
|
+
if (!Object.hasOwn(activity, "slotKey")) {
|
|
234
|
+
return activity;
|
|
235
|
+
}
|
|
236
|
+
const { slotKey: _slotKey, ...content } = activity;
|
|
237
|
+
return content;
|
|
238
|
+
}
|
|
239
|
+
function planSlot(slot, points) {
|
|
240
|
+
if (!Number.isFinite(points) || points < 0) {
|
|
241
|
+
throw new Error(
|
|
242
|
+
`planAttempt: slot "${slot.slotId}" resolved to ${String(points)} points. Points must be a finite, non-negative number, or the paper has no defensible total.`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
slotId: slot.slotId,
|
|
247
|
+
index: slot.index,
|
|
248
|
+
activityId: slot.activity.id,
|
|
249
|
+
activityType: slot.activity.type,
|
|
250
|
+
points,
|
|
251
|
+
contentHash: contentHash(contentOf(slot.activity)),
|
|
252
|
+
...slot.group !== void 0 ? {
|
|
253
|
+
group: {
|
|
254
|
+
id: slot.group.id,
|
|
255
|
+
...slot.group.title !== void 0 ? { title: slot.group.title } : {},
|
|
256
|
+
stimulusHash: contentHash(slot.group.stimulus)
|
|
257
|
+
}
|
|
258
|
+
} : {}
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function planAttempt(entries, options = {}) {
|
|
262
|
+
const { seed, shuffleEntries, points } = options;
|
|
263
|
+
const slots = flattenSequence(entries, {
|
|
264
|
+
...shuffleEntries !== void 0 ? { shuffleEntries } : {},
|
|
265
|
+
...seed !== void 0 ? { seed } : {}
|
|
266
|
+
});
|
|
267
|
+
const planned = slots.map((slot) => planSlot(slot, _nullishCoalesce(_optionalChain([points, 'optionalCall', _ => _(slot)]), () => ( 1))));
|
|
268
|
+
const totalPoints = planned.reduce((sum, slot) => sum + slot.points, 0);
|
|
269
|
+
return {
|
|
270
|
+
planVersion: "1.0",
|
|
271
|
+
...seed !== void 0 ? { seed } : {},
|
|
272
|
+
...shuffleEntries !== void 0 ? { shuffleEntries } : {},
|
|
273
|
+
// The plan's own fingerprint covers the slots verbatim — identity, order,
|
|
274
|
+
// points and content hashes — so one stored value answers "is this still
|
|
275
|
+
// the paper that was sat?" and the per-slot hashes then say what moved.
|
|
276
|
+
planHash: contentHash(planned),
|
|
277
|
+
slots: planned,
|
|
278
|
+
totalPoints
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function verifyAttemptPlan(plan, current) {
|
|
282
|
+
const before = new Map(plan.slots.map((slot) => [slot.slotId, slot]));
|
|
283
|
+
const after = new Map(current.slots.map((slot) => [slot.slotId, slot]));
|
|
284
|
+
const missingSlotIds = plan.slots.filter((s) => !after.has(s.slotId)).map((s) => s.slotId);
|
|
285
|
+
const addedSlotIds = current.slots.filter((s) => !before.has(s.slotId)).map((s) => s.slotId);
|
|
286
|
+
const changedSlotIds = [];
|
|
287
|
+
const changedStimulusSlotIds = [];
|
|
288
|
+
const changedPointsSlotIds = [];
|
|
289
|
+
const reorderedSlotIds = [];
|
|
290
|
+
for (const slot of plan.slots) {
|
|
291
|
+
const now = after.get(slot.slotId);
|
|
292
|
+
if (now === void 0) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (now.contentHash !== slot.contentHash) {
|
|
296
|
+
changedSlotIds.push(slot.slotId);
|
|
297
|
+
}
|
|
298
|
+
if (_optionalChain([now, 'access', _2 => _2.group, 'optionalAccess', _3 => _3.stimulusHash]) !== _optionalChain([slot, 'access', _4 => _4.group, 'optionalAccess', _5 => _5.stimulusHash])) {
|
|
299
|
+
changedStimulusSlotIds.push(slot.slotId);
|
|
300
|
+
}
|
|
301
|
+
if (now.points !== slot.points) {
|
|
302
|
+
changedPointsSlotIds.push(slot.slotId);
|
|
303
|
+
}
|
|
304
|
+
if (now.index !== slot.index) {
|
|
305
|
+
reorderedSlotIds.push(slot.slotId);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
matches: missingSlotIds.length === 0 && addedSlotIds.length === 0 && changedSlotIds.length === 0 && changedStimulusSlotIds.length === 0 && changedPointsSlotIds.length === 0 && reorderedSlotIds.length === 0,
|
|
310
|
+
missingSlotIds,
|
|
311
|
+
addedSlotIds,
|
|
312
|
+
changedSlotIds,
|
|
313
|
+
changedStimulusSlotIds,
|
|
314
|
+
changedPointsSlotIds,
|
|
315
|
+
reorderedSlotIds
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
function missingOutcome(slot, policy) {
|
|
319
|
+
if (typeof policy === "function") {
|
|
320
|
+
return policy(slot);
|
|
321
|
+
}
|
|
322
|
+
if (policy === "zero") {
|
|
323
|
+
return { status: "scored", score: 0, maxScore: 1, passed: false, feedback: null, details: [] };
|
|
324
|
+
}
|
|
325
|
+
return { status: "deferred", reason: "no_response_recorded", maxScore: 1 };
|
|
326
|
+
}
|
|
327
|
+
function scoredItemsFromPlan(plan, outcomes, options = {}) {
|
|
328
|
+
const policy = _nullishCoalesce(options.missing, () => ( "deferred"));
|
|
329
|
+
return plan.slots.map((slot) => ({
|
|
330
|
+
slotId: slot.slotId,
|
|
331
|
+
activityId: slot.activityId,
|
|
332
|
+
points: slot.points,
|
|
333
|
+
// `Object.hasOwn`, not a bare lookup: slot ids come from authored
|
|
334
|
+
// `slotKey`s, and a key of `constructor` or `toString` would otherwise
|
|
335
|
+
// resolve through the prototype chain and hand a FUNCTION to the scorer
|
|
336
|
+
// in place of an outcome.
|
|
337
|
+
outcome: Object.hasOwn(outcomes, slot.slotId) ? outcomes[slot.slotId] : missingOutcome(slot, policy)
|
|
338
|
+
}));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// src/attempt-state.ts
|
|
342
|
+
function cloneResponses(responses) {
|
|
343
|
+
return typeof structuredClone === "function" ? structuredClone(responses) : JSON.parse(JSON.stringify(responses));
|
|
344
|
+
}
|
|
345
|
+
function unknownKeys(plan, keys) {
|
|
346
|
+
const known = new Set(plan.slots.map((slot) => slot.slotId));
|
|
347
|
+
return keys.filter((key) => !known.has(key));
|
|
348
|
+
}
|
|
349
|
+
function serializeAttemptState(plan, progress) {
|
|
350
|
+
const responseKeys = Object.keys(progress.responses);
|
|
351
|
+
const submittedSlotIds = [..._nullishCoalesce(progress.submittedSlotIds, () => ( []))];
|
|
352
|
+
const strayResponses = unknownKeys(plan, responseKeys);
|
|
353
|
+
if (strayResponses.length > 0) {
|
|
354
|
+
throw new Error(
|
|
355
|
+
`serializeAttemptState: response recorded for slot(s) the plan does not contain: ${strayResponses.join(", ")}. A response that belongs to no question cannot be scored and would be lost silently.`
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
const straySubmitted = unknownKeys(plan, submittedSlotIds);
|
|
359
|
+
if (straySubmitted.length > 0) {
|
|
360
|
+
throw new Error(
|
|
361
|
+
`serializeAttemptState: submitted slot(s) the plan does not contain: ${straySubmitted.join(", ")}.`
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
const index = _nullishCoalesce(progress.index, () => ( 0));
|
|
365
|
+
const lastIndex = Math.max(0, plan.slots.length - 1);
|
|
366
|
+
if (!Number.isInteger(index) || index < 0 || index > lastIndex) {
|
|
367
|
+
throw new Error(
|
|
368
|
+
`serializeAttemptState: index ${String(index)} is not a position in a ${plan.slots.length}-slot plan.`
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
return {
|
|
372
|
+
stateVersion: "1.0",
|
|
373
|
+
planHash: plan.planHash,
|
|
374
|
+
// Copied DEEPLY, not aliased: a snapshot that keeps mutating with the
|
|
375
|
+
// live attempt is not a snapshot. See cloneResponses.
|
|
376
|
+
responses: cloneResponses(progress.responses),
|
|
377
|
+
submittedSlotIds,
|
|
378
|
+
index,
|
|
379
|
+
...progress.savedAt !== void 0 ? { savedAt: progress.savedAt } : {}
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function restoreAttemptState(plan, state) {
|
|
383
|
+
if (state === null || typeof state !== "object") {
|
|
384
|
+
throw new Error("restoreAttemptState: the snapshot is not an object.");
|
|
385
|
+
}
|
|
386
|
+
if (state.stateVersion !== "1.0") {
|
|
387
|
+
throw new Error(
|
|
388
|
+
`restoreAttemptState: unsupported stateVersion ${JSON.stringify(state.stateVersion)}. This build understands "1.0"; a newer snapshot must be migrated before it is restored.`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
if (state.responses === null || typeof state.responses !== "object") {
|
|
392
|
+
throw new Error(
|
|
393
|
+
"restoreAttemptState: the snapshot carries no responses object (a NULL column, or a partially written row)."
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
if (state.planHash !== plan.planHash) {
|
|
397
|
+
throw new Error(
|
|
398
|
+
`restoreAttemptState: this snapshot belongs to a different paper (snapshot ${state.planHash}, plan ${plan.planHash}). Restoring it would attach the learner's answers to questions they never saw.`
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
return serializeAttemptState(plan, {
|
|
402
|
+
responses: state.responses,
|
|
403
|
+
submittedSlotIds: state.submittedSlotIds,
|
|
404
|
+
index: state.index,
|
|
405
|
+
...state.savedAt !== void 0 ? { savedAt: state.savedAt } : {}
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
function diffResponses(before, after) {
|
|
409
|
+
const slotIds = [.../* @__PURE__ */ new Set([...Object.keys(before.responses), ...Object.keys(after.responses)])];
|
|
410
|
+
slotIds.sort();
|
|
411
|
+
const entries = [];
|
|
412
|
+
for (const slotId of slotIds) {
|
|
413
|
+
const had = Object.hasOwn(before.responses, slotId);
|
|
414
|
+
const has = Object.hasOwn(after.responses, slotId);
|
|
415
|
+
const from = before.responses[slotId];
|
|
416
|
+
const to = after.responses[slotId];
|
|
417
|
+
if (had && !has) {
|
|
418
|
+
entries.push({ slotId, change: "removed", ...from !== void 0 ? { before: from } : {} });
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
if (!had && has) {
|
|
422
|
+
entries.push({ slotId, change: "added", ...to !== void 0 ? { after: to } : {} });
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
if (canonicalJson(from) !== canonicalJson(to)) {
|
|
426
|
+
entries.push({
|
|
427
|
+
slotId,
|
|
428
|
+
change: "changed",
|
|
429
|
+
...from !== void 0 ? { before: from } : {},
|
|
430
|
+
...to !== void 0 ? { after: to } : {}
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return entries;
|
|
435
|
+
}
|
|
436
|
+
|
|
138
437
|
// src/redact.ts
|
|
139
438
|
function mergePolicy(base, overrides) {
|
|
140
439
|
if (overrides === void 0) {
|
|
@@ -188,9 +487,9 @@ function redactValue(value, policy, reveal) {
|
|
|
188
487
|
}
|
|
189
488
|
function redact(data, options = {}) {
|
|
190
489
|
const reveal = _nullishCoalesce(options.reveal, () => ( "none"));
|
|
191
|
-
const descriptor =
|
|
490
|
+
const descriptor = _chunk5RE3ZRSAcjs.getActivityTypeDescriptor.call(void 0, data.type);
|
|
192
491
|
if (descriptor === void 0) {
|
|
193
|
-
throw new (0,
|
|
492
|
+
throw new (0, _chunk5RE3ZRSAcjs.UnknownActivityTypeError)(String(data.type));
|
|
194
493
|
}
|
|
195
494
|
if (descriptor.fieldPolicy === void 0) {
|
|
196
495
|
throw new Error(
|
|
@@ -203,7 +502,7 @@ function redact(data, options = {}) {
|
|
|
203
502
|
if (reveal === "none" && descriptor.redactedSchema !== void 0) {
|
|
204
503
|
const parsed = descriptor.redactedSchema.safeParse(result);
|
|
205
504
|
if (!parsed.success) {
|
|
206
|
-
throw new (0,
|
|
505
|
+
throw new (0, _chunk5RE3ZRSAcjs.ActivitySchemaError)(
|
|
207
506
|
descriptor.type,
|
|
208
507
|
parsed.error.issues.map((issue) => ({
|
|
209
508
|
path: issue.path.map(String),
|
|
@@ -217,13 +516,13 @@ function redact(data, options = {}) {
|
|
|
217
516
|
}
|
|
218
517
|
function assertRedacted(data) {
|
|
219
518
|
if (typeof data !== "object" || data === null) {
|
|
220
|
-
throw new (0,
|
|
519
|
+
throw new (0, _chunk5RE3ZRSAcjs.ActivitySchemaError)("unknown", [
|
|
221
520
|
{ path: [], message: "Redacted activity data must be an object.", code: "invalid_type" }
|
|
222
521
|
]);
|
|
223
522
|
}
|
|
224
523
|
const candidate = data;
|
|
225
524
|
if (candidate.redacted !== true) {
|
|
226
|
-
throw new (0,
|
|
525
|
+
throw new (0, _chunk5RE3ZRSAcjs.ActivitySchemaError)(String(_nullishCoalesce(candidate.type, () => ( "unknown"))), [
|
|
227
526
|
{
|
|
228
527
|
path: ["redacted"],
|
|
229
528
|
message: "Missing redacted marker \u2014 this payload is not a redact() projection.",
|
|
@@ -232,12 +531,12 @@ function assertRedacted(data) {
|
|
|
232
531
|
]);
|
|
233
532
|
}
|
|
234
533
|
const type = typeof candidate.type === "string" ? candidate.type : "";
|
|
235
|
-
const descriptor =
|
|
534
|
+
const descriptor = _chunk5RE3ZRSAcjs.getActivityTypeDescriptor.call(void 0, type);
|
|
236
535
|
if (descriptor === void 0) {
|
|
237
|
-
throw new (0,
|
|
536
|
+
throw new (0, _chunk5RE3ZRSAcjs.UnknownActivityTypeError)(type);
|
|
238
537
|
}
|
|
239
538
|
if (descriptor.redactedSchema === void 0) {
|
|
240
|
-
throw new (0,
|
|
539
|
+
throw new (0, _chunk5RE3ZRSAcjs.ActivitySchemaError)(descriptor.type, [
|
|
241
540
|
{
|
|
242
541
|
path: [],
|
|
243
542
|
message: `Activity type "${descriptor.type}" registers no redactedSchema; assertRedacted cannot prove this payload is learner-safe.`,
|
|
@@ -247,7 +546,7 @@ function assertRedacted(data) {
|
|
|
247
546
|
}
|
|
248
547
|
const parsed = descriptor.redactedSchema.safeParse(candidate);
|
|
249
548
|
if (!parsed.success) {
|
|
250
|
-
throw new (0,
|
|
549
|
+
throw new (0, _chunk5RE3ZRSAcjs.ActivitySchemaError)(
|
|
251
550
|
descriptor.type,
|
|
252
551
|
parsed.error.issues.map((issue) => ({
|
|
253
552
|
path: issue.path.map(String),
|
|
@@ -273,6 +572,9 @@ var ITEM_GROUP_FIELD_POLICY = {
|
|
|
273
572
|
type: "public",
|
|
274
573
|
id: "public",
|
|
275
574
|
title: "public",
|
|
575
|
+
// See the note on the built-in activity policies: a slot key is identity,
|
|
576
|
+
// and identity has to cross the redaction boundary intact.
|
|
577
|
+
slotKey: "public",
|
|
276
578
|
shuffle: "public",
|
|
277
579
|
stimulus: STIMULUS_FIELD_POLICY
|
|
278
580
|
};
|
|
@@ -286,9 +588,9 @@ function redactItemGroup(group, options = {}) {
|
|
|
286
588
|
redacted: true
|
|
287
589
|
};
|
|
288
590
|
if (reveal === "none") {
|
|
289
|
-
const parsed =
|
|
591
|
+
const parsed = _chunk7NIH5IL3cjs.RedactedItemGroupSchema.safeParse(result);
|
|
290
592
|
if (!parsed.success) {
|
|
291
|
-
throw new (0,
|
|
593
|
+
throw new (0, _chunk5RE3ZRSAcjs.ActivitySchemaError)(
|
|
292
594
|
"item-group",
|
|
293
595
|
parsed.error.issues.map((issue) => ({
|
|
294
596
|
path: issue.path.map(String),
|
|
@@ -302,13 +604,13 @@ function redactItemGroup(group, options = {}) {
|
|
|
302
604
|
}
|
|
303
605
|
function assertRedactedItemGroup(data) {
|
|
304
606
|
if (typeof data !== "object" || data === null) {
|
|
305
|
-
throw new (0,
|
|
607
|
+
throw new (0, _chunk5RE3ZRSAcjs.ActivitySchemaError)("item-group", [
|
|
306
608
|
{ path: [], message: "Redacted item group must be an object.", code: "invalid_type" }
|
|
307
609
|
]);
|
|
308
610
|
}
|
|
309
611
|
const candidate = data;
|
|
310
612
|
if (candidate.redacted !== true) {
|
|
311
|
-
throw new (0,
|
|
613
|
+
throw new (0, _chunk5RE3ZRSAcjs.ActivitySchemaError)("item-group", [
|
|
312
614
|
{
|
|
313
615
|
path: ["redacted"],
|
|
314
616
|
message: "Missing redacted marker \u2014 this payload is not a redactItemGroup() projection.",
|
|
@@ -316,9 +618,9 @@ function assertRedactedItemGroup(data) {
|
|
|
316
618
|
}
|
|
317
619
|
]);
|
|
318
620
|
}
|
|
319
|
-
const parsed =
|
|
621
|
+
const parsed = _chunk7NIH5IL3cjs.RedactedItemGroupSchema.safeParse(candidate);
|
|
320
622
|
if (!parsed.success) {
|
|
321
|
-
throw new (0,
|
|
623
|
+
throw new (0, _chunk5RE3ZRSAcjs.ActivitySchemaError)(
|
|
322
624
|
"item-group",
|
|
323
625
|
parsed.error.issues.map((issue) => ({
|
|
324
626
|
path: issue.path.map(String),
|
|
@@ -331,8 +633,8 @@ function assertRedactedItemGroup(data) {
|
|
|
331
633
|
try {
|
|
332
634
|
assertRedacted(item);
|
|
333
635
|
} catch (error) {
|
|
334
|
-
if (error instanceof
|
|
335
|
-
throw new (0,
|
|
636
|
+
if (error instanceof _chunk5RE3ZRSAcjs.ActivitySchemaError) {
|
|
637
|
+
throw new (0, _chunk5RE3ZRSAcjs.ActivitySchemaError)(
|
|
336
638
|
"item-group",
|
|
337
639
|
error.errors.map((issue) => ({
|
|
338
640
|
...issue,
|
|
@@ -411,5 +713,14 @@ function assertRedactedItemGroup(data) {
|
|
|
411
713
|
|
|
412
714
|
|
|
413
715
|
|
|
414
|
-
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
exports.ActivitySchemaError = _chunk5RE3ZRSAcjs.ActivitySchemaError; exports.BlankConfigSchema = _chunk5RE3ZRSAcjs.BlankConfigSchema; exports.DEFAULT_PASS_THRESHOLD = _chunkMVZKEWZNcjs.DEFAULT_PASS_THRESHOLD; exports.DeferredScoringError = _chunk5RE3ZRSAcjs.DeferredScoringError; exports.FeedbackSchema = _chunk5RE3ZRSAcjs.FeedbackSchema; exports.FillInTheBlanksDataSchema = _chunk5RE3ZRSAcjs.FillInTheBlanksDataSchema; exports.ItemGroupSchema = _chunk7NIH5IL3cjs.ItemGroupSchema; exports.MediaSchema = _chunk5RE3ZRSAcjs.MediaSchema; exports.MediaUrlSchema = _chunk5RE3ZRSAcjs.MediaUrlSchema; exports.MultipleChoiceDataSchema = _chunk5RE3ZRSAcjs.MultipleChoiceDataSchema; exports.MultipleChoiceOptionSchema = _chunk5RE3ZRSAcjs.MultipleChoiceOptionSchema; exports.RedactedBlankConfigSchema = _chunk5RE3ZRSAcjs.RedactedBlankConfigSchema; exports.RedactedFillInTheBlanksDataSchema = _chunk5RE3ZRSAcjs.RedactedFillInTheBlanksDataSchema; exports.RedactedItemGroupSchema = _chunk7NIH5IL3cjs.RedactedItemGroupSchema; exports.RedactedMultipleChoiceDataSchema = _chunk5RE3ZRSAcjs.RedactedMultipleChoiceDataSchema; exports.RedactedMultipleChoiceOptionSchema = _chunk5RE3ZRSAcjs.RedactedMultipleChoiceOptionSchema; exports.RedactedScoringError = _chunk5RE3ZRSAcjs.RedactedScoringError; exports.RedactedStimulusSchema = _chunk7NIH5IL3cjs.RedactedStimulusSchema; exports.RedactedWrittenResponseDataSchema = _chunk5RE3ZRSAcjs.RedactedWrittenResponseDataSchema; exports.StimulusSchema = _chunk7NIH5IL3cjs.StimulusSchema; exports.TextMatchPolicySchema = _chunk5RE3ZRSAcjs.TextMatchPolicySchema; exports.UnknownActivityTypeError = _chunk5RE3ZRSAcjs.UnknownActivityTypeError; exports.WrittenResponseDataSchema = _chunk5RE3ZRSAcjs.WrittenResponseDataSchema; exports.WrittenResponseRubricCriterionSchema = _chunk5RE3ZRSAcjs.WrittenResponseRubricCriterionSchema; exports.WrittenResponseRubricSchema = _chunk5RE3ZRSAcjs.WrittenResponseRubricSchema; exports.XAPIVerb = _chunkAFIQQADJcjs.XAPIVerb; exports.XAPI_VERB_DISPLAY = _chunkAFIQQADJcjs.XAPI_VERB_DISPLAY; exports.assertRedacted = assertRedacted; exports.assertRedactedItemGroup = assertRedactedItemGroup; exports.canonicalJson = canonicalJson; exports.classifyBand = _chunkMVZKEWZNcjs.classifyBand; exports.composeAssessmentScore = _chunkMVZKEWZNcjs.composeAssessmentScore; exports.computePassThreshold = _chunkMVZKEWZNcjs.computePassThreshold; exports.contentHash = contentHash; exports.countWords = _chunk5RE3ZRSAcjs.countWords; exports.defineActivityType = _chunk5RE3ZRSAcjs.defineActivityType; exports.diffResponses = diffResponses; exports.evaluate = _chunkMVZKEWZNcjs.evaluate; exports.fillInTheBlanksJsonSchema = _chunk7NIH5IL3cjs.fillInTheBlanksJsonSchema; exports.fillInTheBlanksType = _chunk5RE3ZRSAcjs.fillInTheBlanksType; exports.fingerprint = fingerprint; exports.flattenSequence = flattenSequence; exports.getActivityTypeDescriptor = _chunk5RE3ZRSAcjs.getActivityTypeDescriptor; exports.gradeFromRubric = _chunkMVZKEWZNcjs.gradeFromRubric; exports.gte = _chunkMVZKEWZNcjs.gte; exports.hasGrade = _chunkMVZKEWZNcjs.hasGrade; exports.hashSeed = hashSeed; exports.isItemGroup = isItemGroup; exports.itemGroupJsonSchema = _chunk7NIH5IL3cjs.itemGroupJsonSchema; exports.jsonSchemaFor = _chunk7NIH5IL3cjs.jsonSchemaFor; exports.levenshteinDistance = _chunk5RE3ZRSAcjs.levenshteinDistance; exports.matchText = _chunk5RE3ZRSAcjs.matchText; exports.multipleChoiceJsonSchema = _chunk7NIH5IL3cjs.multipleChoiceJsonSchema; exports.multipleChoiceType = _chunk5RE3ZRSAcjs.multipleChoiceType; exports.outcomeFromGrade = _chunkMVZKEWZNcjs.outcomeFromGrade; exports.planAttempt = planAttempt; exports.redact = redact; exports.redactItemGroup = redactItemGroup; exports.registerActivityType = _chunk5RE3ZRSAcjs.registerActivityType; exports.registeredActivityTypes = _chunk5RE3ZRSAcjs.registeredActivityTypes; exports.restoreAttemptState = restoreAttemptState; exports.roundGrade = _chunkMVZKEWZNcjs.roundGrade; exports.score = _chunkMVZKEWZNcjs.score; exports.scoredItemsFromPlan = scoredItemsFromPlan; exports.seededShuffle = seededShuffle; exports.serializeAttemptState = serializeAttemptState; exports.stimulusJsonSchema = _chunk7NIH5IL3cjs.stimulusJsonSchema; exports.validateActivity = _chunk7NIH5IL3cjs.validateActivity; exports.validateItemGroup = _chunk7NIH5IL3cjs.validateItemGroup; exports.validateXAPIStatement = _chunkAFIQQADJcjs.validateXAPIStatement; exports.verifyAttemptPlan = verifyAttemptPlan; exports.writtenResponseJsonSchema = _chunk7NIH5IL3cjs.writtenResponseJsonSchema; exports.writtenResponseType = _chunk5RE3ZRSAcjs.writtenResponseType; exports.xAPIBuilder = _chunkAFIQQADJcjs.xAPIBuilder; exports.xapiDefinitionFor = _chunkAFIQQADJcjs.xapiDefinitionFor;
|
|
415
726
|
//# sourceMappingURL=index.cjs.map
|