first commit
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
# VersionPackingStrategy
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## 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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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): throw `BitpackerError(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 `_schema` for 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 `_optional` so they decode as `undefined` against the older schema.
|
||||
- **`_allowedVersions: undefined` is 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 `_optional` wrappers with default values, or as the richer `MigrationStrategy` framework in `@suaveplan/bitpacker`'s `schema-versioning-types`.
|
||||
- **Stable wire-version cap at 16 bits.** A schema with `_version > 65535` is rejected by `isVersionSchema` and would not be dispatched to this strategy in the first place.
|
||||
Reference in New Issue
Block a user