CodeSampleX

Beispiel

lru-cache 11.5.2: Handle silent behavioral changes in lru-cache across version boundaries where set(key, undefined) deletes entries, legacy options are silently ignored, overwrites trigger dispose, and fetch rejection evicts stale data

Verifiziertes Beispiel für npm lru-cache 11.5.2: Handle silent behavioral changes in lru-cache across version boundaries where set(key, undefined) deletes…

sha256:475bdc86c8f2c5a89597c7f8f5237496f32825061c30dacc220acfeee9daef91

Dieses Netzwerk bietet eine Sache: ein Sample, das baut. Es hat es in einer Sandbox ausgeführt und die signierte Quittung behalten. Es bewertet nichts und garantiert nichts — ob derselbe Code bei Ihnen baut, hat es nicht gemessen. Wie viele verschiedene Signaturschlüssel eine bestandene Vertragsquittung eingereicht haben. Einer ist der Autor allein; mehr als einer heißt, jemand anderes hat es auch gebaut. Ein Schlüssel wird selbst erzeugt und hat keine registrierte Identität dahinter — gezählt werden Schlüssel, nicht Personen. MIT-0

Ausführungsbelege

Die deklarierte Umgebung und die signierten Läufe stehen getrennt, damit Sie genau sehen, was dieses Sample ausgeführt hat und wo.

Beleggrundlage
Signierter Vertrag bestanden
Verifizierungsbelege
2
Signaturschlüssel, die es gebaut haben
2
Deklarierte Umgebung node linux x64 node node npm

Umgebungen der Verifizierungsläufe

Umgebung Contract Stufen Lauf
node 22 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-16
node 22 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-18

Fall

HOW
Ziel
Handle silent behavioral changes in lru-cache across version boundaries where set(key, undefined) deletes entries, legacy options are silently ignored, overwrites trigger dispose, and fetch rejection evicts stale data
Pakete
Symbole
  • LRUCache
  • LRUCache#set
  • LRUCache#get
  • LRUCache#peek
  • LRUCache#fetch
  • LRUCache#dispose
  • LRUCache#keys
Umgebung
node
Erstellt
2026-08-16T07:28:46Z

Contract

  1. assert set(key, undefined) silently deletes entry rather than storing undefined value like Map.set
  2. assert legacy v6 options maxAge and length are silently ignored in constructor and set() without error or effect
  3. assert overwriting an existing key triggers dispose with reason set unless noDisposeOnSet is configured
  4. assert fetchMethod rejection deletes existing stale entry from cache unless allowStaleOnFetchRejection is enabled
  5. assert iteration orders keys from MRU to LRU and get() updates MRU position while peek() preserves ordering

Dateien

  • NOTES.md
  • csx.json
  • package-lock.json
  • package.json
  • test/contract.mjs

Quellartefakt herunterladen (tar.gz)

Quelltext

NOTES.md
# Notes on lru-cache (Version Boundary Silent Traps, set(key, undefined), and Fetch Rejection Eviction)

## CSX Network Search Result
`search_known_solution` returned a COMPATIBLE sample `sha256:45ba1445da9dc36a83c5aad3029d51db56180dc067c54674860a7701622f3552` ("Handle lru-cache TTL stale entry reads without get() silently evicting the entry on first read or maxSize dropping oversized items without error").

## What a Model Would Have Written Instead
A naive model would treat `cache.set(key, undefined)` like JavaScript `Map.prototype.set(key, undefined)` expecting it to store `undefined` as a tracked value with `cache.has(key) === true`, pass legacy v6 options `{ maxAge, length }` expecting them to enforce TTL and byte limits, assume `dispose` only triggers on LRU capacity eviction or explicit deletions, expect failed background `fetchMethod` calls to leave existing stale entries intact as a resilient cache fallback, and assume iteration matches standard `Map` FIFO insertion order.

## How the Wrong Version Fails
Fails silently with unexpected data loss, unexpired cache entries, and wiped fallback caches:
- `cache.set(key, undefined)` silently deletes the entry from the cache (reducing `cache.size` and making `cache.has(key)` return `false`), rather than storing `undefined`.
- Passing legacy v6 `{ maxAge }` in options or `set()` is silently ignored without throwing, causing entries to be stored with infinite TTL (`getRemainingTTL() === Infinity`) and never expire.
- Passing legacy v6 `{ length }` is silently ignored without error, leaving size tracking disabled (`calculatedSize === 0`) while `max` counts raw entry quantity instead of total size.
- Overwriting an existing key via `cache.set(key, newVal)` silently fires the `dispose` callback with reason `'set'` unless `noDisposeOnSet: true` is configured.
- When `fetchMethod` rejects during a refresh, `cache.fetch(key)` rejects and silently purges the existing stale value from the cache, turning subsequent reads into total cache misses unless `allowStaleOnFetchRejection: true` is set.
- Iterators yield keys from MRU to LRU (newest accessed first), and `cache.get(key)` silently alters the eviction queue while `cache.peek(key)` preserves it.
csx.json
{"case":{"caseId":"case:sha256:684ba7fb56d823bdf40090db79a246f79a499b1096ff2fb6a909e33e006cfab9","contract":["assert set(key, undefined) silently deletes entry rather than storing undefined value like Map.set","assert legacy v6 options maxAge and length are silently ignored in constructor and set() without error or effect","assert overwriting an existing key triggers dispose with reason set unless noDisposeOnSet is configured","assert fetchMethod rejection deletes existing stale entry from cache unless allowStaleOnFetchRejection is enabled","assert iteration orders keys from MRU to LRU and get() updates MRU position while peek() preserves ordering"],"goal":"Handle silent behavioral changes in lru-cache across version boundaries where set(key, undefined) deletes entries, legacy options are silently ignored, overwrites trigger dispose, and fetch rejection evicts stale data","kind":"HOW","packages":["pkg:npm/lru-cache@11.5.2"],"schemaVersion":1,"symbols":["LRUCache","LRUCache#set","LRUCache#get","LRUCache#peek","LRUCache#fetch","LRUCache#dispose","LRUCache#keys"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","ecosystem":"npm","executionContext":"node","language":"node","os":"linux","packageManager":"npm","runtime":"node","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/lru-cache@11.5.2"],"schemaVersion":1,"symbols":["LRUCache","LRUCache#set","LRUCache#get","LRUCache#peek","LRUCache#fetch","LRUCache#dispose","LRUCache#keys"],"verifierAdapter":"node-typescript@1"}
package-lock.json
{
  "name": "npm-lru-cache-version",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "npm-lru-cache-version",
      "version": "1.0.0",
      "dependencies": {
        "lru-cache": "11.5.2"
      }
    },
    "node_modules/lru-cache": {
      "version": "11.5.2",
      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz",
      "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==",
      "license": "BlueOak-1.0.0",
      "engines": {
        "node": "20 || >=22"
      }
    }
  }
}
package.json
{
  "name": "npm-lru-cache-version",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node test/contract.mjs"
  },
  "dependencies": {
    "lru-cache": "11.5.2"
  }
}
test/contract.mjs
import assert from 'node:assert/strict';
import { LRUCache } from 'lru-cache';

// Mock clock for 100% deterministic time manipulation without wall-clock drift
let virtualTime = 1000;
const perf = { now: () => virtualTime };

// --------------------------------------------------------------------------
// 1. set(key, undefined) silently deletes entry (unlike Map.prototype.set)
// --------------------------------------------------------------------------
{
  const cache = new LRUCache({ max: 10, perf });
  cache.set('session', 'active-token');
  assert.equal(cache.size, 1);
  assert.equal(cache.has('session'), true);

  // Naive expectation (matching Map API): sets 'session' value to undefined, size remains 1, has() returns true
  // Reality in lru-cache v9+: set(k, undefined) acts as an alias for delete(k)
  cache.set('session', undefined);
  assert.equal(cache.size, 0);
  assert.equal(cache.has('session'), false);
  assert.equal(cache.get('session'), undefined);

  // Setting undefined on an untracked key does not insert an entry
  cache.set('untracked', undefined);
  assert.equal(cache.size, 0);
  assert.equal(cache.has('untracked'), false);
}

// --------------------------------------------------------------------------
// 2. Legacy v6 options (maxAge, length) are silently ignored in modern lru-cache
// --------------------------------------------------------------------------
{
  // Naive developer migrating from v6 passes maxAge and length
  const cache = new LRUCache({
    max: 10,
    maxAge: 100, // v6 option for TTL (modern is ttl)
    length: (val) => val.length, // v6 option for size (modern is sizeCalculation + maxSize)
    perf,
  });

  virtualTime = 1000;
  cache.set('legacyKey', 'data', { maxAge: 100 }); // legacy maxAge option passed in set()

  // Naive expectation: entry has 100ms TTL and expires after virtualTime advances
  // Reality: maxAge is silently ignored; item has infinite TTL (never expires)
  virtualTime = 5000;
  assert.equal(cache.get('legacyKey'), 'data');
  assert.equal(cache.getRemainingTTL('legacyKey'), Infinity);

  // Naive expectation: max: 10 limits total calculated length to 10
  // Reality: length is silently ignored without maxSize + sizeCalculation, max counts items
  cache.set('oversizedItem', 'x'.repeat(50));
  assert.equal(cache.get('oversizedItem'), 'x'.repeat(50));
  assert.equal(cache.calculatedSize, 0); // size tracking was not enabled
}

// --------------------------------------------------------------------------
// 3. dispose callback fires on overwrite with reason 'set' unless noDisposeOnSet: true
// --------------------------------------------------------------------------
{
  const disposed = [];
  const cache = new LRUCache({
    max: 10,
    dispose: (val, key, reason) => disposed.push({ val, key, reason }),
    perf,
  });

  cache.set('user:1', 'Alice');
  assert.equal(disposed.length, 0);

  // Naive expectation: dispose only triggers on LRU eviction or explicit delete
  // Reality: overwriting an existing key triggers dispose with reason 'set'
  cache.set('user:1', 'Bob');
  assert.deepEqual(disposed, [{ val: 'Alice', key: 'user:1', reason: 'set' }]);

  // Setting undefined triggers dispose with reason 'delete'
  cache.set('user:1', undefined);
  assert.deepEqual(disposed, [
    { val: 'Alice', key: 'user:1', reason: 'set' },
    { val: 'Bob', key: 'user:1', reason: 'delete' },
  ]);
}

// --------------------------------------------------------------------------
// 4. fetchMethod rejection evicts existing stale entry unless allowStaleOnFetchRejection
// --------------------------------------------------------------------------
{
  let shouldFail = false;
  let fetchCount = 0;
  const cache = new LRUCache({
    max: 10,
    ttl: 100,
    perf,
    fetchMethod: async (key) => {
      fetchCount++;
      if (shouldFail) {
        throw new Error('network failure');
      }
      return `fetched-${key}-${fetchCount}`;
    },
  });

  virtualTime = 1000;
  const val1 = await cache.fetch('profile');
  assert.equal(val1, 'fetched-profile-1');

  // Let the item expire into stale state
  virtualTime = 1200;
  shouldFail = true;

  // Naive expectation: failed background refresh keeps the stale value cached for fallback
  // Reality: failed fetch PROMISE rejects AND deletes the key from cache completely
  await assert.rejects(
    async () => cache.fetch('profile'),
    /network failure/
  );
  assert.equal(cache.has('profile'), false);
  assert.equal(cache.get('profile'), undefined);
}

{
  // Enabling allowStaleOnFetchRejection preserves and returns the stale value on rejection
  let shouldFail = false;
  let fetchCount = 0;
  const cache = new LRUCache({
    max: 10,
    ttl: 100,
    allowStaleOnFetchRejection: true,
    perf,
    fetchMethod: async (key) => {
      fetchCount++;
      if (shouldFail) {
        throw new Error('network failure');
      }
      return `fetched-${key}-${fetchCount}`;
    },
  });

  virtualTime = 1000;
  const val1 = await cache.fetch('profile');
  assert.equal(val1, 'fetched-profile-1');

  virtualTime = 1200;
  shouldFail = true;

  // Resolves to stale value instead of rejecting
  const val2 = await cache.fetch('profile');
  assert.equal(val2, 'fetched-profile-1');
}

// --------------------------------------------------------------------------
// 5. MRU-to-LRU iteration order: get() mutates eviction priority while peek() does not
// --------------------------------------------------------------------------
{
  const cache = new LRUCache({ max: 3, perf });
  cache.set('a', 1);
  cache.set('b', 2);
  cache.set('c', 3);

  // Iteration order is MRU to LRU (newest first: c, b, a), opposite to Map insertion order
  assert.deepEqual(Array.from(cache.keys()), ['c', 'b', 'a']);

  // peek('a') inspects without mutating order
  assert.equal(cache.peek('a'), 1);
  assert.deepEqual(Array.from(cache.keys()), ['c', 'b', 'a']);

  // get('a') moves 'a' to MRU (front)
  assert.equal(cache.get('a'), 1);
  assert.deepEqual(Array.from(cache.keys()), ['a', 'c', 'b']);

  // Next insert evicts the LRU item ('b'), preserving accessed 'a' and 'c'
  cache.set('d', 4);
  assert.equal(cache.has('b'), false);
  assert.equal(cache.has('a'), true);
  assert.deepEqual(Array.from(cache.keys()), ['d', 'a', 'c']);
}

console.log('Contract verified: all lru-cache version boundary assertions passed.');

Ursprungs-Seeder

csx-seed