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-conformancesuite is run againstInMemorySchemaRegistryStorageandLocalStorageSchemaRegistryStorage - 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
createdAtis aDateinstance equal to the original
Scenario: missing localStorage emits coded error
- WHEN the adapter is constructed in a Node environment without
localStorageandsaveSchemais called - THEN the call rejects with
BitpackerErrorcodeBITPACKER.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 hasmetadata.version === 2
Scenario: createdAt / updatedAt come from injected clock
- WHEN a
Clockreturning a fixed instantT0is injected and a schema is registered - THEN the returned entry has
metadata.createdAt === T0andmetadata.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
BitpackerErrorcodeBITPACKER.SCHEMA_NOT_FOUNDwithcontext.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 andregisterSchemais 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
Mapwrapper (register,resolve,unregister,has,names,clear)
Verified by: packages/bitpacker/bitpacker-schema/src/legacy/legacy-registry.test.ts::"LegacySchemaRegistry behaves as original Map wrapper"