@soulcraft/brainy 0.63.0 → 1.0.0-rc.2

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.
@@ -984,27 +984,24 @@ export class BrainyData {
984
984
  }
985
985
  }
986
986
  /**
987
- * Add data to the database (literal storage by default)
988
- *
989
- * 🔒 Safe by default: Only stores your data literally without AI processing
990
- * 🧠 AI processing: Set { process: true } or use addSmart() for Neural Import
987
+ * Add data to the database with intelligent processing
991
988
  *
992
989
  * @param vectorOrData Vector or data to add
993
990
  * @param metadata Optional metadata to associate with the data
994
- * @param options Additional options - use { process: true } for AI analysis
991
+ * @param options Additional options for processing
995
992
  * @returns The ID of the added data
996
993
  *
997
994
  * @example
998
- * // Literal storage (safe, no AI processing)
999
- * await brainy.add("API_KEY=secret123")
995
+ * // Auto mode - intelligently decides processing
996
+ * await brainy.add("Customer feedback: Great product!")
1000
997
  *
1001
998
  * @example
1002
- * // With AI processing (explicit opt-in)
1003
- * await brainy.add("John works at Acme Corp", null, { process: true })
999
+ * // Explicit literal mode for sensitive data
1000
+ * await brainy.add("API_KEY=secret123", null, { process: 'literal' })
1004
1001
  *
1005
1002
  * @example
1006
- * // Smart processing (recommended for data analysis)
1007
- * await brainy.addSmart("Customer feedback: Great product!")
1003
+ * // Force neural processing
1004
+ * await brainy.add("John works at Acme Corp", null, { process: 'neural' })
1008
1005
  */
1009
1006
  async add(vectorOrData, metadata, options = {}) {
1010
1007
  await this.ensureInitialized();
@@ -1262,8 +1259,19 @@ export class BrainyData {
1262
1259
  }
1263
1260
  // Invalidate search cache since data has changed
1264
1261
  this.searchCache.invalidateOnDataChange('add');
1265
- // 🧠 AI Processing (Neural Import) - Only if explicitly requested
1266
- if (options.process === true) {
1262
+ // Determine processing mode
1263
+ const processingMode = options.process || 'auto';
1264
+ let shouldProcessNeurally = false;
1265
+ if (processingMode === 'neural') {
1266
+ shouldProcessNeurally = true;
1267
+ }
1268
+ else if (processingMode === 'auto') {
1269
+ // Auto-detect whether to use neural processing
1270
+ shouldProcessNeurally = this.shouldAutoProcessNeurally(vectorOrData, metadata);
1271
+ }
1272
+ // 'literal' mode means no neural processing
1273
+ // 🧠 AI Processing (Neural Import) - Based on processing mode
1274
+ if (shouldProcessNeurally) {
1267
1275
  try {
1268
1276
  // Execute SENSE pipeline (includes Neural Import and other AI augmentations)
1269
1277
  await augmentationPipeline.executeSensePipeline('processRawData', [vectorOrData, typeof vectorOrData === 'string' ? 'text' : 'data'], { mode: ExecutionMode.SEQUENTIAL });
@@ -2285,6 +2293,13 @@ export class BrainyData {
2285
2293
  * @returns Promise that resolves to true if the vector was deleted, false otherwise
2286
2294
  */
2287
2295
  async delete(id, options = {}) {
2296
+ const opts = {
2297
+ service: undefined,
2298
+ soft: true, // Soft delete is default - preserves indexes
2299
+ cascade: false,
2300
+ force: false,
2301
+ ...options
2302
+ };
2288
2303
  // Validate id parameter first, before any other logic
2289
2304
  if (id === null || id === undefined) {
2290
2305
  throw new Error('ID cannot be null or undefined');
@@ -2310,7 +2325,16 @@ export class BrainyData {
2310
2325
  }
2311
2326
  }
2312
2327
  }
2313
- // Remove from index
2328
+ // Handle soft delete vs hard delete
2329
+ if (opts.soft) {
2330
+ // Soft delete: just mark as deleted - metadata filter will exclude from search
2331
+ return await this.updateMetadata(actualId, {
2332
+ deleted: true,
2333
+ deletedAt: new Date().toISOString(),
2334
+ deletedBy: opts.service || 'user'
2335
+ });
2336
+ }
2337
+ // Hard delete: Remove from index
2314
2338
  const removed = this.index.removeItem(actualId);
2315
2339
  if (!removed) {
2316
2340
  return false;
@@ -2318,7 +2342,7 @@ export class BrainyData {
2318
2342
  // Remove from storage
2319
2343
  await this.storage.deleteNoun(actualId);
2320
2344
  // Track deletion statistics
2321
- const service = this.getServiceName(options);
2345
+ const service = this.getServiceName({ service: opts.service });
2322
2346
  await this.storage.decrementStatistic('noun', service);
2323
2347
  // Try to remove metadata (ignore errors)
2324
2348
  try {
@@ -2452,7 +2476,7 @@ export class BrainyData {
2452
2476
  if (relationType === null || relationType === undefined) {
2453
2477
  throw new Error('Relation type cannot be null or undefined');
2454
2478
  }
2455
- return this.addVerb(sourceId, targetId, undefined, {
2479
+ return this._addVerbInternal(sourceId, targetId, undefined, {
2456
2480
  type: relationType,
2457
2481
  metadata: metadata
2458
2482
  });
@@ -2485,7 +2509,7 @@ export class BrainyData {
2485
2509
  *
2486
2510
  * @throws Error if source or target nouns don't exist and autoCreateMissingNouns is false or auto-creation fails
2487
2511
  */
2488
- async addVerb(sourceId, targetId, vector, options = {}) {
2512
+ async _addVerbInternal(sourceId, targetId, vector, options = {}) {
2489
2513
  await this.ensureInitialized();
2490
2514
  // Check if database is in read-only mode
2491
2515
  this.checkReadOnly();
@@ -3176,22 +3200,12 @@ export class BrainyData {
3176
3200
  }
3177
3201
  }
3178
3202
  /**
3179
- * Add data with AI processing enabled by default
3180
- *
3181
- * 🧠 This method automatically enables Neural Import and other AI augmentations
3182
- * for intelligent data understanding, entity detection, and relationship analysis.
3183
- *
3184
- * Use this when you want AI to understand and process your data.
3185
- * Use regular add() when you want literal storage only.
3186
- *
3187
- * @param vectorOrData The data to add (any format)
3188
- * @param metadata Optional metadata to associate with the data
3189
- * @param options Additional options (process defaults to true)
3190
- * @returns The ID of the added data
3203
+ * @deprecated Use add() instead - it's smart by default now
3204
+ * @hidden
3191
3205
  */
3192
3206
  async addSmart(vectorOrData, metadata, options = {}) {
3193
- // Call add() with process=true by default
3194
- return this.add(vectorOrData, metadata, { ...options, process: true });
3207
+ console.warn('⚠️ addSmart() is deprecated. Use add() instead - it\'s smart by default now!');
3208
+ return this.add(vectorOrData, metadata, { ...options, process: 'auto' });
3195
3209
  }
3196
3210
  /**
3197
3211
  * Get the number of nouns in the database (excluding verbs)
@@ -4488,7 +4502,7 @@ export class BrainyData {
4488
4502
  }
4489
4503
  }
4490
4504
  // Add the verb
4491
- await this.addVerb(verb.sourceId, verb.targetId, verb.vector, {
4505
+ await this._addVerbInternal(verb.sourceId, verb.targetId, verb.vector, {
4492
4506
  id: verb.id,
4493
4507
  type: verb.metadata?.verb || VerbType.RelatedTo,
4494
4508
  metadata: verb.metadata
@@ -4662,7 +4676,7 @@ export class BrainyData {
4662
4676
  }
4663
4677
  };
4664
4678
  // Add the verb
4665
- const id = await this.addVerb(sourceId, targetId, undefined, {
4679
+ const id = await this._addVerbInternal(sourceId, targetId, undefined, {
4666
4680
  type: verbType,
4667
4681
  weight: metadata.weight,
4668
4682
  metadata
@@ -4799,24 +4813,488 @@ export class BrainyData {
4799
4813
  prodLog.debug('Cortex integration coming soon');
4800
4814
  }
4801
4815
  /**
4802
- * Set a configuration value in Cortex
4816
+ * Set a configuration value with optional encryption
4803
4817
  * @param key Configuration key
4804
4818
  * @param value Configuration value
4805
4819
  * @param options Options including encryption
4806
4820
  */
4807
4821
  async setConfig(key, value, options) {
4808
- // Cortex integration coming in next release
4809
- prodLog.debug('Cortex integration coming soon');
4822
+ const configNoun = {
4823
+ configKey: key,
4824
+ configValue: options?.encrypt ? await this.encryptData(JSON.stringify(value)) : value,
4825
+ encrypted: !!options?.encrypt,
4826
+ timestamp: new Date().toISOString()
4827
+ };
4828
+ await this.add(configNoun, {
4829
+ nounType: NounType.State,
4830
+ configKey: key,
4831
+ encrypted: !!options?.encrypt
4832
+ });
4810
4833
  }
4811
4834
  /**
4812
- * Get a configuration value from Cortex
4835
+ * Get a configuration value with automatic decryption
4813
4836
  * @param key Configuration key
4814
4837
  * @returns Configuration value or undefined
4815
4838
  */
4816
4839
  async getConfig(key) {
4817
- // Cortex integration coming in next release
4818
- prodLog.debug('Cortex integration coming soon');
4819
- return undefined;
4840
+ try {
4841
+ const results = await this.search('', 1, {
4842
+ nounTypes: [NounType.State],
4843
+ metadata: { configKey: key }
4844
+ });
4845
+ if (results.length === 0)
4846
+ return undefined;
4847
+ const configNoun = results[0];
4848
+ const value = configNoun.data?.configValue || configNoun.metadata?.configValue;
4849
+ const encrypted = configNoun.data?.encrypted || configNoun.metadata?.encrypted;
4850
+ if (encrypted && typeof value === 'string') {
4851
+ const decrypted = await this.decryptData(value);
4852
+ return JSON.parse(decrypted);
4853
+ }
4854
+ return value;
4855
+ }
4856
+ catch (error) {
4857
+ prodLog.debug('Config retrieval failed:', error);
4858
+ return undefined;
4859
+ }
4860
+ }
4861
+ /**
4862
+ * Encrypt data using universal crypto utilities
4863
+ */
4864
+ async encryptData(data) {
4865
+ const crypto = await import('./universal/crypto.js');
4866
+ const key = crypto.randomBytes(32);
4867
+ const iv = crypto.randomBytes(16);
4868
+ const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
4869
+ let encrypted = cipher.update(data, 'utf8', 'hex');
4870
+ encrypted += cipher.final('hex');
4871
+ // Store key and iv with encrypted data (in production, manage keys separately)
4872
+ return JSON.stringify({
4873
+ encrypted,
4874
+ key: Array.from(key).map(b => b.toString(16).padStart(2, '0')).join(''),
4875
+ iv: Array.from(iv).map(b => b.toString(16).padStart(2, '0')).join('')
4876
+ });
4877
+ }
4878
+ /**
4879
+ * Decrypt data using universal crypto utilities
4880
+ */
4881
+ async decryptData(encryptedData) {
4882
+ const crypto = await import('./universal/crypto.js');
4883
+ const { encrypted, key: keyHex, iv: ivHex } = JSON.parse(encryptedData);
4884
+ const key = new Uint8Array(keyHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
4885
+ const iv = new Uint8Array(ivHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
4886
+ const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
4887
+ let decrypted = decipher.update(encrypted, 'hex', 'utf8');
4888
+ decrypted += decipher.final('utf8');
4889
+ return decrypted;
4890
+ }
4891
+ // ========================================
4892
+ // UNIFIED API - Core Methods (7 total)
4893
+ // ONE way to do everything! 🧠⚛️
4894
+ //
4895
+ // 1. add() - Smart data addition (auto/guided/explicit/literal)
4896
+ // 2. search() - Triple-power search (vector + graph + facets)
4897
+ // 3. import() - Neural import with semantic type detection
4898
+ // 4. addNoun() - Explicit noun creation with NounType
4899
+ // 5. addVerb() - Relationship creation between nouns
4900
+ // 6. update() - Update noun data/metadata with index sync
4901
+ // 7. delete() - Smart delete with soft delete default (enhanced original)
4902
+ // ========================================
4903
+ /**
4904
+ * Neural Import - Smart bulk data import with semantic type detection
4905
+ * Uses transformer embeddings to automatically detect and classify data types
4906
+ * @param data Array of data items or single item to import
4907
+ * @param options Import options including type hints and processing mode
4908
+ * @returns Array of created IDs
4909
+ */
4910
+ async import(data, options) {
4911
+ const items = Array.isArray(data) ? data : [data];
4912
+ const results = [];
4913
+ const batchSize = options?.batchSize || 50;
4914
+ // Process in batches to avoid memory issues
4915
+ for (let i = 0; i < items.length; i += batchSize) {
4916
+ const batch = items.slice(i, i + batchSize);
4917
+ for (const item of batch) {
4918
+ try {
4919
+ // Auto-detect type using semantic schema if enabled
4920
+ let detectedType = options?.typeHint;
4921
+ if (options?.autoDetect !== false && !detectedType) {
4922
+ detectedType = await this.detectNounType(item);
4923
+ }
4924
+ // Create metadata with detected type
4925
+ const metadata = {};
4926
+ if (detectedType) {
4927
+ metadata.nounType = detectedType;
4928
+ }
4929
+ // Import item using standard add method
4930
+ const id = await this.add(item, metadata, {
4931
+ process: options?.process || 'auto'
4932
+ });
4933
+ results.push(id);
4934
+ }
4935
+ catch (error) {
4936
+ prodLog.warn(`Failed to import item:`, error);
4937
+ // Continue with next item rather than failing entire batch
4938
+ }
4939
+ }
4940
+ }
4941
+ prodLog.info(`📦 Neural import completed: ${results.length}/${items.length} items imported`);
4942
+ return results;
4943
+ }
4944
+ /**
4945
+ * Add Noun - Explicit noun creation with strongly-typed NounType
4946
+ * For when you know exactly what type of noun you're creating
4947
+ * @param data The noun data
4948
+ * @param nounType The explicit noun type from NounType enum
4949
+ * @param metadata Additional metadata
4950
+ * @returns Created noun ID
4951
+ */
4952
+ async addNoun(data, nounType, metadata) {
4953
+ const nounMetadata = {
4954
+ nounType,
4955
+ ...metadata
4956
+ };
4957
+ return await this.add(data, nounMetadata, {
4958
+ process: 'neural' // Neural mode since type is already known
4959
+ });
4960
+ }
4961
+ /**
4962
+ * Add Verb - Unified relationship creation between nouns
4963
+ * Creates typed relationships with proper vector embeddings from metadata
4964
+ * @param sourceId Source noun ID
4965
+ * @param targetId Target noun ID
4966
+ * @param verbType Relationship type from VerbType enum
4967
+ * @param metadata Additional metadata for the relationship (will be embedded for searchability)
4968
+ * @param weight Relationship weight/strength (0-1, default: 0.5)
4969
+ * @returns Created verb ID
4970
+ */
4971
+ async addVerb(sourceId, targetId, verbType, metadata, weight) {
4972
+ // Validate that source and target nouns exist
4973
+ const sourceNoun = this.index.getNouns().get(sourceId);
4974
+ const targetNoun = this.index.getNouns().get(targetId);
4975
+ if (!sourceNoun) {
4976
+ throw new Error(`Source noun with ID ${sourceId} does not exist`);
4977
+ }
4978
+ if (!targetNoun) {
4979
+ throw new Error(`Target noun with ID ${targetId} does not exist`);
4980
+ }
4981
+ // Create embeddable text from verb type and metadata for searchability
4982
+ let embeddingText = `${verbType} relationship`;
4983
+ // Include meaningful metadata in embedding
4984
+ if (metadata) {
4985
+ const metadataStrings = [];
4986
+ // Add text-based metadata fields for better searchability
4987
+ for (const [key, value] of Object.entries(metadata)) {
4988
+ if (typeof value === 'string' && value.length > 0) {
4989
+ metadataStrings.push(`${key}: ${value}`);
4990
+ }
4991
+ else if (typeof value === 'number' || typeof value === 'boolean') {
4992
+ metadataStrings.push(`${key}: ${value}`);
4993
+ }
4994
+ }
4995
+ if (metadataStrings.length > 0) {
4996
+ embeddingText += ` with ${metadataStrings.join(', ')}`;
4997
+ }
4998
+ }
4999
+ // Generate embedding for the relationship including metadata
5000
+ const vector = await this.embeddingFunction(embeddingText);
5001
+ // Create complete verb metadata
5002
+ const verbMetadata = {
5003
+ verb: verbType,
5004
+ sourceId,
5005
+ targetId,
5006
+ weight: weight || 0.5,
5007
+ embeddingText, // Include the text used for embedding for debugging
5008
+ ...metadata
5009
+ };
5010
+ // Use existing internal addVerb method with proper parameters
5011
+ return await this._addVerbInternal(sourceId, targetId, vector, {
5012
+ type: verbType,
5013
+ weight: weight || 0.5,
5014
+ metadata: verbMetadata,
5015
+ forceEmbed: false // We already have the vector
5016
+ });
5017
+ }
5018
+ /**
5019
+ * Auto-detect whether to use neural processing for data
5020
+ * @private
5021
+ */
5022
+ shouldAutoProcessNeurally(data, metadata) {
5023
+ // Simple heuristics for auto-detection
5024
+ if (typeof data === 'string') {
5025
+ // Long text likely benefits from neural processing
5026
+ if (data.length > 50)
5027
+ return true;
5028
+ // Short text with meaningful content
5029
+ if (data.includes(' ') && data.length > 10)
5030
+ return true;
5031
+ }
5032
+ if (typeof data === 'object' && data !== null) {
5033
+ // Complex objects usually benefit from neural processing
5034
+ if (Object.keys(data).length > 2)
5035
+ return true;
5036
+ // Objects with text content
5037
+ if (data.content || data.text || data.description)
5038
+ return true;
5039
+ }
5040
+ // Check metadata hints
5041
+ if (metadata?.nounType)
5042
+ return true;
5043
+ if (metadata?.needsProcessing)
5044
+ return metadata.needsProcessing;
5045
+ // Default to neural processing for rich data
5046
+ return true;
5047
+ }
5048
+ /**
5049
+ * Detect noun type using semantic analysis
5050
+ * @private
5051
+ */
5052
+ async detectNounType(data) {
5053
+ // Simple heuristic-based detection (could be enhanced with ML)
5054
+ if (typeof data === 'string') {
5055
+ if (data.includes('@') && data.includes('.')) {
5056
+ return NounType.Person; // Email indicates person
5057
+ }
5058
+ if (data.startsWith('http')) {
5059
+ return NounType.Document; // URL indicates document
5060
+ }
5061
+ if (data.length < 100) {
5062
+ return NounType.Concept; // Short text as concept
5063
+ }
5064
+ return NounType.Content; // Default for longer text
5065
+ }
5066
+ if (typeof data === 'object' && data !== null) {
5067
+ if (data.name || data.title) {
5068
+ return NounType.Concept;
5069
+ }
5070
+ if (data.email || data.phone || data.firstName) {
5071
+ return NounType.Person;
5072
+ }
5073
+ if (data.url || data.content || data.body) {
5074
+ return NounType.Document;
5075
+ }
5076
+ if (data.message || data.text) {
5077
+ return NounType.Message;
5078
+ }
5079
+ }
5080
+ return NounType.Content; // Safe default
5081
+ }
5082
+ /**
5083
+ * Get Noun with Connected Verbs - Retrieve noun and all its relationships
5084
+ * Provides complete traversal view of a noun and its connections using existing searchVerbs
5085
+ * @param nounId The noun ID to retrieve
5086
+ * @param options Traversal options
5087
+ * @returns Noun data with connected verbs and related nouns
5088
+ */
5089
+ async getNounWithVerbs(nounId, options) {
5090
+ const opts = {
5091
+ includeIncoming: true,
5092
+ includeOutgoing: true,
5093
+ verbLimit: 50,
5094
+ ...options
5095
+ };
5096
+ // Get the noun
5097
+ const noun = this.index.getNouns().get(nounId);
5098
+ if (!noun) {
5099
+ return null;
5100
+ }
5101
+ const result = {
5102
+ noun: {
5103
+ id: nounId,
5104
+ data: noun.metadata || {}, // Use metadata as data for consistency
5105
+ metadata: noun.metadata || {},
5106
+ nounType: noun.metadata?.nounType
5107
+ },
5108
+ incomingVerbs: [],
5109
+ outgoingVerbs: [],
5110
+ totalConnections: 0
5111
+ };
5112
+ // Use existing searchVerbs functionality - it searches by target/source filters
5113
+ try {
5114
+ if (opts.includeIncoming) {
5115
+ // Search for verbs where this noun is the target
5116
+ const incomingVerbOptions = {
5117
+ verbTypes: opts.verbTypes
5118
+ };
5119
+ const incomingResults = await this.searchVerbs(nounId, opts.verbLimit, incomingVerbOptions);
5120
+ result.incomingVerbs = incomingResults.filter(verb => verb.targetId === nounId || verb.sourceId === nounId);
5121
+ }
5122
+ if (opts.includeOutgoing) {
5123
+ // Search for verbs where this noun is the source
5124
+ const outgoingVerbOptions = {
5125
+ verbTypes: opts.verbTypes
5126
+ };
5127
+ const outgoingResults = await this.searchVerbs(nounId, opts.verbLimit, outgoingVerbOptions);
5128
+ result.outgoingVerbs = outgoingResults.filter(verb => verb.sourceId === nounId || verb.targetId === nounId);
5129
+ }
5130
+ }
5131
+ catch (error) {
5132
+ prodLog.warn(`Error searching verbs for noun ${nounId}:`, error);
5133
+ // Continue with empty arrays
5134
+ }
5135
+ result.totalConnections = result.incomingVerbs.length + result.outgoingVerbs.length;
5136
+ prodLog.debug(`🔍 Retrieved noun ${nounId} with ${result.totalConnections} connections`);
5137
+ return result;
5138
+ }
5139
+ /**
5140
+ * Update - Smart noun update with automatic index synchronization
5141
+ * Updates both data and metadata while maintaining search index integrity
5142
+ * @param id The noun ID to update
5143
+ * @param data New data (optional - if not provided, only metadata is updated)
5144
+ * @param metadata New metadata (merged with existing)
5145
+ * @param options Update options
5146
+ * @returns Success boolean
5147
+ */
5148
+ async update(id, data, metadata, options) {
5149
+ const opts = {
5150
+ merge: true,
5151
+ reindex: true,
5152
+ cascade: false,
5153
+ ...options
5154
+ };
5155
+ // Update data if provided
5156
+ if (data !== undefined) {
5157
+ // For data updates, we need to regenerate the vector
5158
+ const existingNoun = this.index.getNouns().get(id);
5159
+ if (!existingNoun) {
5160
+ throw new Error(`Noun with ID ${id} does not exist`);
5161
+ }
5162
+ // Create new vector for updated data
5163
+ const vector = await this.embeddingFunction(data);
5164
+ // Update the noun with new data and vector
5165
+ const updatedNoun = {
5166
+ ...existingNoun,
5167
+ vector,
5168
+ metadata: opts.merge ? { ...existingNoun.metadata, ...metadata } : metadata
5169
+ };
5170
+ // Update in index
5171
+ this.index.getNouns().set(id, updatedNoun);
5172
+ // Note: HNSW index will be updated automatically on next search
5173
+ // Reindexing happens lazily for performance
5174
+ }
5175
+ else if (metadata !== undefined) {
5176
+ // Metadata-only update using existing updateMetadata method
5177
+ return await this.updateMetadata(id, metadata);
5178
+ }
5179
+ // Update related verbs if cascade enabled
5180
+ if (opts.cascade) {
5181
+ // TODO: Implement cascade verb updates when verb access methods are clarified
5182
+ prodLog.debug(`Cascade update requested for ${id} - feature pending implementation`);
5183
+ }
5184
+ prodLog.debug(`✅ Updated noun ${id} (data: ${data !== undefined}, metadata: ${metadata !== undefined})`);
5185
+ return true;
5186
+ }
5187
+ /**
5188
+ * Preload Transformer Model - Essential for container deployments
5189
+ * Downloads and caches models during initialization to avoid runtime delays
5190
+ * @param options Preload options
5191
+ * @returns Success boolean and model info
5192
+ */
5193
+ static async preloadModel(options) {
5194
+ const opts = {
5195
+ model: 'Xenova/all-MiniLM-L6-v2',
5196
+ cacheDir: './models',
5197
+ device: 'auto',
5198
+ force: false,
5199
+ ...options
5200
+ };
5201
+ try {
5202
+ // Import embedding utilities
5203
+ const { TransformerEmbedding, resolveDevice } = await import('./utils/embedding.js');
5204
+ // Resolve optimal device
5205
+ const device = await resolveDevice(opts.device);
5206
+ prodLog.info(`🤖 Preloading transformer model: ${opts.model}`);
5207
+ prodLog.info(`📁 Cache directory: ${opts.cacheDir}`);
5208
+ prodLog.info(`⚡ Target device: ${device}`);
5209
+ // Create embedder instance with preload settings
5210
+ const embedder = new TransformerEmbedding({
5211
+ model: opts.model,
5212
+ cacheDir: opts.cacheDir,
5213
+ device: device,
5214
+ localFilesOnly: false, // Allow downloads during preload
5215
+ verbose: true
5216
+ });
5217
+ // Initialize and warm up the model
5218
+ await embedder.init();
5219
+ // Test with a small input to fully load the model
5220
+ await embedder.embed('test initialization');
5221
+ // Get model info for container deployments
5222
+ const modelInfo = {
5223
+ success: true,
5224
+ modelPath: opts.cacheDir,
5225
+ modelSize: await this.getModelSize(opts.cacheDir, opts.model),
5226
+ device: device
5227
+ };
5228
+ prodLog.info(`✅ Model preloaded successfully`);
5229
+ prodLog.info(`📊 Model size: ${(modelInfo.modelSize / 1024 / 1024).toFixed(2)}MB`);
5230
+ return modelInfo;
5231
+ }
5232
+ catch (error) {
5233
+ prodLog.error(`❌ Model preload failed:`, error);
5234
+ return {
5235
+ success: false,
5236
+ modelPath: '',
5237
+ modelSize: 0,
5238
+ device: 'cpu'
5239
+ };
5240
+ }
5241
+ }
5242
+ /**
5243
+ * Warmup - Initialize BrainyData with preloaded models (container-optimized)
5244
+ * For production deployments where models should be ready immediately
5245
+ * @param config BrainyData configuration
5246
+ * @param options Warmup options
5247
+ */
5248
+ static async warmup(config, options) {
5249
+ const opts = {
5250
+ preloadModel: true,
5251
+ testEmbedding: true,
5252
+ ...options
5253
+ };
5254
+ prodLog.info(`🚀 Starting Brainy warmup for container deployment`);
5255
+ // Preload transformer models if requested
5256
+ if (opts.preloadModel) {
5257
+ const modelInfo = await BrainyData.preloadModel(opts.modelOptions);
5258
+ if (!modelInfo.success) {
5259
+ prodLog.warn(`⚠️ Model preload failed, continuing with lazy loading`);
5260
+ }
5261
+ }
5262
+ // Create and initialize BrainyData instance
5263
+ const brainy = new BrainyData(config);
5264
+ await brainy.init();
5265
+ // Test embedding to ensure everything works
5266
+ if (opts.testEmbedding) {
5267
+ try {
5268
+ await brainy.embeddingFunction('test warmup embedding');
5269
+ prodLog.info(`✅ Embedding test successful`);
5270
+ }
5271
+ catch (error) {
5272
+ prodLog.warn(`⚠️ Embedding test failed:`, error);
5273
+ }
5274
+ }
5275
+ prodLog.info(`🎉 Brainy warmup complete - ready for production!`);
5276
+ return brainy;
5277
+ }
5278
+ /**
5279
+ * Get model size for deployment info
5280
+ * @private
5281
+ */
5282
+ static async getModelSize(cacheDir, modelName) {
5283
+ try {
5284
+ const fs = await import('fs');
5285
+ const path = await import('path');
5286
+ // Estimate model size (actual implementation would scan cache directory)
5287
+ // For now, return known sizes for common models
5288
+ const modelSizes = {
5289
+ 'Xenova/all-MiniLM-L6-v2': 90 * 1024 * 1024, // ~90MB
5290
+ 'Xenova/all-mpnet-base-v2': 420 * 1024 * 1024, // ~420MB
5291
+ 'Xenova/distilbert-base-uncased': 250 * 1024 * 1024 // ~250MB
5292
+ };
5293
+ return modelSizes[modelName] || 100 * 1024 * 1024; // Default 100MB
5294
+ }
5295
+ catch {
5296
+ return 0;
5297
+ }
4820
5298
  }
4821
5299
  /**
4822
5300
  * Coordinate storage migration across distributed services
@@ -4866,6 +5344,215 @@ export class BrainyData {
4866
5344
  }
4867
5345
  }
4868
5346
  // ===== Augmentation Control Methods =====
5347
+ /**
5348
+ * UNIFIED API METHOD #8: Augment - Complete augmentation management
5349
+ * Register, enable, disable, list, and manage augmentations
5350
+ *
5351
+ * @param action The action to perform or augmentation to register
5352
+ * @param options Additional options for the action
5353
+ * @returns Various return types based on action
5354
+ */
5355
+ augment(action, options) {
5356
+ // If it's an augmentation object, register it
5357
+ if (typeof action === 'object' && 'name' in action && 'type' in action) {
5358
+ augmentationPipeline.register(action);
5359
+ return this;
5360
+ }
5361
+ // Handle string actions
5362
+ switch (action) {
5363
+ case 'list':
5364
+ // Return list of all augmentations with status
5365
+ return this.listAugmentations();
5366
+ case 'enable':
5367
+ // Enable specific augmentation by name
5368
+ if (typeof options === 'string') {
5369
+ this.enableAugmentation(options);
5370
+ }
5371
+ else if (options?.name) {
5372
+ this.enableAugmentation(options.name);
5373
+ }
5374
+ return this;
5375
+ case 'disable':
5376
+ // Disable specific augmentation by name
5377
+ if (typeof options === 'string') {
5378
+ this.disableAugmentation(options);
5379
+ }
5380
+ else if (options?.name) {
5381
+ this.disableAugmentation(options.name);
5382
+ }
5383
+ return this;
5384
+ case 'unregister':
5385
+ // Remove augmentation from pipeline
5386
+ if (typeof options === 'string') {
5387
+ this.unregister(options);
5388
+ }
5389
+ else if (options?.name) {
5390
+ this.unregister(options.name);
5391
+ }
5392
+ return this;
5393
+ case 'enable-type':
5394
+ // Enable all augmentations of a type
5395
+ if (typeof options === 'string') {
5396
+ const validTypes = ['sense', 'conduit', 'cognition', 'memory', 'perception', 'dialog', 'activation', 'webSocket'];
5397
+ if (validTypes.includes(options)) {
5398
+ return this.enableAugmentationType(options);
5399
+ }
5400
+ }
5401
+ else if (options?.type) {
5402
+ const validTypes = ['sense', 'conduit', 'cognition', 'memory', 'perception', 'dialog', 'activation', 'webSocket'];
5403
+ if (validTypes.includes(options.type)) {
5404
+ return this.enableAugmentationType(options.type);
5405
+ }
5406
+ }
5407
+ throw new Error('Invalid augmentation type');
5408
+ case 'disable-type':
5409
+ // Disable all augmentations of a type
5410
+ if (typeof options === 'string') {
5411
+ const validTypes = ['sense', 'conduit', 'cognition', 'memory', 'perception', 'dialog', 'activation', 'webSocket'];
5412
+ if (validTypes.includes(options)) {
5413
+ return this.disableAugmentationType(options);
5414
+ }
5415
+ }
5416
+ else if (options?.type) {
5417
+ const validTypes = ['sense', 'conduit', 'cognition', 'memory', 'perception', 'dialog', 'activation', 'webSocket'];
5418
+ if (validTypes.includes(options.type)) {
5419
+ return this.disableAugmentationType(options.type);
5420
+ }
5421
+ }
5422
+ throw new Error('Invalid augmentation type');
5423
+ default:
5424
+ throw new Error(`Unknown augment action: ${action}`);
5425
+ }
5426
+ }
5427
+ /**
5428
+ * UNIFIED API METHOD #9: Export - Extract your data in various formats
5429
+ * Export your brain's knowledge for backup, migration, or integration
5430
+ *
5431
+ * @param options Export configuration
5432
+ * @returns The exported data in the specified format
5433
+ */
5434
+ async export(options = {}) {
5435
+ const { format = 'json', includeVectors = false, includeMetadata = true, includeRelationships = true, filter = {}, limit } = options;
5436
+ // Get all data with optional filtering
5437
+ const nounsResult = await this.getNouns();
5438
+ const allNouns = nounsResult.items || [];
5439
+ let exportData = [];
5440
+ // Apply filters and limits
5441
+ let nouns = allNouns;
5442
+ if (Object.keys(filter).length > 0) {
5443
+ nouns = allNouns.filter((noun) => {
5444
+ return Object.entries(filter).every(([key, value]) => {
5445
+ return noun.metadata?.[key] === value;
5446
+ });
5447
+ });
5448
+ }
5449
+ if (limit) {
5450
+ nouns = nouns.slice(0, limit);
5451
+ }
5452
+ // Build export data
5453
+ for (const noun of nouns) {
5454
+ const exportItem = {
5455
+ id: noun.id,
5456
+ text: noun.text || noun.metadata?.text || noun.id
5457
+ };
5458
+ if (includeVectors && noun.vector) {
5459
+ exportItem.vector = noun.vector;
5460
+ }
5461
+ if (includeMetadata && noun.metadata) {
5462
+ exportItem.metadata = noun.metadata;
5463
+ }
5464
+ if (includeRelationships) {
5465
+ const relationships = await this.getNounWithVerbs(noun.id);
5466
+ const allVerbs = [
5467
+ ...(relationships?.incomingVerbs || []),
5468
+ ...(relationships?.outgoingVerbs || [])
5469
+ ];
5470
+ if (allVerbs.length > 0) {
5471
+ exportItem.relationships = allVerbs;
5472
+ }
5473
+ }
5474
+ exportData.push(exportItem);
5475
+ }
5476
+ // Format output based on requested format
5477
+ switch (format) {
5478
+ case 'csv':
5479
+ return this.convertToCSV(exportData);
5480
+ case 'graph':
5481
+ return this.convertToGraphFormat(exportData);
5482
+ case 'embeddings':
5483
+ return exportData.map(item => ({
5484
+ id: item.id,
5485
+ vector: item.vector || []
5486
+ }));
5487
+ case 'json':
5488
+ default:
5489
+ return exportData;
5490
+ }
5491
+ }
5492
+ /**
5493
+ * Helper: Convert data to CSV format
5494
+ * @private
5495
+ */
5496
+ convertToCSV(data) {
5497
+ if (data.length === 0)
5498
+ return '';
5499
+ // Get all unique keys
5500
+ const keys = new Set();
5501
+ data.forEach(item => {
5502
+ Object.keys(item).forEach(key => keys.add(key));
5503
+ });
5504
+ // Create header
5505
+ const headers = Array.from(keys);
5506
+ const csv = [headers.join(',')];
5507
+ // Add data rows
5508
+ data.forEach(item => {
5509
+ const row = headers.map(header => {
5510
+ const value = item[header];
5511
+ if (typeof value === 'object') {
5512
+ return JSON.stringify(value);
5513
+ }
5514
+ return value || '';
5515
+ });
5516
+ csv.push(row.join(','));
5517
+ });
5518
+ return csv.join('\n');
5519
+ }
5520
+ /**
5521
+ * Helper: Convert data to graph format
5522
+ * @private
5523
+ */
5524
+ convertToGraphFormat(data) {
5525
+ const nodes = data.map(item => ({
5526
+ id: item.id,
5527
+ label: item.text || item.id,
5528
+ metadata: item.metadata
5529
+ }));
5530
+ const edges = [];
5531
+ data.forEach(item => {
5532
+ if (item.relationships) {
5533
+ item.relationships.forEach((rel) => {
5534
+ edges.push({
5535
+ source: item.id,
5536
+ target: rel.targetId,
5537
+ type: rel.verbType,
5538
+ metadata: rel.metadata
5539
+ });
5540
+ });
5541
+ }
5542
+ });
5543
+ return { nodes, edges };
5544
+ }
5545
+ /**
5546
+ * Unregister an augmentation by name
5547
+ * Remove augmentations from the pipeline
5548
+ *
5549
+ * @param name The name of the augmentation to unregister
5550
+ * @returns The BrainyData instance for chaining
5551
+ */
5552
+ unregister(name) {
5553
+ augmentationPipeline.unregister(name);
5554
+ return this;
5555
+ }
4869
5556
  /**
4870
5557
  * Enable an augmentation by name
4871
5558
  * Universal control for built-in, community, and premium augmentations