@memizy/oqse 0.1.1 → 0.1.3

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.
@@ -2,7 +2,7 @@
2
2
  * OQSEP v0.1 Type Definitions
3
3
  * (Open Quiz & Study Exchange - Progress)
4
4
  *
5
- * @see /SPECIFICATION.md#25-user-progress-portability-oqsep
5
+ * @see ../oqse-progress.md#data-model
6
6
  */
7
7
  /** Aggregate performance statistics for a single item. */
8
8
  export interface StatsObject {
@@ -16,7 +16,7 @@ export interface LastAnswerObject {
16
16
  confidence?: 1 | 2 | 3 | 4;
17
17
  answeredAt: string;
18
18
  timeSpent?: number;
19
- hintsUsed?: number;
19
+ hintsUsed: number;
20
20
  isSkipped?: boolean;
21
21
  }
22
22
  /** Progress state for one item from the linked OQSE set. */
@@ -0,0 +1,46 @@
1
+ import { FeatureProfile } from './oqse';
2
+ export interface TokenMap {
3
+ [tokenId: string]: {
4
+ type: 'asset' | 'blank';
5
+ key: string;
6
+ originalTag: string;
7
+ };
8
+ }
9
+ export interface RichTextProcessingOptions {
10
+ /** * Function to parse Markdown to HTML (e.g., `marked.parse`).
11
+ * For Tier 1, this should ideally be configured to escape raw HTML.
12
+ */
13
+ markdownParser: (markdown: string) => string;
14
+ /** * Function to sanitize HTML (e.g., `DOMPurify.sanitize`).
15
+ * REQUIRED if the set declares the 'html' feature (Tier 2).
16
+ */
17
+ htmlSanitizer?: (html: string) => string;
18
+ /** Callback to render an <asset:key /> tag into HTML (e.g., <img src="...">) */
19
+ assetReplacer?: (key: string) => string;
20
+ /** Callback to render a <blank:key /> tag into HTML (e.g., <input type="text">) */
21
+ blankReplacer?: (key: string) => string;
22
+ }
23
+ /**
24
+ * Step 1: Tokenization.
25
+ * Temporarily replaces <asset:key /> and <blank:key /> with cryptographically safe
26
+ * text tokens so they survive Markdown parsing and HTML sanitization.
27
+ */
28
+ export declare function tokenizeOqseTags(rawText: string): {
29
+ text: string;
30
+ tokens: TokenMap;
31
+ };
32
+ /**
33
+ * Step 2 (Tier 1): Strict HTML Validation.
34
+ * If 'html' is not allowed, any raw HTML tags written by the user must cause a validation error.
35
+ */
36
+ export declare function validateTier1Markdown(textWithoutOqseTags: string): void;
37
+ /**
38
+ * Step 4: Detokenization.
39
+ * Replaces the safe tokens back with the final interactive HTML elements.
40
+ */
41
+ export declare function detokenizeOqseTags(safeHtml: string, tokens: TokenMap, options: Pick<RichTextProcessingOptions, 'assetReplacer' | 'blankReplacer'>): string;
42
+ /**
43
+ * Main Facade for rendering OQSE Rich Content securely.
44
+ * Executes the complete Tokenization -> Validation -> Parsing -> Sanitization -> Detokenization pipeline.
45
+ */
46
+ export declare function prepareRichTextForDisplay(rawContent: string, requirements: FeatureProfile | undefined, options: RichTextProcessingOptions): string;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,3 +1,17 @@
1
1
  export type UUID = string;
2
+ /**
3
+ * Validates if the given string is a syntactically valid UUID (any version).
4
+ * Uses the robust validator from the 'uuid' package.
5
+ */
2
6
  export declare function isValidUUID(id: string): boolean;
7
+ /**
8
+ * Generates a new UUIDv7.
9
+ * UUIDv7 is required by the OQSE specification for new items
10
+ * because it is time-sortable and optimized for database indexing.
11
+ */
3
12
  export declare function generateUUID(): UUID;
13
+ /**
14
+ * Checks if an object exceeds the maximum allowed nesting depth.
15
+ * OQSE specification requires a limit of 10 levels for customData and appSpecific.
16
+ */
17
+ export declare function validateJsonDepth(data: unknown, maxDepth?: number, currentDepth?: number): void;
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memizy/oqse",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "TypeScript SDK and Zod validators for the OQSE specification.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -29,6 +29,7 @@
29
29
  "prepublishOnly": "npm run build"
30
30
  },
31
31
  "devDependencies": {
32
+ "@types/uuid": "^10.0.0",
32
33
  "tsx": "^4.21.0",
33
34
  "typescript": "~6.0.2",
34
35
  "vite": "^8.0.4",
@@ -36,6 +37,7 @@
36
37
  "vitest": "^4.1.4"
37
38
  },
38
39
  "dependencies": {
40
+ "uuid": "^13.0.0",
39
41
  "zod": "^4.3.6"
40
42
  }
41
43
  }
@@ -0,0 +1,120 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "type": "object",
4
+ "properties": {
5
+ "id": {
6
+ "type": "string",
7
+ "format": "uuid",
8
+ "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
9
+ },
10
+ "title": {
11
+ "type": "string",
12
+ "minLength": 1,
13
+ "maxLength": 500
14
+ },
15
+ "language": {
16
+ "type": "string",
17
+ "minLength": 2,
18
+ "pattern": "^[a-z]{2,3}(-[A-Z][a-z]{3})?(-[A-Z]{2})?$"
19
+ },
20
+ "updatedAt": {
21
+ "type": "string",
22
+ "pattern": "^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d{3})?(Z|[+-]\\d{2}:\\d{2})?)?$"
23
+ },
24
+ "description": {
25
+ "type": "string",
26
+ "minLength": 1,
27
+ "maxLength": 5000
28
+ },
29
+ "author": {
30
+ "type": "object",
31
+ "properties": {
32
+ "name": {
33
+ "type": "string",
34
+ "minLength": 1
35
+ },
36
+ "role": {
37
+ "type": "string"
38
+ },
39
+ "email": {
40
+ "type": "string",
41
+ "format": "email",
42
+ "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
43
+ },
44
+ "url": {
45
+ "type": "string",
46
+ "format": "uri"
47
+ }
48
+ },
49
+ "required": [
50
+ "name"
51
+ ],
52
+ "additionalProperties": false
53
+ },
54
+ "subject": {
55
+ "type": "string"
56
+ },
57
+ "tags": {
58
+ "type": "array",
59
+ "items": {
60
+ "type": "string",
61
+ "minLength": 1
62
+ }
63
+ },
64
+ "createdAt": {
65
+ "type": "string",
66
+ "pattern": "^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d{3})?(Z|[+-]\\d{2}:\\d{2})?)?$"
67
+ },
68
+ "estimatedTime": {
69
+ "type": "number",
70
+ "exclusiveMinimum": 0
71
+ },
72
+ "requirements": {
73
+ "type": "object",
74
+ "properties": {
75
+ "features": {
76
+ "type": "array",
77
+ "items": {
78
+ "type": "string"
79
+ }
80
+ },
81
+ "latexPackages": {
82
+ "type": "array",
83
+ "items": {
84
+ "type": "string",
85
+ "minLength": 1
86
+ }
87
+ },
88
+ "itemProperties": {
89
+ "type": "array",
90
+ "items": {
91
+ "type": "string"
92
+ }
93
+ },
94
+ "metaProperties": {
95
+ "type": "array",
96
+ "items": {
97
+ "type": "string"
98
+ }
99
+ }
100
+ },
101
+ "additionalProperties": false
102
+ },
103
+ "url": {
104
+ "type": "string",
105
+ "format": "uri"
106
+ },
107
+ "$schema": {
108
+ "type": "string",
109
+ "format": "uri"
110
+ }
111
+ },
112
+ "required": [
113
+ "id",
114
+ "title",
115
+ "language",
116
+ "updatedAt",
117
+ "url"
118
+ ],
119
+ "additionalProperties": false
120
+ }
@@ -7,22 +7,28 @@
7
7
  "format": "uri"
8
8
  },
9
9
  "id": {
10
- "type": "string",
11
- "minLength": 1,
12
- "pattern": "^[a-z0-9-]+(\\.[a-z0-9-]+)*$"
10
+ "anyOf": [
11
+ {
12
+ "type": "string",
13
+ "format": "uri"
14
+ },
15
+ {
16
+ "type": "string",
17
+ "pattern": "^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
18
+ }
19
+ ]
13
20
  },
14
21
  "appName": {
15
22
  "type": "string",
16
23
  "minLength": 1,
17
- "maxLength": 100
24
+ "maxLength": 200
18
25
  },
19
26
  "version": {
20
27
  "type": "string",
21
28
  "pattern": "^\\d+\\.\\d+$"
22
29
  },
23
30
  "pluginVersion": {
24
- "type": "string",
25
- "pattern": "^\\d+\\.\\d+\\.\\d+$"
31
+ "type": "string"
26
32
  },
27
33
  "minOqseVersion": {
28
34
  "type": "string",
@@ -38,8 +44,7 @@
38
44
  "features": {
39
45
  "type": "array",
40
46
  "items": {
41
- "type": "string",
42
- "minLength": 1
47
+ "type": "string"
43
48
  }
44
49
  },
45
50
  "latexPackages": {
@@ -52,15 +57,13 @@
52
57
  "itemProperties": {
53
58
  "type": "array",
54
59
  "items": {
55
- "type": "string",
56
- "minLength": 1
60
+ "type": "string"
57
61
  }
58
62
  },
59
63
  "metaProperties": {
60
64
  "type": "array",
61
65
  "items": {
62
- "type": "string",
63
- "minLength": 1
66
+ "type": "string"
64
67
  }
65
68
  },
66
69
  "actions": {
@@ -194,10 +197,63 @@
194
197
  "additionalProperties": false
195
198
  },
196
199
  "author": {
200
+ "type": "object",
201
+ "properties": {
202
+ "name": {
203
+ "type": "string",
204
+ "minLength": 1
205
+ },
206
+ "role": {
207
+ "type": "string"
208
+ },
209
+ "email": {
210
+ "type": "string",
211
+ "format": "email",
212
+ "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
213
+ },
214
+ "url": {
215
+ "type": "string",
216
+ "format": "uri"
217
+ }
218
+ },
219
+ "required": [
220
+ "name"
221
+ ],
222
+ "additionalProperties": false
223
+ },
224
+ "contributors": {
225
+ "type": "array",
226
+ "items": {
227
+ "type": "object",
228
+ "properties": {
229
+ "name": {
230
+ "type": "string",
231
+ "minLength": 1
232
+ },
233
+ "role": {
234
+ "type": "string"
235
+ },
236
+ "email": {
237
+ "type": "string",
238
+ "format": "email",
239
+ "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
240
+ },
241
+ "url": {
242
+ "type": "string",
243
+ "format": "uri"
244
+ }
245
+ },
246
+ "required": [
247
+ "name"
248
+ ],
249
+ "additionalProperties": false
250
+ }
251
+ },
252
+ "license": {
197
253
  "type": "string",
198
254
  "maxLength": 100
199
255
  },
200
- "authorUrl": {
256
+ "licenseUrl": {
201
257
  "type": "string",
202
258
  "format": "uri"
203
259
  },
@@ -205,12 +261,12 @@
205
261
  "type": "array",
206
262
  "items": {
207
263
  "type": "string",
208
- "minLength": 1
264
+ "pattern": "^[a-zA-Z]{2,8}(-[a-zA-Z0-9\\-]+)*$"
209
265
  }
210
266
  },
211
267
  "description": {
212
268
  "type": "string",
213
- "maxLength": 500
269
+ "maxLength": 5000
214
270
  },
215
271
  "emoji": {
216
272
  "type": "string"
@@ -133,6 +133,7 @@
133
133
  "maximum": 9007199254740991
134
134
  },
135
135
  "hintsUsed": {
136
+ "default": 0,
136
137
  "type": "integer",
137
138
  "minimum": 0,
138
139
  "maximum": 9007199254740991
@@ -143,7 +144,8 @@
143
144
  },
144
145
  "required": [
145
146
  "isCorrect",
146
- "answeredAt"
147
+ "answeredAt",
148
+ "hintsUsed"
147
149
  ],
148
150
  "additionalProperties": false
149
151
  },