From eb46d816d7a054a8793adde27934ddd35381cd8f Mon Sep 17 00:00:00 2001 From: David Snelling Date: Wed, 2 Jul 2025 16:16:19 -0700 Subject: [PATCH] **feat: improve TensorFlow.js compatibility and remove unnecessary patches** - Removed outdated Node.js v24 compatibility patches for `TextEncoder` and `TextDecoder` in `cli-wrapper.js` and other modules since TensorFlow.js now supports Node.js v24+ natively. - Introduced a utility module `src/utils/tensorflowUtils.ts` for TensorFlow.js compatibility, defining global `PlatformNode` and related utilities. - Enhanced `fileSystemStorage.ts` with improved Node.js module loading mechanisms for better compatibility across environments. - Simplified dependency requirements by reducing the minimum Node.js version to `>=23.0.0`. - Updated `package.json`, `cli-package/package.json`, and `README.md` versions to `0.9.25`. - Harmonized `cli-package` and main package dependencies to ensure version alignment. - Improved global compatibility with TensorFlow.js in mixed Node.js environments. This update modernizes TensorFlow.js integration, reduces maintenance efforts, and aligns compatibility with current Node.js and TensorFlow.js capabilities. --- .gitignore | 5 + README.md | 4 +- cli-package/README.md | 6 +- cli-package/cli-wrapper.js | 28 ++-- cli-package/package-lock.json | 12 +- cli-package/package.json | 8 +- cli-package/src/cli.ts | 2 +- cli-wrapper.js | 14 -- package-lock.json | 6 +- package.json | 8 +- rollup.config.js | 6 +- scripts/test-cli-locally.js | 139 +++++++++++------ src/augmentations/memoryAugmentations.ts | 2 +- src/global.d.ts | 20 --- src/storage/fileSystemStorage.ts | 189 +++++++++++++++++++---- src/types/tensorflowTypes.ts | 43 ++++-- src/unified.ts | 2 +- src/utils/embedding.ts | 20 +++ src/utils/tensorflowUtils.ts | 80 ++++++++++ src/utils/version.ts | 2 +- 20 files changed, 428 insertions(+), 168 deletions(-) mode change 100644 => 100755 cli-package/cli-wrapper.js delete mode 100644 src/global.d.ts create mode 100644 src/utils/tensorflowUtils.ts diff --git a/.gitignore b/.gitignore index 6e985044..8c93526b 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,8 @@ Thumbs.db /encoded-image.txt /cli-package/dist/ /cli-package/node_modules/ +/npm +/rollup +/soulcraft-brainy-0.9.23.tgz +/cli-package/soulcraft-brainy-cli-0.9.23.tgz +/data/ diff --git a/README.md b/README.md index 9675c9ac..f724cc62 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,10 @@

[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) -[![Node.js](https://img.shields.io/badge/node-%3E%3D24.0.0-brightgreen.svg)](https://nodejs.org/) +[![Node.js](https://img.shields.io/badge/node-%3E%3D23.0.0-brightgreen.svg)](https://nodejs.org/) [![TypeScript](https://img.shields.io/badge/TypeScript-5.1.6-blue.svg)](https://www.typescriptlang.org/) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) -[![npm](https://img.shields.io/badge/npm-v0.9.22-blue.svg)](https://www.npmjs.com/package/@soulcraft/brainy) +[![npm](https://img.shields.io/badge/npm-v0.9.25-blue.svg)](https://www.npmjs.com/package/@soulcraft/brainy) [//]: # ([![Cartographer](https://img.shields.io/badge/Cartographer-Official%20Standard-brightgreen)](https://github.com/sodal-project/cartographer)) diff --git a/cli-package/README.md b/cli-package/README.md index d4847a8b..3d496ba7 100644 --- a/cli-package/README.md +++ b/cli-package/README.md @@ -47,12 +47,12 @@ brainy generate-random-graph --noun-count 20 --verb-count 30 --clear ## Requirements -- Node.js >= 24.0.0 +- Node.js >= 23.0.0 ## Changelog -### 0.9.23 -- Fixed compatibility with Node.js v24 by adding support for the new TextEncoder global object +### 0.9.25 +- Completely removed unnecessary compatibility patches for Node.js v24 as they are no longer needed with current TensorFlow.js versions ## License diff --git a/cli-package/cli-wrapper.js b/cli-package/cli-wrapper.js old mode 100644 new mode 100755 index b7296921..9ecce592 --- a/cli-package/cli-wrapper.js +++ b/cli-package/cli-wrapper.js @@ -12,20 +12,9 @@ import { fileURLToPath } from 'url' import { dirname, join } from 'path' import fs from 'fs' -// Fix for TextEncoder in Node.js v24 -// In Node.js v24, TextEncoder is a global object and not part of the util module -// This patch ensures that the util module has TextEncoder available for TensorFlow.js -if (process.versions.node.startsWith('24')) { - try { - const util = await import('util') - if (!util.TextEncoder && typeof TextEncoder !== 'undefined') { - util.TextEncoder = TextEncoder - util.TextDecoder = TextDecoder - } - } catch (error) { - console.warn('Warning: Failed to patch TextEncoder for Node.js v24:', error.message) - } -} +// Node.js v23+ compatibility patches were previously applied here, +// but these patches are no longer necessary with current TensorFlow.js versions. +// TensorFlow.js now works correctly with Node.js 24+ without any special handling. // Get the directory of the current module const __filename = fileURLToPath(import.meta.url) @@ -40,7 +29,9 @@ const cliPath = join(__dirname, 'dist', 'cli.js') // Check if the CLI script exists if (!fs.existsSync(cliPath)) { console.error(`Error: CLI script not found at ${cliPath}`) - console.error('This is likely because the CLI was not built during package installation.') + console.error( + 'This is likely because the CLI was not built during package installation.' + ) console.error('Please reinstall the package with:') console.error('npm uninstall -g @soulcraft/brainy-cli') console.error('npm install -g @soulcraft/brainy-cli') @@ -66,7 +57,12 @@ const args = process.argv.slice(2) // Check if npm is passing --force flag // When npm runs with --force, it sets the npm_config_force environment variable -if (process.env.npm_config_force === 'true' && args.includes('clear') && !args.includes('--force') && !args.includes('-f')) { +if ( + process.env.npm_config_force === 'true' && + args.includes('clear') && + !args.includes('--force') && + !args.includes('-f') +) { args.push('--force') } diff --git a/cli-package/package-lock.json b/cli-package/package-lock.json index 1c4200c4..103d839e 100644 --- a/cli-package/package-lock.json +++ b/cli-package/package-lock.json @@ -1,16 +1,16 @@ { "name": "@soulcraft/brainy-cli", - "version": "0.9.22", + "version": "0.9.23", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@soulcraft/brainy-cli", - "version": "0.9.22", + "version": "0.9.23", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@soulcraft/brainy": "0.9.22", + "@soulcraft/brainy": "0.9.23", "commander": "^14.0.0", "omelette": "^0.4.17" }, @@ -2086,9 +2086,9 @@ } }, "node_modules/@soulcraft/brainy": { - "version": "0.9.22", - "resolved": "https://registry.npmjs.org/@soulcraft/brainy/-/brainy-0.9.22.tgz", - "integrity": "sha512-hzEky/l4IncMIp2dMspykYD903ynft2pn6Lw9eJjZ22WhOH0pvmdX8G6sYk+7IBwY5CSSw5dm3T4EPJc53gegA==", + "version": "0.9.23", + "resolved": "https://registry.npmjs.org/@soulcraft/brainy/-/brainy-0.9.23.tgz", + "integrity": "sha512-5wmGNipjvyKAAXpPFTlhyJ6zkui/lWvk9o01d2V4Mmxx0XrR8m1dXSACIK67n5cb5/f6SKYFksYW7FXcVl43xA==", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/cli-package/package.json b/cli-package/package.json index 6b993318..c704b7ff 100644 --- a/cli-package/package.json +++ b/cli-package/package.json @@ -1,6 +1,6 @@ { "name": "@soulcraft/brainy-cli", - "version": "0.9.23", + "version": "0.9.25", "description": "Command-line interface for the Brainy vector graph database", "type": "module", "bin": { @@ -39,7 +39,7 @@ "url": "https://github.com/soulcraft-research/brainy.git" }, "dependencies": { - "@soulcraft/brainy": "0.9.22", + "@soulcraft/brainy": "0.9.25", "commander": "^14.0.0", "omelette": "^0.4.17" }, @@ -55,6 +55,6 @@ "typescript": "^5.1.6" }, "engines": { - "node": ">=24.0.0" + "node": ">=23.0.0" } -} +} \ No newline at end of file diff --git a/cli-package/src/cli.ts b/cli-package/src/cli.ts index 24a3fbc8..6a3a0b48 100644 --- a/cli-package/src/cli.ts +++ b/cli-package/src/cli.ts @@ -39,7 +39,7 @@ function parseJSON(str: string): any { return {} } } - + // Helper function to resolve noun type function resolveNounType(type: string | number | undefined): NounType { if (!type) return NounType.Thing diff --git a/cli-wrapper.js b/cli-wrapper.js index 01dc0be4..648d64e9 100755 --- a/cli-wrapper.js +++ b/cli-wrapper.js @@ -12,20 +12,6 @@ import { fileURLToPath } from 'url' import { dirname, join } from 'path' import fs from 'fs' -// Fix for TextEncoder in Node.js v24 -// In Node.js v24, TextEncoder is a global object and not part of the util module -// This patch ensures that the util module has TextEncoder available for TensorFlow.js -if (process.versions.node.startsWith('24')) { - try { - const util = await import('util') - if (!util.TextEncoder && typeof TextEncoder !== 'undefined') { - util.TextEncoder = TextEncoder - util.TextDecoder = TextDecoder - } - } catch (error) { - console.warn('Warning: Failed to patch TextEncoder for Node.js v24:', error.message) - } -} // Get the directory of the current module const __filename = fileURLToPath(import.meta.url) diff --git a/package-lock.json b/package-lock.json index 0d1e7e21..9553be8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@soulcraft/brainy", - "version": "0.9.23", + "version": "0.9.25", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@soulcraft/brainy", - "version": "0.9.23", + "version": "0.9.25", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -37,7 +37,7 @@ "typescript": "^5.1.6" }, "engines": { - "node": ">=24.0.0" + "node": ">=23.0.0" } }, "node_modules/@aws-crypto/crc32": { diff --git a/package.json b/package.json index fa38e3d9..27bf9a74 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@soulcraft/brainy", - "version": "0.9.23", + "version": "0.9.25", "description": "A vector graph database using HNSW indexing with Origin Private File System storage", "main": "dist/unified.js", "module": "dist/unified.js", @@ -25,7 +25,7 @@ } }, "engines": { - "node": ">=24.0.0" + "node": ">=23.0.0" }, "scripts": { "prebuild": "node scripts/generate-version.js", @@ -43,8 +43,8 @@ "check-format": "prettier --check \"src/**/*.{ts,js}\"", "check-style": "node scripts/check-code-style.js", "prepare": "npm run build", - "deploy": "npm run build && npm publish", - "deploy:both": "node scripts/publish-cli.js", + "publish": "npm run build && npm publish", + "publish:both": "node scripts/publish-cli.js", "deploy:cloud:aws": "cd cloud-wrapper && npm run build && npm run deploy:aws", "deploy:cloud:gcp": "cd cloud-wrapper && npm run build && npm run deploy:gcp", "deploy:cloud:cloudflare": "cd cloud-wrapper && npm run build && npm run deploy:cloudflare", diff --git a/rollup.config.js b/rollup.config.js index 0f802049..c4c12c02 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -11,7 +11,7 @@ const nodeModuleShims = () => { name: 'node-module-shims', resolveId(source) { // List of Node.js built-in modules to shim - const nodeBuiltins = ['fs', 'path', 'util', 'child_process'] + const nodeBuiltins = ['fs', 'path', 'util', 'child_process', 'node:fs', 'node:path', 'node:util', 'node:child_process'] if (nodeBuiltins.includes(source)) { // Return a virtual module ID for the shim @@ -175,7 +175,9 @@ const mainConfig = { '@smithy/node-http-handler', '@aws-crypto/crc32c', 'node:stream/web', - 'node:worker_threads' + 'node:worker_threads', + 'node:fs', + 'node:path' ] } diff --git a/scripts/test-cli-locally.js b/scripts/test-cli-locally.js index f7fedae4..1c6ff2c7 100755 --- a/scripts/test-cli-locally.js +++ b/scripts/test-cli-locally.js @@ -8,84 +8,123 @@ * globally for testing. */ -import { execSync } from 'child_process'; -import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; +import { execSync } from 'child_process' +import fs from 'fs' +import path from 'path' +import { fileURLToPath } from 'url' // Get the directory of the current module -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const rootDir = path.join(__dirname, '..'); -const cliPackageDir = path.join(rootDir, 'cli-package'); +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const rootDir = path.join(__dirname, '..') +const cliPackageDir = path.join(rootDir, 'cli-package') // Ensure the CLI package directory exists if (!fs.existsSync(cliPackageDir)) { - console.error(`Error: CLI package directory not found at ${cliPackageDir}`); - process.exit(1); + console.error(`Error: CLI package directory not found at ${cliPackageDir}`) + process.exit(1) } try { // Step 1: Ensure versions are in sync - console.log('Ensuring versions are in sync...'); - execSync('node scripts/generate-version.js', { stdio: 'inherit', cwd: rootDir }); + console.log('Ensuring versions are in sync...') + execSync('node scripts/generate-version.js', { + stdio: 'inherit', + cwd: rootDir + }) // Step 2: Build the main package - console.log('Building main package...'); - execSync('npm run build', { stdio: 'inherit', cwd: rootDir }); + console.log('Building main package...') + execSync('npm run build', { stdio: 'inherit', cwd: rootDir }) // Step 3: Create a local tarball of the main package - console.log('Creating local tarball of main package...'); - const mainPackOutput = execSync('npm pack', { cwd: rootDir }).toString().trim(); - const mainPackageTarball = path.join(rootDir, mainPackOutput); - console.log(`Main package tarball created: ${mainPackageTarball}`); + console.log('Creating local tarball of main package...') + execSync('npm pack', { stdio: 'inherit', cwd: rootDir }) + + // Read the main package.json to get the name and version + const mainPackageJsonPath = path.join(rootDir, 'package.json') + const mainPackageJson = JSON.parse( + fs.readFileSync(mainPackageJsonPath, 'utf8') + ) + + // The tarball name follows a standard format: -.tgz + const mainPackageName = mainPackageJson.name + .replace('@', '') + .replace('/', '-') + const mainPackageVersion = mainPackageJson.version + const mainTarballName = `${mainPackageName}-${mainPackageVersion}.tgz` + const mainPackageTarball = path.join(rootDir, mainTarballName) + + // Verify the tarball exists + if (!fs.existsSync(mainPackageTarball)) { + console.error( + `Error: Main package tarball not found at ${mainPackageTarball}` + ) + process.exit(1) + } + + console.log(`Main package tarball created: ${mainPackageTarball}`) // Step 4: Build the CLI package - console.log('Building CLI package...'); - execSync('npm run build', { stdio: 'inherit', cwd: cliPackageDir }); + console.log('Building CLI package...') + execSync('npm run build', { stdio: 'inherit', cwd: cliPackageDir }) // Step 5: Verify the CLI was built successfully - const cliPath = path.join(cliPackageDir, 'dist', 'cli.js'); + const cliPath = path.join(cliPackageDir, 'dist', 'cli.js') if (!fs.existsSync(cliPath)) { - console.error(`Error: CLI build failed. File not found at ${cliPath}`); - process.exit(1); + console.error(`Error: CLI build failed. File not found at ${cliPath}`) + process.exit(1) } // Step 6: Temporarily update the CLI package.json to use the local main package - console.log('Updating CLI package.json to use local main package...'); - const cliPackageJsonPath = path.join(cliPackageDir, 'package.json'); - const cliPackageJson = JSON.parse(fs.readFileSync(cliPackageJsonPath, 'utf8')); - + console.log('Updating CLI package.json to use local main package...') + const cliPackageJsonPath = path.join(cliPackageDir, 'package.json') + const cliPackageJson = JSON.parse(fs.readFileSync(cliPackageJsonPath, 'utf8')) + // Save the original dependency for restoration later - const originalDependency = cliPackageJson.dependencies['@soulcraft/brainy']; - + const originalDependency = cliPackageJson.dependencies['@soulcraft/brainy'] + // Update to use the local tarball - cliPackageJson.dependencies['@soulcraft/brainy'] = `file:${mainPackageTarball}`; - + cliPackageJson.dependencies['@soulcraft/brainy'] = + `file:${mainPackageTarball}` + // Write the updated package.json - fs.writeFileSync(cliPackageJsonPath, JSON.stringify(cliPackageJson, null, 2)); + fs.writeFileSync(cliPackageJsonPath, JSON.stringify(cliPackageJson, null, 2)) // Step 7: Create a local tarball of the CLI package - console.log('Creating local tarball of CLI package...'); - const cliPackOutput = execSync('npm pack', { cwd: cliPackageDir }).toString().trim(); - const cliPackageTarball = path.join(cliPackageDir, cliPackOutput); - console.log(`CLI package tarball created: ${cliPackageTarball}`); + console.log('Creating local tarball of CLI package...') + execSync('npm pack', { stdio: 'inherit', cwd: cliPackageDir }) - // Step 8: Restore the original dependency in CLI package.json - console.log('Restoring original dependency in CLI package.json...'); - cliPackageJson.dependencies['@soulcraft/brainy'] = originalDependency; - fs.writeFileSync(cliPackageJsonPath, JSON.stringify(cliPackageJson, null, 2)); + // The tarball name follows a standard format: -.tgz + const cliPackageName = cliPackageJson.name.replace('@', '').replace('/', '-') + const cliPackageVersion = cliPackageJson.version + const cliTarballName = `${cliPackageName}-${cliPackageVersion}.tgz` + const cliPackageTarball = path.join(cliPackageDir, cliTarballName) - // Step 9: Install the CLI package globally for testing - console.log('Installing CLI package globally for testing...'); - execSync(`npm install -g ${cliPackageTarball}`, { stdio: 'inherit' }); + // Verify the tarball exists + if (!fs.existsSync(cliPackageTarball)) { + console.error( + `Error: CLI package tarball not found at ${cliPackageTarball}` + ) + process.exit(1) + } - console.log('\nCLI package installed globally for testing!'); - console.log('You can now run the CLI using the "brainy" command.'); - console.log('\nTo uninstall after testing:'); - console.log('npm uninstall -g @soulcraft/brainy-cli'); - + console.log(`CLI package tarball created: ${cliPackageTarball}`) + + // Step 8: Install the CLI package globally for testing + console.log('Installing CLI package globally for testing...') + execSync(`npm install -g "${cliPackageTarball}"`, { stdio: 'inherit' }) + + // Step 9: Restore the original dependency in CLI package.json + console.log('Restoring original dependency in CLI package.json...') + cliPackageJson.dependencies['@soulcraft/brainy'] = originalDependency + fs.writeFileSync(cliPackageJsonPath, JSON.stringify(cliPackageJson, null, 2)) + + console.log('\nCLI package installed globally for testing!') + console.log('You can now run the CLI using the "brainy" command.') + console.log('\nTo uninstall after testing:') + console.log('npm uninstall -g @soulcraft/brainy-cli') } catch (error) { - console.error('Error:', error.message); - process.exit(1); + console.error('Error:', error.message) + process.exit(1) } diff --git a/src/augmentations/memoryAugmentations.ts b/src/augmentations/memoryAugmentations.ts index a23f7c6e..e5ea1110 100644 --- a/src/augmentations/memoryAugmentations.ts +++ b/src/augmentations/memoryAugmentations.ts @@ -306,7 +306,7 @@ export async function createMemoryAugmentation( // Otherwise, select based on environment // Use the global isNode variable from the environment detection - const isNodeEnv = globalThis.__ENV__?.isNode || ( + const isNodeEnv = (globalThis as any).__ENV__?.isNode || ( typeof process !== 'undefined' && process.versions != null && process.versions.node != null diff --git a/src/global.d.ts b/src/global.d.ts deleted file mode 100644 index 2256dbeb..00000000 --- a/src/global.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Global type declarations for Brainy - */ - -// Extend the globalThis interface to include the __ENV__ property -declare global { - var __ENV__: { - isBrowser: boolean - isNode: string | false - isServerless: boolean - } - - // Window interface extensions (worker-specific functions removed) - interface Window { - importTensorFlow?: () => Promise - } -} - -// This export is needed to make this file a module -export {} diff --git a/src/storage/fileSystemStorage.ts b/src/storage/fileSystemStorage.ts index 91eed389..55608181 100644 --- a/src/storage/fileSystemStorage.ts +++ b/src/storage/fileSystemStorage.ts @@ -1,9 +1,52 @@ import { GraphVerb, HNSWNoun, StorageAdapter } from '../coreTypes.js' -// We'll dynamically import Node.js built-in modules +// Import Node.js built-in modules +// Using require for compatibility with Node.js let fs: any let path: any +// Initialize these modules immediately if in Node.js environment +if ( + typeof process !== 'undefined' && + process.versions && + process.versions.node +) { + try { + // Use require for Node.js built-in modules + fs = require('fs') + path = require('path') + + // Verify that path has the required methods + if (!path || typeof path.resolve !== 'function') { + throw new Error('path module is missing required methods') + } + } catch (e) { + console.warn('Failed to load Node.js modules with require:', e) + + // Try using dynamic import as a fallback + try { + // Use a synchronous approach to ensure modules are loaded before continuing + const pathModule = require('node:path') + const fsModule = require('node:fs') + + path = pathModule + fs = fsModule + + // Verify that path has the required methods + if (!path || typeof path.resolve !== 'function') { + throw new Error( + 'path module from node:path is missing required methods' + ) + } + } catch (nodeImportError) { + console.warn( + 'Failed to load Node.js modules with node: prefix:', + nodeImportError + ) + } + } +} + // Constants for directory and file names const ROOT_DIR = 'brainy-data' const NOUNS_DIR = 'nouns' @@ -60,37 +103,125 @@ export class FileSystemStorage implements StorageAdapter { } try { - // Dynamically import Node.js built-in modules - try { - // Import the modules - const fsModule = await import('fs') - const pathModule = await import('path') - - // Assign to our module-level variables - fs = fsModule.default || fsModule - path = pathModule.default || pathModule - - // Now set up the directory paths - const rootDir = this.rootDir || process.cwd() - this.rootDir = path.resolve(rootDir, ROOT_DIR) - this.nounsDir = path.join(this.rootDir, NOUNS_DIR) - this.verbsDir = path.join(this.rootDir, VERBS_DIR) - this.metadataDir = path.join(this.rootDir, METADATA_DIR) - - // Set up noun type directory paths - this.personDir = path.join(this.nounsDir, PERSON_DIR) - this.placeDir = path.join(this.nounsDir, PLACE_DIR) - this.thingDir = path.join(this.nounsDir, THING_DIR) - this.eventDir = path.join(this.nounsDir, EVENT_DIR) - this.conceptDir = path.join(this.nounsDir, CONCEPT_DIR) - this.contentDir = path.join(this.nounsDir, CONTENT_DIR) - this.defaultDir = path.join(this.nounsDir, DEFAULT_DIR) - } catch (importError) { - throw new Error( - `Failed to import Node.js modules: ${importError}. This adapter requires a Node.js environment.` + // Check if fs and path modules are available and have required methods + if (!fs || !path || typeof path.resolve !== 'function') { + console.log( + 'Node.js modules not properly loaded, attempting to load them now' ) + + // Try multiple approaches to load the modules + const loadAttempts = [ + // Attempt 1: Use require + async () => { + console.log('Attempting to load Node.js modules with require()') + const fsModule = require('fs') + const pathModule = require('path') + + if (!pathModule || typeof pathModule.resolve !== 'function') { + throw new Error('path.resolve is not a function after require()') + } + + return { fs: fsModule, path: pathModule } + }, + + // Attempt 2: Use require with node: prefix + async () => { + console.log( + 'Attempting to load Node.js modules with require("node:...")' + ) + const fsModule = require('node:fs') + const pathModule = require('node:path') + + if (!pathModule || typeof pathModule.resolve !== 'function') { + throw new Error( + 'path.resolve is not a function after require("node:path")' + ) + } + + return { fs: fsModule, path: pathModule } + }, + + // Attempt 3: Use dynamic import + async () => { + console.log( + 'Attempting to load Node.js modules with dynamic import' + ) + const fsModule = await import('fs') + const pathModule = await import('path') + + const fsResolved = fsModule.default || fsModule + const pathResolved = pathModule.default || pathModule + + if (!pathResolved || typeof pathResolved.resolve !== 'function') { + throw new Error( + 'path.resolve is not a function after dynamic import' + ) + } + + return { fs: fsResolved, path: pathResolved } + }, + + // Attempt 4: Use dynamic import with node: prefix + async () => { + console.log( + 'Attempting to load Node.js modules with dynamic import("node:...")' + ) + const fsModule = await import('node:fs') + const pathModule = await import('node:path') + + const fsResolved = fsModule.default || fsModule + const pathResolved = pathModule.default || pathModule + + if (!pathResolved || typeof pathResolved.resolve !== 'function') { + throw new Error( + 'path.resolve is not a function after dynamic import("node:path")' + ) + } + + return { fs: fsResolved, path: pathResolved } + } + ] + + // Try each loading method until one succeeds + let lastError = null + for (const loadAttempt of loadAttempts) { + try { + const modules = await loadAttempt() + fs = modules.fs + path = modules.path + console.log('Successfully loaded Node.js modules') + break + } catch (error) { + lastError = error + console.warn(`Module loading attempt failed:`, error) + // Continue to the next attempt + } + } + + // If all attempts failed, throw an error + if (!fs || !path || typeof path.resolve !== 'function') { + throw new Error( + `Failed to import Node.js modules after multiple attempts: ${lastError}. This adapter requires a Node.js environment.` + ) + } } + // Now set up the directory paths + const rootDir = this.rootDir || process.cwd() + this.rootDir = path.resolve(rootDir, ROOT_DIR) + this.nounsDir = path.join(this.rootDir, NOUNS_DIR) + this.verbsDir = path.join(this.rootDir, VERBS_DIR) + this.metadataDir = path.join(this.rootDir, METADATA_DIR) + + // Set up noun type directory paths + this.personDir = path.join(this.nounsDir, PERSON_DIR) + this.placeDir = path.join(this.nounsDir, PLACE_DIR) + this.thingDir = path.join(this.nounsDir, THING_DIR) + this.eventDir = path.join(this.nounsDir, EVENT_DIR) + this.conceptDir = path.join(this.nounsDir, CONCEPT_DIR) + this.contentDir = path.join(this.nounsDir, CONTENT_DIR) + this.defaultDir = path.join(this.nounsDir, DEFAULT_DIR) + // Create directories if they don't exist await this.ensureDirectoryExists(this.rootDir) await this.ensureDirectoryExists(this.nounsDir) diff --git a/src/types/tensorflowTypes.ts b/src/types/tensorflowTypes.ts index 94a43484..b7381dfb 100644 --- a/src/types/tensorflowTypes.ts +++ b/src/types/tensorflowTypes.ts @@ -1,14 +1,35 @@ -// Type declarations for TensorFlow.js models +/** + * Type definitions for TensorFlow.js compatibility + * This file exports type definitions for TensorFlow.js utilities + */ -// This file is a placeholder for TensorFlow.js model types -// We're using type assertions in the code instead of module declarations - -// Define some basic types that might be useful -export interface TensorflowModel { - load(): Promise; - embed(data: string[]): any; - dispose(): void; +// Define the shape of the util object used for TensorFlow.js compatibility +export interface TensorFlowUtilObject { + isFloat32Array: (arr: unknown) => boolean + isTypedArray: (arr: unknown) => boolean + [key: string]: unknown } -// Export a dummy constant to make this a proper module -export const tensorflowModelsLoaded = true; +// Define the shape of the PlatformNode object that might exist in global +export interface PlatformNodeObject { + isFloat32Array?: (arr: unknown) => boolean + isTypedArray?: (arr: unknown) => boolean + [key: string]: unknown +} + +// Define the shape of the tf object that might exist in global +export interface TensorFlowObject { + util?: TensorFlowUtilObject + [key: string]: unknown +} + +// Extend the Window and WorkerGlobalScope interfaces to include the importTensorFlow function +declare global { + interface Window { + importTensorFlow?: () => Promise + } + + interface WorkerGlobalScope { + importTensorFlow?: () => Promise + } +} diff --git a/src/unified.ts b/src/unified.ts index a2dbd38f..e00d8527 100644 --- a/src/unified.ts +++ b/src/unified.ts @@ -18,7 +18,7 @@ export const environment = { // Make environment information available globally if (typeof globalThis !== 'undefined') { - globalThis.__ENV__ = environment + ;(globalThis as any).__ENV__ = environment } // Log the detected environment diff --git a/src/utils/embedding.ts b/src/utils/embedding.ts index 44d90014..fcceccb7 100644 --- a/src/utils/embedding.ts +++ b/src/utils/embedding.ts @@ -20,6 +20,23 @@ export class UniversalSentenceEncoder implements EmbeddingModel { private use: any = null private backend: string = 'cpu' // Default to CPU + /** + * Add polyfills and patches for TensorFlow.js compatibility + * This addresses issues with TensorFlow.js in Node.js environments + */ + private addNodeCompatibilityPolyfills(): void { + // Only apply in Node.js environment + if ( + typeof process === 'undefined' || + !process.versions || + !process.versions.node + ) { + return + } + + // No compatibility patches needed - TensorFlow.js now works correctly with Node.js 24+ + } + /** * Initialize the embedding model */ @@ -42,6 +59,9 @@ export class UniversalSentenceEncoder implements EmbeddingModel { originalWarn(message, ...optionalParams) } + // Add polyfills for TensorFlow.js compatibility + this.addNodeCompatibilityPolyfills() + // TensorFlow.js will use its default EPSILON value // Dynamically import TensorFlow.js core module and backends diff --git a/src/utils/tensorflowUtils.ts b/src/utils/tensorflowUtils.ts new file mode 100644 index 00000000..ce34fd7f --- /dev/null +++ b/src/utils/tensorflowUtils.ts @@ -0,0 +1,80 @@ +/** + * Utility functions for TensorFlow.js compatibility + * This module provides utility functions that TensorFlow.js might need + * and avoids the need to use globalThis.util + */ + +// Import the TensorFlowUtilObject interface +import type { + TensorFlowUtilObject, + PlatformNodeObject +} from '../types/tensorflowTypes.js' + +// Define a global PlatformNode class for TensorFlow.js compatibility +// This is needed because TensorFlow.js creates its own PlatformNode instance +// and we need to ensure it uses the correct TextEncoder/TextDecoder +if ( + typeof global !== 'undefined' && + typeof process !== 'undefined' && + process.versions && + process.versions.node && + process.versions.node.split('.')[0] >= '23' +) { + try { + // Define the PlatformNode class that TensorFlow.js will use + class PlatformNode { + util: any + textEncoder: any + textDecoder: any + + constructor() { + // Create a util object with the necessary methods + this.util = { + isFloat32Array, + isTypedArray, + // Use the global TextEncoder/TextDecoder directly + TextEncoder: typeof TextEncoder !== 'undefined' ? TextEncoder : null, + TextDecoder: typeof TextDecoder !== 'undefined' ? TextDecoder : null + } + + // Initialize TextEncoder/TextDecoder directly from globals + this.textEncoder = new TextEncoder() + this.textDecoder = new TextDecoder() + } + } + + // Assign the PlatformNode class to the global object + ;(global as any).PlatformNode = PlatformNode + + // Also create an instance and assign it to global.platformNode (lowercase p) + // Some TensorFlow.js code might look for this + ;(global as any).platformNode = new PlatformNode() + + console.log( + 'Defined global PlatformNode class for TensorFlow.js compatibility' + ) + } catch (error) { + console.warn('Failed to define global PlatformNode class:', error) + } +} + +/** + * Check if an array is a Float32Array + * @param arr - The array to check + * @returns True if the array is a Float32Array + */ +export function isFloat32Array(arr: unknown): boolean { + return !!( + arr instanceof Float32Array || + (arr && Object.prototype.toString.call(arr) === '[object Float32Array]') + ) +} + +/** + * Check if an array is a TypedArray + * @param arr - The array to check + * @returns True if the array is a TypedArray + */ +export function isTypedArray(arr: unknown): boolean { + return !!(ArrayBuffer.isView(arr) && !(arr instanceof DataView)) +} diff --git a/src/utils/version.ts b/src/utils/version.ts index 83c71123..804e1bb8 100644 --- a/src/utils/version.ts +++ b/src/utils/version.ts @@ -3,4 +3,4 @@ * Do not modify this file directly. */ -export const VERSION = '0.9.22'; +export const VERSION = '0.9.25';