cultcache-ts: the single-file store replaces a record by schema and key
SingleFileMessagePackBackingStore.push and delete matched an existing record by `candidate.type === entry.type`. A record read back carries its catalog entry's schema name as its type and a fresh write carries the definition's type, so for any definition whose type is not its schema name a rewrite appended a duplicate and a delete removed nothing. The bug has been present since the monorepo import (5c69bfc5). isSameRecord now decides identity the way the cache resolves a persisted record to a definition: same key, and the stored record's schema id is one the written envelope answers to (its own or a declared compatible id), else its schema name, else its type label (legacy envelopes). push and delete share it. Follow-up: once hands/variants-c2a merges, schemaIdentityOf (426c956a) should own the set of ids and names an envelope answers to, and this function should read it rather than re-derive it from the catalog entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e7dea469b4
commit
142a2e1385
1 changed files with 22 additions and 6 deletions
|
|
@ -97,9 +97,7 @@ export class SingleFileMessagePackBackingStore implements CacheBackingStore {
|
|||
async push(entry: CultCacheEnvelope): Promise<void> {
|
||||
await this.#enqueue(async () => {
|
||||
const existing = await this.pullAll();
|
||||
const filtered = existing.filter(
|
||||
(candidate) => !(candidate.type === entry.type && candidate.key === entry.key),
|
||||
);
|
||||
const filtered = existing.filter((candidate) => !isSameRecord(candidate, entry));
|
||||
filtered.push(entry);
|
||||
await this.#writeAll(filtered);
|
||||
});
|
||||
|
|
@ -108,9 +106,7 @@ export class SingleFileMessagePackBackingStore implements CacheBackingStore {
|
|||
async delete(entry: CultCacheEnvelope): Promise<void> {
|
||||
await this.#enqueue(async () => {
|
||||
const existing = await this.pullAll();
|
||||
const filtered = existing.filter(
|
||||
(candidate) => !(candidate.type === entry.type && candidate.key === entry.key),
|
||||
);
|
||||
const filtered = existing.filter((candidate) => !isSameRecord(candidate, entry));
|
||||
await this.#writeAll(filtered);
|
||||
});
|
||||
}
|
||||
|
|
@ -270,6 +266,26 @@ function schemaIdForEnvelope(entry: CultCacheEnvelope): string {
|
|||
return entry.schemaId ?? entry.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* A record is its schema and its key. An envelope's `type` is a label, not the schema: a record
|
||||
* read back carries its catalog entry's schema name, while one being written carries its
|
||||
* definition's type, and the two differ whenever a definition names its schema. So a stored record
|
||||
* is the one being written or deleted when the keys match and the stored record names the written
|
||||
* envelope's schema the way the cache resolves a persisted record to a definition: by a schema id
|
||||
* the written envelope answers to (its own and every id its catalog entry declares compatible),
|
||||
* else by its schema name, else by its type.
|
||||
*/
|
||||
function isSameRecord(stored: CultCacheEnvelope, written: CultCacheEnvelope): boolean {
|
||||
if (stored.key !== written.key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const writtenSchemaIds = [schemaIdForEnvelope(written), ...(written.catalogEntry?.compatibleSchemaIds ?? [])];
|
||||
return writtenSchemaIds.some((schemaId) => schemaId === stored.schemaId)
|
||||
|| stored.type === written.catalogEntry?.schemaName
|
||||
|| stored.type === written.type;
|
||||
}
|
||||
|
||||
function encodeCatalogEntry(entry: CultCacheSchemaCatalogEntry): unknown[] {
|
||||
return [
|
||||
entry.schemaId,
|
||||
|
|
|
|||
Loading…
Reference in a new issue