WOLS v1.2.0: Real-World Integrations Shape the Open Standard
mbeacom
Author9 min read

mbeacom
Author9 min read

Today we're releasing WOLS v1.2.0, the most significant update to the WeMush Open Labeling Standard since its initial launch. This release is special because it was shaped entirely by real-world integration feedback from the WeMush cultivation tracking platform.
This release includes:
WOLS (WeMush Open Labeling Standard) is an open specification for encoding specimen data in QR codes and digital labels for mushroom cultivation and research. It enables growers, researchers, and labs to share specimen data in a standardized, interoperable format—regardless of which software they use.
The specification is open source under CC BY 4.0, and the official libraries are Apache 2.0.
When we set out to integrate @wemush/wols into the WeMush platform for production use, we discovered gaps between the theoretical specification and practical implementation needs. Rather than working around these issues privately, we decided to contribute our learnings back to the open standard.
Here's what we learned and how v1.2.0 addresses each challenge.
The Problem: The v1.0 spec required IDs to match wemush:[a-z0-9]+, but most platforms use ULIDs or UUIDs as primary keys. This forced awkward workarounds like storing original IDs in custom fields.
The Solution: v1.2.0 introduces configurable ID validation modes.
import { validateSpecimen } from '@wemush/wols';
// Accept ULIDs after the wemush: prefix
const result = validateSpecimen(specimen, { idMode: 'ulid' });
// Accept UUIDs after the wemush: prefix
const result = validateSpecimen(specimen, { idMode: 'uuid' });
// Accept any non-empty string (for maximum flexibility)
const result = validateSpecimen(specimen, { idMode: 'any' });
// Or provide a custom validator
const result = validateSpecimen(specimen, {
customIdValidator: (id) => /^wemush:[A-Z0-9]{26}$/.test(id)
});The Problem: WOLS defines 5 specimen types (CULTURE, SPAWN, SUBSTRATE, FRUITING, HARVEST), but platforms often have 15+ types like LIQUID_CULTURE, GRAIN_SPAWN, AGAR, etc.
The Solution: A built-in type alias registry with 20+ common aliases.
import { registerTypeAlias, resolveTypeAlias, mapToWolsType } from '@wemush/wols';
// Built-in aliases work automatically
resolveTypeAlias('LIQUID_CULTURE') // Returns 'CULTURE'
resolveTypeAlias('GRAIN_SPAWN') // Returns 'SPAWN'
resolveTypeAlias('PRIMORDIA') // Returns 'FRUITING'
// Register custom aliases for your platform
registerTypeAlias('MYCELIUM_BLOCK', 'SUBSTRATE');
// Map from user-friendly names
mapToWolsType('Liquid Culture') // Returns 'CULTURE'The Problem: Different communities use different notation for filial generations: F1, G1, or just "1". The v1.0 spec only accepted F{n} format.
The Solution: Accept and normalize multiple formats.
import { normalizeGeneration, isValidGeneration } from '@wemush/wols';
// All these inputs are now valid
isValidGeneration('F2') // true
isValidGeneration('G2') // true
isValidGeneration('2') // true
isValidGeneration('P') // true (parental)
// Normalize to your preferred format
normalizeGeneration('G2', 'filial') // Returns 'F2'
normalizeGeneration('3', 'filial') // Returns 'F3'
normalizeGeneration('F1', 'numeric') // Returns '1'_meta NamespaceThe Problem: During import/export operations, platforms need to preserve metadata like original IDs or source system identifiers. Storing these in the custom field felt wrong since they're implementation details, not user data.
The Solution: A reserved _meta namespace that's preserved through all operations but excluded from validation.
const specimen = createSpecimen({
type: 'CULTURE',
species: 'Pleurotus ostreatus',
strain: 'Blue Oyster',
_meta: {
sourceId: '01HQWK3R4S5T6V7W8X9Y0Z',
sourceSystem: 'wemush-platform',
importedAt: '2026-01-26T10:30:00Z',
schemaVersion: '2.0'
}
});
// _meta is preserved through encryption, serialization, etc.
const encrypted = await encryptSpecimen(specimen, { key: password });
const decrypted = await decryptSpecimen(encrypted, { key: password });
console.log(decrypted._meta.sourceId); // Still there!The Problem: The parseCompactUrl() function returns a discriminated union, which is correct but requires verbose error handling for simple cases.
The Solution: Two convenience variants.
import { parseCompactUrl, parseCompactUrlOrThrow, parseCompactUrlOrNull } from '@wemush/wols';
// Original API - full control over error handling
const result = parseCompactUrl(url);
if (result.success) {
console.log(result.data);
} else {
console.error(result.error);
}
// New: Throw on error (for when you want exceptions)
try {
const specimen = parseCompactUrlOrThrow(url);
} catch (e) {
// Handle error
}
// New: Return null on error (for optional chaining)
const specimen = parseCompactUrlOrNull(url);
if (specimen) {
// Use specimen
}The Problem: WOLS supports both browser (Web Crypto API) and server (Node.js crypto) encryption. Detecting the environment correctly is surprisingly tricky, especially with bundlers, SSR, and edge runtimes.
The Solution: Robust, tested environment detection.
import {
isServer,
isBrowser,
isWebWorker,
isDeno,
isBun,
getRuntimeEnvironment,
isCryptoSupported,
supportsWebCrypto
} from '@wemush/wols';
// Check environment
if (isServer()) {
// Use Node.js crypto
} else if (isBrowser()) {
// Use Web Crypto API
}
// Check crypto availability
if (isCryptoSupported()) {
const encrypted = await encryptSpecimen(specimen, { key: password });
}
// Get detailed runtime info
console.log(getRuntimeEnvironment());
// 'node' | 'browser' | 'webworker' | 'deno' | 'bun' | 'unknown'The Problem: As WOLS evolves, specimens created with older versions need to be upgraded. Without tooling, this burden falls on every integrating platform.
The Solution: Built-in version comparison and migration.
import { compareVersions, isOutdated, migrate, registerMigration } from '@wemush/wols';
// Check versions
compareVersions('1.0.0', '1.2.0') // -1 (older)
isOutdated('1.0.0') // true (vs current library version)
// Register custom migrations
registerMigration('1.0.0', '1.1.0', (specimen) => {
// Transform specimen from 1.0.0 to 1.1.0 format
return { ...specimen, version: '1.1.0' };
});
// Migrate specimens
const updated = migrate(oldSpecimen); // Automatically chains migrationsThe Problem: The v1.0 spec defined only 4 growth stages (INOCULATION, COLONIZATION, FRUITING, HARVEST), but research and commercial operations track more granular lifecycle phases—like distinguishing between post-inoculation incubation and visible colonization, or tracking pin formation separately from full fruiting.
The Solution: WOLS v1.2.0 expands to 7 research-grade growth stages.
import { GROWTH_STAGES, type GrowthStage } from '@wemush/wols';
const stages: GrowthStage[] = [
'INOCULATION', // Initial spore/culture introduction
'INCUBATION', // Post-inoculation, pre-visible growth (NEW)
'COLONIZATION', // Active mycelial growth
'PRIMORDIA', // Pin initiation / hyphal knots (NEW)
'FRUITING', // Fruiting body development
'SENESCENCE', // End-of-life monitoring (NEW)
'HARVEST' // Final harvest stage
];
// Use in specimen tracking
const specimen = createSpecimen({
type: 'FRUITING',
species: 'Pleurotus ostreatus',
growthStage: 'PRIMORDIA', // Track pin initiation!
notes: 'First pins visible, day 14'
});Why This Matters for Researchers:
| Stage | Scientific Use Case |
|---|---|
| INCUBATION | Monitor post-inoculation period before visible growth; track contamination window |
| PRIMORDIA | Precise timing of pin initiation; correlate with environmental conditions |
| SENESCENCE | Research end-of-life characteristics; spore production timing |
None! WOLS v1.2.0 is fully backward compatible. All v1.0.0 and v1.1.0 specimens continue to work. New features are opt-in.
Install:
# TypeScript/JavaScript
npm install @wemush/wols
# Python
pip install wols
# or
uv add wolsRead the specification:
Migrate existing specimens:
import { isOutdated, migrate } from '@wemush/wols';
// Check if specimens need upgrading
if (isOutdated(specimen.version)) {
const updated = migrate(specimen);
// Save updated specimen
}Contribute:
We welcome feedback, bug reports, and PRs. Start a GitHub Discussion or check out our CONTRIBUTING.md.
WOLS v1.2.0 wouldn't exist without real-world integration challenges. Thank you to everyone who tested early versions and provided feedback. This is how open standards should work: built by the community, for the community.
Happy cultivating! 🍄
The WeMush Open Labeling Standard is licensed under CC BY 4.0 (specification) and Apache 2.0 (code). Built with 🍄 by cultivators, for cultivators.