Initial Commit
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
<!-- markdownlint-disable MD024 -->
|
||||
|
||||
# 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"`
|
||||
|
||||
<!-- markdownlint-enable MD024 -->
|
||||
Reference in New Issue
Block a user