Files
2026-05-27 23:29:53 +01:00

8.2 KiB

Capability Spec Delta: bitpacker-schema (bitpacker-stub-completion)

Package: @suaveplan/bitpacker-schema Status: PROPOSED

Every requirement below carries a **Verified by:** line at authoring time.

ADDED Requirements

Requirement: SchemaRegistryStorage interface

The package SHALL expose a SchemaRegistryStorage interface with the four async methods saveSchema(entry), loadSchema(namespace, name, version?), listSchemas(namespace?, tag?), deleteSchema(namespace, name, version?). The interface SHALL be the contract that every storage adapter implements; both adapters shipped in this change SHALL pin to the same spec-conformance test suite.

Scenario: contract pinned across adapters

  • WHEN the same spec-conformance suite is run against InMemorySchemaRegistryStorage and LocalStorageSchemaRegistryStorage
  • THEN every assertion passes for both

Verified by: packages/bitpacker/bitpacker-schema/src/registry/registry.spec-conformance.test.ts::"InMemorySchemaRegistryStorage satisfies SchemaRegistryStorage contract" AND packages/bitpacker/bitpacker-schema/src/registry/registry.spec-conformance.test.ts::"LocalStorageSchemaRegistryStorage satisfies SchemaRegistryStorage contract"


Requirement: InMemorySchemaRegistryStorage adapter

The package SHALL ship InMemorySchemaRegistryStorage matching upstream registry.ts L13-L141: stores entries in a process-local array, treats (namespace, name, version) as the unique key, and updates in place when re-saving an existing triple. loadSchema without version SHALL return the entry whose metadata.version is highest. listSchemas SHALL filter by namespace AND tag when both are given. deleteSchema without version SHALL drop every entry matching (namespace, name).

Scenario: re-save under existing triple updates in place

  • WHEN an entry is saved twice with the same (namespace, name, version) and different schemas
  • THEN listSchemas() returns one entry whose schema is the second value

Scenario: loadSchema without version returns highest version

  • WHEN versions 1, 3, and 2 of (ns: "game", name: "player") exist
  • THEN loadSchema("game", "player") returns version 3

Scenario: deleteSchema without version drops all versions

  • WHEN deleteSchema("game", "player") is called with no version
  • THEN every entry matching that namespace+name is removed; entries for other names remain

Verified by: packages/bitpacker/bitpacker-schema/src/registry/registry.test.ts::"in-memory adapter: re-save under existing triple updates in place" AND packages/bitpacker/bitpacker-schema/src/registry/registry.test.ts::"in-memory adapter: loadSchema without version returns highest version" AND packages/bitpacker/bitpacker-schema/src/registry/registry.test.ts::"in-memory adapter: deleteSchema without version drops all versions"


Requirement: LocalStorageSchemaRegistryStorage adapter

The package SHALL ship LocalStorageSchemaRegistryStorage matching upstream registry.ts L146-L368. The constructor SHALL accept an optional storageKey (default "bitpacker-schema-registry"). The adapter SHALL serialize entries to JSON in localStorage[storageKey]; SHALL roundtrip Date fields (createdAt / updatedAt); SHALL be safe to construct in environments where localStorage is undefined (in that case all methods reject with BitpackerError code BITPACKER.SCHEMA_STORAGE_UNAVAILABLE).

Scenario: round-trip through localStorage preserves Date fields

  • WHEN an entry with metadata.createdAt: new Date("2026-01-01") is saved, the adapter is recreated, and the entry is reloaded
  • THEN the reloaded createdAt is a Date instance equal to the original

Scenario: missing localStorage emits coded error

  • WHEN the adapter is constructed in a Node environment without localStorage and saveSchema is called
  • THEN the call rejects with BitpackerError code BITPACKER.SCHEMA_STORAGE_UNAVAILABLE

Verified by: packages/bitpacker/bitpacker-schema/src/registry/registry.test.ts::"localStorage adapter: round-trip preserves Date fields" AND packages/bitpacker/bitpacker-schema/src/registry/registry.spec-conformance.test.ts::"localStorage adapter rejects with SCHEMA_STORAGE_UNAVAILABLE when localStorage is undefined"


Requirement: Storage-backed SchemaRegistry

The package SHALL replace the legacy Map-based SchemaRegistry with a storage-backed class matching upstream registry.ts L372-L554. The class SHALL accept a SchemaRegistryStorage (default InMemorySchemaRegistryStorage) and expose async registerSchema(schema, metadata), registerVersionedSchema(versionedSchema, metadata), loadSchema(namespace, name, version?), listSchemas(namespace?, tag?), deleteSchema(namespace, name, version?). registerSchema SHALL auto-increment version: existing entries return existing.metadata.version + 1; new entries start at 1. createdAt / updatedAt SHALL come from an injectable Clock (@suaveplan/types::Clock).

Scenario: registerSchema auto-increments version

  • WHEN registerSchema(schema, { namespace: "n", name: "x", description: "..." }) is called twice
  • THEN the first returned entry has metadata.version === 1; the second has metadata.version === 2

Scenario: createdAt / updatedAt come from injected clock

  • WHEN a Clock returning a fixed instant T0 is injected and a schema is registered
  • THEN the returned entry has metadata.createdAt === T0 and metadata.updatedAt === T0

Scenario: missing schema emits SCHEMA_NOT_FOUND

  • WHEN loadSchema("ns", "missing", 5) is called and no such entry exists
  • THEN the call rejects with BitpackerError code BITPACKER.SCHEMA_NOT_FOUND with context.namespace, context.name, context.version

Verified by: packages/bitpacker/bitpacker-schema/src/registry/registry.test.ts::"registerSchema auto-increments version" AND packages/bitpacker/bitpacker-schema/src/registry/registry.spec-conformance.test.ts::"createdAt and updatedAt come from injected Clock" AND packages/bitpacker/bitpacker-schema/src/registry/registry.spec-conformance.test.ts::"loadSchema rejects with SCHEMA_NOT_FOUND for unknown entry"


Requirement: Factory helpers

The package SHALL export createInMemorySchemaRegistry() and createLocalStorageSchemaRegistry(storageKey?) matching upstream registry.ts L560-L576. Both SHALL accept an optional clock for deterministic timestamping in tests.

Scenario: createInMemorySchemaRegistry returns a usable registry

  • WHEN createInMemorySchemaRegistry() is called and registerSchema is invoked
  • THEN the entry persists for the lifetime of the registry instance

Scenario: createLocalStorageSchemaRegistry honours custom storageKey

  • WHEN createLocalStorageSchemaRegistry("my-app") is called and a schema is registered
  • THEN localStorage["my-app"] contains the serialized entry

Verified by: packages/bitpacker/bitpacker-schema/src/registry/registry.test.ts::"createInMemorySchemaRegistry returns a working registry" AND packages/bitpacker/bitpacker-schema/src/registry/registry.test.ts::"createLocalStorageSchemaRegistry honours custom storageKey"

MODIFIED Requirements

Requirement: Legacy Map-based registry preserved as deprecated alias

The legacy 50-line Map-based SchemaRegistry SHALL be moved to bitpacker-schema/src/legacy/legacy-registry.ts and re-exported as LegacySchemaRegistry with a @deprecated JSDoc tag. The deprecation note SHALL specify removal in the next breaking release of @suaveplan/bitpacker-schema. Existing call sites importing it directly via @suaveplan/bitpacker-schema/legacy SHALL keep compiling.

Scenario: legacy alias retained for one release

  • WHEN a consumer imports { LegacySchemaRegistry } from "@suaveplan/bitpacker-schema/legacy"
  • THEN the import resolves and the class behaves as the original 50-line Map wrapper (register, resolve, unregister, has, names, clear)

Verified by: packages/bitpacker/bitpacker-schema/src/legacy/legacy-registry.test.ts::"LegacySchemaRegistry behaves as original Map wrapper"