open-board-format 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +99 -36
- package/dist/index.mjs +154 -42
- package/package.json +7 -7
package/dist/index.d.mts
CHANGED
|
@@ -12,7 +12,7 @@ import { z } from "zod";
|
|
|
12
12
|
* @author Shay Cojocaru
|
|
13
13
|
* @license MIT
|
|
14
14
|
*/
|
|
15
|
-
/** Unique identifier
|
|
15
|
+
/** Unique board-element identifier, coerced to a non-empty string. */
|
|
16
16
|
declare const OBFIDSchema: z.ZodPipe<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodTransform<string, string | number>>, z.ZodString>;
|
|
17
17
|
type OBFID = z.infer<typeof OBFIDSchema>;
|
|
18
18
|
/**
|
|
@@ -26,35 +26,35 @@ type OBFFormatVersion = z.infer<typeof OBFFormatVersionSchema>;
|
|
|
26
26
|
declare const OBFLocaleCodeSchema: z.ZodString;
|
|
27
27
|
type OBFLocaleCode = z.infer<typeof OBFLocaleCodeSchema>;
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
29
|
+
* Key–value pairs mapping symbolic names to their translations in a single locale.
|
|
30
30
|
*/
|
|
31
31
|
declare const OBFLocalizedStringsSchema: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
32
32
|
type OBFLocalizedStrings = z.infer<typeof OBFLocalizedStringsSchema>;
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
34
|
+
* Locale-keyed dictionary of translated strings,
|
|
35
|
+
* e.g., `{ en: { greeting: "Hello" }, fr: { greeting: "Bonjour" } }`.
|
|
35
36
|
*/
|
|
36
37
|
declare const OBFStringsSchema: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
37
38
|
type OBFStrings = z.infer<typeof OBFStringsSchema>;
|
|
38
39
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
40
|
+
* Spelling action: a `+` prefix followed by the text to append,
|
|
41
|
+
* e.g., `"+hello"`.
|
|
41
42
|
*/
|
|
42
43
|
declare const OBFSpellingActionSchema: z.ZodString;
|
|
43
44
|
type OBFSpellingAction = z.infer<typeof OBFSpellingActionSchema>;
|
|
44
45
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* Custom actions start with ':ext_'.
|
|
46
|
+
* Specialty action prefixed with `:`, e.g., `":clear"`.
|
|
47
|
+
* Custom extensions use the `:ext_` prefix.
|
|
48
48
|
*/
|
|
49
49
|
declare const OBFSpecialtyActionSchema: z.ZodString;
|
|
50
50
|
type OBFSpecialtyAction = z.infer<typeof OBFSpecialtyActionSchema>;
|
|
51
51
|
/**
|
|
52
|
-
*
|
|
52
|
+
* Union of spelling and specialty actions that a button can trigger.
|
|
53
53
|
*/
|
|
54
54
|
declare const OBFButtonActionSchema: z.ZodUnion<readonly [z.ZodString, z.ZodString]>;
|
|
55
55
|
type OBFButtonAction = z.infer<typeof OBFButtonActionSchema>;
|
|
56
56
|
/**
|
|
57
|
-
*
|
|
57
|
+
* License terms and attribution for a resource.
|
|
58
58
|
*/
|
|
59
59
|
declare const OBFLicenseSchema: z.ZodObject<{
|
|
60
60
|
type: z.ZodString;
|
|
@@ -91,7 +91,7 @@ declare const OBFMediaSchema: z.ZodObject<{
|
|
|
91
91
|
}, z.core.$strip>;
|
|
92
92
|
type OBFMedia = z.infer<typeof OBFMediaSchema>;
|
|
93
93
|
/**
|
|
94
|
-
*
|
|
94
|
+
* Reference to a symbol in a proprietary symbol set (e.g., SymbolStix).
|
|
95
95
|
*/
|
|
96
96
|
declare const OBFSymbolInfoSchema: z.ZodObject<{
|
|
97
97
|
set: z.ZodString;
|
|
@@ -99,13 +99,14 @@ declare const OBFSymbolInfoSchema: z.ZodObject<{
|
|
|
99
99
|
}, z.core.$strip>;
|
|
100
100
|
type OBFSymbolInfo = z.infer<typeof OBFSymbolInfoSchema>;
|
|
101
101
|
/**
|
|
102
|
-
*
|
|
102
|
+
* Image resource, extending {@link OBFMediaSchema} with optional
|
|
103
|
+
* symbol and dimension properties.
|
|
103
104
|
*
|
|
104
|
-
* When resolving the image,
|
|
105
|
-
* 1. data
|
|
106
|
-
* 2. path
|
|
107
|
-
* 3. url
|
|
108
|
-
* 4. symbol
|
|
105
|
+
* When resolving the image, consumers should prefer sources in this order:
|
|
106
|
+
* 1. `data`
|
|
107
|
+
* 2. `path`
|
|
108
|
+
* 3. `url`
|
|
109
|
+
* 4. `symbol`
|
|
109
110
|
*/
|
|
110
111
|
declare const OBFImageSchema: z.ZodIntersection<z.ZodObject<{
|
|
111
112
|
id: z.ZodPipe<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodTransform<string, string | number>>, z.ZodString>;
|
|
@@ -132,7 +133,7 @@ declare const OBFImageSchema: z.ZodIntersection<z.ZodObject<{
|
|
|
132
133
|
}, z.core.$strip>>;
|
|
133
134
|
type OBFImage = z.infer<typeof OBFImageSchema>;
|
|
134
135
|
/**
|
|
135
|
-
*
|
|
136
|
+
* Audio resource. Identical to {@link OBFMediaSchema} — no additional properties.
|
|
136
137
|
*/
|
|
137
138
|
declare const OBFSoundSchema: z.ZodObject<{
|
|
138
139
|
id: z.ZodPipe<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodTransform<string, string | number>>, z.ZodString>;
|
|
@@ -152,7 +153,7 @@ declare const OBFSoundSchema: z.ZodObject<{
|
|
|
152
153
|
}, z.core.$strip>;
|
|
153
154
|
type OBFSound = z.infer<typeof OBFSoundSchema>;
|
|
154
155
|
/**
|
|
155
|
-
*
|
|
156
|
+
* Reference to another board, resolved by ID, path, or URL.
|
|
156
157
|
*/
|
|
157
158
|
declare const OBFLoadBoardSchema: z.ZodObject<{
|
|
158
159
|
id: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodTransform<string | undefined, string | number>>>;
|
|
@@ -163,7 +164,7 @@ declare const OBFLoadBoardSchema: z.ZodObject<{
|
|
|
163
164
|
}, z.core.$strip>;
|
|
164
165
|
type OBFLoadBoard = z.infer<typeof OBFLoadBoardSchema>;
|
|
165
166
|
/**
|
|
166
|
-
*
|
|
167
|
+
* Interactive element on a board, optionally linked to images, sounds, and actions.
|
|
167
168
|
*/
|
|
168
169
|
declare const OBFButtonSchema: z.ZodObject<{
|
|
169
170
|
id: z.ZodPipe<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodTransform<string, string | number>>, z.ZodString>;
|
|
@@ -189,7 +190,7 @@ declare const OBFButtonSchema: z.ZodObject<{
|
|
|
189
190
|
}, z.core.$strip>;
|
|
190
191
|
type OBFButton = z.infer<typeof OBFButtonSchema>;
|
|
191
192
|
/**
|
|
192
|
-
*
|
|
193
|
+
* Row-and-column layout that arranges buttons by their IDs.
|
|
193
194
|
*/
|
|
194
195
|
declare const OBFGridSchema: z.ZodObject<{
|
|
195
196
|
rows: z.ZodNumber;
|
|
@@ -198,7 +199,7 @@ declare const OBFGridSchema: z.ZodObject<{
|
|
|
198
199
|
}, z.core.$strip>;
|
|
199
200
|
type OBFGrid = z.infer<typeof OBFGridSchema>;
|
|
200
201
|
/**
|
|
201
|
-
*
|
|
202
|
+
* Root object of an `.obf` file: the complete definition of a single communication board.
|
|
202
203
|
*/
|
|
203
204
|
declare const OBFBoardSchema: z.ZodObject<{
|
|
204
205
|
format: z.ZodString;
|
|
@@ -285,7 +286,7 @@ declare const OBFBoardSchema: z.ZodObject<{
|
|
|
285
286
|
}, z.core.$strip>;
|
|
286
287
|
type OBFBoard = z.infer<typeof OBFBoardSchema>;
|
|
287
288
|
/**
|
|
288
|
-
*
|
|
289
|
+
* Table of contents for an `.obz` package, mapping resource IDs to their archive paths.
|
|
289
290
|
*/
|
|
290
291
|
declare const OBFManifestSchema: z.ZodObject<{
|
|
291
292
|
format: z.ZodString;
|
|
@@ -302,48 +303,101 @@ type OBFManifest = z.infer<typeof OBFManifestSchema>;
|
|
|
302
303
|
/**
|
|
303
304
|
* Parse a JSON string into a validated OBF board.
|
|
304
305
|
*
|
|
305
|
-
*
|
|
306
|
-
* error if the input is malformed or fails schema validation.
|
|
306
|
+
* Strips an optional UTF-8 BOM prefix before parsing and throws a
|
|
307
|
+
* descriptive error if the input is malformed or fails schema validation.
|
|
308
|
+
*
|
|
309
|
+
* @param json - The JSON string to parse.
|
|
310
|
+
* @returns The validated board object.
|
|
311
|
+
*
|
|
312
|
+
* @throws {Error} If the JSON is malformed or does not conform to the OBF schema.
|
|
307
313
|
*/
|
|
308
314
|
declare function parseOBF(json: string): OBFBoard;
|
|
309
315
|
/**
|
|
310
|
-
* Read a `File`
|
|
316
|
+
* Read a `File` and parse its contents as a validated OBF board.
|
|
311
317
|
*
|
|
312
318
|
* This relies on the browser `File` API; for Node environments,
|
|
313
319
|
* read the file to a string and pass it to {@link parseOBF} instead.
|
|
320
|
+
*
|
|
321
|
+
* @param file - A `File` handle pointing to an `.obf` file.
|
|
322
|
+
* @returns The validated board object.
|
|
323
|
+
*
|
|
324
|
+
* @throws {Error} If the file content is malformed or fails schema validation.
|
|
314
325
|
*/
|
|
315
326
|
declare function loadOBF(file: File): Promise<OBFBoard>;
|
|
316
327
|
/**
|
|
317
328
|
* Validate an unknown value against the OBF board schema.
|
|
318
329
|
*
|
|
319
|
-
* @
|
|
330
|
+
* @param data - The value to validate.
|
|
331
|
+
* @returns The validated board object.
|
|
332
|
+
*
|
|
333
|
+
* @throws {Error} If the value does not conform to the OBF schema.
|
|
320
334
|
*/
|
|
321
335
|
declare function validateOBF(data: unknown): OBFBoard;
|
|
322
336
|
/**
|
|
323
|
-
*
|
|
337
|
+
* Stringify an OBF board to a pretty-printed JSON string.
|
|
338
|
+
*
|
|
339
|
+
* @param board - The board to stringify.
|
|
340
|
+
* @returns A JSON string with two-space indentation.
|
|
324
341
|
*/
|
|
325
342
|
declare function stringifyOBF(board: OBFBoard): string;
|
|
326
343
|
//#endregion
|
|
327
344
|
//#region src/obz.d.ts
|
|
345
|
+
/**
|
|
346
|
+
* Fully extracted contents of an `.obz` archive.
|
|
347
|
+
*
|
|
348
|
+
* @property manifest - The package table of contents.
|
|
349
|
+
* @property boards - Board ID → validated board object.
|
|
350
|
+
* @property resources - Archive path → raw binary content (images, sounds, etc.).
|
|
351
|
+
*/
|
|
328
352
|
interface ParsedOBZ {
|
|
329
353
|
manifest: OBFManifest;
|
|
330
354
|
boards: Map<string, OBFBoard>;
|
|
331
355
|
resources: Map<string, Uint8Array>;
|
|
332
356
|
}
|
|
333
357
|
/**
|
|
334
|
-
*
|
|
358
|
+
* Read a `File` and extract its contents as a parsed OBZ package.
|
|
359
|
+
*
|
|
360
|
+
* This relies on the browser `File` API; for Node environments,
|
|
361
|
+
* read the file to an `ArrayBuffer` and pass it to {@link extractOBZ} instead.
|
|
362
|
+
*
|
|
363
|
+
* @param file - A `File` handle pointing to an `.obz` archive.
|
|
364
|
+
* @returns The parsed manifest, boards, and binary resources.
|
|
365
|
+
*
|
|
366
|
+
* @throws {Error} If the file is not a valid ZIP or the manifest is missing.
|
|
335
367
|
*/
|
|
336
368
|
declare function loadOBZ(file: File): Promise<ParsedOBZ>;
|
|
337
369
|
/**
|
|
338
|
-
*
|
|
370
|
+
* Decompress an OBZ archive and return its manifest, boards, and resources.
|
|
371
|
+
*
|
|
372
|
+
* @param archive - The OBZ archive as an `ArrayBuffer`.
|
|
373
|
+
* @returns The parsed manifest, a map of board IDs to validated boards,
|
|
374
|
+
* and a map of file paths to their binary content.
|
|
375
|
+
*
|
|
376
|
+
* @throws {Error} If the archive is not a valid ZIP or the manifest is missing.
|
|
339
377
|
*/
|
|
340
378
|
declare function extractOBZ(archive: ArrayBuffer): Promise<ParsedOBZ>;
|
|
341
379
|
/**
|
|
342
|
-
* Parse and validate
|
|
380
|
+
* Parse and validate an OBZ manifest — the table of contents that maps
|
|
381
|
+
* board IDs to their file paths within the archive.
|
|
382
|
+
*
|
|
383
|
+
* @param json - A JSON string representing the manifest.
|
|
384
|
+
* @returns The validated manifest object.
|
|
385
|
+
*
|
|
386
|
+
* @throws {Error} If the JSON is malformed or fails schema validation.
|
|
343
387
|
*/
|
|
344
388
|
declare function parseManifest(json: string): OBFManifest;
|
|
345
389
|
/**
|
|
346
|
-
* Bundle boards and optional resources into a
|
|
390
|
+
* Bundle boards and optional resources into a compressed OBZ archive.
|
|
391
|
+
*
|
|
392
|
+
* A manifest is generated automatically from the supplied boards,
|
|
393
|
+
* using the `rootBoardId` to designate the entry-point board.
|
|
394
|
+
*
|
|
395
|
+
* @param boards - The boards to include in the archive.
|
|
396
|
+
* @param rootBoardId - The ID of the board that serves as the archive's entry point.
|
|
397
|
+
* @param resources - Optional map of file paths to binary content (images, sounds, etc.).
|
|
398
|
+
* @returns A `Blob` containing the compressed OBZ archive.
|
|
399
|
+
*
|
|
400
|
+
* @throws {Error} If `rootBoardId` does not match any of the supplied boards.
|
|
347
401
|
*/
|
|
348
402
|
declare function createOBZ(boards: OBFBoard[], rootBoardId: string, resources?: Map<string, Uint8Array | ArrayBuffer>): Promise<Blob>;
|
|
349
403
|
//#endregion
|
|
@@ -351,22 +405,31 @@ declare function createOBZ(boards: OBFBoard[], rootBoardId: string, resources?:
|
|
|
351
405
|
/**
|
|
352
406
|
* Decompress a ZIP archive into a map of file paths to raw bytes.
|
|
353
407
|
*
|
|
354
|
-
* @param archive - The
|
|
408
|
+
* @param archive - The ZIP archive as an `ArrayBuffer`.
|
|
409
|
+
* @returns A map of file paths to their decompressed content.
|
|
410
|
+
*
|
|
411
|
+
* @throws {Error} If decompression fails.
|
|
355
412
|
*/
|
|
356
413
|
declare function unzip(archive: ArrayBuffer): Promise<Map<string, Uint8Array>>;
|
|
357
414
|
/**
|
|
358
415
|
* Compress a map of file paths and contents into a single ZIP archive.
|
|
359
416
|
*
|
|
360
417
|
* Accepts both `Uint8Array` and `ArrayBuffer` values so callers can
|
|
361
|
-
* pass the output of
|
|
418
|
+
* pass the output of {@link unzip} directly or supply raw `ArrayBuffer`s
|
|
362
419
|
* without converting first.
|
|
363
420
|
*
|
|
364
421
|
* @param entries - A map of file paths to their content bytes.
|
|
422
|
+
* @returns The compressed archive as a `Uint8Array`.
|
|
423
|
+
*
|
|
424
|
+
* @throws {Error} If compression fails.
|
|
365
425
|
*/
|
|
366
426
|
declare function zip(entries: Map<string, Uint8Array | ArrayBuffer>): Promise<Uint8Array>;
|
|
367
427
|
/**
|
|
368
|
-
* Test whether an `ArrayBuffer` begins with the
|
|
369
|
-
*
|
|
428
|
+
* Test whether an `ArrayBuffer` begins with the two-byte ZIP magic
|
|
429
|
+
* prefix (`PK`).
|
|
430
|
+
*
|
|
431
|
+
* @param archive - The buffer to inspect.
|
|
432
|
+
* @returns `true` if the buffer starts with the ZIP signature.
|
|
370
433
|
*/
|
|
371
434
|
declare function isZip(archive: ArrayBuffer): boolean;
|
|
372
435
|
//#endregion
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { unzip as unzip$1, zip as zip$1 } from "fflate";
|
|
3
|
-
|
|
4
3
|
//#region src/schema.ts
|
|
5
4
|
/**
|
|
6
5
|
* Open Board Format (OBF) Zod Schemas
|
|
@@ -22,7 +21,7 @@ const OBFOptionalIDSchema = z.union([z.string(), z.number()]).transform((val) =>
|
|
|
22
21
|
const str = String(val);
|
|
23
22
|
return str === "" ? void 0 : str;
|
|
24
23
|
}).optional();
|
|
25
|
-
/** Unique identifier
|
|
24
|
+
/** Unique board-element identifier, coerced to a non-empty string. */
|
|
26
25
|
const OBFIDSchema = z.union([z.string(), z.number()]).transform((val) => String(val)).pipe(z.string().min(1));
|
|
27
26
|
/**
|
|
28
27
|
* Format version of the Open Board Format, e.g., 'open-board-0.1'.
|
|
@@ -33,37 +32,43 @@ const OBFFormatVersionSchema = z.string().regex(/^open-board-.+$/);
|
|
|
33
32
|
*/
|
|
34
33
|
const OBFLocaleCodeSchema = z.string();
|
|
35
34
|
/**
|
|
36
|
-
*
|
|
35
|
+
* Key–value pairs mapping symbolic names to their translations in a single locale.
|
|
37
36
|
*/
|
|
38
37
|
const OBFLocalizedStringsSchema = z.record(z.string(), z.string());
|
|
39
38
|
/**
|
|
40
|
-
*
|
|
39
|
+
* Locale-keyed dictionary of translated strings,
|
|
40
|
+
* e.g., `{ en: { greeting: "Hello" }, fr: { greeting: "Bonjour" } }`.
|
|
41
41
|
*/
|
|
42
42
|
const OBFStringsSchema = z.record(z.string(), OBFLocalizedStringsSchema);
|
|
43
43
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
44
|
+
* Spelling action: a `+` prefix followed by the text to append,
|
|
45
|
+
* e.g., `"+hello"`.
|
|
46
46
|
*/
|
|
47
47
|
const OBFSpellingActionSchema = z.string().regex(/^\+.+$/);
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* Custom actions start with ':ext_'.
|
|
49
|
+
* Specialty action prefixed with `:`, e.g., `":clear"`.
|
|
50
|
+
* Custom extensions use the `:ext_` prefix.
|
|
52
51
|
*/
|
|
53
52
|
const OBFSpecialtyActionSchema = z.string().regex(/^:[a-z][a-z0-9_-]*$/i);
|
|
54
53
|
/**
|
|
55
|
-
*
|
|
54
|
+
* Union of spelling and specialty actions that a button can trigger.
|
|
56
55
|
*/
|
|
57
56
|
const OBFButtonActionSchema = z.union([OBFSpellingActionSchema, OBFSpecialtyActionSchema]);
|
|
58
57
|
/**
|
|
59
|
-
*
|
|
58
|
+
* License terms and attribution for a resource.
|
|
60
59
|
*/
|
|
61
60
|
const OBFLicenseSchema = z.object({
|
|
61
|
+
/** Type of the license, e.g., 'CC-BY-SA'. */
|
|
62
62
|
type: z.string(),
|
|
63
|
+
/** URL to the license terms. */
|
|
63
64
|
copyright_notice_url: OBFOptionalUrlSchema,
|
|
65
|
+
/** Source URL of the resource. */
|
|
64
66
|
source_url: OBFOptionalUrlSchema,
|
|
67
|
+
/** Name of the author. */
|
|
65
68
|
author_name: z.string().optional(),
|
|
69
|
+
/** URL of the author's webpage. */
|
|
66
70
|
author_url: OBFOptionalUrlSchema,
|
|
71
|
+
/** Email address of the author. */
|
|
67
72
|
author_email: OBFOptionalEmailSchema
|
|
68
73
|
});
|
|
69
74
|
/**
|
|
@@ -75,106 +80,161 @@ const OBFLicenseSchema = z.object({
|
|
|
75
80
|
* 3. url
|
|
76
81
|
*/
|
|
77
82
|
const OBFMediaSchema = z.object({
|
|
83
|
+
/** Unique identifier for the media resource. */
|
|
78
84
|
id: OBFIDSchema,
|
|
85
|
+
/** Data URI containing the media data. */
|
|
79
86
|
data: z.string().optional(),
|
|
87
|
+
/** Path to the media file within an .obz package. */
|
|
80
88
|
path: z.string().optional(),
|
|
89
|
+
/** Data URL to fetch the media programmatically. */
|
|
81
90
|
data_url: OBFOptionalUrlSchema,
|
|
91
|
+
/** URL to the media resource. */
|
|
82
92
|
url: OBFOptionalUrlSchema,
|
|
93
|
+
/** MIME type of the media, e.g., 'image/png', 'audio/mpeg'. */
|
|
83
94
|
content_type: z.string().optional(),
|
|
95
|
+
/** Licensing information for the media. */
|
|
84
96
|
license: OBFLicenseSchema.optional()
|
|
85
97
|
});
|
|
86
98
|
/**
|
|
87
|
-
*
|
|
99
|
+
* Reference to a symbol in a proprietary symbol set (e.g., SymbolStix).
|
|
88
100
|
*/
|
|
89
101
|
const OBFSymbolInfoSchema = z.object({
|
|
102
|
+
/** Name of the symbol set, e.g., 'symbolstix'. */
|
|
90
103
|
set: z.string(),
|
|
104
|
+
/** Filename of the symbol within the set. */
|
|
91
105
|
filename: z.string()
|
|
92
106
|
});
|
|
93
107
|
/**
|
|
94
|
-
*
|
|
108
|
+
* Image resource, extending {@link OBFMediaSchema} with optional
|
|
109
|
+
* symbol and dimension properties.
|
|
95
110
|
*
|
|
96
|
-
* When resolving the image,
|
|
97
|
-
* 1. data
|
|
98
|
-
* 2. path
|
|
99
|
-
* 3. url
|
|
100
|
-
* 4. symbol
|
|
111
|
+
* When resolving the image, consumers should prefer sources in this order:
|
|
112
|
+
* 1. `data`
|
|
113
|
+
* 2. `path`
|
|
114
|
+
* 3. `url`
|
|
115
|
+
* 4. `symbol`
|
|
101
116
|
*/
|
|
102
117
|
const OBFImageSchema = OBFMediaSchema.and(z.object({
|
|
118
|
+
/** Information about a symbol from a proprietary symbol set. */
|
|
103
119
|
symbol: OBFSymbolInfoSchema.optional(),
|
|
120
|
+
/** Width of the image in pixels. */
|
|
104
121
|
width: z.number().optional(),
|
|
122
|
+
/** Height of the image in pixels. */
|
|
105
123
|
height: z.number().optional()
|
|
106
124
|
}));
|
|
107
125
|
/**
|
|
108
|
-
*
|
|
126
|
+
* Audio resource. Identical to {@link OBFMediaSchema} — no additional properties.
|
|
109
127
|
*/
|
|
110
128
|
const OBFSoundSchema = OBFMediaSchema;
|
|
111
129
|
/**
|
|
112
|
-
*
|
|
130
|
+
* Reference to another board, resolved by ID, path, or URL.
|
|
113
131
|
*/
|
|
114
132
|
const OBFLoadBoardSchema = z.object({
|
|
133
|
+
/** Unique identifier of the board to load. */
|
|
115
134
|
id: OBFOptionalIDSchema,
|
|
135
|
+
/** Name of the board to load. */
|
|
116
136
|
name: z.string().optional(),
|
|
137
|
+
/** Data URL to fetch the board programmatically. */
|
|
117
138
|
data_url: OBFOptionalUrlSchema,
|
|
139
|
+
/** URL to access the board via a web browser. */
|
|
118
140
|
url: OBFOptionalUrlSchema,
|
|
141
|
+
/** Path to the board within an .obz package. */
|
|
119
142
|
path: z.string().optional()
|
|
120
143
|
});
|
|
121
144
|
/**
|
|
122
|
-
*
|
|
145
|
+
* Interactive element on a board, optionally linked to images, sounds, and actions.
|
|
123
146
|
*/
|
|
124
147
|
const OBFButtonSchema = z.object({
|
|
148
|
+
/** Unique identifier for the button. */
|
|
125
149
|
id: OBFIDSchema,
|
|
150
|
+
/** Label text displayed on the button. */
|
|
126
151
|
label: z.string().optional(),
|
|
152
|
+
/** Alternative text for vocalization when the button is activated. */
|
|
127
153
|
vocalization: z.string().optional(),
|
|
154
|
+
/** Identifier of the image associated with the button. */
|
|
128
155
|
image_id: OBFOptionalIDSchema,
|
|
156
|
+
/** Identifier of the sound associated with the button. */
|
|
129
157
|
sound_id: OBFOptionalIDSchema,
|
|
158
|
+
/** Action associated with the button. */
|
|
130
159
|
action: OBFButtonActionSchema.optional(),
|
|
160
|
+
/** List of multiple actions for the button, executed in order. */
|
|
131
161
|
actions: z.array(OBFButtonActionSchema).optional(),
|
|
162
|
+
/** Information to load another board when this button is activated. */
|
|
132
163
|
load_board: OBFLoadBoardSchema.optional(),
|
|
164
|
+
/** Background color of the button in 'rgb' or 'rgba' format. */
|
|
133
165
|
background_color: z.string().optional(),
|
|
166
|
+
/** Border color of the button in 'rgb' or 'rgba' format. */
|
|
134
167
|
border_color: z.string().optional(),
|
|
168
|
+
/** Vertical position for absolute positioning (0.0 to 1.0). */
|
|
135
169
|
top: z.number().min(0).max(1).optional(),
|
|
170
|
+
/** Horizontal position for absolute positioning (0.0 to 1.0). */
|
|
136
171
|
left: z.number().min(0).max(1).optional(),
|
|
172
|
+
/** Width of the button for absolute positioning (0.0 to 1.0). */
|
|
137
173
|
width: z.number().min(0).max(1).optional(),
|
|
174
|
+
/** Height of the button for absolute positioning (0.0 to 1.0). */
|
|
138
175
|
height: z.number().min(0).max(1).optional()
|
|
139
176
|
});
|
|
140
177
|
/**
|
|
141
|
-
*
|
|
178
|
+
* Row-and-column layout that arranges buttons by their IDs.
|
|
142
179
|
*/
|
|
143
180
|
const OBFGridSchema = z.object({
|
|
181
|
+
/** Number of rows in the grid. */
|
|
144
182
|
rows: z.number().int().min(1),
|
|
183
|
+
/** Number of columns in the grid. */
|
|
145
184
|
columns: z.number().int().min(1),
|
|
185
|
+
/**
|
|
186
|
+
* 2D array representing the order of buttons by their IDs.
|
|
187
|
+
* Each sub-array corresponds to a row, and each element is a button ID or null for empty slots.
|
|
188
|
+
*/
|
|
146
189
|
order: z.array(z.array(z.union([OBFIDSchema, z.null()])))
|
|
147
190
|
}).refine((g) => g.order.length === g.rows, { message: "Grid order length must match rows" }).refine((g) => g.order.every((row) => row.length === g.columns), { message: "Each grid row must have length equal to columns" });
|
|
148
191
|
/**
|
|
149
|
-
*
|
|
192
|
+
* Root object of an `.obf` file: the complete definition of a single communication board.
|
|
150
193
|
*/
|
|
151
194
|
const OBFBoardSchema = z.object({
|
|
195
|
+
/** Format version of the Open Board Format, e.g., 'open-board-0.1'. */
|
|
152
196
|
format: OBFFormatVersionSchema,
|
|
197
|
+
/** Unique identifier for the board. */
|
|
153
198
|
id: OBFIDSchema,
|
|
199
|
+
/** Locale of the board as a BCP 47 language tag, e.g., 'en', 'en-US'. */
|
|
154
200
|
locale: OBFLocaleCodeSchema.optional(),
|
|
201
|
+
/** List of buttons on the board. */
|
|
155
202
|
buttons: z.array(OBFButtonSchema),
|
|
203
|
+
/** URL where the board can be accessed or downloaded. */
|
|
156
204
|
url: OBFOptionalUrlSchema,
|
|
205
|
+
/** Name of the board. */
|
|
157
206
|
name: z.string().optional(),
|
|
207
|
+
/** Description of the board in HTML format. */
|
|
158
208
|
description_html: z.string().optional(),
|
|
209
|
+
/** Grid layout information for arranging buttons. */
|
|
159
210
|
grid: OBFGridSchema,
|
|
211
|
+
/** List of images used in the board. */
|
|
160
212
|
images: z.array(OBFImageSchema).optional(),
|
|
213
|
+
/** List of sounds used in the board. */
|
|
161
214
|
sounds: z.array(OBFSoundSchema).optional(),
|
|
215
|
+
/** Licensing information for the board. */
|
|
162
216
|
license: OBFLicenseSchema.optional(),
|
|
217
|
+
/** String translations for multiple locales. */
|
|
163
218
|
strings: OBFStringsSchema.optional()
|
|
164
219
|
});
|
|
165
220
|
/**
|
|
166
|
-
*
|
|
221
|
+
* Table of contents for an `.obz` package, mapping resource IDs to their archive paths.
|
|
167
222
|
*/
|
|
168
223
|
const OBFManifestSchema = z.object({
|
|
224
|
+
/** Format version of the Open Board Format, e.g., 'open-board-0.1'. */
|
|
169
225
|
format: OBFFormatVersionSchema,
|
|
226
|
+
/** Path to the root board within the .obz package. */
|
|
170
227
|
root: z.string(),
|
|
228
|
+
/** Mapping of IDs to paths for boards, images, and sounds. */
|
|
171
229
|
paths: z.object({
|
|
230
|
+
/** Mapping of board IDs to their file paths. */
|
|
172
231
|
boards: z.record(z.string(), z.string()),
|
|
232
|
+
/** Mapping of image IDs to their file paths. */
|
|
173
233
|
images: z.record(z.string(), z.string()),
|
|
234
|
+
/** Mapping of sound IDs to their file paths. */
|
|
174
235
|
sounds: z.record(z.string(), z.string()).optional()
|
|
175
236
|
})
|
|
176
237
|
});
|
|
177
|
-
|
|
178
238
|
//#endregion
|
|
179
239
|
//#region src/obf.ts
|
|
180
240
|
const UTF8_BOM = "";
|
|
@@ -190,8 +250,13 @@ function buildParseErrorMessage(error) {
|
|
|
190
250
|
/**
|
|
191
251
|
* Parse a JSON string into a validated OBF board.
|
|
192
252
|
*
|
|
193
|
-
*
|
|
194
|
-
* error if the input is malformed or fails schema validation.
|
|
253
|
+
* Strips an optional UTF-8 BOM prefix before parsing and throws a
|
|
254
|
+
* descriptive error if the input is malformed or fails schema validation.
|
|
255
|
+
*
|
|
256
|
+
* @param json - The JSON string to parse.
|
|
257
|
+
* @returns The validated board object.
|
|
258
|
+
*
|
|
259
|
+
* @throws {Error} If the JSON is malformed or does not conform to the OBF schema.
|
|
195
260
|
*/
|
|
196
261
|
function parseOBF(json) {
|
|
197
262
|
const sanitized = stripBom(json);
|
|
@@ -204,10 +269,15 @@ function parseOBF(json) {
|
|
|
204
269
|
return validateOBF(rawBoard);
|
|
205
270
|
}
|
|
206
271
|
/**
|
|
207
|
-
* Read a `File`
|
|
272
|
+
* Read a `File` and parse its contents as a validated OBF board.
|
|
208
273
|
*
|
|
209
274
|
* This relies on the browser `File` API; for Node environments,
|
|
210
275
|
* read the file to a string and pass it to {@link parseOBF} instead.
|
|
276
|
+
*
|
|
277
|
+
* @param file - A `File` handle pointing to an `.obf` file.
|
|
278
|
+
* @returns The validated board object.
|
|
279
|
+
*
|
|
280
|
+
* @throws {Error} If the file content is malformed or fails schema validation.
|
|
211
281
|
*/
|
|
212
282
|
async function loadOBF(file) {
|
|
213
283
|
return parseOBF(await file.text());
|
|
@@ -215,7 +285,10 @@ async function loadOBF(file) {
|
|
|
215
285
|
/**
|
|
216
286
|
* Validate an unknown value against the OBF board schema.
|
|
217
287
|
*
|
|
218
|
-
* @
|
|
288
|
+
* @param data - The value to validate.
|
|
289
|
+
* @returns The validated board object.
|
|
290
|
+
*
|
|
291
|
+
* @throws {Error} If the value does not conform to the OBF schema.
|
|
219
292
|
*/
|
|
220
293
|
function validateOBF(data) {
|
|
221
294
|
const result = OBFBoardSchema.safeParse(data);
|
|
@@ -223,12 +296,14 @@ function validateOBF(data) {
|
|
|
223
296
|
return result.data;
|
|
224
297
|
}
|
|
225
298
|
/**
|
|
226
|
-
*
|
|
299
|
+
* Stringify an OBF board to a pretty-printed JSON string.
|
|
300
|
+
*
|
|
301
|
+
* @param board - The board to stringify.
|
|
302
|
+
* @returns A JSON string with two-space indentation.
|
|
227
303
|
*/
|
|
228
304
|
function stringifyOBF(board) {
|
|
229
305
|
return JSON.stringify(board, null, 2);
|
|
230
306
|
}
|
|
231
|
-
|
|
232
307
|
//#endregion
|
|
233
308
|
//#region src/zip.ts
|
|
234
309
|
/**
|
|
@@ -244,7 +319,10 @@ const COMPRESSION_LEVEL = 6;
|
|
|
244
319
|
/**
|
|
245
320
|
* Decompress a ZIP archive into a map of file paths to raw bytes.
|
|
246
321
|
*
|
|
247
|
-
* @param archive - The
|
|
322
|
+
* @param archive - The ZIP archive as an `ArrayBuffer`.
|
|
323
|
+
* @returns A map of file paths to their decompressed content.
|
|
324
|
+
*
|
|
325
|
+
* @throws {Error} If decompression fails.
|
|
248
326
|
*/
|
|
249
327
|
function unzip(archive) {
|
|
250
328
|
return new Promise((resolve, reject) => {
|
|
@@ -261,10 +339,13 @@ function unzip(archive) {
|
|
|
261
339
|
* Compress a map of file paths and contents into a single ZIP archive.
|
|
262
340
|
*
|
|
263
341
|
* Accepts both `Uint8Array` and `ArrayBuffer` values so callers can
|
|
264
|
-
* pass the output of
|
|
342
|
+
* pass the output of {@link unzip} directly or supply raw `ArrayBuffer`s
|
|
265
343
|
* without converting first.
|
|
266
344
|
*
|
|
267
345
|
* @param entries - A map of file paths to their content bytes.
|
|
346
|
+
* @returns The compressed archive as a `Uint8Array`.
|
|
347
|
+
*
|
|
348
|
+
* @throws {Error} If compression fails.
|
|
268
349
|
*/
|
|
269
350
|
function zip(entries) {
|
|
270
351
|
return new Promise((resolve, reject) => {
|
|
@@ -280,24 +361,40 @@ function zip(entries) {
|
|
|
280
361
|
});
|
|
281
362
|
}
|
|
282
363
|
/**
|
|
283
|
-
* Test whether an `ArrayBuffer` begins with the
|
|
284
|
-
*
|
|
364
|
+
* Test whether an `ArrayBuffer` begins with the two-byte ZIP magic
|
|
365
|
+
* prefix (`PK`).
|
|
366
|
+
*
|
|
367
|
+
* @param archive - The buffer to inspect.
|
|
368
|
+
* @returns `true` if the buffer starts with the ZIP signature.
|
|
285
369
|
*/
|
|
286
370
|
function isZip(archive) {
|
|
287
371
|
const bytes = new Uint8Array(archive);
|
|
288
372
|
return bytes.length >= ZIP_MAGIC.length && ZIP_MAGIC.every((byte, index) => bytes[index] === byte);
|
|
289
373
|
}
|
|
290
|
-
|
|
291
374
|
//#endregion
|
|
292
375
|
//#region src/obz.ts
|
|
293
376
|
/**
|
|
294
|
-
*
|
|
377
|
+
* Read a `File` and extract its contents as a parsed OBZ package.
|
|
378
|
+
*
|
|
379
|
+
* This relies on the browser `File` API; for Node environments,
|
|
380
|
+
* read the file to an `ArrayBuffer` and pass it to {@link extractOBZ} instead.
|
|
381
|
+
*
|
|
382
|
+
* @param file - A `File` handle pointing to an `.obz` archive.
|
|
383
|
+
* @returns The parsed manifest, boards, and binary resources.
|
|
384
|
+
*
|
|
385
|
+
* @throws {Error} If the file is not a valid ZIP or the manifest is missing.
|
|
295
386
|
*/
|
|
296
387
|
async function loadOBZ(file) {
|
|
297
388
|
return extractOBZ(await file.arrayBuffer());
|
|
298
389
|
}
|
|
299
390
|
/**
|
|
300
|
-
*
|
|
391
|
+
* Decompress an OBZ archive and return its manifest, boards, and resources.
|
|
392
|
+
*
|
|
393
|
+
* @param archive - The OBZ archive as an `ArrayBuffer`.
|
|
394
|
+
* @returns The parsed manifest, a map of board IDs to validated boards,
|
|
395
|
+
* and a map of file paths to their binary content.
|
|
396
|
+
*
|
|
397
|
+
* @throws {Error} If the archive is not a valid ZIP or the manifest is missing.
|
|
301
398
|
*/
|
|
302
399
|
async function extractOBZ(archive) {
|
|
303
400
|
if (!isZip(archive)) throw new Error("Invalid OBZ: not a ZIP file");
|
|
@@ -310,7 +407,13 @@ async function extractOBZ(archive) {
|
|
|
310
407
|
};
|
|
311
408
|
}
|
|
312
409
|
/**
|
|
313
|
-
* Parse and validate
|
|
410
|
+
* Parse and validate an OBZ manifest — the table of contents that maps
|
|
411
|
+
* board IDs to their file paths within the archive.
|
|
412
|
+
*
|
|
413
|
+
* @param json - A JSON string representing the manifest.
|
|
414
|
+
* @returns The validated manifest object.
|
|
415
|
+
*
|
|
416
|
+
* @throws {Error} If the JSON is malformed or fails schema validation.
|
|
314
417
|
*/
|
|
315
418
|
function parseManifest(json) {
|
|
316
419
|
let data;
|
|
@@ -324,7 +427,17 @@ function parseManifest(json) {
|
|
|
324
427
|
return result.data;
|
|
325
428
|
}
|
|
326
429
|
/**
|
|
327
|
-
* Bundle boards and optional resources into a
|
|
430
|
+
* Bundle boards and optional resources into a compressed OBZ archive.
|
|
431
|
+
*
|
|
432
|
+
* A manifest is generated automatically from the supplied boards,
|
|
433
|
+
* using the `rootBoardId` to designate the entry-point board.
|
|
434
|
+
*
|
|
435
|
+
* @param boards - The boards to include in the archive.
|
|
436
|
+
* @param rootBoardId - The ID of the board that serves as the archive's entry point.
|
|
437
|
+
* @param resources - Optional map of file paths to binary content (images, sounds, etc.).
|
|
438
|
+
* @returns A `Blob` containing the compressed OBZ archive.
|
|
439
|
+
*
|
|
440
|
+
* @throws {Error} If `rootBoardId` does not match any of the supplied boards.
|
|
328
441
|
*/
|
|
329
442
|
async function createOBZ(boards, rootBoardId, resources) {
|
|
330
443
|
const entries = /* @__PURE__ */ new Map();
|
|
@@ -363,6 +476,5 @@ function extractBoards(manifest, entries) {
|
|
|
363
476
|
}
|
|
364
477
|
return boards;
|
|
365
478
|
}
|
|
366
|
-
|
|
367
479
|
//#endregion
|
|
368
|
-
export { OBFBoardSchema, OBFButtonActionSchema, OBFButtonSchema, OBFFormatVersionSchema, OBFGridSchema, OBFIDSchema, OBFImageSchema, OBFLicenseSchema, OBFLoadBoardSchema, OBFLocaleCodeSchema, OBFLocalizedStringsSchema, OBFManifestSchema, OBFMediaSchema, OBFSoundSchema, OBFSpecialtyActionSchema, OBFSpellingActionSchema, OBFStringsSchema, OBFSymbolInfoSchema, createOBZ, extractOBZ, isZip, loadOBF, loadOBZ, parseManifest, parseOBF, stringifyOBF, unzip, validateOBF, zip };
|
|
480
|
+
export { OBFBoardSchema, OBFButtonActionSchema, OBFButtonSchema, OBFFormatVersionSchema, OBFGridSchema, OBFIDSchema, OBFImageSchema, OBFLicenseSchema, OBFLoadBoardSchema, OBFLocaleCodeSchema, OBFLocalizedStringsSchema, OBFManifestSchema, OBFMediaSchema, OBFSoundSchema, OBFSpecialtyActionSchema, OBFSpellingActionSchema, OBFStringsSchema, OBFSymbolInfoSchema, createOBZ, extractOBZ, isZip, loadOBF, loadOBZ, parseManifest, parseOBF, stringifyOBF, unzip, validateOBF, zip };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "open-board-format",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.5",
|
|
5
5
|
"description": "Parse, validate, and create Open Board Format (OBF/OBZ) files for AAC applications.",
|
|
6
6
|
"author": "Shay Cojocaru <shayc@outlook.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -35,14 +35,14 @@
|
|
|
35
35
|
"prepublishOnly": "npm run build"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@types/node": "^25.
|
|
39
|
-
"bumpp": "^
|
|
40
|
-
"tsdown": "^0.
|
|
41
|
-
"typescript": "
|
|
42
|
-
"vitest": "^4.
|
|
38
|
+
"@types/node": "^25.7.0",
|
|
39
|
+
"bumpp": "^11.1.0",
|
|
40
|
+
"tsdown": "^0.22.0",
|
|
41
|
+
"typescript": "~6.0.3",
|
|
42
|
+
"vitest": "^4.1.6"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"fflate": "^0.8.2",
|
|
46
|
-
"zod": "^4.3
|
|
46
|
+
"zod": "^4.4.3"
|
|
47
47
|
}
|
|
48
48
|
}
|