first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
# VersionedSchema
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`VersionedSchema` is the working unit for evolving a `BitPackerSchema` over time without breaking previously-packed data. A single `VersionedSchema` instance keeps an ordered list of `SchemaVersion` rows (each pairing a version number with a `BitPackerSchema` and an optional description), plus a list of `SchemaMigration` rows that describe how to transform data between adjacent versions. Production code constructs versioned schemas in two ways: directly via the constructor when authoring a new schema lineage, or via `SchemaRegistry.getVersionedSchema(namespace, name)` when reconstructing one from storage. It is the contract the migration engine, the registry, and the runtime pack/unpack path all consult.
## Features
- Ordered version list with `addVersion` for new entries and `latestSchema` / `latestVersion` accessors.
- Per-version migration registration via `addMigration<U extends BitPackerSchema>` with a `MigrationFunction<T, U>`.
- Bit-packer-aware: pack/unpack methods that select the schema by version number and route through an injectable `BitPacker`.
- Bulk reconstruction from `SchemaRegistry` storage rows.
## Usage: Basic Example
```ts
import { VersionedSchema } from "@suaveplan/bitpacker-schema";
const player = new VersionedSchema({ name: "-16", health: 255 }, { name: "player" });
player.addVersion({ name: "-16", health: 255, mana: 255 }, "added mana");
const latest = player.latestSchema;
```
## Usage: Advanced Example
```ts
import { VersionedSchema } from "@suaveplan/bitpacker-schema";
const player = new VersionedSchema(
{ name: "-16", health: 255 },
{ name: "player", namespace: "game" }
);
player.addVersion({ name: "-16", health: 255, mana: 255 });
player.addMigration(1, 2, (data) => ({ ...data, mana: 100 }));
// Pack v1 data, then transparently migrate up.
const packed = player.pack({ name: "Hero", health: 200 }, 1);
const v2Data = player.unpackAndMigrate(packed, 1, 2);
```
## API Reference
### `class VersionedSchema<T extends BitPackerSchema = BitPackerSchema>`
- `constructor(schema: T, options: VersionedSchemaOptions)`
- `addVersion(schema: BitPackerSchema, description?: string): number` — appends; returns new version number.
- `addMigration<U extends BitPackerSchema>(from: number, to: number, fn: MigrationFunction): void`
- `getSchema(version: number): BitPackerSchema` — throws if version not found.
- `pack(data, version): Uint8Array` / `unpack(data, version): unknown` — pack/unpack at a specific version.
- `get versions: readonly SchemaVersion[]`
- `get latestSchema: BitPackerSchema`
- `get latestVersion: number`
### `interface SchemaVersion`
`{ version: number; schema: BitPackerSchema; description?: string; createdAt?: Date }`
### `interface VersionedSchemaOptions`
`{ name: string; namespace?: string; description?: string; autoMigrate?: boolean; includeVersion?: boolean; bitPacker?: BitPacker }`
## Implementation Notes
- The internal `_bitPacker` defaults to a fresh `BitPacker()` instance; tests inject a stub to verify pack/unpack are routed through the injectable, not module-scope state.
- `MigrationFunction` signatures are intentionally `any → any` because migration functions transform between heterogeneous data shapes; the `T → U` generic threading is encouraged but not enforced at runtime.
- `addVersion` appends in monotonic order. There is no insertion or rewriting — historical versions are immutable so packed data on disk stays decodable.