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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/api/models/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe("Models.getModelPricing caching", () => {
let getModelPricing: ReturnType<typeof vi.fn>;

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 = {
Expand Down
2 changes: 1 addition & 1 deletion src/api/models/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe("ModelsHttpClient.getModelPricing", () => {
let logError: ReturnType<typeof vi.spyOn>;

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(() => {});
Expand Down
3 changes: 1 addition & 2 deletions src/api/models/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
21 changes: 19 additions & 2 deletions src/api/prompts/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ 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;
let getPromptVersion: ReturnType<typeof vi.fn>;

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 = {
Expand Down Expand Up @@ -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" } });

Expand All @@ -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" } });

Expand Down
10 changes: 7 additions & 3 deletions src/api/prompts/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -12,7 +16,7 @@ export class Prompts {
constructor(config: Config) {
this.config = config;
this.client = new PromptsHttpClient(config);
this.cache = new TTLCache<PromptResponse>(config.cacheTtlSeconds);
this.cache = new TTLCache<PromptResponse>(PROMPT_CACHE_TTL_SECONDS);
}

/** Clear all cached prompt entries. */
Expand All @@ -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<PromptResponse | null> {
if (!params || typeof params.name !== "string" || !params.name) {
Expand Down
2 changes: 1 addition & 1 deletion src/api/prompts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
*/

export { Prompts } from "./api";

export { PROMPT_CACHE_TTL_SECONDS } from "./models";
export type { GetPromptParams, PromptResponse } from "./models";
8 changes: 7 additions & 1 deletion src/api/prompts/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
26 changes: 0 additions & 26 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ export interface NetraConfig {
instruments?: Set<NetraInstruments>;
blockInstruments?: Set<NetraInstruments>;
rootInstruments?: Set<NetraInstruments>;
/** Default TTL in seconds for opt-in SDK read caches (env: NETRA_CACHE_TTL_SECONDS). */
cacheTtlSeconds?: number;
}

export enum NetraInstruments {
Expand Down Expand Up @@ -130,7 +128,6 @@ export class Config {
environment: string;
resourceAttributes: Record<string, any>;
blockedSpans?: string[];
cacheTtlSeconds: number;

constructor(config: NetraConfig = {}) {
this.appName = this._getAppName(config.appName);
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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,
Expand Down
8 changes: 4 additions & 4 deletions src/index.shutdown-cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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;
Expand All @@ -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 = [
{
Expand All @@ -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;
Expand Down
3 changes: 1 addition & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export {
Usage,
// Prompts API
Prompts,
PROMPT_CACHE_TTL_SECONDS,
// Models API
Models,
MODEL_PRICING_CACHE_TTL_SECONDS,
Expand Down Expand Up @@ -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<void> {
if (this._initialized) {
Expand Down