@memizy/oqse 1.0.0-rc.5 → 1.0.0-rc.6

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 (48) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +3 -1
  3. package/dist/checkAnswer.cjs +1 -1
  4. package/dist/checkAnswer.js +1 -1
  5. package/dist/index.d.ts +20 -20
  6. package/dist/oqseValidation.cjs +1 -1
  7. package/dist/oqseValidation.js +23 -25
  8. package/dist/richTextFields.cjs +1 -1
  9. package/dist/richTextFields.js +1 -1
  10. package/dist/src/assets.d.ts +1 -1
  11. package/dist/src/checkAnswer.d.ts +1 -1
  12. package/dist/src/compatibility.d.ts +2 -2
  13. package/dist/src/features.d.ts +1 -1
  14. package/dist/src/fileValidation.d.ts +1 -1
  15. package/dist/src/header.d.ts +1 -1
  16. package/dist/src/headerValidation.d.ts +1 -1
  17. package/dist/src/index.d.ts +20 -20
  18. package/dist/src/manifest.d.ts +2 -2
  19. package/dist/src/manifestValidation.d.ts +1 -1
  20. package/dist/src/markdownSet.d.ts +2 -2
  21. package/dist/src/oqse.d.ts +7 -11
  22. package/dist/src/oqseValidation.d.ts +13 -21
  23. package/dist/src/plainText.d.ts +1 -1
  24. package/dist/src/progressValidation.d.ts +1 -1
  25. package/dist/src/richTextFields.d.ts +1 -1
  26. package/dist/src/richTextProcessor.d.ts +1 -1
  27. package/oqse-header.md +6 -0
  28. package/oqse-manifest.md +6 -0
  29. package/oqse-progress.md +7 -1
  30. package/oqse.md +39 -26
  31. package/package.json +14 -2
  32. package/schemas/oqse-v1.0.json +7 -13
  33. package/dist/scripts/fix-types-entry.d.ts +0 -1
  34. package/dist/scripts/generate-schemas.d.ts +0 -1
  35. package/dist/src/answers.test.d.ts +0 -1
  36. package/dist/src/checkAnswer.test.d.ts +0 -1
  37. package/dist/src/compatibility.test.d.ts +0 -1
  38. package/dist/src/features.test.d.ts +0 -1
  39. package/dist/src/fileValidation.test.d.ts +0 -1
  40. package/dist/src/headerValidation.test.d.ts +0 -1
  41. package/dist/src/manifestValidation.test.d.ts +0 -1
  42. package/dist/src/markdownSet.test.d.ts +0 -1
  43. package/dist/src/oqseValidation.test.d.ts +0 -1
  44. package/dist/src/plainText.test.d.ts +0 -1
  45. package/dist/src/progressValidation.test.d.ts +0 -1
  46. package/dist/src/richTextProcessor.test.d.ts +0 -1
  47. package/dist/src/specExamples.test.d.ts +0 -5
  48. package/dist/src/utils.test.d.ts +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # Changelog of @memizy/oqse
2
+
3
+ The format itself has its own changelog at the end of [oqse.md](https://github.com/memizy/memizy/blob/main/packages/oqse/oqse.md#version-10-2026-10-14) (OQSE 1.0, 2026-10-14). This file lists the releases of the npm package.
4
+
5
+ ## 1.0.0-rc.6 (2026-10-14)
6
+
7
+ Release candidate of OQSE 1.0; the format is frozen (1.x only adds).
8
+
9
+ * `HotspotObject`: the shape fields (`x`, `y`, `width`, `height`, `radius`, `points`, `targetName`) are marked required in the specification, as the validators always treated them.
10
+ * New section "Canonical and Authoring Formats" in `oqse.md`: the JSON file is the canonical form that applications store and exchange; authoring formats (OQSE Markdown for notes, tool-specific shorthands) are only inputs converted to it, and item IDs survive edits.
11
+ * Aligned before 1.0: `math-input` has `correctAnswers` (a list, like `short-answer`) instead of `correctAnswer` + `alternativeAnswers`, and no `tolerance` (answers are compared as LaTeX; numbers are `numeric-input`); `match-complex` has `prompts` / `matches` (like `match-pairs`) instead of `leftItems` / `rightItems`. `checkAnswer` and the JSON Schemas follow.
12
+ * The sub-specifications (`oqse-manifest.md`, `oqse-progress.md`, `oqse-header.md`) point to the changelog in `oqse.md`.
13
+
14
+ ## 1.0.0-rc.5
15
+
16
+ OQSE 1.0: choices with IDs, answers by ID, `skills`, Answer Formats and `checkAnswer`, typed-answer comparison, timeline rules, feature detection, version rules (0.x drafts are refused) and the JSON Schemas at `https://cdn.jsdelivr.net/npm/@memizy/oqse@1/schemas/`. See the "Version 1.0" changelog in `oqse.md`.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Official TypeScript SDK for the Open Quiz & Study Exchange ecosystem, published as @memizy/oqse.
4
4
 
5
- 🚀 **[OQSE Web Validator](https://memizy.github.io/memizy/)** - Online tool for easy testing and debugging of OQSE JSON structures in the browser (source: [`docs/`](./docs/), published by GitHub Pages).
5
+ 🚀 **[OQSE Web Validator](https://memizy.github.io/memizy/)** - Online tool for easy testing and debugging of OQSE JSON structures in the browser (source: [`docs/`](https://github.com/memizy/memizy/blob/main/packages/oqse/docs/), published by GitHub Pages).
6
6
 
7
7
  This repository contains:
8
8
  - The human-readable specifications split into four standalone documents:
@@ -36,6 +36,8 @@ const { success, data, errors, warnings } = loadOQSEFile(JSON.parse(text));
36
36
 
37
37
  ## Schemas
38
38
 
39
+ The exported `*Schema` objects are **Zod 4** schemas (use them with `.safeParse`); the 1.x line of this package stays on Zod 4.
40
+
39
41
  The auto-generated JSON Schemas are published with this repository and are available at the following URLs:
40
42
 
41
43
  - Core OQSE Schema: https://cdn.jsdelivr.net/npm/@memizy/oqse@1/schemas/oqse-v1.0.json
@@ -1 +1 @@
1
- const e=require("./answers.cjs");function t(t,p){if(!t||typeof t!=`object`)throw Error(`checkAnswer: missing item`);switch(t.type){case`mcq-single`:return p===t.correctId;case`mcq-multi`:{let e=i(p);return e!==null&&c(e,t.correctIds)}case`true-false`:return r(p)===t.correctAnswer;case`short-answer`:{let n=p;return typeof n==`string`&&t.correctAnswers.some(r=>e.textAnswerMatches(n,r,t))}case`numeric-input`:{let e=n(p);return e!==null&&Math.abs(e-t.correctAnswer)<=(t.tolerance??0)+1e-9}case`slider`:{let e=n(p);return e!==null&&Math.abs(e-t.correctAnswer)<=(t.tolerance??0)+1e-9}case`math-input`:{if(typeof p!=`string`)return!1;let e=l(p);return[t.correctAnswer,...t.alternativeAnswers??[]].some(t=>l(t)===e)}case`sort-items`:{let e=i(p);return e!==null&&e.length===t.correctOrder.length&&e.every((e,n)=>e===t.correctOrder[n])}case`match-pairs`:{let e=s(p);return!!e&&t.prompts.every(n=>e[n.id]===t.pairs[n.id])}case`match-complex`:{let e=a(p);if(!e)return!1;let n=new Set(t.connections.map(([e,t])=>o(e,t))),r=new Set(e.map(([e,t])=>o(e,t))),i=[...r].filter(e=>n.has(e)).length;return i===r.size&&i>=(t.minCorrect??n.size)}case`matrix`:{let e=a(p);return e?c(e.map(([e,t])=>o(e,t)),t.correctCells.map(([e,t])=>o(e,t))):!1}case`fill-in-blanks`:{let n=s(p);return n?Object.entries(t.blanks).every(([r,i])=>{let a=n[r];return typeof a==`string`&&i.some(n=>e.textAnswerMatches(a,n,t))}):!1}case`fill-in-select`:{let e=s(p);return!!e&&Object.entries(t.blanks).every(([t,n])=>e[t]===n.correctId)}case`categorize`:{let e=s(p);return!!e&&t.items.every(t=>e[t.id]===t.correctCategoryId)}case`timeline`:{let n=i(p);if(!n||n.length!==t.events.length||new Set(n).size!==n.length)return!1;let r=n.map(e=>t.events.find(t=>t.id===e));return r.every(e=>!!e)&&e.isTimelineOrderCorrect(r)}case`pin-on-image`:{let e=(Array.isArray(p)?p:[p]).map(d);if(e.length===0||e.some(e=>e===null))return!1;if(!t.multipleCorrect)return e.length===1&&t.hotspots.some(t=>f(e[0],t));let n=new Set;for(let r of e){let e=t.hotspots.findIndex(e=>f(r,e));if(e===-1)return!1;n.add(e)}return n.size>=(t.minCorrect??t.hotspots.length)}case`diagram-label`:{let n=s(p);return n?t.zones.every(r=>{let i=n[r.id];if(t.requireTyping){let n=t.labels.find(e=>e.id===r.correctLabelId);return!!n&&typeof i==`string`&&e.textAnswerMatches(i,n.text,t)}return i===r.correctLabelId}):!1}case`pin-on-model`:{let e=(Array.isArray(p)?p:[p]).filter(e=>typeof e==`string`);if(e.length===0)return!1;let n=e=>t.hotspots.findIndex(t=>e.toLowerCase().includes(t.targetName.toLowerCase()));if(!t.multipleCorrect)return e.length===1&&n(e[0])!==-1;let r=new Set;for(let t of e){let e=n(t);if(e===-1)return!1;r.add(e)}return r.size>=(t.minCorrect??t.hotspots.length)}case`chess-puzzle`:{if(!Array.isArray(p)||p.some(e=>typeof e!=`string`))return!1;let e=p.map(u);return t.correctAnswers.some(t=>t.length===e.length&&t.every((t,n)=>u(t)===e[n]))}case`flashcard`:{let e=n(p);return e===3||e===4}case`note`:case`open-ended`:throw Error(`checkAnswer: "${t.type}" items cannot be checked automatically.`);default:throw Error(`checkAnswer: unsupported item type "${t.type}".`)}}function n(e){if(typeof e==`number`)return Number.isFinite(e)?e:null;if(typeof e!=`string`)return null;let t=e.trim().replace(/\s+/g,``).replace(`,`,`.`);return/^[-+]?(\d+\.?\d*|\.\d+)(e[-+]?\d+)?$/i.test(t)?Number(t):null}function r(e){return typeof e==`boolean`?e:e===`true`||e!==`false`&&null}function i(e){return Array.isArray(e)&&e.every(e=>typeof e==`string`)?e:null}function a(e){return Array.isArray(e)&&e.every(e=>Array.isArray(e)&&e.length===2&&typeof e[0]==`string`&&typeof e[1]==`string`)?e:null}var o=(e,t)=>`${e}\u0000${t}`;function s(e){return e&&typeof e==`object`&&!Array.isArray(e)?e:null}function c(e,t){let n=new Set(e),r=new Set(t);return n.size===e.length&&n.size===r.size&&[...n].every(e=>r.has(e))}function l(e){return e.trim().replace(/^\$\$?|\$\$?$/g,``).replace(/\s+/g,``)}function u(e){return e.trim().replace(/[+#!?]+$/g,``).replace(/^0-0(-0)?$/,e=>e.replace(/0/g,`O`)).replace(/^([a-h](?:x[a-h])?[18])([QRBN])$/,`$1=$2`)}function d(e){let t=s(e);return t&&typeof t.x==`number`&&typeof t.y==`number`?{x:t.x,y:t.y}:null}function f(e,t){switch(t.type){case`rect`:return e.x>=t.x&&e.x<=t.x+t.width&&e.y>=t.y&&e.y<=t.y+t.height;case`circle`:return(e.x-t.x)**2+(e.y-t.y)**2<=t.radius**2;case`polygon`:{let n=!1,r=t.points;for(let t=0,i=r.length-1;t<r.length;i=t++)r[t].y>e.y!=r[i].y>e.y&&e.x<(r[i].x-r[t].x)*(e.y-r[t].y)/(r[i].y-r[t].y)+r[t].x&&(n=!n);return n}}}exports.checkAnswer=t;
1
+ const e=require("./answers.cjs");function t(t,p){if(!t||typeof t!=`object`)throw Error(`checkAnswer: missing item`);switch(t.type){case`mcq-single`:return p===t.correctId;case`mcq-multi`:{let e=i(p);return e!==null&&c(e,t.correctIds)}case`true-false`:return r(p)===t.correctAnswer;case`short-answer`:{let n=p;return typeof n==`string`&&t.correctAnswers.some(r=>e.textAnswerMatches(n,r,t))}case`numeric-input`:{let e=n(p);return e!==null&&Math.abs(e-t.correctAnswer)<=(t.tolerance??0)+1e-9}case`slider`:{let e=n(p);return e!==null&&Math.abs(e-t.correctAnswer)<=(t.tolerance??0)+1e-9}case`math-input`:{if(typeof p!=`string`)return!1;let e=l(p);return t.correctAnswers.some(t=>l(t)===e)}case`sort-items`:{let e=i(p);return e!==null&&e.length===t.correctOrder.length&&e.every((e,n)=>e===t.correctOrder[n])}case`match-pairs`:{let e=s(p);return!!e&&t.prompts.every(n=>e[n.id]===t.pairs[n.id])}case`match-complex`:{let e=a(p);if(!e)return!1;let n=new Set(t.connections.map(([e,t])=>o(e,t))),r=new Set(e.map(([e,t])=>o(e,t))),i=[...r].filter(e=>n.has(e)).length;return i===r.size&&i>=(t.minCorrect??n.size)}case`matrix`:{let e=a(p);return e?c(e.map(([e,t])=>o(e,t)),t.correctCells.map(([e,t])=>o(e,t))):!1}case`fill-in-blanks`:{let n=s(p);return n?Object.entries(t.blanks).every(([r,i])=>{let a=n[r];return typeof a==`string`&&i.some(n=>e.textAnswerMatches(a,n,t))}):!1}case`fill-in-select`:{let e=s(p);return!!e&&Object.entries(t.blanks).every(([t,n])=>e[t]===n.correctId)}case`categorize`:{let e=s(p);return!!e&&t.items.every(t=>e[t.id]===t.correctCategoryId)}case`timeline`:{let n=i(p);if(!n||n.length!==t.events.length||new Set(n).size!==n.length)return!1;let r=n.map(e=>t.events.find(t=>t.id===e));return r.every(e=>!!e)&&e.isTimelineOrderCorrect(r)}case`pin-on-image`:{let e=(Array.isArray(p)?p:[p]).map(d);if(e.length===0||e.some(e=>e===null))return!1;if(!t.multipleCorrect)return e.length===1&&t.hotspots.some(t=>f(e[0],t));let n=new Set;for(let r of e){let e=t.hotspots.findIndex(e=>f(r,e));if(e===-1)return!1;n.add(e)}return n.size>=(t.minCorrect??t.hotspots.length)}case`diagram-label`:{let n=s(p);return n?t.zones.every(r=>{let i=n[r.id];if(t.requireTyping){let n=t.labels.find(e=>e.id===r.correctLabelId);return!!n&&typeof i==`string`&&e.textAnswerMatches(i,n.text,t)}return i===r.correctLabelId}):!1}case`pin-on-model`:{let e=(Array.isArray(p)?p:[p]).filter(e=>typeof e==`string`);if(e.length===0)return!1;let n=e=>t.hotspots.findIndex(t=>e.toLowerCase().includes(t.targetName.toLowerCase()));if(!t.multipleCorrect)return e.length===1&&n(e[0])!==-1;let r=new Set;for(let t of e){let e=n(t);if(e===-1)return!1;r.add(e)}return r.size>=(t.minCorrect??t.hotspots.length)}case`chess-puzzle`:{if(!Array.isArray(p)||p.some(e=>typeof e!=`string`))return!1;let e=p.map(u);return t.correctAnswers.some(t=>t.length===e.length&&t.every((t,n)=>u(t)===e[n]))}case`flashcard`:{let e=n(p);return e===3||e===4}case`note`:case`open-ended`:throw Error(`checkAnswer: "${t.type}" items cannot be checked automatically.`);default:throw Error(`checkAnswer: unsupported item type "${t.type}".`)}}function n(e){if(typeof e==`number`)return Number.isFinite(e)?e:null;if(typeof e!=`string`)return null;let t=e.trim().replace(/\s+/g,``).replace(`,`,`.`);return/^[-+]?(\d+\.?\d*|\.\d+)(e[-+]?\d+)?$/i.test(t)?Number(t):null}function r(e){return typeof e==`boolean`?e:e===`true`||e!==`false`&&null}function i(e){return Array.isArray(e)&&e.every(e=>typeof e==`string`)?e:null}function a(e){return Array.isArray(e)&&e.every(e=>Array.isArray(e)&&e.length===2&&typeof e[0]==`string`&&typeof e[1]==`string`)?e:null}var o=(e,t)=>`${e}\u0000${t}`;function s(e){return e&&typeof e==`object`&&!Array.isArray(e)?e:null}function c(e,t){let n=new Set(e),r=new Set(t);return n.size===e.length&&n.size===r.size&&[...n].every(e=>r.has(e))}function l(e){return e.trim().replace(/^\$\$?|\$\$?$/g,``).replace(/\s+/g,``)}function u(e){return e.trim().replace(/[+#!?]+$/g,``).replace(/^0-0(-0)?$/,e=>e.replace(/0/g,`O`)).replace(/^([a-h](?:x[a-h])?[18])([QRBN])$/,`$1=$2`)}function d(e){let t=s(e);return t&&typeof t.x==`number`&&typeof t.y==`number`?{x:t.x,y:t.y}:null}function f(e,t){switch(t.type){case`rect`:return e.x>=t.x&&e.x<=t.x+t.width&&e.y>=t.y&&e.y<=t.y+t.height;case`circle`:return(e.x-t.x)**2+(e.y-t.y)**2<=t.radius**2;case`polygon`:{let n=!1,r=t.points;for(let t=0,i=r.length-1;t<r.length;i=t++)r[t].y>e.y!=r[i].y>e.y&&e.x<(r[i].x-r[t].x)*(e.y-r[t].y)/(r[i].y-r[t].y)+r[t].x&&(n=!n);return n}}}exports.checkAnswer=t;
@@ -24,7 +24,7 @@ function n(n, m) {
24
24
  case "math-input": {
25
25
  if (typeof m != "string") return !1;
26
26
  let e = u(m);
27
- return [n.correctAnswer, ...n.alternativeAnswers ?? []].some((t) => u(t) === e);
27
+ return n.correctAnswers.some((t) => u(t) === e);
28
28
  }
29
29
  case "sort-items": {
30
30
  let e = a(m);
package/dist/index.d.ts CHANGED
@@ -1,20 +1,20 @@
1
- export * from './src/oqse';
2
- export * from './src/oqseValidation';
3
- export * from './src/manifest';
4
- export * from './src/manifestValidation';
5
- export * from './src/progress';
6
- export * from './src/progressValidation';
7
- export * from './src/header';
8
- export * from './src/headerValidation';
9
- export * from './src/utils';
10
- export * from './src/richTextProcessor';
11
- export * from './src/markdownSet';
12
- export { shiftHeadings } from './src/markdownUtils';
13
- export * from './src/fileValidation';
14
- export * from './src/richTextFields';
15
- export * from './src/assets';
16
- export * from './src/answers';
17
- export * from './src/checkAnswer';
18
- export * from './src/plainText';
19
- export * from './src/features';
20
- export * from './src/compatibility';
1
+ export * from './src/oqse.js';
2
+ export * from './src/oqseValidation.js';
3
+ export * from './src/manifest.js';
4
+ export * from './src/manifestValidation.js';
5
+ export * from './src/progress.js';
6
+ export * from './src/progressValidation.js';
7
+ export * from './src/header.js';
8
+ export * from './src/headerValidation.js';
9
+ export * from './src/utils.js';
10
+ export * from './src/richTextProcessor.js';
11
+ export * from './src/markdownSet.js';
12
+ export { shiftHeadings } from './src/markdownUtils.js';
13
+ export * from './src/fileValidation.js';
14
+ export * from './src/richTextFields.js';
15
+ export * from './src/assets.js';
16
+ export * from './src/answers.js';
17
+ export * from './src/checkAnswer.js';
18
+ export * from './src/plainText.js';
19
+ export * from './src/features.js';
20
+ export * from './src/compatibility.js';
@@ -1 +1 @@
1
- const e=require("./utils.cjs"),t=require("./answers.cjs");let n=require("zod");var r=n.z.string().uuid({message:`Invalid UUID format`}),i=n.z.string().min(2,`Language code must have at least 2 characters`).regex(/^[a-z]{2,3}(-[A-Z][a-z]{3})?(-[A-Z]{2})?$/,`Invalid BCP 47 format (e.g. "cs", "en-US")`),a=n.z.string().min(1,`License identifier must not be empty`),o=n.z.string().regex(/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?(Z|[+-]\d{2}:\d{2})?)?$/,`Invalid ISO 8601 format (e.g. "2025-11-21T14:30:00Z")`),s=n.z.string().regex(/^-?\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?(Z|[+-]\d{2}:\d{2})?)?$/,`Invalid date (e.g. "1969-07-20", or "-0480-08-01" for 480 BC)`).refine(t.isTimelineDate,`Year 0000 does not exist (1 BC is "-0001", AD 1 is "0001")`),c=n.z.string().url({message:`Must be a valid absolute URL`}),l=n.z.string().regex(/^[a-z0-9_-]+$/,`Asset key must contain only lowercase letters, numbers, hyphens and underscores`),u=n.z.string().refine(e=>e.trim().length>0,`Text must not be empty`).refine(e=>!/<asset:[^>]*>/i.test(e),`Plain Text fields must not contain media tags (<asset:key />)`),d=u.refine(e=>!/^\s*\$|\$\s*$/.test(e),`LaTeX answer must not be wrapped in $ delimiters (write "2x + 2", not "$2x + 2$")`),f=n.z.string().min(1,`Content must not be empty`),p=n.z.string().max(1e4,`Question must not be longer than 10000 characters`).optional(),m=n.z.string().regex(/^[a-zA-Z0-9_-]{1,64}$/,`Token must be alphanumeric, max 64 chars`),h=n.z.string().regex(/^[A-Za-z0-9_.-]{1,64}$/,`Choice ID must contain only letters, digits, "_", "-" and "." (max 64 chars)`),g=n.z.looseObject({id:h,text:f.max(2e3,`Choice text must not be longer than 2000 characters`),explanation:f.max(1e4,`Choice explanation must not be longer than 10000 characters`).optional()});function _(e,t){return n.z.array(g).min(e,`Must have at least ${e} ${t}`).max(100,`Maximum 100 ${t}`).refine(e=>new Set(e.map(e=>e.id)).size===e.length,{message:`Choice IDs must be unique within the list`})}var v=e=>new Set(e.map(e=>e.id)),ee=n.z.string().max(128,`Skill ID must not be longer than 128 characters`).regex(/^[a-z0-9][a-z0-9_-]*(\.[a-z0-9][a-z0-9_-]*)*$/,`Skill ID must be lowercase segments joined by dots (e.g. "chess.tactics.fork")`),y=n.z.looseObject({lang:i,value:n.z.string().min(1,`Subtitle URI must not be empty`).regex(/\.(vtt|srt)(\?.*)?$/i,`Subtitle URI must end with .vtt or .srt`),label:n.z.string().optional(),kind:n.z.enum([`captions`,`subtitles`,`descriptions`]).optional()}),b=n.z.looseObject({type:n.z.enum([`image`,`audio`,`video`,`model`]),value:n.z.string().min(1,`Media URI must not be empty`),mimeType:n.z.string().optional(),altText:n.z.string().optional(),transcript:f.optional(),caption:f.optional(),width:n.z.number().int().positive().optional(),height:n.z.number().int().positive().optional(),start:n.z.number().nonnegative().optional(),end:n.z.number().positive().optional(),loop:n.z.boolean().optional(),subtitles:n.z.array(y).optional(),license:a.optional(),attribution:n.z.string().optional(),checksums:n.z.record(n.z.string(),n.z.string()).optional()}).refine(e=>e.start!==void 0&&e.end!==void 0?e.start<e.end:!0,{message:`Start time must be less than end time`,path:[`end`]}),x=n.z.record(l,b),S=n.z.looseObject({name:u,role:n.z.string().optional(),email:n.z.string().email(`Invalid email format`).optional(),url:c.optional()}),C=n.z.enum([`url`,`doi`,`isbn`,`pdf`,`textbook`,`video`,`audio`,`image`,`model`,`other`]),w=n.z.looseObject({id:u,type:C,value:u,title:u,description:n.z.string().optional(),authors:n.z.array(u).optional(),publishedDate:o.optional(),retrievedAt:o.optional(),license:a.optional()}).refine(e=>{if([`url`,`pdf`,`video`,`audio`,`image`].includes(e.type))try{return new URL(e.value),!0}catch{return!1}return!0},{message:`For url, pdf, video, audio and image types, value must be a valid URL`,path:[`value`]}),T=n.z.looseObject({id:u,location:n.z.string().optional(),quote:f.optional()}),te=n.z.looseObject({wikidataId:n.z.string().regex(/^Q\d+$/,`Wikidata ID must have format Q followed by number`).optional(),description:n.z.string().optional()}),ne=n.z.record(n.z.string(),te),re=n.z.string().regex(/^(x-)?[a-z0-9]+(-[a-z0-9]+)*$/,`Feature must be lowercase words joined by hyphens (e.g. "latex", "x-my-feature")`),E=n.z.string().regex(/^(x-)?[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$/,`Property must be a key such as "hints" or "x-my-property"`),D=n.z.looseObject({features:n.z.array(re).optional(),latexPackages:n.z.array(n.z.string().min(1,`Package name must not be empty`)).optional(),itemProperties:n.z.array(E).optional(),metaProperties:n.z.array(E).optional()}),O=n.z.looseObject({lang:i,id:r,title:u,downloadUrl:c.optional()}),k=n.z.looseObject({id:r,title:u,downloadUrl:c.optional()}),A=n.z.enum([`remember`,`understand`,`apply`,`analyze`,`evaluate`,`create`]),j=n.z.enum([`low`,`medium`,`high`]),M=n.z.looseObject({bloomLevel:A.optional(),irtDifficulty:n.z.number().optional(),irtDiscrimination:n.z.number().optional(),irtGuessing:n.z.number().min(0).max(1,`IRT guessing must be between 0 and 1`).optional(),avgTime:n.z.number().positive().optional(),cognitiveLoad:j.optional(),partialCredit:n.z.boolean().optional(),penaltyPerWrong:n.z.number().min(0).max(1,`Penalty must be between 0 and 1`).optional()}),N=n.z.looseObject({id:r,language:i,title:u.max(500,`Title must not be longer than 500 characters`),description:f.max(5e3,`Description must not be longer than 5000 characters`).optional(),thumbnail:l.optional(),assets:x.optional(),ageMin:n.z.number().int().nonnegative().optional(),ageMax:n.z.number().int().nonnegative().optional(),subject:n.z.string().optional(),createdAt:o,updatedAt:o,author:S.optional(),contributors:n.z.array(S).optional(),license:a.optional(),licenseUrl:c.optional(),requirements:D.optional(),tags:n.z.array(u).optional(),tagDefinitions:ne.optional(),translations:n.z.array(O).optional(),sourceMaterials:n.z.array(w).optional(),estimatedTime:n.z.number().positive().optional(),prerequisites:n.z.array(k).optional(),relatedSets:n.z.array(k).optional(),customData:n.z.record(n.z.string(),n.z.unknown()).optional(),appSpecific:n.z.record(n.z.string(),n.z.unknown()).optional()}).refine(e=>e.ageMin!==void 0&&e.ageMax!==void 0?e.ageMin<=e.ageMax:!0,{message:`Minimum age (ageMin) must be less than or equal to maximum age (ageMax)`,path:[`ageMax`]}).refine(e=>new Date(e.createdAt)<=new Date(e.updatedAt),{message:`Creation date (createdAt) must be before or equal to update date (updatedAt)`,path:[`updatedAt`]}),P=n.z.looseObject({id:r,type:n.z.string(),assets:x.optional(),lang:i.optional(),tags:n.z.array(u).optional(),skills:n.z.array(ee).max(20,`Maximum 20 skills per item`).optional(),topic:u.optional(),difficulty:n.z.number().int().min(1,`Difficulty must be at least 1`).max(5,`Difficulty must be at most 5`).optional(),timeLimit:n.z.number().positive(`Time limit must be a positive number`).optional(),hints:n.z.array(f.max(2e3,`Hint must not be longer than 2000 characters`)).max(20,`Maximum 20 hints per item`).optional(),explanation:f.max(1e4,`Explanation must not be longer than 10000 characters`).optional(),incorrectFeedback:f.optional(),sources:n.z.array(T).optional(),relatedItems:n.z.array(r).optional(),dependencyItems:n.z.array(r).optional(),pedagogy:M.optional(),customData:n.z.record(n.z.string(),n.z.unknown()).optional(),appSpecific:n.z.record(n.z.string(),n.z.unknown()).optional()}),F=n.z.looseObject({options:_(1,`options`),correctId:h,shuffle:n.z.boolean().optional()}).refine(e=>v(e.options).has(e.correctId),{message:`correctId references a non-existent option`,path:[`correctId`]}),I=n.z.looseObject({type:n.z.literal(`rect`),label:n.z.string().optional(),x:n.z.number().min(0).max(100,`X must be in range 0-100%`),y:n.z.number().min(0).max(100,`Y must be in range 0-100%`),width:n.z.number().min(0).max(100,`Width must be in range 0-100%`),height:n.z.number().min(0).max(100,`Height must be in range 0-100%`)}),L=n.z.looseObject({type:n.z.literal(`circle`),label:n.z.string().optional(),x:n.z.number().min(0).max(100,`X must be in range 0-100%`),y:n.z.number().min(0).max(100,`Y must be in range 0-100%`),radius:n.z.number().min(0).max(100,`Radius must be in range 0-100%`)}),R=n.z.looseObject({type:n.z.literal(`polygon`),label:n.z.string().optional(),points:n.z.array(n.z.looseObject({x:n.z.number().min(0).max(100),y:n.z.number().min(0).max(100)})).min(3,`Polygon must have at least 3 points`)}),z=n.z.looseObject({type:n.z.literal(`mesh`),label:n.z.string().optional(),targetName:u}),ie=n.z.discriminatedUnion(`type`,[I,L,R,z]),B=n.z.discriminatedUnion(`type`,[I,L,R]),V=n.z.looseObject({x:n.z.number(),y:n.z.number(),z:n.z.number()}),H=n.z.looseObject({position:V.optional(),target:V.optional()}),U=n.z.looseObject({id:h,text:f,correctCategoryId:h}),W=n.z.enum([`year`,`month`,`day`,`datetime`]),G=n.z.looseObject({id:u,text:f,date:s,precision:W.optional()}),K=n.z.union([I.extend({id:h,correctLabelId:h}),L.extend({id:h,correctLabelId:h}),R.extend({id:h,correctLabelId:h})]),q=n.z.looseObject({label:u,percentage:n.z.number().min(0,`Percentage must be non-negative`).max(100,`Percentage must not exceed 100`),description:n.z.string().optional()}),J=n.z.looseObject({criteria:n.z.array(q)}).refine(e=>{let t=e.criteria.reduce((e,t)=>e+t.percentage,0);return e.criteria.length===0||t>0},{message:`Sum of percentages of all criteria must be greater than 0`,path:[`criteria`]}),Y=P.extend({type:n.z.literal(`note`),title:n.z.string().optional(),content:f.max(1e5,`Content must not be longer than 100000 characters`),hiddenContent:f.max(1e5,`Hidden content must not be longer than 100000 characters`).optional()}),ae=P.extend({type:n.z.literal(`flashcard`),front:f.max(1e4,`Front side must not be longer than 10000 characters`),back:f.max(1e4,`Back side must not be longer than 10000 characters`)}),oe=P.extend({type:n.z.literal(`true-false`),question:f.max(1e4,`Question must not be longer than 10000 characters`),correctAnswer:n.z.boolean()}),se=P.extend({type:n.z.literal(`mcq-single`),question:f.max(1e4,`Question must not be longer than 10000 characters`),options:_(2,`options`),correctId:h,shuffle:n.z.boolean().optional()}).refine(e=>v(e.options).has(e.correctId),{message:`correctId references a non-existent option`,path:[`correctId`]}).refine(e=>new Set(e.options.map(e=>e.text.toLowerCase())).size===e.options.length,{message:`Options must not contain duplicates (case-insensitive)`,path:[`options`]}),ce=P.extend({type:n.z.literal(`mcq-multi`),question:f.max(1e4,`Question must not be longer than 10000 characters`),options:_(2,`options`),correctIds:n.z.array(h).min(1,`Must have at least 1 correct answer`),minSelections:n.z.number().int().positive().optional(),maxSelections:n.z.number().int().positive().optional(),shuffle:n.z.boolean().optional()}).refine(e=>e.correctIds.every(t=>v(e.options).has(t)),{message:`Some of correctIds reference a non-existent option`,path:[`correctIds`]}).refine(e=>new Set(e.correctIds).size===e.correctIds.length,{message:`correctIds contain duplicates`,path:[`correctIds`]}).refine(e=>!(e.minSelections&&e.maxSelections)||e.minSelections<=e.maxSelections,{message:`Minimum number of selections must be less than or equal to maximum`,path:[`maxSelections`]}).refine(e=>!e.maxSelections||e.maxSelections<=e.options.length,{message:`Maximum number of selections must not exceed number of options`,path:[`maxSelections`]}).refine(e=>!e.minSelections||e.minSelections<=e.options.length,{message:`minSelections cannot be greater than the number of options`,path:[`minSelections`]}).refine(e=>new Set(e.options.map(e=>e.text.toLowerCase())).size===e.options.length,{message:`Options must not contain duplicates (case-insensitive)`,path:[`options`]}),le=P.extend({type:n.z.literal(`short-answer`),question:f.max(1e4,`Question must not be longer than 10000 characters`),correctAnswers:n.z.array(u).min(1,`Must have at least 1 correct answer`),caseSensitive:n.z.boolean().optional(),ignoreDiacritics:n.z.boolean().optional(),acceptPartial:n.z.boolean().optional()}),ue=P.extend({type:n.z.literal(`fill-in-blanks`),question:p,text:f.max(1e4,`Text must not be longer than 10000 characters`),blanks:n.z.record(m,n.z.array(u).min(1,`Each blank must have at least 1 correct answer`)),caseSensitive:n.z.boolean().optional(),ignoreDiacritics:n.z.boolean().optional(),acceptPartial:n.z.boolean().optional()}).refine(e=>Object.keys(e.blanks).length>0,{message:`Text must contain at least 1 blank to fill`,path:[`blanks`]}).refine(e=>{let t=/<blank:([a-zA-Z0-9_-]{1,64})\s*\/>/g,n=new Set,r;for(;(r=t.exec(e.text))!==null;)r[1]&&n.add(r[1]);let i=new Set(Object.keys(e.blanks));for(let e of n)if(!i.has(e))return!1;for(let e of i)if(!n.has(e))return!1;return!0},{message:`All tokens in text must have definition in blanks and all definitions must be used in text`,path:[`blanks`]}),de=P.extend({type:n.z.literal(`fill-in-select`),question:p,text:f.max(1e4,`Text must not be longer than 10000 characters`),blanks:n.z.record(m,F)}).refine(e=>Object.keys(e.blanks).length>0,{message:`Text must contain at least 1 blank to select`,path:[`blanks`]}).refine(e=>{let t=/<blank:([a-zA-Z0-9_-]{1,64})\s*\/>/g,n=new Set,r;for(;(r=t.exec(e.text))!==null;)r[1]&&n.add(r[1]);let i=new Set(Object.keys(e.blanks));for(let e of n)if(!i.has(e))return!1;for(let e of i)if(!n.has(e))return!1;return!0},{message:`All tokens in text must have definition in blanks and all definitions must be used in text`,path:[`blanks`]}),fe=P.extend({type:n.z.literal(`match-pairs`),question:p,prompts:_(2,`prompts`),matches:_(2,`matches`),pairs:n.z.record(h,h)}).refine(e=>e.prompts.every(t=>t.id in e.pairs)&&Object.keys(e.pairs).every(t=>v(e.prompts).has(t)),{message:`pairs must have exactly one entry for every prompt`,path:[`pairs`]}).refine(e=>Object.values(e.pairs).every(t=>v(e.matches).has(t)),{message:`Some pair references a non-existent match`,path:[`pairs`]}).refine(e=>new Set(Object.values(e.pairs)).size===Object.values(e.pairs).length,{message:`Each match may belong to only one prompt`,path:[`pairs`]}),pe=P.extend({type:n.z.literal(`match-complex`),question:p,leftItems:_(1,`items on the left`),rightItems:_(1,`items on the right`),connections:n.z.array(n.z.tuple([h,h])).min(1,`Must have at least 1 connection`),minCorrect:n.z.number().int().positive().optional()}).refine(e=>e.connections.every(([t,n])=>v(e.leftItems).has(t)&&v(e.rightItems).has(n)),{message:`Some connection references a non-existent item`,path:[`connections`]}).refine(e=>new Set(e.connections.map(([e,t])=>`${e}\u0000${t}`)).size===e.connections.length,{message:`Connections contain duplicates`,path:[`connections`]}).refine(e=>!e.minCorrect||e.minCorrect<=e.connections.length,{message:`Minimum correct answers must not exceed total number of connections`,path:[`minCorrect`]}),me=P.extend({type:n.z.literal(`sort-items`),question:f.max(1e4,`Question must not be longer than 10000 characters`),items:_(2,`items to sort`),correctOrder:n.z.array(h)}).refine(e=>e.correctOrder.length===e.items.length&&new Set(e.correctOrder).size===e.items.length&&e.correctOrder.every(t=>v(e.items).has(t)),{message:`correctOrder must list every item ID exactly once`,path:[`correctOrder`]}),he=P.extend({type:n.z.literal(`slider`),question:f.max(1e4,`Question must not be longer than 10000 characters`),min:n.z.number(),max:n.z.number(),step:n.z.number().positive(`Step must be a positive number`),correctAnswer:n.z.number(),tolerance:n.z.number().nonnegative(`Tolerance must be non-negative`).optional(),unit:n.z.string().optional()}).refine(e=>e.min<e.max,{message:`Minimum value must be less than maximum value`,path:[`max`]}).refine(e=>e.correctAnswer>=e.min&&e.correctAnswer<=e.max,{message:`Correct answer must be in range min-max`,path:[`correctAnswer`]}).refine(e=>{let t=(e.correctAnswer-e.min)/e.step;return Math.abs(t-Math.round(t))<1e-4},{message:`Correct answer must be reachable by defined step`,path:[`correctAnswer`]}).refine(e=>(e.tolerance??0)<=(e.max-e.min)/2,{message:`Tolerance must not be greater than half of value range`,path:[`tolerance`]}),ge=P.extend({type:n.z.literal(`pin-on-image`),question:f.max(1e4,`Question must not be longer than 10000 characters`),targetAsset:l,hotspots:n.z.array(B).min(1,`Must have at least 1 hotspot`),multipleCorrect:n.z.boolean().optional(),minCorrect:n.z.number().int().positive().optional()}).refine(e=>!e.minCorrect||e.minCorrect<=e.hotspots.length,{message:`Minimum correct answers must not exceed number of hotspots`,path:[`minCorrect`]}),_e=P.extend({type:n.z.literal(`categorize`),question:f.max(1e4,`Question must not be longer than 10000 characters`),categories:_(2,`categories`),items:n.z.array(U).min(1,`Must have at least 1 item to categorize`)}).refine(e=>new Set(e.categories.map(e=>e.text.toLowerCase())).size===e.categories.length,{message:`Categories must not contain duplicates (case-insensitive)`,path:[`categories`]}).refine(e=>e.items.every(t=>v(e.categories).has(t.correctCategoryId)),{message:`Some item references a non-existent category`,path:[`items`]}),ve=P.extend({type:n.z.literal(`timeline`),question:f.max(1e4,`Question must not be longer than 10000 characters`),events:n.z.array(G).min(2,`Must have at least 2 events`),shuffle:n.z.boolean().optional()}),ye=P.extend({type:n.z.literal(`matrix`),question:f.max(1e4,`Question must not be longer than 10000 characters`),rows:_(1,`rows`),columns:_(1,`columns`),correctCells:n.z.array(n.z.tuple([h,h])).min(1,`Must have at least 1 correct cell`),multiplePerRow:n.z.boolean().optional()}).refine(e=>e.correctCells.every(([t,n])=>v(e.rows).has(t)&&v(e.columns).has(n)),{message:`Some cell references a non-existent row or column`,path:[`correctCells`]}).refine(e=>new Set(e.correctCells.map(([e,t])=>`${e}\u0000${t}`)).size===e.correctCells.length,{message:`Correct cells contain duplicates`,path:[`correctCells`]}).refine(e=>e.multiplePerRow||new Set(e.correctCells.map(([e])=>e)).size===e.correctCells.length,{message:`If multiplePerRow is false, each row may have maximum 1 correct cell`,path:[`correctCells`]}),be=P.extend({type:n.z.literal(`math-input`),question:f.max(1e4,`Question must not be longer than 10000 characters`),correctAnswer:d,alternativeAnswers:n.z.array(d).optional(),tolerance:n.z.number().nonnegative().optional()}),xe=P.extend({type:n.z.literal(`diagram-label`),question:f.max(1e4,`Question must not be longer than 10000 characters`),targetAsset:l,labels:_(1,`labels`),caseSensitive:n.z.boolean().optional(),ignoreDiacritics:n.z.boolean().optional(),acceptPartial:n.z.boolean().optional(),requireTyping:n.z.boolean().optional(),zones:n.z.array(K).min(1,`Must have at least 1 zone`)}).refine(e=>e.zones.every(t=>v(e.labels).has(t.correctLabelId)),{message:`Some zone references a non-existent label`,path:[`zones`]}).refine(e=>new Set(e.zones.map(e=>e.id)).size===e.zones.length,{message:`Zone IDs must be unique within the item`,path:[`zones`]}),Se=P.extend({type:n.z.literal(`open-ended`),question:f.max(1e4,`Question must not be longer than 10000 characters`),minWords:n.z.number().int().positive().optional(),maxWords:n.z.number().int().positive().optional(),sampleAnswer:f.optional(),rubric:J.optional()}).refine(e=>e.minWords&&e.maxWords?e.minWords<=e.maxWords:!0,{message:`Minimum word count must be less than or equal to maximum`,path:[`maxWords`]}),Ce=P.extend({type:n.z.literal(`numeric-input`),question:f.max(1e4,`Question must not be longer than 10000 characters`),correctAnswer:n.z.number(),tolerance:n.z.number().nonnegative(`Tolerance must be non-negative`).optional(),unit:n.z.string().optional()}),X=P.extend({type:n.z.literal(`pin-on-model`),question:f.max(1e4,`Question must not be longer than 10000 characters`),targetAsset:l,hotspots:n.z.array(z).min(1,`Must have at least 1 hotspot`),multipleCorrect:n.z.boolean().optional(),minCorrect:n.z.number().int().positive().optional(),camera:H.optional()}).refine(e=>!e.minCorrect||e.minCorrect<=e.hotspots.length,{message:`Minimum correct answers must not exceed number of hotspots`,path:[`minCorrect`]}),we=P.extend({type:n.z.literal(`chess-puzzle`),question:f.max(1e4,`Question must not be longer than 10000 characters`),fen:u,correctAnswers:n.z.array(n.z.array(u).min(1,`Move sequence must not be empty`)).min(1,`Must have at least 1 correct move sequence`),elo:n.z.number().int().nonnegative(`ELO must be non-negative integer`).optional()}),Z=n.z.discriminatedUnion(`type`,[Y,ae,oe,se,ce,le,ue,de,fe,pe,me,he,ge,_e,ve,ye,be,xe,Se,Ce,X,we]),Te=n.z.string().regex(/^x-[a-z0-9]+(?:-[a-z0-9]+)*$/,`Custom item type must start with "x-" followed by lowercase letters, digits and hyphens`),Q=P.extend({type:Te}),Ee=n.z.union([Z,Q]);function $(e){let t=e?.type;return typeof t==`string`&&t.startsWith(`x-`)?Q:Z}var De=n.z.looseObject({$schema:n.z.string().url().optional(),version:n.z.string().regex(/^\d+\.\d+$/,`Version must be in MAJOR.MINOR format (e.g. "1.0")`),meta:N,items:n.z.array(Ee).max(1e4,`Maximum 10000 items per set`)});function Oe(e){let t=e instanceof Error?e.message:`OQSE Security Error: Maximum nesting depth exceeded limit of 10 levels.`;return new n.z.ZodError([{code:`custom`,message:t,path:[],input:void 0}])}function ke(t){return e.validateJsonDepth(t,10),$(t).parse(t)}function Ae(t){try{e.validateJsonDepth(t,10)}catch(e){return{success:!1,error:Oe(e)}}let n=$(t).safeParse(t);return n.success?{success:!0,data:n.data}:{success:!1,error:n.error}}exports.AbsoluteURLSchema=c,exports.AnyOQSEItemSchema=Ee,exports.AssetDictionarySchema=x,exports.AssetKeySchema=l,exports.BaseItemSchema=P,exports.BlankTokenSchema=m,exports.BloomLevelSchema=A,exports.CameraSetupSchema=H,exports.CategorizeEntrySchema=U,exports.CategorizeItemSchema=_e,exports.ChessPuzzleItemSchema=we,exports.ChoiceIdSchema=h,exports.ChoiceSchema=g,exports.CircleHotspotSchema=L,exports.CognitiveLoadSchema=j,exports.CustomItemSchema=Q,exports.CustomItemTypeSchema=Te,exports.DiagramLabelItemSchema=xe,exports.DiagramZoneSchema=K,exports.FeatureKeySchema=re,exports.FeatureProfileSchema=D,exports.FillInBlanksItemSchema=ue,exports.FillInSelectItemSchema=de,exports.FlashcardItemSchema=ae,exports.Hotspot2DSchema=B,exports.HotspotObjectSchema=ie,exports.ISO8601DateTimeSchema=o,exports.LanguageCodeSchema=i,exports.LinkedSetObjectSchema=k,exports.MCQMultiItemSchema=ce,exports.MCQSingleItemSchema=se,exports.MatchComplexItemSchema=pe,exports.MatchPairsItemSchema=fe,exports.MathInputItemSchema=be,exports.MatrixItemSchema=ye,exports.MediaObjectSchema=b,exports.MeshHotspotSchema=z,exports.NoteItemSchema=Y,exports.NumericInputItemSchema=Ce,exports.OQSEFileSchema=De,exports.OQSEItemSchema=Z,exports.OQSEMetaSchema=N,exports.OpenEndedItemSchema=Se,exports.OptionalRichContentSchema=p,exports.PedagogySchema=M,exports.PersonObjectSchema=S,exports.PinOnImageItemSchema=ge,exports.PinOnModelItemSchema=X,exports.PlainTextSchema=u,exports.PolygonHotspotSchema=R,exports.PropertyKeySchema=E,exports.RawLatexSchema=d,exports.RectHotspotSchema=I,exports.RichContentSchema=f,exports.RubricCriterionSchema=q,exports.RubricSchema=J,exports.SPDXLicenseSchema=a,exports.SelectBlankObjectSchema=F,exports.ShortAnswerItemSchema=le,exports.SkillIdSchema=ee,exports.SliderItemSchema=he,exports.SortItemsItemSchema=me,exports.SourceMaterialSchema=w,exports.SourceMaterialTypeSchema=C,exports.SourceReferenceSchema=T,exports.SubtitleTrackSchema=y,exports.TagDefinitionDictionarySchema=ne,exports.TagDefinitionSchema=te,exports.TimelineDateSchema=s,exports.TimelineEventSchema=G,exports.TimelineItemSchema=ve,exports.TimelinePrecisionSchema=W,exports.TranslationObjectSchema=O,exports.TrueFalseItemSchema=oe,exports.UUIDSchema=r,exports.Vector3Schema=V,exports.choiceList=_,exports.getItemSchema=$,exports.safeValidateOQSEItem=Ae,exports.validateOQSEItem=ke;
1
+ const e=require("./utils.cjs"),t=require("./answers.cjs");let n=require("zod");var r=n.z.string().uuid({message:`Invalid UUID format`}),i=n.z.string().min(2,`Language code must have at least 2 characters`).regex(/^[a-z]{2,3}(-[A-Z][a-z]{3})?(-[A-Z]{2})?$/,`Invalid BCP 47 format (e.g. "cs", "en-US")`),a=n.z.string().min(1,`License identifier must not be empty`),o=n.z.string().regex(/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?(Z|[+-]\d{2}:\d{2})?)?$/,`Invalid ISO 8601 format (e.g. "2025-11-21T14:30:00Z")`),s=n.z.string().regex(/^-?\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?(Z|[+-]\d{2}:\d{2})?)?$/,`Invalid date (e.g. "1969-07-20", or "-0480-08-01" for 480 BC)`).refine(t.isTimelineDate,`Year 0000 does not exist (1 BC is "-0001", AD 1 is "0001")`),c=n.z.string().url({message:`Must be a valid absolute URL`}),l=n.z.string().regex(/^[a-z0-9_-]+$/,`Asset key must contain only lowercase letters, numbers, hyphens and underscores`),u=n.z.string().refine(e=>e.trim().length>0,`Text must not be empty`).refine(e=>!/<asset:[^>]*>/i.test(e),`Plain Text fields must not contain media tags (<asset:key />)`),d=u.refine(e=>!/^\s*\$|\$\s*$/.test(e),`LaTeX answer must not be wrapped in $ delimiters (write "2x + 2", not "$2x + 2$")`),f=n.z.string().min(1,`Content must not be empty`),p=n.z.string().max(1e4,`Question must not be longer than 10000 characters`).optional(),m=n.z.string().regex(/^[a-zA-Z0-9_-]{1,64}$/,`Token must be alphanumeric, max 64 chars`),h=n.z.string().regex(/^[A-Za-z0-9_.-]{1,64}$/,`Choice ID must contain only letters, digits, "_", "-" and "." (max 64 chars)`),g=n.z.looseObject({id:h,text:f.max(2e3,`Choice text must not be longer than 2000 characters`),explanation:f.max(1e4,`Choice explanation must not be longer than 10000 characters`).optional()});function _(e,t){return n.z.array(g).min(e,`Must have at least ${e} ${t}`).max(100,`Maximum 100 ${t}`).refine(e=>new Set(e.map(e=>e.id)).size===e.length,{message:`Choice IDs must be unique within the list`})}var v=e=>new Set(e.map(e=>e.id)),y=n.z.string().max(128,`Skill ID must not be longer than 128 characters`).regex(/^[a-z0-9][a-z0-9_-]*(\.[a-z0-9][a-z0-9_-]*)*$/,`Skill ID must be lowercase segments joined by dots (e.g. "chess.tactics.fork")`),b=n.z.looseObject({lang:i,value:n.z.string().min(1,`Subtitle URI must not be empty`).regex(/\.(vtt|srt)(\?.*)?$/i,`Subtitle URI must end with .vtt or .srt`),label:n.z.string().optional(),kind:n.z.enum([`captions`,`subtitles`,`descriptions`]).optional()}),x=n.z.looseObject({type:n.z.enum([`image`,`audio`,`video`,`model`]),value:n.z.string().min(1,`Media URI must not be empty`),mimeType:n.z.string().optional(),altText:n.z.string().optional(),transcript:f.optional(),caption:f.optional(),width:n.z.number().int().positive().optional(),height:n.z.number().int().positive().optional(),start:n.z.number().nonnegative().optional(),end:n.z.number().positive().optional(),loop:n.z.boolean().optional(),subtitles:n.z.array(b).optional(),license:a.optional(),attribution:n.z.string().optional(),checksums:n.z.record(n.z.string(),n.z.string()).optional()}).refine(e=>e.start!==void 0&&e.end!==void 0?e.start<e.end:!0,{message:`Start time must be less than end time`,path:[`end`]}),S=n.z.record(l,x),C=n.z.looseObject({name:u,role:n.z.string().optional(),email:n.z.string().email(`Invalid email format`).optional(),url:c.optional()}),w=n.z.enum([`url`,`doi`,`isbn`,`pdf`,`textbook`,`video`,`audio`,`image`,`model`,`other`]),T=n.z.looseObject({id:u,type:w,value:u,title:u,description:n.z.string().optional(),authors:n.z.array(u).optional(),publishedDate:o.optional(),retrievedAt:o.optional(),license:a.optional()}).refine(e=>{if([`url`,`pdf`,`video`,`audio`,`image`].includes(e.type))try{return new URL(e.value),!0}catch{return!1}return!0},{message:`For url, pdf, video, audio and image types, value must be a valid URL`,path:[`value`]}),ee=n.z.looseObject({id:u,location:n.z.string().optional(),quote:f.optional()}),te=n.z.looseObject({wikidataId:n.z.string().regex(/^Q\d+$/,`Wikidata ID must have format Q followed by number`).optional(),description:n.z.string().optional()}),ne=n.z.record(n.z.string(),te),re=n.z.string().regex(/^(x-)?[a-z0-9]+(-[a-z0-9]+)*$/,`Feature must be lowercase words joined by hyphens (e.g. "latex", "x-my-feature")`),E=n.z.string().regex(/^(x-)?[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$/,`Property must be a key such as "hints" or "x-my-property"`),D=n.z.looseObject({features:n.z.array(re).optional(),latexPackages:n.z.array(n.z.string().min(1,`Package name must not be empty`)).optional(),itemProperties:n.z.array(E).optional(),metaProperties:n.z.array(E).optional()}),O=n.z.looseObject({lang:i,id:r,title:u,downloadUrl:c.optional()}),k=n.z.looseObject({id:r,title:u,downloadUrl:c.optional()}),A=n.z.enum([`remember`,`understand`,`apply`,`analyze`,`evaluate`,`create`]),j=n.z.enum([`low`,`medium`,`high`]),M=n.z.looseObject({bloomLevel:A.optional(),irtDifficulty:n.z.number().optional(),irtDiscrimination:n.z.number().optional(),irtGuessing:n.z.number().min(0).max(1,`IRT guessing must be between 0 and 1`).optional(),avgTime:n.z.number().positive().optional(),cognitiveLoad:j.optional(),partialCredit:n.z.boolean().optional(),penaltyPerWrong:n.z.number().min(0).max(1,`Penalty must be between 0 and 1`).optional()}),N=n.z.looseObject({id:r,language:i,title:u.max(500,`Title must not be longer than 500 characters`),description:f.max(5e3,`Description must not be longer than 5000 characters`).optional(),thumbnail:l.optional(),assets:S.optional(),ageMin:n.z.number().int().nonnegative().optional(),ageMax:n.z.number().int().nonnegative().optional(),subject:n.z.string().optional(),createdAt:o,updatedAt:o,author:C.optional(),contributors:n.z.array(C).optional(),license:a.optional(),licenseUrl:c.optional(),requirements:D.optional(),tags:n.z.array(u).optional(),tagDefinitions:ne.optional(),translations:n.z.array(O).optional(),sourceMaterials:n.z.array(T).optional(),estimatedTime:n.z.number().positive().optional(),prerequisites:n.z.array(k).optional(),relatedSets:n.z.array(k).optional(),customData:n.z.record(n.z.string(),n.z.unknown()).optional(),appSpecific:n.z.record(n.z.string(),n.z.unknown()).optional()}).refine(e=>e.ageMin!==void 0&&e.ageMax!==void 0?e.ageMin<=e.ageMax:!0,{message:`Minimum age (ageMin) must be less than or equal to maximum age (ageMax)`,path:[`ageMax`]}).refine(e=>new Date(e.createdAt)<=new Date(e.updatedAt),{message:`Creation date (createdAt) must be before or equal to update date (updatedAt)`,path:[`updatedAt`]}),P=n.z.looseObject({id:r,type:n.z.string(),assets:S.optional(),lang:i.optional(),tags:n.z.array(u).optional(),skills:n.z.array(y).max(20,`Maximum 20 skills per item`).optional(),topic:u.optional(),difficulty:n.z.number().int().min(1,`Difficulty must be at least 1`).max(5,`Difficulty must be at most 5`).optional(),timeLimit:n.z.number().positive(`Time limit must be a positive number`).optional(),hints:n.z.array(f.max(2e3,`Hint must not be longer than 2000 characters`)).max(20,`Maximum 20 hints per item`).optional(),explanation:f.max(1e4,`Explanation must not be longer than 10000 characters`).optional(),incorrectFeedback:f.optional(),sources:n.z.array(ee).optional(),relatedItems:n.z.array(r).optional(),dependencyItems:n.z.array(r).optional(),pedagogy:M.optional(),customData:n.z.record(n.z.string(),n.z.unknown()).optional(),appSpecific:n.z.record(n.z.string(),n.z.unknown()).optional()}),F=n.z.looseObject({options:_(1,`options`),correctId:h,shuffle:n.z.boolean().optional()}).refine(e=>v(e.options).has(e.correctId),{message:`correctId references a non-existent option`,path:[`correctId`]}),I=n.z.looseObject({type:n.z.literal(`rect`),label:n.z.string().optional(),x:n.z.number().min(0).max(100,`X must be in range 0-100%`),y:n.z.number().min(0).max(100,`Y must be in range 0-100%`),width:n.z.number().min(0).max(100,`Width must be in range 0-100%`),height:n.z.number().min(0).max(100,`Height must be in range 0-100%`)}),L=n.z.looseObject({type:n.z.literal(`circle`),label:n.z.string().optional(),x:n.z.number().min(0).max(100,`X must be in range 0-100%`),y:n.z.number().min(0).max(100,`Y must be in range 0-100%`),radius:n.z.number().min(0).max(100,`Radius must be in range 0-100%`)}),R=n.z.looseObject({type:n.z.literal(`polygon`),label:n.z.string().optional(),points:n.z.array(n.z.looseObject({x:n.z.number().min(0).max(100),y:n.z.number().min(0).max(100)})).min(3,`Polygon must have at least 3 points`)}),z=n.z.looseObject({type:n.z.literal(`mesh`),label:n.z.string().optional(),targetName:u}),ie=n.z.discriminatedUnion(`type`,[I,L,R,z]),B=n.z.discriminatedUnion(`type`,[I,L,R]),V=n.z.looseObject({x:n.z.number(),y:n.z.number(),z:n.z.number()}),H=n.z.looseObject({position:V.optional(),target:V.optional()}),U=n.z.looseObject({id:h,text:f,correctCategoryId:h}),W=n.z.enum([`year`,`month`,`day`,`datetime`]),G=n.z.looseObject({id:u,text:f,date:s,precision:W.optional()}),K=n.z.union([I.extend({id:h,correctLabelId:h}),L.extend({id:h,correctLabelId:h}),R.extend({id:h,correctLabelId:h})]),q=n.z.looseObject({label:u,percentage:n.z.number().min(0,`Percentage must be non-negative`).max(100,`Percentage must not exceed 100`),description:n.z.string().optional()}),J=n.z.looseObject({criteria:n.z.array(q)}).refine(e=>{let t=e.criteria.reduce((e,t)=>e+t.percentage,0);return e.criteria.length===0||t>0},{message:`Sum of percentages of all criteria must be greater than 0`,path:[`criteria`]}),ae=P.extend({type:n.z.literal(`note`),title:n.z.string().optional(),content:f.max(1e5,`Content must not be longer than 100000 characters`),hiddenContent:f.max(1e5,`Hidden content must not be longer than 100000 characters`).optional()}),Y=P.extend({type:n.z.literal(`flashcard`),front:f.max(1e4,`Front side must not be longer than 10000 characters`),back:f.max(1e4,`Back side must not be longer than 10000 characters`)}),oe=P.extend({type:n.z.literal(`true-false`),question:f.max(1e4,`Question must not be longer than 10000 characters`),correctAnswer:n.z.boolean()}),se=P.extend({type:n.z.literal(`mcq-single`),question:f.max(1e4,`Question must not be longer than 10000 characters`),options:_(2,`options`),correctId:h,shuffle:n.z.boolean().optional()}).refine(e=>v(e.options).has(e.correctId),{message:`correctId references a non-existent option`,path:[`correctId`]}).refine(e=>new Set(e.options.map(e=>e.text.toLowerCase())).size===e.options.length,{message:`Options must not contain duplicates (case-insensitive)`,path:[`options`]}),ce=P.extend({type:n.z.literal(`mcq-multi`),question:f.max(1e4,`Question must not be longer than 10000 characters`),options:_(2,`options`),correctIds:n.z.array(h).min(1,`Must have at least 1 correct answer`),minSelections:n.z.number().int().positive().optional(),maxSelections:n.z.number().int().positive().optional(),shuffle:n.z.boolean().optional()}).refine(e=>e.correctIds.every(t=>v(e.options).has(t)),{message:`Some of correctIds reference a non-existent option`,path:[`correctIds`]}).refine(e=>new Set(e.correctIds).size===e.correctIds.length,{message:`correctIds contain duplicates`,path:[`correctIds`]}).refine(e=>!(e.minSelections&&e.maxSelections)||e.minSelections<=e.maxSelections,{message:`Minimum number of selections must be less than or equal to maximum`,path:[`maxSelections`]}).refine(e=>!e.maxSelections||e.maxSelections<=e.options.length,{message:`Maximum number of selections must not exceed number of options`,path:[`maxSelections`]}).refine(e=>!e.minSelections||e.minSelections<=e.options.length,{message:`minSelections cannot be greater than the number of options`,path:[`minSelections`]}).refine(e=>new Set(e.options.map(e=>e.text.toLowerCase())).size===e.options.length,{message:`Options must not contain duplicates (case-insensitive)`,path:[`options`]}),le=P.extend({type:n.z.literal(`short-answer`),question:f.max(1e4,`Question must not be longer than 10000 characters`),correctAnswers:n.z.array(u).min(1,`Must have at least 1 correct answer`),caseSensitive:n.z.boolean().optional(),ignoreDiacritics:n.z.boolean().optional(),acceptPartial:n.z.boolean().optional()}),ue=P.extend({type:n.z.literal(`fill-in-blanks`),question:p,text:f.max(1e4,`Text must not be longer than 10000 characters`),blanks:n.z.record(m,n.z.array(u).min(1,`Each blank must have at least 1 correct answer`)),caseSensitive:n.z.boolean().optional(),ignoreDiacritics:n.z.boolean().optional(),acceptPartial:n.z.boolean().optional()}).refine(e=>Object.keys(e.blanks).length>0,{message:`Text must contain at least 1 blank to fill`,path:[`blanks`]}).refine(e=>{let t=/<blank:([a-zA-Z0-9_-]{1,64})\s*\/>/g,n=new Set,r;for(;(r=t.exec(e.text))!==null;)r[1]&&n.add(r[1]);let i=new Set(Object.keys(e.blanks));for(let e of n)if(!i.has(e))return!1;for(let e of i)if(!n.has(e))return!1;return!0},{message:`All tokens in text must have definition in blanks and all definitions must be used in text`,path:[`blanks`]}),de=P.extend({type:n.z.literal(`fill-in-select`),question:p,text:f.max(1e4,`Text must not be longer than 10000 characters`),blanks:n.z.record(m,F)}).refine(e=>Object.keys(e.blanks).length>0,{message:`Text must contain at least 1 blank to select`,path:[`blanks`]}).refine(e=>{let t=/<blank:([a-zA-Z0-9_-]{1,64})\s*\/>/g,n=new Set,r;for(;(r=t.exec(e.text))!==null;)r[1]&&n.add(r[1]);let i=new Set(Object.keys(e.blanks));for(let e of n)if(!i.has(e))return!1;for(let e of i)if(!n.has(e))return!1;return!0},{message:`All tokens in text must have definition in blanks and all definitions must be used in text`,path:[`blanks`]}),fe=P.extend({type:n.z.literal(`match-pairs`),question:p,prompts:_(2,`prompts`),matches:_(2,`matches`),pairs:n.z.record(h,h)}).refine(e=>e.prompts.every(t=>t.id in e.pairs)&&Object.keys(e.pairs).every(t=>v(e.prompts).has(t)),{message:`pairs must have exactly one entry for every prompt`,path:[`pairs`]}).refine(e=>Object.values(e.pairs).every(t=>v(e.matches).has(t)),{message:`Some pair references a non-existent match`,path:[`pairs`]}).refine(e=>new Set(Object.values(e.pairs)).size===Object.values(e.pairs).length,{message:`Each match may belong to only one prompt`,path:[`pairs`]}),pe=P.extend({type:n.z.literal(`match-complex`),question:p,prompts:_(1,`prompts`),matches:_(1,`matches`),connections:n.z.array(n.z.tuple([h,h])).min(1,`Must have at least 1 connection`),minCorrect:n.z.number().int().positive().optional()}).refine(e=>e.connections.every(([t,n])=>v(e.prompts).has(t)&&v(e.matches).has(n)),{message:`Some connection references a non-existent item`,path:[`connections`]}).refine(e=>new Set(e.connections.map(([e,t])=>`${e}\u0000${t}`)).size===e.connections.length,{message:`Connections contain duplicates`,path:[`connections`]}).refine(e=>!e.minCorrect||e.minCorrect<=e.connections.length,{message:`Minimum correct answers must not exceed total number of connections`,path:[`minCorrect`]}),me=P.extend({type:n.z.literal(`sort-items`),question:f.max(1e4,`Question must not be longer than 10000 characters`),items:_(2,`items to sort`),correctOrder:n.z.array(h)}).refine(e=>e.correctOrder.length===e.items.length&&new Set(e.correctOrder).size===e.items.length&&e.correctOrder.every(t=>v(e.items).has(t)),{message:`correctOrder must list every item ID exactly once`,path:[`correctOrder`]}),he=P.extend({type:n.z.literal(`slider`),question:f.max(1e4,`Question must not be longer than 10000 characters`),min:n.z.number(),max:n.z.number(),step:n.z.number().positive(`Step must be a positive number`),correctAnswer:n.z.number(),tolerance:n.z.number().nonnegative(`Tolerance must be non-negative`).optional(),unit:n.z.string().optional()}).refine(e=>e.min<e.max,{message:`Minimum value must be less than maximum value`,path:[`max`]}).refine(e=>e.correctAnswer>=e.min&&e.correctAnswer<=e.max,{message:`Correct answer must be in range min-max`,path:[`correctAnswer`]}).refine(e=>{let t=(e.correctAnswer-e.min)/e.step;return Math.abs(t-Math.round(t))<1e-4},{message:`Correct answer must be reachable by defined step`,path:[`correctAnswer`]}).refine(e=>(e.tolerance??0)<=(e.max-e.min)/2,{message:`Tolerance must not be greater than half of value range`,path:[`tolerance`]}),ge=P.extend({type:n.z.literal(`pin-on-image`),question:f.max(1e4,`Question must not be longer than 10000 characters`),targetAsset:l,hotspots:n.z.array(B).min(1,`Must have at least 1 hotspot`),multipleCorrect:n.z.boolean().optional(),minCorrect:n.z.number().int().positive().optional()}).refine(e=>!e.minCorrect||e.minCorrect<=e.hotspots.length,{message:`Minimum correct answers must not exceed number of hotspots`,path:[`minCorrect`]}),_e=P.extend({type:n.z.literal(`categorize`),question:f.max(1e4,`Question must not be longer than 10000 characters`),categories:_(2,`categories`),items:n.z.array(U).min(1,`Must have at least 1 item to categorize`)}).refine(e=>new Set(e.categories.map(e=>e.text.toLowerCase())).size===e.categories.length,{message:`Categories must not contain duplicates (case-insensitive)`,path:[`categories`]}).refine(e=>e.items.every(t=>v(e.categories).has(t.correctCategoryId)),{message:`Some item references a non-existent category`,path:[`items`]}),ve=P.extend({type:n.z.literal(`timeline`),question:f.max(1e4,`Question must not be longer than 10000 characters`),events:n.z.array(G).min(2,`Must have at least 2 events`),shuffle:n.z.boolean().optional()}),ye=P.extend({type:n.z.literal(`matrix`),question:f.max(1e4,`Question must not be longer than 10000 characters`),rows:_(1,`rows`),columns:_(1,`columns`),correctCells:n.z.array(n.z.tuple([h,h])).min(1,`Must have at least 1 correct cell`),multiplePerRow:n.z.boolean().optional()}).refine(e=>e.correctCells.every(([t,n])=>v(e.rows).has(t)&&v(e.columns).has(n)),{message:`Some cell references a non-existent row or column`,path:[`correctCells`]}).refine(e=>new Set(e.correctCells.map(([e,t])=>`${e}\u0000${t}`)).size===e.correctCells.length,{message:`Correct cells contain duplicates`,path:[`correctCells`]}).refine(e=>e.multiplePerRow||new Set(e.correctCells.map(([e])=>e)).size===e.correctCells.length,{message:`If multiplePerRow is false, each row may have maximum 1 correct cell`,path:[`correctCells`]}),be=P.extend({type:n.z.literal(`math-input`),question:f.max(1e4,`Question must not be longer than 10000 characters`),correctAnswers:n.z.array(d).min(1,`Must have at least 1 correct answer`)}),xe=P.extend({type:n.z.literal(`diagram-label`),question:f.max(1e4,`Question must not be longer than 10000 characters`),targetAsset:l,labels:_(1,`labels`),caseSensitive:n.z.boolean().optional(),ignoreDiacritics:n.z.boolean().optional(),acceptPartial:n.z.boolean().optional(),requireTyping:n.z.boolean().optional(),zones:n.z.array(K).min(1,`Must have at least 1 zone`)}).refine(e=>e.zones.every(t=>v(e.labels).has(t.correctLabelId)),{message:`Some zone references a non-existent label`,path:[`zones`]}).refine(e=>new Set(e.zones.map(e=>e.id)).size===e.zones.length,{message:`Zone IDs must be unique within the item`,path:[`zones`]}),Se=P.extend({type:n.z.literal(`open-ended`),question:f.max(1e4,`Question must not be longer than 10000 characters`),minWords:n.z.number().int().positive().optional(),maxWords:n.z.number().int().positive().optional(),sampleAnswer:f.optional(),rubric:J.optional()}).refine(e=>e.minWords&&e.maxWords?e.minWords<=e.maxWords:!0,{message:`Minimum word count must be less than or equal to maximum`,path:[`maxWords`]}),Ce=P.extend({type:n.z.literal(`numeric-input`),question:f.max(1e4,`Question must not be longer than 10000 characters`),correctAnswer:n.z.number(),tolerance:n.z.number().nonnegative(`Tolerance must be non-negative`).optional(),unit:n.z.string().optional()}),X=P.extend({type:n.z.literal(`pin-on-model`),question:f.max(1e4,`Question must not be longer than 10000 characters`),targetAsset:l,hotspots:n.z.array(z).min(1,`Must have at least 1 hotspot`),multipleCorrect:n.z.boolean().optional(),minCorrect:n.z.number().int().positive().optional(),camera:H.optional()}).refine(e=>!e.minCorrect||e.minCorrect<=e.hotspots.length,{message:`Minimum correct answers must not exceed number of hotspots`,path:[`minCorrect`]}),we=P.extend({type:n.z.literal(`chess-puzzle`),question:f.max(1e4,`Question must not be longer than 10000 characters`),fen:u,correctAnswers:n.z.array(n.z.array(u).min(1,`Move sequence must not be empty`)).min(1,`Must have at least 1 correct move sequence`),elo:n.z.number().int().nonnegative(`ELO must be non-negative integer`).optional()}),Z=n.z.discriminatedUnion(`type`,[ae,Y,oe,se,ce,le,ue,de,fe,pe,me,he,ge,_e,ve,ye,be,xe,Se,Ce,X,we]),Te=n.z.string().regex(/^x-[a-z0-9]+(?:-[a-z0-9]+)*$/,`Custom item type must start with "x-" followed by lowercase letters, digits and hyphens`),Q=P.extend({type:Te}),Ee=n.z.union([Z,Q]);function $(e){let t=e?.type;return typeof t==`string`&&t.startsWith(`x-`)?Q:Z}var De=n.z.looseObject({$schema:n.z.string().url().optional(),version:n.z.string().regex(/^\d+\.\d+$/,`Version must be in MAJOR.MINOR format (e.g. "1.0")`),meta:N,items:n.z.array(Ee).max(1e4,`Maximum 10000 items per set`)});function Oe(e){let t=e instanceof Error?e.message:`OQSE Security Error: Maximum nesting depth exceeded limit of 10 levels.`;return new n.z.ZodError([{code:`custom`,message:t,path:[],input:void 0}])}function ke(t){return e.validateJsonDepth(t,10),$(t).parse(t)}function Ae(t){try{e.validateJsonDepth(t,10)}catch(e){return{success:!1,error:Oe(e)}}let n=$(t).safeParse(t);return n.success?{success:!0,data:n.data}:{success:!1,error:n.error}}exports.AbsoluteURLSchema=c,exports.AnyOQSEItemSchema=Ee,exports.AssetDictionarySchema=S,exports.AssetKeySchema=l,exports.BaseItemSchema=P,exports.BlankTokenSchema=m,exports.BloomLevelSchema=A,exports.CameraSetupSchema=H,exports.CategorizeEntrySchema=U,exports.CategorizeItemSchema=_e,exports.ChessPuzzleItemSchema=we,exports.ChoiceIdSchema=h,exports.ChoiceSchema=g,exports.CircleHotspotSchema=L,exports.CognitiveLoadSchema=j,exports.CustomItemSchema=Q,exports.CustomItemTypeSchema=Te,exports.DiagramLabelItemSchema=xe,exports.DiagramZoneSchema=K,exports.FeatureKeySchema=re,exports.FeatureProfileSchema=D,exports.FillInBlanksItemSchema=ue,exports.FillInSelectItemSchema=de,exports.FlashcardItemSchema=Y,exports.Hotspot2DSchema=B,exports.HotspotObjectSchema=ie,exports.ISO8601DateTimeSchema=o,exports.LanguageCodeSchema=i,exports.LinkedSetObjectSchema=k,exports.MCQMultiItemSchema=ce,exports.MCQSingleItemSchema=se,exports.MatchComplexItemSchema=pe,exports.MatchPairsItemSchema=fe,exports.MathInputItemSchema=be,exports.MatrixItemSchema=ye,exports.MediaObjectSchema=x,exports.MeshHotspotSchema=z,exports.NoteItemSchema=ae,exports.NumericInputItemSchema=Ce,exports.OQSEFileSchema=De,exports.OQSEItemSchema=Z,exports.OQSEMetaSchema=N,exports.OpenEndedItemSchema=Se,exports.OptionalRichContentSchema=p,exports.PedagogySchema=M,exports.PersonObjectSchema=C,exports.PinOnImageItemSchema=ge,exports.PinOnModelItemSchema=X,exports.PlainTextSchema=u,exports.PolygonHotspotSchema=R,exports.PropertyKeySchema=E,exports.RawLatexSchema=d,exports.RectHotspotSchema=I,exports.RichContentSchema=f,exports.RubricCriterionSchema=q,exports.RubricSchema=J,exports.SPDXLicenseSchema=a,exports.SelectBlankObjectSchema=F,exports.ShortAnswerItemSchema=le,exports.SkillIdSchema=y,exports.SliderItemSchema=he,exports.SortItemsItemSchema=me,exports.SourceMaterialSchema=T,exports.SourceMaterialTypeSchema=w,exports.SourceReferenceSchema=ee,exports.SubtitleTrackSchema=b,exports.TagDefinitionDictionarySchema=ne,exports.TagDefinitionSchema=te,exports.TimelineDateSchema=s,exports.TimelineEventSchema=G,exports.TimelineItemSchema=ve,exports.TimelinePrecisionSchema=W,exports.TranslationObjectSchema=O,exports.TrueFalseItemSchema=oe,exports.UUIDSchema=r,exports.Vector3Schema=V,exports.choiceList=_,exports.getItemSchema=$,exports.safeValidateOQSEItem=Ae,exports.validateOQSEItem=ke;
@@ -10,7 +10,7 @@ var r = n.string().uuid({ message: "Invalid UUID format" }), i = n.string().min(
10
10
  function _(e, t) {
11
11
  return n.array(g).min(e, `Must have at least ${e} ${t}`).max(100, `Maximum 100 ${t}`).refine((e) => new Set(e.map((e) => e.id)).size === e.length, { message: "Choice IDs must be unique within the list" });
12
12
  }
13
- var v = (e) => new Set(e.map((e) => e.id)), ee = n.string().max(128, "Skill ID must not be longer than 128 characters").regex(/^[a-z0-9][a-z0-9_-]*(\.[a-z0-9][a-z0-9_-]*)*$/, "Skill ID must be lowercase segments joined by dots (e.g. \"chess.tactics.fork\")"), y = n.looseObject({
13
+ var v = (e) => new Set(e.map((e) => e.id)), y = n.string().max(128, "Skill ID must not be longer than 128 characters").regex(/^[a-z0-9][a-z0-9_-]*(\.[a-z0-9][a-z0-9_-]*)*$/, "Skill ID must be lowercase segments joined by dots (e.g. \"chess.tactics.fork\")"), b = n.looseObject({
14
14
  lang: i,
15
15
  value: n.string().min(1, "Subtitle URI must not be empty").regex(/\.(vtt|srt)(\?.*)?$/i, "Subtitle URI must end with .vtt or .srt"),
16
16
  label: n.string().optional(),
@@ -19,7 +19,7 @@ var v = (e) => new Set(e.map((e) => e.id)), ee = n.string().max(128, "Skill ID m
19
19
  "subtitles",
20
20
  "descriptions"
21
21
  ]).optional()
22
- }), b = n.looseObject({
22
+ }), x = n.looseObject({
23
23
  type: n.enum([
24
24
  "image",
25
25
  "audio",
@@ -36,19 +36,19 @@ var v = (e) => new Set(e.map((e) => e.id)), ee = n.string().max(128, "Skill ID m
36
36
  start: n.number().nonnegative().optional(),
37
37
  end: n.number().positive().optional(),
38
38
  loop: n.boolean().optional(),
39
- subtitles: n.array(y).optional(),
39
+ subtitles: n.array(b).optional(),
40
40
  license: a.optional(),
41
41
  attribution: n.string().optional(),
42
42
  checksums: n.record(n.string(), n.string()).optional()
43
43
  }).refine((e) => e.start !== void 0 && e.end !== void 0 ? e.start < e.end : !0, {
44
44
  message: "Start time must be less than end time",
45
45
  path: ["end"]
46
- }), x = n.record(l, b), S = n.looseObject({
46
+ }), S = n.record(l, x), C = n.looseObject({
47
47
  name: u,
48
48
  role: n.string().optional(),
49
49
  email: n.string().email("Invalid email format").optional(),
50
50
  url: c.optional()
51
- }), C = n.enum([
51
+ }), w = n.enum([
52
52
  "url",
53
53
  "doi",
54
54
  "isbn",
@@ -59,9 +59,9 @@ var v = (e) => new Set(e.map((e) => e.id)), ee = n.string().max(128, "Skill ID m
59
59
  "image",
60
60
  "model",
61
61
  "other"
62
- ]), w = n.looseObject({
62
+ ]), T = n.looseObject({
63
63
  id: u,
64
- type: C,
64
+ type: w,
65
65
  value: u,
66
66
  title: u,
67
67
  description: n.string().optional(),
@@ -85,7 +85,7 @@ var v = (e) => new Set(e.map((e) => e.id)), ee = n.string().max(128, "Skill ID m
85
85
  }, {
86
86
  message: "For url, pdf, video, audio and image types, value must be a valid URL",
87
87
  path: ["value"]
88
- }), T = n.looseObject({
88
+ }), ee = n.looseObject({
89
89
  id: u,
90
90
  location: n.string().optional(),
91
91
  quote: f.optional()
@@ -132,21 +132,21 @@ var v = (e) => new Set(e.map((e) => e.id)), ee = n.string().max(128, "Skill ID m
132
132
  title: u.max(500, "Title must not be longer than 500 characters"),
133
133
  description: f.max(5e3, "Description must not be longer than 5000 characters").optional(),
134
134
  thumbnail: l.optional(),
135
- assets: x.optional(),
135
+ assets: S.optional(),
136
136
  ageMin: n.number().int().nonnegative().optional(),
137
137
  ageMax: n.number().int().nonnegative().optional(),
138
138
  subject: n.string().optional(),
139
139
  createdAt: o,
140
140
  updatedAt: o,
141
- author: S.optional(),
142
- contributors: n.array(S).optional(),
141
+ author: C.optional(),
142
+ contributors: n.array(C).optional(),
143
143
  license: a.optional(),
144
144
  licenseUrl: c.optional(),
145
145
  requirements: D.optional(),
146
146
  tags: n.array(u).optional(),
147
147
  tagDefinitions: ne.optional(),
148
148
  translations: n.array(O).optional(),
149
- sourceMaterials: n.array(w).optional(),
149
+ sourceMaterials: n.array(T).optional(),
150
150
  estimatedTime: n.number().positive().optional(),
151
151
  prerequisites: n.array(k).optional(),
152
152
  relatedSets: n.array(k).optional(),
@@ -161,17 +161,17 @@ var v = (e) => new Set(e.map((e) => e.id)), ee = n.string().max(128, "Skill ID m
161
161
  }), P = n.looseObject({
162
162
  id: r,
163
163
  type: n.string(),
164
- assets: x.optional(),
164
+ assets: S.optional(),
165
165
  lang: i.optional(),
166
166
  tags: n.array(u).optional(),
167
- skills: n.array(ee).max(20, "Maximum 20 skills per item").optional(),
167
+ skills: n.array(y).max(20, "Maximum 20 skills per item").optional(),
168
168
  topic: u.optional(),
169
169
  difficulty: n.number().int().min(1, "Difficulty must be at least 1").max(5, "Difficulty must be at most 5").optional(),
170
170
  timeLimit: n.number().positive("Time limit must be a positive number").optional(),
171
171
  hints: n.array(f.max(2e3, "Hint must not be longer than 2000 characters")).max(20, "Maximum 20 hints per item").optional(),
172
172
  explanation: f.max(1e4, "Explanation must not be longer than 10000 characters").optional(),
173
173
  incorrectFeedback: f.optional(),
174
- sources: n.array(T).optional(),
174
+ sources: n.array(ee).optional(),
175
175
  relatedItems: n.array(r).optional(),
176
176
  dependencyItems: n.array(r).optional(),
177
177
  pedagogy: M.optional(),
@@ -261,12 +261,12 @@ var v = (e) => new Set(e.map((e) => e.id)), ee = n.string().max(128, "Skill ID m
261
261
  }, {
262
262
  message: "Sum of percentages of all criteria must be greater than 0",
263
263
  path: ["criteria"]
264
- }), Y = P.extend({
264
+ }), ae = P.extend({
265
265
  type: n.literal("note"),
266
266
  title: n.string().optional(),
267
267
  content: f.max(1e5, "Content must not be longer than 100000 characters"),
268
268
  hiddenContent: f.max(1e5, "Hidden content must not be longer than 100000 characters").optional()
269
- }), ae = P.extend({
269
+ }), Y = P.extend({
270
270
  type: n.literal("flashcard"),
271
271
  front: f.max(1e4, "Front side must not be longer than 10000 characters"),
272
272
  back: f.max(1e4, "Back side must not be longer than 10000 characters")
@@ -376,11 +376,11 @@ var v = (e) => new Set(e.map((e) => e.id)), ee = n.string().max(128, "Skill ID m
376
376
  }), pe = P.extend({
377
377
  type: n.literal("match-complex"),
378
378
  question: p,
379
- leftItems: _(1, "items on the left"),
380
- rightItems: _(1, "items on the right"),
379
+ prompts: _(1, "prompts"),
380
+ matches: _(1, "matches"),
381
381
  connections: n.array(n.tuple([h, h])).min(1, "Must have at least 1 connection"),
382
382
  minCorrect: n.number().int().positive().optional()
383
- }).refine((e) => e.connections.every(([t, n]) => v(e.leftItems).has(t) && v(e.rightItems).has(n)), {
383
+ }).refine((e) => e.connections.every(([t, n]) => v(e.prompts).has(t) && v(e.matches).has(n)), {
384
384
  message: "Some connection references a non-existent item",
385
385
  path: ["connections"]
386
386
  }).refine((e) => new Set(e.connections.map(([e, t]) => `${e}\u0000${t}`)).size === e.connections.length, {
@@ -466,9 +466,7 @@ var v = (e) => new Set(e.map((e) => e.id)), ee = n.string().max(128, "Skill ID m
466
466
  }), be = P.extend({
467
467
  type: n.literal("math-input"),
468
468
  question: f.max(1e4, "Question must not be longer than 10000 characters"),
469
- correctAnswer: d,
470
- alternativeAnswers: n.array(d).optional(),
471
- tolerance: n.number().nonnegative().optional()
469
+ correctAnswers: n.array(d).min(1, "Must have at least 1 correct answer")
472
470
  }), xe = P.extend({
473
471
  type: n.literal("diagram-label"),
474
472
  question: f.max(1e4, "Question must not be longer than 10000 characters"),
@@ -519,8 +517,8 @@ var v = (e) => new Set(e.map((e) => e.id)), ee = n.string().max(128, "Skill ID m
519
517
  correctAnswers: n.array(n.array(u).min(1, "Move sequence must not be empty")).min(1, "Must have at least 1 correct move sequence"),
520
518
  elo: n.number().int().nonnegative("ELO must be non-negative integer").optional()
521
519
  }), Z = n.discriminatedUnion("type", [
522
- Y,
523
520
  ae,
521
+ Y,
524
522
  oe,
525
523
  se,
526
524
  ce,
@@ -583,4 +581,4 @@ function Ae(t) {
583
581
  };
584
582
  }
585
583
  //#endregion
586
- export { c as AbsoluteURLSchema, Ee as AnyOQSEItemSchema, x as AssetDictionarySchema, l as AssetKeySchema, P as BaseItemSchema, m as BlankTokenSchema, A as BloomLevelSchema, H as CameraSetupSchema, U as CategorizeEntrySchema, _e as CategorizeItemSchema, we as ChessPuzzleItemSchema, h as ChoiceIdSchema, g as ChoiceSchema, L as CircleHotspotSchema, j as CognitiveLoadSchema, Q as CustomItemSchema, Te as CustomItemTypeSchema, xe as DiagramLabelItemSchema, K as DiagramZoneSchema, re as FeatureKeySchema, D as FeatureProfileSchema, ue as FillInBlanksItemSchema, de as FillInSelectItemSchema, ae as FlashcardItemSchema, B as Hotspot2DSchema, ie as HotspotObjectSchema, o as ISO8601DateTimeSchema, i as LanguageCodeSchema, k as LinkedSetObjectSchema, ce as MCQMultiItemSchema, se as MCQSingleItemSchema, pe as MatchComplexItemSchema, fe as MatchPairsItemSchema, be as MathInputItemSchema, ye as MatrixItemSchema, b as MediaObjectSchema, z as MeshHotspotSchema, Y as NoteItemSchema, Ce as NumericInputItemSchema, De as OQSEFileSchema, Z as OQSEItemSchema, N as OQSEMetaSchema, Se as OpenEndedItemSchema, p as OptionalRichContentSchema, M as PedagogySchema, S as PersonObjectSchema, ge as PinOnImageItemSchema, X as PinOnModelItemSchema, u as PlainTextSchema, R as PolygonHotspotSchema, E as PropertyKeySchema, d as RawLatexSchema, I as RectHotspotSchema, f as RichContentSchema, q as RubricCriterionSchema, J as RubricSchema, a as SPDXLicenseSchema, F as SelectBlankObjectSchema, le as ShortAnswerItemSchema, ee as SkillIdSchema, he as SliderItemSchema, me as SortItemsItemSchema, w as SourceMaterialSchema, C as SourceMaterialTypeSchema, T as SourceReferenceSchema, y as SubtitleTrackSchema, ne as TagDefinitionDictionarySchema, te as TagDefinitionSchema, s as TimelineDateSchema, G as TimelineEventSchema, ve as TimelineItemSchema, W as TimelinePrecisionSchema, O as TranslationObjectSchema, oe as TrueFalseItemSchema, r as UUIDSchema, V as Vector3Schema, _ as choiceList, $ as getItemSchema, Ae as safeValidateOQSEItem, ke as validateOQSEItem };
584
+ export { c as AbsoluteURLSchema, Ee as AnyOQSEItemSchema, S as AssetDictionarySchema, l as AssetKeySchema, P as BaseItemSchema, m as BlankTokenSchema, A as BloomLevelSchema, H as CameraSetupSchema, U as CategorizeEntrySchema, _e as CategorizeItemSchema, we as ChessPuzzleItemSchema, h as ChoiceIdSchema, g as ChoiceSchema, L as CircleHotspotSchema, j as CognitiveLoadSchema, Q as CustomItemSchema, Te as CustomItemTypeSchema, xe as DiagramLabelItemSchema, K as DiagramZoneSchema, re as FeatureKeySchema, D as FeatureProfileSchema, ue as FillInBlanksItemSchema, de as FillInSelectItemSchema, Y as FlashcardItemSchema, B as Hotspot2DSchema, ie as HotspotObjectSchema, o as ISO8601DateTimeSchema, i as LanguageCodeSchema, k as LinkedSetObjectSchema, ce as MCQMultiItemSchema, se as MCQSingleItemSchema, pe as MatchComplexItemSchema, fe as MatchPairsItemSchema, be as MathInputItemSchema, ye as MatrixItemSchema, x as MediaObjectSchema, z as MeshHotspotSchema, ae as NoteItemSchema, Ce as NumericInputItemSchema, De as OQSEFileSchema, Z as OQSEItemSchema, N as OQSEMetaSchema, Se as OpenEndedItemSchema, p as OptionalRichContentSchema, M as PedagogySchema, C as PersonObjectSchema, ge as PinOnImageItemSchema, X as PinOnModelItemSchema, u as PlainTextSchema, R as PolygonHotspotSchema, E as PropertyKeySchema, d as RawLatexSchema, I as RectHotspotSchema, f as RichContentSchema, q as RubricCriterionSchema, J as RubricSchema, a as SPDXLicenseSchema, F as SelectBlankObjectSchema, le as ShortAnswerItemSchema, y as SkillIdSchema, he as SliderItemSchema, me as SortItemsItemSchema, T as SourceMaterialSchema, w as SourceMaterialTypeSchema, ee as SourceReferenceSchema, b as SubtitleTrackSchema, ne as TagDefinitionDictionarySchema, te as TagDefinitionSchema, s as TimelineDateSchema, G as TimelineEventSchema, ve as TimelineItemSchema, W as TimelinePrecisionSchema, O as TranslationObjectSchema, oe as TrueFalseItemSchema, r as UUIDSchema, V as Vector3Schema, _ as choiceList, $ as getItemSchema, Ae as safeValidateOQSEItem, ke as validateOQSEItem };
@@ -1 +1 @@
1
- function e(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function t(e,t,n){typeof n==`string`&&e.push([t,n])}function n(e,n,r){Array.isArray(r)&&r.forEach((r,i)=>t(e,[...n,i],r))}function r(n,r,i){Array.isArray(i)&&i.forEach((i,a)=>{e(i)&&(t(n,[...r,a,`text`],i.text),t(n,[...r,a,`explanation`],i.explanation))})}function i(e,n,r){for(let[i,a]of Object.entries(r??{}))t(e,[...n,i,`transcript`],a.transcript),t(e,[...n,i,`caption`],a.caption)}function a(e){let n=[];return t(n,[`meta`,`description`],e.description),i(n,[`meta`,`assets`],e.assets),n}function o(e){let a=[];switch(n(a,[`hints`],e.hints),t(a,[`explanation`],e.explanation),t(a,[`incorrectFeedback`],e.incorrectFeedback),e.sources?.forEach((e,n)=>t(a,[`sources`,n,`quote`],e.quote)),i(a,[`assets`],e.assets),`question`in e&&t(a,[`question`],e.question),e.type){case`note`:t(a,[`content`],e.content),t(a,[`hiddenContent`],e.hiddenContent);break;case`flashcard`:t(a,[`front`],e.front),t(a,[`back`],e.back);break;case`mcq-single`:case`mcq-multi`:r(a,[`options`],e.options);break;case`fill-in-blanks`:t(a,[`text`],e.text);break;case`fill-in-select`:t(a,[`text`],e.text);for(let[t,n]of Object.entries(e.blanks))r(a,[`blanks`,t,`options`],n.options);break;case`match-pairs`:r(a,[`prompts`],e.prompts),r(a,[`matches`],e.matches);break;case`match-complex`:r(a,[`leftItems`],e.leftItems),r(a,[`rightItems`],e.rightItems);break;case`sort-items`:r(a,[`items`],e.items);break;case`categorize`:r(a,[`categories`],e.categories),e.items.forEach((e,n)=>t(a,[`items`,n,`text`],e.text));break;case`timeline`:e.events.forEach((e,n)=>t(a,[`events`,n,`text`],e.text));break;case`diagram-label`:r(a,[`labels`],e.labels);break;case`matrix`:r(a,[`rows`],e.rows),r(a,[`columns`],e.columns);break;case`open-ended`:t(a,[`sampleAnswer`],e.sampleAnswer)}return a}exports.itemRichText=o,exports.metaRichText=a;
1
+ function e(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function t(e,t,n){typeof n==`string`&&e.push([t,n])}function n(e,n,r){Array.isArray(r)&&r.forEach((r,i)=>t(e,[...n,i],r))}function r(n,r,i){Array.isArray(i)&&i.forEach((i,a)=>{e(i)&&(t(n,[...r,a,`text`],i.text),t(n,[...r,a,`explanation`],i.explanation))})}function i(e,n,r){for(let[i,a]of Object.entries(r??{}))t(e,[...n,i,`transcript`],a.transcript),t(e,[...n,i,`caption`],a.caption)}function a(e){let n=[];return t(n,[`meta`,`description`],e.description),i(n,[`meta`,`assets`],e.assets),n}function o(e){let a=[];switch(n(a,[`hints`],e.hints),t(a,[`explanation`],e.explanation),t(a,[`incorrectFeedback`],e.incorrectFeedback),e.sources?.forEach((e,n)=>t(a,[`sources`,n,`quote`],e.quote)),i(a,[`assets`],e.assets),`question`in e&&t(a,[`question`],e.question),e.type){case`note`:t(a,[`content`],e.content),t(a,[`hiddenContent`],e.hiddenContent);break;case`flashcard`:t(a,[`front`],e.front),t(a,[`back`],e.back);break;case`mcq-single`:case`mcq-multi`:r(a,[`options`],e.options);break;case`fill-in-blanks`:t(a,[`text`],e.text);break;case`fill-in-select`:t(a,[`text`],e.text);for(let[t,n]of Object.entries(e.blanks))r(a,[`blanks`,t,`options`],n.options);break;case`match-pairs`:r(a,[`prompts`],e.prompts),r(a,[`matches`],e.matches);break;case`match-complex`:r(a,[`prompts`],e.prompts),r(a,[`matches`],e.matches);break;case`sort-items`:r(a,[`items`],e.items);break;case`categorize`:r(a,[`categories`],e.categories),e.items.forEach((e,n)=>t(a,[`items`,n,`text`],e.text));break;case`timeline`:e.events.forEach((e,n)=>t(a,[`events`,n,`text`],e.text));break;case`diagram-label`:r(a,[`labels`],e.labels);break;case`matrix`:r(a,[`rows`],e.rows),r(a,[`columns`],e.columns);break;case`open-ended`:t(a,[`sampleAnswer`],e.sampleAnswer)}return a}exports.itemRichText=o,exports.metaRichText=a;
@@ -68,7 +68,7 @@ function o(e) {
68
68
  r(a, ["prompts"], e.prompts), r(a, ["matches"], e.matches);
69
69
  break;
70
70
  case "match-complex":
71
- r(a, ["leftItems"], e.leftItems), r(a, ["rightItems"], e.rightItems);
71
+ r(a, ["prompts"], e.prompts), r(a, ["matches"], e.matches);
72
72
  break;
73
73
  case "sort-items":
74
74
  r(a, ["items"], e.items);
@@ -1,4 +1,4 @@
1
- import { MediaObject, OQSEAnyItem, OQSEMeta } from './oqse';
1
+ import { MediaObject, OQSEAnyItem, OQSEMeta } from './oqse.js';
2
2
  /**
3
3
  * Looks up a media object for `<asset:key />` or `targetAsset`: first in the
4
4
  * item's own `assets`, then in `meta.assets`. The key is case-insensitive.
@@ -1,2 +1,2 @@
1
- import { OQSEAnyItem } from './oqse';
1
+ import { OQSEAnyItem } from './oqse.js';
2
2
  export declare function checkAnswer(item: OQSEAnyItem, answer: unknown): boolean;
@@ -1,5 +1,5 @@
1
- import { MediaObject, OQSEFile } from './oqse';
2
- import { OQSEManifest } from './manifest';
1
+ import { MediaObject, OQSEFile } from './oqse.js';
2
+ import { OQSEManifest } from './manifest.js';
3
3
  export interface UnsupportedAsset {
4
4
  /** `meta.assets` key, or `items[i].assets` key prefixed with the item path. */
5
5
  path: string;
@@ -1,4 +1,4 @@
1
- import { OQSEFile } from './oqse';
1
+ import { OQSEFile } from './oqse.js';
2
2
  /** Languages written right to left (BCP 47 primary language subtags). */
3
3
  export declare const RTL_LANGUAGES: readonly string[];
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { OQSEFile } from './oqse';
1
+ import { OQSEFile } from './oqse.js';
2
2
  export type OQSEIssueSeverity = 'error' | 'warning';
3
3
  export type OQSEIssueCode = 'INVALID_ROOT' | 'INVALID_VERSION' | 'UNSUPPORTED_VERSION' | 'INVALID_META' | 'TOO_MANY_ITEMS' | 'INVALID_ITEM' | 'UNKNOWN_ITEM_TYPE' | 'DUPLICATE_ID' | 'DANGLING_REFERENCE' | 'MISSING_ASSET' | 'MISSING_TARGET_ASSET' | 'INVALID_TARGET_ASSET' | 'MISSING_THUMBNAIL_ASSET' | 'ASSET_KEY_NORMALIZED' | 'ASSET_KEY_COLLISION' | 'ASSET_SHADOWED' | 'RAW_HTML_NOT_ALLOWED' | 'NOTE_HEADING_LEVEL' | 'CHESS_SEQUENCE_LENGTH' | 'UNKNOWN_FEATURE' | 'MISSING_ALT_TEXT' | 'INVALID_IMAGE_URL' | 'BLANK_OUTSIDE_TEXT' | 'UNDECLARED_FEATURE' | 'LOOKS_LIKE_LATEX' | 'SECURITY_LIMIT';
4
4
  /** One entry of the structured error log (see "Structured Error Log" in the specification). */
@@ -1,4 +1,4 @@
1
- import { OQSEMeta } from './oqse';
1
+ import { OQSEMeta } from './oqse.js';
2
2
  type OQSEHeaderMetaProjection = Pick<OQSEMeta, 'id' | 'title' | 'language' | 'updatedAt' | 'description' | 'author' | 'subject' | 'tags' | 'createdAt' | 'estimatedTime' | 'requirements'>;
3
3
  export type OQSEHeader = Omit<OQSEHeaderMetaProjection, 'createdAt'> & Partial<Pick<OQSEHeaderMetaProjection, 'createdAt'>> & {
4
4
  url: string;
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { OQSEHeader } from './header';
2
+ import { OQSEHeader } from './header.js';
3
3
  export declare const OQSEHeaderSchema: z.ZodType<OQSEHeader>;
4
4
  export declare function validateOQSEHeader(data: unknown): OQSEHeader;
5
5
  export declare function safeValidateOQSEHeader(data: unknown): ReturnType<typeof OQSEHeaderSchema.safeParse>;
@@ -1,20 +1,20 @@
1
- export * from './oqse';
2
- export * from './oqseValidation';
3
- export * from './manifest';
4
- export * from './manifestValidation';
5
- export * from './progress';
6
- export * from './progressValidation';
7
- export * from './header';
8
- export * from './headerValidation';
9
- export * from './utils';
10
- export * from './richTextProcessor';
11
- export * from './markdownSet';
12
- export { shiftHeadings } from './markdownUtils';
13
- export * from './fileValidation';
14
- export * from './richTextFields';
15
- export * from './assets';
16
- export * from './answers';
17
- export * from './checkAnswer';
18
- export * from './plainText';
19
- export * from './features';
20
- export * from './compatibility';
1
+ export * from './oqse.js';
2
+ export * from './oqseValidation.js';
3
+ export * from './manifest.js';
4
+ export * from './manifestValidation.js';
5
+ export * from './progress.js';
6
+ export * from './progressValidation.js';
7
+ export * from './header.js';
8
+ export * from './headerValidation.js';
9
+ export * from './utils.js';
10
+ export * from './richTextProcessor.js';
11
+ export * from './markdownSet.js';
12
+ export { shiftHeadings } from './markdownUtils.js';
13
+ export * from './fileValidation.js';
14
+ export * from './richTextFields.js';
15
+ export * from './assets.js';
16
+ export * from './answers.js';
17
+ export * from './checkAnswer.js';
18
+ export * from './plainText.js';
19
+ export * from './features.js';
20
+ export * from './compatibility.js';
@@ -1,5 +1,5 @@
1
- import { FeatureProfile, PersonObject } from './oqse';
2
- export type { FeatureProfile } from './oqse';
1
+ import { FeatureProfile, PersonObject } from './oqse.js';
2
+ export type { FeatureProfile } from './oqse.js';
3
3
  /**
4
4
  * Official actions from the OQSE Action Registry (Section 2.1.3).
5
5
  *
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { OQSEManifest } from './manifest';
2
+ import { OQSEManifest } from './manifest.js';
3
3
  /**
4
4
  * A valid OQSE action: one of the five official actions, or a vendor-specific
5
5
  * action prefixed with `x-` (e.g. `x-acme-grade`).
@@ -1,5 +1,5 @@
1
- import { OQSEFile } from './oqse';
2
- import { OQSEIssue } from './fileValidation';
1
+ import { OQSEFile } from './oqse.js';
2
+ import { OQSEIssue } from './fileValidation.js';
3
3
  /** Callout marker that introduces `hiddenContent` (case-insensitive when parsing). */
4
4
  export declare const HIDDEN_CALLOUT_MARKER = "[!hidden]";
5
5
  /** Default heading level of notes: `#` set title, `##` chapters, `###` notes. */
@@ -692,11 +692,11 @@ export interface MatchComplexItem extends BaseItem {
692
692
  type: 'match-complex';
693
693
  /** Instructions (Rich Content) */
694
694
  question?: string;
695
- /** Items on left side. Min 1. */
696
- leftItems: Choice[];
697
- /** Items on right side. Min 1. */
698
- rightItems: Choice[];
699
- /** Correct connections as `[leftId, rightId]`. Min 1. */
695
+ /** What the learner connects from (like the prompts of `match-pairs`). Min 1. */
696
+ prompts: Choice[];
697
+ /** What the prompts are connected to (may include distractors). Min 1. */
698
+ matches: Choice[];
699
+ /** Correct connections as `[promptId, matchId]` (any number per prompt and per match). Min 1. */
700
700
  connections: Array<[string, string]>;
701
701
  /** Minimum number of correct connections required for success */
702
702
  minCorrect?: number;
@@ -799,12 +799,8 @@ export interface MathInputItem extends BaseItem {
799
799
  type: 'math-input';
800
800
  /** Question text (Rich Content) */
801
801
  question: string;
802
- /** Correct answer as raw LaTeX without `$` delimiters (e.g., "2x + 2") */
803
- correctAnswer: string;
804
- /** Other raw LaTeX answers (without `$` delimiters) that should also be considered correct */
805
- alternativeAnswers?: string[];
806
- /** For purely numeric answers, allowed numeric deviation */
807
- tolerance?: number;
802
+ /** Correct answers as raw LaTeX without `$` delimiters (e.g., ["2x+2", "2+2x"]). Min 1. */
803
+ correctAnswers: string[];
808
804
  }
809
805
  /**
810
806
  * Diagram Labeling
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { OQSEAnyItem } from './oqse';
2
+ import { OQSEAnyItem } from './oqse.js';
3
3
  /**
4
4
  * UUID validation: any valid UUID (new IDs should be UUIDv7, not required)
5
5
  */
@@ -1471,12 +1471,12 @@ export declare const MatchComplexItemSchema: z.ZodObject<{
1471
1471
  appSpecific: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1472
1472
  type: z.ZodLiteral<"match-complex">;
1473
1473
  question: z.ZodOptional<z.ZodString>;
1474
- leftItems: z.ZodArray<z.ZodObject<{
1474
+ prompts: z.ZodArray<z.ZodObject<{
1475
1475
  id: z.ZodString;
1476
1476
  text: z.ZodString;
1477
1477
  explanation: z.ZodOptional<z.ZodString>;
1478
1478
  }, z.core.$loose>>;
1479
- rightItems: z.ZodArray<z.ZodObject<{
1479
+ matches: z.ZodArray<z.ZodObject<{
1480
1480
  id: z.ZodString;
1481
1481
  text: z.ZodString;
1482
1482
  explanation: z.ZodOptional<z.ZodString>;
@@ -2099,9 +2099,7 @@ export declare const MathInputItemSchema: z.ZodObject<{
2099
2099
  appSpecific: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
2100
2100
  type: z.ZodLiteral<"math-input">;
2101
2101
  question: z.ZodString;
2102
- correctAnswer: z.ZodString;
2103
- alternativeAnswers: z.ZodOptional<z.ZodArray<z.ZodString>>;
2104
- tolerance: z.ZodOptional<z.ZodNumber>;
2102
+ correctAnswers: z.ZodArray<z.ZodString>;
2105
2103
  }, z.core.$loose>;
2106
2104
  /**
2107
2105
  * Diagram Label Item
@@ -3365,12 +3363,12 @@ export declare const OQSEItemSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
3365
3363
  appSpecific: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
3366
3364
  type: z.ZodLiteral<"match-complex">;
3367
3365
  question: z.ZodOptional<z.ZodString>;
3368
- leftItems: z.ZodArray<z.ZodObject<{
3366
+ prompts: z.ZodArray<z.ZodObject<{
3369
3367
  id: z.ZodString;
3370
3368
  text: z.ZodString;
3371
3369
  explanation: z.ZodOptional<z.ZodString>;
3372
3370
  }, z.core.$loose>>;
3373
- rightItems: z.ZodArray<z.ZodObject<{
3371
+ matches: z.ZodArray<z.ZodObject<{
3374
3372
  id: z.ZodString;
3375
3373
  text: z.ZodString;
3376
3374
  explanation: z.ZodOptional<z.ZodString>;
@@ -3965,9 +3963,7 @@ export declare const OQSEItemSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
3965
3963
  appSpecific: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
3966
3964
  type: z.ZodLiteral<"math-input">;
3967
3965
  question: z.ZodString;
3968
- correctAnswer: z.ZodString;
3969
- alternativeAnswers: z.ZodOptional<z.ZodArray<z.ZodString>>;
3970
- tolerance: z.ZodOptional<z.ZodNumber>;
3966
+ correctAnswers: z.ZodArray<z.ZodString>;
3971
3967
  }, z.core.$loose>, z.ZodObject<{
3972
3968
  id: z.ZodString;
3973
3969
  assets: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
@@ -5283,12 +5279,12 @@ export declare const AnyOQSEItemSchema: z.ZodUnion<readonly [z.ZodDiscriminatedU
5283
5279
  appSpecific: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
5284
5280
  type: z.ZodLiteral<"match-complex">;
5285
5281
  question: z.ZodOptional<z.ZodString>;
5286
- leftItems: z.ZodArray<z.ZodObject<{
5282
+ prompts: z.ZodArray<z.ZodObject<{
5287
5283
  id: z.ZodString;
5288
5284
  text: z.ZodString;
5289
5285
  explanation: z.ZodOptional<z.ZodString>;
5290
5286
  }, z.core.$loose>>;
5291
- rightItems: z.ZodArray<z.ZodObject<{
5287
+ matches: z.ZodArray<z.ZodObject<{
5292
5288
  id: z.ZodString;
5293
5289
  text: z.ZodString;
5294
5290
  explanation: z.ZodOptional<z.ZodString>;
@@ -5883,9 +5879,7 @@ export declare const AnyOQSEItemSchema: z.ZodUnion<readonly [z.ZodDiscriminatedU
5883
5879
  appSpecific: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
5884
5880
  type: z.ZodLiteral<"math-input">;
5885
5881
  question: z.ZodString;
5886
- correctAnswer: z.ZodString;
5887
- alternativeAnswers: z.ZodOptional<z.ZodArray<z.ZodString>>;
5888
- tolerance: z.ZodOptional<z.ZodNumber>;
5882
+ correctAnswers: z.ZodArray<z.ZodString>;
5889
5883
  }, z.core.$loose>, z.ZodObject<{
5890
5884
  id: z.ZodString;
5891
5885
  assets: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
@@ -7314,12 +7308,12 @@ export declare const OQSEFileSchema: z.ZodObject<{
7314
7308
  appSpecific: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
7315
7309
  type: z.ZodLiteral<"match-complex">;
7316
7310
  question: z.ZodOptional<z.ZodString>;
7317
- leftItems: z.ZodArray<z.ZodObject<{
7311
+ prompts: z.ZodArray<z.ZodObject<{
7318
7312
  id: z.ZodString;
7319
7313
  text: z.ZodString;
7320
7314
  explanation: z.ZodOptional<z.ZodString>;
7321
7315
  }, z.core.$loose>>;
7322
- rightItems: z.ZodArray<z.ZodObject<{
7316
+ matches: z.ZodArray<z.ZodObject<{
7323
7317
  id: z.ZodString;
7324
7318
  text: z.ZodString;
7325
7319
  explanation: z.ZodOptional<z.ZodString>;
@@ -7914,9 +7908,7 @@ export declare const OQSEFileSchema: z.ZodObject<{
7914
7908
  appSpecific: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
7915
7909
  type: z.ZodLiteral<"math-input">;
7916
7910
  question: z.ZodString;
7917
- correctAnswer: z.ZodString;
7918
- alternativeAnswers: z.ZodOptional<z.ZodArray<z.ZodString>>;
7919
- tolerance: z.ZodOptional<z.ZodNumber>;
7911
+ correctAnswers: z.ZodArray<z.ZodString>;
7920
7912
  }, z.core.$loose>, z.ZodObject<{
7921
7913
  id: z.ZodString;
7922
7914
  assets: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
@@ -1,4 +1,4 @@
1
- import { OQSEAnyItem, OQSEMeta } from './oqse';
1
+ import { OQSEAnyItem, OQSEMeta } from './oqse.js';
2
2
  export interface PlainTextOptions {
3
3
  /** The set declares `latex`: `$…$` / `$$…$$` are formulas (shown as their source). */
4
4
  latex?: boolean;
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { LastAnswerObject, OQSEProgress, ProgressMeta, ProgressRecord, StatsObject } from './progress';
2
+ import { LastAnswerObject, OQSEProgress, ProgressMeta, ProgressRecord, StatsObject } from './progress.js';
3
3
  export declare const StatsObjectSchema: z.ZodType<StatsObject>;
4
4
  export declare const LastAnswerObjectSchema: z.ZodType<LastAnswerObject>;
5
5
  export declare const ProgressRecordSchema: z.ZodType<ProgressRecord>;
@@ -1,4 +1,4 @@
1
- import { OQSEAnyItem, OQSEMeta } from './oqse';
1
+ import { OQSEAnyItem, OQSEMeta } from './oqse.js';
2
2
  type Path = (string | number)[];
3
3
  /** A Rich Content field: its path (relative to the item, or from the root for meta) and its text. */
4
4
  export type TextEntry = [Path, string];
@@ -1,4 +1,4 @@
1
- import { FeatureProfile } from './oqse';
1
+ import { FeatureProfile } from './oqse.js';
2
2
  export interface TokenMap {
3
3
  [tokenId: string]: {
4
4
  type: 'asset' | 'blank';
package/oqse-header.md CHANGED
@@ -54,3 +54,9 @@ An OQSEHeader represents one catalog entry that points to a full OQSE set.
54
54
 
55
55
  - OQSEH is intentionally lightweight and should remain a projection of OQSE `meta` plus `url`.
56
56
  - Header consumers should use `url` as the canonical pointer to the full set payload.
57
+
58
+ ---
59
+
60
+ ## Changelog
61
+
62
+ OQSEH 1.0 is part of OQSE 1.0 (2026-10-14); its changes are listed in the [OQSE changelog](./oqse.md#version-10-2026-10-14).
package/oqse-manifest.md CHANGED
@@ -247,3 +247,9 @@ For the full semantics of the capabilities object, see [Capabilities Object](#ca
247
247
  **`capabilities.itemProperties` / `capabilities.metaProperties`:**
248
248
  - Each value SHOULD appear in the relevant registry or carry the `x-` prefix.
249
249
  - Unrecognized values without an `x-` prefix SHOULD trigger a warning but MUST NOT cause manifest rejection.
250
+
251
+ ---
252
+
253
+ ## Changelog
254
+
255
+ OQSEM 1.0 is part of OQSE 1.0 (2026-10-14); its changes are listed in the [OQSE changelog](./oqse.md#version-10-2026-10-14).
package/oqse-progress.md CHANGED
@@ -169,4 +169,10 @@ Note that an item belonging to the set might be entirely absent from `records`.
169
169
 
170
170
  **On `setId` verification:** Importing applications MUST compare `meta.setId` against the UUID of the OQSE set being opened. If the values do not match, the application SHOULD warn the user and refrain from applying the progress data automatically to prevent data corruption.
171
171
 
172
- **On partial records and unseen items:** The `records` map need not contain an entry for every item in the set. Items absent from the map are treated as unseen. However, applications MAY explicitly generate a record with `bucket: 0` for an item that has never been presented if they need to store pre-study initialization data, suspended states, or `appSpecific` metadata. While permitted, applications SHOULD NOT unnecessarily synthesize placeholder records for all unseen items just to fill the file, as this bloats the file size.
172
+ **On partial records and unseen items:** The `records` map need not contain an entry for every item in the set. Items absent from the map are treated as unseen. However, applications MAY explicitly generate a record with `bucket: 0` for an item that has never been presented if they need to store pre-study initialization data, suspended states, or `appSpecific` metadata. While permitted, applications SHOULD NOT unnecessarily synthesize placeholder records for all unseen items just to fill the file, as this bloats the file size.
173
+
174
+ ---
175
+
176
+ ## Changelog
177
+
178
+ OQSEP 1.0 is part of OQSE 1.0 (2026-10-14); its changes are listed in the [OQSE changelog](./oqse.md#version-10-2026-10-14).
package/oqse.md CHANGED
@@ -33,6 +33,7 @@ In this specification, the keywords **MUST**, **MUST NOT**, **SHOULD**, **SHOULD
33
33
  * [Item Types](#item-types-itemtype)
34
34
  * [Helper Data Structures](#helper-data-structures)
35
35
  * [Complete Example File](#complete-example-file)
36
+ * [Canonical and Authoring Formats](#canonical-and-authoring-formats)
36
37
  * [OQSE Markdown (Note Sets)](#oqse-markdown-note-sets)
37
38
  * [Validation Rules and Constraints](#validation-rules-and-constraints)
38
39
  * [Error Handling](#error-handling-error-handling-policy)
@@ -481,7 +482,7 @@ If same key exists in both scopes, local definition (`item.assets`) takes preced
481
482
 
482
483
  ## Choices and Answers by ID
483
484
 
484
- Lists that the learner selects from or arranges are lists of **choices**: options of `mcq-single` / `mcq-multi` and of `fill-in-select` blanks, `items` of `sort-items`, `prompts` and `matches` of `match-pairs`, the sides of `match-complex`, `categories` of `categorize`, `rows` and `columns` of `matrix` and `labels` of `diagram-label`.
485
+ Lists that the learner selects from or arranges are lists of **choices**: options of `mcq-single` / `mcq-multi` and of `fill-in-select` blanks, `items` of `sort-items`, `prompts` and `matches` of `match-pairs` and `match-complex`, `categories` of `categorize`, `rows` and `columns` of `matrix` and `labels` of `diagram-label`.
485
486
 
486
487
  ```json
487
488
  { "id": "b", "text": "Praha", "explanation": "The capital since the 10th century." }
@@ -535,7 +536,7 @@ This section defines what a learner's answer to each item type looks like and wh
535
536
  | `fill-in-blanks` | Object `{ token: string }` | For every token in `blanks`, the string matches one of its accepted answers ([Comparing Typed Answers](#comparing-typed-answers)). |
536
537
  | `fill-in-select` | Object `{ token: choiceId }` | For every token in `blanks`, the choice is its `correctId`. |
537
538
  | `match-pairs` | Object `{ promptId: matchId }` | Every prompt is mapped to its match in `pairs`. |
538
- | `match-complex` | Array of `[leftId, rightId]` pairs (oriented: left first) | It contains no pair outside `connections`, and at least `minCorrect` (default: all) different pairs of `connections`. |
539
+ | `match-complex` | Array of `[promptId, matchId]` pairs (oriented: prompt first) | It contains no pair outside `connections`, and at least `minCorrect` (default: all) different pairs of `connections`. |
539
540
  | `sort-items` | Array of item IDs in the learner's order | It equals `correctOrder`. |
540
541
  | `slider` | Number | `|answer − correctAnswer| <= tolerance`. |
541
542
  | `numeric-input` | Number | `|answer − correctAnswer| <= tolerance` (see [`numeric-input`](#type-numeric-input-numeric-input)). |
@@ -544,7 +545,7 @@ This section defines what a learner's answer to each item type looks like and wh
544
545
  | `categorize` | Object `{ entryId: categoryId }` | Every entry is mapped to its `correctCategoryId`. |
545
546
  | `timeline` | Array of event IDs in the learner's order | Every event appears exactly once and the order is correct ([see TimelineEvent](#timelineevent-for-timeline)). |
546
547
  | `matrix` | Array of `[rowId, columnId]` cells | It contains exactly the cells of `correctCells` (no duplicates). |
547
- | `math-input` | LaTeX string | It equals `correctAnswer` or one of `alternativeAnswers` by the string comparison rules of [`math-input`](#type-math-input-math-input) (`$` delimiters typed by the learner are ignored), or is mathematically equivalent when the application checks symbolically. |
548
+ | `math-input` | LaTeX string | It equals one of `correctAnswers` by the string comparison rules of [`math-input`](#type-math-input-math-input) (`$` delimiters typed by the learner are ignored), or is mathematically equivalent when the application checks symbolically. |
548
549
  | `diagram-label` | Object `{ zoneId: labelId }`; with `requireTyping`, `{ zoneId: string }` | Every zone gets its `correctLabelId`; with `requireTyping`, every typed string matches the text of the zone's correct label ([Comparing Typed Answers](#comparing-typed-answers)). |
549
550
  | `chess-puzzle` | Array of moves in SAN: the learner's moves and the opponent's replies, as in `correctAnswers` | It equals one of `correctAnswers` move by move, after normalizing each move ([see chess-puzzle](#type-chess-puzzle-chess-puzzle)). |
550
551
  | `flashcard` | Self-rating: integer `1`–`4` on the [OQSEP confidence scale](./oqse-progress.md#confidence-scale) | The rating is `3` or `4` (recalled). Applications SHOULD offer all four ratings. |
@@ -781,9 +782,10 @@ A set consisting only of notes can also be written as one Markdown document, see
781
782
  ### `type: "match-complex"` (Advanced Matching)
782
783
 
783
784
  * `question` (string, optional): **Rich Content.** Instructions.
784
- * `leftItems` (Choice[], required): Items on left side.
785
- * `rightItems` (Choice[], required): Items on right side.
786
- * `connections` (string[][], required): Correct pairs as `[leftId, rightId]`.
785
+ * `prompts` (Choice[], required): What the learner connects from (as in `match-pairs`).
786
+ * `matches` (Choice[], required): What the prompts are connected to; may contain distractors.
787
+ * `connections` (string[][], required): Correct pairs as `[promptId, matchId]`.
788
+ * **Note:** The names say what the lists mean, not where they are shown: an application may lay them out side by side, one above the other, or in any other way.
787
789
  * `minCorrect` (number, optional): Minimum number of correct connections required for success. If not specified, user must find all connections in `connections`.
788
790
  * **Note:** This type explicitly supports more complex graph structures:
789
791
  * Multiple connections are allowed in both directions (`1:N`, `N:1`, `N:M`).
@@ -797,12 +799,12 @@ A set consisting only of notes can also be written as one Markdown document, see
797
799
  "id": "019aa5f5-907f-7373-aeed-128158901d17",
798
800
  "type": "match-complex",
799
801
  "question": "Match rockets to their main engines (some options are extra):",
800
- "leftItems": [
802
+ "prompts": [
801
803
  { "id": "starship", "text": "Starship" },
802
804
  { "id": "falcon9", "text": "Falcon 9" },
803
805
  { "id": "sls", "text": "Space Launch System" }
804
806
  ],
805
- "rightItems": [
807
+ "matches": [
806
808
  { "id": "raptor", "text": "Raptor" },
807
809
  { "id": "merlin", "text": "Merlin" },
808
810
  { "id": "rs25", "text": "RS-25" },
@@ -992,10 +994,8 @@ In the example above, `BE-4` has no connection: it is a distractor.
992
994
  ### `type: "math-input"` (Math Input)
993
995
 
994
996
  * `question` (string, required): **Rich Content.** Question text.
995
- * `correctAnswer` (string, required): Correct answer as raw LaTeX **without** `$` / `$$` delimiters (e.g., `2x + 2`). Values wrapped in `$` MUST cause a validation error.
996
- * `alternativeAnswers` (string[], optional): Array of other raw **LaTeX strings** (same format as `correctAnswer`, without delimiters) that should also be considered correct (e.g., `2*x + 2`, `2+2x`).
997
- * `tolerance` (number, optional): For purely numeric answers, allowed numeric deviation.
998
- * **Implementation note:** Application **SHOULD** support symbolic checking of mathematical equivalence (e.g., recognize `2+2x` same as `2x+2`). All LaTeX notations MUST be compatible with web rendering libraries (KaTeX, MathJax). If application does not support symbolic checking (e.g., due to technical limitations), it **MUST** use plain string comparison. **String comparison rules:** (1) Normalize whitespace (remove all spaces inside LaTeX). (2) Compare string with `correctAnswer` and each item in `alternativeAnswers`. (3) Case-sensitive. (4) DO NOT convert symbols (e.g., `*` stays `*`, not rewritten to `\cdot`). **Recommendations for set authors:** Prefer normalized form without spaces if Application doesn't support symbolic mathematical checking (CAS). Include all common notation variants in `alternativeAnswers`. Example: `correctAnswer: '2x+2', alternativeAnswers: ['2*x+2', '2 x + 2', '2+2x']`.
997
+ * `correctAnswers` (string[], required, at least 1): Correct answers as raw LaTeX **without** `$` / `$$` delimiters (e.g., `["2x + 2", "2*x + 2", "2+2x"]`); all are equally correct, as the `correctAnswers` of `short-answer`. Values wrapped in `$` MUST cause a validation error.
998
+ * **Implementation note:** Application **SHOULD** support symbolic checking of mathematical equivalence (e.g., recognize `2+2x` same as `2x+2`). All LaTeX notations MUST be compatible with web rendering libraries (KaTeX, MathJax). If application does not support symbolic checking (e.g., due to technical limitations), it **MUST** use plain string comparison. **String comparison rules:** (1) Normalize whitespace (remove all spaces inside LaTeX). (2) Compare the string with each item of `correctAnswers`. (3) Case-sensitive. (4) DO NOT convert symbols (e.g., `*` stays `*`, not rewritten to `\cdot`). **Recommendations for set authors:** Prefer normalized form without spaces if Application doesn't support symbolic mathematical checking (CAS). Include all common notation variants in `correctAnswers`. Example: `correctAnswers: ['2x+2', '2*x+2', '2+2x']`.
999
999
 
1000
1000
  **Example:**
1001
1001
 
@@ -1005,8 +1005,8 @@ In the example above, `BE-4` has no connection: it is a distractor.
1005
1005
  "type": "math-input",
1006
1006
  "difficulty": 4,
1007
1007
  "question": "Simplify the following expression: $2(x+1)$",
1008
- "correctAnswer": "2x + 2",
1009
- "alternativeAnswers": [
1008
+ "correctAnswers": [
1009
+ "2x + 2",
1010
1010
  "2*x + 2"
1011
1011
  ]
1012
1012
  }
@@ -1203,23 +1203,24 @@ Defines clickable zone on image or named mesh in a 3D model. For `pin-on-image`,
1203
1203
 
1204
1204
  **For `type: "rect"` (rectangle):**
1205
1205
 
1206
- * `x` (number): Percent X from left edge.
1207
- * `y` (number): Percent Y from top edge.
1208
- * `width` (number): Width in percentages.
1209
- * `height` (number): Height in percentages.
1206
+ * `x` (number, required): Percent X from left edge.
1207
+ * `y` (number, required): Percent Y from top edge.
1208
+ * `width` (number, required): Width in percentages.
1209
+ * `height` (number, required): Height in percentages.
1210
1210
 
1211
1211
  **For `type: "circle"` (circle):**
1212
1212
 
1213
- * `x` (number): Center X in percentages.
1214
- * `y` (number): Center Y in percentages.
1215
- * `radius` (number): Radius in percentages.
1213
+ * `x` (number, required): Center X in percentages.
1214
+ * `y` (number, required): Center Y in percentages.
1215
+ * `radius` (number, required): Radius in percentages.
1216
1216
 
1217
1217
  **For `type: "polygon"` (polygon):**
1218
1218
 
1219
- * `points` (object[]): Array of points defining shape. Each point is `{ "x": number, "y": number }`.
1219
+ * `points` (object[], required, at least 3): Array of points defining shape. Each point is `{ "x": number, "y": number }` (both required).
1220
1220
 
1221
1221
  **For `type: "mesh"` (3D model part):**
1222
- * `targetName` (string): Name (or partial name) of the object/mesh in the 3D scene (glTF node name).
1222
+
1223
+ * `targetName` (string, required): **Plain Text.** Name (or partial name) of the object/mesh in the 3D scene (glTF node name).
1223
1224
 
1224
1225
  ### `CategorizeEntry` (for `categorize`)
1225
1226
 
@@ -1472,8 +1473,8 @@ This optional object (`item.pedagogy`) serves to store advanced metadata about d
1472
1473
  "tags": ["Rocket Engine"],
1473
1474
  "difficulty": 3,
1474
1475
  "question": "Match countries with their main spaceports (some items are extra):",
1475
- "leftItems": [{ "id": "usa", "text": "USA" }, { "id": "france", "text": "France" }, { "id": "kz", "text": "Kazakhstan" }],
1476
- "rightItems": [{ "id": "cape", "text": "Cape Canaveral" }, { "id": "kourou", "text": "Kourou" }, { "id": "baikonur", "text": "Baikonur" }, { "id": "cornwall", "text": "Spaceport Cornwall" }],
1476
+ "prompts": [{ "id": "usa", "text": "USA" }, { "id": "france", "text": "France" }, { "id": "kz", "text": "Kazakhstan" }],
1477
+ "matches": [{ "id": "cape", "text": "Cape Canaveral" }, { "id": "kourou", "text": "Kourou" }, { "id": "baikonur", "text": "Baikonur" }, { "id": "cornwall", "text": "Spaceport Cornwall" }],
1477
1478
  "connections": [
1478
1479
  ["usa", "cape"],
1479
1480
  ["france", "kourou"],
@@ -1531,6 +1532,15 @@ This optional object (`item.pedagogy`) serves to store advanced metadata about d
1531
1532
 
1532
1533
  -----
1533
1534
 
1535
+ ## Canonical and Authoring Formats
1536
+
1537
+ The JSON file described in this document is the **canonical** form of a study set. Applications store it, exchange it and validate it; item IDs, learning progress and statistics refer to it.
1538
+
1539
+ People and AI assistants may write sets in **authoring formats** that are easier to write – today [OQSE Markdown](#oqse-markdown-note-sets) for notes; tools may offer others (e.g., a shorthand with options as plain texts). An authoring format is only an input: a tool converts it to the canonical form, and applications are not required to read it. Two rules apply to every authoring format:
1540
+
1541
+ 1. **The canonical form is what is stored and exchanged.** An authoring file is never a substitute for it.
1542
+ 2. **Item IDs survive edits.** Progress and statistics are bound to item IDs (and answers to choice IDs). Converting an edited source again MUST keep the IDs of existing items and choices: the source keeps them (as OQSE Markdown does in its metadata comments), or the authoring format is used once and the canonical file is edited afterwards.
1543
+
1534
1544
  ## OQSE Markdown (Note Sets)
1535
1545
 
1536
1546
  A set that contains only `note` items MAY be written as a single Markdown document (RECOMMENDED extension `.oqse.md`, UTF-8). Each note is a section under a heading. The representation is **losslessly equivalent** to the JSON file (see the normalization rules below) and exists so that notes with complex content (Mermaid diagrams, LaTeX, code) can be written and edited without escaping them inside JSON strings. It follows the common Markdown convention of a single top-level heading (as recommended by markdownlint and GitHub) and works well in editors such as Obsidian.
@@ -2211,8 +2221,11 @@ OQSE is an open standard. Suggestions for improvements:
2211
2221
 
2212
2222
  -----
2213
2223
 
2214
- ### Version 1.0 (October 2026)
2224
+ ### Version 1.0 (2026-10-14)
2215
2225
  The first stable version. From now on, 1.x versions only add (see [Versioning and Compatibility](#versioning-and-compatibility)).
2226
+ * The shape fields of a `HotspotObject` (`x`, `y`, `width`, `height`, `radius`, `points`, `targetName`) are marked required, as validators always treated them.
2227
+ * Before 1.0, two item types were aligned with the others: `math-input` has `correctAnswers` (a list, like `short-answer`) instead of `correctAnswer` + `alternativeAnswers`, and `match-complex` has `prompts` / `matches` (like `match-pairs`) instead of `leftItems` / `rightItems`. `math-input` has no `tolerance` (its answers are compared as LaTeX; numbers are `numeric-input`).
2228
+ * [Canonical and Authoring Formats](#canonical-and-authoring-formats): the JSON file is the canonical form that is stored and exchanged; authoring formats (OQSE Markdown for notes, tool-specific shorthands) are converted to it and keep item IDs across edits.
2216
2229
  * Version rules for sets, manifests and progress files: applications read the MAJOR versions they support and never read another MAJOR version (including the pre-release 0.x drafts) silently as a supported one.
2217
2230
  * Schema URLs: `https://cdn.jsdelivr.net/npm/@memizy/oqse@1/schemas/oqse-v1.0.json` (and `oqse-manifest-v1.0.json`, `oqse-progress-v1.0.json`, `oqse-header-v1.0.json`).
2218
2231
  * Markdown images `![alt](https://…)` are explicitly allowed for pictures from the web (absolute URL; `<asset:key />` for everything else). Blank tags belong only in the `text` of `fill-in-blanks` and `fill-in-select` (elsewhere a warning; shown as an empty line).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memizy/oqse",
3
- "version": "1.0.0-rc.5",
3
+ "version": "1.0.0-rc.6",
4
4
  "description": "TypeScript SDK and Zod validators for the OQSE specification.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -22,12 +22,13 @@
22
22
  "oqse-manifest.md",
23
23
  "oqse-progress.md",
24
24
  "oqse-header.md",
25
+ "CHANGELOG.md",
25
26
  "LICENSE"
26
27
  ],
27
28
  "scripts": {
28
29
  "dev": "vite build --watch",
29
30
  "generate:schemas": "tsx scripts/generate-schemas.ts",
30
- "build": "npm run generate:schemas && tsc && vite build && tsx scripts/fix-types-entry.ts",
31
+ "build": "npm run generate:schemas && tsc && vite build && tsx scripts/fix-types-entry.ts && node ../../scripts/dts-extensions.mjs dist",
31
32
  "test": "vitest run",
32
33
  "test:watch": "vitest",
33
34
  "prepublishOnly": "npm run build",
@@ -47,6 +48,17 @@
47
48
  "yaml": "^2.9.1",
48
49
  "zod": "^4.3.6"
49
50
  },
51
+ "license": "MIT",
52
+ "author": "Memizy Team",
53
+ "keywords": [
54
+ "oqse",
55
+ "memizy",
56
+ "study-set",
57
+ "quiz",
58
+ "flashcards",
59
+ "education",
60
+ "zod"
61
+ ],
50
62
  "repository": {
51
63
  "type": "git",
52
64
  "url": "git+https://github.com/memizy/memizy.git",
@@ -3638,7 +3638,7 @@
3638
3638
  "type": "string",
3639
3639
  "maxLength": 10000
3640
3640
  },
3641
- "leftItems": {
3641
+ "prompts": {
3642
3642
  "minItems": 1,
3643
3643
  "maxItems": 100,
3644
3644
  "type": "array",
@@ -3667,7 +3667,7 @@
3667
3667
  "additionalProperties": {}
3668
3668
  }
3669
3669
  },
3670
- "rightItems": {
3670
+ "matches": {
3671
3671
  "minItems": 1,
3672
3672
  "maxItems": 100,
3673
3673
  "type": "array",
@@ -3725,8 +3725,8 @@
3725
3725
  "required": [
3726
3726
  "id",
3727
3727
  "type",
3728
- "leftItems",
3729
- "rightItems",
3728
+ "prompts",
3729
+ "matches",
3730
3730
  "connections"
3731
3731
  ],
3732
3732
  "additionalProperties": {}
@@ -6133,25 +6133,19 @@
6133
6133
  "minLength": 1,
6134
6134
  "maxLength": 10000
6135
6135
  },
6136
- "correctAnswer": {
6137
- "type": "string"
6138
- },
6139
- "alternativeAnswers": {
6136
+ "correctAnswers": {
6137
+ "minItems": 1,
6140
6138
  "type": "array",
6141
6139
  "items": {
6142
6140
  "type": "string"
6143
6141
  }
6144
- },
6145
- "tolerance": {
6146
- "type": "number",
6147
- "minimum": 0
6148
6142
  }
6149
6143
  },
6150
6144
  "required": [
6151
6145
  "id",
6152
6146
  "type",
6153
6147
  "question",
6154
- "correctAnswer"
6148
+ "correctAnswers"
6155
6149
  ],
6156
6150
  "additionalProperties": {}
6157
6151
  },
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1,5 +0,0 @@
1
- /**
2
- * The examples in the specification (oqse.md) must stay valid: a broken example
3
- * teaches authors and AI models the wrong thing.
4
- */
5
- export {};
@@ -1 +0,0 @@
1
- export {};