comapeocat
v1.1.0
Published
A JavaScript library for reading and writing CoMapeo Categories archives (`.comapeocat`). These files package category definitions, custom fields, icons, and translations for use in [CoMapeo](https://www.comapeo.app) applications.
Readme
CoMapeo Categories File Utilities comapeocat
A JavaScript library for reading and writing CoMapeo Categories files (.comapeocat). These files package category definitions, custom fields, icons, and translations for use in CoMapeo applications.
Features
- Read
.comapeocatfiles and access categories, fields, icons, and translations - Write
.comapeocatfiles with full validation - Validate category files against the specification
- CLI tool for creating and linting category files
Contents
- Quick Start
- CLI Usage
- VSCode settings for JSON Schema Validation
- API Reference
- Installation
- Quick Start
- Reader
- Writer
new Writer(options?)writer.addCategory(id, category)(synchronous)writer.addField(id, field)(synchronous)async writer.addIcon(id, svg)async writer.addTranslations(lang, translations)writer.setCategorySelection(categorySelection)(synchronous)writer.setMetadata(metadata)(synchronous)writer.finish()(synchronous)writer.outputStream(property)
- File Format Specification
- Validation
- License
- Contributing
Quick Start
Build a .comapeocat file from a directory of JSON files and icons:
npx comapeocat build --output mycategories.comapeocatExtract messages for translation:
npx comapeocat messages --output messages/en.jsonCLI Usage
Installation
Install the CLI globally for offline-use and convenience (you can always use npx comapeocat without installing):
npm install -g comapeocatCommands
npx comapeocat build [inputDir]
Build a .comapeocat file from a directory containing JSON files and icons.
Arguments:
[inputDir]- Directory containing categories, fields, categorySelection, icons, and messages (default: current directory)
Options:
-o, --output <file>- Output file path (default: stdout)--name <name>- Name of the category set (overrides metadata.json)--version <version>- Version of the category set (overrides metadata.json)--addCategoryIdTags- Add acategoryIdtag to each category'saddTagsproperty
Directory structure:
inputDir/
├── categories/
│ ├── tree.json
│ └── river.json
├── fields/
│ ├── species.json
│ └── height.json
├── icons/
│ ├── tree.svg
│ └── river.svg
├── messages/
│ ├── en.json
│ └── es.json
├── categorySelection.json
└── metadata.jsonExample:
# Build from current directory to stdout
npx comapeocat build
# Build to a specific file
npx comapeocat build ./my_categories --output output.comapeocat
# Override metadata
npx comapeocat build --name "My Categories" --version "1.0.0" --output output.comapeocat
# Add categoryId tags to categories
npx comapeocat build --addCategoryIdTags --output output.comapeocatnpx comapeocat lint [inputDir]
Lint category and field JSON files to validate against schemas and check references.
Arguments:
[inputDir]- Directory containing categories and fields (default: current directory)
Example:
# Lint files in current directory
npx comapeocat lintnpx comapeocat validate [file]
Validate a .comapeocat archive file.
Arguments:
[file]- Path to the.comapeocatfile (required)
Example:
# Validate a specific .comapeocat file
npx comapeocat validate mycategories.comapeocatnpx comapeocat messages [inputDir]
Extract translatable messages from categories and fields for a given language.
Arguments:
[inputDir]- Directory containing categories and fields (default: current directory)
Options:
-o, --output <file>- Output file path (default: stdout)
Example:
comapeocat messages --output messages/en.jsonThis creates a messages/<lang>.json file with all translatable strings extracted from categories and fields. The filename should be a valid BCP 47 language code (e.g., en, es-PE, fr) and the file should be placed in a messages subdirectory of the input directory to be picked up by the build command.
VSCode settings for JSON Schema Validation
To enable JSON schema validation in VSCode for the various JSON files used in a .comapeocat project, add the following to your workspace settings (.vscode/settings.json):
{
"json.schemas": [
{
"fileMatch": ["categories/**/*.json", "presets/**/*.json"],
"url": "./node_modules/comapeocat/dist/schema/category.json"
},
{
"fileMatch": ["fields/**/*.json"],
"url": "./node_modules/comapeocat/dist/schema/field.json"
},
{
"fileMatch": ["messages/*.json"],
"url": "./node_modules/comapeocat/dist/schema/messages.json"
},
{
"fileMatch": ["categorySelection.json"],
"url": "./node_modules/comapeocat/dist/schema/categorySelection.json"
},
{
"fileMatch": ["metadata.json"],
"url": "./node_modules/comapeocat/dist/schema/metadata.json"
}
]
}API Reference
Installation
npm install comapeocatQuick Start
Reading a Categories File
import { Reader } from 'comapeocat'
const reader = new Reader('path/to/categories.comapeocat')
// Wait for the file to be opened
await reader.opened()
// Read categories
const categories = await reader.categories()
for (const [id, category] of categories) {
console.log(id, category.name, category.appliesTo)
}
// Read fields
const fields = await reader.fields()
const species = fields.get('species')
console.log(species.label, species.type)
// Read icons
for await (const { name, iconXml } of reader.icons()) {
console.log(name, iconXml)
}
// Read translations
for await (const { lang, translations } of reader.translations()) {
console.log(lang, translations)
}
// Validate the file
await reader.validate()
// Always close when done
await reader.close()Writing a Categories File
import { Writer } from 'comapeocat'
import { createWriteStream } from 'fs'
import { pipeline } from 'stream/promises'
const writer = new Writer()
// Add categories
writer.addCategory('tree', {
name: 'Tree',
appliesTo: ['observation'],
tags: { natural: 'tree' },
fields: ['species', 'height'],
icon: 'tree',
color: '#228B22',
})
// Add fields
writer.addField('species', {
type: 'text',
tagKey: 'species',
label: 'Species',
appearance: 'singleline',
})
writer.addField('height', {
type: 'number',
tagKey: 'height',
label: 'Height (meters)',
})
// Add icons (async)
await writer.addIcon('tree', '<svg>...</svg>')
// Add translations (async)
await writer.addTranslations('es', {
category: {
tree: { name: 'Árbol' },
},
field: {
species: { label: 'Especie' },
},
})
// Set category selection
writer.setCategorySelection({
observation: ['tree'],
track: [],
})
// Set metadata
writer.setMetadata({
name: 'Forest Monitoring',
version: '1.0.0',
builderName: 'comapeocat',
builderVersion: '1.0.0',
})
// Finalize and write
writer.finish()
await pipeline(writer.outputStream, createWriteStream('output.comapeocat'))Reader
new Reader(filepath)
Creates a new reader for a .comapeocat file.
- filepath:
string|ZipFile- Path to the file or an open yauzl ZipFile instance
async reader.opened()
Returns a promise that resolves when the file has been successfully opened and validated.
async reader.categories()
Returns a Promise<Map<string, Category>> of all categories in the file.
async reader.fields()
Returns a Promise<Map<string, Field>> of all fields in the file.
async reader.categorySelection()
Returns a Promise<CategorySelection> - the category selection object mapping document types to category IDs.
async reader.metadata()
Returns a Promise<Metadata> - the metadata object containing:
name- Human-readable name for the category setversion- Version identifier (optional)buildDateValue- Build timestamp in millisecondsbuilderName- Name of the tool used to build the archive (optional)builderVersion- Version of the tool used to build the archive (optional)
reader.supportedFileVersion()
Returns the supported file version string (e.g., "1.0").
- Returns:
string- The supported file version
const reader = new Reader('path/to/categories.comapeocat')
const supportedVersion = reader.supportedFileVersion()
console.log('Supported version:', supportedVersion) // "1.0"async reader.fileVersion()
Returns the actual file version string from the archive.
- Returns:
Promise<string>- The file version (e.g.,"1.0","1.5")
const reader = new Reader('path/to/categories.comapeocat')
await reader.opened()
const fileVersion = await reader.fileVersion()
console.log('File version:', fileVersion)async reader.iconNames()
Returns a Promise<Set<string>> of all icon names (without .svg extension).
async reader.getIcon(iconId)
Returns the SVG XML content of an icon by its ID, or null if the icon doesn't exist.
- iconId:
string- Icon ID (without.svgextension) - Returns:
Promise<string | null>- SVG XML content or null
const iconXml = await reader.getIcon('tree')
if (iconXml) {
console.log('Icon found:', iconXml)
}async *reader.icons()
Returns an async generator that yields { name, iconXml } objects.
for await (const { name, iconXml } of reader.icons()) {
// Process each icon
}async *reader.translations()
Returns an async generator that yields { lang, translations } objects.
for await (const { lang, translations } of reader.translations()) {
// Process each translation
}async reader.validate()
Validates the file structure and all references. Throws errors if validation fails.
async reader.close()
Closes the underlying zip file.
Writer
new Writer(options?)
Creates a new writer.
- options.highWaterMark:
number- Stream high water mark (default: 1MB)
writer.addCategory(id, category) (synchronous)
Adds a category definition. Throws if called after finish().
writer.addField(id, field) (synchronous)
Adds a field definition. Throws if called after finish().
async writer.addIcon(id, svg)
Adds an SVG icon. Returns a promise that resolves when the icon is added. Throws if SVG is invalid or if called after finish().
async writer.addTranslations(lang, translations)
Adds translations for a language. Returns a promise that resolves when translations are added. Throws if called after finish().
writer.setCategorySelection(categorySelection) (synchronous)
Sets the category selection object mapping document types to category IDs. Throws if called after finish().
writer.setMetadata(metadata) (synchronous)
Sets the metadata (required). Throws if called after finish().
- metadata.name:
string(required) - Human-readable name for the category set (max 100 characters) - metadata.version:
string(optional) - Version identifier (max 20 characters) - metadata.builderName:
string(optional) - Name of the tool used to build the archive (max 100 characters) - metadata.builderVersion:
string(optional) - Version of the tool (max 20 characters)
writer.finish() (synchronous)
Finalizes the archive. Must be called before reading from outputStream. Validates all references and throws if validation fails. After calling this, no more data can be added.
writer.outputStream (property)
Readable stream containing the .comapeocat file data. Only readable after calling finish().
File Format Specification
The .comapeocat file format is a ZIP archive containing JSON configuration files and SVG icons. See the full specification for details.
Required Files
VERSION- Format version (e.g., "1.0")categories.json- Category definitionscategorySelection.json- Category selection for each document typemetadata.json- Package metadata
Optional Files
fields.json- Field definitionsicons/*.svg- Icon filestranslations/*.json- Translation files
Validation
Both Reader and Writer perform comprehensive validation:
- Schema validation - All JSON files are validated against their schemas
- Reference validation - Field and icon references are checked
- Version validation - File format version is checked
- SVG validation - Icon files are validated and sanitized
Error messages include file names and specific issues to help debug problems.
License
MIT
Contributing
Issues and pull requests welcome at github.com/digidem/comapeocat.
