From 2b7c531fc9d7d2417f225f4fd5cdb6214d703565 Mon Sep 17 00:00:00 2001 From: Akhilesh Nair <72593014+AkhileshNair2201@users.noreply.github.com> Date: Fri, 10 Jul 2026 18:57:09 +0530 Subject: [PATCH 1/2] NET-1329 simplify cache TTL to module const and per-call override Co-authored-by: Cursor --- CHANGELOG.md | 6 ++++++ README.md | 6 ++---- src/api/index.ts | 2 +- src/api/models/api.test.ts | 2 +- src/api/models/models.ts | 3 +-- src/api/prompts/api.test.ts | 2 +- src/api/prompts/api.ts | 10 +++++++--- src/api/prompts/index.ts | 2 +- src/api/prompts/models.ts | 8 +++++++- src/config.ts | 26 -------------------------- src/index.shutdown-cache.test.ts | 8 ++++---- src/index.ts | 3 +-- 12 files changed, 32 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 441524d..3335431 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Changed + +- **Prompt cache TTL** — Default TTL is the module constant `PROMPT_CACHE_TTL_SECONDS` (60). Override per call with `cacheTtl`. Removed unused `cacheTtlSeconds` init config and `NETRA_CACHE_TTL_SECONDS` env var. + ## [1.1.0-beta.1] - 2026-06-09 ### Fixed diff --git a/README.md b/README.md index 079be41..d3bf196 100644 --- a/README.md +++ b/README.md @@ -189,20 +189,19 @@ async function generateContent(prompt: string) { Fetch prompt versions from Prompt Studio via `Netra.prompts.getPrompt()`. Caching is **opt-in per call** — omit `useCache` (or set it to `false`) to always hit the API. -Set a global default TTL at init with `cacheTtlSeconds` (default: **60** seconds), or override TTL for a single call with `cacheTtl`. +Default TTL is **60 seconds** (`PROMPT_CACHE_TTL_SECONDS`). Override TTL for a single call with `cacheTtl`. ```typescript import { Netra } from "netra-sdk"; await Netra.init({ appName: "my-ai-app", - cacheTtlSeconds: 60, // optional; env: NETRA_CACHE_TTL_SECONDS }); // Always fetches from the API (default) const prompt = await Netra.prompts.getPrompt({ name: "my-prompt" }); -// Cached for 60s (global default TTL) +// Cached for 60s (default TTL) const cached = await Netra.prompts.getPrompt({ name: "my-prompt", useCache: true, @@ -229,7 +228,6 @@ You can configure the SDK using environment variables: | `NETRA_APP_NAME` | Name of your application | | `NETRA_ENV` | Environment (e.g., prod, dev) | | `NETRA_TRACE_CONTENT` | Capture prompt/completion content (default: true) | -| `NETRA_CACHE_TTL_SECONDS` | Default TTL in seconds for opt-in SDK read caches (default: 60) | ## 🤝 License diff --git a/src/api/index.ts b/src/api/index.ts index 0e555e2..249df25 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -66,7 +66,7 @@ export type { } from "./dashboard"; // Prompts API -export { Prompts } from "./prompts"; +export { Prompts, PROMPT_CACHE_TTL_SECONDS } from "./prompts"; export type { GetPromptParams, PromptResponse } from "./prompts"; // Models API diff --git a/src/api/models/api.test.ts b/src/api/models/api.test.ts index a640c87..3acf8ff 100644 --- a/src/api/models/api.test.ts +++ b/src/api/models/api.test.ts @@ -26,7 +26,7 @@ describe("Models.getModelPricing caching", () => { let getModelPricing: ReturnType; beforeEach(() => { - const config = new Config({ cacheTtlSeconds: 60 }); + const config = new Config(); models = new Models(config); getModelPricing = vi.fn(); (models as unknown as { client: ModelsHttpClient }).client = { diff --git a/src/api/models/models.ts b/src/api/models/models.ts index 40ea2e1..4ccf3a8 100644 --- a/src/api/models/models.ts +++ b/src/api/models/models.ts @@ -26,8 +26,7 @@ export interface GetModelPricingParams { useCache?: boolean; /** * Per-call TTL in seconds. - * When omitted with useCache: true, uses MODEL_PRICING_CACHE_TTL_SECONDS (300), - * NOT Config.cacheTtlSeconds. + * When omitted with useCache: true, uses MODEL_PRICING_CACHE_TTL_SECONDS (300). */ cacheTtl?: number; } diff --git a/src/api/prompts/api.test.ts b/src/api/prompts/api.test.ts index 9044eb5..d76c581 100644 --- a/src/api/prompts/api.test.ts +++ b/src/api/prompts/api.test.ts @@ -8,7 +8,7 @@ describe("Prompts.getPrompt caching", () => { let getPromptVersion: ReturnType; beforeEach(() => { - const config = new Config({ cacheTtlSeconds: 60 }); + const config = new Config(); prompts = new Prompts(config); getPromptVersion = vi.fn(); (prompts as unknown as { client: PromptsHttpClient }).client = { diff --git a/src/api/prompts/api.ts b/src/api/prompts/api.ts index 90c3423..3d8ca6d 100644 --- a/src/api/prompts/api.ts +++ b/src/api/prompts/api.ts @@ -2,7 +2,11 @@ import { TTLCache } from "../../cache"; import { Config } from "../../config"; import { Logger } from "../../logger"; import { PromptsHttpClient } from "./client"; -import { GetPromptParams, PromptResponse } from "./models"; +import { + GetPromptParams, + PROMPT_CACHE_TTL_SECONDS, + PromptResponse, +} from "./models"; export class Prompts { private config: Config; @@ -12,7 +16,7 @@ export class Prompts { constructor(config: Config) { this.config = config; this.client = new PromptsHttpClient(config); - this.cache = new TTLCache(config.cacheTtlSeconds); + this.cache = new TTLCache(PROMPT_CACHE_TTL_SECONDS); } /** Clear all cached prompt entries. */ @@ -26,7 +30,7 @@ export class Prompts { * @param params.name - Name of the prompt (required) * @param params.label - Label of the prompt version (default: "production") * @param params.useCache - When true, read/write the in-memory cache (default: false) - * @param params.cacheTtl - Per-call cache TTL in seconds (default: init cacheTtlSeconds) + * @param params.cacheTtl - Per-call cache TTL in seconds (default: PROMPT_CACHE_TTL_SECONDS) */ async getPrompt(params: GetPromptParams): Promise { if (!params || typeof params.name !== "string" || !params.name) { diff --git a/src/api/prompts/index.ts b/src/api/prompts/index.ts index bedb08f..0847ecf 100644 --- a/src/api/prompts/index.ts +++ b/src/api/prompts/index.ts @@ -3,5 +3,5 @@ */ export { Prompts } from "./api"; - +export { PROMPT_CACHE_TTL_SECONDS } from "./models"; export type { GetPromptParams, PromptResponse } from "./models"; diff --git a/src/api/prompts/models.ts b/src/api/prompts/models.ts index ae9c1d8..50ed7cc 100644 --- a/src/api/prompts/models.ts +++ b/src/api/prompts/models.ts @@ -2,12 +2,18 @@ * Prompts API Models */ +/** Default TTL (seconds) for prompt cache when useCache is true and cacheTtl is omitted. */ +export const PROMPT_CACHE_TTL_SECONDS = 60; + export interface GetPromptParams { name: string; label?: string; /** When true, serve from in-memory cache when available (default: false). */ useCache?: boolean; - /** Per-call TTL in seconds; falls back to init `cacheTtlSeconds` when omitted. */ + /** + * Per-call TTL in seconds. + * When omitted with useCache: true, uses PROMPT_CACHE_TTL_SECONDS (60). + */ cacheTtl?: number; } diff --git a/src/config.ts b/src/config.ts index 5cdbed4..9bf08b5 100644 --- a/src/config.ts +++ b/src/config.ts @@ -19,8 +19,6 @@ export interface NetraConfig { instruments?: Set; blockInstruments?: Set; rootInstruments?: Set; - /** Default TTL in seconds for opt-in SDK read caches (env: NETRA_CACHE_TTL_SECONDS). */ - cacheTtlSeconds?: number; } export enum NetraInstruments { @@ -130,7 +128,6 @@ export class Config { environment: string; resourceAttributes: Record; blockedSpans?: string[]; - cacheTtlSeconds: number; constructor(config: NetraConfig = {}) { this.appName = this._getAppName(config.appName); @@ -167,11 +164,6 @@ export class Config { config.resourceAttributes, ); this.blockedSpans = config.blockedSpans; - this.cacheTtlSeconds = this._getIntConfig( - config.cacheTtlSeconds, - "NETRA_CACHE_TTL_SECONDS", - 60, - ); this._validateApiKey(); this._setupAuthentication(); @@ -251,24 +243,6 @@ export class Config { } } - private _getIntConfig( - param: number | undefined, - envVar: string, - defaultValue: number, - ): number { - if (param !== undefined) { - return param; - } - - const envValue = process.env[envVar]; - if (envValue === undefined) { - return defaultValue; - } - - const parsed = parseInt(envValue, 10); - return Number.isNaN(parsed) ? defaultValue : parsed; - } - private _getBoolConfig( param: boolean | undefined, envVar: string, diff --git a/src/index.shutdown-cache.test.ts b/src/index.shutdown-cache.test.ts index b37ddf2..ed9076e 100644 --- a/src/index.shutdown-cache.test.ts +++ b/src/index.shutdown-cache.test.ts @@ -18,7 +18,7 @@ describe("Netra.shutdown cache clearing", () => { }); it("clears prompts cache on shutdown so the next cached getPrompt hits HTTP", async () => { - await Netra.init({ cacheTtlSeconds: 60 }); + await Netra.init({}); const getPromptVersion = vi .fn() @@ -33,7 +33,7 @@ describe("Netra.shutdown cache clearing", () => { await Netra.shutdown(); - await Netra.init({ cacheTtlSeconds: 60 }); + await Netra.init({}); (Netra.prompts as unknown as { client: PromptsHttpClient }).client = { getPromptVersion, } as unknown as PromptsHttpClient; @@ -43,7 +43,7 @@ describe("Netra.shutdown cache clearing", () => { }); it("clears models cache on shutdown so the next cached getModelPricing hits HTTP", async () => { - await Netra.init({ cacheTtlSeconds: 60 }); + await Netra.init({}); const pricing = [ { @@ -64,7 +64,7 @@ describe("Netra.shutdown cache clearing", () => { await Netra.shutdown(); - await Netra.init({ cacheTtlSeconds: 60 }); + await Netra.init({}); (Netra.models as unknown as { client: ModelsHttpClient }).client = { getModelPricing, } as unknown as ModelsHttpClient; diff --git a/src/index.ts b/src/index.ts index 4fbf8dc..87aaffe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -66,6 +66,7 @@ export { Usage, // Prompts API Prompts, + PROMPT_CACHE_TTL_SECONDS, // Models API Models, MODEL_PRICING_CACHE_TTL_SECONDS, @@ -163,8 +164,6 @@ export class Netra { /** * Initialize the Netra SDK. - * - * @param config.cacheTtlSeconds - Default TTL in seconds for opt-in read caches (default: 60, env: NETRA_CACHE_TTL_SECONDS) */ static async init(config: NetraConfig = {}): Promise { if (this._initialized) { From 2a0400c31d419fda3d1edf75a69563a45451f7a2 Mon Sep 17 00:00:00 2001 From: Akhilesh Nair <72593014+AkhileshNair2201@users.noreply.github.com> Date: Fri, 10 Jul 2026 19:52:27 +0530 Subject: [PATCH 2/2] test: cover prompt default TTL and drop removed cacheTtlSeconds Assert PROMPT_CACHE_TTL_SECONDS expiry when cacheTtl is omitted, and stop passing the removed Config option in models client tests. Co-authored-by: Cursor --- src/api/models/client.test.ts | 2 +- src/api/prompts/api.test.ts | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/api/models/client.test.ts b/src/api/models/client.test.ts index fc15d7b..3fe917d 100644 --- a/src/api/models/client.test.ts +++ b/src/api/models/client.test.ts @@ -28,7 +28,7 @@ describe("ModelsHttpClient.getModelPricing", () => { let logError: ReturnType; beforeEach(() => { - client = new ModelsHttpClient(new Config({ cacheTtlSeconds: 60 })); + client = new ModelsHttpClient(new Config()); get = vi.spyOn(client, "get"); isInitialized = vi.spyOn(client, "isInitialized").mockReturnValue(true); logError = vi.spyOn(Logger, "error").mockImplementation(() => {}); diff --git a/src/api/prompts/api.test.ts b/src/api/prompts/api.test.ts index d76c581..75294de 100644 --- a/src/api/prompts/api.test.ts +++ b/src/api/prompts/api.test.ts @@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { Config } from "../../config"; import { Prompts } from "./api"; import { PromptsHttpClient } from "./client"; +import { PROMPT_CACHE_TTL_SECONDS } from "./models"; describe("Prompts.getPrompt caching", () => { let prompts: Prompts; @@ -87,7 +88,7 @@ describe("Prompts.getPrompt caching", () => { expect(getPromptVersion).toHaveBeenCalledTimes(2); }); - it("expires per-call cacheTtl before the global default TTL", async () => { + it("expires per-call cacheTtl before the module default TTL", async () => { vi.useFakeTimers(); getPromptVersion.mockResolvedValue({ data: { template: "v1" } }); @@ -108,6 +109,22 @@ describe("Prompts.getPrompt caching", () => { expect(getPromptVersion).toHaveBeenCalledTimes(2); }); + it("expires cached prompts after PROMPT_CACHE_TTL_SECONDS when cacheTtl is omitted", async () => { + vi.useFakeTimers(); + getPromptVersion.mockResolvedValue({ data: { template: "v1" } }); + + await prompts.getPrompt({ name: "my-prompt", useCache: true }); + expect(getPromptVersion).toHaveBeenCalledTimes(1); + + vi.advanceTimersByTime(PROMPT_CACHE_TTL_SECONDS * 1000 - 1); + await prompts.getPrompt({ name: "my-prompt", useCache: true }); + expect(getPromptVersion).toHaveBeenCalledTimes(1); + + vi.advanceTimersByTime(2); + await prompts.getPrompt({ name: "my-prompt", useCache: true }); + expect(getPromptVersion).toHaveBeenCalledTimes(2); + }); + it("hits HTTP again after clearCache when useCache is true", async () => { getPromptVersion.mockResolvedValue({ data: { template: "v1" } });