@lanonasis/memory-client 2.0.0 → 2.1.0

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.
@@ -1,5 +1,221 @@
1
1
  import { z } from 'zod';
2
2
 
3
+ /**
4
+ * Memory types supported by the service
5
+ */
6
+ const MEMORY_TYPES = ['context', 'project', 'knowledge', 'reference', 'personal', 'workflow'];
7
+ /**
8
+ * Memory status values
9
+ */
10
+ const MEMORY_STATUSES = ['active', 'archived', 'draft', 'deleted'];
11
+ /**
12
+ * Validation schemas using Zod
13
+ */
14
+ const createMemorySchema = z.object({
15
+ title: z.string().min(1).max(500),
16
+ content: z.string().min(1).max(50000),
17
+ summary: z.string().max(1000).optional(),
18
+ memory_type: z.enum(MEMORY_TYPES).default('context'),
19
+ topic_id: z.string().uuid().optional(),
20
+ project_ref: z.string().max(100).optional(),
21
+ tags: z.array(z.string().min(1).max(50)).max(20).default([]),
22
+ metadata: z.record(z.string(), z.unknown()).optional()
23
+ });
24
+ const updateMemorySchema = z.object({
25
+ title: z.string().min(1).max(500).optional(),
26
+ content: z.string().min(1).max(50000).optional(),
27
+ summary: z.string().max(1000).optional(),
28
+ memory_type: z.enum(MEMORY_TYPES).optional(),
29
+ status: z.enum(MEMORY_STATUSES).optional(),
30
+ topic_id: z.string().uuid().nullable().optional(),
31
+ project_ref: z.string().max(100).nullable().optional(),
32
+ tags: z.array(z.string().min(1).max(50)).max(20).optional(),
33
+ metadata: z.record(z.string(), z.unknown()).optional()
34
+ });
35
+ const searchMemorySchema = z.object({
36
+ query: z.string().min(1).max(1000),
37
+ memory_types: z.array(z.enum(MEMORY_TYPES)).optional(),
38
+ tags: z.array(z.string()).optional(),
39
+ topic_id: z.string().uuid().optional(),
40
+ project_ref: z.string().optional(),
41
+ status: z.enum(MEMORY_STATUSES).default('active'),
42
+ limit: z.number().int().min(1).max(100).default(20),
43
+ threshold: z.number().min(0).max(1).default(0.7)
44
+ });
45
+ const createTopicSchema = z.object({
46
+ name: z.string().min(1).max(100),
47
+ description: z.string().max(500).optional(),
48
+ color: z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
49
+ icon: z.string().max(50).optional(),
50
+ parent_topic_id: z.string().uuid().optional()
51
+ });
52
+ // ========================================
53
+ // Intelligence Feature Types (v2.0)
54
+ // ========================================
55
+ /**
56
+ * Chunking strategies for content preprocessing
57
+ */
58
+ const CHUNKING_STRATEGIES = ['semantic', 'fixed-size', 'paragraph', 'sentence', 'code-block'];
59
+ /**
60
+ * Content types detected or specified
61
+ */
62
+ const CONTENT_TYPES = ['text', 'code', 'markdown', 'json', 'yaml'];
63
+ // ========================================
64
+ // Enhanced Search Types
65
+ // ========================================
66
+ /**
67
+ * Search modes for memory queries
68
+ */
69
+ const SEARCH_MODES = ['vector', 'text', 'hybrid'];
70
+ // ========================================
71
+ // Validation Schemas for Intelligence
72
+ // ========================================
73
+ const preprocessingOptionsSchema = z.object({
74
+ chunking: z.object({
75
+ strategy: z.enum(CHUNKING_STRATEGIES).optional(),
76
+ maxChunkSize: z.number().int().min(100).max(10000).optional(),
77
+ overlap: z.number().int().min(0).max(500).optional()
78
+ }).optional(),
79
+ cleanContent: z.boolean().optional(),
80
+ extractMetadata: z.boolean().optional()
81
+ }).optional();
82
+ const enhancedSearchSchema = z.object({
83
+ query: z.string().min(1).max(1000),
84
+ type: z.enum(MEMORY_TYPES).optional(),
85
+ threshold: z.number().min(0).max(1).default(0.7),
86
+ limit: z.number().int().min(1).max(100).default(20),
87
+ search_mode: z.enum(SEARCH_MODES).default('hybrid'),
88
+ filters: z.object({
89
+ tags: z.array(z.string()).optional(),
90
+ project_id: z.string().uuid().optional(),
91
+ topic_id: z.string().uuid().optional(),
92
+ date_range: z.object({
93
+ from: z.string().optional(),
94
+ to: z.string().optional()
95
+ }).optional()
96
+ }).optional(),
97
+ include_chunks: z.boolean().default(false)
98
+ });
99
+ const analyticsDateRangeSchema = z.object({
100
+ from: z.string().optional(),
101
+ to: z.string().optional(),
102
+ group_by: z.enum(['day', 'week', 'month']).default('day')
103
+ });
104
+
105
+ /**
106
+ * Core Utilities for Memory Client
107
+ * Browser-safe, no Node.js dependencies
108
+ */
109
+ /**
110
+ * Safely parse JSON with detailed error reporting
111
+ * Prevents scattered try/catch blocks throughout the codebase
112
+ */
113
+ function safeJsonParse(input) {
114
+ try {
115
+ const data = JSON.parse(input);
116
+ return { success: true, data };
117
+ }
118
+ catch (error) {
119
+ const message = error instanceof Error
120
+ ? error.message
121
+ : 'Unknown JSON parse error';
122
+ return { success: false, error: `Invalid JSON: ${message}` };
123
+ }
124
+ }
125
+ /**
126
+ * HTTP status code to error code mapping
127
+ */
128
+ function httpStatusToErrorCode(status) {
129
+ switch (status) {
130
+ case 400:
131
+ return 'VALIDATION_ERROR';
132
+ case 401:
133
+ return 'AUTH_ERROR';
134
+ case 403:
135
+ return 'FORBIDDEN';
136
+ case 404:
137
+ return 'NOT_FOUND';
138
+ case 408:
139
+ return 'TIMEOUT_ERROR';
140
+ case 409:
141
+ return 'CONFLICT';
142
+ case 429:
143
+ return 'RATE_LIMIT_ERROR';
144
+ case 500:
145
+ case 502:
146
+ case 503:
147
+ case 504:
148
+ return 'SERVER_ERROR';
149
+ default:
150
+ return 'API_ERROR';
151
+ }
152
+ }
153
+ /**
154
+ * Create a standardized error response from various error sources
155
+ */
156
+ function createErrorResponse(message, code = 'API_ERROR', statusCode, details) {
157
+ return {
158
+ code,
159
+ message,
160
+ statusCode,
161
+ details,
162
+ timestamp: new Date().toISOString()
163
+ };
164
+ }
165
+ /**
166
+ * Create an error response from an HTTP response
167
+ */
168
+ function createErrorFromResponse(status, statusText, body) {
169
+ const code = httpStatusToErrorCode(status);
170
+ // Try to extract message from response body
171
+ let message = `HTTP ${status}: ${statusText}`;
172
+ let details = undefined;
173
+ if (body && typeof body === 'object') {
174
+ const bodyObj = body;
175
+ if (typeof bodyObj.error === 'string') {
176
+ message = bodyObj.error;
177
+ }
178
+ else if (typeof bodyObj.message === 'string') {
179
+ message = bodyObj.message;
180
+ }
181
+ if (bodyObj.details) {
182
+ details = bodyObj.details;
183
+ }
184
+ }
185
+ return createErrorResponse(message, code, status, details);
186
+ }
187
+ /**
188
+ * Sleep utility for retry logic
189
+ */
190
+ function sleep(ms) {
191
+ return new Promise(resolve => setTimeout(resolve, ms));
192
+ }
193
+ /**
194
+ * Calculate retry delay with exponential backoff and jitter
195
+ */
196
+ function calculateRetryDelay(attempt, baseDelay = 1000, backoff = 'exponential', maxDelay = 30000) {
197
+ let delay;
198
+ if (backoff === 'exponential') {
199
+ delay = baseDelay * Math.pow(2, attempt);
200
+ }
201
+ else {
202
+ delay = baseDelay * (attempt + 1);
203
+ }
204
+ // Add jitter (±20%) to prevent thundering herd
205
+ const jitter = delay * 0.2 * (Math.random() * 2 - 1);
206
+ delay = Math.min(delay + jitter, maxDelay);
207
+ return Math.round(delay);
208
+ }
209
+ /**
210
+ * Check if an error is retryable based on status code
211
+ */
212
+ function isRetryableError(statusCode) {
213
+ if (!statusCode)
214
+ return true; // Network errors are retryable
215
+ // Retry on server errors and rate limits
216
+ return statusCode >= 500 || statusCode === 429 || statusCode === 408;
217
+ }
218
+
3
219
  /**
4
220
  * Core Memory Client - Pure Browser-Safe Implementation
5
221
  *
@@ -8,6 +224,18 @@ import { z } from 'zod';
8
224
  *
9
225
  * Bundle size: ~15KB gzipped
10
226
  */
227
+ /**
228
+ * Helper to check if response has error
229
+ */
230
+ function hasError(response) {
231
+ return response.error !== undefined;
232
+ }
233
+ /**
234
+ * Helper to check if response has data
235
+ */
236
+ function hasData(response) {
237
+ return response.data !== undefined;
238
+ }
11
239
  /**
12
240
  * Core Memory Client class for interacting with the Memory as a Service API
13
241
  *
@@ -23,6 +251,7 @@ class CoreMemoryClient {
23
251
  this.baseHeaders = {
24
252
  'Content-Type': 'application/json',
25
253
  'User-Agent': '@lanonasis/memory-client/2.0.0',
254
+ 'X-Project-Scope': 'lanonasis-maas', // Required by backend auth middleware
26
255
  ...config.headers
27
256
  };
28
257
  // Set authentication headers
@@ -59,10 +288,13 @@ class CoreMemoryClient {
59
288
  return body;
60
289
  }
61
290
  /**
62
- * Make an HTTP request to the API
291
+ * Make an HTTP request to the API with retry support
63
292
  */
64
293
  async request(endpoint, options = {}) {
65
294
  const startTime = Date.now();
295
+ const maxRetries = this.config.retry?.maxRetries ?? 3;
296
+ const baseDelay = this.config.retry?.retryDelay ?? 1000;
297
+ const backoff = this.config.retry?.backoff ?? 'exponential';
66
298
  // Call onRequest hook if provided
67
299
  if (this.config.onRequest) {
68
300
  try {
@@ -77,85 +309,123 @@ class CoreMemoryClient {
77
309
  ? this.config.apiUrl.replace('/api', '')
78
310
  : this.config.apiUrl;
79
311
  const url = `${baseUrl}/api/v1${endpoint}`;
80
- try {
81
- const controller = new AbortController();
82
- const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
83
- const response = await fetch(url, {
84
- headers: { ...this.baseHeaders, ...options.headers },
85
- signal: controller.signal,
86
- ...options,
87
- });
88
- clearTimeout(timeoutId);
89
- let data;
90
- const contentType = response.headers.get('content-type');
91
- if (contentType && contentType.includes('application/json')) {
92
- data = await response.json();
93
- }
94
- else {
95
- data = await response.text();
96
- }
97
- if (!response.ok) {
98
- const error = {
99
- message: data?.error || `HTTP ${response.status}: ${response.statusText}`,
100
- statusCode: response.status,
101
- code: 'API_ERROR'
102
- };
103
- // Call onError hook if provided
104
- if (this.config.onError) {
312
+ let lastError;
313
+ let attempt = 0;
314
+ while (attempt <= maxRetries) {
315
+ try {
316
+ const controller = new AbortController();
317
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
318
+ const response = await fetch(url, {
319
+ headers: { ...this.baseHeaders, ...options.headers },
320
+ signal: controller.signal,
321
+ ...options,
322
+ });
323
+ clearTimeout(timeoutId);
324
+ let data;
325
+ const contentType = response.headers.get('content-type');
326
+ if (contentType && contentType.includes('application/json')) {
327
+ data = await response.json();
328
+ }
329
+ else {
330
+ data = await response.text();
331
+ }
332
+ if (!response.ok) {
333
+ const error = createErrorFromResponse(response.status, response.statusText, data);
334
+ // Only retry on retryable errors (5xx, 429, 408)
335
+ if (isRetryableError(response.status) && attempt < maxRetries) {
336
+ lastError = error;
337
+ const delay = calculateRetryDelay(attempt, baseDelay, backoff);
338
+ await sleep(delay);
339
+ attempt++;
340
+ continue;
341
+ }
342
+ // Call onError hook if provided
343
+ if (this.config.onError) {
344
+ try {
345
+ this.config.onError(error);
346
+ }
347
+ catch (hookError) {
348
+ console.warn('onError hook error:', hookError);
349
+ }
350
+ }
351
+ return { error, meta: { duration: Date.now() - startTime, retries: attempt } };
352
+ }
353
+ // Call onResponse hook if provided
354
+ if (this.config.onResponse) {
105
355
  try {
106
- this.config.onError(error);
356
+ const duration = Date.now() - startTime;
357
+ this.config.onResponse(endpoint, duration);
107
358
  }
108
- catch (hookError) {
109
- console.warn('onError hook error:', hookError);
359
+ catch (error) {
360
+ console.warn('onResponse hook error:', error);
110
361
  }
111
362
  }
112
- return { error: error.message };
363
+ return { data, meta: { duration: Date.now() - startTime, retries: attempt } };
113
364
  }
114
- // Call onResponse hook if provided
115
- if (this.config.onResponse) {
116
- try {
117
- const duration = Date.now() - startTime;
118
- this.config.onResponse(endpoint, duration);
365
+ catch (error) {
366
+ if (error instanceof Error && error.name === 'AbortError') {
367
+ const timeoutError = createErrorResponse('Request timeout', 'TIMEOUT_ERROR', 408);
368
+ // Retry on timeout
369
+ if (attempt < maxRetries) {
370
+ lastError = timeoutError;
371
+ const delay = calculateRetryDelay(attempt, baseDelay, backoff);
372
+ await sleep(delay);
373
+ attempt++;
374
+ continue;
375
+ }
376
+ if (this.config.onError) {
377
+ try {
378
+ this.config.onError(timeoutError);
379
+ }
380
+ catch (hookError) {
381
+ console.warn('onError hook error:', hookError);
382
+ }
383
+ }
384
+ return { error: timeoutError, meta: { duration: Date.now() - startTime, retries: attempt } };
119
385
  }
120
- catch (error) {
121
- console.warn('onResponse hook error:', error);
386
+ const networkError = createErrorResponse(error instanceof Error ? error.message : 'Network error', 'NETWORK_ERROR');
387
+ // Retry on network errors
388
+ if (attempt < maxRetries) {
389
+ lastError = networkError;
390
+ const delay = calculateRetryDelay(attempt, baseDelay, backoff);
391
+ await sleep(delay);
392
+ attempt++;
393
+ continue;
122
394
  }
123
- }
124
- return { data };
125
- }
126
- catch (error) {
127
- if (error instanceof Error && error.name === 'AbortError') {
128
- const timeoutError = {
129
- message: 'Request timeout',
130
- code: 'TIMEOUT_ERROR',
131
- statusCode: 408
132
- };
133
395
  if (this.config.onError) {
134
396
  try {
135
- this.config.onError(timeoutError);
397
+ this.config.onError(networkError);
136
398
  }
137
399
  catch (hookError) {
138
400
  console.warn('onError hook error:', hookError);
139
401
  }
140
402
  }
141
- return { error: 'Request timeout' };
142
- }
143
- const networkError = {
144
- message: error instanceof Error ? error.message : 'Network error',
145
- code: 'NETWORK_ERROR'
146
- };
147
- if (this.config.onError) {
148
- try {
149
- this.config.onError(networkError);
150
- }
151
- catch (hookError) {
152
- console.warn('onError hook error:', hookError);
153
- }
403
+ return { error: networkError, meta: { duration: Date.now() - startTime, retries: attempt } };
154
404
  }
405
+ }
406
+ // Should never reach here, but handle it gracefully
407
+ return {
408
+ error: lastError ?? createErrorResponse('Max retries exceeded', 'API_ERROR'),
409
+ meta: { duration: Date.now() - startTime, retries: attempt }
410
+ };
411
+ }
412
+ /**
413
+ * Validate input using Zod schema and return validation error if invalid
414
+ */
415
+ validateInput(schema, data) {
416
+ const result = schema.safeParse(data);
417
+ if (!result.success) {
418
+ // Extract error details from Zod error
419
+ const zodError = result.error;
420
+ const details = zodError?.issues?.map(issue => ({
421
+ field: issue.path.map(String).join('.'),
422
+ message: issue.message
423
+ })) ?? [];
155
424
  return {
156
- error: error instanceof Error ? error.message : 'Network error'
425
+ error: createErrorResponse('Validation failed', 'VALIDATION_ERROR', 400, details)
157
426
  };
158
427
  }
428
+ return null;
159
429
  }
160
430
  /**
161
431
  * Test the API connection and authentication
@@ -165,9 +435,14 @@ class CoreMemoryClient {
165
435
  }
166
436
  // Memory Operations
167
437
  /**
168
- * Create a new memory
438
+ * Create a new memory with validation
169
439
  */
170
440
  async createMemory(memory) {
441
+ // Validate input before making request
442
+ const validationError = this.validateInput(createMemorySchema, memory);
443
+ if (validationError) {
444
+ return { error: validationError.error };
445
+ }
171
446
  const enrichedMemory = this.enrichWithOrgContext(memory);
172
447
  return this.request('/memory', {
173
448
  method: 'POST',
@@ -181,9 +456,14 @@ class CoreMemoryClient {
181
456
  return this.request(`/memory/${encodeURIComponent(id)}`);
182
457
  }
183
458
  /**
184
- * Update an existing memory
459
+ * Update an existing memory with validation
185
460
  */
186
461
  async updateMemory(id, updates) {
462
+ // Validate input before making request
463
+ const validationError = this.validateInput(updateMemorySchema, updates);
464
+ if (validationError) {
465
+ return { error: validationError.error };
466
+ }
187
467
  return this.request(`/memory/${encodeURIComponent(id)}`, {
188
468
  method: 'PUT',
189
469
  body: JSON.stringify(updates)
@@ -217,9 +497,15 @@ class CoreMemoryClient {
217
497
  return this.request(endpoint);
218
498
  }
219
499
  /**
220
- * Search memories using semantic search
500
+ * Search memories using semantic search with validation
221
501
  */
222
502
  async searchMemories(request) {
503
+ // Validate input before making request
504
+ const validationError = this.validateInput(searchMemorySchema, request);
505
+ if (validationError) {
506
+ // Return error response (data will be undefined, only error is set)
507
+ return { error: validationError.error };
508
+ }
223
509
  const enrichedRequest = this.enrichWithOrgContext(request);
224
510
  return this.request('/memory/search', {
225
511
  method: 'POST',
@@ -238,9 +524,14 @@ class CoreMemoryClient {
238
524
  }
239
525
  // Topic Operations
240
526
  /**
241
- * Create a new topic
527
+ * Create a new topic with validation
242
528
  */
243
529
  async createTopic(topic) {
530
+ // Validate input before making request
531
+ const validationError = this.validateInput(createTopicSchema, topic);
532
+ if (validationError) {
533
+ return { error: validationError.error };
534
+ }
244
535
  const enrichedTopic = this.enrichWithOrgContext(topic);
245
536
  return this.request('/topics', {
246
537
  method: 'POST',
@@ -282,6 +573,182 @@ class CoreMemoryClient {
282
573
  async getMemoryStats() {
283
574
  return this.request('/memory/stats');
284
575
  }
576
+ // ========================================
577
+ // Intelligence Features (v2.0)
578
+ // ========================================
579
+ /**
580
+ * Create a memory with preprocessing options (chunking, intelligence extraction)
581
+ *
582
+ * @example
583
+ * ```typescript
584
+ * const result = await client.createMemoryWithPreprocessing({
585
+ * title: 'Auth System Docs',
586
+ * content: 'Long content...',
587
+ * memory_type: 'knowledge',
588
+ * preprocessing: {
589
+ * chunking: { strategy: 'semantic', maxChunkSize: 1000 },
590
+ * extractMetadata: true
591
+ * }
592
+ * });
593
+ * ```
594
+ */
595
+ async createMemoryWithPreprocessing(memory) {
596
+ // Validate base memory fields
597
+ const validationError = this.validateInput(createMemorySchema, memory);
598
+ if (validationError) {
599
+ return { error: validationError.error };
600
+ }
601
+ const enrichedMemory = this.enrichWithOrgContext(memory);
602
+ return this.request('/memory', {
603
+ method: 'POST',
604
+ body: JSON.stringify(enrichedMemory)
605
+ });
606
+ }
607
+ /**
608
+ * Update a memory with re-chunking and embedding regeneration
609
+ *
610
+ * @example
611
+ * ```typescript
612
+ * const result = await client.updateMemoryWithPreprocessing('mem_123', {
613
+ * content: 'Updated content...',
614
+ * rechunk: true,
615
+ * regenerate_embedding: true
616
+ * });
617
+ * ```
618
+ */
619
+ async updateMemoryWithPreprocessing(id, updates) {
620
+ const validationError = this.validateInput(updateMemorySchema, updates);
621
+ if (validationError) {
622
+ return { error: validationError.error };
623
+ }
624
+ return this.request(`/memory/${encodeURIComponent(id)}`, {
625
+ method: 'PUT',
626
+ body: JSON.stringify(updates)
627
+ });
628
+ }
629
+ /**
630
+ * Enhanced semantic search with hybrid mode (vector + text)
631
+ *
632
+ * @example
633
+ * ```typescript
634
+ * const result = await client.enhancedSearch({
635
+ * query: 'authentication flow',
636
+ * search_mode: 'hybrid',
637
+ * filters: { tags: ['auth'], project_id: 'proj_123' },
638
+ * include_chunks: true
639
+ * });
640
+ * ```
641
+ */
642
+ async enhancedSearch(request) {
643
+ const validationError = this.validateInput(enhancedSearchSchema, request);
644
+ if (validationError) {
645
+ return { error: validationError.error };
646
+ }
647
+ const enrichedRequest = this.enrichWithOrgContext(request);
648
+ return this.request('/memory/search', {
649
+ method: 'POST',
650
+ body: JSON.stringify(enrichedRequest)
651
+ });
652
+ }
653
+ // ========================================
654
+ // Analytics Operations
655
+ // ========================================
656
+ /**
657
+ * Get search analytics data
658
+ *
659
+ * @example
660
+ * ```typescript
661
+ * const analytics = await client.getSearchAnalytics({
662
+ * from: '2025-01-01',
663
+ * to: '2025-12-31',
664
+ * group_by: 'day'
665
+ * });
666
+ * ```
667
+ */
668
+ async getSearchAnalytics(options = {}) {
669
+ const validationError = this.validateInput(analyticsDateRangeSchema, options);
670
+ if (validationError) {
671
+ return { error: validationError.error };
672
+ }
673
+ const params = new URLSearchParams();
674
+ if (options.from)
675
+ params.append('from', options.from);
676
+ if (options.to)
677
+ params.append('to', options.to);
678
+ if (options.group_by)
679
+ params.append('group_by', options.group_by);
680
+ const queryString = params.toString();
681
+ const endpoint = queryString ? `/analytics/search?${queryString}` : '/analytics/search';
682
+ return this.request(endpoint);
683
+ }
684
+ /**
685
+ * Get memory access patterns
686
+ *
687
+ * @example
688
+ * ```typescript
689
+ * const patterns = await client.getAccessPatterns({
690
+ * from: '2025-01-01',
691
+ * to: '2025-12-31'
692
+ * });
693
+ * console.log(patterns.data?.most_accessed);
694
+ * ```
695
+ */
696
+ async getAccessPatterns(options = {}) {
697
+ const params = new URLSearchParams();
698
+ if (options.from)
699
+ params.append('from', options.from);
700
+ if (options.to)
701
+ params.append('to', options.to);
702
+ const queryString = params.toString();
703
+ const endpoint = queryString ? `/analytics/access?${queryString}` : '/analytics/access';
704
+ return this.request(endpoint);
705
+ }
706
+ /**
707
+ * Get extended memory statistics with storage and activity metrics
708
+ *
709
+ * @example
710
+ * ```typescript
711
+ * const stats = await client.getExtendedStats();
712
+ * console.log(`Total chunks: ${stats.data?.storage.total_chunks}`);
713
+ * console.log(`Created today: ${stats.data?.activity.created_today}`);
714
+ * ```
715
+ */
716
+ async getExtendedStats() {
717
+ return this.request('/analytics/stats');
718
+ }
719
+ /**
720
+ * Get topic with its memories
721
+ *
722
+ * @example
723
+ * ```typescript
724
+ * const topic = await client.getTopicWithMemories('topic_123');
725
+ * console.log(topic.data?.memories);
726
+ * ```
727
+ */
728
+ async getTopicWithMemories(topicId, options = {}) {
729
+ const params = new URLSearchParams();
730
+ if (options.limit)
731
+ params.append('limit', String(options.limit));
732
+ if (options.offset)
733
+ params.append('offset', String(options.offset));
734
+ const queryString = params.toString();
735
+ const endpoint = queryString
736
+ ? `/topics/${encodeURIComponent(topicId)}/memories?${queryString}`
737
+ : `/topics/${encodeURIComponent(topicId)}/memories`;
738
+ return this.request(endpoint);
739
+ }
740
+ /**
741
+ * Get topics in hierarchical structure
742
+ *
743
+ * @example
744
+ * ```typescript
745
+ * const topics = await client.getTopicsHierarchy();
746
+ * // Returns nested topic tree with children
747
+ * ```
748
+ */
749
+ async getTopicsHierarchy() {
750
+ return this.request('/topics?include_hierarchy=true');
751
+ }
285
752
  // Utility Methods
286
753
  /**
287
754
  * Update authentication token
@@ -330,64 +797,40 @@ function createMemoryClient(config) {
330
797
  }
331
798
 
332
799
  /**
333
- * Memory types supported by the service
800
+ * Error handling for Memory Client
801
+ * Browser-safe, no Node.js dependencies
334
802
  */
335
- const MEMORY_TYPES = ['context', 'project', 'knowledge', 'reference', 'personal', 'workflow'];
336
803
  /**
337
- * Memory status values
804
+ * Standardized error codes for programmatic error handling
338
805
  */
339
- const MEMORY_STATUSES = ['active', 'archived', 'draft', 'deleted'];
806
+ const ERROR_CODES = [
807
+ 'API_ERROR',
808
+ 'AUTH_ERROR',
809
+ 'VALIDATION_ERROR',
810
+ 'TIMEOUT_ERROR',
811
+ 'RATE_LIMIT_ERROR',
812
+ 'NOT_FOUND',
813
+ 'NETWORK_ERROR',
814
+ 'FORBIDDEN',
815
+ 'CONFLICT',
816
+ 'SERVER_ERROR'
817
+ ];
340
818
  /**
341
- * Validation schemas using Zod
342
- */
343
- const createMemorySchema = z.object({
344
- title: z.string().min(1).max(500),
345
- content: z.string().min(1).max(50000),
346
- summary: z.string().max(1000).optional(),
347
- memory_type: z.enum(MEMORY_TYPES).default('context'),
348
- topic_id: z.string().uuid().optional(),
349
- project_ref: z.string().max(100).optional(),
350
- tags: z.array(z.string().min(1).max(50)).max(20).default([]),
351
- metadata: z.record(z.string(), z.unknown()).optional()
352
- });
353
- const updateMemorySchema = z.object({
354
- title: z.string().min(1).max(500).optional(),
355
- content: z.string().min(1).max(50000).optional(),
356
- summary: z.string().max(1000).optional(),
357
- memory_type: z.enum(MEMORY_TYPES).optional(),
358
- status: z.enum(MEMORY_STATUSES).optional(),
359
- topic_id: z.string().uuid().nullable().optional(),
360
- project_ref: z.string().max(100).nullable().optional(),
361
- tags: z.array(z.string().min(1).max(50)).max(20).optional(),
362
- metadata: z.record(z.string(), z.unknown()).optional()
363
- });
364
- const searchMemorySchema = z.object({
365
- query: z.string().min(1).max(1000),
366
- memory_types: z.array(z.enum(MEMORY_TYPES)).optional(),
367
- tags: z.array(z.string()).optional(),
368
- topic_id: z.string().uuid().optional(),
369
- project_ref: z.string().optional(),
370
- status: z.enum(MEMORY_STATUSES).default('active'),
371
- limit: z.number().int().min(1).max(100).default(20),
372
- threshold: z.number().min(0).max(1).default(0.7)
373
- });
374
- const createTopicSchema = z.object({
375
- name: z.string().min(1).max(100),
376
- description: z.string().max(500).optional(),
377
- color: z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
378
- icon: z.string().max(50).optional(),
379
- parent_topic_id: z.string().uuid().optional()
380
- });
381
-
382
- /**
383
- * Error handling for Memory Client
384
- * Browser-safe, no Node.js dependencies
819
+ * Type guard to check if an object is an ApiErrorResponse
385
820
  */
821
+ function isApiErrorResponse(value) {
822
+ return (typeof value === 'object' &&
823
+ value !== null &&
824
+ 'code' in value &&
825
+ 'message' in value &&
826
+ typeof value.code === 'string' &&
827
+ typeof value.message === 'string');
828
+ }
386
829
  /**
387
830
  * Base error class for Memory Client errors
388
831
  */
389
832
  class MemoryClientError extends Error {
390
- constructor(message, code, statusCode, details) {
833
+ constructor(message, code = 'API_ERROR', statusCode, details) {
391
834
  super(message);
392
835
  this.code = code;
393
836
  this.statusCode = statusCode;
@@ -398,6 +841,18 @@ class MemoryClientError extends Error {
398
841
  Error.captureStackTrace(this, MemoryClientError);
399
842
  }
400
843
  }
844
+ /**
845
+ * Convert to ApiErrorResponse for consistent API responses
846
+ */
847
+ toResponse() {
848
+ return {
849
+ code: this.code,
850
+ message: this.message,
851
+ statusCode: this.statusCode,
852
+ details: this.details,
853
+ timestamp: new Date().toISOString()
854
+ };
855
+ }
401
856
  }
402
857
  /**
403
858
  * Network/API error
@@ -407,6 +862,26 @@ class ApiError extends MemoryClientError {
407
862
  super(message, 'API_ERROR', statusCode, details);
408
863
  this.name = 'ApiError';
409
864
  }
865
+ /**
866
+ * Create from an HTTP response
867
+ */
868
+ static fromResponse(status, statusText, body) {
869
+ let message = `HTTP ${status}: ${statusText}`;
870
+ let details = undefined;
871
+ if (body && typeof body === 'object') {
872
+ const bodyObj = body;
873
+ if (typeof bodyObj.error === 'string') {
874
+ message = bodyObj.error;
875
+ }
876
+ else if (typeof bodyObj.message === 'string') {
877
+ message = bodyObj.message;
878
+ }
879
+ if (bodyObj.details) {
880
+ details = bodyObj.details;
881
+ }
882
+ }
883
+ return new ApiError(message, status, details);
884
+ }
410
885
  }
411
886
  /**
412
887
  * Authentication error
@@ -418,12 +893,30 @@ class AuthenticationError extends MemoryClientError {
418
893
  }
419
894
  }
420
895
  /**
421
- * Validation error
896
+ * Validation error with field-level details
422
897
  */
423
898
  class ValidationError extends MemoryClientError {
424
899
  constructor(message, details) {
425
900
  super(message, 'VALIDATION_ERROR', 400, details);
426
901
  this.name = 'ValidationError';
902
+ // Parse validation details into field errors
903
+ this.validationErrors = [];
904
+ if (Array.isArray(details)) {
905
+ this.validationErrors = details.filter((item) => typeof item === 'object' &&
906
+ item !== null &&
907
+ typeof item.field === 'string' &&
908
+ typeof item.message === 'string');
909
+ }
910
+ }
911
+ /**
912
+ * Create from Zod error
913
+ */
914
+ static fromZodError(error) {
915
+ const details = error.issues.map(issue => ({
916
+ field: issue.path.join('.'),
917
+ message: issue.message
918
+ }));
919
+ return new ValidationError('Validation failed', details);
427
920
  }
428
921
  }
429
922
  /**
@@ -436,12 +929,13 @@ class TimeoutError extends MemoryClientError {
436
929
  }
437
930
  }
438
931
  /**
439
- * Rate limit error
932
+ * Rate limit error with retry-after info
440
933
  */
441
934
  class RateLimitError extends MemoryClientError {
442
- constructor(message = 'Rate limit exceeded') {
443
- super(message, 'RATE_LIMIT_ERROR', 429);
935
+ constructor(message = 'Rate limit exceeded', retryAfter) {
936
+ super(message, 'RATE_LIMIT_ERROR', 429, { retryAfter });
444
937
  this.name = 'RateLimitError';
938
+ this.retryAfter = retryAfter;
445
939
  }
446
940
  }
447
941
  /**
@@ -451,6 +945,49 @@ class NotFoundError extends MemoryClientError {
451
945
  constructor(resource) {
452
946
  super(`${resource} not found`, 'NOT_FOUND', 404);
453
947
  this.name = 'NotFoundError';
948
+ this.resource = resource;
949
+ }
950
+ }
951
+ /**
952
+ * Network error (no response received)
953
+ */
954
+ class NetworkError extends MemoryClientError {
955
+ constructor(message = 'Network error') {
956
+ super(message, 'NETWORK_ERROR');
957
+ this.name = 'NetworkError';
958
+ }
959
+ }
960
+ /**
961
+ * Server error (5xx responses)
962
+ */
963
+ class ServerError extends MemoryClientError {
964
+ constructor(message, statusCode = 500) {
965
+ super(message, 'SERVER_ERROR', statusCode);
966
+ this.name = 'ServerError';
967
+ }
968
+ }
969
+ /**
970
+ * Create appropriate error class from status code
971
+ */
972
+ function createErrorFromStatus(status, message, details) {
973
+ switch (status) {
974
+ case 400:
975
+ return new ValidationError(message, details);
976
+ case 401:
977
+ return new AuthenticationError(message);
978
+ case 404:
979
+ return new NotFoundError(message);
980
+ case 408:
981
+ return new TimeoutError(message);
982
+ case 429:
983
+ return new RateLimitError(message);
984
+ case 500:
985
+ case 502:
986
+ case 503:
987
+ case 504:
988
+ return new ServerError(message, status);
989
+ default:
990
+ return new ApiError(message, status, details);
454
991
  }
455
992
  }
456
993
 
@@ -486,5 +1023,5 @@ const defaultConfigs = {
486
1023
  }
487
1024
  };
488
1025
 
489
- export { ApiError as ApiErrorClass, AuthenticationError, CLIENT_NAME, CoreMemoryClient, MEMORY_STATUSES, MEMORY_TYPES, MemoryClientError, NotFoundError, RateLimitError, TimeoutError, VERSION, ValidationError, createMemoryClient, createMemorySchema, createTopicSchema, defaultConfigs, isBrowser, isNode, searchMemorySchema, updateMemorySchema };
1026
+ export { ApiError, AuthenticationError, CHUNKING_STRATEGIES, CLIENT_NAME, CONTENT_TYPES, CoreMemoryClient, ERROR_CODES, MEMORY_STATUSES, MEMORY_TYPES, MemoryClientError, NetworkError, NotFoundError, RateLimitError, SEARCH_MODES, ServerError, TimeoutError, VERSION, ValidationError, analyticsDateRangeSchema, calculateRetryDelay, createErrorFromStatus, createErrorResponse, createMemoryClient, createMemorySchema, createTopicSchema, defaultConfigs, enhancedSearchSchema, hasData, hasError, httpStatusToErrorCode, isApiErrorResponse, isBrowser, isNode, isRetryableError, preprocessingOptionsSchema, safeJsonParse, searchMemorySchema, updateMemorySchema };
490
1027
  //# sourceMappingURL=index.js.map