5.3 KiB
VersionPackingStrategy
(no submodules)
Purpose
Packing strategy for the version-pinned schema shape { _version: number; _schema: BitPackerSchema; _tolerance?: "strict" | "forward"; _allowedVersions?: number[] }. Writes a 16-bit unsigned big-endian wire-version followed by the schema-encoded payload. On unpack, the strategy reads the wire-version and either matches it strictly against _version (the default) or accepts a configurable set of alternate versions when the schema opts into forward tolerance.
Forward tolerance is the right choice for clients that read newer-version payloads from a server they don't control: setting _tolerance: "forward" lets the client decode a future-versioned wire format using its current schema, as long as the consumer guarantees structural compatibility (e.g. fields added in the newer version are wrapped in _optional so they decode as undefined against the older schema). Strict mode remains the default because most consumers benefit from the early failure of a version mismatch.
Features
- 16-bit big-endian wire-version prefix (range
[0, 65535]) - Strict mode (default) — throws
BitpackerError(INVALID_SCHEMA)on any wire-version mismatch - Forward-tolerant mode (
_tolerance: "forward") — accepts alternate versions - Optional
_allowedVersions: number[]— restricts forward-tolerant acceptance to a curated allowlist - Wire format unchanged across all modes — only the unpack-time validation changes
Usage: Basic Example
import { BitPacker } from "@suaveplan/bitpacker";
import { registerDefaultStrategies } from "@suaveplan/bitpacker-strategies";
const bp = new BitPacker();
registerDefaultStrategies(bp.strategyManager);
const schema = { _version: 1, _schema: 255 };
const bytes = bp.pack(42, schema);
console.log(bp.unpack(bytes, schema)); // 42
Usage: Advanced Example
import { BitPacker, BitpackerError, BITPACKER_CODES } from "@suaveplan/bitpacker";
import { registerDefaultStrategies } from "@suaveplan/bitpacker-strategies";
const bp = new BitPacker();
registerDefaultStrategies(bp.strategyManager);
// Write at version 1:
const writeSchema = { _version: 1, _schema: 255 };
const wireBytes = bp.pack(42, writeSchema);
// Read with forward tolerance, accepting versions 1-3:
const readSchema = {
_version: 1,
_schema: 255,
_tolerance: "forward" as const,
_allowedVersions: [1, 2, 3],
};
console.log(bp.unpack(wireBytes, readSchema)); // 42
// Reading a payload tagged version 9 with the same readSchema fails:
const futureBytes = bp.pack(42, { _version: 9, _schema: 255 });
try {
bp.unpack(futureBytes, readSchema);
} catch (err) {
if (err instanceof BitpackerError && err.code === BITPACKER_CODES.INVALID_SCHEMA) {
// Error context: { expected: 1, received: 9, allowedVersions: [1, 2, 3] }
}
}
API Reference
class VersionPackingStrategy implements PackingStrategy
canHandle(schema)
Returns true if isVersionSchema(schema) is true.
pack(data, schema, ctx)
Writes 16 bits of _version masked to 16 bits, then delegates to ctx.packValue(data, schema._schema).
unpack(schema, ctx)
Reads 16 bits of wire-version. Behaviour depends on _tolerance:
"strict"(default, or omitted): throwBitpackerError(INVALID_SCHEMA)on mismatch."forward"with_allowedVersions: throw if the wire-version is NOT in the allowlist."forward"without_allowedVersions: accept any wire-version in[0, 65535].
On acceptance, delegates to ctx.unpackValue(schema._schema).
function isVersionSchema(value): value is VersionSchemaShape
Type guard. Returns true for any object with _version: number (in [0, 65535]), _schema: BitPackerSchema, optional _tolerance: "strict" | "forward", and optional _allowedVersions: number[] (each in [0, 65535]).
type VersionTolerance = "strict" | "forward"
The two supported tolerance modes.
Implementation Notes
- Wire format unchanged. Forward-tolerance is purely an unpack-time decision. Bytes produced by a strict-mode pack are byte-identical to those produced by a forward-mode pack at the same
_version. Bytes produced by either mode are unpacked equivalently when wire-versions match. - Schema identity on success. When forward-tolerance accepts an alternate wire-version, the strategy still uses
_schemafor decoding. This places the burden on the consumer to ensure the schema can decode the alternate wire format. Typical pattern: wrap fields added in newer versions in_optionalso they decode asundefinedagainst the older schema. _allowedVersions: undefinedis permissive,[]is restrictive. An empty allowlist accepts no alternate versions (forward-tolerant mode collapses to strict). An undefined allowlist with_tolerance: "forward"accepts any wire-version.- No silent migration. The strategy does NOT auto-migrate fields between versions. Migrations are the consumer's responsibility — typically encoded as
_optionalwrappers with default values, or as the richerMigrationStrategyframework in@suaveplan/bitpacker'sschema-versioning-types. - Stable wire-version cap at 16 bits. A schema with
_version > 65535is rejected byisVersionSchemaand would not be dispatched to this strategy in the first place.