@lanonasis/memory-client 1.0.1 → 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.
@@ -0,0 +1,1027 @@
1
+ import { z } from 'zod';
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
+
219
+ /**
220
+ * Core Memory Client - Pure Browser-Safe Implementation
221
+ *
222
+ * NO Node.js dependencies, NO CLI code, NO child_process
223
+ * Works in: Browser, React Native, Cloudflare Workers, Edge Functions, Deno, Bun
224
+ *
225
+ * Bundle size: ~15KB gzipped
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
+ }
239
+ /**
240
+ * Core Memory Client class for interacting with the Memory as a Service API
241
+ *
242
+ * This is a pure browser-safe client with zero Node.js dependencies.
243
+ * It uses only standard web APIs (fetch, AbortController, etc.)
244
+ */
245
+ class CoreMemoryClient {
246
+ constructor(config) {
247
+ this.config = {
248
+ timeout: 30000,
249
+ ...config
250
+ };
251
+ this.baseHeaders = {
252
+ 'Content-Type': 'application/json',
253
+ 'User-Agent': '@lanonasis/memory-client/2.0.0',
254
+ 'X-Project-Scope': 'lanonasis-maas', // Required by backend auth middleware
255
+ ...config.headers
256
+ };
257
+ // Set authentication headers
258
+ if (config.authToken) {
259
+ this.baseHeaders['Authorization'] = `Bearer ${config.authToken}`;
260
+ }
261
+ else if (config.apiKey) {
262
+ this.baseHeaders['X-API-Key'] = config.apiKey;
263
+ }
264
+ // Add organization ID header if provided
265
+ if (config.organizationId) {
266
+ this.baseHeaders['X-Organization-ID'] = config.organizationId;
267
+ }
268
+ }
269
+ /**
270
+ * Enrich request body with organization context if configured
271
+ * This ensures the API has the organization_id even if not in auth token
272
+ */
273
+ enrichWithOrgContext(body) {
274
+ // If organizationId is configured, include it in the request body
275
+ if (this.config.organizationId && !body.organization_id) {
276
+ return {
277
+ ...body,
278
+ organization_id: this.config.organizationId
279
+ };
280
+ }
281
+ // Fallback to userId if no organizationId configured
282
+ if (!this.config.organizationId && this.config.userId && !body.organization_id) {
283
+ return {
284
+ ...body,
285
+ organization_id: this.config.userId
286
+ };
287
+ }
288
+ return body;
289
+ }
290
+ /**
291
+ * Make an HTTP request to the API with retry support
292
+ */
293
+ async request(endpoint, options = {}) {
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';
298
+ // Call onRequest hook if provided
299
+ if (this.config.onRequest) {
300
+ try {
301
+ this.config.onRequest(endpoint);
302
+ }
303
+ catch (error) {
304
+ console.warn('onRequest hook error:', error);
305
+ }
306
+ }
307
+ // Handle gateway vs direct API URL formatting
308
+ const baseUrl = this.config.apiUrl.includes('/api')
309
+ ? this.config.apiUrl.replace('/api', '')
310
+ : this.config.apiUrl;
311
+ const url = `${baseUrl}/api/v1${endpoint}`;
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) {
355
+ try {
356
+ const duration = Date.now() - startTime;
357
+ this.config.onResponse(endpoint, duration);
358
+ }
359
+ catch (error) {
360
+ console.warn('onResponse hook error:', error);
361
+ }
362
+ }
363
+ return { data, meta: { duration: Date.now() - startTime, retries: attempt } };
364
+ }
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 } };
385
+ }
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;
394
+ }
395
+ if (this.config.onError) {
396
+ try {
397
+ this.config.onError(networkError);
398
+ }
399
+ catch (hookError) {
400
+ console.warn('onError hook error:', hookError);
401
+ }
402
+ }
403
+ return { error: networkError, meta: { duration: Date.now() - startTime, retries: attempt } };
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
+ })) ?? [];
424
+ return {
425
+ error: createErrorResponse('Validation failed', 'VALIDATION_ERROR', 400, details)
426
+ };
427
+ }
428
+ return null;
429
+ }
430
+ /**
431
+ * Test the API connection and authentication
432
+ */
433
+ async healthCheck() {
434
+ return this.request('/health');
435
+ }
436
+ // Memory Operations
437
+ /**
438
+ * Create a new memory with validation
439
+ */
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
+ }
446
+ const enrichedMemory = this.enrichWithOrgContext(memory);
447
+ return this.request('/memory', {
448
+ method: 'POST',
449
+ body: JSON.stringify(enrichedMemory)
450
+ });
451
+ }
452
+ /**
453
+ * Get a memory by ID
454
+ */
455
+ async getMemory(id) {
456
+ return this.request(`/memory/${encodeURIComponent(id)}`);
457
+ }
458
+ /**
459
+ * Update an existing memory with validation
460
+ */
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
+ }
467
+ return this.request(`/memory/${encodeURIComponent(id)}`, {
468
+ method: 'PUT',
469
+ body: JSON.stringify(updates)
470
+ });
471
+ }
472
+ /**
473
+ * Delete a memory
474
+ */
475
+ async deleteMemory(id) {
476
+ return this.request(`/memory/${encodeURIComponent(id)}`, {
477
+ method: 'DELETE'
478
+ });
479
+ }
480
+ /**
481
+ * List memories with optional filtering and pagination
482
+ */
483
+ async listMemories(options = {}) {
484
+ const params = new URLSearchParams();
485
+ Object.entries(options).forEach(([key, value]) => {
486
+ if (value !== undefined && value !== null) {
487
+ if (Array.isArray(value)) {
488
+ params.append(key, value.join(','));
489
+ }
490
+ else {
491
+ params.append(key, String(value));
492
+ }
493
+ }
494
+ });
495
+ const queryString = params.toString();
496
+ const endpoint = queryString ? `/memory?${queryString}` : '/memory';
497
+ return this.request(endpoint);
498
+ }
499
+ /**
500
+ * Search memories using semantic search with validation
501
+ */
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
+ }
509
+ const enrichedRequest = this.enrichWithOrgContext(request);
510
+ return this.request('/memory/search', {
511
+ method: 'POST',
512
+ body: JSON.stringify(enrichedRequest)
513
+ });
514
+ }
515
+ /**
516
+ * Bulk delete multiple memories
517
+ */
518
+ async bulkDeleteMemories(memoryIds) {
519
+ const enrichedRequest = this.enrichWithOrgContext({ memory_ids: memoryIds });
520
+ return this.request('/memory/bulk/delete', {
521
+ method: 'POST',
522
+ body: JSON.stringify(enrichedRequest)
523
+ });
524
+ }
525
+ // Topic Operations
526
+ /**
527
+ * Create a new topic with validation
528
+ */
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
+ }
535
+ const enrichedTopic = this.enrichWithOrgContext(topic);
536
+ return this.request('/topics', {
537
+ method: 'POST',
538
+ body: JSON.stringify(enrichedTopic)
539
+ });
540
+ }
541
+ /**
542
+ * Get all topics
543
+ */
544
+ async getTopics() {
545
+ return this.request('/topics');
546
+ }
547
+ /**
548
+ * Get a topic by ID
549
+ */
550
+ async getTopic(id) {
551
+ return this.request(`/topics/${encodeURIComponent(id)}`);
552
+ }
553
+ /**
554
+ * Update a topic
555
+ */
556
+ async updateTopic(id, updates) {
557
+ return this.request(`/topics/${encodeURIComponent(id)}`, {
558
+ method: 'PUT',
559
+ body: JSON.stringify(updates)
560
+ });
561
+ }
562
+ /**
563
+ * Delete a topic
564
+ */
565
+ async deleteTopic(id) {
566
+ return this.request(`/topics/${encodeURIComponent(id)}`, {
567
+ method: 'DELETE'
568
+ });
569
+ }
570
+ /**
571
+ * Get user memory statistics
572
+ */
573
+ async getMemoryStats() {
574
+ return this.request('/memory/stats');
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
+ }
752
+ // Utility Methods
753
+ /**
754
+ * Update authentication token
755
+ */
756
+ setAuthToken(token) {
757
+ this.baseHeaders['Authorization'] = `Bearer ${token}`;
758
+ delete this.baseHeaders['X-API-Key'];
759
+ }
760
+ /**
761
+ * Update API key
762
+ */
763
+ setApiKey(apiKey) {
764
+ this.baseHeaders['X-API-Key'] = apiKey;
765
+ delete this.baseHeaders['Authorization'];
766
+ }
767
+ /**
768
+ * Clear authentication
769
+ */
770
+ clearAuth() {
771
+ delete this.baseHeaders['Authorization'];
772
+ delete this.baseHeaders['X-API-Key'];
773
+ }
774
+ /**
775
+ * Update configuration
776
+ */
777
+ updateConfig(updates) {
778
+ this.config = { ...this.config, ...updates };
779
+ if (updates.headers) {
780
+ this.baseHeaders = { ...this.baseHeaders, ...updates.headers };
781
+ }
782
+ }
783
+ /**
784
+ * Get current configuration (excluding sensitive data)
785
+ */
786
+ getConfig() {
787
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
788
+ const { apiKey, authToken, ...safeConfig } = this.config;
789
+ return safeConfig;
790
+ }
791
+ }
792
+ /**
793
+ * Factory function to create a new Core Memory Client instance
794
+ */
795
+ function createMemoryClient(config) {
796
+ return new CoreMemoryClient(config);
797
+ }
798
+
799
+ /**
800
+ * Error handling for Memory Client
801
+ * Browser-safe, no Node.js dependencies
802
+ */
803
+ /**
804
+ * Standardized error codes for programmatic error handling
805
+ */
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
+ ];
818
+ /**
819
+ * Type guard to check if an object is an ApiErrorResponse
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
+ }
829
+ /**
830
+ * Base error class for Memory Client errors
831
+ */
832
+ class MemoryClientError extends Error {
833
+ constructor(message, code = 'API_ERROR', statusCode, details) {
834
+ super(message);
835
+ this.code = code;
836
+ this.statusCode = statusCode;
837
+ this.details = details;
838
+ this.name = 'MemoryClientError';
839
+ // Maintains proper stack trace for where our error was thrown (only available on V8)
840
+ if (Error.captureStackTrace) {
841
+ Error.captureStackTrace(this, MemoryClientError);
842
+ }
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
+ }
856
+ }
857
+ /**
858
+ * Network/API error
859
+ */
860
+ class ApiError extends MemoryClientError {
861
+ constructor(message, statusCode, details) {
862
+ super(message, 'API_ERROR', statusCode, details);
863
+ this.name = 'ApiError';
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
+ }
885
+ }
886
+ /**
887
+ * Authentication error
888
+ */
889
+ class AuthenticationError extends MemoryClientError {
890
+ constructor(message = 'Authentication required') {
891
+ super(message, 'AUTH_ERROR', 401);
892
+ this.name = 'AuthenticationError';
893
+ }
894
+ }
895
+ /**
896
+ * Validation error with field-level details
897
+ */
898
+ class ValidationError extends MemoryClientError {
899
+ constructor(message, details) {
900
+ super(message, 'VALIDATION_ERROR', 400, details);
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);
920
+ }
921
+ }
922
+ /**
923
+ * Timeout error
924
+ */
925
+ class TimeoutError extends MemoryClientError {
926
+ constructor(message = 'Request timeout') {
927
+ super(message, 'TIMEOUT_ERROR', 408);
928
+ this.name = 'TimeoutError';
929
+ }
930
+ }
931
+ /**
932
+ * Rate limit error with retry-after info
933
+ */
934
+ class RateLimitError extends MemoryClientError {
935
+ constructor(message = 'Rate limit exceeded', retryAfter) {
936
+ super(message, 'RATE_LIMIT_ERROR', 429, { retryAfter });
937
+ this.name = 'RateLimitError';
938
+ this.retryAfter = retryAfter;
939
+ }
940
+ }
941
+ /**
942
+ * Not found error
943
+ */
944
+ class NotFoundError extends MemoryClientError {
945
+ constructor(resource) {
946
+ super(`${resource} not found`, 'NOT_FOUND', 404);
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);
991
+ }
992
+ }
993
+
994
+ /**
995
+ * @lanonasis/memory-client/core
996
+ *
997
+ * Pure browser-safe Memory Client
998
+ * NO Node.js dependencies, NO CLI code, NO child_process
999
+ * Works in: Browser, React Native, Cloudflare Workers, Edge Functions, Deno, Bun
1000
+ *
1001
+ * Bundle size: ~15KB gzipped
1002
+ */
1003
+ // Client
1004
+ // Constants
1005
+ const VERSION = '2.0.0';
1006
+ const CLIENT_NAME = '@lanonasis/memory-client';
1007
+ // Environment detection (browser-safe)
1008
+ const isBrowser = typeof window !== 'undefined';
1009
+ const isNode = typeof globalThis !== 'undefined' && 'process' in globalThis && globalThis.process?.versions?.node;
1010
+ // Default configurations for different environments
1011
+ const defaultConfigs = {
1012
+ development: {
1013
+ apiUrl: 'http://localhost:3001',
1014
+ timeout: 30000,
1015
+ },
1016
+ production: {
1017
+ apiUrl: 'https://api.lanonasis.com',
1018
+ timeout: 15000,
1019
+ },
1020
+ edge: {
1021
+ apiUrl: 'https://api.lanonasis.com',
1022
+ timeout: 5000, // Lower timeout for edge environments
1023
+ }
1024
+ };
1025
+
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 };
1027
+ //# sourceMappingURL=index.js.map