@nexussdk/core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,287 @@
1
+ /**
2
+ * @fileoverview Resilient HTTP client with exponential backoff and jitter.
3
+ * Zero external dependencies — uses only native browser fetch and AbortController.
4
+ * @module @nexus/core/http-client
5
+ */
6
+ /**
7
+ * Options for configuring a single HTTP request with retry behaviour.
8
+ *
9
+ * @example
10
+ * const opts: HttpClientOptions = {
11
+ * url: 'https://api.nexus.dev/api/v1/flags/eval',
12
+ * method: 'GET',
13
+ * headers: { Authorization: 'Bearer pk_live_...' },
14
+ * timeoutMs: 3000,
15
+ * maxRetries: 3,
16
+ * };
17
+ */
18
+ interface HttpClientOptions {
19
+ /** Request URL. */
20
+ url: string;
21
+ /** HTTP method. Defaults to 'GET'. */
22
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
23
+ /** Request headers. */
24
+ headers?: Record<string, string>;
25
+ /** Request body (will be JSON-serialized if object). */
26
+ body?: unknown;
27
+ /** Request timeout in milliseconds. Defaults to 5000ms. */
28
+ timeoutMs?: number;
29
+ /** Maximum retry attempts on 5xx / network errors. Defaults to 3. */
30
+ maxRetries?: number;
31
+ /** Base delay in milliseconds for exponential backoff. Defaults to 1000ms. */
32
+ retryBaseMs?: number;
33
+ /** Maximum backoff delay in milliseconds. Defaults to 30000ms. */
34
+ retryMaxMs?: number;
35
+ /** Optional AbortSignal for external cancellation. */
36
+ signal?: AbortSignal;
37
+ }
38
+ /**
39
+ * Result of a successful HTTP fetch.
40
+ *
41
+ * @template T The expected response body type.
42
+ */
43
+ interface HttpClientResult<T> {
44
+ /** Parsed response body. */
45
+ data: T;
46
+ /** HTTP status code. */
47
+ status: number;
48
+ /** Response headers. */
49
+ headers: Headers;
50
+ }
51
+ /**
52
+ * Computes the exponential backoff sleep duration with full random jitter.
53
+ * Formula: sleep = random(0, min(maxMs, baseMs * 2^attempt))
54
+ *
55
+ * @param attempt - Zero-based retry attempt index.
56
+ * @param baseMs - Base delay in milliseconds.
57
+ * @param maxMs - Maximum delay cap in milliseconds.
58
+ * @returns Sleep duration in milliseconds.
59
+ *
60
+ * @example
61
+ * const delay = computeBackoffMs(2, 1000, 30000); // ~0-4000ms
62
+ */
63
+ declare function computeBackoffMs(attempt: number, baseMs?: number, maxMs?: number): number;
64
+ /**
65
+ * Performs a resilient HTTP fetch with exponential backoff and jitter.
66
+ * Automatically retries on network failures and 5xx/429 responses.
67
+ *
68
+ * @template T The expected response body type.
69
+ * @param options - Request configuration options.
70
+ * @returns Promise resolving to {@link HttpClientResult}.
71
+ * @throws {Error} When all retry attempts are exhausted or request is aborted.
72
+ *
73
+ * @example
74
+ * const result = await fetchWithRetry<BatchFlagEvaluation>({
75
+ * url: 'https://api.nexus.dev/api/v1/flags/eval',
76
+ * method: 'GET',
77
+ * headers: { Authorization: 'Bearer pk_live_...' },
78
+ * timeoutMs: 3000,
79
+ * maxRetries: 3,
80
+ * });
81
+ * console.log(result.data); // { checkout_v2: { enabled: true, ... } }
82
+ */
83
+ declare function fetchWithRetry<T = unknown>(options: HttpClientOptions): Promise<HttpClientResult<T>>;
84
+
85
+ /**
86
+ * @fileoverview Fixed-capacity ring buffer with FIFO eviction under pressure.
87
+ * Used by SDK Tracker for bounded breadcrumb and telemetry event storage.
88
+ * @module @nexus/core/ring-buffer
89
+ */
90
+ /**
91
+ * A fixed-capacity circular buffer that drops the oldest entry when full.
92
+ * Never causes memory leaks or starvation of host applications.
93
+ *
94
+ * @template T The type of items stored in the buffer.
95
+ *
96
+ * @example
97
+ * const buffer = new RingBuffer<string>(3);
98
+ * buffer.push('a'); // [a]
99
+ * buffer.push('b'); // [a, b]
100
+ * buffer.push('c'); // [a, b, c]
101
+ * buffer.push('d'); // [b, c, d] — 'a' evicted (FIFO)
102
+ * buffer.toArray(); // ['b', 'c', 'd']
103
+ */
104
+ declare class RingBuffer<T> {
105
+ private readonly capacity;
106
+ private readonly buffer;
107
+ private head;
108
+ private count;
109
+ /**
110
+ * Creates a new RingBuffer with the given capacity.
111
+ *
112
+ * @param capacity - Maximum number of items to retain. Must be >= 1.
113
+ * @throws {RangeError} If capacity is less than 1.
114
+ *
115
+ * @example
116
+ * const breadcrumbBuffer = new RingBuffer<Breadcrumb>(20);
117
+ */
118
+ constructor(capacity: number);
119
+ /**
120
+ * Appends an item to the buffer.
121
+ * If the buffer is at capacity, the oldest item is silently dropped.
122
+ *
123
+ * @param item - The item to insert.
124
+ *
125
+ * @example
126
+ * buffer.push({ timestamp: Date.now(), category: 'ui.click', message: 'Clicked #btn' });
127
+ */
128
+ push(item: T): void;
129
+ /**
130
+ * Returns all stored items in chronological order (oldest first).
131
+ *
132
+ * @returns Ordered array of stored items.
133
+ *
134
+ * @example
135
+ * const breadcrumbs = buffer.toArray(); // [{...}, {...}]
136
+ */
137
+ toArray(): T[];
138
+ /**
139
+ * Returns the current number of items in the buffer.
140
+ *
141
+ * @returns Item count (0 to capacity).
142
+ *
143
+ * @example
144
+ * console.log(buffer.size); // 3
145
+ */
146
+ get size(): number;
147
+ /**
148
+ * Returns the maximum capacity of the buffer.
149
+ *
150
+ * @returns Buffer capacity.
151
+ *
152
+ * @example
153
+ * console.log(buffer.maxCapacity); // 20
154
+ */
155
+ get maxCapacity(): number;
156
+ /**
157
+ * Checks if the buffer is currently at full capacity.
158
+ *
159
+ * @returns `true` if the buffer is full.
160
+ *
161
+ * @example
162
+ * if (buffer.isFull) console.log('Oldest breadcrumb will be evicted on next push.');
163
+ */
164
+ get isFull(): boolean;
165
+ /**
166
+ * Removes all items from the buffer and resets internal state.
167
+ *
168
+ * @example
169
+ * buffer.clear(); // buffer is now empty
170
+ */
171
+ clear(): void;
172
+ /**
173
+ * Peeks at the most recently added item without removing it.
174
+ *
175
+ * @returns The last item pushed, or `undefined` if empty.
176
+ *
177
+ * @example
178
+ * const last = buffer.peek(); // most recent item
179
+ */
180
+ peek(): T | undefined;
181
+ }
182
+
183
+ /**
184
+ * @fileoverview Universal API key resolver across all JavaScript runtimes.
185
+ * Supports: Browser globals, Node.js process.env, Vite, Next.js, and Nuxt.
186
+ * @module @nexus/core/env-resolver
187
+ */
188
+ /**
189
+ * Resolution priority for API key lookup.
190
+ * 1. Direct parameter passed into init options
191
+ * 2. Browser global: window.__NEXUS_API_KEY__
192
+ * 3. Node/Next.js: process.env.NEXUS_API_KEY
193
+ * 4. Next.js client bundler: process.env.NEXT_PUBLIC_NEXUS_API_KEY
194
+ * 5. Vite bundler: import.meta.env.VITE_NEXUS_API_KEY
195
+ * 6. Nuxt: process.env.NUXT_PUBLIC_NEXUS_API_KEY
196
+ */
197
+ declare global {
198
+ interface Window {
199
+ /** Browser-injected API key for Nexus SDK. */
200
+ __NEXUS_API_KEY__?: string;
201
+ }
202
+ }
203
+ /**
204
+ * Resolves the Nexus API key from the environment using a prioritized lookup chain.
205
+ *
206
+ * Resolution order:
207
+ * 1. `directValue` — Parameter passed directly into SDK init options
208
+ * 2. `window.__NEXUS_API_KEY__` — Browser global injection (CDN/script embed use cases)
209
+ * 3. `process.env.NEXUS_API_KEY` — Node.js / Docker / CI environments
210
+ * 4. `process.env.NEXT_PUBLIC_NEXUS_API_KEY` — Next.js client-side bundling
211
+ * 5. `import.meta.env.VITE_NEXUS_API_KEY` — Vite / Vitest bundling
212
+ * 6. `process.env.NUXT_PUBLIC_NEXUS_API_KEY` — Nuxt 3 public runtime config
213
+ *
214
+ * @param directValue - Explicitly provided API key (highest priority).
215
+ * @returns The resolved API key string.
216
+ * @throws {Error} If no API key can be resolved from any source.
217
+ *
218
+ * @example
219
+ * // In a Next.js application:
220
+ * // process.env.NEXT_PUBLIC_NEXUS_API_KEY = 'pk_live_abc123'
221
+ * const key = resolveApiKey(); // 'pk_live_abc123'
222
+ *
223
+ * @example
224
+ * // Passing directly (overrides all env vars):
225
+ * const key = resolveApiKey('pk_live_directkey'); // 'pk_live_directkey'
226
+ */
227
+ declare function resolveApiKey(directValue?: string): string;
228
+ /**
229
+ * Resolves the Nexus base URL from configuration or environment variables.
230
+ *
231
+ * @param directValue - Explicitly provided base URL.
232
+ * @param defaultUrl - Default URL if no env var is found.
233
+ * @returns The resolved base URL string (trailing slash stripped).
234
+ *
235
+ * @example
236
+ * // Resolves to http://localhost:8080 in development
237
+ * const url = resolveBaseUrl(undefined, 'https://api.nexus.dev');
238
+ */
239
+ declare function resolveBaseUrl(directValue?: string, defaultUrl?: string): string;
240
+
241
+ /**
242
+ * @fileoverview Circular-reference-safe JSON serializer using WeakSet tracking.
243
+ * Prevents TypeError crashes when serializing objects with circular references.
244
+ * @module @nexus/core/safe-json
245
+ */
246
+ /**
247
+ * Serializes a value to a JSON string, gracefully handling:
248
+ * - Circular references (replaced with "[Circular]")
249
+ * - BigInt values (converted to string)
250
+ * - Symbol values (converted to string)
251
+ * - Function values (replaced with "[Function]")
252
+ * - Deep nesting (capped at maxDepth, replaced with "[MaxDepthExceeded]")
253
+ *
254
+ * @param value - Any value to serialize.
255
+ * @param maxDepth - Maximum recursion depth. Defaults to 8.
256
+ * @returns JSON string representation of the value.
257
+ *
258
+ * @example
259
+ * const obj: Record<string, unknown> = { name: 'test' };
260
+ * obj['self'] = obj; // circular reference
261
+ * const json = safeStringify(obj);
262
+ * // '{"name":"test","self":"[Circular]"}'
263
+ *
264
+ * @example
265
+ * const err = new Error('Something failed');
266
+ * const json = safeStringify({ error: err, code: 500 });
267
+ * // '{"error":{"name":"Error","message":"Something failed","stack":"..."},"code":500}'
268
+ */
269
+ declare function safeStringify(value: unknown, maxDepth?: number): string;
270
+ /**
271
+ * Safely parses a JSON string without throwing on invalid input.
272
+ *
273
+ * @param input - The JSON string to parse.
274
+ * @param fallback - Value to return if parsing fails. Defaults to `null`.
275
+ * @returns Parsed value or fallback.
276
+ *
277
+ * @example
278
+ * const data = safeParse<{ id: string }>('{"id":"123"}');
279
+ * // { id: '123' }
280
+ *
281
+ * @example
282
+ * const data = safeParse<unknown>('{{invalid json}}', null);
283
+ * // null
284
+ */
285
+ declare function safeParse<T = unknown>(input: string, fallback?: T | null): T | null;
286
+
287
+ export { type HttpClientOptions, type HttpClientResult, RingBuffer, computeBackoffMs, fetchWithRetry, resolveApiKey, resolveBaseUrl, safeParse, safeStringify };
@@ -0,0 +1,9 @@
1
+ var NexusCore=(function(exports){'use strict';function h(t,e=1e3,n=3e4){let r=e*Math.pow(2,t),i=Math.min(n,r);return Math.random()*i}function w(t,e){return new Promise((n,r)=>{if(e?.aborted){r(new DOMException("Aborted","AbortError"));return}let i=setTimeout(n,t);e?.addEventListener("abort",()=>{clearTimeout(i),r(new DOMException("Aborted","AbortError"));});})}function S(t){return t>=500||t===429}async function A(t){let{url:e,method:n="GET",headers:r={},body:i,timeoutMs:u=5e3,maxRetries:a=3,retryBaseMs:y=1e3,retryMaxMs:E=3e4,signal:f}=t,p=new Error("Request failed");for(let c=0;c<=a;c++){if(f?.aborted)throw new DOMException("Request aborted by caller.","AbortError");let l=new AbortController,m=setTimeout(()=>l.abort(),u);f?.addEventListener("abort",()=>l.abort());try{let s={method:n,headers:{"Content-Type":"application/json",...r},signal:l.signal};i!==void 0&&(s.body=typeof i=="string"?i:JSON.stringify(i));let o=await fetch(e,s);if(clearTimeout(m),o.ok)return {data:await o.json(),status:o.status,headers:o.headers};if(S(o.status)&&c<a){p=new Error(`HTTP ${o.status}: ${o.statusText}`);let g=h(c,y,E);await w(g,f);continue}let x=await o.text().catch(()=>"");throw new Error(`HTTP ${o.status}: ${x}`)}catch(s){if(clearTimeout(m),s instanceof DOMException&&s.name==="AbortError")throw s;if(p=s instanceof Error?s:new Error(String(s)),c<a){let o=h(c,y,E);await w(o,f);}}}throw p}var _=class{capacity;buffer;head=0;count=0;constructor(e){if(e<1)throw new RangeError(`RingBuffer capacity must be >= 1, got ${e}`);this.capacity=e,this.buffer=new Array(e).fill(void 0);}push(e){this.buffer[this.head]=e,this.head=(this.head+1)%this.capacity,this.count<this.capacity&&this.count++;}toArray(){if(this.count===0)return [];let e=[];if(this.count<this.capacity)for(let n=0;n<this.count;n++)e.push(this.buffer[n]);else for(let n=0;n<this.capacity;n++)e.push(this.buffer[(this.head+n)%this.capacity]);return e}get size(){return this.count}get maxCapacity(){return this.capacity}get isFull(){return this.count===this.capacity}clear(){this.buffer.fill(void 0),this.head=0,this.count=0;}peek(){if(this.count===0)return;let e=(this.head-1+this.capacity)%this.capacity;return this.buffer[e]}};function d(t){try{let e=globalThis.process;if(typeof e<"u"&&e&&e.env)return e.env[t]??void 0}catch{}}function T(t){try{let e=typeof globalThis<"u"&&globalThis.__import_meta__;if(e&&e.env)return e.env[t]??void 0;let n=new Function("try { return import.meta; } catch(e) { return undefined; }")();if(n&&n.env)return n.env[t]??void 0}catch{}}function P(){try{if(typeof window<"u"&&window.__NEXUS_API_KEY__)return window.__NEXUS_API_KEY__}catch{}}function N(t){let e=t||P()||d("NEXUS_API_KEY")||d("NEXT_PUBLIC_NEXUS_API_KEY")||T("VITE_NEXUS_API_KEY")||d("NUXT_PUBLIC_NEXUS_API_KEY");if(!e||e.trim()==="")throw new Error(`[Nexus SDK] No API key found. Please provide one via:
2
+ 1. Nexus.init({ apiKey: "pk_live_..." })
3
+ 2. window.__NEXUS_API_KEY__ = "pk_live_..."
4
+ 3. NEXUS_API_KEY env var
5
+ 4. NEXT_PUBLIC_NEXUS_API_KEY (Next.js)
6
+ 5. VITE_NEXUS_API_KEY (Vite)
7
+ 6. NUXT_PUBLIC_NEXUS_API_KEY (Nuxt)`);return e.trim()}function U(t,e="https://api.nexus.dev"){return (t||d("NEXT_PUBLIC_NEXUS_URL")||T("VITE_NEXUS_URL")||e).replace(/\/$/,"")}function b(t,e,n,r){if(n>r)return "[MaxDepthExceeded]";if(t==null)return t;if(typeof t!="object"&&typeof t!="function")return typeof t=="bigint"||typeof t=="symbol"?t.toString():typeof t=="function"?"[Function]":t;if(t instanceof Error)return {name:t.name,message:t.message,stack:t.stack};if(e.has(t))return "[Circular]";if(e.add(t),Array.isArray(t)){let u=t.map(a=>b(a,e,n+1,r));return e.delete(t),u}let i={};for(let u of Object.keys(t)){let a=t[u];i[u]=b(a,e,n+1,r);}return e.delete(t),i}function I(t,e=8){let r=b(t,new WeakSet,0,e);try{return JSON.stringify(r)}catch{return JSON.stringify({error:"[SerializationFailed]"})}}function k(t,e=null){try{return JSON.parse(t)}catch{return e}}
8
+ exports.RingBuffer=_;exports.computeBackoffMs=h;exports.fetchWithRetry=A;exports.resolveApiKey=N;exports.resolveBaseUrl=U;exports.safeParse=k;exports.safeStringify=I;return exports;})({});//# sourceMappingURL=index.global.js.map
9
+ //# sourceMappingURL=index.global.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/http-client.ts","../src/ring-buffer.ts","../src/env-resolver.ts","../src/safe-json.ts"],"names":["computeBackoffMs","attempt","baseMs","maxMs","exponential","capped","sleep","ms","signal","resolve","reject","timer","isRetryableStatus","status","fetchWithRetry","options","url","method","headers","body","timeoutMs","maxRetries","retryBaseMs","retryMaxMs","externalSignal","lastError","controller","timeoutId","requestInit","response","delay","errorBody","err","RingBuffer","capacity","item","result","i","lastIndex","readProcessEnv","key","proc","readViteEnv","meta","dynamicMeta","readWindowGlobal","resolveApiKey","directValue","resolved","resolveBaseUrl","defaultUrl","sanitizeForSerialization","value","seen","depth","maxDepth","propValue","safeStringify","sanitized","safeParse","input","fallback"],"mappings":"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","file":"index.global.js","sourcesContent":["/**\n * @fileoverview Resilient HTTP client with exponential backoff and jitter.\n * Zero external dependencies — uses only native browser fetch and AbortController.\n * @module @nexus/core/http-client\n */\n\n/**\n * Options for configuring a single HTTP request with retry behaviour.\n *\n * @example\n * const opts: HttpClientOptions = {\n * url: 'https://api.nexus.dev/api/v1/flags/eval',\n * method: 'GET',\n * headers: { Authorization: 'Bearer pk_live_...' },\n * timeoutMs: 3000,\n * maxRetries: 3,\n * };\n */\nexport interface HttpClientOptions {\n /** Request URL. */\n url: string;\n /** HTTP method. Defaults to 'GET'. */\n method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n /** Request headers. */\n headers?: Record<string, string>;\n /** Request body (will be JSON-serialized if object). */\n body?: unknown;\n /** Request timeout in milliseconds. Defaults to 5000ms. */\n timeoutMs?: number;\n /** Maximum retry attempts on 5xx / network errors. Defaults to 3. */\n maxRetries?: number;\n /** Base delay in milliseconds for exponential backoff. Defaults to 1000ms. */\n retryBaseMs?: number;\n /** Maximum backoff delay in milliseconds. Defaults to 30000ms. */\n retryMaxMs?: number;\n /** Optional AbortSignal for external cancellation. */\n signal?: AbortSignal;\n}\n\n/**\n * Result of a successful HTTP fetch.\n *\n * @template T The expected response body type.\n */\nexport interface HttpClientResult<T> {\n /** Parsed response body. */\n data: T;\n /** HTTP status code. */\n status: number;\n /** Response headers. */\n headers: Headers;\n}\n\n/**\n * Computes the exponential backoff sleep duration with full random jitter.\n * Formula: sleep = random(0, min(maxMs, baseMs * 2^attempt))\n *\n * @param attempt - Zero-based retry attempt index.\n * @param baseMs - Base delay in milliseconds.\n * @param maxMs - Maximum delay cap in milliseconds.\n * @returns Sleep duration in milliseconds.\n *\n * @example\n * const delay = computeBackoffMs(2, 1000, 30000); // ~0-4000ms\n */\nexport function computeBackoffMs(attempt: number, baseMs = 1000, maxMs = 30_000): number {\n const exponential = baseMs * Math.pow(2, attempt);\n const capped = Math.min(maxMs, exponential);\n // Full jitter: random value in [0, capped]\n return Math.random() * capped;\n}\n\n/**\n * Sleeps for the given number of milliseconds.\n *\n * @param ms - Delay in milliseconds.\n * @param signal - Optional AbortSignal to cancel the sleep.\n * @returns Promise that resolves after delay, or rejects if aborted.\n */\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new DOMException('Aborted', 'AbortError'));\n return;\n }\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener('abort', () => {\n clearTimeout(timer);\n reject(new DOMException('Aborted', 'AbortError'));\n });\n });\n}\n\n/**\n * Determines if an HTTP response status warrants a retry attempt.\n *\n * @param status - HTTP status code.\n * @returns `true` if the request should be retried.\n */\nfunction isRetryableStatus(status: number): boolean {\n return status >= 500 || status === 429;\n}\n\n/**\n * Performs a resilient HTTP fetch with exponential backoff and jitter.\n * Automatically retries on network failures and 5xx/429 responses.\n *\n * @template T The expected response body type.\n * @param options - Request configuration options.\n * @returns Promise resolving to {@link HttpClientResult}.\n * @throws {Error} When all retry attempts are exhausted or request is aborted.\n *\n * @example\n * const result = await fetchWithRetry<BatchFlagEvaluation>({\n * url: 'https://api.nexus.dev/api/v1/flags/eval',\n * method: 'GET',\n * headers: { Authorization: 'Bearer pk_live_...' },\n * timeoutMs: 3000,\n * maxRetries: 3,\n * });\n * console.log(result.data); // { checkout_v2: { enabled: true, ... } }\n */\nexport async function fetchWithRetry<T = unknown>(\n options: HttpClientOptions,\n): Promise<HttpClientResult<T>> {\n const {\n url,\n method = 'GET',\n headers = {},\n body,\n timeoutMs = 5_000,\n maxRetries = 3,\n retryBaseMs = 1_000,\n retryMaxMs = 30_000,\n signal: externalSignal,\n } = options;\n\n let lastError: Error = new Error('Request failed');\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n // Abort if external signal is already triggered\n if (externalSignal?.aborted) {\n throw new DOMException('Request aborted by caller.', 'AbortError');\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n // Merge external abort signal\n externalSignal?.addEventListener('abort', () => controller.abort());\n\n try {\n const requestInit: RequestInit = {\n method,\n headers: {\n 'Content-Type': 'application/json',\n ...headers,\n },\n signal: controller.signal,\n };\n\n if (body !== undefined) {\n requestInit.body = typeof body === 'string' ? body : JSON.stringify(body);\n }\n\n const response = await fetch(url, requestInit);\n clearTimeout(timeoutId);\n\n if (response.ok) {\n const data = (await response.json()) as T;\n return { data, status: response.status, headers: response.headers };\n }\n\n // Non-ok response: check if retryable\n if (isRetryableStatus(response.status) && attempt < maxRetries) {\n lastError = new Error(`HTTP ${response.status}: ${response.statusText}`);\n const delay = computeBackoffMs(attempt, retryBaseMs, retryMaxMs);\n await sleep(delay, externalSignal);\n continue;\n }\n\n // Non-retryable error (400, 401, 403, 404, etc.)\n const errorBody = await response.text().catch(() => '');\n throw new Error(`HTTP ${response.status}: ${errorBody}`);\n } catch (err) {\n clearTimeout(timeoutId);\n\n if (err instanceof DOMException && err.name === 'AbortError') {\n throw err; // Propagate abort without retry\n }\n\n lastError = err instanceof Error ? err : new Error(String(err));\n\n if (attempt < maxRetries) {\n const delay = computeBackoffMs(attempt, retryBaseMs, retryMaxMs);\n await sleep(delay, externalSignal);\n }\n }\n }\n\n throw lastError;\n}\n","/**\n * @fileoverview Fixed-capacity ring buffer with FIFO eviction under pressure.\n * Used by SDK Tracker for bounded breadcrumb and telemetry event storage.\n * @module @nexus/core/ring-buffer\n */\n\n/**\n * A fixed-capacity circular buffer that drops the oldest entry when full.\n * Never causes memory leaks or starvation of host applications.\n *\n * @template T The type of items stored in the buffer.\n *\n * @example\n * const buffer = new RingBuffer<string>(3);\n * buffer.push('a'); // [a]\n * buffer.push('b'); // [a, b]\n * buffer.push('c'); // [a, b, c]\n * buffer.push('d'); // [b, c, d] — 'a' evicted (FIFO)\n * buffer.toArray(); // ['b', 'c', 'd']\n */\nexport class RingBuffer<T> {\n private readonly capacity: number;\n private readonly buffer: Array<T | undefined>;\n private head = 0; // Points to the next write position\n private count = 0; // Current number of items\n\n /**\n * Creates a new RingBuffer with the given capacity.\n *\n * @param capacity - Maximum number of items to retain. Must be >= 1.\n * @throws {RangeError} If capacity is less than 1.\n *\n * @example\n * const breadcrumbBuffer = new RingBuffer<Breadcrumb>(20);\n */\n constructor(capacity: number) {\n if (capacity < 1) {\n throw new RangeError(`RingBuffer capacity must be >= 1, got ${capacity}`);\n }\n this.capacity = capacity;\n this.buffer = new Array<T | undefined>(capacity).fill(undefined);\n }\n\n /**\n * Appends an item to the buffer.\n * If the buffer is at capacity, the oldest item is silently dropped.\n *\n * @param item - The item to insert.\n *\n * @example\n * buffer.push({ timestamp: Date.now(), category: 'ui.click', message: 'Clicked #btn' });\n */\n push(item: T): void {\n this.buffer[this.head] = item;\n this.head = (this.head + 1) % this.capacity;\n if (this.count < this.capacity) {\n this.count++;\n }\n }\n\n /**\n * Returns all stored items in chronological order (oldest first).\n *\n * @returns Ordered array of stored items.\n *\n * @example\n * const breadcrumbs = buffer.toArray(); // [{...}, {...}]\n */\n toArray(): T[] {\n if (this.count === 0) return [];\n\n const result: T[] = [];\n if (this.count < this.capacity) {\n // Buffer not yet full — read from index 0 to head-1\n for (let i = 0; i < this.count; i++) {\n result.push(this.buffer[i] as T);\n }\n } else {\n // Buffer full — oldest item is at `head`\n for (let i = 0; i < this.capacity; i++) {\n result.push(this.buffer[(this.head + i) % this.capacity] as T);\n }\n }\n return result;\n }\n\n /**\n * Returns the current number of items in the buffer.\n *\n * @returns Item count (0 to capacity).\n *\n * @example\n * console.log(buffer.size); // 3\n */\n get size(): number {\n return this.count;\n }\n\n /**\n * Returns the maximum capacity of the buffer.\n *\n * @returns Buffer capacity.\n *\n * @example\n * console.log(buffer.maxCapacity); // 20\n */\n get maxCapacity(): number {\n return this.capacity;\n }\n\n /**\n * Checks if the buffer is currently at full capacity.\n *\n * @returns `true` if the buffer is full.\n *\n * @example\n * if (buffer.isFull) console.log('Oldest breadcrumb will be evicted on next push.');\n */\n get isFull(): boolean {\n return this.count === this.capacity;\n }\n\n /**\n * Removes all items from the buffer and resets internal state.\n *\n * @example\n * buffer.clear(); // buffer is now empty\n */\n clear(): void {\n this.buffer.fill(undefined);\n this.head = 0;\n this.count = 0;\n }\n\n /**\n * Peeks at the most recently added item without removing it.\n *\n * @returns The last item pushed, or `undefined` if empty.\n *\n * @example\n * const last = buffer.peek(); // most recent item\n */\n peek(): T | undefined {\n if (this.count === 0) return undefined;\n const lastIndex = (this.head - 1 + this.capacity) % this.capacity;\n return this.buffer[lastIndex];\n }\n}\n","/**\n * @fileoverview Universal API key resolver across all JavaScript runtimes.\n * Supports: Browser globals, Node.js process.env, Vite, Next.js, and Nuxt.\n * @module @nexus/core/env-resolver\n */\n\n/**\n * Resolution priority for API key lookup.\n * 1. Direct parameter passed into init options\n * 2. Browser global: window.__NEXUS_API_KEY__\n * 3. Node/Next.js: process.env.NEXUS_API_KEY\n * 4. Next.js client bundler: process.env.NEXT_PUBLIC_NEXUS_API_KEY\n * 5. Vite bundler: import.meta.env.VITE_NEXUS_API_KEY\n * 6. Nuxt: process.env.NUXT_PUBLIC_NEXUS_API_KEY\n */\n\n// Augment global types for browser injection\ndeclare global {\n interface Window {\n /** Browser-injected API key for Nexus SDK. */\n __NEXUS_API_KEY__?: string;\n }\n}\n\n/**\n * Safely reads an environment variable from process.env without throwing.\n *\n * @param key - The environment variable name.\n * @returns The value or `undefined`.\n */\nfunction readProcessEnv(key: string): string | undefined {\n try {\n const proc = (globalThis as unknown as { process?: { env?: Record<string, string | undefined> } }).process;\n if (typeof proc !== 'undefined' && proc && proc.env) {\n return proc.env[key] ?? undefined;\n }\n } catch {\n // process is not defined in pure browser environments\n }\n return undefined;\n}\n\n/**\n * Safely reads a Vite environment variable from import.meta.env.\n *\n * @param key - The Vite env variable name (VITE_* prefix required by Vite).\n * @returns The value or `undefined`.\n */\nfunction readViteEnv(key: string): string | undefined {\n try {\n const meta = typeof globalThis !== 'undefined' && (globalThis as unknown as { __import_meta__?: { env?: Record<string, string | undefined> } }).__import_meta__;\n if (meta && meta.env) {\n return meta.env[key] ?? undefined;\n }\n // Safe dynamic evaluation avoiding CJS compile-time import.meta error\n const dynamicMeta = new Function('try { return import.meta; } catch(e) { return undefined; }')() as { env?: Record<string, string | undefined> } | undefined;\n if (dynamicMeta && dynamicMeta.env) {\n return dynamicMeta.env[key] ?? undefined;\n }\n } catch {\n // Not a Vite runtime\n }\n return undefined;\n}\n\n/**\n * Safely reads the browser window global injection.\n *\n * @returns The window-injected API key or `undefined`.\n */\nfunction readWindowGlobal(): string | undefined {\n try {\n if (typeof window !== 'undefined' && window.__NEXUS_API_KEY__) {\n return window.__NEXUS_API_KEY__;\n }\n } catch {\n // window is not accessible (SSR/Worker context)\n }\n return undefined;\n}\n\n/**\n * Resolves the Nexus API key from the environment using a prioritized lookup chain.\n *\n * Resolution order:\n * 1. `directValue` — Parameter passed directly into SDK init options\n * 2. `window.__NEXUS_API_KEY__` — Browser global injection (CDN/script embed use cases)\n * 3. `process.env.NEXUS_API_KEY` — Node.js / Docker / CI environments\n * 4. `process.env.NEXT_PUBLIC_NEXUS_API_KEY` — Next.js client-side bundling\n * 5. `import.meta.env.VITE_NEXUS_API_KEY` — Vite / Vitest bundling\n * 6. `process.env.NUXT_PUBLIC_NEXUS_API_KEY` — Nuxt 3 public runtime config\n *\n * @param directValue - Explicitly provided API key (highest priority).\n * @returns The resolved API key string.\n * @throws {Error} If no API key can be resolved from any source.\n *\n * @example\n * // In a Next.js application:\n * // process.env.NEXT_PUBLIC_NEXUS_API_KEY = 'pk_live_abc123'\n * const key = resolveApiKey(); // 'pk_live_abc123'\n *\n * @example\n * // Passing directly (overrides all env vars):\n * const key = resolveApiKey('pk_live_directkey'); // 'pk_live_directkey'\n */\nexport function resolveApiKey(directValue?: string): string {\n const resolved =\n directValue ||\n readWindowGlobal() ||\n readProcessEnv('NEXUS_API_KEY') ||\n readProcessEnv('NEXT_PUBLIC_NEXUS_API_KEY') ||\n readViteEnv('VITE_NEXUS_API_KEY') ||\n readProcessEnv('NUXT_PUBLIC_NEXUS_API_KEY');\n\n if (!resolved || resolved.trim() === '') {\n throw new Error(\n '[Nexus SDK] No API key found. ' +\n 'Please provide one via:\\n' +\n ' 1. Nexus.init({ apiKey: \"pk_live_...\" })\\n' +\n ' 2. window.__NEXUS_API_KEY__ = \"pk_live_...\"\\n' +\n ' 3. NEXUS_API_KEY env var\\n' +\n ' 4. NEXT_PUBLIC_NEXUS_API_KEY (Next.js)\\n' +\n ' 5. VITE_NEXUS_API_KEY (Vite)\\n' +\n ' 6. NUXT_PUBLIC_NEXUS_API_KEY (Nuxt)',\n );\n }\n\n return resolved.trim();\n}\n\n/**\n * Resolves the Nexus base URL from configuration or environment variables.\n *\n * @param directValue - Explicitly provided base URL.\n * @param defaultUrl - Default URL if no env var is found.\n * @returns The resolved base URL string (trailing slash stripped).\n *\n * @example\n * // Resolves to http://localhost:8080 in development\n * const url = resolveBaseUrl(undefined, 'https://api.nexus.dev');\n */\nexport function resolveBaseUrl(\n directValue?: string,\n defaultUrl = 'https://api.nexus.dev',\n): string {\n const resolved =\n directValue ||\n readProcessEnv('NEXT_PUBLIC_NEXUS_URL') ||\n readViteEnv('VITE_NEXUS_URL') ||\n defaultUrl;\n\n return resolved.replace(/\\/$/, '');\n}\n","/**\n * @fileoverview Circular-reference-safe JSON serializer using WeakSet tracking.\n * Prevents TypeError crashes when serializing objects with circular references.\n * @module @nexus/core/safe-json\n */\n\n/**\n * Recursively sanitizes an object for JSON serialization by replacing\n * circular references with the string \"[Circular]\".\n *\n * @param value - The value to sanitize.\n * @param seen - WeakSet tracking visited objects (used internally for recursion).\n * @param depth - Current recursion depth.\n * @param maxDepth - Maximum allowed recursion depth.\n * @returns A serialization-safe copy of the value.\n */\nfunction sanitizeForSerialization(\n value: unknown,\n seen: WeakSet<object>,\n depth: number,\n maxDepth: number,\n): unknown {\n if (depth > maxDepth) {\n return '[MaxDepthExceeded]';\n }\n\n if (value === null || value === undefined) {\n return value;\n }\n\n if (typeof value !== 'object' && typeof value !== 'function') {\n // Primitive value: string, number, boolean, bigint, symbol\n if (typeof value === 'bigint') {\n return value.toString(); // JSON cannot handle BigInt natively\n }\n if (typeof value === 'symbol') {\n return value.toString();\n }\n if (typeof value === 'function') {\n return '[Function]';\n }\n return value;\n }\n\n // Handle Error objects specially — preserve message and stack\n if (value instanceof Error) {\n return {\n name: value.name,\n message: value.message,\n stack: value.stack,\n };\n }\n\n // Circular reference detection\n if (seen.has(value as object)) {\n return '[Circular]';\n }\n seen.add(value as object);\n\n // Handle Arrays\n if (Array.isArray(value)) {\n const result = value.map((item) =>\n sanitizeForSerialization(item, seen, depth + 1, maxDepth),\n );\n seen.delete(value as object);\n return result;\n }\n\n // Handle plain Objects\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(value as object)) {\n const propValue = (value as Record<string, unknown>)[key];\n result[key] = sanitizeForSerialization(propValue, seen, depth + 1, maxDepth);\n }\n seen.delete(value as object);\n return result;\n}\n\n/**\n * Serializes a value to a JSON string, gracefully handling:\n * - Circular references (replaced with \"[Circular]\")\n * - BigInt values (converted to string)\n * - Symbol values (converted to string)\n * - Function values (replaced with \"[Function]\")\n * - Deep nesting (capped at maxDepth, replaced with \"[MaxDepthExceeded]\")\n *\n * @param value - Any value to serialize.\n * @param maxDepth - Maximum recursion depth. Defaults to 8.\n * @returns JSON string representation of the value.\n *\n * @example\n * const obj: Record<string, unknown> = { name: 'test' };\n * obj['self'] = obj; // circular reference\n * const json = safeStringify(obj);\n * // '{\"name\":\"test\",\"self\":\"[Circular]\"}'\n *\n * @example\n * const err = new Error('Something failed');\n * const json = safeStringify({ error: err, code: 500 });\n * // '{\"error\":{\"name\":\"Error\",\"message\":\"Something failed\",\"stack\":\"...\"},\"code\":500}'\n */\nexport function safeStringify(value: unknown, maxDepth = 8): string {\n const seen = new WeakSet<object>();\n const sanitized = sanitizeForSerialization(value, seen, 0, maxDepth);\n try {\n return JSON.stringify(sanitized);\n } catch {\n // Absolute last resort fallback\n return JSON.stringify({ error: '[SerializationFailed]' });\n }\n}\n\n/**\n * Safely parses a JSON string without throwing on invalid input.\n *\n * @param input - The JSON string to parse.\n * @param fallback - Value to return if parsing fails. Defaults to `null`.\n * @returns Parsed value or fallback.\n *\n * @example\n * const data = safeParse<{ id: string }>('{\"id\":\"123\"}');\n * // { id: '123' }\n *\n * @example\n * const data = safeParse<unknown>('{{invalid json}}', null);\n * // null\n */\nexport function safeParse<T = unknown>(input: string, fallback: T | null = null): T | null {\n try {\n return JSON.parse(input) as T;\n } catch {\n return fallback;\n }\n}\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,9 @@
1
+ function h(t,e=1e3,n=3e4){let r=e*Math.pow(2,t),i=Math.min(n,r);return Math.random()*i}function w(t,e){return new Promise((n,r)=>{if(e?.aborted){r(new DOMException("Aborted","AbortError"));return}let i=setTimeout(n,t);e?.addEventListener("abort",()=>{clearTimeout(i),r(new DOMException("Aborted","AbortError"));});})}function S(t){return t>=500||t===429}async function A(t){let{url:e,method:n="GET",headers:r={},body:i,timeoutMs:u=5e3,maxRetries:a=3,retryBaseMs:y=1e3,retryMaxMs:E=3e4,signal:f}=t,p=new Error("Request failed");for(let c=0;c<=a;c++){if(f?.aborted)throw new DOMException("Request aborted by caller.","AbortError");let l=new AbortController,m=setTimeout(()=>l.abort(),u);f?.addEventListener("abort",()=>l.abort());try{let s={method:n,headers:{"Content-Type":"application/json",...r},signal:l.signal};i!==void 0&&(s.body=typeof i=="string"?i:JSON.stringify(i));let o=await fetch(e,s);if(clearTimeout(m),o.ok)return {data:await o.json(),status:o.status,headers:o.headers};if(S(o.status)&&c<a){p=new Error(`HTTP ${o.status}: ${o.statusText}`);let g=h(c,y,E);await w(g,f);continue}let x=await o.text().catch(()=>"");throw new Error(`HTTP ${o.status}: ${x}`)}catch(s){if(clearTimeout(m),s instanceof DOMException&&s.name==="AbortError")throw s;if(p=s instanceof Error?s:new Error(String(s)),c<a){let o=h(c,y,E);await w(o,f);}}}throw p}var _=class{capacity;buffer;head=0;count=0;constructor(e){if(e<1)throw new RangeError(`RingBuffer capacity must be >= 1, got ${e}`);this.capacity=e,this.buffer=new Array(e).fill(void 0);}push(e){this.buffer[this.head]=e,this.head=(this.head+1)%this.capacity,this.count<this.capacity&&this.count++;}toArray(){if(this.count===0)return [];let e=[];if(this.count<this.capacity)for(let n=0;n<this.count;n++)e.push(this.buffer[n]);else for(let n=0;n<this.capacity;n++)e.push(this.buffer[(this.head+n)%this.capacity]);return e}get size(){return this.count}get maxCapacity(){return this.capacity}get isFull(){return this.count===this.capacity}clear(){this.buffer.fill(void 0),this.head=0,this.count=0;}peek(){if(this.count===0)return;let e=(this.head-1+this.capacity)%this.capacity;return this.buffer[e]}};function d(t){try{let e=globalThis.process;if(typeof e<"u"&&e&&e.env)return e.env[t]??void 0}catch{}}function T(t){try{let e=typeof globalThis<"u"&&globalThis.__import_meta__;if(e&&e.env)return e.env[t]??void 0;let n=new Function("try { return import.meta; } catch(e) { return undefined; }")();if(n&&n.env)return n.env[t]??void 0}catch{}}function P(){try{if(typeof window<"u"&&window.__NEXUS_API_KEY__)return window.__NEXUS_API_KEY__}catch{}}function N(t){let e=t||P()||d("NEXUS_API_KEY")||d("NEXT_PUBLIC_NEXUS_API_KEY")||T("VITE_NEXUS_API_KEY")||d("NUXT_PUBLIC_NEXUS_API_KEY");if(!e||e.trim()==="")throw new Error(`[Nexus SDK] No API key found. Please provide one via:
2
+ 1. Nexus.init({ apiKey: "pk_live_..." })
3
+ 2. window.__NEXUS_API_KEY__ = "pk_live_..."
4
+ 3. NEXUS_API_KEY env var
5
+ 4. NEXT_PUBLIC_NEXUS_API_KEY (Next.js)
6
+ 5. VITE_NEXUS_API_KEY (Vite)
7
+ 6. NUXT_PUBLIC_NEXUS_API_KEY (Nuxt)`);return e.trim()}function U(t,e="https://api.nexus.dev"){return (t||d("NEXT_PUBLIC_NEXUS_URL")||T("VITE_NEXUS_URL")||e).replace(/\/$/,"")}function b(t,e,n,r){if(n>r)return "[MaxDepthExceeded]";if(t==null)return t;if(typeof t!="object"&&typeof t!="function")return typeof t=="bigint"||typeof t=="symbol"?t.toString():typeof t=="function"?"[Function]":t;if(t instanceof Error)return {name:t.name,message:t.message,stack:t.stack};if(e.has(t))return "[Circular]";if(e.add(t),Array.isArray(t)){let u=t.map(a=>b(a,e,n+1,r));return e.delete(t),u}let i={};for(let u of Object.keys(t)){let a=t[u];i[u]=b(a,e,n+1,r);}return e.delete(t),i}function I(t,e=8){let r=b(t,new WeakSet,0,e);try{return JSON.stringify(r)}catch{return JSON.stringify({error:"[SerializationFailed]"})}}function k(t,e=null){try{return JSON.parse(t)}catch{return e}}
8
+ export{_ as RingBuffer,h as computeBackoffMs,A as fetchWithRetry,N as resolveApiKey,U as resolveBaseUrl,k as safeParse,I as safeStringify};//# sourceMappingURL=index.mjs.map
9
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/http-client.ts","../src/ring-buffer.ts","../src/env-resolver.ts","../src/safe-json.ts"],"names":["computeBackoffMs","attempt","baseMs","maxMs","exponential","capped","sleep","ms","signal","resolve","reject","timer","isRetryableStatus","status","fetchWithRetry","options","url","method","headers","body","timeoutMs","maxRetries","retryBaseMs","retryMaxMs","externalSignal","lastError","controller","timeoutId","requestInit","response","delay","errorBody","err","RingBuffer","capacity","item","result","i","lastIndex","readProcessEnv","key","proc","readViteEnv","meta","dynamicMeta","readWindowGlobal","resolveApiKey","directValue","resolved","resolveBaseUrl","defaultUrl","sanitizeForSerialization","value","seen","depth","maxDepth","propValue","safeStringify","sanitized","safeParse","input","fallback"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * @fileoverview Resilient HTTP client with exponential backoff and jitter.\n * Zero external dependencies — uses only native browser fetch and AbortController.\n * @module @nexus/core/http-client\n */\n\n/**\n * Options for configuring a single HTTP request with retry behaviour.\n *\n * @example\n * const opts: HttpClientOptions = {\n * url: 'https://api.nexus.dev/api/v1/flags/eval',\n * method: 'GET',\n * headers: { Authorization: 'Bearer pk_live_...' },\n * timeoutMs: 3000,\n * maxRetries: 3,\n * };\n */\nexport interface HttpClientOptions {\n /** Request URL. */\n url: string;\n /** HTTP method. Defaults to 'GET'. */\n method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n /** Request headers. */\n headers?: Record<string, string>;\n /** Request body (will be JSON-serialized if object). */\n body?: unknown;\n /** Request timeout in milliseconds. Defaults to 5000ms. */\n timeoutMs?: number;\n /** Maximum retry attempts on 5xx / network errors. Defaults to 3. */\n maxRetries?: number;\n /** Base delay in milliseconds for exponential backoff. Defaults to 1000ms. */\n retryBaseMs?: number;\n /** Maximum backoff delay in milliseconds. Defaults to 30000ms. */\n retryMaxMs?: number;\n /** Optional AbortSignal for external cancellation. */\n signal?: AbortSignal;\n}\n\n/**\n * Result of a successful HTTP fetch.\n *\n * @template T The expected response body type.\n */\nexport interface HttpClientResult<T> {\n /** Parsed response body. */\n data: T;\n /** HTTP status code. */\n status: number;\n /** Response headers. */\n headers: Headers;\n}\n\n/**\n * Computes the exponential backoff sleep duration with full random jitter.\n * Formula: sleep = random(0, min(maxMs, baseMs * 2^attempt))\n *\n * @param attempt - Zero-based retry attempt index.\n * @param baseMs - Base delay in milliseconds.\n * @param maxMs - Maximum delay cap in milliseconds.\n * @returns Sleep duration in milliseconds.\n *\n * @example\n * const delay = computeBackoffMs(2, 1000, 30000); // ~0-4000ms\n */\nexport function computeBackoffMs(attempt: number, baseMs = 1000, maxMs = 30_000): number {\n const exponential = baseMs * Math.pow(2, attempt);\n const capped = Math.min(maxMs, exponential);\n // Full jitter: random value in [0, capped]\n return Math.random() * capped;\n}\n\n/**\n * Sleeps for the given number of milliseconds.\n *\n * @param ms - Delay in milliseconds.\n * @param signal - Optional AbortSignal to cancel the sleep.\n * @returns Promise that resolves after delay, or rejects if aborted.\n */\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new DOMException('Aborted', 'AbortError'));\n return;\n }\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener('abort', () => {\n clearTimeout(timer);\n reject(new DOMException('Aborted', 'AbortError'));\n });\n });\n}\n\n/**\n * Determines if an HTTP response status warrants a retry attempt.\n *\n * @param status - HTTP status code.\n * @returns `true` if the request should be retried.\n */\nfunction isRetryableStatus(status: number): boolean {\n return status >= 500 || status === 429;\n}\n\n/**\n * Performs a resilient HTTP fetch with exponential backoff and jitter.\n * Automatically retries on network failures and 5xx/429 responses.\n *\n * @template T The expected response body type.\n * @param options - Request configuration options.\n * @returns Promise resolving to {@link HttpClientResult}.\n * @throws {Error} When all retry attempts are exhausted or request is aborted.\n *\n * @example\n * const result = await fetchWithRetry<BatchFlagEvaluation>({\n * url: 'https://api.nexus.dev/api/v1/flags/eval',\n * method: 'GET',\n * headers: { Authorization: 'Bearer pk_live_...' },\n * timeoutMs: 3000,\n * maxRetries: 3,\n * });\n * console.log(result.data); // { checkout_v2: { enabled: true, ... } }\n */\nexport async function fetchWithRetry<T = unknown>(\n options: HttpClientOptions,\n): Promise<HttpClientResult<T>> {\n const {\n url,\n method = 'GET',\n headers = {},\n body,\n timeoutMs = 5_000,\n maxRetries = 3,\n retryBaseMs = 1_000,\n retryMaxMs = 30_000,\n signal: externalSignal,\n } = options;\n\n let lastError: Error = new Error('Request failed');\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n // Abort if external signal is already triggered\n if (externalSignal?.aborted) {\n throw new DOMException('Request aborted by caller.', 'AbortError');\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n // Merge external abort signal\n externalSignal?.addEventListener('abort', () => controller.abort());\n\n try {\n const requestInit: RequestInit = {\n method,\n headers: {\n 'Content-Type': 'application/json',\n ...headers,\n },\n signal: controller.signal,\n };\n\n if (body !== undefined) {\n requestInit.body = typeof body === 'string' ? body : JSON.stringify(body);\n }\n\n const response = await fetch(url, requestInit);\n clearTimeout(timeoutId);\n\n if (response.ok) {\n const data = (await response.json()) as T;\n return { data, status: response.status, headers: response.headers };\n }\n\n // Non-ok response: check if retryable\n if (isRetryableStatus(response.status) && attempt < maxRetries) {\n lastError = new Error(`HTTP ${response.status}: ${response.statusText}`);\n const delay = computeBackoffMs(attempt, retryBaseMs, retryMaxMs);\n await sleep(delay, externalSignal);\n continue;\n }\n\n // Non-retryable error (400, 401, 403, 404, etc.)\n const errorBody = await response.text().catch(() => '');\n throw new Error(`HTTP ${response.status}: ${errorBody}`);\n } catch (err) {\n clearTimeout(timeoutId);\n\n if (err instanceof DOMException && err.name === 'AbortError') {\n throw err; // Propagate abort without retry\n }\n\n lastError = err instanceof Error ? err : new Error(String(err));\n\n if (attempt < maxRetries) {\n const delay = computeBackoffMs(attempt, retryBaseMs, retryMaxMs);\n await sleep(delay, externalSignal);\n }\n }\n }\n\n throw lastError;\n}\n","/**\n * @fileoverview Fixed-capacity ring buffer with FIFO eviction under pressure.\n * Used by SDK Tracker for bounded breadcrumb and telemetry event storage.\n * @module @nexus/core/ring-buffer\n */\n\n/**\n * A fixed-capacity circular buffer that drops the oldest entry when full.\n * Never causes memory leaks or starvation of host applications.\n *\n * @template T The type of items stored in the buffer.\n *\n * @example\n * const buffer = new RingBuffer<string>(3);\n * buffer.push('a'); // [a]\n * buffer.push('b'); // [a, b]\n * buffer.push('c'); // [a, b, c]\n * buffer.push('d'); // [b, c, d] — 'a' evicted (FIFO)\n * buffer.toArray(); // ['b', 'c', 'd']\n */\nexport class RingBuffer<T> {\n private readonly capacity: number;\n private readonly buffer: Array<T | undefined>;\n private head = 0; // Points to the next write position\n private count = 0; // Current number of items\n\n /**\n * Creates a new RingBuffer with the given capacity.\n *\n * @param capacity - Maximum number of items to retain. Must be >= 1.\n * @throws {RangeError} If capacity is less than 1.\n *\n * @example\n * const breadcrumbBuffer = new RingBuffer<Breadcrumb>(20);\n */\n constructor(capacity: number) {\n if (capacity < 1) {\n throw new RangeError(`RingBuffer capacity must be >= 1, got ${capacity}`);\n }\n this.capacity = capacity;\n this.buffer = new Array<T | undefined>(capacity).fill(undefined);\n }\n\n /**\n * Appends an item to the buffer.\n * If the buffer is at capacity, the oldest item is silently dropped.\n *\n * @param item - The item to insert.\n *\n * @example\n * buffer.push({ timestamp: Date.now(), category: 'ui.click', message: 'Clicked #btn' });\n */\n push(item: T): void {\n this.buffer[this.head] = item;\n this.head = (this.head + 1) % this.capacity;\n if (this.count < this.capacity) {\n this.count++;\n }\n }\n\n /**\n * Returns all stored items in chronological order (oldest first).\n *\n * @returns Ordered array of stored items.\n *\n * @example\n * const breadcrumbs = buffer.toArray(); // [{...}, {...}]\n */\n toArray(): T[] {\n if (this.count === 0) return [];\n\n const result: T[] = [];\n if (this.count < this.capacity) {\n // Buffer not yet full — read from index 0 to head-1\n for (let i = 0; i < this.count; i++) {\n result.push(this.buffer[i] as T);\n }\n } else {\n // Buffer full — oldest item is at `head`\n for (let i = 0; i < this.capacity; i++) {\n result.push(this.buffer[(this.head + i) % this.capacity] as T);\n }\n }\n return result;\n }\n\n /**\n * Returns the current number of items in the buffer.\n *\n * @returns Item count (0 to capacity).\n *\n * @example\n * console.log(buffer.size); // 3\n */\n get size(): number {\n return this.count;\n }\n\n /**\n * Returns the maximum capacity of the buffer.\n *\n * @returns Buffer capacity.\n *\n * @example\n * console.log(buffer.maxCapacity); // 20\n */\n get maxCapacity(): number {\n return this.capacity;\n }\n\n /**\n * Checks if the buffer is currently at full capacity.\n *\n * @returns `true` if the buffer is full.\n *\n * @example\n * if (buffer.isFull) console.log('Oldest breadcrumb will be evicted on next push.');\n */\n get isFull(): boolean {\n return this.count === this.capacity;\n }\n\n /**\n * Removes all items from the buffer and resets internal state.\n *\n * @example\n * buffer.clear(); // buffer is now empty\n */\n clear(): void {\n this.buffer.fill(undefined);\n this.head = 0;\n this.count = 0;\n }\n\n /**\n * Peeks at the most recently added item without removing it.\n *\n * @returns The last item pushed, or `undefined` if empty.\n *\n * @example\n * const last = buffer.peek(); // most recent item\n */\n peek(): T | undefined {\n if (this.count === 0) return undefined;\n const lastIndex = (this.head - 1 + this.capacity) % this.capacity;\n return this.buffer[lastIndex];\n }\n}\n","/**\n * @fileoverview Universal API key resolver across all JavaScript runtimes.\n * Supports: Browser globals, Node.js process.env, Vite, Next.js, and Nuxt.\n * @module @nexus/core/env-resolver\n */\n\n/**\n * Resolution priority for API key lookup.\n * 1. Direct parameter passed into init options\n * 2. Browser global: window.__NEXUS_API_KEY__\n * 3. Node/Next.js: process.env.NEXUS_API_KEY\n * 4. Next.js client bundler: process.env.NEXT_PUBLIC_NEXUS_API_KEY\n * 5. Vite bundler: import.meta.env.VITE_NEXUS_API_KEY\n * 6. Nuxt: process.env.NUXT_PUBLIC_NEXUS_API_KEY\n */\n\n// Augment global types for browser injection\ndeclare global {\n interface Window {\n /** Browser-injected API key for Nexus SDK. */\n __NEXUS_API_KEY__?: string;\n }\n}\n\n/**\n * Safely reads an environment variable from process.env without throwing.\n *\n * @param key - The environment variable name.\n * @returns The value or `undefined`.\n */\nfunction readProcessEnv(key: string): string | undefined {\n try {\n const proc = (globalThis as unknown as { process?: { env?: Record<string, string | undefined> } }).process;\n if (typeof proc !== 'undefined' && proc && proc.env) {\n return proc.env[key] ?? undefined;\n }\n } catch {\n // process is not defined in pure browser environments\n }\n return undefined;\n}\n\n/**\n * Safely reads a Vite environment variable from import.meta.env.\n *\n * @param key - The Vite env variable name (VITE_* prefix required by Vite).\n * @returns The value or `undefined`.\n */\nfunction readViteEnv(key: string): string | undefined {\n try {\n const meta = typeof globalThis !== 'undefined' && (globalThis as unknown as { __import_meta__?: { env?: Record<string, string | undefined> } }).__import_meta__;\n if (meta && meta.env) {\n return meta.env[key] ?? undefined;\n }\n // Safe dynamic evaluation avoiding CJS compile-time import.meta error\n const dynamicMeta = new Function('try { return import.meta; } catch(e) { return undefined; }')() as { env?: Record<string, string | undefined> } | undefined;\n if (dynamicMeta && dynamicMeta.env) {\n return dynamicMeta.env[key] ?? undefined;\n }\n } catch {\n // Not a Vite runtime\n }\n return undefined;\n}\n\n/**\n * Safely reads the browser window global injection.\n *\n * @returns The window-injected API key or `undefined`.\n */\nfunction readWindowGlobal(): string | undefined {\n try {\n if (typeof window !== 'undefined' && window.__NEXUS_API_KEY__) {\n return window.__NEXUS_API_KEY__;\n }\n } catch {\n // window is not accessible (SSR/Worker context)\n }\n return undefined;\n}\n\n/**\n * Resolves the Nexus API key from the environment using a prioritized lookup chain.\n *\n * Resolution order:\n * 1. `directValue` — Parameter passed directly into SDK init options\n * 2. `window.__NEXUS_API_KEY__` — Browser global injection (CDN/script embed use cases)\n * 3. `process.env.NEXUS_API_KEY` — Node.js / Docker / CI environments\n * 4. `process.env.NEXT_PUBLIC_NEXUS_API_KEY` — Next.js client-side bundling\n * 5. `import.meta.env.VITE_NEXUS_API_KEY` — Vite / Vitest bundling\n * 6. `process.env.NUXT_PUBLIC_NEXUS_API_KEY` — Nuxt 3 public runtime config\n *\n * @param directValue - Explicitly provided API key (highest priority).\n * @returns The resolved API key string.\n * @throws {Error} If no API key can be resolved from any source.\n *\n * @example\n * // In a Next.js application:\n * // process.env.NEXT_PUBLIC_NEXUS_API_KEY = 'pk_live_abc123'\n * const key = resolveApiKey(); // 'pk_live_abc123'\n *\n * @example\n * // Passing directly (overrides all env vars):\n * const key = resolveApiKey('pk_live_directkey'); // 'pk_live_directkey'\n */\nexport function resolveApiKey(directValue?: string): string {\n const resolved =\n directValue ||\n readWindowGlobal() ||\n readProcessEnv('NEXUS_API_KEY') ||\n readProcessEnv('NEXT_PUBLIC_NEXUS_API_KEY') ||\n readViteEnv('VITE_NEXUS_API_KEY') ||\n readProcessEnv('NUXT_PUBLIC_NEXUS_API_KEY');\n\n if (!resolved || resolved.trim() === '') {\n throw new Error(\n '[Nexus SDK] No API key found. ' +\n 'Please provide one via:\\n' +\n ' 1. Nexus.init({ apiKey: \"pk_live_...\" })\\n' +\n ' 2. window.__NEXUS_API_KEY__ = \"pk_live_...\"\\n' +\n ' 3. NEXUS_API_KEY env var\\n' +\n ' 4. NEXT_PUBLIC_NEXUS_API_KEY (Next.js)\\n' +\n ' 5. VITE_NEXUS_API_KEY (Vite)\\n' +\n ' 6. NUXT_PUBLIC_NEXUS_API_KEY (Nuxt)',\n );\n }\n\n return resolved.trim();\n}\n\n/**\n * Resolves the Nexus base URL from configuration or environment variables.\n *\n * @param directValue - Explicitly provided base URL.\n * @param defaultUrl - Default URL if no env var is found.\n * @returns The resolved base URL string (trailing slash stripped).\n *\n * @example\n * // Resolves to http://localhost:8080 in development\n * const url = resolveBaseUrl(undefined, 'https://api.nexus.dev');\n */\nexport function resolveBaseUrl(\n directValue?: string,\n defaultUrl = 'https://api.nexus.dev',\n): string {\n const resolved =\n directValue ||\n readProcessEnv('NEXT_PUBLIC_NEXUS_URL') ||\n readViteEnv('VITE_NEXUS_URL') ||\n defaultUrl;\n\n return resolved.replace(/\\/$/, '');\n}\n","/**\n * @fileoverview Circular-reference-safe JSON serializer using WeakSet tracking.\n * Prevents TypeError crashes when serializing objects with circular references.\n * @module @nexus/core/safe-json\n */\n\n/**\n * Recursively sanitizes an object for JSON serialization by replacing\n * circular references with the string \"[Circular]\".\n *\n * @param value - The value to sanitize.\n * @param seen - WeakSet tracking visited objects (used internally for recursion).\n * @param depth - Current recursion depth.\n * @param maxDepth - Maximum allowed recursion depth.\n * @returns A serialization-safe copy of the value.\n */\nfunction sanitizeForSerialization(\n value: unknown,\n seen: WeakSet<object>,\n depth: number,\n maxDepth: number,\n): unknown {\n if (depth > maxDepth) {\n return '[MaxDepthExceeded]';\n }\n\n if (value === null || value === undefined) {\n return value;\n }\n\n if (typeof value !== 'object' && typeof value !== 'function') {\n // Primitive value: string, number, boolean, bigint, symbol\n if (typeof value === 'bigint') {\n return value.toString(); // JSON cannot handle BigInt natively\n }\n if (typeof value === 'symbol') {\n return value.toString();\n }\n if (typeof value === 'function') {\n return '[Function]';\n }\n return value;\n }\n\n // Handle Error objects specially — preserve message and stack\n if (value instanceof Error) {\n return {\n name: value.name,\n message: value.message,\n stack: value.stack,\n };\n }\n\n // Circular reference detection\n if (seen.has(value as object)) {\n return '[Circular]';\n }\n seen.add(value as object);\n\n // Handle Arrays\n if (Array.isArray(value)) {\n const result = value.map((item) =>\n sanitizeForSerialization(item, seen, depth + 1, maxDepth),\n );\n seen.delete(value as object);\n return result;\n }\n\n // Handle plain Objects\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(value as object)) {\n const propValue = (value as Record<string, unknown>)[key];\n result[key] = sanitizeForSerialization(propValue, seen, depth + 1, maxDepth);\n }\n seen.delete(value as object);\n return result;\n}\n\n/**\n * Serializes a value to a JSON string, gracefully handling:\n * - Circular references (replaced with \"[Circular]\")\n * - BigInt values (converted to string)\n * - Symbol values (converted to string)\n * - Function values (replaced with \"[Function]\")\n * - Deep nesting (capped at maxDepth, replaced with \"[MaxDepthExceeded]\")\n *\n * @param value - Any value to serialize.\n * @param maxDepth - Maximum recursion depth. Defaults to 8.\n * @returns JSON string representation of the value.\n *\n * @example\n * const obj: Record<string, unknown> = { name: 'test' };\n * obj['self'] = obj; // circular reference\n * const json = safeStringify(obj);\n * // '{\"name\":\"test\",\"self\":\"[Circular]\"}'\n *\n * @example\n * const err = new Error('Something failed');\n * const json = safeStringify({ error: err, code: 500 });\n * // '{\"error\":{\"name\":\"Error\",\"message\":\"Something failed\",\"stack\":\"...\"},\"code\":500}'\n */\nexport function safeStringify(value: unknown, maxDepth = 8): string {\n const seen = new WeakSet<object>();\n const sanitized = sanitizeForSerialization(value, seen, 0, maxDepth);\n try {\n return JSON.stringify(sanitized);\n } catch {\n // Absolute last resort fallback\n return JSON.stringify({ error: '[SerializationFailed]' });\n }\n}\n\n/**\n * Safely parses a JSON string without throwing on invalid input.\n *\n * @param input - The JSON string to parse.\n * @param fallback - Value to return if parsing fails. Defaults to `null`.\n * @returns Parsed value or fallback.\n *\n * @example\n * const data = safeParse<{ id: string }>('{\"id\":\"123\"}');\n * // { id: '123' }\n *\n * @example\n * const data = safeParse<unknown>('{{invalid json}}', null);\n * // null\n */\nexport function safeParse<T = unknown>(input: string, fallback: T | null = null): T | null {\n try {\n return JSON.parse(input) as T;\n } catch {\n return fallback;\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@nexussdk/core",
3
+ "version": "0.0.1",
4
+ "private": false,
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "description": "Foundation transport, ring buffer, and resilience micro-kernels for the Nexus SDK ecosystem",
9
+ "main": "./dist/index.cjs",
10
+ "module": "./dist/index.mjs",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.mjs",
16
+ "require": "./dist/index.cjs"
17
+ }
18
+ },
19
+ "dependencies": {
20
+ "@nexussdk/contracts": "0.0.1"
21
+ },
22
+ "devDependencies": {
23
+ "tsup": "^8.0.2",
24
+ "typescript": "^5.4.5",
25
+ "rimraf": "^5.0.5"
26
+ },
27
+ "scripts": {
28
+ "build": "tsup",
29
+ "dev": "tsup --watch",
30
+ "lint": "tsc --noEmit",
31
+ "clean": "rimraf dist"
32
+ }
33
+ }
@@ -0,0 +1,153 @@
1
+ /**
2
+ * @fileoverview Universal API key resolver across all JavaScript runtimes.
3
+ * Supports: Browser globals, Node.js process.env, Vite, Next.js, and Nuxt.
4
+ * @module @nexus/core/env-resolver
5
+ */
6
+
7
+ /**
8
+ * Resolution priority for API key lookup.
9
+ * 1. Direct parameter passed into init options
10
+ * 2. Browser global: window.__NEXUS_API_KEY__
11
+ * 3. Node/Next.js: process.env.NEXUS_API_KEY
12
+ * 4. Next.js client bundler: process.env.NEXT_PUBLIC_NEXUS_API_KEY
13
+ * 5. Vite bundler: import.meta.env.VITE_NEXUS_API_KEY
14
+ * 6. Nuxt: process.env.NUXT_PUBLIC_NEXUS_API_KEY
15
+ */
16
+
17
+ // Augment global types for browser injection
18
+ declare global {
19
+ interface Window {
20
+ /** Browser-injected API key for Nexus SDK. */
21
+ __NEXUS_API_KEY__?: string;
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Safely reads an environment variable from process.env without throwing.
27
+ *
28
+ * @param key - The environment variable name.
29
+ * @returns The value or `undefined`.
30
+ */
31
+ function readProcessEnv(key: string): string | undefined {
32
+ try {
33
+ const proc = (globalThis as unknown as { process?: { env?: Record<string, string | undefined> } }).process;
34
+ if (typeof proc !== 'undefined' && proc && proc.env) {
35
+ return proc.env[key] ?? undefined;
36
+ }
37
+ } catch {
38
+ // process is not defined in pure browser environments
39
+ }
40
+ return undefined;
41
+ }
42
+
43
+ /**
44
+ * Safely reads a Vite environment variable from import.meta.env.
45
+ *
46
+ * @param key - The Vite env variable name (VITE_* prefix required by Vite).
47
+ * @returns The value or `undefined`.
48
+ */
49
+ function readViteEnv(key: string): string | undefined {
50
+ try {
51
+ const meta = typeof globalThis !== 'undefined' && (globalThis as unknown as { __import_meta__?: { env?: Record<string, string | undefined> } }).__import_meta__;
52
+ if (meta && meta.env) {
53
+ return meta.env[key] ?? undefined;
54
+ }
55
+ // Safe dynamic evaluation avoiding CJS compile-time import.meta error
56
+ const dynamicMeta = new Function('try { return import.meta; } catch(e) { return undefined; }')() as { env?: Record<string, string | undefined> } | undefined;
57
+ if (dynamicMeta && dynamicMeta.env) {
58
+ return dynamicMeta.env[key] ?? undefined;
59
+ }
60
+ } catch {
61
+ // Not a Vite runtime
62
+ }
63
+ return undefined;
64
+ }
65
+
66
+ /**
67
+ * Safely reads the browser window global injection.
68
+ *
69
+ * @returns The window-injected API key or `undefined`.
70
+ */
71
+ function readWindowGlobal(): string | undefined {
72
+ try {
73
+ if (typeof window !== 'undefined' && window.__NEXUS_API_KEY__) {
74
+ return window.__NEXUS_API_KEY__;
75
+ }
76
+ } catch {
77
+ // window is not accessible (SSR/Worker context)
78
+ }
79
+ return undefined;
80
+ }
81
+
82
+ /**
83
+ * Resolves the Nexus API key from the environment using a prioritized lookup chain.
84
+ *
85
+ * Resolution order:
86
+ * 1. `directValue` — Parameter passed directly into SDK init options
87
+ * 2. `window.__NEXUS_API_KEY__` — Browser global injection (CDN/script embed use cases)
88
+ * 3. `process.env.NEXUS_API_KEY` — Node.js / Docker / CI environments
89
+ * 4. `process.env.NEXT_PUBLIC_NEXUS_API_KEY` — Next.js client-side bundling
90
+ * 5. `import.meta.env.VITE_NEXUS_API_KEY` — Vite / Vitest bundling
91
+ * 6. `process.env.NUXT_PUBLIC_NEXUS_API_KEY` — Nuxt 3 public runtime config
92
+ *
93
+ * @param directValue - Explicitly provided API key (highest priority).
94
+ * @returns The resolved API key string.
95
+ * @throws {Error} If no API key can be resolved from any source.
96
+ *
97
+ * @example
98
+ * // In a Next.js application:
99
+ * // process.env.NEXT_PUBLIC_NEXUS_API_KEY = 'pk_live_abc123'
100
+ * const key = resolveApiKey(); // 'pk_live_abc123'
101
+ *
102
+ * @example
103
+ * // Passing directly (overrides all env vars):
104
+ * const key = resolveApiKey('pk_live_directkey'); // 'pk_live_directkey'
105
+ */
106
+ export function resolveApiKey(directValue?: string): string {
107
+ const resolved =
108
+ directValue ||
109
+ readWindowGlobal() ||
110
+ readProcessEnv('NEXUS_API_KEY') ||
111
+ readProcessEnv('NEXT_PUBLIC_NEXUS_API_KEY') ||
112
+ readViteEnv('VITE_NEXUS_API_KEY') ||
113
+ readProcessEnv('NUXT_PUBLIC_NEXUS_API_KEY');
114
+
115
+ if (!resolved || resolved.trim() === '') {
116
+ throw new Error(
117
+ '[Nexus SDK] No API key found. ' +
118
+ 'Please provide one via:\n' +
119
+ ' 1. Nexus.init({ apiKey: "pk_live_..." })\n' +
120
+ ' 2. window.__NEXUS_API_KEY__ = "pk_live_..."\n' +
121
+ ' 3. NEXUS_API_KEY env var\n' +
122
+ ' 4. NEXT_PUBLIC_NEXUS_API_KEY (Next.js)\n' +
123
+ ' 5. VITE_NEXUS_API_KEY (Vite)\n' +
124
+ ' 6. NUXT_PUBLIC_NEXUS_API_KEY (Nuxt)',
125
+ );
126
+ }
127
+
128
+ return resolved.trim();
129
+ }
130
+
131
+ /**
132
+ * Resolves the Nexus base URL from configuration or environment variables.
133
+ *
134
+ * @param directValue - Explicitly provided base URL.
135
+ * @param defaultUrl - Default URL if no env var is found.
136
+ * @returns The resolved base URL string (trailing slash stripped).
137
+ *
138
+ * @example
139
+ * // Resolves to http://localhost:8080 in development
140
+ * const url = resolveBaseUrl(undefined, 'https://api.nexus.dev');
141
+ */
142
+ export function resolveBaseUrl(
143
+ directValue?: string,
144
+ defaultUrl = 'https://api.nexus.dev',
145
+ ): string {
146
+ const resolved =
147
+ directValue ||
148
+ readProcessEnv('NEXT_PUBLIC_NEXUS_URL') ||
149
+ readViteEnv('VITE_NEXUS_URL') ||
150
+ defaultUrl;
151
+
152
+ return resolved.replace(/\/$/, '');
153
+ }