@lanonasis/memory-client 2.0.0 → 2.2.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,264 @@
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
+ z
53
+ .object({
54
+ memory_id: z.string().uuid().optional(),
55
+ content: z.string().min(1).optional(),
56
+ title: z.string().optional(),
57
+ existing_tags: z.array(z.string()).optional(),
58
+ max_suggestions: z.number().int().min(1).max(10).optional()
59
+ })
60
+ .refine((data) => data.memory_id || data.content, {
61
+ message: 'Either memory_id or content is required'
62
+ });
63
+ z.object({
64
+ time_range_days: z.number().int().min(1).max(365).optional(),
65
+ include_insights: z.boolean().optional(),
66
+ response_format: z.enum(['json', 'markdown']).optional()
67
+ });
68
+ z.object({
69
+ include_recommendations: z.boolean().optional(),
70
+ detailed_breakdown: z.boolean().optional()
71
+ });
72
+ z
73
+ .object({
74
+ memory_id: z.string().uuid().optional(),
75
+ query: z.string().min(1).optional(),
76
+ limit: z.number().int().min(1).max(20).optional(),
77
+ similarity_threshold: z.number().min(0).max(1).optional(),
78
+ exclude_ids: z.array(z.string().uuid()).optional()
79
+ })
80
+ .refine((data) => data.memory_id || data.query, {
81
+ message: 'Either memory_id or query is required'
82
+ });
83
+ z.object({
84
+ similarity_threshold: z.number().min(0).max(1).optional(),
85
+ include_archived: z.boolean().optional(),
86
+ limit: z.number().int().min(1).max(50).optional()
87
+ });
88
+ z.object({
89
+ memory_ids: z.array(z.string().uuid()).optional(),
90
+ topic: z.string().min(1).optional(),
91
+ time_range_days: z.number().int().min(1).max(365).optional(),
92
+ insight_types: z.array(z.enum(['themes', 'connections', 'gaps', 'actions', 'summary'])).optional(),
93
+ detail_level: z.enum(['brief', 'detailed', 'comprehensive']).optional()
94
+ });
95
+ // ========================================
96
+ // Intelligence Feature Types (v2.0)
97
+ // ========================================
98
+ /**
99
+ * Chunking strategies for content preprocessing
100
+ */
101
+ const CHUNKING_STRATEGIES = ['semantic', 'fixed-size', 'paragraph', 'sentence', 'code-block'];
102
+ /**
103
+ * Content types detected or specified
104
+ */
105
+ const CONTENT_TYPES = ['text', 'code', 'markdown', 'json', 'yaml'];
106
+ // ========================================
107
+ // Enhanced Search Types
108
+ // ========================================
109
+ /**
110
+ * Search modes for memory queries
111
+ */
112
+ const SEARCH_MODES = ['vector', 'text', 'hybrid'];
113
+ // ========================================
114
+ // Validation Schemas for Intelligence
115
+ // ========================================
116
+ const preprocessingOptionsSchema = z.object({
117
+ chunking: z.object({
118
+ strategy: z.enum(CHUNKING_STRATEGIES).optional(),
119
+ maxChunkSize: z.number().int().min(100).max(10000).optional(),
120
+ overlap: z.number().int().min(0).max(500).optional()
121
+ }).optional(),
122
+ cleanContent: z.boolean().optional(),
123
+ extractMetadata: z.boolean().optional()
124
+ }).optional();
125
+ const enhancedSearchSchema = z.object({
126
+ query: z.string().min(1).max(1000),
127
+ type: z.enum(MEMORY_TYPES).optional(),
128
+ threshold: z.number().min(0).max(1).default(0.7),
129
+ limit: z.number().int().min(1).max(100).default(20),
130
+ search_mode: z.enum(SEARCH_MODES).default('hybrid'),
131
+ filters: z.object({
132
+ tags: z.array(z.string()).optional(),
133
+ project_id: z.string().uuid().optional(),
134
+ topic_id: z.string().uuid().optional(),
135
+ date_range: z.object({
136
+ from: z.string().optional(),
137
+ to: z.string().optional()
138
+ }).optional()
139
+ }).optional(),
140
+ include_chunks: z.boolean().default(false)
141
+ });
142
+ const analyticsDateRangeSchema = z.object({
143
+ from: z.string().optional(),
144
+ to: z.string().optional(),
145
+ group_by: z.enum(['day', 'week', 'month']).default('day')
146
+ });
147
+
148
+ /**
149
+ * Core Utilities for Memory Client
150
+ * Browser-safe, no Node.js dependencies
151
+ */
152
+ /**
153
+ * Safely parse JSON with detailed error reporting
154
+ * Prevents scattered try/catch blocks throughout the codebase
155
+ */
156
+ function safeJsonParse(input) {
157
+ try {
158
+ const data = JSON.parse(input);
159
+ return { success: true, data };
160
+ }
161
+ catch (error) {
162
+ const message = error instanceof Error
163
+ ? error.message
164
+ : 'Unknown JSON parse error';
165
+ return { success: false, error: `Invalid JSON: ${message}` };
166
+ }
167
+ }
168
+ /**
169
+ * HTTP status code to error code mapping
170
+ */
171
+ function httpStatusToErrorCode(status) {
172
+ switch (status) {
173
+ case 400:
174
+ return 'VALIDATION_ERROR';
175
+ case 401:
176
+ return 'AUTH_ERROR';
177
+ case 403:
178
+ return 'FORBIDDEN';
179
+ case 404:
180
+ return 'NOT_FOUND';
181
+ case 408:
182
+ return 'TIMEOUT_ERROR';
183
+ case 409:
184
+ return 'CONFLICT';
185
+ case 429:
186
+ return 'RATE_LIMIT_ERROR';
187
+ case 500:
188
+ case 502:
189
+ case 503:
190
+ case 504:
191
+ return 'SERVER_ERROR';
192
+ default:
193
+ return 'API_ERROR';
194
+ }
195
+ }
196
+ /**
197
+ * Create a standardized error response from various error sources
198
+ */
199
+ function createErrorResponse(message, code = 'API_ERROR', statusCode, details) {
200
+ return {
201
+ code,
202
+ message,
203
+ statusCode,
204
+ details,
205
+ timestamp: new Date().toISOString()
206
+ };
207
+ }
208
+ /**
209
+ * Create an error response from an HTTP response
210
+ */
211
+ function createErrorFromResponse(status, statusText, body) {
212
+ const code = httpStatusToErrorCode(status);
213
+ // Try to extract message from response body
214
+ let message = `HTTP ${status}: ${statusText}`;
215
+ let details = undefined;
216
+ if (body && typeof body === 'object') {
217
+ const bodyObj = body;
218
+ if (typeof bodyObj.error === 'string') {
219
+ message = bodyObj.error;
220
+ }
221
+ else if (typeof bodyObj.message === 'string') {
222
+ message = bodyObj.message;
223
+ }
224
+ if (bodyObj.details) {
225
+ details = bodyObj.details;
226
+ }
227
+ }
228
+ return createErrorResponse(message, code, status, details);
229
+ }
230
+ /**
231
+ * Sleep utility for retry logic
232
+ */
233
+ function sleep(ms) {
234
+ return new Promise(resolve => setTimeout(resolve, ms));
235
+ }
236
+ /**
237
+ * Calculate retry delay with exponential backoff and jitter
238
+ */
239
+ function calculateRetryDelay(attempt, baseDelay = 1000, backoff = 'exponential', maxDelay = 30000) {
240
+ let delay;
241
+ if (backoff === 'exponential') {
242
+ delay = baseDelay * Math.pow(2, attempt);
243
+ }
244
+ else {
245
+ delay = baseDelay * (attempt + 1);
246
+ }
247
+ // Add jitter (±20%) to prevent thundering herd
248
+ const jitter = delay * 0.2 * (Math.random() * 2 - 1);
249
+ delay = Math.min(delay + jitter, maxDelay);
250
+ return Math.round(delay);
251
+ }
252
+ /**
253
+ * Check if an error is retryable based on status code
254
+ */
255
+ function isRetryableError(statusCode) {
256
+ if (!statusCode)
257
+ return true; // Network errors are retryable
258
+ // Retry on server errors and rate limits
259
+ return statusCode >= 500 || statusCode === 429 || statusCode === 408;
260
+ }
261
+
3
262
  /**
4
263
  * Core Memory Client - Pure Browser-Safe Implementation
5
264
  *
@@ -8,6 +267,18 @@ import { z } from 'zod';
8
267
  *
9
268
  * Bundle size: ~15KB gzipped
10
269
  */
270
+ /**
271
+ * Helper to check if response has error
272
+ */
273
+ function hasError(response) {
274
+ return response.error !== undefined;
275
+ }
276
+ /**
277
+ * Helper to check if response has data
278
+ */
279
+ function hasData(response) {
280
+ return response.data !== undefined;
281
+ }
11
282
  /**
12
283
  * Core Memory Client class for interacting with the Memory as a Service API
13
284
  *
@@ -23,6 +294,7 @@ class CoreMemoryClient {
23
294
  this.baseHeaders = {
24
295
  'Content-Type': 'application/json',
25
296
  'User-Agent': '@lanonasis/memory-client/2.0.0',
297
+ 'X-Project-Scope': 'lanonasis-maas', // Required by backend auth middleware
26
298
  ...config.headers
27
299
  };
28
300
  // Set authentication headers
@@ -59,10 +331,13 @@ class CoreMemoryClient {
59
331
  return body;
60
332
  }
61
333
  /**
62
- * Make an HTTP request to the API
334
+ * Make an HTTP request to the API with retry support
63
335
  */
64
336
  async request(endpoint, options = {}) {
65
337
  const startTime = Date.now();
338
+ const maxRetries = this.config.retry?.maxRetries ?? 3;
339
+ const baseDelay = this.config.retry?.retryDelay ?? 1000;
340
+ const backoff = this.config.retry?.backoff ?? 'exponential';
66
341
  // Call onRequest hook if provided
67
342
  if (this.config.onRequest) {
68
343
  try {
@@ -77,85 +352,123 @@ class CoreMemoryClient {
77
352
  ? this.config.apiUrl.replace('/api', '')
78
353
  : this.config.apiUrl;
79
354
  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) {
355
+ let lastError;
356
+ let attempt = 0;
357
+ while (attempt <= maxRetries) {
358
+ try {
359
+ const controller = new AbortController();
360
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
361
+ const response = await fetch(url, {
362
+ headers: { ...this.baseHeaders, ...options.headers },
363
+ signal: controller.signal,
364
+ ...options,
365
+ });
366
+ clearTimeout(timeoutId);
367
+ let data;
368
+ const contentType = response.headers.get('content-type');
369
+ if (contentType && contentType.includes('application/json')) {
370
+ data = await response.json();
371
+ }
372
+ else {
373
+ data = await response.text();
374
+ }
375
+ if (!response.ok) {
376
+ const error = createErrorFromResponse(response.status, response.statusText, data);
377
+ // Only retry on retryable errors (5xx, 429, 408)
378
+ if (isRetryableError(response.status) && attempt < maxRetries) {
379
+ lastError = error;
380
+ const delay = calculateRetryDelay(attempt, baseDelay, backoff);
381
+ await sleep(delay);
382
+ attempt++;
383
+ continue;
384
+ }
385
+ // Call onError hook if provided
386
+ if (this.config.onError) {
387
+ try {
388
+ this.config.onError(error);
389
+ }
390
+ catch (hookError) {
391
+ console.warn('onError hook error:', hookError);
392
+ }
393
+ }
394
+ return { error, meta: { duration: Date.now() - startTime, retries: attempt } };
395
+ }
396
+ // Call onResponse hook if provided
397
+ if (this.config.onResponse) {
105
398
  try {
106
- this.config.onError(error);
399
+ const duration = Date.now() - startTime;
400
+ this.config.onResponse(endpoint, duration);
107
401
  }
108
- catch (hookError) {
109
- console.warn('onError hook error:', hookError);
402
+ catch (error) {
403
+ console.warn('onResponse hook error:', error);
110
404
  }
111
405
  }
112
- return { error: error.message };
406
+ return { data, meta: { duration: Date.now() - startTime, retries: attempt } };
113
407
  }
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);
408
+ catch (error) {
409
+ if (error instanceof Error && error.name === 'AbortError') {
410
+ const timeoutError = createErrorResponse('Request timeout', 'TIMEOUT_ERROR', 408);
411
+ // Retry on timeout
412
+ if (attempt < maxRetries) {
413
+ lastError = timeoutError;
414
+ const delay = calculateRetryDelay(attempt, baseDelay, backoff);
415
+ await sleep(delay);
416
+ attempt++;
417
+ continue;
418
+ }
419
+ if (this.config.onError) {
420
+ try {
421
+ this.config.onError(timeoutError);
422
+ }
423
+ catch (hookError) {
424
+ console.warn('onError hook error:', hookError);
425
+ }
426
+ }
427
+ return { error: timeoutError, meta: { duration: Date.now() - startTime, retries: attempt } };
119
428
  }
120
- catch (error) {
121
- console.warn('onResponse hook error:', error);
429
+ const networkError = createErrorResponse(error instanceof Error ? error.message : 'Network error', 'NETWORK_ERROR');
430
+ // Retry on network errors
431
+ if (attempt < maxRetries) {
432
+ lastError = networkError;
433
+ const delay = calculateRetryDelay(attempt, baseDelay, backoff);
434
+ await sleep(delay);
435
+ attempt++;
436
+ continue;
122
437
  }
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
438
  if (this.config.onError) {
134
439
  try {
135
- this.config.onError(timeoutError);
440
+ this.config.onError(networkError);
136
441
  }
137
442
  catch (hookError) {
138
443
  console.warn('onError hook error:', hookError);
139
444
  }
140
445
  }
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
- }
446
+ return { error: networkError, meta: { duration: Date.now() - startTime, retries: attempt } };
154
447
  }
448
+ }
449
+ // Should never reach here, but handle it gracefully
450
+ return {
451
+ error: lastError ?? createErrorResponse('Max retries exceeded', 'API_ERROR'),
452
+ meta: { duration: Date.now() - startTime, retries: attempt }
453
+ };
454
+ }
455
+ /**
456
+ * Validate input using Zod schema and return validation error if invalid
457
+ */
458
+ validateInput(schema, data) {
459
+ const result = schema.safeParse(data);
460
+ if (!result.success) {
461
+ // Extract error details from Zod error
462
+ const zodError = result.error;
463
+ const details = zodError?.issues?.map(issue => ({
464
+ field: issue.path.map(String).join('.'),
465
+ message: issue.message
466
+ })) ?? [];
155
467
  return {
156
- error: error instanceof Error ? error.message : 'Network error'
468
+ error: createErrorResponse('Validation failed', 'VALIDATION_ERROR', 400, details)
157
469
  };
158
470
  }
471
+ return null;
159
472
  }
160
473
  /**
161
474
  * Test the API connection and authentication
@@ -165,9 +478,14 @@ class CoreMemoryClient {
165
478
  }
166
479
  // Memory Operations
167
480
  /**
168
- * Create a new memory
481
+ * Create a new memory with validation
169
482
  */
170
483
  async createMemory(memory) {
484
+ // Validate input before making request
485
+ const validationError = this.validateInput(createMemorySchema, memory);
486
+ if (validationError) {
487
+ return { error: validationError.error };
488
+ }
171
489
  const enrichedMemory = this.enrichWithOrgContext(memory);
172
490
  return this.request('/memory', {
173
491
  method: 'POST',
@@ -181,9 +499,14 @@ class CoreMemoryClient {
181
499
  return this.request(`/memory/${encodeURIComponent(id)}`);
182
500
  }
183
501
  /**
184
- * Update an existing memory
502
+ * Update an existing memory with validation
185
503
  */
186
504
  async updateMemory(id, updates) {
505
+ // Validate input before making request
506
+ const validationError = this.validateInput(updateMemorySchema, updates);
507
+ if (validationError) {
508
+ return { error: validationError.error };
509
+ }
187
510
  return this.request(`/memory/${encodeURIComponent(id)}`, {
188
511
  method: 'PUT',
189
512
  body: JSON.stringify(updates)
@@ -217,9 +540,15 @@ class CoreMemoryClient {
217
540
  return this.request(endpoint);
218
541
  }
219
542
  /**
220
- * Search memories using semantic search
543
+ * Search memories using semantic search with validation
221
544
  */
222
545
  async searchMemories(request) {
546
+ // Validate input before making request
547
+ const validationError = this.validateInput(searchMemorySchema, request);
548
+ if (validationError) {
549
+ // Return error response (data will be undefined, only error is set)
550
+ return { error: validationError.error };
551
+ }
223
552
  const enrichedRequest = this.enrichWithOrgContext(request);
224
553
  return this.request('/memory/search', {
225
554
  method: 'POST',
@@ -238,9 +567,14 @@ class CoreMemoryClient {
238
567
  }
239
568
  // Topic Operations
240
569
  /**
241
- * Create a new topic
570
+ * Create a new topic with validation
242
571
  */
243
572
  async createTopic(topic) {
573
+ // Validate input before making request
574
+ const validationError = this.validateInput(createTopicSchema, topic);
575
+ if (validationError) {
576
+ return { error: validationError.error };
577
+ }
244
578
  const enrichedTopic = this.enrichWithOrgContext(topic);
245
579
  return this.request('/topics', {
246
580
  method: 'POST',
@@ -282,6 +616,182 @@ class CoreMemoryClient {
282
616
  async getMemoryStats() {
283
617
  return this.request('/memory/stats');
284
618
  }
619
+ // ========================================
620
+ // Intelligence Features (v2.0)
621
+ // ========================================
622
+ /**
623
+ * Create a memory with preprocessing options (chunking, intelligence extraction)
624
+ *
625
+ * @example
626
+ * ```typescript
627
+ * const result = await client.createMemoryWithPreprocessing({
628
+ * title: 'Auth System Docs',
629
+ * content: 'Long content...',
630
+ * memory_type: 'knowledge',
631
+ * preprocessing: {
632
+ * chunking: { strategy: 'semantic', maxChunkSize: 1000 },
633
+ * extractMetadata: true
634
+ * }
635
+ * });
636
+ * ```
637
+ */
638
+ async createMemoryWithPreprocessing(memory) {
639
+ // Validate base memory fields
640
+ const validationError = this.validateInput(createMemorySchema, memory);
641
+ if (validationError) {
642
+ return { error: validationError.error };
643
+ }
644
+ const enrichedMemory = this.enrichWithOrgContext(memory);
645
+ return this.request('/memory', {
646
+ method: 'POST',
647
+ body: JSON.stringify(enrichedMemory)
648
+ });
649
+ }
650
+ /**
651
+ * Update a memory with re-chunking and embedding regeneration
652
+ *
653
+ * @example
654
+ * ```typescript
655
+ * const result = await client.updateMemoryWithPreprocessing('mem_123', {
656
+ * content: 'Updated content...',
657
+ * rechunk: true,
658
+ * regenerate_embedding: true
659
+ * });
660
+ * ```
661
+ */
662
+ async updateMemoryWithPreprocessing(id, updates) {
663
+ const validationError = this.validateInput(updateMemorySchema, updates);
664
+ if (validationError) {
665
+ return { error: validationError.error };
666
+ }
667
+ return this.request(`/memory/${encodeURIComponent(id)}`, {
668
+ method: 'PUT',
669
+ body: JSON.stringify(updates)
670
+ });
671
+ }
672
+ /**
673
+ * Enhanced semantic search with hybrid mode (vector + text)
674
+ *
675
+ * @example
676
+ * ```typescript
677
+ * const result = await client.enhancedSearch({
678
+ * query: 'authentication flow',
679
+ * search_mode: 'hybrid',
680
+ * filters: { tags: ['auth'], project_id: 'proj_123' },
681
+ * include_chunks: true
682
+ * });
683
+ * ```
684
+ */
685
+ async enhancedSearch(request) {
686
+ const validationError = this.validateInput(enhancedSearchSchema, request);
687
+ if (validationError) {
688
+ return { error: validationError.error };
689
+ }
690
+ const enrichedRequest = this.enrichWithOrgContext(request);
691
+ return this.request('/memory/search', {
692
+ method: 'POST',
693
+ body: JSON.stringify(enrichedRequest)
694
+ });
695
+ }
696
+ // ========================================
697
+ // Analytics Operations
698
+ // ========================================
699
+ /**
700
+ * Get search analytics data
701
+ *
702
+ * @example
703
+ * ```typescript
704
+ * const analytics = await client.getSearchAnalytics({
705
+ * from: '2025-01-01',
706
+ * to: '2025-12-31',
707
+ * group_by: 'day'
708
+ * });
709
+ * ```
710
+ */
711
+ async getSearchAnalytics(options = {}) {
712
+ const validationError = this.validateInput(analyticsDateRangeSchema, options);
713
+ if (validationError) {
714
+ return { error: validationError.error };
715
+ }
716
+ const params = new URLSearchParams();
717
+ if (options.from)
718
+ params.append('from', options.from);
719
+ if (options.to)
720
+ params.append('to', options.to);
721
+ if (options.group_by)
722
+ params.append('group_by', options.group_by);
723
+ const queryString = params.toString();
724
+ const endpoint = queryString ? `/analytics/search?${queryString}` : '/analytics/search';
725
+ return this.request(endpoint);
726
+ }
727
+ /**
728
+ * Get memory access patterns
729
+ *
730
+ * @example
731
+ * ```typescript
732
+ * const patterns = await client.getAccessPatterns({
733
+ * from: '2025-01-01',
734
+ * to: '2025-12-31'
735
+ * });
736
+ * console.log(patterns.data?.most_accessed);
737
+ * ```
738
+ */
739
+ async getAccessPatterns(options = {}) {
740
+ const params = new URLSearchParams();
741
+ if (options.from)
742
+ params.append('from', options.from);
743
+ if (options.to)
744
+ params.append('to', options.to);
745
+ const queryString = params.toString();
746
+ const endpoint = queryString ? `/analytics/access?${queryString}` : '/analytics/access';
747
+ return this.request(endpoint);
748
+ }
749
+ /**
750
+ * Get extended memory statistics with storage and activity metrics
751
+ *
752
+ * @example
753
+ * ```typescript
754
+ * const stats = await client.getExtendedStats();
755
+ * console.log(`Total chunks: ${stats.data?.storage.total_chunks}`);
756
+ * console.log(`Created today: ${stats.data?.activity.created_today}`);
757
+ * ```
758
+ */
759
+ async getExtendedStats() {
760
+ return this.request('/analytics/stats');
761
+ }
762
+ /**
763
+ * Get topic with its memories
764
+ *
765
+ * @example
766
+ * ```typescript
767
+ * const topic = await client.getTopicWithMemories('topic_123');
768
+ * console.log(topic.data?.memories);
769
+ * ```
770
+ */
771
+ async getTopicWithMemories(topicId, options = {}) {
772
+ const params = new URLSearchParams();
773
+ if (options.limit)
774
+ params.append('limit', String(options.limit));
775
+ if (options.offset)
776
+ params.append('offset', String(options.offset));
777
+ const queryString = params.toString();
778
+ const endpoint = queryString
779
+ ? `/topics/${encodeURIComponent(topicId)}/memories?${queryString}`
780
+ : `/topics/${encodeURIComponent(topicId)}/memories`;
781
+ return this.request(endpoint);
782
+ }
783
+ /**
784
+ * Get topics in hierarchical structure
785
+ *
786
+ * @example
787
+ * ```typescript
788
+ * const topics = await client.getTopicsHierarchy();
789
+ * // Returns nested topic tree with children
790
+ * ```
791
+ */
792
+ async getTopicsHierarchy() {
793
+ return this.request('/topics?include_hierarchy=true');
794
+ }
285
795
  // Utility Methods
286
796
  /**
287
797
  * Update authentication token
@@ -330,64 +840,40 @@ function createMemoryClient(config) {
330
840
  }
331
841
 
332
842
  /**
333
- * Memory types supported by the service
334
- */
335
- const MEMORY_TYPES = ['context', 'project', 'knowledge', 'reference', 'personal', 'workflow'];
336
- /**
337
- * Memory status values
843
+ * Error handling for Memory Client
844
+ * Browser-safe, no Node.js dependencies
338
845
  */
339
- const MEMORY_STATUSES = ['active', 'archived', 'draft', 'deleted'];
340
846
  /**
341
- * Validation schemas using Zod
847
+ * Standardized error codes for programmatic error handling
342
848
  */
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
-
849
+ const ERROR_CODES = [
850
+ 'API_ERROR',
851
+ 'AUTH_ERROR',
852
+ 'VALIDATION_ERROR',
853
+ 'TIMEOUT_ERROR',
854
+ 'RATE_LIMIT_ERROR',
855
+ 'NOT_FOUND',
856
+ 'NETWORK_ERROR',
857
+ 'FORBIDDEN',
858
+ 'CONFLICT',
859
+ 'SERVER_ERROR'
860
+ ];
382
861
  /**
383
- * Error handling for Memory Client
384
- * Browser-safe, no Node.js dependencies
862
+ * Type guard to check if an object is an ApiErrorResponse
385
863
  */
864
+ function isApiErrorResponse(value) {
865
+ return (typeof value === 'object' &&
866
+ value !== null &&
867
+ 'code' in value &&
868
+ 'message' in value &&
869
+ typeof value.code === 'string' &&
870
+ typeof value.message === 'string');
871
+ }
386
872
  /**
387
873
  * Base error class for Memory Client errors
388
874
  */
389
875
  class MemoryClientError extends Error {
390
- constructor(message, code, statusCode, details) {
876
+ constructor(message, code = 'API_ERROR', statusCode, details) {
391
877
  super(message);
392
878
  this.code = code;
393
879
  this.statusCode = statusCode;
@@ -398,6 +884,18 @@ class MemoryClientError extends Error {
398
884
  Error.captureStackTrace(this, MemoryClientError);
399
885
  }
400
886
  }
887
+ /**
888
+ * Convert to ApiErrorResponse for consistent API responses
889
+ */
890
+ toResponse() {
891
+ return {
892
+ code: this.code,
893
+ message: this.message,
894
+ statusCode: this.statusCode,
895
+ details: this.details,
896
+ timestamp: new Date().toISOString()
897
+ };
898
+ }
401
899
  }
402
900
  /**
403
901
  * Network/API error
@@ -407,6 +905,26 @@ class ApiError extends MemoryClientError {
407
905
  super(message, 'API_ERROR', statusCode, details);
408
906
  this.name = 'ApiError';
409
907
  }
908
+ /**
909
+ * Create from an HTTP response
910
+ */
911
+ static fromResponse(status, statusText, body) {
912
+ let message = `HTTP ${status}: ${statusText}`;
913
+ let details = undefined;
914
+ if (body && typeof body === 'object') {
915
+ const bodyObj = body;
916
+ if (typeof bodyObj.error === 'string') {
917
+ message = bodyObj.error;
918
+ }
919
+ else if (typeof bodyObj.message === 'string') {
920
+ message = bodyObj.message;
921
+ }
922
+ if (bodyObj.details) {
923
+ details = bodyObj.details;
924
+ }
925
+ }
926
+ return new ApiError(message, status, details);
927
+ }
410
928
  }
411
929
  /**
412
930
  * Authentication error
@@ -418,12 +936,30 @@ class AuthenticationError extends MemoryClientError {
418
936
  }
419
937
  }
420
938
  /**
421
- * Validation error
939
+ * Validation error with field-level details
422
940
  */
423
941
  class ValidationError extends MemoryClientError {
424
942
  constructor(message, details) {
425
943
  super(message, 'VALIDATION_ERROR', 400, details);
426
944
  this.name = 'ValidationError';
945
+ // Parse validation details into field errors
946
+ this.validationErrors = [];
947
+ if (Array.isArray(details)) {
948
+ this.validationErrors = details.filter((item) => typeof item === 'object' &&
949
+ item !== null &&
950
+ typeof item.field === 'string' &&
951
+ typeof item.message === 'string');
952
+ }
953
+ }
954
+ /**
955
+ * Create from Zod error
956
+ */
957
+ static fromZodError(error) {
958
+ const details = error.issues.map(issue => ({
959
+ field: issue.path.join('.'),
960
+ message: issue.message
961
+ }));
962
+ return new ValidationError('Validation failed', details);
427
963
  }
428
964
  }
429
965
  /**
@@ -436,12 +972,13 @@ class TimeoutError extends MemoryClientError {
436
972
  }
437
973
  }
438
974
  /**
439
- * Rate limit error
975
+ * Rate limit error with retry-after info
440
976
  */
441
977
  class RateLimitError extends MemoryClientError {
442
- constructor(message = 'Rate limit exceeded') {
443
- super(message, 'RATE_LIMIT_ERROR', 429);
978
+ constructor(message = 'Rate limit exceeded', retryAfter) {
979
+ super(message, 'RATE_LIMIT_ERROR', 429, { retryAfter });
444
980
  this.name = 'RateLimitError';
981
+ this.retryAfter = retryAfter;
445
982
  }
446
983
  }
447
984
  /**
@@ -451,6 +988,49 @@ class NotFoundError extends MemoryClientError {
451
988
  constructor(resource) {
452
989
  super(`${resource} not found`, 'NOT_FOUND', 404);
453
990
  this.name = 'NotFoundError';
991
+ this.resource = resource;
992
+ }
993
+ }
994
+ /**
995
+ * Network error (no response received)
996
+ */
997
+ class NetworkError extends MemoryClientError {
998
+ constructor(message = 'Network error') {
999
+ super(message, 'NETWORK_ERROR');
1000
+ this.name = 'NetworkError';
1001
+ }
1002
+ }
1003
+ /**
1004
+ * Server error (5xx responses)
1005
+ */
1006
+ class ServerError extends MemoryClientError {
1007
+ constructor(message, statusCode = 500) {
1008
+ super(message, 'SERVER_ERROR', statusCode);
1009
+ this.name = 'ServerError';
1010
+ }
1011
+ }
1012
+ /**
1013
+ * Create appropriate error class from status code
1014
+ */
1015
+ function createErrorFromStatus(status, message, details) {
1016
+ switch (status) {
1017
+ case 400:
1018
+ return new ValidationError(message, details);
1019
+ case 401:
1020
+ return new AuthenticationError(message);
1021
+ case 404:
1022
+ return new NotFoundError(message);
1023
+ case 408:
1024
+ return new TimeoutError(message);
1025
+ case 429:
1026
+ return new RateLimitError(message);
1027
+ case 500:
1028
+ case 502:
1029
+ case 503:
1030
+ case 504:
1031
+ return new ServerError(message, status);
1032
+ default:
1033
+ return new ApiError(message, status, details);
454
1034
  }
455
1035
  }
456
1036
 
@@ -486,5 +1066,5 @@ const defaultConfigs = {
486
1066
  }
487
1067
  };
488
1068
 
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 };
1069
+ 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
1070
  //# sourceMappingURL=index.js.map