Skip to content

feat: sorted sets, dual ESM/CJS build, sweepDelay; drop defaultTtl#1

Merged
FarizPrawira merged 3 commits into
mainfrom
feat/sorted-sets-and-dual-build
May 28, 2026
Merged

feat: sorted sets, dual ESM/CJS build, sweepDelay; drop defaultTtl#1
FarizPrawira merged 3 commits into
mainfrom
feat/sorted-sets-and-dual-build

Conversation

@FarizPrawira

Copy link
Copy Markdown
Owner

Summary

  • Sorted sets on Cache (Redis-style): zadd / zrem / zscore / zrange / zrevrange / zrangebyscore / zincrby / zcard, backed by an internal SortedSet (Map + sorted array, binary search).
  • WrongTypeError on cross-kind access (with key / has / wanted properties for branching). Auto-deletes empty zsets on the last zrem.
  • sweepDelay option (default 1000ms): the single sweep timer fires sweepDelay ms past the soonest expiry so neighbors coalesce into one pass. get / has / mget / keys and zset reads still observe expiry immediately via lazy cleanup.
  • Internal helpers consolidate the new kind handling: getLiveEntry, lruBump, assertKind (TS assertion function).
  • Dual ESM/CJS build via tsup. Same Symbol.for("suncache.registry") keeps the named-cache registry shared across both copies.
  • README rewritten for the new surface — badges, multi-PM install, dual-format docs, Big-O per method, sorted-set Usage section, WrongTypeError example.

Breaking

  • defaultTtl removed. It only applied to set() and silently surprised users when incr / zadd ignored it. TTL is now strictly per-call via SetOptions.ttl.

Other fixes (from code review)

  • getOrCompute no longer treats a cached undefined as a miss.
  • zrangebyscore validates NaN (still accepts ±Infinity).
  • incr uses getLiveEntry + assertKind instead of an as ValueEntry<number> cast.

FarizPrawira and others added 3 commits May 21, 2026 20:29
Switch the build pipeline from raw tsc to tsup. tsup emits both
dist/index.js (ESM) and dist/index.cjs (CommonJS) plus a .d.ts/.d.cts
type pair from a single source. Updates the package.json exports map so
consumers get the right format whether they import or require, keeps
TypeScript handling the test-side typecheck.

The named-cache registry (Symbol.for on globalThis) still works across
both copies if a process happens to load suncache as both ESM and CJS —
the symbol identity is preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds Redis-style sorted-set methods on Cache (zadd/zrem/zscore/zrange/
zrevrange/zrangebyscore/zincrby/zcard) and the supporting infrastructure
for storing multiple value kinds in one cache instance:

- Internal SortedSet (Map<member, score> + sorted array, binary search)
- Tagged-union Entry<T> = ValueEntry<T> | ZSetEntry
- WrongTypeError thrown on cross-kind access (e.g. get() on a zset key,
  zadd() on a string key). Mirrors a Redis WRONGTYPE response, including
  the structured key/has/wanted properties for catch blocks.
- Empty zsets are auto-deleted on the last zrem() so has()/keys() stay
  honest.

Three private helpers consolidate duplication that grew with the new
kind handling:

- getLiveEntry(key)  — fetch + lazy-sweep expired entries
- lruBump(key, e)    — re-insert in Map order to mark MRU
- assertKind(e, k, w) — TS assertion function, throws WrongTypeError on
  mismatch and narrows the union for callers

Adds the sweepDelay option (default 1000ms): the single sweep timer now
fires sweepDelay ms after the soonest pending expiry, so neighbors that
fall in the window are pruned in the same pass. Pass 0 to disable
coalescing. get()/has()/mget()/keys() and zset reads still observe
expiry immediately via lazy cleanup, regardless of sweepDelay.

BREAKING CHANGE: defaultTtl was removed. The option only applied to
set() and surprised users when incr/zadd ignored it. TTL is now strictly
per-call via SetOptions.ttl. Callers who relied on a global default can
wrap set() in a project-local helper.

Other fixes folded in:
- getOrCompute no longer treats a cached undefined as a miss
- zrangebyscore validates NaN (still accepts +/-Infinity for open ranges)
- incr uses getLiveEntry + assertKind (no ValueEntry<number> cast)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reflects the new feature surface and aligns with the sibling project's
conventions:

- npm/license/types/zero-deps badges
- Multi-package-manager install (npm/pnpm/yarn/bun) and dual-format
  import/require example
- Richer intro showing named caches, getOrCompute, counters+TTL refresh,
  and sorted sets in one block
- Sorted set Usage section with a leaderboard example
- New 'Sorted set API' top-level section (h2 parallel to API) so the
  zset methods aren't visually nested as peers of their own introducer
- Big-O notation on every API method, plus a brief note on when the
  O(n) zset writes become a problem
- Errors section explaining WrongTypeError with an instanceof catch
- 'How expiry works' updated to cover sweepDelay coalescing
- Each method now has at least one focused example for consistency
- Removed product-name mentions (Redis, specific dev tools) from prose
- Dropped the empty-zset gotcha (now auto-deleted in code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@FarizPrawira FarizPrawira merged commit 91fb50d into main May 28, 2026
3 checks passed
@FarizPrawira FarizPrawira deleted the feat/sorted-sets-and-dual-build branch May 28, 2026 03:08
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