Skip to content

fix(sdk): refresh cache writes in LRU order#61

Merged
aryasaatvik merged 2 commits into
devfrom
contrib/dev-sdk-cache-primitive
Jun 26, 2026
Merged

fix(sdk): refresh cache writes in LRU order#61
aryasaatvik merged 2 commits into
devfrom
contrib/dev-sdk-cache-primitive

Conversation

@aryasaatvik

Copy link
Copy Markdown
Owner

Summary

Mirrors the Greptile follow-up from upstream PR RhysSullivan#1118 into fork dev.

dev already has the richer cache primitive surface, including SDK cache config, API scoped service lookup, Cloudflare KV support, host CACHE wiring, and later cache consumers. This PR intentionally ports only the remaining behavior drift: refreshing fallback cache insertion order when writing an existing key, plus the focused regression tests from RhysSullivan#1118.

Changes

  • Delete an existing fallback cache key before re-inserting it on set, so Map insertion order reflects the newest write.
  • Add executor-cache.test.ts coverage for default fallback cache get, remove, TTL expiry, size cleanup, and LRU write refresh behavior.

Intentional Differences From Upstream RhysSullivan#1118

  • No generic cache primitive API changes are included here because dev already has them.
  • No Cloudflare key value store changes are included here because dev already has them.
  • No changeset is included because this PR changes behavior and tests only, with no new public package surface.

Tests

  • bun run --cwd packages/core/sdk test -- src/executor-cache.test.ts
  • bun run --cwd packages/core/sdk typecheck
  • bunx oxlint -c .oxlintrc.jsonc --deny-warnings packages/core/sdk/src/executor.ts packages/core/sdk/src/executor-cache.test.ts
  • git diff --check

@greptile-apps

greptile-apps Bot commented Jun 24, 2026

Copy link
Copy Markdown

Greptile Summary

This PR ports the LRU write-refresh fix from upstream RhysSullivan#1118, ensuring that re-writing an existing key in the in-memory fallback cache moves it to the tail of the Map's insertion order so evictCapacity evicts the truly oldest entries. It also adds a focused test suite for the fallback cache.

  • executor.ts: One line added (rows.delete(key)) before rows.set(key, …) inside makeMemoryCacheStore.set, mirroring the delete-before-set pattern already present in the get path for read-refresh.
  • executor-cache.test.ts: Three Effect-based tests covering basic get/remove, TTL boundary expiry, and the new LRU write-refresh scenario, with arithmetic that correctly exercises the 2 048-entry capacity limit.

Confidence Score: 5/5

Safe to merge — the change is a single-line delete-before-set that mirrors an identical pattern already present in the get path, with no API surface changes.

The fix is minimal and self-contained: one rows.delete(key) call restores correct Map-insertion-order semantics for LRU eviction. The existing get path already used the same pattern, so the behaviour is symmetric and well-understood. The new tests correctly model capacity arithmetic and the TTL boundary condition, giving good regression coverage for the fixed path.

No files require special attention.

Important Files Changed

Filename Overview
packages/core/sdk/src/executor.ts Adds rows.delete(key) before rows.set(key, …) in makeMemoryCacheStore.set so that re-writing an existing key refreshes its Map insertion position, giving correct LRU eviction order. One-line, targeted fix.
packages/core/sdk/src/executor-cache.test.ts New test file covering fallback in-memory cache: basic get/remove, TTL expiry (via a monkey-patched Date.now), and the new LRU write-refresh behaviour. Test logic correctly models the capacity/eviction arithmetic.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["cache.set(key, value)"] --> B["evictExpired(now)"]
    B --> C{"key already\nin Map?"}
    C -- "yes (before fix)" --> D["rows.set(key, …)\n(key stays at original position)"]
    C -- "yes (after fix)" --> E["rows.delete(key)\nrows.set(key, …)\n(key moves to tail = newest)"]
    C -- "no" --> F["rows.set(key, …)"]
    E --> G["evictCapacity()"]
    F --> G
    D --> G
    G --> H{"rows.size >\nCAPACITY?"}
    H -- yes --> I["delete oldest entry\n(first in insertion order)"]
    I --> H
    H -- no --> J["done"]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["cache.set(key, value)"] --> B["evictExpired(now)"]
    B --> C{"key already\nin Map?"}
    C -- "yes (before fix)" --> D["rows.set(key, …)\n(key stays at original position)"]
    C -- "yes (after fix)" --> E["rows.delete(key)\nrows.set(key, …)\n(key moves to tail = newest)"]
    C -- "no" --> F["rows.set(key, …)"]
    E --> G["evictCapacity()"]
    F --> G
    D --> G
    G --> H{"rows.size >\nCAPACITY?"}
    H -- yes --> I["delete oldest entry\n(first in insertion order)"]
    I --> H
    H -- no --> J["done"]
Loading

Reviews (2): Last reviewed commit: "test(sdk): document cache defaults in te..." | Re-trigger Greptile

Comment thread packages/core/sdk/src/executor-cache.test.ts
@aryasaatvik aryasaatvik merged commit 50bed06 into dev Jun 26, 2026
8 checks passed
@aryasaatvik aryasaatvik deleted the contrib/dev-sdk-cache-primitive branch June 26, 2026 13:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant