Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions packages/vinext/src/shims/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,58 @@ export function cacheTag(...tags: string[]): void {
}
}

/**
* @deprecated Use `cacheLife` instead. `unstable_cacheLife` was stabilized
* upstream and the `unstable_`-prefixed name will be removed in a future
* version of Next.js. Kept as a delegating alias for parity.
*
* Emits a one-time deprecation warning via `console.error` (matching Next.js),
* then delegates to `cacheLife`.
*
* Ported from Next.js: packages/next/cache.js
* https://github.com/vercel/next.js/blob/canary/packages/next/cache.js
*
* Asserted by Next.js test:
* test/e2e/app-dir/cache-components-errors/cache-components-unstable-deprecations.test.ts
*/
let _unstableCacheLifeWarned = false;
export function unstable_cacheLife(profile: string | CacheLifeConfig): void {
if (!_unstableCacheLifeWarned) {
_unstableCacheLifeWarned = true;
const error = new Error(
"`unstable_cacheLife` was recently stabilized and should be imported as `cacheLife`. The `unstable` prefixed form will be removed in a future version of Next.js.",
);
console.error(error);
}
return cacheLife(profile);
}

/**
* @deprecated Use `cacheTag` instead. `unstable_cacheTag` was stabilized
* upstream and the `unstable_`-prefixed name will be removed in a future
* version of Next.js. Kept as a delegating alias for parity.
*
* Emits a one-time deprecation warning via `console.error` (matching Next.js),
* then delegates to `cacheTag`.
*
* Ported from Next.js: packages/next/cache.js
* https://github.com/vercel/next.js/blob/canary/packages/next/cache.js
*
* Asserted by Next.js test:
* test/e2e/app-dir/cache-components-errors/cache-components-unstable-deprecations.test.ts
*/
let _unstableCacheTagWarned = false;
export function unstable_cacheTag(...tags: string[]): void {
if (!_unstableCacheTagWarned) {
_unstableCacheTagWarned = true;
const error = new Error(
"`unstable_cacheTag` was recently stabilized and should be imported as `cacheTag`. The `unstable` prefixed form will be removed in a future version of Next.js.",
);
console.error(error);
}
return cacheTag(...tags);
}

// ---------------------------------------------------------------------------
// unstable_cache — the older caching API
// ---------------------------------------------------------------------------
Expand Down
48 changes: 48 additions & 0 deletions tests/shims.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2516,6 +2516,54 @@ describe("next/cache shim", () => {
expect(() => cacheTag("tag1", "tag2", "tag3")).not.toThrow();
});

// Ported from Next.js: test/e2e/app-dir/cache-components-errors/cache-components-unstable-deprecations.test.ts
// https://github.com/vercel/next.js/blob/canary/test/e2e/app-dir/cache-components-errors/cache-components-unstable-deprecations.test.ts
it("unstable_cacheLife is exported as a deprecation alias for cacheLife", async () => {
const mod = await import("../packages/vinext/src/shims/cache.js");
expect(typeof mod.unstable_cacheLife).toBe("function");

// Matches the cacheLife signature: accepts a profile name or an inline config.
const error = vi.spyOn(console, "error").mockImplementation(() => {});
try {
expect(() => mod.unstable_cacheLife("hours")).not.toThrow();
expect(() =>
mod.unstable_cacheLife({ stale: 60, revalidate: 300, expire: 3600 }),
).not.toThrow();

// Next.js asserts CLI output contains "Error: `unstable_cacheLife` was recently stabilized".
// That string comes from console.error(new Error(...)) — match the same surface here.
const warned = error.mock.calls.some((args) => {
const msg = args[0];
const text = msg instanceof Error ? msg.message : String(msg ?? "");
return text.includes("`unstable_cacheLife` was recently stabilized");
});
expect(warned).toBe(true);
} finally {
error.mockRestore();
}
});

it("unstable_cacheTag is exported as a deprecation alias for cacheTag", async () => {
const mod = await import("../packages/vinext/src/shims/cache.js");
expect(typeof mod.unstable_cacheTag).toBe("function");

const error = vi.spyOn(console, "error").mockImplementation(() => {});
try {
// Matches the cacheTag signature: variadic string tags. Outside a "use cache"
// scope this is a no-op (same as cacheTag).
expect(() => mod.unstable_cacheTag("tag1", "tag2", "tag3")).not.toThrow();

const warned = error.mock.calls.some((args) => {
const msg = args[0];
const text = msg instanceof Error ? msg.message : String(msg ?? "");
return text.includes("`unstable_cacheTag` was recently stabilized");
});
expect(warned).toBe(true);
} finally {
error.mockRestore();
}
});

it("unstable_cache re-fetches when entry is stale (time-expired)", async () => {
const { unstable_cache, setCacheHandler, MemoryCacheHandler } =
await import("../packages/vinext/src/shims/cache.js");
Expand Down
Loading