From 42159f2bd788a6ae8d44b9b4ae1737bebb96e659 Mon Sep 17 00:00:00 2001 From: David Snelling Date: Tue, 9 Jun 2026 16:46:16 -0700 Subject: [PATCH] chore(8.0): collapse dead defensive guards + redundant polyfills MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the browser/cloud/threading sweep — everything that was guarding against unreachable runtimes is now dead. cacheManager.ts: Environment enum + this.environment field removed (only NODE was reachable). StorageType narrowed to MEMORY + FILESYSTEM. navigator.deviceMemory and performance.memory paths in detectOptimalCacheSize + detectAvailableMemory deleted; node:os is the sole source. environmentConfig keeps the index signature for future per-runtime tuning but only the node slot is wired. Dead 'typeof window === undefined' guards (the check is always true on 8.0): paramValidation, structuredLogger, mutex, brainy.ts stats block, and networkTransport ws-dynamic-import all collapsed. IntegrationLoader's inverse guard ('!== undefined' returning 'browser') deleted. mutex's createMutex default type simplified from "(typeof window === 'undefined' ? 'file' : 'memory')" to plain 'file'. Redundant TextEncoder/TextDecoder polyfills: Node 22+ ships both as globals. src/utils/textEncoding.ts deleted (applyTensorFlowPatch was named for a defunct dep and only re-assigned globals already present). setup.ts collapsed to an empty stable import target. unified.ts loses its applyTensorFlowPatch re-export. modelAutoConfig.getModelPath() loses the unreachable trailing fallback now that isNode() is effectively the only branch the function ever takes. jsonProcessing.ts left unchanged — its 'typeof document' checks are value-shape guards on the parsed JSON, not environment detection. --- src/brainy.ts | 14 +- src/config/modelAutoConfig.ts | 10 +- src/distributed/networkTransport.ts | 11 +- src/integrations/core/IntegrationLoader.ts | 5 - src/setup.ts | 33 +-- src/storage/cacheManager.ts | 323 ++++++--------------- src/unified.ts | 1 - src/utils/mutex.ts | 22 +- src/utils/paramValidation.ts | 15 +- src/utils/structuredLogger.ts | 15 +- src/utils/textEncoding.ts | 69 ----- 11 files changed, 121 insertions(+), 397 deletions(-) delete mode 100644 src/utils/textEncoding.ts diff --git a/src/brainy.ts b/src/brainy.ts index c25daecc..21556db0 100644 --- a/src/brainy.ts +++ b/src/brainy.ts @@ -5231,14 +5231,12 @@ export class Brainy implements BrainyInterface { // Get system memory info let freeMemory = 0 let totalMemory = 0 - if (typeof window === 'undefined') { - try { - const os = require('node:os') - freeMemory = os.freemem() - totalMemory = os.totalmem() - } catch (e) { - // OS module not available - } + try { + const os = require('node:os') + freeMemory = os.freemem() + totalMemory = os.totalmem() + } catch (e) { + // OS module not available } const stats = { diff --git a/src/config/modelAutoConfig.ts b/src/config/modelAutoConfig.ts index 08da4ad4..22ca949a 100644 --- a/src/config/modelAutoConfig.ts +++ b/src/config/modelAutoConfig.ts @@ -65,11 +65,7 @@ export function getModelPath(): string { return '/tmp/.brainy/models' } - // Node.js - use home directory for persistent storage - if (isNode()) { - const homeDir = process.env.HOME || process.env.USERPROFILE || '~' - return `${homeDir}/.brainy/models` - } - - return './.brainy/models' + // Node-like runtime - use home directory for persistent storage + const homeDir = process.env.HOME || process.env.USERPROFILE || '~' + return `${homeDir}/.brainy/models` } diff --git a/src/distributed/networkTransport.ts b/src/distributed/networkTransport.ts index 816db853..f18cc9cf 100644 --- a/src/distributed/networkTransport.ts +++ b/src/distributed/networkTransport.ts @@ -73,13 +73,10 @@ export class NetworkTransport extends EventEmitter { */ async start(): Promise { if (this.isRunning) return - - // Dynamic import for Node.js environment - if (typeof window === 'undefined') { - const ws = await import('ws') - WebSocketServer = (ws as any).WebSocketServer || (ws as any).Server || ws.default - } - + + const ws = await import('ws') + WebSocketServer = (ws as any).WebSocketServer || (ws as any).Server || ws.default + await this.startHTTPServer() await this.startWebSocketServer() await this.discoverPeers() diff --git a/src/integrations/core/IntegrationLoader.ts b/src/integrations/core/IntegrationLoader.ts index 86a44df0..c73d6b79 100644 --- a/src/integrations/core/IntegrationLoader.ts +++ b/src/integrations/core/IntegrationLoader.ts @@ -103,11 +103,6 @@ export function detectEnvironment(): RuntimeEnvironment { return 'node' } - // Browser - if (typeof window !== 'undefined') { - return 'browser' - } - return 'node' } diff --git a/src/setup.ts b/src/setup.ts index 97e4a220..8c9d3dda 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -1,31 +1,12 @@ /** - * Brainy Setup - Minimal Polyfills + * Brainy Setup * * ARCHITECTURE: - * Brainy uses Candle WASM (Rust-based) for embeddings. - * No transformers.js or ONNX Runtime dependency, no hacks required. + * Brainy 8.0 runs only on Node-like runtimes (Node.js 22+, Bun, Deno). All + * supported runtimes ship `TextEncoder` and `TextDecoder` as global built-ins, + * so no polyfills are required. * - * This file provides minimal polyfills for cross-environment compatibility: - * - TextEncoder/TextDecoder for older environments - * - * BENEFITS: - * - Clean codebase with no workarounds - * - Works everywhere: Node.js, Bun, Bun --compile, browsers, Deno - * - No platform-specific binaries - * - Model bundled in package (no runtime downloads) + * This module is intentionally side-effect-only and currently empty. It is kept + * as a stable import target in case future runtime initialization is needed. */ - -// ============================================================================ -// TextEncoder/TextDecoder Polyfills -// ============================================================================ -const globalObj = globalThis ?? global ?? self - -if (globalObj) { - if (!globalObj.TextEncoder) globalObj.TextEncoder = TextEncoder - if (!globalObj.TextDecoder) globalObj.TextDecoder = TextDecoder - ;(globalObj as any).__TextEncoder__ = TextEncoder - ;(globalObj as any).__TextDecoder__ = TextDecoder -} - -import { applyTensorFlowPatch } from './utils/textEncoding.js' -applyTensorFlowPatch() +export {} diff --git a/src/storage/cacheManager.ts b/src/storage/cacheManager.ts index 5575f079..cfeb61d7 100644 --- a/src/storage/cacheManager.ts +++ b/src/storage/cacheManager.ts @@ -10,21 +10,6 @@ import { HNSWNoun, GraphVerb, HNSWVerb } from '../coreTypes.js' import { BrainyError } from '../errors/brainyError.js' -// Extend Navigator interface to include deviceMemory property -// and WorkerGlobalScope to include storage property -declare global { - interface Navigator { - deviceMemory?: number; - } - - interface WorkerGlobalScope { - storage?: { - getDirectory?: () => Promise; - [key: string]: any; - }; - } -} - // Type aliases for better readability type HNSWNode = HNSWNoun type Edge = GraphVerb @@ -52,20 +37,11 @@ interface CacheStats { warmCacheMisses: number } -// Environment detection for storage selection -enum Environment { - BROWSER, - NODE, - WORKER -} - -// Storage type for warm and cold caches +// Storage type for warm and cold caches. Brainy 8.0 only ships filesystem + +// memory tiers, but the enum is retained for diagnostic/log output. enum StorageType { MEMORY, - OPFS, - FILESYSTEM, - S3, - REMOTE_API + FILESYSTEM } /** @@ -90,8 +66,7 @@ export class CacheManager { warmCacheMisses: 0 } - // Environment and storage configuration - private environment: Environment + // Storage configuration private warmStorageType: StorageType private coldStorageType: StorageType @@ -127,19 +102,7 @@ export class CacheManager { hotCacheEvictionThreshold?: number warmCacheTTL?: number batchSize?: number - }, - browser?: { - hotCacheMaxSize?: number - hotCacheEvictionThreshold?: number - warmCacheTTL?: number - batchSize?: number - }, - worker?: { - hotCacheMaxSize?: number - hotCacheEvictionThreshold?: number - warmCacheTTL?: number - batchSize?: number - }, + } [key: string]: { hotCacheMaxSize?: number hotCacheEvictionThreshold?: number @@ -148,7 +111,7 @@ export class CacheManager { } | undefined } } - + /** * Initialize the cache manager * @param options Configuration options @@ -168,19 +131,7 @@ export class CacheManager { hotCacheEvictionThreshold?: number warmCacheTTL?: number batchSize?: number - }, - browser?: { - hotCacheMaxSize?: number - hotCacheEvictionThreshold?: number - warmCacheTTL?: number - batchSize?: number - }, - worker?: { - hotCacheMaxSize?: number - hotCacheEvictionThreshold?: number - warmCacheTTL?: number - batchSize?: number - }, + } [key: string]: { hotCacheMaxSize?: number hotCacheEvictionThreshold?: number @@ -191,39 +142,37 @@ export class CacheManager { } = {}) { // Store options for later reference this.options = options - - // Detect environment - this.environment = this.detectEnvironment() - - // Set storage types based on environment + + // Set storage types (Brainy 8.0 ships filesystem only) this.warmStorageType = this.detectWarmStorageType() this.coldStorageType = this.detectColdStorageType() - + // Initialize storage adapters this.warmStorage = options.warmStorage || this.initializeWarmStorage() this.coldStorage = options.coldStorage || this.initializeColdStorage() - + // Set auto-tuning flag this.autoTune = options.autoTune !== undefined ? options.autoTune : true - - // Get environment-specific configuration if available - const envConfig = options.environmentConfig?.[Environment[this.environment].toLowerCase()] - + + // Brainy 8.0 only runs on Node-like runtimes, so only the `node` slot of + // environmentConfig is honored. + const envConfig = options.environmentConfig?.node + // Set default values or use environment-specific values or global values this.hotCacheMaxSize = envConfig?.hotCacheMaxSize || options.hotCacheMaxSize || this.detectOptimalCacheSize() this.hotCacheEvictionThreshold = envConfig?.hotCacheEvictionThreshold || options.hotCacheEvictionThreshold || 0.8 this.warmCacheTTL = envConfig?.warmCacheTTL || options.warmCacheTTL || 24 * 60 * 60 * 1000 // 24 hours this.batchSize = envConfig?.batchSize || options.batchSize || 10 - + // If auto-tuning is enabled, perform initial tuning if (this.autoTune) { this.tuneParameters() } - + // Log configuration if (process.env.DEBUG) { console.log('Cache Manager initialized with configuration:', { - environment: Environment[this.environment], + environment: 'node', hotCacheMaxSize: this.hotCacheMaxSize, hotCacheEvictionThreshold: this.hotCacheEvictionThreshold, warmCacheTTL: this.warmCacheTTL, @@ -234,21 +183,8 @@ export class CacheManager { }) } } - - /** - * Detect the current environment - */ - private detectEnvironment(): Environment { - if (typeof window !== 'undefined' && typeof document !== 'undefined') { - return Environment.BROWSER - } else if (typeof self !== 'undefined' && typeof window === 'undefined') { - // In a worker environment, self is defined but window is not - return Environment.WORKER - } else { - return Environment.NODE - } - } - + + /** * Detect the optimal cache size based on available memory and operating mode * @@ -262,111 +198,65 @@ export class CacheManager { try { // Default to a conservative value const defaultSize = 1000 - + // Get the total dataset size if available - const totalItems = this.storageStatistics ? + const totalItems = this.storageStatistics ? (this.storageStatistics.totalNodes || 0) + (this.storageStatistics.totalEdges || 0) : 0 - + // Determine if we're dealing with a large dataset (>100K items) const isLargeDataset = totalItems > 100000 - + // Check if we're in read-only mode (from parent Brainy instance) const isReadOnly = this.options?.readOnly || false - - // In Node.js, use available system memory with enhanced allocation - if (this.environment === Environment.NODE) { - try { - // For ES module compatibility, we'll use a fixed default value - // since we can't use dynamic imports in a synchronous function - - // Use conservative defaults that don't require OS module - // These values are reasonable for most systems - const estimatedTotalMemory = 8 * 1024 * 1024 * 1024 // Assume 8GB total - const estimatedFreeMemory = 4 * 1024 * 1024 * 1024 // Assume 4GB free - - // Estimate average entry size (in bytes) - // This is a conservative estimate for complex objects with vectors - const ESTIMATED_BYTES_PER_ENTRY = 1024 // 1KB per entry - - // Base memory percentage - 10% by default - let memoryPercentage = 0.1 - - // Adjust based on operating mode and dataset size - if (isReadOnly) { - // In read-only mode, we can use more memory for caching - memoryPercentage = 0.25 // 25% of free memory - - // For large datasets in read-only mode, be even more aggressive - if (isLargeDataset) { - memoryPercentage = 0.4 // 40% of free memory - } - } else if (isLargeDataset) { - // For large datasets in normal mode, increase slightly - memoryPercentage = 0.15 // 15% of free memory - } - - // Calculate optimal size based on adjusted percentage - const optimalSize = Math.max( - Math.floor(estimatedFreeMemory * memoryPercentage / ESTIMATED_BYTES_PER_ENTRY), - 1000 - ) - - // If we know the total dataset size, cap at a reasonable percentage - if (totalItems > 0) { - // In read-only mode, we can cache a larger percentage - const maxPercentage = isReadOnly ? 0.5 : 0.3 - const maxItems = Math.ceil(totalItems * maxPercentage) - - // Return the smaller of the two to avoid excessive memory usage - return Math.min(optimalSize, maxItems) - } - - return optimalSize - } catch (error) { - console.warn('Failed to detect optimal cache size:', error) - return defaultSize - } - } - - // In browser, use navigator.deviceMemory with enhanced allocation - if (this.environment === Environment.BROWSER && navigator.deviceMemory) { - // Base entries per GB - let entriesPerGB = 500 - + + try { + // Synchronous path can't use dynamic imports, so we use conservative + // assumed defaults. The async variant (detectAvailableMemory) reads + // real values via `node:os` when possible. + const estimatedFreeMemory = 4 * 1024 * 1024 * 1024 // Assume 4GB free + + // Estimate average entry size (in bytes) + // This is a conservative estimate for complex objects with vectors + const ESTIMATED_BYTES_PER_ENTRY = 1024 // 1KB per entry + + // Base memory percentage - 10% by default + let memoryPercentage = 0.1 + // Adjust based on operating mode and dataset size if (isReadOnly) { - entriesPerGB = 800 // More aggressive caching in read-only mode - + // In read-only mode, we can use more memory for caching + memoryPercentage = 0.25 // 25% of free memory + + // For large datasets in read-only mode, be even more aggressive if (isLargeDataset) { - entriesPerGB = 1000 // Even more aggressive for large datasets + memoryPercentage = 0.4 // 40% of free memory } } else if (isLargeDataset) { - entriesPerGB = 600 // Slightly more aggressive for large datasets + // For large datasets in normal mode, increase slightly + memoryPercentage = 0.15 // 15% of free memory } - - // Calculate based on device memory - const browserCacheSize = Math.max(navigator.deviceMemory * entriesPerGB, 1000) - + + // Calculate optimal size based on adjusted percentage + const optimalSize = Math.max( + Math.floor(estimatedFreeMemory * memoryPercentage / ESTIMATED_BYTES_PER_ENTRY), + 1000 + ) + // If we know the total dataset size, cap at a reasonable percentage if (totalItems > 0) { // In read-only mode, we can cache a larger percentage - const maxPercentage = isReadOnly ? 0.4 : 0.25 + const maxPercentage = isReadOnly ? 0.5 : 0.3 const maxItems = Math.ceil(totalItems * maxPercentage) - + // Return the smaller of the two to avoid excessive memory usage - return Math.min(browserCacheSize, maxItems) + return Math.min(optimalSize, maxItems) } - - return browserCacheSize + + return optimalSize + } catch (error) { + console.warn('Failed to detect optimal cache size:', error) + return defaultSize } - - // For worker environments or when memory detection fails - if (this.environment === Environment.WORKER) { - // Workers typically have limited memory, be conservative - return isReadOnly ? 2000 : 1000 - } - - return defaultSize } catch (error) { console.warn('Error detecting optimal cache size:', error) return 1000 // Conservative default @@ -448,82 +338,31 @@ export class CacheManager { } /** - * Detects available memory across different environments - * - * This method uses different techniques to detect memory in: - * - Node.js: Uses the OS module with dynamic import - * - Browser: Uses performance.memory or navigator.deviceMemory - * - Worker: Uses performance.memory if available - * + * Detect available memory using Node-like runtime primitives. + * + * Brainy 8.0 only runs on Node.js, Bun, and Deno, so this method reads real + * values from `node:os` (`process.memoryUsage()` is not used here because we + * want system-wide free memory, not just the V8 heap). If the import fails, + * we fall back to conservative defaults that match the synchronous path. + * * @returns An object with totalMemory and freeMemory in bytes, or null if detection fails */ private async detectAvailableMemory(): Promise<{ totalMemory: number, freeMemory: number } | null> { try { - // Node.js environment - if (this.environment === Environment.NODE) { - try { - // Use dynamic import for OS module - const os = await import('node:os') - - // Get actual system memory information - const totalMemory = os.totalmem() - const freeMemory = os.freemem() - - return { totalMemory, freeMemory } - } catch (error) { - console.warn('Failed to detect memory in Node.js environment:', error) - } + try { + // Use dynamic import for OS module + const os = await import('node:os') + + // Get actual system memory information + const totalMemory = os.totalmem() + const freeMemory = os.freemem() + + return { totalMemory, freeMemory } + } catch (error) { + console.warn('Failed to detect memory via node:os:', error) } - - // Browser environment - if (this.environment === Environment.BROWSER) { - // Try using performance.memory (Chrome only) - if (performance && (performance as any).memory) { - const memoryInfo = (performance as any).memory - - // jsHeapSizeLimit is the maximum size of the heap - // totalJSHeapSize is the currently allocated heap size - // usedJSHeapSize is the amount of heap currently being used - const totalMemory = memoryInfo.jsHeapSizeLimit || 0 - const usedMemory = memoryInfo.usedJSHeapSize || 0 - const freeMemory = Math.max(totalMemory - usedMemory, 0) - - return { totalMemory, freeMemory } - } - - // Try using navigator.deviceMemory as fallback - if (navigator.deviceMemory) { - // deviceMemory is in GB, convert to bytes - const totalMemory = navigator.deviceMemory * 1024 * 1024 * 1024 - // Assume 50% is free - const freeMemory = totalMemory * 0.5 - - return { totalMemory, freeMemory } - } - } - - // Worker environment - if (this.environment === Environment.WORKER) { - // Try using performance.memory if available (Chrome workers) - if (performance && (performance as any).memory) { - const memoryInfo = (performance as any).memory - - const totalMemory = memoryInfo.jsHeapSizeLimit || 0 - const usedMemory = memoryInfo.usedJSHeapSize || 0 - const freeMemory = Math.max(totalMemory - usedMemory, 0) - - return { totalMemory, freeMemory } - } - - // For workers, use a conservative estimate - // Assume 2GB total memory with 1GB free - return { - totalMemory: 2 * 1024 * 1024 * 1024, - freeMemory: 1 * 1024 * 1024 * 1024 - } - } - - // If all detection methods fail, use conservative defaults + + // If detection failed, use conservative defaults return { totalMemory: 8 * 1024 * 1024 * 1024, // Assume 8GB total freeMemory: 4 * 1024 * 1024 * 1024 // Assume 4GB free diff --git a/src/unified.ts b/src/unified.ts index 0add5a98..cd724c7e 100644 --- a/src/unified.ts +++ b/src/unified.ts @@ -27,4 +27,3 @@ console.log( ) export * from './index.js' -export { applyTensorFlowPatch } from './utils/textEncoding.js' diff --git a/src/utils/mutex.ts b/src/utils/mutex.ts index ddbfb07d..87f56065 100644 --- a/src/utils/mutex.ts +++ b/src/utils/mutex.ts @@ -105,16 +105,14 @@ export class FileMutex implements MutexInterface { private async loadNodeModules(): Promise { if (this.modulesLoaded) return - if (typeof window === 'undefined') { - // Modern ESM-compatible dynamic imports - const [fs, path] = await Promise.all([ - import('fs'), - import('path') - ]) - this.fs = fs - this.path = path - this.modulesLoaded = true - } + // Modern ESM-compatible dynamic imports + const [fs, path] = await Promise.all([ + import('fs'), + import('path') + ]) + this.fs = fs + this.path = path + this.modulesLoaded = true } async acquire(key: string, timeout: number = 30000): Promise<() => void> { @@ -255,9 +253,9 @@ export function createMutex(options?: { type?: 'memory' | 'file' lockDir?: string }): MutexInterface { - const type = options?.type || (typeof window === 'undefined' ? 'file' : 'memory') + const type = options?.type || 'file' - if (type === 'file' && typeof window === 'undefined') { + if (type === 'file') { const lockDir = options?.lockDir || '.brainy/locks' return new FileMutex(lockDir) } diff --git a/src/utils/paramValidation.ts b/src/utils/paramValidation.ts index cb26baaf..17748340 100644 --- a/src/utils/paramValidation.ts +++ b/src/utils/paramValidation.ts @@ -13,21 +13,17 @@ import { findCallerLocation } from './callerLocation.js' // Dynamic import for Node.js os and fs modules let os: any = null let fs: any = null -if (typeof window === 'undefined') { - try { - os = await import('node:os') - fs = await import('node:fs') - } catch (e) { - // OS/FS modules not available - } +try { + os = await import('node:os') + fs = await import('node:fs') +} catch (e) { + // OS/FS modules not available } -// Browser-safe memory detection const getSystemMemory = (): number => { if (os) { return os.totalmem() } - // Browser fallback: assume 4GB return 4 * 1024 * 1024 * 1024 } @@ -35,7 +31,6 @@ const getAvailableMemory = (): number => { if (os) { return os.freemem() } - // Browser fallback: assume 2GB available return 2 * 1024 * 1024 * 1024 } diff --git a/src/utils/structuredLogger.ts b/src/utils/structuredLogger.ts index 47240a41..7ec5c079 100644 --- a/src/utils/structuredLogger.ts +++ b/src/utils/structuredLogger.ts @@ -311,16 +311,11 @@ export class StructuredLogger { } if (this.config.includeHost) { - // Use dynamic import for hostname (Node.js only) - if (typeof window === 'undefined') { - try { - const os = require('node:os') - entry.host = os.hostname() - } catch { - entry.host = 'unknown' - } - } else { - entry.host = window.location?.hostname || 'browser' + try { + const os = require('node:os') + entry.host = os.hostname() + } catch { + entry.host = 'unknown' } } diff --git a/src/utils/textEncoding.ts b/src/utils/textEncoding.ts deleted file mode 100644 index e1111f94..00000000 --- a/src/utils/textEncoding.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { isNode } from './environment.js' - -// Simplified TextEncoder/TextDecoder utilities for Node.js compatibility -// No longer needs complex TensorFlow.js patches - only basic TextEncoder/TextDecoder - -/** - * Flag to track if the patch has been applied - */ -let patchApplied = false - -/** - * Apply TextEncoder/TextDecoder patches for Node.js compatibility - * Simplified version for Transformers.js/ONNX Runtime - */ -export async function applyTensorFlowPatch(): Promise { - if (patchApplied || !isNode()) return - - try { - console.log('Brainy: Applying TextEncoder/TextDecoder patch for Node.js') - - // Get the appropriate global object - const globalObj = (() => { - if (typeof globalThis !== 'undefined') return globalThis - if (typeof global !== 'undefined') return global - throw new Error( - 'Cannot apply TextEncoder/TextDecoder patches: No global object found. ' + - 'This environment does not have globalThis or global defined.' - ) - })() - - // Make sure TextEncoder and TextDecoder are available globally - if (!globalObj.TextEncoder) { - globalObj.TextEncoder = TextEncoder - } - if (!globalObj.TextDecoder) { - globalObj.TextDecoder = TextDecoder - } - - // Also set them on the global object for older code - if (typeof global !== 'undefined') { - if (!global.TextEncoder) { - global.TextEncoder = TextEncoder - } - if (!global.TextDecoder) { - global.TextDecoder = TextDecoder - } - } - - patchApplied = true - console.log('Brainy: TextEncoder/TextDecoder patches applied successfully') - } catch (error) { - console.warn('Brainy: Failed to apply TextEncoder/TextDecoder patch:', error) - } -} - -export function getTextEncoder(): TextEncoder { - return new TextEncoder() -} - -export function getTextDecoder(): TextDecoder { - return new TextDecoder() -} - -// Apply patch immediately if in Node.js -if (isNode()) { - applyTensorFlowPatch().catch((error) => { - console.warn('Failed to apply TextEncoder/TextDecoder patch at module load:', error) - }) -} \ No newline at end of file