@hbarefoot/engram 1.0.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.
- package/LICENSE +21 -0
- package/README.md +535 -0
- package/bin/engram.js +421 -0
- package/dashboard/dist/assets/index-BHkLa5w_.css +1 -0
- package/dashboard/dist/assets/index-D9QR_Cnu.js +45 -0
- package/dashboard/dist/index.html +14 -0
- package/dashboard/package.json +21 -0
- package/package.json +76 -0
- package/src/config/index.js +150 -0
- package/src/embed/index.js +249 -0
- package/src/export/static.js +396 -0
- package/src/extract/rules.js +233 -0
- package/src/extract/secrets.js +114 -0
- package/src/index.js +54 -0
- package/src/memory/consolidate.js +420 -0
- package/src/memory/context.js +346 -0
- package/src/memory/feedback.js +197 -0
- package/src/memory/recall.js +350 -0
- package/src/memory/store.js +626 -0
- package/src/server/mcp.js +668 -0
- package/src/server/rest.js +499 -0
- package/src/utils/id.js +9 -0
- package/src/utils/logger.js +79 -0
- package/src/utils/time.js +296 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
import Fastify from 'fastify';
|
|
2
|
+
import { loadConfig, getDatabasePath, getModelsPath } from '../config/index.js';
|
|
3
|
+
import { initDatabase, createMemory, getMemory, deleteMemory, listMemories, getStats } from '../memory/store.js';
|
|
4
|
+
import { recallMemories } from '../memory/recall.js';
|
|
5
|
+
import { consolidate, getConflicts } from '../memory/consolidate.js';
|
|
6
|
+
import { validateContent } from '../extract/secrets.js';
|
|
7
|
+
import { extractMemory } from '../extract/rules.js';
|
|
8
|
+
import { exportToStatic } from '../export/static.js';
|
|
9
|
+
import * as logger from '../utils/logger.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create and configure the Fastify REST API server
|
|
13
|
+
* @param {Object} config - Engram configuration
|
|
14
|
+
* @returns {Object} Fastify instance
|
|
15
|
+
*/
|
|
16
|
+
export function createRESTServer(config) {
|
|
17
|
+
const fastify = Fastify({
|
|
18
|
+
logger: false, // Use our own logger
|
|
19
|
+
trustProxy: true
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Initialize database
|
|
23
|
+
const db = initDatabase(getDatabasePath(config));
|
|
24
|
+
const modelsPath = getModelsPath(config);
|
|
25
|
+
|
|
26
|
+
// CORS support
|
|
27
|
+
fastify.addHook('onRequest', async (request, reply) => {
|
|
28
|
+
reply.header('Access-Control-Allow-Origin', '*');
|
|
29
|
+
reply.header('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
|
|
30
|
+
reply.header('Access-Control-Allow-Headers', 'Content-Type');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Handle OPTIONS requests
|
|
34
|
+
fastify.options('/*', async (request, reply) => {
|
|
35
|
+
reply.code(204).send();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Health check endpoint
|
|
39
|
+
fastify.get('/health', async (request, reply) => {
|
|
40
|
+
return {
|
|
41
|
+
status: 'healthy',
|
|
42
|
+
timestamp: new Date().toISOString(),
|
|
43
|
+
uptime: process.uptime()
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// System status endpoint
|
|
48
|
+
fastify.get('/api/status', async (request, reply) => {
|
|
49
|
+
try {
|
|
50
|
+
const stats = getStats(db);
|
|
51
|
+
|
|
52
|
+
// Get model info
|
|
53
|
+
let modelInfo;
|
|
54
|
+
try {
|
|
55
|
+
const { getModelInfo } = await import('../embed/index.js');
|
|
56
|
+
modelInfo = getModelInfo(modelsPath);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
modelInfo = {
|
|
59
|
+
name: 'unknown',
|
|
60
|
+
available: false,
|
|
61
|
+
error: error.message
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
status: 'ok',
|
|
67
|
+
memory: {
|
|
68
|
+
total: stats.total,
|
|
69
|
+
withEmbeddings: stats.withEmbeddings,
|
|
70
|
+
byCategory: stats.byCategory,
|
|
71
|
+
byNamespace: stats.byNamespace
|
|
72
|
+
},
|
|
73
|
+
model: {
|
|
74
|
+
name: modelInfo.name,
|
|
75
|
+
available: modelInfo.available,
|
|
76
|
+
cached: modelInfo.cached,
|
|
77
|
+
size: modelInfo.sizeMB,
|
|
78
|
+
path: modelInfo.path
|
|
79
|
+
},
|
|
80
|
+
config: {
|
|
81
|
+
dataDir: config.dataDir,
|
|
82
|
+
defaultNamespace: config.defaults.namespace,
|
|
83
|
+
recallLimit: config.defaults.recallLimit,
|
|
84
|
+
secretDetection: config.security.secretDetection
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
} catch (error) {
|
|
88
|
+
logger.error('Status endpoint error', { error: error.message });
|
|
89
|
+
reply.code(500);
|
|
90
|
+
return { error: error.message };
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Create memory endpoint
|
|
95
|
+
fastify.post('/api/memories', async (request, reply) => {
|
|
96
|
+
try {
|
|
97
|
+
const { content, category, entity, confidence, namespace, tags } = request.body;
|
|
98
|
+
|
|
99
|
+
if (!content) {
|
|
100
|
+
reply.code(400);
|
|
101
|
+
return { error: 'Content is required' };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Validate content for secrets
|
|
105
|
+
const validation = validateContent(content, {
|
|
106
|
+
autoRedact: config.security?.secretDetection !== false
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (!validation.valid) {
|
|
110
|
+
reply.code(400);
|
|
111
|
+
return {
|
|
112
|
+
error: 'Cannot store memory',
|
|
113
|
+
details: validation.errors,
|
|
114
|
+
warnings: validation.warnings
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Auto-extract category and entity if not provided
|
|
119
|
+
let memoryData = {
|
|
120
|
+
content: validation.content,
|
|
121
|
+
category: category || 'fact',
|
|
122
|
+
entity: entity,
|
|
123
|
+
confidence: confidence !== undefined ? confidence : 0.8,
|
|
124
|
+
namespace: namespace || 'default',
|
|
125
|
+
tags: tags || [],
|
|
126
|
+
source: 'api'
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
if (!entity || !category) {
|
|
130
|
+
const extracted = extractMemory(validation.content, {
|
|
131
|
+
source: 'api',
|
|
132
|
+
namespace: namespace || 'default'
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (!entity) {
|
|
136
|
+
memoryData.entity = extracted.entity;
|
|
137
|
+
}
|
|
138
|
+
if (!category) {
|
|
139
|
+
memoryData.category = extracted.category;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Generate embedding
|
|
144
|
+
try {
|
|
145
|
+
const { generateEmbedding } = await import('../embed/index.js');
|
|
146
|
+
const embedding = await generateEmbedding(validation.content, modelsPath);
|
|
147
|
+
memoryData.embedding = embedding;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
logger.warn('Failed to generate embedding', { error: error.message });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Store memory
|
|
153
|
+
const memory = createMemory(db, memoryData);
|
|
154
|
+
|
|
155
|
+
logger.info('Memory created via API', { id: memory.id, category: memory.category });
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
success: true,
|
|
159
|
+
memory: {
|
|
160
|
+
id: memory.id,
|
|
161
|
+
content: memory.content,
|
|
162
|
+
category: memory.category,
|
|
163
|
+
entity: memory.entity,
|
|
164
|
+
confidence: memory.confidence,
|
|
165
|
+
namespace: memory.namespace,
|
|
166
|
+
tags: memory.tags,
|
|
167
|
+
createdAt: memory.created_at
|
|
168
|
+
},
|
|
169
|
+
warnings: validation.warnings
|
|
170
|
+
};
|
|
171
|
+
} catch (error) {
|
|
172
|
+
logger.error('Create memory error', { error: error.message });
|
|
173
|
+
reply.code(500);
|
|
174
|
+
return { error: error.message };
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// List memories endpoint
|
|
179
|
+
fastify.get('/api/memories', async (request, reply) => {
|
|
180
|
+
try {
|
|
181
|
+
const { limit = 50, offset = 0, category, namespace } = request.query;
|
|
182
|
+
|
|
183
|
+
const memories = listMemories(db, {
|
|
184
|
+
limit: parseInt(limit),
|
|
185
|
+
offset: parseInt(offset),
|
|
186
|
+
category,
|
|
187
|
+
namespace
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
success: true,
|
|
192
|
+
memories: memories.map(m => ({
|
|
193
|
+
id: m.id,
|
|
194
|
+
content: m.content,
|
|
195
|
+
category: m.category,
|
|
196
|
+
entity: m.entity,
|
|
197
|
+
confidence: m.confidence,
|
|
198
|
+
namespace: m.namespace,
|
|
199
|
+
tags: m.tags,
|
|
200
|
+
accessCount: m.access_count,
|
|
201
|
+
createdAt: m.created_at,
|
|
202
|
+
lastAccessed: m.last_accessed
|
|
203
|
+
})),
|
|
204
|
+
pagination: {
|
|
205
|
+
limit: parseInt(limit),
|
|
206
|
+
offset: parseInt(offset),
|
|
207
|
+
total: memories.length
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
} catch (error) {
|
|
211
|
+
logger.error('List memories error', { error: error.message });
|
|
212
|
+
reply.code(500);
|
|
213
|
+
return { error: error.message };
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Search/recall memories endpoint
|
|
218
|
+
fastify.post('/api/memories/search', async (request, reply) => {
|
|
219
|
+
try {
|
|
220
|
+
const { query, limit = 5, category, namespace, threshold = 0.3 } = request.body;
|
|
221
|
+
|
|
222
|
+
if (!query) {
|
|
223
|
+
reply.code(400);
|
|
224
|
+
return { error: 'Query is required' };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const memories = await recallMemories(
|
|
228
|
+
db,
|
|
229
|
+
query,
|
|
230
|
+
{ limit, category, namespace, threshold },
|
|
231
|
+
modelsPath
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
success: true,
|
|
236
|
+
query,
|
|
237
|
+
memories: memories.map(m => ({
|
|
238
|
+
id: m.id,
|
|
239
|
+
content: m.content,
|
|
240
|
+
category: m.category,
|
|
241
|
+
entity: m.entity,
|
|
242
|
+
confidence: m.confidence,
|
|
243
|
+
namespace: m.namespace,
|
|
244
|
+
tags: m.tags,
|
|
245
|
+
score: m.score,
|
|
246
|
+
scoreBreakdown: m.scoreBreakdown,
|
|
247
|
+
accessCount: m.access_count,
|
|
248
|
+
createdAt: m.created_at,
|
|
249
|
+
lastAccessed: m.last_accessed
|
|
250
|
+
}))
|
|
251
|
+
};
|
|
252
|
+
} catch (error) {
|
|
253
|
+
logger.error('Search memories error', { error: error.message });
|
|
254
|
+
reply.code(500);
|
|
255
|
+
return { error: error.message };
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Get single memory endpoint
|
|
260
|
+
fastify.get('/api/memories/:id', async (request, reply) => {
|
|
261
|
+
try {
|
|
262
|
+
const { id } = request.params;
|
|
263
|
+
const memory = getMemory(db, id);
|
|
264
|
+
|
|
265
|
+
if (!memory) {
|
|
266
|
+
reply.code(404);
|
|
267
|
+
return { error: 'Memory not found' };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
success: true,
|
|
272
|
+
memory: {
|
|
273
|
+
id: memory.id,
|
|
274
|
+
content: memory.content,
|
|
275
|
+
category: memory.category,
|
|
276
|
+
entity: memory.entity,
|
|
277
|
+
confidence: memory.confidence,
|
|
278
|
+
namespace: memory.namespace,
|
|
279
|
+
tags: memory.tags,
|
|
280
|
+
accessCount: memory.access_count,
|
|
281
|
+
decayRate: memory.decay_rate,
|
|
282
|
+
createdAt: memory.created_at,
|
|
283
|
+
updatedAt: memory.updated_at,
|
|
284
|
+
lastAccessed: memory.last_accessed,
|
|
285
|
+
hasEmbedding: !!memory.embedding
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
} catch (error) {
|
|
289
|
+
logger.error('Get memory error', { error: error.message });
|
|
290
|
+
reply.code(500);
|
|
291
|
+
return { error: error.message };
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Delete memory endpoint
|
|
296
|
+
fastify.delete('/api/memories/:id', async (request, reply) => {
|
|
297
|
+
try {
|
|
298
|
+
const { id } = request.params;
|
|
299
|
+
|
|
300
|
+
// Check if memory exists
|
|
301
|
+
const memory = getMemory(db, id);
|
|
302
|
+
if (!memory) {
|
|
303
|
+
reply.code(404);
|
|
304
|
+
return { error: 'Memory not found' };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Delete the memory
|
|
308
|
+
const deleted = deleteMemory(db, id);
|
|
309
|
+
|
|
310
|
+
if (deleted) {
|
|
311
|
+
logger.info('Memory deleted via API', { id });
|
|
312
|
+
return {
|
|
313
|
+
success: true,
|
|
314
|
+
message: 'Memory deleted successfully',
|
|
315
|
+
deletedMemory: {
|
|
316
|
+
id: memory.id,
|
|
317
|
+
content: memory.content
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
} else {
|
|
321
|
+
reply.code(500);
|
|
322
|
+
return { error: 'Failed to delete memory' };
|
|
323
|
+
}
|
|
324
|
+
} catch (error) {
|
|
325
|
+
logger.error('Delete memory error', { error: error.message });
|
|
326
|
+
reply.code(500);
|
|
327
|
+
return { error: error.message };
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// Consolidate endpoint
|
|
332
|
+
fastify.post('/api/consolidate', async (request, reply) => {
|
|
333
|
+
try {
|
|
334
|
+
const {
|
|
335
|
+
detectDuplicates = true,
|
|
336
|
+
detectContradictions = true,
|
|
337
|
+
applyDecay = true,
|
|
338
|
+
cleanupStale = false
|
|
339
|
+
} = request.body || {};
|
|
340
|
+
|
|
341
|
+
const results = await consolidate(db, {
|
|
342
|
+
detectDuplicates,
|
|
343
|
+
detectContradictions,
|
|
344
|
+
applyDecay,
|
|
345
|
+
cleanupStale
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
logger.info('Consolidation completed via API', results);
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
success: true,
|
|
352
|
+
results: {
|
|
353
|
+
duplicatesRemoved: results.duplicatesRemoved,
|
|
354
|
+
contradictionsDetected: results.contradictionsDetected,
|
|
355
|
+
memoriesDecayed: results.memoriesDecayed,
|
|
356
|
+
staleMemoriesCleaned: results.staleMemoriesCleaned,
|
|
357
|
+
duration: results.duration
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
} catch (error) {
|
|
361
|
+
logger.error('Consolidate error', { error: error.message });
|
|
362
|
+
reply.code(500);
|
|
363
|
+
return { error: error.message };
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// Get conflicts endpoint
|
|
368
|
+
fastify.get('/api/conflicts', async (request, reply) => {
|
|
369
|
+
try {
|
|
370
|
+
const conflicts = getConflicts(db);
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
success: true,
|
|
374
|
+
conflicts: conflicts.map(conflict => ({
|
|
375
|
+
conflictId: conflict.conflictId,
|
|
376
|
+
memories: conflict.memories.map(m => ({
|
|
377
|
+
id: m.id,
|
|
378
|
+
content: m.content,
|
|
379
|
+
category: m.category,
|
|
380
|
+
entity: m.entity,
|
|
381
|
+
confidence: m.confidence,
|
|
382
|
+
createdAt: m.created_at
|
|
383
|
+
}))
|
|
384
|
+
}))
|
|
385
|
+
};
|
|
386
|
+
} catch (error) {
|
|
387
|
+
logger.error('Get conflicts error', { error: error.message });
|
|
388
|
+
reply.code(500);
|
|
389
|
+
return { error: error.message };
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// Get installation info endpoint
|
|
394
|
+
fastify.get('/api/installation-info', async (request, reply) => {
|
|
395
|
+
try {
|
|
396
|
+
const path = await import('path');
|
|
397
|
+
const { fileURLToPath } = await import('url');
|
|
398
|
+
const fs = await import('fs');
|
|
399
|
+
|
|
400
|
+
// Determine installation path
|
|
401
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
402
|
+
const __dirname = path.dirname(__filename);
|
|
403
|
+
const installationPath = path.resolve(__dirname, '../../bin/engram.js');
|
|
404
|
+
|
|
405
|
+
// Verify the path exists
|
|
406
|
+
const exists = fs.existsSync(installationPath);
|
|
407
|
+
|
|
408
|
+
return {
|
|
409
|
+
success: true,
|
|
410
|
+
installation: {
|
|
411
|
+
binPath: installationPath,
|
|
412
|
+
exists,
|
|
413
|
+
platform: process.platform,
|
|
414
|
+
nodeVersion: process.version
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
} catch (error) {
|
|
418
|
+
logger.error('Installation info error', { error: error.message });
|
|
419
|
+
reply.code(500);
|
|
420
|
+
return { error: error.message };
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// Export to static context endpoint
|
|
425
|
+
fastify.post('/api/export/static', async (request, reply) => {
|
|
426
|
+
try {
|
|
427
|
+
const {
|
|
428
|
+
namespace,
|
|
429
|
+
format = 'markdown',
|
|
430
|
+
categories,
|
|
431
|
+
min_confidence = 0.5,
|
|
432
|
+
min_access = 0,
|
|
433
|
+
include_low_feedback = false,
|
|
434
|
+
group_by = 'category',
|
|
435
|
+
header,
|
|
436
|
+
footer
|
|
437
|
+
} = request.body;
|
|
438
|
+
|
|
439
|
+
if (!namespace) {
|
|
440
|
+
reply.code(400);
|
|
441
|
+
return { error: 'Namespace is required' };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const result = exportToStatic(db, {
|
|
445
|
+
namespace,
|
|
446
|
+
format,
|
|
447
|
+
categories,
|
|
448
|
+
minConfidence: min_confidence,
|
|
449
|
+
minAccess: min_access,
|
|
450
|
+
includeLowFeedback: include_low_feedback,
|
|
451
|
+
groupBy: group_by,
|
|
452
|
+
header,
|
|
453
|
+
footer
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
return {
|
|
457
|
+
success: true,
|
|
458
|
+
content: result.content,
|
|
459
|
+
filename: result.filename,
|
|
460
|
+
stats: result.stats
|
|
461
|
+
};
|
|
462
|
+
} catch (error) {
|
|
463
|
+
logger.error('Export error', { error: error.message });
|
|
464
|
+
reply.code(500);
|
|
465
|
+
return { error: error.message };
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// Cleanup on shutdown
|
|
470
|
+
fastify.addHook('onClose', async (instance) => {
|
|
471
|
+
if (db) {
|
|
472
|
+
db.close();
|
|
473
|
+
logger.info('Database connection closed');
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
return fastify;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Start the REST API server
|
|
482
|
+
* @param {Object} config - Engram configuration
|
|
483
|
+
* @param {number} port - Port to listen on
|
|
484
|
+
* @returns {Promise<Object>} Running Fastify instance
|
|
485
|
+
*/
|
|
486
|
+
export async function startRESTServer(config, port = 3838) {
|
|
487
|
+
try {
|
|
488
|
+
const fastify = createRESTServer(config);
|
|
489
|
+
|
|
490
|
+
await fastify.listen({ port, host: '0.0.0.0' });
|
|
491
|
+
|
|
492
|
+
logger.info('REST API server started', { port, url: `http://localhost:${port}` });
|
|
493
|
+
|
|
494
|
+
return fastify;
|
|
495
|
+
} catch (error) {
|
|
496
|
+
logger.error('Failed to start REST server', { error: error.message });
|
|
497
|
+
throw error;
|
|
498
|
+
}
|
|
499
|
+
}
|
package/src/utils/id.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log levels
|
|
3
|
+
*/
|
|
4
|
+
export const LOG_LEVELS = {
|
|
5
|
+
DEBUG: 0,
|
|
6
|
+
INFO: 1,
|
|
7
|
+
WARN: 2,
|
|
8
|
+
ERROR: 3
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Current log level (defaults to INFO)
|
|
13
|
+
*/
|
|
14
|
+
let currentLevel = LOG_LEVELS.INFO;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Set the log level
|
|
18
|
+
* @param {number} level - Log level from LOG_LEVELS
|
|
19
|
+
*/
|
|
20
|
+
export function setLogLevel(level) {
|
|
21
|
+
currentLevel = level;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Format a log message
|
|
26
|
+
* @param {string} level - Log level name
|
|
27
|
+
* @param {string} message - Log message
|
|
28
|
+
* @param {Object} [meta] - Optional metadata
|
|
29
|
+
* @returns {string} Formatted log message
|
|
30
|
+
*/
|
|
31
|
+
function formatMessage(level, message, meta) {
|
|
32
|
+
const timestamp = new Date().toISOString();
|
|
33
|
+
const metaStr = meta ? ` ${JSON.stringify(meta)}` : '';
|
|
34
|
+
return `[${timestamp}] ${level}: ${message}${metaStr}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Log a debug message
|
|
39
|
+
* @param {string} message - Log message
|
|
40
|
+
* @param {Object} [meta] - Optional metadata
|
|
41
|
+
*/
|
|
42
|
+
export function debug(message, meta) {
|
|
43
|
+
if (currentLevel <= LOG_LEVELS.DEBUG) {
|
|
44
|
+
console.error(formatMessage('DEBUG', message, meta));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Log an info message
|
|
50
|
+
* @param {string} message - Log message
|
|
51
|
+
* @param {Object} [meta] - Optional metadata
|
|
52
|
+
*/
|
|
53
|
+
export function info(message, meta) {
|
|
54
|
+
if (currentLevel <= LOG_LEVELS.INFO) {
|
|
55
|
+
console.error(formatMessage('INFO', message, meta));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Log a warning message
|
|
61
|
+
* @param {string} message - Log message
|
|
62
|
+
* @param {Object} [meta] - Optional metadata
|
|
63
|
+
*/
|
|
64
|
+
export function warn(message, meta) {
|
|
65
|
+
if (currentLevel <= LOG_LEVELS.WARN) {
|
|
66
|
+
console.warn(formatMessage('WARN', message, meta));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Log an error message
|
|
72
|
+
* @param {string} message - Log message
|
|
73
|
+
* @param {Object} [meta] - Optional metadata
|
|
74
|
+
*/
|
|
75
|
+
export function error(message, meta) {
|
|
76
|
+
if (currentLevel <= LOG_LEVELS.ERROR) {
|
|
77
|
+
console.error(formatMessage('ERROR', message, meta));
|
|
78
|
+
}
|
|
79
|
+
}
|