🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

ocache

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ocache - npm Package Compare versions

Comparing version
0.1.4
to
0.1.5
+33
-10
dist/index.d.mts

@@ -1,2 +0,1 @@

//#region src/types.d.ts
/**

@@ -34,2 +33,4 @@ * Extended `Request` interface with optional `waitUntil` for background tasks.

integrity?: string;
/** When `true`, the entry is treated as expired on next access (set by `expireCache`). Cleared after a successful revalidation. */
stale?: boolean;
}

@@ -115,8 +116,7 @@ /**

}
//#endregion
//#region src/cache.d.ts
type CachedFunction<T, ArgsT extends unknown[]> = {
(...args: ArgsT): Promise<T>; /** Resolves all storage keys (one per base prefix) for the given arguments. */
resolveKeys: (...args: ArgsT) => Promise<string[]>; /** Invalidates (removes) cached entries for the given arguments across all base prefixes. */
invalidate: (...args: ArgsT) => Promise<void>;
invalidate: (...args: ArgsT) => Promise<void>; /** Marks cached entries as stale across all base prefixes. With SWR, stale values are still served (within `staleMaxAge`) while the next access triggers a background refresh. */
expire: (...args: ArgsT) => Promise<void>;
};

@@ -181,5 +181,31 @@ /**

}): Promise<void>;
//#endregion
//#region src/http.d.ts
/**
* Expires cached entries for given arguments and cache options across all base prefixes,
* without removing them.
*
* Unlike {@link invalidateCache} (which removes entries entirely), expired entries keep
* serving the stale value with SWR — still bounded by the originally configured
* `staleMaxAge` window — while the next access triggers a background refresh.
* Without SWR, the next call re-resolves before returning.
*
* Uses the same key derivation as `defineCachedFunction` / `resolveCacheKeys`.
* Pass the same `maxAge` / `swr` / `staleMaxAge` options you cache with so the
* remaining storage TTL is preserved.
*
* @param input - Object with `options` (cache options) and optional `args` (function arguments).
*
* @example
* ```ts
* // Mark a cached entry for background refresh on next access
* await expireCache({
* options: { name: "fetchUser", getKey: (id: string) => id, maxAge: 60, staleMaxAge: 300 },
* args: ["user-123"],
* });
* ```
*/
declare function expireCache<ArgsT extends unknown[] = any[]>(input?: {
options?: Pick<CacheOptions<any, ArgsT>, "base" | "group" | "name" | "getKey" | "maxAge" | "swr" | "staleMaxAge">;
args?: ArgsT;
}): Promise<void>;
/**
* Wraps an HTTP event handler with response caching.

@@ -196,4 +222,2 @@ *

declare function defineCachedHandler<E extends HTTPEvent = HTTPEvent>(handler: EventHandler<E>, opts?: CachedEventHandlerOptions<E>): EventHandler<E>;
//#endregion
//#region src/storage.d.ts
interface StorageInterface {

@@ -211,3 +235,2 @@ get<T = unknown>(key: string): T | null | Promise<T | null>;

declare function setStorage(storage: StorageInterface): void;
//#endregion
export { type CacheConditions, type CacheEntry, type CacheOptions, type CachedEventHandlerOptions, type CachedFunction, type EventHandler, type HTTPEvent, type ResponseCacheEntry, type ServerRequest, type StorageInterface, cachedFunction, createMemoryStorage, defineCachedFunction, defineCachedHandler, invalidateCache, resolveCacheKeys, setStorage, useStorage };
export { type CacheConditions, type CacheEntry, type CacheOptions, type CachedEventHandlerOptions, type CachedFunction, type EventHandler, type HTTPEvent, type ResponseCacheEntry, type ServerRequest, type StorageInterface, cachedFunction, createMemoryStorage, defineCachedFunction, defineCachedHandler, expireCache, invalidateCache, resolveCacheKeys, setStorage, useStorage };
import { hash } from "ohash";
//#region src/storage.ts
/** Creates an in-memory storage backed by a `Map` with optional TTL support (in seconds). */
function createMemoryStorage() {

@@ -48,3 +46,2 @@ const map = /* @__PURE__ */ new Map();

let _storage;
/** Returns the current storage instance. If none has been set via `setStorage`, lazily initializes an in-memory storage. */
function useStorage() {

@@ -54,8 +51,5 @@ if (!_storage) _storage = createMemoryStorage();

}
/** Sets a custom storage implementation to be used by all cached functions. */
function setStorage(storage) {
_storage = storage;
}
//#endregion
//#region src/cache.ts
function defaultCacheOptions$1() {

@@ -69,9 +63,2 @@ return {

}
/**
* Wraps a function with caching support including TTL, SWR, integrity checks, and request deduplication.
*
* @param fn - The function to cache.
* @param opts - Cache configuration options.
* @returns A cached function with a `.resolveKey(...args)` method for cache key resolution.
*/
function defineCachedFunction(fn, opts = {}) {

@@ -118,3 +105,3 @@ opts = {

const isFullyExpired = staleTtl !== void 0 && ttl > 0 && Date.now() - (entry.mtime || 0) > ttl + staleTtl;
const expired = shouldInvalidateCache || entry.integrity !== integrity || opts.maxAge === 0 || ttl > 0 && Date.now() - (entry.mtime || 0) > ttl || validate(entry) === false;
const expired = shouldInvalidateCache || entry.stale === true || entry.integrity !== integrity || opts.maxAge === 0 || ttl > 0 && Date.now() - (entry.mtime || 0) > ttl || validate(entry) === false;
if (isFullyExpired) {

@@ -142,3 +129,6 @@ entry.value = void 0;

delete pending[key];
_evictFromStorage(key, bases, group, name);
const evictPromise = _evictFromStorage(key, bases, group, name).catch((error) => {
_onError("[cache] Cache eviction error.", error);
});
event?.req.waitUntil?.(evictPromise);
}

@@ -150,2 +140,3 @@ throw error;

entry.integrity = integrity;
entry.stale = void 0;
delete pending[key];

@@ -168,4 +159,9 @@ if (validate(entry) !== false) {

})();
if ((event?.req)?.waitUntil) event.req.waitUntil(promise);
} else _evictFromStorage(key, bases, group, name);
event?.req.waitUntil?.(promise);
} else {
const evictPromise = _evictFromStorage(key, bases, group, name).catch((error) => {
_onError("[cache] Cache eviction error.", error);
});
event?.req.waitUntil?.(evictPromise);
}
}

@@ -175,3 +171,3 @@ };

if (entry.value === void 0) await _resolvePromise;
else if (expired && (event?.req)?.waitUntil) event.req.waitUntil(_resolvePromise);
else if (expired) event?.req.waitUntil?.(_resolvePromise);
if (opts.swr && validate(entry) !== false) {

@@ -200,30 +196,9 @@ _resolvePromise.catch((error) => {

});
cachedFn.expire = (...args) => expireCache({
options: opts,
args
});
return cachedFn;
}
/** Alias for {@link defineCachedFunction}. */
const cachedFunction = defineCachedFunction;
/**
* Resolves all cache storage keys (one per base prefix) for given arguments and cache options.
*
* Uses the same key derivation as `defineCachedFunction` internally:
* - When `opts.getKey` is provided, it is called with `args` to produce the key segment.
* - Otherwise, `args` are hashed with `ohash` (same default as `defineCachedFunction`).
*
* Pass the same `getKey`, `name`, `group`, and `base` options you use in
* `defineCachedFunction` / `defineCachedHandler` to get the exact storage keys.
*
* @param input - Object with `options` (cache options) and optional `args` (function arguments).
* @returns An array of storage key strings (one per base prefix).
*
* @example
* ```ts
* const keys = await resolveCacheKeys({
* options: { name: "fetchUser", getKey: (id: string) => id },
* args: ["user-123"],
* });
* for (const key of keys) {
* await useStorage().set(key, null); // invalidate all tiers
* }
* ```
*/
async function resolveCacheKeys(input = {}) {

@@ -235,18 +210,2 @@ const opts = input.options ?? {};

}
/**
* Invalidates (removes) cached entries for given arguments and cache options across all base prefixes.
*
* Uses the same key derivation as `defineCachedFunction` / `resolveCacheKeys`.
*
* @param input - Object with `options` (cache options) and optional `args` (function arguments).
*
* @example
* ```ts
* // Invalidate a specific cached entry
* await invalidateCache({
* options: { name: "fetchUser", getKey: (id: string) => id },
* args: ["user-123"],
* });
* ```
*/
async function invalidateCache(input = {}) {

@@ -257,2 +216,15 @@ const keys = await resolveCacheKeys(input);

}
async function expireCache(input = {}) {
const opts = input.options ?? {};
const keys = await resolveCacheKeys(input);
const storage = useStorage();
await Promise.all(keys.map(async (key) => {
const entry = await storage.get(key);
if (!entry || typeof entry !== "object" || entry.value === void 0) return;
await storage.set(key, {
...entry,
stale: true
}, _remainingTtl(entry, opts));
}));
}
function isHTTPEvent(input) {

@@ -276,9 +248,14 @@ return input?.req instanceof Request;

}
function _evictFromStorage(key, bases, group, name) {
for (const b of bases) useStorage().set(_buildCacheKey(key, {
async function _evictFromStorage(key, bases, group, name) {
await Promise.all(bases.map((b) => useStorage().set(_buildCacheKey(key, {
group,
name
}, b), null);
}, b), null)));
}
/** Strips storage-location fields from opts so integrity only reflects the cached computation. */
function _remainingTtl(entry, opts) {
if (!entry.mtime || opts.maxAge == null || opts.maxAge <= 0) return;
const ttlWindow = opts.swr === false ? opts.maxAge : opts.staleMaxAge != null && opts.staleMaxAge >= 0 ? opts.maxAge + opts.staleMaxAge : void 0;
if (ttlWindow === void 0) return;
return { ttl: Math.max(Math.ceil((entry.mtime + ttlWindow * 1e3 - Date.now()) / 1e3), 1) };
}
function _integrityOpts$1(opts) {

@@ -288,4 +265,2 @@ const { base: _, group: _g, name: _n, ...rest } = opts;

}
//#endregion
//#region src/http.ts
function defaultCacheOptions() {

@@ -299,13 +274,2 @@ return {

}
/**
* Wraps an HTTP event handler with response caching.
*
* Automatically generates cache keys from the URL path and variable headers,
* sets `cache-control`, `etag`, and `last-modified` headers, and handles
* `304 Not Modified` responses via conditional request headers.
*
* @param handler - The event handler to cache.
* @param opts - Cache and HTTP-specific configuration options.
* @returns A new event handler that serves cached responses when available.
*/
function defineCachedHandler(handler, opts = {}) {

@@ -398,3 +362,2 @@ opts = {

}
/** Strips storage-location fields from opts so integrity only reflects the cached computation. */
function _integrityOpts(opts) {

@@ -413,3 +376,2 @@ const { base: _, group: _g, name: _n, ...rest } = opts;

}
//#endregion
export { cachedFunction, createMemoryStorage, defineCachedFunction, defineCachedHandler, invalidateCache, resolveCacheKeys, setStorage, useStorage };
export { cachedFunction, createMemoryStorage, defineCachedFunction, defineCachedHandler, expireCache, invalidateCache, resolveCacheKeys, setStorage, useStorage };
{
"name": "ocache",
"version": "0.1.4",
"version": "0.1.5",
"description": "Standalone caching utilities with TTL, SWR, and HTTP response caching",

@@ -30,5 +30,5 @@ "license": "MIT",

"devDependencies": {
"@types/node": "^25.5.0",
"@typescript/native-preview": "^7.0.0-dev.20260319.1",
"@vitest/coverage-v8": "^4.1.0",
"@types/node": "^25.9.2",
"@typescript/native-preview": "7.0.0-dev.20260609.1",
"@vitest/coverage-v8": "^4.1.8",
"automd": "^0.4.3",

@@ -38,9 +38,9 @@ "changelogen": "^0.6.2",

"mitata": "^1.0.34",
"obuild": "^0.4.32",
"oxfmt": "^0.41.0",
"oxlint": "^1.56.0",
"typescript": "^5.9.3",
"vitest": "^4.1.0"
"obuild": "^0.4.36",
"oxfmt": "^0.54.0",
"oxlint": "^1.69.0",
"typescript": "^6.0.3",
"vitest": "^4.1.8"
},
"packageManager": "pnpm@10.32.1"
"packageManager": "pnpm@11.5.2"
}

@@ -129,2 +129,22 @@ # ocache

### Cache Expiration (SWR refresh)
While `.invalidate()` removes an entry entirely (the next call must wait for a fresh value), `.expire()` only marks it as stale. With SWR enabled, stale values keep being served — still bounded by the originally configured `staleMaxAge` window — and the next access triggers a background refresh:
```ts
// Mark the entry stale: next call serves the stale value and refetches in the background
await getUser.expire("user-123");
```
The standalone `expireCache()` works like `invalidateCache()` — pass the same `maxAge` / `swr` / `staleMaxAge` options you cache with so the remaining storage TTL is preserved:
```ts
import { expireCache } from "ocache";
await expireCache({
options: { name: "getUser", getKey: (id: string) => id, maxAge: 60, staleMaxAge: 300 },
args: ["user-123"],
});
```
### Multi-tier Caching

@@ -250,2 +270,37 @@

### `expireCache`
```ts
async function expireCache<ArgsT extends unknown[] = any[]>(
input:
```
Expires cached entries for given arguments and cache options across all base prefixes,
without removing them.
Unlike [`invalidateCache`](#invalidatecache) (which removes entries entirely), expired entries keep
serving the stale value with SWR — still bounded by the originally configured
`staleMaxAge` window — while the next access triggers a background refresh.
Without SWR, the next call re-resolves before returning.
Uses the same key derivation as `defineCachedFunction` / `resolveCacheKeys`.
Pass the same `maxAge` / `swr` / `staleMaxAge` options you cache with so the
remaining storage TTL is preserved.
**Parameters:**
- **`input`** — Object with `options` (cache options) and optional `args` (function arguments).
**Example:**
```ts
// Mark a cached entry for background refresh on next access
await expireCache({
options: { name: "fetchUser", getKey: (id: string) => id, maxAge: 60, staleMaxAge: 300 },
args: ["user-123"],
});
```
---
### `invalidateCache`

@@ -252,0 +307,0 @@