@longsightgroup/qti3-core 0.10.4 → 0.10.5

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 (39) hide show
  1. package/README.md +35 -0
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/qti-package-assets.d.ts +3 -2
  7. package/dist/qti-package-assets.d.ts.map +1 -1
  8. package/dist/qti-package-assets.js.map +1 -1
  9. package/dist/qti-package-items.d.ts +1 -0
  10. package/dist/qti-package-items.d.ts.map +1 -1
  11. package/dist/qti-package-items.js +36 -32
  12. package/dist/qti-package-items.js.map +1 -1
  13. package/dist/qti-package-manifest.d.ts +3 -2
  14. package/dist/qti-package-manifest.d.ts.map +1 -1
  15. package/dist/qti-package-manifest.js.map +1 -1
  16. package/dist/qti-package-metadata.d.ts +3 -2
  17. package/dist/qti-package-metadata.d.ts.map +1 -1
  18. package/dist/qti-package-metadata.js +5 -2
  19. package/dist/qti-package-metadata.js.map +1 -1
  20. package/dist/qti-package-plan.d.ts +41 -0
  21. package/dist/qti-package-plan.d.ts.map +1 -0
  22. package/dist/qti-package-plan.js +85 -0
  23. package/dist/qti-package-plan.js.map +1 -0
  24. package/dist/qti-package-stream.d.ts +38 -0
  25. package/dist/qti-package-stream.d.ts.map +1 -0
  26. package/dist/qti-package-stream.js +149 -0
  27. package/dist/qti-package-stream.js.map +1 -0
  28. package/dist/qti-package.d.ts.map +1 -1
  29. package/dist/qti-package.js +21 -51
  30. package/dist/qti-package.js.map +1 -1
  31. package/package.json +1 -1
  32. package/src/index.ts +8 -0
  33. package/src/qti-package-assets.ts +3 -4
  34. package/src/qti-package-items.ts +50 -40
  35. package/src/qti-package-manifest.ts +2 -3
  36. package/src/qti-package-metadata.ts +16 -4
  37. package/src/qti-package-plan.ts +168 -0
  38. package/src/qti-package-stream.ts +254 -0
  39. package/src/qti-package.ts +27 -87
@@ -0,0 +1,38 @@
1
+ import { type QtiPackageSummary } from "./qti-package-plan.js";
2
+ import type { QtiPackageItem } from "./qti-package-types.js";
3
+ /** An immutable entry inventory and host-owned, bounded reads of decoded entry bytes. */
4
+ export interface QtiPackageSource {
5
+ readonly entries: readonly {
6
+ readonly path: string;
7
+ readonly size: number;
8
+ }[];
9
+ /** Enforce maxBytes during extraction, before allocating an oversized body. */
10
+ readonly readEntry: (path: string, maxBytes: number) => Promise<Uint8Array>;
11
+ }
12
+ /** Explicit resource budgets for an incremental package read. */
13
+ export interface QtiPackageStreamLimits {
14
+ readonly maxEntries: number;
15
+ readonly maxEntryBytes: number;
16
+ readonly maxTotalBytes: number;
17
+ readonly maxDiagnostics: number;
18
+ }
19
+ /** Package metadata without extracted bodies or retained item models. */
20
+ export type QtiPackageStreamSummary = QtiPackageSummary;
21
+ /** Items are provisional until the terminal summary reports ok; hosts must stage them. */
22
+ export type QtiPackageStreamEvent = {
23
+ readonly kind: "item";
24
+ readonly index: number;
25
+ readonly item: QtiPackageItem;
26
+ } | {
27
+ readonly kind: "summary";
28
+ readonly summary: QtiPackageStreamSummary;
29
+ };
30
+ /**
31
+ * Parse immutable package entries sequentially with canonical package semantics.
32
+ *
33
+ * The caller owns ZIP/object storage and must not collect item events when bounded
34
+ * memory is required. Assets are described in the terminal summary, never loaded.
35
+ * Breaking iteration stops subsequent reads. Read failures become safe diagnostics.
36
+ */
37
+ export declare function parseQtiPackageStream(source: QtiPackageSource, limits: QtiPackageStreamLimits): AsyncGenerator<QtiPackageStreamEvent>;
38
+ //# sourceMappingURL=qti-package-stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qti-package-stream.d.ts","sourceRoot":"","sources":["../src/qti-package-stream.ts"],"names":[],"mappings":"AAMA,OAAO,EAKL,KAAK,iBAAiB,EACvB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAK7D,yFAAyF;AACzF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9E,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;CAC7E;AAED,iEAAiE;AACjE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED,yEAAyE;AACzE,MAAM,MAAM,uBAAuB,GAAG,iBAAiB,CAAC;AAExD,0FAA0F;AAC1F,MAAM,MAAM,qBAAqB,GAC7B;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;CAAE,GAChF;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,uBAAuB,CAAA;CAAE,CAAC;AAE5E;;;;;;GAMG;AACH,wBAAuB,qBAAqB,CAC1C,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,sBAAsB,GAC7B,cAAc,CAAC,qBAAqB,CAAC,CA6FvC"}
@@ -0,0 +1,149 @@
1
+ import { itemReferencesForPackageShape, parseAssessmentTestPackageModel, parsePackageItem, } from "./qti-package-items.js";
2
+ import { QTI_PACKAGE_MANIFEST_PATH } from "./qti-package-manifest.js";
3
+ import { buildPackageSummary, enforceDiagnosticLimit, failedPackageSummary, inspectPackageManifest, } from "./qti-package-plan.js";
4
+ import { normalizePackagePath, pushPackageDiagnostic } from "./qti-package-paths.js";
5
+ import { parsePackageXml } from "./qti-package-xml.js";
6
+ /**
7
+ * Parse immutable package entries sequentially with canonical package semantics.
8
+ *
9
+ * The caller owns ZIP/object storage and must not collect item events when bounded
10
+ * memory is required. Assets are described in the terminal summary, never loaded.
11
+ * Breaking iteration stops subsequent reads. Read failures become safe diagnostics.
12
+ */
13
+ export async function* parseQtiPackageStream(source, limits) {
14
+ const diagnostics = [];
15
+ const inventory = inspectInventory(source, limits, diagnostics);
16
+ if (!inventory) {
17
+ yield { kind: "summary", summary: failedPackageSummary(diagnostics) };
18
+ return;
19
+ }
20
+ const read = createEntryReader(source, inventory, limits, diagnostics);
21
+ const manifestEntry = await read(QTI_PACKAGE_MANIFEST_PATH);
22
+ const manifest = inspectPackageManifest(inventory, manifestEntry ? parsePackageXml(manifestEntry) : undefined, diagnostics);
23
+ // Only the primary assessment test supplies the package structure. The shared
24
+ // parser diagnoses additional test resources without loading their bodies.
25
+ const testEntries = new Map();
26
+ const testHref = manifest.assessmentTestResources[0]?.href;
27
+ if (testHref !== undefined) {
28
+ const entry = await read(testHref);
29
+ if (entry)
30
+ testEntries.set(testHref, entry);
31
+ }
32
+ const assessmentTest = parseAssessmentTestPackageModel(manifest.packageShape, manifest.assessmentTestResources, testEntries, new Map(), diagnostics);
33
+ const references = itemReferencesForPackageShape(manifest.packageShape, manifest.itemResources, assessmentTest, diagnostics);
34
+ const assetItems = [];
35
+ const itemStandards = [];
36
+ const titleItems = [];
37
+ const seen = new Set();
38
+ let itemCount = 0;
39
+ let timedItemCount = 0;
40
+ let soleItemTiming;
41
+ for (const reference of references) {
42
+ if (diagnostics.length >= limits.maxDiagnostics)
43
+ break;
44
+ if (seen.has(reference.href)) {
45
+ pushPackageDiagnostic(diagnostics, "package.item.reference.duplicate", "warning", `Package item ${reference.href} is referenced more than once.`, reference.href);
46
+ continue;
47
+ }
48
+ seen.add(reference.href);
49
+ const entry = await read(reference.href);
50
+ if (!entry)
51
+ continue;
52
+ const item = parsePackageItem(reference, entry, undefined, diagnostics);
53
+ if (item.title !== undefined && titleItems.length === 0) {
54
+ titleItems.push({ title: item.title });
55
+ }
56
+ if (item.timing !== undefined) {
57
+ timedItemCount += 1;
58
+ soleItemTiming = item.timing;
59
+ }
60
+ itemStandards.push(...item.standards);
61
+ assetItems.push({
62
+ href: item.href,
63
+ manifestResourceIdentifier: item.manifestResourceIdentifier,
64
+ assetHrefs: item.assetHrefs,
65
+ });
66
+ yield { kind: "item", index: itemCount++, item };
67
+ }
68
+ enforceDiagnosticLimit(diagnostics, limits.maxDiagnostics);
69
+ yield {
70
+ kind: "summary",
71
+ summary: buildPackageSummary({
72
+ manifestRoot: manifest.manifestRoot,
73
+ manifestResources: manifest.manifestResources,
74
+ resourcesByIdentifier: manifest.resourcesByIdentifier,
75
+ packageShape: manifest.packageShape,
76
+ assessmentTest,
77
+ assetItems,
78
+ titleItems,
79
+ timedItemCount,
80
+ soleItemTiming,
81
+ itemStandards,
82
+ entriesByPath: inventory,
83
+ diagnostics,
84
+ itemCount,
85
+ }),
86
+ };
87
+ }
88
+ function createEntryReader(source, inventory, limits, diagnostics) {
89
+ return async (path) => {
90
+ const size = inventory.get(path);
91
+ if (size === undefined)
92
+ return undefined;
93
+ try {
94
+ const bytes = await source.readEntry(path, limits.maxEntryBytes);
95
+ if (!(bytes instanceof Uint8Array) || bytes.byteLength !== size) {
96
+ pushPackageDiagnostic(diagnostics, "package.entry.size", "error", "Package entry size does not match its immutable inventory.", path);
97
+ return undefined;
98
+ }
99
+ return { path, bytes };
100
+ }
101
+ catch {
102
+ pushPackageDiagnostic(diagnostics, "package.entry.read", "error", "Package entry could not be read.", path);
103
+ return undefined;
104
+ }
105
+ };
106
+ }
107
+ function inspectInventory(source, limits, diagnostics) {
108
+ if (!validStreamLimits(limits)) {
109
+ pushPackageDiagnostic(diagnostics, "package.limits.invalid", "error", "Package limits must be positive safe integers.");
110
+ return undefined;
111
+ }
112
+ if (source.entries.length > limits.maxEntries) {
113
+ pushPackageDiagnostic(diagnostics, "package.entries.limit", "error", "Package contains more entries than allowed.");
114
+ return undefined;
115
+ }
116
+ const inventory = new Map();
117
+ let total = 0;
118
+ for (const entry of source.entries) {
119
+ const path = normalizePackagePath(entry.path, "entry", diagnostics);
120
+ if (!path)
121
+ return undefined;
122
+ if (path !== entry.path || inventory.has(path)) {
123
+ pushPackageDiagnostic(diagnostics, "package.entry.duplicate", "error", "Package inventory paths must be unique and canonical.", entry.path);
124
+ return undefined;
125
+ }
126
+ total += entry.size;
127
+ if (!Number.isSafeInteger(entry.size) ||
128
+ entry.size < 0 ||
129
+ entry.size > limits.maxEntryBytes ||
130
+ !Number.isSafeInteger(total) ||
131
+ total > limits.maxTotalBytes) {
132
+ pushPackageDiagnostic(diagnostics, "package.bytes.limit", "error", "Package entry or total size exceeds its configured budget.", path);
133
+ return undefined;
134
+ }
135
+ inventory.set(path, entry.size);
136
+ }
137
+ return inventory;
138
+ }
139
+ function validStreamLimits(limits) {
140
+ return (Number.isSafeInteger(limits.maxEntries) &&
141
+ limits.maxEntries > 0 &&
142
+ Number.isSafeInteger(limits.maxEntryBytes) &&
143
+ limits.maxEntryBytes > 0 &&
144
+ Number.isSafeInteger(limits.maxTotalBytes) &&
145
+ limits.maxTotalBytes > 0 &&
146
+ Number.isSafeInteger(limits.maxDiagnostics) &&
147
+ limits.maxDiagnostics > 0);
148
+ }
149
+ //# sourceMappingURL=qti-package-stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qti-package-stream.js","sourceRoot":"","sources":["../src/qti-package-stream.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"qti-package.d.ts","sourceRoot":"","sources":["../src/qti-package.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAGpE,OAAO,EACL,mCAAmC,EACnC,UAAU,EACV,wBAAwB,EACxB,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC5B,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,mCAAmC,EAAE,UAAU,EAAE,wBAAwB,EAAE,CAAC;AACrF,OAAO,EAAE,+BAA+B,EAAE,MAAM,yBAAyB,CAAC;AAC1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAC9F,YAAY,EAAE,eAAe,EAAE,CAAC;AAEhC,YAAY,EACV,gCAAgC,EAChC,wBAAwB,EACxB,6BAA6B,EAC7B,eAAe,EACf,mBAAmB,EACnB,eAAe,EACf,+BAA+B,EAC/B,qBAAqB,EACrB,cAAc,EACd,oBAAoB,EACpB,qBAAqB,EACrB,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,yBAAyB,EACzB,uBAAuB,EACvB,yBAAyB,EACzB,aAAa,EACb,iBAAiB,GAClB,MAAM,wBAAwB,CAAC;AAEhC,YAAY,EACV,wBAAwB,EACxB,oBAAoB,EACpB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,sBAAsB,CAAC;AAE9B,0FAA0F;AAC1F,wBAAgB,eAAe,CAC7B,KAAK,EAAE,UAAU,EACjB,OAAO,GAAE,sBAA2B,GACnC,qBAAqB,CAIvB"}
1
+ {"version":3,"file":"qti-package.d.ts","sourceRoot":"","sources":["../src/qti-package.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAEpE,OAAO,EACL,mCAAmC,EACnC,UAAU,EACV,wBAAwB,EACxB,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC5B,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,mCAAmC,EAAE,UAAU,EAAE,wBAAwB,EAAE,CAAC;AACrF,OAAO,EAAE,+BAA+B,EAAE,MAAM,yBAAyB,CAAC;AAC1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAC9F,YAAY,EAAE,eAAe,EAAE,CAAC;AAEhC,YAAY,EACV,gCAAgC,EAChC,wBAAwB,EACxB,6BAA6B,EAC7B,eAAe,EACf,mBAAmB,EACnB,eAAe,EACf,+BAA+B,EAC/B,qBAAqB,EACrB,cAAc,EACd,oBAAoB,EACpB,qBAAqB,EACrB,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,yBAAyB,EACzB,uBAAuB,EACvB,yBAAyB,EACzB,aAAa,EACb,iBAAiB,GAClB,MAAM,wBAAwB,CAAC;AAEhC,YAAY,EACV,wBAAwB,EACxB,oBAAoB,EACpB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,sBAAsB,CAAC;AAE9B,0FAA0F;AAC1F,wBAAgB,eAAe,CAC7B,KAAK,EAAE,UAAU,EACjB,OAAO,GAAE,sBAA2B,GACnC,qBAAqB,CAIvB"}
@@ -1,10 +1,8 @@
1
1
  import { pushPackageDiagnostic } from "./qti-package-paths.js";
2
- import { collectPackageAssets } from "./qti-package-assets.js";
3
- import { detectPackageShape, diagnoseManifestDependencyReferences, diagnosePrimaryResourceHrefs, indexManifestResources, isQtiAssessmentTestResource, isQtiItemResource, parseManifestResources, QTI_PACKAGE_MANIFEST_PATH, } from "./qti-package-manifest.js";
2
+ import { QTI_PACKAGE_MANIFEST_PATH } from "./qti-package-manifest.js";
3
+ import { buildPackageSummary, inspectPackageManifest } from "./qti-package-plan.js";
4
4
  import { itemReferencesForPackageShape, parseAssessmentTestPackageModel, parsePackageItems, } from "./qti-package-items.js";
5
- import { packageTitle, parseStandardAlignments, primaryTiming, uniqueStandards, } from "./qti-package-metadata.js";
6
- import { QTI_PACKAGE_MANIFEST_NAMESPACE } from "./qti-namespaces.js";
7
- import { parseXmlFiles, pushXmlDiagnostics } from "./qti-package-xml.js";
5
+ import { parseXmlFiles } from "./qti-package-xml.js";
8
6
  import { DEFAULT_QTI_PACKAGE_RESOURCE_LIMITS, decodeUtf8, readQtiPackageZipEntries, } from "./qti-package-zip.js";
9
7
  export { DEFAULT_QTI_PACKAGE_RESOURCE_LIMITS, decodeUtf8, readQtiPackageZipEntries };
10
8
  export { discoverQtiPackageContentAssets } from "./qti-package-assets.js";
@@ -19,58 +17,30 @@ export function parseQtiPackage(bytes, options = {}) {
19
17
  function parseQtiPackageEntries(entries, options, diagnostics) {
20
18
  const entriesByPath = indexEntries(entries, diagnostics);
21
19
  const xmlFilesByPath = parseXmlFiles(entriesByPath);
22
- const manifestXml = xmlFilesByPath.get(QTI_PACKAGE_MANIFEST_PATH);
23
- if (!manifestXml) {
24
- pushPackageDiagnostic(diagnostics, "package.manifest.missing", "error", "QTI package does not contain imsmanifest.xml.", QTI_PACKAGE_MANIFEST_PATH);
25
- }
26
- else {
27
- pushXmlDiagnostics(manifestXml, diagnostics);
28
- }
29
- const manifestRoot = manifestXml?.root?.localName === "manifest" &&
30
- manifestXml.root.uri === QTI_PACKAGE_MANIFEST_NAMESPACE
31
- ? manifestXml.root
32
- : undefined;
33
- if (manifestXml?.root && !manifestRoot) {
34
- pushPackageDiagnostic(diagnostics, "package.manifest.root", "error", manifestXml.root.localName === "manifest"
35
- ? `Expected imsmanifest.xml manifest in namespace ${QTI_PACKAGE_MANIFEST_NAMESPACE}, found ${manifestXml.root.uri ?? "(none)"}.`
36
- : `Expected imsmanifest.xml root manifest, found ${manifestXml.root.localName}.`, QTI_PACKAGE_MANIFEST_PATH);
37
- }
38
- const manifestResourceRecords = manifestRoot
39
- ? parseManifestResources(manifestRoot, entriesByPath, diagnostics)
40
- : [];
41
- const manifestResources = manifestResourceRecords;
42
- const resourcesByIdentifier = indexManifestResources(manifestResourceRecords, diagnostics);
43
- diagnoseManifestDependencyReferences(manifestResourceRecords, resourcesByIdentifier, diagnostics);
44
- const itemResources = manifestResourceRecords.filter((resource) => isQtiItemResource(resource.type));
45
- const assessmentTestResources = manifestResourceRecords.filter((resource) => isQtiAssessmentTestResource(resource.type));
46
- diagnosePrimaryResourceHrefs([...itemResources, ...assessmentTestResources], diagnostics);
47
- const packageShape = detectPackageShape(itemResources, assessmentTestResources, diagnostics);
20
+ const { manifestRoot, manifestResources, resourcesByIdentifier, itemResources, assessmentTestResources, packageShape, } = inspectPackageManifest(entriesByPath, xmlFilesByPath.get(QTI_PACKAGE_MANIFEST_PATH), diagnostics);
48
21
  const assessmentTest = parseAssessmentTestPackageModel(packageShape, assessmentTestResources, entriesByPath, xmlFilesByPath, diagnostics);
49
22
  const itemReferences = itemReferencesForPackageShape(packageShape, itemResources, assessmentTest, diagnostics);
50
23
  const items = parsePackageItems(itemReferences, entriesByPath, xmlFilesByPath, diagnostics);
51
- const assets = collectPackageAssets(resourcesByIdentifier, assessmentTest, items, entriesByPath, diagnostics);
52
- const manifestStandards = manifestRoot
53
- ? parseStandardAlignments(manifestRoot, QTI_PACKAGE_MANIFEST_PATH)
54
- : [];
55
- const standards = uniqueStandards([
56
- ...manifestStandards,
57
- ...(assessmentTest?.standards ?? []),
58
- ...items.flatMap((item) => item.standards),
59
- ]);
60
- const timing = primaryTiming(assessmentTest, items);
61
- const title = packageTitle(manifestRoot, assessmentTest, items);
62
- return {
63
- ok: diagnostics.every((diagnostic) => diagnostic.severity !== "error"),
64
- title,
65
- entries,
66
- packageShape,
67
- items,
68
- assets,
24
+ const timedItems = items.filter((item) => item.timing !== undefined);
25
+ const { itemCount: _itemCount, ...summary } = buildPackageSummary({
26
+ manifestRoot,
69
27
  manifestResources,
28
+ resourcesByIdentifier,
29
+ packageShape,
70
30
  assessmentTest,
71
- timing,
72
- standards,
31
+ assetItems: items,
32
+ titleItems: items,
33
+ timedItemCount: timedItems.length,
34
+ soleItemTiming: timedItems.length === 1 ? timedItems[0]?.timing : undefined,
35
+ itemStandards: items.flatMap((item) => item.standards),
36
+ entriesByPath,
73
37
  diagnostics,
38
+ itemCount: items.length,
39
+ });
40
+ return {
41
+ ...summary,
42
+ entries,
43
+ items,
74
44
  };
75
45
  }
76
46
  function indexEntries(entries, diagnostics) {
@@ -1 +1 @@
1
- {"version":3,"file":"qti-package.js","sourceRoot":"","sources":["../src/qti-package.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"qti-package.js","sourceRoot":"","sources":["../src/qti-package.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@longsightgroup/qti3-core",
3
- "version": "0.10.4",
3
+ "version": "0.10.5",
4
4
  "description": "Framework-neutral TypeScript core for parsing, validating, scoring, and serializing QTI 3 assessment items.",
5
5
  "keywords": [
6
6
  "assessment",
package/src/index.ts CHANGED
@@ -368,3 +368,11 @@ export {
368
368
  readQtiJsonValue,
369
369
  unknownToDisplayString,
370
370
  } from "./value-format.js";
371
+
372
+ export {
373
+ parseQtiPackageStream,
374
+ type QtiPackageSource,
375
+ type QtiPackageStreamLimits,
376
+ type QtiPackageStreamEvent,
377
+ type QtiPackageStreamSummary,
378
+ } from "./qti-package-stream.js";
@@ -14,7 +14,6 @@ import {
14
14
  resolvePackageHref,
15
15
  type PackageXmlFile,
16
16
  } from "./qti-package-xml.js";
17
- import type { QtiPackageEntry } from "./qti-package-zip.js";
18
17
  import type { QtiDiagnostic } from "./types.js";
19
18
 
20
19
  interface PendingAssetReference {
@@ -87,8 +86,8 @@ export function discoverContentAssetHrefs(
87
86
  export function collectPackageAssets(
88
87
  resourcesByIdentifier: ReadonlyMap<string, QtiManifestResource>,
89
88
  assessmentTest: QtiAssessmentTestPackageModel | undefined,
90
- items: readonly QtiPackageItem[],
91
- entriesByPath: ReadonlyMap<string, QtiPackageEntry>,
89
+ items: readonly Pick<QtiPackageItem, "href" | "manifestResourceIdentifier" | "assetHrefs">[],
90
+ entriesByPath: { has(path: string): boolean },
92
91
  diagnostics: QtiDiagnostic[],
93
92
  ): QtiPackageAsset[] {
94
93
  const pending: PendingAssetReference[] = [];
@@ -220,7 +219,7 @@ function collectPackageRelativeTextRefs(
220
219
 
221
220
  function materializeAssets(
222
221
  pending: readonly PendingAssetReference[],
223
- entriesByPath: ReadonlyMap<string, QtiPackageEntry>,
222
+ entriesByPath: { has(path: string): boolean },
224
223
  ): QtiPackageAsset[] {
225
224
  const assetsByHref = new Map<string, PendingAssetReference[]>();
226
225
  for (const reference of pending) {
@@ -346,6 +346,55 @@ export function itemReferencesForPackageShape(
346
346
  return references;
347
347
  }
348
348
 
349
+ export function parsePackageItem(
350
+ reference: PackageItemReference,
351
+ entry: QtiPackageEntry,
352
+ xmlFile: PackageXmlFile | undefined,
353
+ diagnostics: QtiDiagnostic[],
354
+ ): QtiPackageItem {
355
+ const resolvedXml = xmlFile ?? parsePackageXml(entry);
356
+ // Full QTI parse for document model; lightweight tree above supplies metadata and asset refs.
357
+ const parsed = parseQtiXml(resolvedXml.xml);
358
+ const itemDiagnostics = parsed.diagnostics.map((diagnostic) =>
359
+ scopeDiagnosticToPackagePath(reference.href, diagnostic),
360
+ );
361
+ diagnostics.push(...itemDiagnostics);
362
+
363
+ const assetHrefs = resolvedXml.root ? discoverContentAssetHrefs(resolvedXml, diagnostics) : [];
364
+ const root =
365
+ resolvedXml.root?.localName === "qti-assessment-item" &&
366
+ resolvedXml.root.uri === QTI_ASI_NAMESPACE
367
+ ? resolvedXml.root
368
+ : undefined;
369
+ const item = parsed.document?.item;
370
+ const timeLimits = root
371
+ ? parseTimeLimits(qtiChildElements(root, "qti-time-limits")[0], reference.href, diagnostics)
372
+ : undefined;
373
+
374
+ return {
375
+ href: reference.href,
376
+ source: reference.source,
377
+ manifestResourceIdentifier: reference.manifestResourceIdentifier,
378
+ assessmentItemRefIdentifier: reference.assessmentItemRefIdentifier,
379
+ identifier: item?.identifier,
380
+ title: item?.title,
381
+ document: parsed.document,
382
+ timing: root ? parseTimingMetadata(root, reference.href, diagnostics, timeLimits) : undefined,
383
+ timeLimits,
384
+ standards: uniqueStandards([
385
+ ...reference.standards.filter(
386
+ (standard) =>
387
+ standard.resourcePartIdentifier === undefined ||
388
+ standard.resourcePartIdentifier === item?.identifier,
389
+ ),
390
+ ...(root ? parseStandardAlignments(root, reference.href) : []),
391
+ ]),
392
+ assetHrefs,
393
+ diagnostics: itemDiagnostics,
394
+ xml: resolvedXml.xml,
395
+ };
396
+ }
397
+
349
398
  export function parsePackageItems(
350
399
  references: readonly PackageItemReference[],
351
400
  entriesByPath: ReadonlyMap<string, QtiPackageEntry>,
@@ -380,46 +429,7 @@ export function parsePackageItems(
380
429
  continue;
381
430
  }
382
431
 
383
- const xmlFile = xmlFilesByPath.get(reference.href) ?? parsePackageXml(entry);
384
- // Full QTI parse for document model; lightweight tree above supplies metadata and asset refs.
385
- const parsed = parseQtiXml(xmlFile.xml);
386
- const itemDiagnostics = parsed.diagnostics.map((diagnostic) =>
387
- scopeDiagnosticToPackagePath(reference.href, diagnostic),
388
- );
389
- diagnostics.push(...itemDiagnostics);
390
-
391
- const assetHrefs = xmlFile.root ? discoverContentAssetHrefs(xmlFile, diagnostics) : [];
392
- const root =
393
- xmlFile.root?.localName === "qti-assessment-item" && xmlFile.root.uri === QTI_ASI_NAMESPACE
394
- ? xmlFile.root
395
- : undefined;
396
- const item = parsed.document?.item;
397
- const timeLimits = root
398
- ? parseTimeLimits(qtiChildElements(root, "qti-time-limits")[0], reference.href, diagnostics)
399
- : undefined;
400
-
401
- items.push({
402
- href: reference.href,
403
- source: reference.source,
404
- manifestResourceIdentifier: reference.manifestResourceIdentifier,
405
- assessmentItemRefIdentifier: reference.assessmentItemRefIdentifier,
406
- identifier: item?.identifier,
407
- title: item?.title,
408
- document: parsed.document,
409
- timing: root ? parseTimingMetadata(root, reference.href, diagnostics, timeLimits) : undefined,
410
- timeLimits,
411
- standards: uniqueStandards([
412
- ...reference.standards.filter(
413
- (standard) =>
414
- standard.resourcePartIdentifier === undefined ||
415
- standard.resourcePartIdentifier === item?.identifier,
416
- ),
417
- ...(root ? parseStandardAlignments(root, reference.href) : []),
418
- ]),
419
- assetHrefs,
420
- diagnostics: itemDiagnostics,
421
- xml: xmlFile.xml,
422
- });
432
+ items.push(parsePackageItem(reference, entry, xmlFilesByPath.get(reference.href), diagnostics));
423
433
  }
424
434
 
425
435
  return items;
@@ -3,7 +3,6 @@ import { normalizePackagePath, pushPackageDiagnostic } from "./qti-package-paths
3
3
  import { parseStandardAlignments } from "./qti-package-metadata.js";
4
4
  import type { QtiManifestFile, QtiManifestResource, QtiPackageShape } from "./qti-package-types.js";
5
5
  import { childPackageElements, packageDescendants, stripHrefSuffix } from "./qti-package-xml.js";
6
- import type { QtiPackageEntry } from "./qti-package-zip.js";
7
6
  import type { QtiDiagnostic } from "./types.js";
8
7
  import { QTI_PACKAGE_MANIFEST_NAMESPACE } from "./qti-namespaces.js";
9
8
 
@@ -21,7 +20,7 @@ export function isQtiAssessmentTestResource(type: string): boolean {
21
20
 
22
21
  export function parseManifestResources(
23
22
  manifestRoot: QtiPackageXmlNode,
24
- entriesByPath: ReadonlyMap<string, QtiPackageEntry>,
23
+ entriesByPath: { has(path: string): boolean },
25
24
  diagnostics: QtiDiagnostic[],
26
25
  ): QtiManifestResource[] {
27
26
  const resourceNodes = packageDescendants(
@@ -72,7 +71,7 @@ export function parseManifestResources(
72
71
 
73
72
  function parseManifestFiles(
74
73
  resourceNode: QtiPackageXmlNode,
75
- entriesByPath: ReadonlyMap<string, QtiPackageEntry>,
74
+ entriesByPath: { has(path: string): boolean },
76
75
  diagnostics: QtiDiagnostic[],
77
76
  ): QtiManifestFile[] {
78
77
  const files: QtiManifestFile[] = [];
@@ -282,19 +282,31 @@ function optionalFiniteNumber(raw: string | undefined): number | undefined {
282
282
  return Number.isFinite(value) ? value : undefined;
283
283
  }
284
284
 
285
- export function primaryTiming(
285
+ export function primaryTimingFromScan(
286
286
  assessmentTest: QtiAssessmentTestPackageModel | undefined,
287
- items: readonly QtiPackageItem[],
287
+ timedItemCount: number,
288
+ soleItemTiming: QtiTimingMetadata | undefined,
288
289
  ): QtiTimingMetadata | undefined {
289
290
  if (assessmentTest?.timing) return assessmentTest.timing;
291
+ return timedItemCount === 1 ? soleItemTiming : undefined;
292
+ }
293
+
294
+ export function primaryTiming(
295
+ assessmentTest: QtiAssessmentTestPackageModel | undefined,
296
+ items: readonly Pick<QtiPackageItem, "timing">[],
297
+ ): QtiTimingMetadata | undefined {
290
298
  const timedItems = items.filter((item) => item.timing !== undefined);
291
- return timedItems.length === 1 ? timedItems[0]?.timing : undefined;
299
+ return primaryTimingFromScan(
300
+ assessmentTest,
301
+ timedItems.length,
302
+ timedItems.length === 1 ? timedItems[0]?.timing : undefined,
303
+ );
292
304
  }
293
305
 
294
306
  export function packageTitle(
295
307
  manifestRoot: QtiPackageXmlNode | undefined,
296
308
  assessmentTest: QtiAssessmentTestPackageModel | undefined,
297
- items: readonly QtiPackageItem[],
309
+ items: readonly Pick<QtiPackageItem, "title">[],
298
310
  ): string {
299
311
  const manifestTitle = manifestRoot
300
312
  ? packageDescendants(manifestRoot, "title", QTI_PACKAGE_MANIFEST_NAMESPACE)