@shayc/open-board-format
v0.1.7
Published
A TypeScript toolkit for Open Board Format — the open standard for Augmentative and Alternative Communication (AAC) boards.
Maintainers
Readme
@shayc/open-board-format
A TypeScript toolkit for Open Board Format — the open standard for Augmentative and Alternative Communication (AAC) boards. Parse, validate, and create OBF boards and OBZ packages, all backed by Zod schemas with full TypeScript types inferred.
OBF (.obf) is a JSON file describing a single communication board — buttons, images, sounds, grid layout, metadata. OBZ (.obz) is a ZIP archive bundling one or more .obf boards with their media and a manifest.json.
Install
npm install @shayc/open-board-formatQuick start
import { parseOBF } from "@shayc/open-board-format";
const board = parseOBF(jsonString);
console.log(board.id, board.buttons.length);parseOBF throws on invalid input; the returned value is a fully typed OBFBoard. For other input shapes (already-parsed object, browser File, OBZ archive), see Overview.
Overview
Two file types; pick the entry point by what you have:
- OBF is a single board (a JSON object). Use
parseOBFfor a JSON string,validateOBFfor an already-parsed object,loadOBFfor a browserFile.stringifyOBFserializes back out. - OBZ is a package of boards plus media (a ZIP archive). Use
loadOBZfor aFile,extractOBZfor anArrayBuffer,createOBZto build a new one.
Every OBF type ships with a matching *Schema Zod schema (e.g. OBFBoardSchema, OBFManifestSchema), so you can validate inline with safeParse or wire the schema straight into an API contract — the TypeScript types are inferred from those schemas.
Validation preserves unknown fields rather than stripping them, so vendor extensions allowed by the OBF spec survive a parseOBF → stringifyOBF round trip.
Requirements
- Module format: ESM only.
- Runtime: browser or Node 22+ — works against
File,ArrayBuffer, andBlob.
Examples
Extract an OBZ package
import { loadOBZ, extractOBZ } from "@shayc/open-board-format";
// From a File (e.g. drag-and-drop)
const { manifest, boards, resources } = await loadOBZ(file);
// Or from an ArrayBuffer (e.g. fetch response)
const parsed = await extractOBZ(buffer);
const homeBoard = parsed.boards.get("1");
const imageBytes = parsed.resources.get("images/logo.png");Create an OBZ package
import { createOBZ } from "@shayc/open-board-format";
import type { OBFBoard } from "@shayc/open-board-format";
const boards: OBFBoard[] = [
{
format: "open-board-0.1",
id: "board-1",
buttons: [{ id: "btn-1", label: "Hello" }],
grid: { rows: 1, columns: 1, order: [["btn-1"]] },
},
];
const pngBytes = new Uint8Array(/* ... */);
const resources = new Map([["images/logo.png", pngBytes]]);
const blob = await createOBZ(boards, "board-1", resources);Validate with Zod directly
import { OBFBoardSchema } from "@shayc/open-board-format";
const result = OBFBoardSchema.safeParse(data);
if (result.success) {
console.log(result.data.buttons);
} else {
console.error(result.error.issues);
}API
OBF (single board)
| Function | Description |
| --------------------- | ------------------------------------------------------------ |
| parseOBF(json) | Parse a JSON string into a validated OBFBoard |
| validateOBF(data) | Validate an unknown object as OBFBoard (throws on failure) |
| stringifyOBF(board) | Serialize an OBFBoard to a JSON string |
| loadOBF(file) | Load an OBFBoard from a browser File |
OBZ (board package)
| Function | Description |
| -------------------------------------------- | ------------------------------------------------------------- |
| loadOBZ(file) | Load an OBZ package from a browser File |
| extractOBZ(archive) | Extract boards, manifest, and resources from an ArrayBuffer |
| createOBZ(boards, rootBoardId, resources?) | Create an OBZ package as a Blob |
| parseManifest(json) | Parse a manifest.json string into a validated OBFManifest |
Utilities
| Function | Description |
| ---------------- | -------------------------------------------------------- |
| isZip(archive) | Check if an ArrayBuffer starts with a ZIP magic number |
| zip(entries) | Create a ZIP from a map of paths to buffers |
| unzip(archive) | Extract a ZIP into a map of paths to Uint8Array |
Types
| Type | Description |
| --------------------- | --------------------------------------------------------------------------- |
| OBFBoard | A single communication board |
| OBFGrid | Grid layout (rows, columns, order) |
| OBFButton | A button on the board |
| OBFButtonAction | Button action (spelling or specialty) |
| OBFSpellingAction | Spelling action (e.g., +s) |
| OBFSpecialtyAction | Specialty action (e.g., :clear) |
| OBFLoadBoard | Reference to load another board |
| OBFMedia | Common media properties (base for OBFImage and OBFSound) |
| OBFImage | An image resource (extends OBFMedia) |
| OBFSound | A sound resource (extends OBFMedia) |
| OBFSymbolInfo | Symbol set reference |
| OBFManifest | OBZ package manifest |
| ParsedOBZ | Return type of extractOBZ / loadOBZ — { manifest, boards, resources } |
| OBFID | Unique identifier (string, coerced from number) |
| OBFFormatVersion | Format version string (e.g., open-board-0.1) |
| OBFLicense | Licensing information |
| OBFLocaleCode | BCP 47 locale code |
| OBFLocalizedStrings | Key-value string translations |
| OBFStrings | Multi-locale string translations |
Schemas
Every type above except ParsedOBZ is exported alongside a matching Zod schema with a Schema suffix — OBFBoard → OBFBoardSchema, OBFManifest → OBFManifestSchema, and so on. Import any of them to validate with safeParse/parse or to compose into your own schemas:
import { OBFButtonSchema, OBFManifestSchema } from "@shayc/open-board-format";Errors
All failures throw plain Error. The message identifies what failed, typically with one of these prefixes:
Invalid OBF:— schema validation rejected an OBF board.Invalid OBZ:— the package was rejected. On read: not a ZIP, missing manifest, or the manifest references a board file not in the archive. On write (createOBZ):rootBoardIdmatches no board, a board fails validation, two boards map the same media id to conflicting paths, a declared image/soundpathhas no matching resource, or a resource would overwrite a generated entry.Invalid manifest:—manifest.jsonfailed to parse or validate.
When the root cause is a JSON.parse failure, the original error is preserved as error.cause. For finer-grained validation, drop one level down and use the Zod schemas directly with safeParse — the issues array tells you exactly which field failed.
Security
OBZ archives are untrusted input. This library does not enforce limits on entry size or count, and does not sanitize entry paths — if you write extracted resources to disk, validate paths yourself first to avoid directory traversal. For stronger guarantees against zip-bomb-style payloads, run extraction in a sandboxed context (Web Worker, isolated process).
Found a security issue? Open a private advisory at github.com/shayc/open-board-format/security/advisories/new.
Versioning
Semver; see CHANGELOG.md.
Contributing
See CONTRIBUTING.md for development setup (Node 22+, Vitest, the changeset workflow).
Related
- Open Board Format specification — the official standard and format documentation.
- AAC Board AI — an offline-first AAC web app built on this package, using on-device browser AI for grammar, tone, and translation (live app).
License
MIT © Shay Cojocaru
