@retrivora-ai/rag-engine 2.2.5 → 2.2.7

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,146 @@
1
+ import { SDK_VERSION } from '../version';
2
+ import { SDKVersionUnsupportedError, LicenseValidationError } from '../exceptions';
3
+
4
+ export interface LicenseValidationRequest {
5
+ licenseKey: string;
6
+ projectId?: string;
7
+ sdkVersion?: string;
8
+ sdk?: string;
9
+ platform?: string;
10
+ retrivoraApiBase?: string;
11
+ headers?: Record<string, string>;
12
+ }
13
+
14
+ export interface LicenseValidationResponse {
15
+ valid: boolean;
16
+ licenseStatus: 'ACTIVE' | 'SUSPENDED' | 'TERMINATED' | 'EXPIRED' | 'REVOKED';
17
+ minimumSupportedVersion: string;
18
+ latestVersion: string;
19
+ forceUpgrade: boolean;
20
+ expiresAt: string | null;
21
+ message: string;
22
+ }
23
+
24
+ interface CachedSuccessValidation {
25
+ response: LicenseValidationResponse;
26
+ timestamp: number;
27
+ }
28
+
29
+ export class LicenseValidator {
30
+ private static instance: LicenseValidator;
31
+ private cache: Map<string, CachedSuccessValidation> = new Map();
32
+ private static readonly SUCCESS_CACHE_TTL_MS = 5 * 60 * 1000; // 5-minute TTL for successful validation only
33
+
34
+ private constructor() {}
35
+
36
+ public static getInstance(): LicenseValidator {
37
+ if (!LicenseValidator.instance) {
38
+ LicenseValidator.instance = new LicenseValidator();
39
+ }
40
+ return LicenseValidator.instance;
41
+ }
42
+
43
+ /**
44
+ * Invalidate local validation cache for a license key
45
+ */
46
+ public purgeCache(licenseKey?: string): void {
47
+ if (licenseKey) {
48
+ this.cache.delete(licenseKey);
49
+ } else {
50
+ this.cache.clear();
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Validate license status and SDK version against Retrivora License Service
56
+ */
57
+ public async validate(request: LicenseValidationRequest): Promise<LicenseValidationResponse> {
58
+ const {
59
+ licenseKey,
60
+ projectId,
61
+ sdkVersion = SDK_VERSION,
62
+ sdk = '@retrivora-ai/rag-engine',
63
+ platform = typeof window !== 'undefined' ? 'browser' : 'node',
64
+ retrivoraApiBase,
65
+ headers: customHeaders,
66
+ } = request;
67
+
68
+ if (!licenseKey) {
69
+ this.purgeCache(licenseKey);
70
+ throw new LicenseValidationError('Missing RETRIVORA_LICENSE_KEY in request.');
71
+ }
72
+
73
+ // 1. Check successful 5-minute cache
74
+ const cached = this.cache.get(licenseKey);
75
+ if (cached) {
76
+ const isFresh = Date.now() - cached.timestamp < LicenseValidator.SUCCESS_CACHE_TTL_MS;
77
+ if (isFresh && cached.response.valid && cached.response.licenseStatus === 'ACTIVE' && !cached.response.forceUpgrade) {
78
+ return cached.response;
79
+ } else {
80
+ this.cache.delete(licenseKey);
81
+ }
82
+ }
83
+
84
+ // 2. Fetch live validation from Retrivora backend API
85
+ const base = retrivoraApiBase || (typeof process !== 'undefined' ? process.env.NEXT_PUBLIC_RETRIVORA_API_BASE || process.env.RETRIVORA_API_BASE : '') || '/api/retrivora';
86
+ const validateUrl = `${base.replace(/\/$/, '')}/v1/license/validate`;
87
+
88
+ let res: Response;
89
+ try {
90
+ res = await fetch(validateUrl, {
91
+ method: 'POST',
92
+ cache: 'no-store',
93
+ headers: {
94
+ 'Content-Type': 'application/json',
95
+ 'x-license-key': licenseKey,
96
+ 'x-sdk-version': sdkVersion,
97
+ ...(customHeaders || {}),
98
+ },
99
+ body: JSON.stringify({
100
+ licenseKey,
101
+ projectId,
102
+ sdkVersion,
103
+ sdk,
104
+ platform,
105
+ }),
106
+ });
107
+ } catch (err: any) {
108
+ // Fallback: If network error occurs, do not cache and throw error
109
+ this.purgeCache(licenseKey);
110
+ throw new LicenseValidationError(`License validation request failed: ${err?.message || 'Network error'}`);
111
+ }
112
+
113
+ const data: LicenseValidationResponse = await res.json().catch(() => ({
114
+ valid: false,
115
+ licenseStatus: 'REVOKED' as const,
116
+ minimumSupportedVersion: '2.1.0',
117
+ latestVersion: SDK_VERSION,
118
+ forceUpgrade: false,
119
+ expiresAt: null,
120
+ message: 'Failed to parse license validation response.',
121
+ }));
122
+
123
+ // 3. Negative validation responses immediately purge any existing cache
124
+ if (!res.ok || !data.valid || data.licenseStatus !== 'ACTIVE' || data.forceUpgrade) {
125
+ this.purgeCache(licenseKey);
126
+
127
+ if (data.forceUpgrade || res.status === 426) {
128
+ throw new SDKVersionUnsupportedError(
129
+ data.message || `This SDK version (${sdkVersion}) is no longer supported. Upgrade to version ${data.latestVersion || '2.2.6'} or later.`,
130
+ data
131
+ );
132
+ }
133
+
134
+ const errMsg = data.message || `License validation failed with status: ${data.licenseStatus}. Access denied.`;
135
+ throw new LicenseValidationError(errMsg, data);
136
+ }
137
+
138
+ // 4. Cache SUCCESSFUL validation only (TTL = 5 minutes)
139
+ this.cache.set(licenseKey, {
140
+ response: data,
141
+ timestamp: Date.now(),
142
+ });
143
+
144
+ return data;
145
+ }
146
+ }
@@ -8,7 +8,9 @@ export type RetrivoraErrorCode =
8
8
  | 'RETRIEVAL_FAILED'
9
9
  | 'RATE_LIMITED'
10
10
  | 'CONFIGURATION_ERROR'
11
- | 'AUTHENTICATION_ERROR';
11
+ | 'AUTHENTICATION_ERROR'
12
+ | 'SDK_VERSION_UNSUPPORTED'
13
+ | 'LICENSE_VALIDATION_ERROR';
12
14
 
13
15
  export class RetrivoraError extends Error {
14
16
  readonly code: RetrivoraErrorCode;
@@ -58,6 +60,18 @@ export class AuthenticationException extends RetrivoraError {
58
60
  }
59
61
  }
60
62
 
63
+ export class SDKVersionUnsupportedError extends RetrivoraError {
64
+ constructor(message = 'This SDK version is no longer supported.', details?: unknown) {
65
+ super(message, 'SDK_VERSION_UNSUPPORTED', details);
66
+ }
67
+ }
68
+
69
+ export class LicenseValidationError extends RetrivoraError {
70
+ constructor(message = 'License validation failed.', details?: unknown) {
71
+ super(message, 'LICENSE_VALIDATION_ERROR', details);
72
+ }
73
+ }
74
+
61
75
  /**
62
76
  * Wraps any unknown error into an appropriate RetrivoraError subclass.
63
77
  */
@@ -80,14 +94,20 @@ export function wrapError(
80
94
  return new RateLimitException(message, err);
81
95
  }
82
96
 
83
- // 2. Authentication Recognition
97
+ // 2. Authentication / License Recognition
84
98
  if (
85
99
  status === 401 ||
86
100
  status === 403 ||
87
- /unauthorized|auth|api[- ]?key/i.test(message) ||
88
- code === 'INVALID_API_KEY'
101
+ /unauthorized|auth|license|revoked|terminated|api[- ]?key/i.test(message) ||
102
+ code === 'INVALID_API_KEY' ||
103
+ code === 'LICENSE_DELETED' ||
104
+ code === 'LICENSE_INACTIVE'
89
105
  ) {
90
- return new AuthenticationException(message, err);
106
+ const isLicenseError = /403|license|revoked|terminated/i.test(message) || status === 403;
107
+ const cleanMsg = isLicenseError
108
+ ? 'Your Retrivora license key has been terminated, suspended, or revoked. Access denied.'
109
+ : message;
110
+ return new AuthenticationException(cleanMsg, err);
91
111
  }
92
112
 
93
113
  // Fallback Mapping based on defaultCode
@@ -492,8 +492,18 @@ export function createStreamHandler(
492
492
  }
493
493
  } catch (streamError) {
494
494
  if (isActive) {
495
- const errorMessage =
495
+ let errorMessage =
496
496
  streamError instanceof Error ? streamError.message : String(streamError);
497
+
498
+ if (
499
+ errorMessage.includes('403') ||
500
+ errorMessage.toLowerCase().includes('license') ||
501
+ errorMessage.toLowerCase().includes('revoked') ||
502
+ errorMessage.toLowerCase().includes('terminated')
503
+ ) {
504
+ errorMessage = 'Your Retrivora license key has been terminated, suspended, or revoked. Access denied.';
505
+ }
506
+
497
507
  console.error('[createStreamHandler] Stream error:', streamError);
498
508
  reportTelemetry(req, plugin, 'QUERY_GENERATION', 'error', errorMessage);
499
509
  try {
@@ -154,7 +154,15 @@ export function useRagChat(
154
154
 
155
155
  if (!response.ok) {
156
156
  const errorData = await response.json().catch(() => ({}));
157
- throw new Error(errorData.error || `Server returned ${response.status}`);
157
+ if (
158
+ response.status === 403 ||
159
+ response.status === 401 ||
160
+ (typeof errorData.error === 'string' && errorData.error.toLowerCase().includes('license')) ||
161
+ errorData.error?.type === 'license_revoked'
162
+ ) {
163
+ throw new Error('Your Retrivora license is no longer valid. Please contact your administrator or obtain a valid license key.');
164
+ }
165
+ throw new Error(errorData.error?.message || errorData.error || `Server returned ${response.status}`);
158
166
  }
159
167
 
160
168
  if (!response.body) {
@@ -284,7 +292,15 @@ export function useRagChat(
284
292
  console.log('[useRagChat] Streaming stopped by user.');
285
293
  return;
286
294
  }
287
- const msg = err instanceof Error ? err.message : 'Something went wrong. Please try again.';
295
+ let msg = err instanceof Error ? err.message : 'Something went wrong. Please try again.';
296
+ if (
297
+ msg.includes('403') ||
298
+ msg.toLowerCase().includes('license') ||
299
+ msg.toLowerCase().includes('revoked') ||
300
+ msg.toLowerCase().includes('terminated')
301
+ ) {
302
+ msg = 'Your Retrivora license key has been terminated, suspended, or revoked. Access denied.';
303
+ }
288
304
  setError(msg);
289
305
  onError?.(msg);
290
306
  } finally {
package/src/index.css CHANGED
@@ -96,6 +96,9 @@
96
96
  --color-rose-400: oklch(71.2% 0.194 13.428);
97
97
  --color-rose-500: oklch(64.5% 0.246 16.439);
98
98
  --color-rose-600: oklch(58.6% 0.253 17.585);
99
+ --color-rose-800: oklch(45.5% 0.188 13.697);
100
+ --color-rose-900: oklch(41% 0.159 10.272);
101
+ --color-rose-950: oklch(27.1% 0.105 12.094);
99
102
  --color-slate-50: oklch(98.4% 0.003 247.858);
100
103
  --color-slate-100: oklch(96.8% 0.007 247.896);
101
104
  --color-slate-200: oklch(92.9% 0.013 255.508);
@@ -370,6 +373,9 @@
370
373
  .bottom-20 {
371
374
  bottom: calc(var(--spacing) * 20);
372
375
  }
376
+ .bottom-24 {
377
+ bottom: calc(var(--spacing) * 24);
378
+ }
373
379
  .-left-4 {
374
380
  left: calc(var(--spacing) * -4);
375
381
  }
@@ -451,6 +457,9 @@
451
457
  .my-6 {
452
458
  margin-block: calc(var(--spacing) * 6);
453
459
  }
460
+ .my-auto {
461
+ margin-block: auto;
462
+ }
454
463
  .mt-0\.5 {
455
464
  margin-top: calc(var(--spacing) * 0.5);
456
465
  }
@@ -1121,6 +1130,12 @@
1121
1130
  .border-amber-200 {
1122
1131
  border-color: var(--color-amber-200);
1123
1132
  }
1133
+ .border-amber-200\/80 {
1134
+ border-color: color-mix(in srgb, oklch(92.4% 0.12 95.746) 80%, transparent);
1135
+ @supports (color: color-mix(in lab, red, red)) {
1136
+ border-color: color-mix(in oklab, var(--color-amber-200) 80%, transparent);
1137
+ }
1138
+ }
1124
1139
  .border-emerald-100 {
1125
1140
  border-color: var(--color-emerald-100);
1126
1141
  }
@@ -1157,6 +1172,12 @@
1157
1172
  .border-rose-200 {
1158
1173
  border-color: var(--color-rose-200);
1159
1174
  }
1175
+ .border-rose-200\/60 {
1176
+ border-color: color-mix(in srgb, oklch(89.2% 0.058 10.001) 60%, transparent);
1177
+ @supports (color: color-mix(in lab, red, red)) {
1178
+ border-color: color-mix(in oklab, var(--color-rose-200) 60%, transparent);
1179
+ }
1180
+ }
1160
1181
  .border-slate-100 {
1161
1182
  border-color: var(--color-slate-100);
1162
1183
  }
@@ -1217,6 +1238,12 @@
1217
1238
  .bg-amber-50 {
1218
1239
  background-color: var(--color-amber-50);
1219
1240
  }
1241
+ .bg-amber-50\/70 {
1242
+ background-color: color-mix(in srgb, oklch(98.7% 0.022 95.277) 70%, transparent);
1243
+ @supports (color: color-mix(in lab, red, red)) {
1244
+ background-color: color-mix(in oklab, var(--color-amber-50) 70%, transparent);
1245
+ }
1246
+ }
1220
1247
  .bg-amber-100 {
1221
1248
  background-color: var(--color-amber-100);
1222
1249
  }
@@ -1322,6 +1349,15 @@
1322
1349
  .bg-rose-50 {
1323
1350
  background-color: var(--color-rose-50);
1324
1351
  }
1352
+ .bg-rose-50\/50 {
1353
+ background-color: color-mix(in srgb, oklch(96.9% 0.015 12.422) 50%, transparent);
1354
+ @supports (color: color-mix(in lab, red, red)) {
1355
+ background-color: color-mix(in oklab, var(--color-rose-50) 50%, transparent);
1356
+ }
1357
+ }
1358
+ .bg-rose-100 {
1359
+ background-color: var(--color-rose-100);
1360
+ }
1325
1361
  .bg-rose-500 {
1326
1362
  background-color: var(--color-rose-500);
1327
1363
  }
@@ -1409,6 +1445,12 @@
1409
1445
  background-color: color-mix(in oklab, var(--color-white) 90%, transparent);
1410
1446
  }
1411
1447
  }
1448
+ .bg-white\/95 {
1449
+ background-color: color-mix(in srgb, #fff 95%, transparent);
1450
+ @supports (color: color-mix(in lab, red, red)) {
1451
+ background-color: color-mix(in oklab, var(--color-white) 95%, transparent);
1452
+ }
1453
+ }
1412
1454
  .bg-gradient-to-br {
1413
1455
  --tw-gradient-position: to bottom right in oklab;
1414
1456
  background-image: linear-gradient(var(--tw-gradient-stops));
@@ -1619,6 +1661,9 @@
1619
1661
  .whitespace-pre-wrap {
1620
1662
  white-space: pre-wrap;
1621
1663
  }
1664
+ .text-amber-300 {
1665
+ color: var(--color-amber-300);
1666
+ }
1622
1667
  .text-amber-400 {
1623
1668
  color: var(--color-amber-400);
1624
1669
  }
@@ -1950,6 +1995,10 @@
1950
1995
  --tw-outline-style: none;
1951
1996
  outline-style: none;
1952
1997
  }
1998
+ .select-all {
1999
+ -webkit-user-select: all;
2000
+ user-select: all;
2001
+ }
1953
2002
  .select-none {
1954
2003
  -webkit-user-select: none;
1955
2004
  user-select: none;
@@ -2391,6 +2440,14 @@
2391
2440
  }
2392
2441
  }
2393
2442
  }
2443
+ .dark\:border-amber-800\/40 {
2444
+ @media (prefers-color-scheme: dark) {
2445
+ border-color: color-mix(in srgb, oklch(47.3% 0.137 46.201) 40%, transparent);
2446
+ @supports (color: color-mix(in lab, red, red)) {
2447
+ border-color: color-mix(in oklab, var(--color-amber-800) 40%, transparent);
2448
+ }
2449
+ }
2450
+ }
2394
2451
  .dark\:border-emerald-400\/20 {
2395
2452
  @media (prefers-color-scheme: dark) {
2396
2453
  border-color: color-mix(in srgb, oklch(76.5% 0.177 163.223) 20%, transparent);
@@ -2463,6 +2520,22 @@
2463
2520
  }
2464
2521
  }
2465
2522
  }
2523
+ .dark\:border-rose-800\/40 {
2524
+ @media (prefers-color-scheme: dark) {
2525
+ border-color: color-mix(in srgb, oklch(45.5% 0.188 13.697) 40%, transparent);
2526
+ @supports (color: color-mix(in lab, red, red)) {
2527
+ border-color: color-mix(in oklab, var(--color-rose-800) 40%, transparent);
2528
+ }
2529
+ }
2530
+ }
2531
+ .dark\:border-rose-900\/60 {
2532
+ @media (prefers-color-scheme: dark) {
2533
+ border-color: color-mix(in srgb, oklch(41% 0.159 10.272) 60%, transparent);
2534
+ @supports (color: color-mix(in lab, red, red)) {
2535
+ border-color: color-mix(in oklab, var(--color-rose-900) 60%, transparent);
2536
+ }
2537
+ }
2538
+ }
2466
2539
  .dark\:border-slate-700 {
2467
2540
  @media (prefers-color-scheme: dark) {
2468
2541
  border-color: var(--color-slate-700);
@@ -2515,6 +2588,11 @@
2515
2588
  background-color: color-mix(in oklab, #0f0f1a 90%, transparent);
2516
2589
  }
2517
2590
  }
2591
+ .dark\:bg-\[\#0f0f1a\]\/95 {
2592
+ @media (prefers-color-scheme: dark) {
2593
+ background-color: color-mix(in oklab, #0f0f1a 95%, transparent);
2594
+ }
2595
+ }
2518
2596
  .dark\:bg-\[\#080811\] {
2519
2597
  @media (prefers-color-scheme: dark) {
2520
2598
  background-color: #080811;
@@ -2560,6 +2638,22 @@
2560
2638
  }
2561
2639
  }
2562
2640
  }
2641
+ .dark\:bg-amber-900\/40 {
2642
+ @media (prefers-color-scheme: dark) {
2643
+ background-color: color-mix(in srgb, oklch(41.4% 0.112 45.904) 40%, transparent);
2644
+ @supports (color: color-mix(in lab, red, red)) {
2645
+ background-color: color-mix(in oklab, var(--color-amber-900) 40%, transparent);
2646
+ }
2647
+ }
2648
+ }
2649
+ .dark\:bg-amber-950\/20 {
2650
+ @media (prefers-color-scheme: dark) {
2651
+ background-color: color-mix(in srgb, oklch(27.9% 0.077 45.635) 20%, transparent);
2652
+ @supports (color: color-mix(in lab, red, red)) {
2653
+ background-color: color-mix(in oklab, var(--color-amber-950) 20%, transparent);
2654
+ }
2655
+ }
2656
+ }
2563
2657
  .dark\:bg-amber-950\/40 {
2564
2658
  @media (prefers-color-scheme: dark) {
2565
2659
  background-color: color-mix(in srgb, oklch(27.9% 0.077 45.635) 40%, transparent);
@@ -2826,6 +2920,22 @@
2826
2920
  }
2827
2921
  }
2828
2922
  }
2923
+ .dark\:bg-rose-900\/40 {
2924
+ @media (prefers-color-scheme: dark) {
2925
+ background-color: color-mix(in srgb, oklch(41% 0.159 10.272) 40%, transparent);
2926
+ @supports (color: color-mix(in lab, red, red)) {
2927
+ background-color: color-mix(in oklab, var(--color-rose-900) 40%, transparent);
2928
+ }
2929
+ }
2930
+ }
2931
+ .dark\:bg-rose-950\/20 {
2932
+ @media (prefers-color-scheme: dark) {
2933
+ background-color: color-mix(in srgb, oklch(27.1% 0.105 12.094) 20%, transparent);
2934
+ @supports (color: color-mix(in lab, red, red)) {
2935
+ background-color: color-mix(in oklab, var(--color-rose-950) 20%, transparent);
2936
+ }
2937
+ }
2938
+ }
2829
2939
  .dark\:bg-slate-700 {
2830
2940
  @media (prefers-color-scheme: dark) {
2831
2941
  background-color: var(--color-slate-700);
@@ -3372,6 +3482,15 @@
3372
3482
  }
3373
3483
  }
3374
3484
  }
3485
+ .dark\:hover\:text-white {
3486
+ @media (prefers-color-scheme: dark) {
3487
+ &:hover {
3488
+ @media (hover: hover) {
3489
+ color: var(--color-white);
3490
+ }
3491
+ }
3492
+ }
3493
+ }
3375
3494
  .dark\:hover\:text-white\/60 {
3376
3495
  @media (prefers-color-scheme: dark) {
3377
3496
  &:hover {
package/src/index.ts CHANGED
@@ -63,6 +63,8 @@ export type { ILLMProvider, EmbedOptions } from './llm/ILLMProvider';
63
63
  export type { Chunk, ChunkOptions } from './rag/DocumentChunker';
64
64
  export type { Retrivora } from './core/Retrivora';
65
65
  export type { RetrivoraErrorCode } from './exceptions';
66
+ export { LicenseValidator } from './core/LicenseValidator';
67
+ export type { LicenseValidationRequest, LicenseValidationResponse } from './core/LicenseValidator';
66
68
  export {
67
69
  RetrivoraError,
68
70
  ProviderNotFoundException,
@@ -71,6 +73,8 @@ export {
71
73
  RateLimitException,
72
74
  ConfigurationException,
73
75
  AuthenticationException,
76
+ SDKVersionUnsupportedError,
77
+ LicenseValidationError,
74
78
  } from './exceptions';
75
79
  export type {
76
80
  VectorMatch,
package/src/server.ts CHANGED
@@ -90,6 +90,10 @@ export {
90
90
  sseErrorFrame,
91
91
  } from './handlers';
92
92
 
93
+ // ── License Validator ─────────────────────────────────────────
94
+ export { LicenseValidator } from './core/LicenseValidator';
95
+ export type { LicenseValidationRequest, LicenseValidationResponse } from './core/LicenseValidator';
96
+
93
97
  // ── Exceptions ────────────────────────────────────────────────
94
98
  export type { RetrivoraErrorCode } from './exceptions';
95
99
  export {
@@ -100,6 +104,8 @@ export {
100
104
  RateLimitException,
101
105
  ConfigurationException,
102
106
  AuthenticationException,
107
+ SDKVersionUnsupportedError,
108
+ LicenseValidationError,
103
109
  wrapError,
104
110
  } from './exceptions';
105
111