@twelvehart/supermemory-runtime 1.0.0-next.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/.env.example +57 -0
- package/README.md +374 -0
- package/dist/index.js +189 -0
- package/dist/mcp/index.js +1132 -0
- package/docker-compose.prod.yml +91 -0
- package/docker-compose.yml +358 -0
- package/drizzle/0000_dapper_the_professor.sql +159 -0
- package/drizzle/0001_api_keys.sql +51 -0
- package/drizzle/meta/0000_snapshot.json +1532 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +20 -0
- package/package.json +114 -0
- package/scripts/add-extraction-job.ts +122 -0
- package/scripts/benchmark-pgvector.ts +122 -0
- package/scripts/bootstrap.sh +209 -0
- package/scripts/check-runtime-pack.ts +111 -0
- package/scripts/claude-mcp-config.ts +336 -0
- package/scripts/docker-entrypoint.sh +183 -0
- package/scripts/doctor.ts +377 -0
- package/scripts/init-db.sql +33 -0
- package/scripts/install.sh +1110 -0
- package/scripts/mcp-setup.ts +271 -0
- package/scripts/migrations/001_create_pgvector_extension.sql +31 -0
- package/scripts/migrations/002_create_memory_embeddings_table.sql +75 -0
- package/scripts/migrations/003_create_hnsw_index.sql +94 -0
- package/scripts/migrations/004_create_memory_embeddings_standalone.sql +70 -0
- package/scripts/migrations/005_create_chunks_table.sql +95 -0
- package/scripts/migrations/006_create_processing_queue.sql +45 -0
- package/scripts/migrations/generate_test_data.sql +42 -0
- package/scripts/migrations/phase1_comprehensive_test.sql +204 -0
- package/scripts/migrations/run_migrations.sh +286 -0
- package/scripts/migrations/test_hnsw_index.sql +255 -0
- package/scripts/pre-commit-secrets +282 -0
- package/scripts/run-extraction-worker.ts +46 -0
- package/scripts/run-phase1-tests.sh +291 -0
- package/scripts/setup.ts +222 -0
- package/scripts/smoke-install.sh +12 -0
- package/scripts/test-health-endpoint.sh +328 -0
- package/src/api/index.ts +2 -0
- package/src/api/middleware/auth.ts +80 -0
- package/src/api/middleware/csrf.ts +308 -0
- package/src/api/middleware/errorHandler.ts +166 -0
- package/src/api/middleware/rateLimit.ts +360 -0
- package/src/api/middleware/validation.ts +514 -0
- package/src/api/routes/documents.ts +286 -0
- package/src/api/routes/profiles.ts +237 -0
- package/src/api/routes/search.ts +71 -0
- package/src/api/stores/index.ts +58 -0
- package/src/config/bootstrap-env.ts +3 -0
- package/src/config/env.ts +71 -0
- package/src/config/feature-flags.ts +25 -0
- package/src/config/index.ts +140 -0
- package/src/config/secrets.config.ts +291 -0
- package/src/db/client.ts +92 -0
- package/src/db/index.ts +73 -0
- package/src/db/postgres.ts +72 -0
- package/src/db/schema/chunks.schema.ts +31 -0
- package/src/db/schema/containers.schema.ts +46 -0
- package/src/db/schema/documents.schema.ts +49 -0
- package/src/db/schema/embeddings.schema.ts +32 -0
- package/src/db/schema/index.ts +11 -0
- package/src/db/schema/memories.schema.ts +72 -0
- package/src/db/schema/profiles.schema.ts +34 -0
- package/src/db/schema/queue.schema.ts +59 -0
- package/src/db/schema/relationships.schema.ts +42 -0
- package/src/db/schema.ts +223 -0
- package/src/db/worker-connection.ts +47 -0
- package/src/index.ts +235 -0
- package/src/mcp/CLAUDE.md +1 -0
- package/src/mcp/index.ts +1380 -0
- package/src/mcp/legacyState.ts +22 -0
- package/src/mcp/rateLimit.ts +358 -0
- package/src/mcp/resources.ts +309 -0
- package/src/mcp/results.ts +104 -0
- package/src/mcp/tools.ts +401 -0
- package/src/queues/config.ts +119 -0
- package/src/queues/index.ts +289 -0
- package/src/sdk/client.ts +225 -0
- package/src/sdk/errors.ts +266 -0
- package/src/sdk/http.ts +560 -0
- package/src/sdk/index.ts +244 -0
- package/src/sdk/resources/base.ts +65 -0
- package/src/sdk/resources/connections.ts +204 -0
- package/src/sdk/resources/documents.ts +163 -0
- package/src/sdk/resources/index.ts +10 -0
- package/src/sdk/resources/memories.ts +150 -0
- package/src/sdk/resources/search.ts +60 -0
- package/src/sdk/resources/settings.ts +36 -0
- package/src/sdk/types.ts +674 -0
- package/src/services/chunking/index.ts +451 -0
- package/src/services/chunking.service.ts +650 -0
- package/src/services/csrf.service.ts +252 -0
- package/src/services/documents.repository.ts +219 -0
- package/src/services/documents.service.ts +191 -0
- package/src/services/embedding.service.ts +404 -0
- package/src/services/extraction.service.ts +300 -0
- package/src/services/extractors/code.extractor.ts +451 -0
- package/src/services/extractors/index.ts +9 -0
- package/src/services/extractors/markdown.extractor.ts +461 -0
- package/src/services/extractors/pdf.extractor.ts +315 -0
- package/src/services/extractors/text.extractor.ts +118 -0
- package/src/services/extractors/url.extractor.ts +243 -0
- package/src/services/index.ts +235 -0
- package/src/services/ingestion.service.ts +177 -0
- package/src/services/llm/anthropic.ts +400 -0
- package/src/services/llm/base.ts +460 -0
- package/src/services/llm/contradiction-detector.service.ts +526 -0
- package/src/services/llm/heuristics.ts +148 -0
- package/src/services/llm/index.ts +309 -0
- package/src/services/llm/memory-classifier.service.ts +383 -0
- package/src/services/llm/memory-extension-detector.service.ts +523 -0
- package/src/services/llm/mock.ts +470 -0
- package/src/services/llm/openai.ts +398 -0
- package/src/services/llm/prompts.ts +438 -0
- package/src/services/llm/types.ts +373 -0
- package/src/services/memory.repository.ts +1769 -0
- package/src/services/memory.service.ts +1338 -0
- package/src/services/memory.types.ts +234 -0
- package/src/services/persistence/index.ts +295 -0
- package/src/services/pipeline.service.ts +509 -0
- package/src/services/profile.repository.ts +436 -0
- package/src/services/profile.service.ts +560 -0
- package/src/services/profile.types.ts +270 -0
- package/src/services/relationships/detector.ts +1128 -0
- package/src/services/relationships/index.ts +268 -0
- package/src/services/relationships/memory-integration.ts +459 -0
- package/src/services/relationships/strategies.ts +132 -0
- package/src/services/relationships/types.ts +370 -0
- package/src/services/search.service.ts +761 -0
- package/src/services/search.types.ts +220 -0
- package/src/services/secrets.service.ts +384 -0
- package/src/services/vectorstore/base.ts +327 -0
- package/src/services/vectorstore/index.ts +444 -0
- package/src/services/vectorstore/memory.ts +286 -0
- package/src/services/vectorstore/migration.ts +295 -0
- package/src/services/vectorstore/mock.ts +403 -0
- package/src/services/vectorstore/pgvector.ts +695 -0
- package/src/services/vectorstore/types.ts +247 -0
- package/src/startup.ts +389 -0
- package/src/types/api.types.ts +193 -0
- package/src/types/document.types.ts +103 -0
- package/src/types/index.ts +241 -0
- package/src/types/profile.base.ts +133 -0
- package/src/utils/errors.ts +447 -0
- package/src/utils/id.ts +15 -0
- package/src/utils/index.ts +101 -0
- package/src/utils/logger.ts +313 -0
- package/src/utils/sanitization.ts +501 -0
- package/src/utils/secret-validation.ts +273 -0
- package/src/utils/synonyms.ts +188 -0
- package/src/utils/validation.ts +581 -0
- package/src/workers/chunking.worker.ts +242 -0
- package/src/workers/embedding.worker.ts +358 -0
- package/src/workers/extraction.worker.ts +346 -0
- package/src/workers/indexing.worker.ts +505 -0
- package/tsconfig.json +38 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Types for Supermemory Clone
|
|
3
|
+
*
|
|
4
|
+
* Defines the core data structures for the memory system including
|
|
5
|
+
* memories, relationships, and classification types.
|
|
6
|
+
*
|
|
7
|
+
* Note: These types extend the base types from ../types/index.ts
|
|
8
|
+
* for use specifically within the memory service layer.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
Memory as BaseMemory,
|
|
13
|
+
MemoryType as BaseMemoryType,
|
|
14
|
+
MemoryRelationship,
|
|
15
|
+
RelationshipType as BaseRelationshipType,
|
|
16
|
+
Entity,
|
|
17
|
+
} from '../types/index.js'
|
|
18
|
+
|
|
19
|
+
// Re-export base types for convenience
|
|
20
|
+
export type { BaseMemory, MemoryRelationship, Entity }
|
|
21
|
+
export type MemoryType = BaseMemoryType
|
|
22
|
+
export type RelationshipType = BaseRelationshipType
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Service-level memory type (compatible with base Memory)
|
|
26
|
+
* Includes additional service-specific fields
|
|
27
|
+
*/
|
|
28
|
+
export interface Memory extends BaseMemory {
|
|
29
|
+
/** Source content this memory was extracted from */
|
|
30
|
+
sourceContent?: string
|
|
31
|
+
|
|
32
|
+
/** Source identifier (URL, document ID, etc.) */
|
|
33
|
+
sourceId?: string
|
|
34
|
+
|
|
35
|
+
/** Confidence score of extraction (0-1) - moved to top level for convenience */
|
|
36
|
+
confidence: number
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Relationship edge between two memories (standalone, for graph storage)
|
|
41
|
+
*/
|
|
42
|
+
export interface Relationship {
|
|
43
|
+
/** Unique identifier */
|
|
44
|
+
id: string
|
|
45
|
+
|
|
46
|
+
/** Source memory ID */
|
|
47
|
+
sourceMemoryId: string
|
|
48
|
+
|
|
49
|
+
/** Target memory ID */
|
|
50
|
+
targetMemoryId: string
|
|
51
|
+
|
|
52
|
+
/** Type of relationship */
|
|
53
|
+
type: BaseRelationshipType
|
|
54
|
+
|
|
55
|
+
/** Confidence score for this relationship (0-1) */
|
|
56
|
+
confidence: number
|
|
57
|
+
|
|
58
|
+
/** Optional description of the relationship */
|
|
59
|
+
description?: string
|
|
60
|
+
|
|
61
|
+
/** Timestamp of creation */
|
|
62
|
+
createdAt: Date
|
|
63
|
+
|
|
64
|
+
/** Additional metadata */
|
|
65
|
+
metadata?: Record<string, unknown>
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Confidence levels for memory extraction and relationships
|
|
70
|
+
*/
|
|
71
|
+
export type ConfidenceLevel = 'high' | 'medium' | 'low'
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Result of memory extraction from content
|
|
75
|
+
*/
|
|
76
|
+
export interface MemoryExtractionResult {
|
|
77
|
+
/** Extracted memories */
|
|
78
|
+
memories: Memory[]
|
|
79
|
+
|
|
80
|
+
/** Raw extraction response (for debugging) */
|
|
81
|
+
rawResponse?: string
|
|
82
|
+
|
|
83
|
+
/** Processing statistics */
|
|
84
|
+
stats: {
|
|
85
|
+
totalExtracted: number
|
|
86
|
+
factsCount: number
|
|
87
|
+
preferencesCount: number
|
|
88
|
+
episodesCount: number
|
|
89
|
+
processingTimeMs: number
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Result of relationship detection
|
|
95
|
+
*/
|
|
96
|
+
export interface RelationshipDetectionResult {
|
|
97
|
+
/** Detected relationships */
|
|
98
|
+
relationships: Relationship[]
|
|
99
|
+
|
|
100
|
+
/** Memories that should be marked as superseded */
|
|
101
|
+
supersededMemoryIds: string[]
|
|
102
|
+
|
|
103
|
+
/** Processing statistics */
|
|
104
|
+
stats: {
|
|
105
|
+
totalRelationships: number
|
|
106
|
+
updatesCount: number
|
|
107
|
+
extendsCount: number
|
|
108
|
+
contradictsCount: number
|
|
109
|
+
processingTimeMs: number
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Update check result
|
|
115
|
+
*/
|
|
116
|
+
export interface UpdateCheckResult {
|
|
117
|
+
/** Whether the new memory updates an existing one */
|
|
118
|
+
isUpdate: boolean
|
|
119
|
+
|
|
120
|
+
/** The existing memory being updated (if applicable) */
|
|
121
|
+
existingMemory?: Memory
|
|
122
|
+
|
|
123
|
+
/** Confidence of the update detection */
|
|
124
|
+
confidence: number
|
|
125
|
+
|
|
126
|
+
/** Reason for the determination */
|
|
127
|
+
reason: string
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Extension check result
|
|
132
|
+
*/
|
|
133
|
+
export interface ExtensionCheckResult {
|
|
134
|
+
/** Whether the new memory extends an existing one */
|
|
135
|
+
isExtension: boolean
|
|
136
|
+
|
|
137
|
+
/** The existing memory being extended (if applicable) */
|
|
138
|
+
existingMemory?: Memory
|
|
139
|
+
|
|
140
|
+
/** Confidence of the extension detection */
|
|
141
|
+
confidence: number
|
|
142
|
+
|
|
143
|
+
/** Reason for the determination */
|
|
144
|
+
reason: string
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Options for memory queries
|
|
149
|
+
*/
|
|
150
|
+
export interface MemoryQueryOptions {
|
|
151
|
+
/** Filter by container tag */
|
|
152
|
+
containerTag?: string
|
|
153
|
+
|
|
154
|
+
/** Filter by memory type */
|
|
155
|
+
type?: BaseMemoryType
|
|
156
|
+
|
|
157
|
+
/** Only return latest versions */
|
|
158
|
+
latestOnly?: boolean
|
|
159
|
+
|
|
160
|
+
/** Minimum confidence threshold */
|
|
161
|
+
minConfidence?: number
|
|
162
|
+
|
|
163
|
+
/** Maximum number of results */
|
|
164
|
+
limit?: number
|
|
165
|
+
|
|
166
|
+
/** Offset for pagination */
|
|
167
|
+
offset?: number
|
|
168
|
+
|
|
169
|
+
/** Sort field */
|
|
170
|
+
sortBy?: 'createdAt' | 'updatedAt' | 'confidence'
|
|
171
|
+
|
|
172
|
+
/** Sort direction */
|
|
173
|
+
sortOrder?: 'asc' | 'desc'
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Options for semantic search
|
|
178
|
+
*/
|
|
179
|
+
export interface SemanticSearchOptions extends MemoryQueryOptions {
|
|
180
|
+
/** Query text to search for */
|
|
181
|
+
query: string
|
|
182
|
+
|
|
183
|
+
/** Similarity threshold (0-1) */
|
|
184
|
+
similarityThreshold?: number
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Configuration for the memory service
|
|
189
|
+
*/
|
|
190
|
+
export interface MemoryServiceConfig {
|
|
191
|
+
/** Default container tag for new memories */
|
|
192
|
+
defaultContainerTag: string
|
|
193
|
+
|
|
194
|
+
/** Minimum confidence threshold for storing memories */
|
|
195
|
+
minConfidenceThreshold: number
|
|
196
|
+
|
|
197
|
+
/** Whether to automatically detect relationships */
|
|
198
|
+
autoDetectRelationships: boolean
|
|
199
|
+
|
|
200
|
+
/** Maximum memories to compare for relationship detection */
|
|
201
|
+
maxRelationshipComparisons: number
|
|
202
|
+
|
|
203
|
+
/** Embedding model configuration */
|
|
204
|
+
embeddingConfig?: {
|
|
205
|
+
model: string
|
|
206
|
+
dimensions: number
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** LLM configuration for extraction */
|
|
210
|
+
llmConfig?: {
|
|
211
|
+
model: string
|
|
212
|
+
temperature: number
|
|
213
|
+
maxTokens: number
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Default configuration values
|
|
219
|
+
*/
|
|
220
|
+
export const DEFAULT_MEMORY_CONFIG: MemoryServiceConfig = {
|
|
221
|
+
defaultContainerTag: 'default',
|
|
222
|
+
minConfidenceThreshold: 0.5,
|
|
223
|
+
autoDetectRelationships: true,
|
|
224
|
+
maxRelationshipComparisons: 100,
|
|
225
|
+
embeddingConfig: {
|
|
226
|
+
model: 'text-embedding-3-small',
|
|
227
|
+
dimensions: 1536,
|
|
228
|
+
},
|
|
229
|
+
llmConfig: {
|
|
230
|
+
model: 'gpt-4o-mini',
|
|
231
|
+
temperature: 0.1,
|
|
232
|
+
maxTokens: 2000,
|
|
233
|
+
},
|
|
234
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistence Layer - Abstract storage interface with multiple backends
|
|
3
|
+
*
|
|
4
|
+
* Provides a consistent interface for storing and retrieving data
|
|
5
|
+
* with support for in-memory, file-based, and database backends.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { writeFile, readFile, mkdir } from 'node:fs/promises'
|
|
9
|
+
import { existsSync } from 'node:fs'
|
|
10
|
+
import { dirname, join } from 'node:path'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Persistence store interface
|
|
14
|
+
*/
|
|
15
|
+
export interface PersistenceStore<T> {
|
|
16
|
+
/** Get all items */
|
|
17
|
+
getAll(): Promise<T[]>
|
|
18
|
+
|
|
19
|
+
/** Get item by ID */
|
|
20
|
+
get(id: string): Promise<T | undefined>
|
|
21
|
+
|
|
22
|
+
/** Save an item (insert or update) */
|
|
23
|
+
save(item: T): Promise<void>
|
|
24
|
+
|
|
25
|
+
/** Save multiple items in batch */
|
|
26
|
+
saveBatch(items: T[]): Promise<void>
|
|
27
|
+
|
|
28
|
+
/** Delete an item by ID */
|
|
29
|
+
delete(id: string): Promise<boolean>
|
|
30
|
+
|
|
31
|
+
/** Clear all items */
|
|
32
|
+
clear(): Promise<void>
|
|
33
|
+
|
|
34
|
+
/** Persist current state (for backends that buffer writes) */
|
|
35
|
+
flush(): Promise<void>
|
|
36
|
+
|
|
37
|
+
/** Get item count */
|
|
38
|
+
count(): Promise<number>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Options for persistence stores
|
|
43
|
+
*/
|
|
44
|
+
export interface PersistenceOptions {
|
|
45
|
+
/** Directory for file-based storage */
|
|
46
|
+
dataDir?: string
|
|
47
|
+
|
|
48
|
+
/** Auto-flush interval in milliseconds (0 to disable) */
|
|
49
|
+
autoFlushIntervalMs?: number
|
|
50
|
+
|
|
51
|
+
/** Namespace/prefix for keys */
|
|
52
|
+
namespace?: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Item with an ID (required for persistence)
|
|
57
|
+
*/
|
|
58
|
+
export interface Identifiable {
|
|
59
|
+
id: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* In-memory persistence store with optional file backup
|
|
64
|
+
*/
|
|
65
|
+
export class MemoryPersistenceStore<T extends Identifiable> implements PersistenceStore<T> {
|
|
66
|
+
protected items: Map<string, T> = new Map()
|
|
67
|
+
protected dirty: boolean = false
|
|
68
|
+
protected options: PersistenceOptions
|
|
69
|
+
protected flushInterval: NodeJS.Timeout | null = null
|
|
70
|
+
protected filePath: string | null = null
|
|
71
|
+
|
|
72
|
+
constructor(
|
|
73
|
+
protected name: string,
|
|
74
|
+
options: PersistenceOptions = {}
|
|
75
|
+
) {
|
|
76
|
+
this.options = options
|
|
77
|
+
|
|
78
|
+
// Set up file path if dataDir is provided
|
|
79
|
+
if (options.dataDir) {
|
|
80
|
+
const namespace = options.namespace ?? 'default'
|
|
81
|
+
this.filePath = join(options.dataDir, namespace, `${name}.json`)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Set up auto-flush interval
|
|
85
|
+
if (options.autoFlushIntervalMs && options.autoFlushIntervalMs > 0) {
|
|
86
|
+
this.flushInterval = setInterval(() => {
|
|
87
|
+
if (this.dirty) {
|
|
88
|
+
this.flush().catch(console.error)
|
|
89
|
+
}
|
|
90
|
+
}, options.autoFlushIntervalMs)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async getAll(): Promise<T[]> {
|
|
95
|
+
return Array.from(this.items.values())
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async get(id: string): Promise<T | undefined> {
|
|
99
|
+
return this.items.get(id)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async save(item: T): Promise<void> {
|
|
103
|
+
this.items.set(item.id, item)
|
|
104
|
+
this.dirty = true
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async saveBatch(items: T[]): Promise<void> {
|
|
108
|
+
for (const item of items) {
|
|
109
|
+
this.items.set(item.id, item)
|
|
110
|
+
}
|
|
111
|
+
this.dirty = true
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async delete(id: string): Promise<boolean> {
|
|
115
|
+
const existed = this.items.has(id)
|
|
116
|
+
this.items.delete(id)
|
|
117
|
+
if (existed) {
|
|
118
|
+
this.dirty = true
|
|
119
|
+
}
|
|
120
|
+
return existed
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async clear(): Promise<void> {
|
|
124
|
+
this.items.clear()
|
|
125
|
+
this.dirty = true
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async flush(): Promise<void> {
|
|
129
|
+
if (!this.filePath || !this.dirty) return
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
// Ensure directory exists
|
|
133
|
+
const dir = dirname(this.filePath)
|
|
134
|
+
if (!existsSync(dir)) {
|
|
135
|
+
await mkdir(dir, { recursive: true })
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Write data to file
|
|
139
|
+
const data = JSON.stringify(
|
|
140
|
+
{
|
|
141
|
+
version: 1,
|
|
142
|
+
name: this.name,
|
|
143
|
+
items: Array.from(this.items.values()),
|
|
144
|
+
savedAt: new Date().toISOString(),
|
|
145
|
+
},
|
|
146
|
+
null,
|
|
147
|
+
2
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
await writeFile(this.filePath, data, 'utf-8')
|
|
151
|
+
this.dirty = false
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error(`[Persistence] Failed to flush ${this.name}:`, error)
|
|
154
|
+
throw error
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async count(): Promise<number> {
|
|
159
|
+
return this.items.size
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Load data from file (call during initialization)
|
|
164
|
+
*/
|
|
165
|
+
async load(): Promise<void> {
|
|
166
|
+
if (!this.filePath) return
|
|
167
|
+
|
|
168
|
+
if (!existsSync(this.filePath)) {
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const content = await readFile(this.filePath, 'utf-8')
|
|
174
|
+
const data = JSON.parse(content) as {
|
|
175
|
+
version: number
|
|
176
|
+
name: string
|
|
177
|
+
items: T[]
|
|
178
|
+
savedAt: string
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
this.items.clear()
|
|
182
|
+
for (const item of data.items) {
|
|
183
|
+
this.items.set(item.id, item)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
console.log(`[Persistence] Loaded ${this.items.size} items from ${this.name}`)
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error(`[Persistence] Failed to load ${this.name}:`, error)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Export all data (for backup)
|
|
194
|
+
*/
|
|
195
|
+
export(): T[] {
|
|
196
|
+
return Array.from(this.items.values())
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Import data (for restore)
|
|
201
|
+
*/
|
|
202
|
+
async import(items: T[]): Promise<void> {
|
|
203
|
+
for (const item of items) {
|
|
204
|
+
this.items.set(item.id, item)
|
|
205
|
+
}
|
|
206
|
+
this.dirty = true
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Cleanup resources
|
|
211
|
+
*/
|
|
212
|
+
async destroy(): Promise<void> {
|
|
213
|
+
if (this.flushInterval) {
|
|
214
|
+
clearInterval(this.flushInterval)
|
|
215
|
+
this.flushInterval = null
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Final flush
|
|
219
|
+
if (this.dirty) {
|
|
220
|
+
await this.flush()
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Persistence store factory
|
|
227
|
+
*/
|
|
228
|
+
export class PersistenceFactory {
|
|
229
|
+
private static stores: Map<string, PersistenceStore<unknown>> = new Map()
|
|
230
|
+
private static defaultOptions: PersistenceOptions = {}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Configure default options for all stores
|
|
234
|
+
*/
|
|
235
|
+
static configure(options: PersistenceOptions): void {
|
|
236
|
+
this.defaultOptions = { ...this.defaultOptions, ...options }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Get or create a persistence store
|
|
241
|
+
*/
|
|
242
|
+
static getStore<T extends Identifiable>(name: string, options?: PersistenceOptions): MemoryPersistenceStore<T> {
|
|
243
|
+
const key = `${options?.namespace ?? 'default'}:${name}`
|
|
244
|
+
|
|
245
|
+
if (!this.stores.has(key)) {
|
|
246
|
+
const store = new MemoryPersistenceStore<T>(name, {
|
|
247
|
+
...this.defaultOptions,
|
|
248
|
+
...options,
|
|
249
|
+
})
|
|
250
|
+
this.stores.set(key, store as unknown as PersistenceStore<unknown>)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return this.stores.get(key) as MemoryPersistenceStore<T>
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Flush all stores
|
|
258
|
+
*/
|
|
259
|
+
static async flushAll(): Promise<void> {
|
|
260
|
+
const promises: Promise<void>[] = []
|
|
261
|
+
for (const store of this.stores.values()) {
|
|
262
|
+
promises.push(store.flush())
|
|
263
|
+
}
|
|
264
|
+
await Promise.all(promises)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Destroy all stores and cleanup resources
|
|
269
|
+
*/
|
|
270
|
+
static async destroyAll(): Promise<void> {
|
|
271
|
+
for (const store of this.stores.values()) {
|
|
272
|
+
if ('destroy' in store && typeof store.destroy === 'function') {
|
|
273
|
+
await (store as { destroy: () => Promise<void> }).destroy()
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
this.stores.clear()
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Configure persistence based on environment
|
|
282
|
+
*/
|
|
283
|
+
export function configurePersistence(options?: PersistenceOptions): void {
|
|
284
|
+
const defaultDataDir = process.env.SUPERMEMORY_DATA_DIR ?? './data'
|
|
285
|
+
const autoFlush = process.env.SUPERMEMORY_AUTO_FLUSH !== 'false'
|
|
286
|
+
|
|
287
|
+
PersistenceFactory.configure({
|
|
288
|
+
dataDir: options?.dataDir ?? defaultDataDir,
|
|
289
|
+
autoFlushIntervalMs: autoFlush ? (options?.autoFlushIntervalMs ?? 30000) : 0,
|
|
290
|
+
...options,
|
|
291
|
+
})
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Export convenience types
|
|
295
|
+
export type { Identifiable as PersistableItem }
|