npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@shayc/open-board-format

v1.3.5

Published

Parse, validate, and create Open Board Format (.obf/.obz) files — the open standard for Augmentative and Alternative Communication (AAC) boards. TypeScript, browser and Node.js.

Readme

@shayc/open-board-format

npm version CI

A TypeScript library for parsing, validating, and creating Open Board Format communication boards for AAC applications.

Add Open Board Format support without implementing schemas or archive handling yourself.

Features

  • Load OBF or OBZ files through a single API.
  • Create OBZ archives with generated manifests and validated media resources.
  • Use exported Zod schemas and inferred TypeScript types.
  • Preserve unknown fields, including vendor extensions.

Install

npm install @shayc/open-board-format

Requires zod ^4.0.0 as a peer dependency.

Works in browsers and Node.js. Browser File uploads and Node.js Buffer values use the same loading API.

Quick start

import { loadBoard } from "@shayc/open-board-format";

const result = await loadBoard(input);

loadBoard accepts a File, Blob, ArrayBuffer, or ArrayBufferView and detects the format from the bytes, not the filename. It returns a TypeScript discriminated union: OBF files contain a board directly, while OBZ files contain an archive whose rootBoard is the entry point.

File refers to the Web API object, not a filesystem path.

const board = result.format === "obf" ? result.board : result.archive.rootBoard;

Formats

  • OBF (.obf) is a single JSON communication board.
  • OBZ (.obz) is a ZIP archive containing one or more boards and optional media.
my-board.obz
├── manifest.json
├── boards/
│   └── home.obf
├── images/
│   └── dog.png
└── sounds/
    └── hello.mp3

Every OBZ archive requires manifest.json at its root, even when it contains only one board.

Examples

Read an OBZ archive

import { extractOBZ } from "@shayc/open-board-format";

const archive = await extractOBZ(obzBytes);

The returned ParsedOBZ contains:

  • manifest: the validated OBZ manifest.
  • rootBoard: the board referenced by manifest.root.
  • boards: a Map keyed by board ID.
  • resources: a Map containing the raw bytes of every file entry.

resources includes the manifest, board files, media, and any other files in the archive.

For untrusted archives, configure extraction limits.

Create an OBZ archive

Given a board and its media resources:

import { createOBZ } from "@shayc/open-board-format";

const obz = await createOBZ([board], board.id, resources);

createOBZ generates the manifest automatically, writes boards to boards/<encoded-id>.obf, and uses rootBoardId as the archive's entry-point board.

Validate a board

import { OBFBoardSchema } from "@shayc/open-board-format";

export const validateBoard = (value: unknown) =>
  OBFBoardSchema.safeParse(value);

Every public OBF data model has a matching Zod schema export with a Schema suffix. The schemas can also be composed with Zod APIs such as .extend() and .pick().

Validation details

Validation returns a parsed copy of the input. Known fields may be normalized during parsing:

  • Numeric IDs become strings.
  • Empty optional IDs, URLs, and email addresses become undefined.
  • Unknown properties are preserved at every loose-object level, with or without an ext_ prefix.

Structural validation checks:

  • URL and email fields are syntax-checked.
  • Grid dimensions must be integers from 1 through 100.
  • grid.order must exactly match the declared row and column counts.
  • Positioned buttons must provide top, left, width, and height, each between 0 and 1.
  • Format versions must match open-board-*; they are not restricted to open-board-0.1.
  • An OBZ manifest root must appear in paths.boards.

Validation is not a complete OBF conformance or graph-integrity check. It does not enforce:

  • Unique button, image, or sound IDs.
  • Resolution of grid.order, image_id, sound_id, or load_board references.
  • A consistent positioning mode across every button on a board.
  • BCP 47 locale syntax, color syntax, MIME correctness, or safe HTML.
  • During extraction, the existence of manifest-declared media files or their agreement with board media records.

Add application-specific checks after parsing when those guarantees matter.

API reference

High-level API

Board data

| Function | Returns | Behavior | | --------------------- | ------------------- | ----------------------------------------------------------- | | parseOBF(json) | OBFBoard | Parse JSON and validate a board; strips a leading UTF-8 BOM | | validateOBF(value) | OBFBoard | Validate and normalize an unknown value | | stringifyOBF(board) | string | Serialize as two-space JSON without revalidating | | loadOBF(file) | Promise<OBFBoard> | Read a File, then parse and validate it |

Archives and format detection

| Function | Returns | Behavior | | -------------------------------------------- | ---------------------- | ------------------------------------------------------------------- | | loadBoard(input, options?) | Promise<LoadedBoard> | Detect OBF or OBZ from the bytes, then load it | | loadOBZ(file, options?) | Promise<ParsedOBZ> | File convenience wrapper around extractOBZ | | extractOBZ(input, options?) | Promise<ParsedOBZ> | Extract and validate the manifest and every manifest-declared board | | createOBZ(boards, rootBoardId, resources?) | Promise<Blob> | Validate and package boards and resources with a generated manifest | | parseManifest(json) | OBFManifest | Parse and validate manifest JSON |

Before writing an archive, createOBZ checks board IDs, the root board, generated paths, media-path conflicts, and declared media resources. It does not resolve load_board, image_id, or sound_id references.

Types and schemas

LoadedBoard is a discriminated union:

{ format: "obf", board: OBFBoard }
  | { format: "obz", archive: ParsedOBZ }

ParsedOBZ provides the validated archive contents:

interface ParsedOBZ {
  manifest: OBFManifest;
  boards: Map<string, OBFBoard>;
  rootBoard: OBFBoard;
  resources: Map<string, Uint8Array>;
}

Main exports include:

  • Board, action, media, metadata, and manifest types.
  • Matching Zod schemas, including OBFBoardSchema and OBFManifestSchema.
  • Input and archive types: BinaryInput, ParsedOBZ, and LoadedBoard.
  • Structured errors through OBFError and its related types.

Errors

High-level APIs report expected parsing, validation, and archive failures as OBFError.

Branch on error.info.code, not error.message.

import { loadBoard, OBFError } from "@shayc/open-board-format";

try {
  await loadBoard(file);
} catch (error) {
  if (error instanceof OBFError) {
    console.error(error.info.code);
  }

  throw error;
}

| Area | info.code | Additional fields | | -------------- | ------------------- | -------------------------------------------------- | | Decoding | not-json | source | | Decoding | not-zip | — | | Decoding | unreadable-zip | — | | Limits | archive-too-large | limit, path, and fields for the exceeded limit | | Validation | invalid-board | issues, boardId? | | Validation | invalid-manifest | issues | | OBZ extraction | missing-manifest | — | | OBZ extraction | missing-board | boardId, path | | OBZ extraction | board-id-mismatch | path, declaredId, actualId | | OBZ creation | unknown-root | rootBoardId | | OBZ creation | duplicate-board | boardId | | OBZ creation | missing-resource | kind, mediaId, path | | OBZ creation | conflicting-paths | kind, mediaId, paths | | OBZ creation | path-collision | path | | OBZ creation | zip-failed | — | | Internal | internal | detail |

Validation failures expose the underlying ZodError as error.cause and provide its flat issue list through error.info.issues.

not-json, unreadable-zip, and zip-failed expose the underlying parser or ZIP error as error.cause. An internal error indicates a library invariant failure and should be reported.

Direct schema .parse() calls throw ZodError rather than OBFError.

The following exports are available for advanced archive workflows:

| Function | Returns | Behavior | | ------------------------- | ---------------------------------- | -------------------------------------------------------- | | isZip(buffer) | boolean | Check whether an ArrayBuffer has a ZIP signature | | zip(entries) | Promise<Uint8Array> | Compress a map of paths to Uint8Array or ArrayBuffer | | unzip(buffer, options?) | Promise<Map<string, Uint8Array>> | Extract an ArrayBuffer and omit directory markers |

Security

Treat OBZ archives and their contents as untrusted input.

Extraction limits

import { extractOBZ } from "@shayc/open-board-format";
import type { BinaryInput } from "@shayc/open-board-format";

export function extractUntrusted(input: BinaryInput) {
  return extractOBZ(input, {
    limits: {
      // Examples only—choose limits appropriate for your application.
      maxEntrySize: 100 * 1024 ** 2, // 100 MiB
      maxTotalOriginalSize: 500 * 1024 ** 2, // 500 MiB
      maxEntries: 10_000,
    },
  });
}

Extraction limits are optional and disabled by default. Entry and total-size limits are checked against ZIP metadata before inflation, while maxEntries caps the number of entries processed.

These limits reduce risk, but they are not strict memory guarantees. ZIP metadata can be dishonest, and stored entries can produce more output than their declared uncompressed size.

Also enforce a limit on the compressed archive size before passing it to this package. Use process isolation or a streaming design when your threat model requires a strict memory boundary.

Other boundaries

  • Archive entry paths are not sanitized. Validate them before writing files to disk to prevent directory traversal.
  • description_html is not sanitized. Sanitize it before inserting it into the DOM.
  • URLs and data_url values are validated syntactically but are never fetched.

Found a vulnerability? Email [email protected] rather than opening a public issue.

Runtime

  • Pure ESM for Node.js >=22 and modern browsers; CommonJS is unsupported.
  • Browser environments must provide Blob, File, TextEncoder, and TextDecoder.
  • fflate is the only runtime dependency; zod ^4.0.0 is a peer dependency.
  • CI covers Node.js 22, 24, and 26. Browser engines are not currently tested in CI.

Project

The public API follows semantic versioning. Breaking changes to exported APIs, schemas, or documented behavior ship as major releases.

License

MIT © Shay Cojocaru