- Create universal adapters for cross-platform support (browser/Node/serverless) - Replace Node.js-specific imports with universal implementations - Add OPFS support for browser persistent storage - Maintain same BrainyData interface across all environments - Enable real Brainy usage in browser console UI - Keep package size optimized (no bloat) Universal adapters in /src/universal/: - uuid.ts: Cross-platform UUID generation - crypto.ts: Browser/Node crypto operations - fs.ts: OPFS/FileSystem/Memory storage adapter - path.ts: Universal path operations - events.ts: EventEmitter compatibility layer This enables 'write once, run anywhere' for Brainy while maintaining the exact same API. No breaking changes to existing code.
187 lines
No EOL
3.9 KiB
TypeScript
187 lines
No EOL
3.9 KiB
TypeScript
/**
|
|
* Universal Path implementation
|
|
* Browser: Manual path operations
|
|
* Node.js: Uses built-in path module
|
|
*/
|
|
|
|
import { isNode } from '../utils/environment.js'
|
|
|
|
let nodePath: any = null
|
|
|
|
// Dynamic import for Node.js path (only in Node.js environment)
|
|
if (isNode()) {
|
|
try {
|
|
nodePath = await import('path')
|
|
} catch {
|
|
// Ignore import errors in non-Node environments
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Universal path operations
|
|
*/
|
|
export function join(...paths: string[]): string {
|
|
if (nodePath) {
|
|
return nodePath.join(...paths)
|
|
}
|
|
|
|
// Browser fallback implementation
|
|
const parts: string[] = []
|
|
for (const path of paths) {
|
|
if (path) {
|
|
parts.push(...path.split('/').filter(p => p))
|
|
}
|
|
}
|
|
return parts.join('/')
|
|
}
|
|
|
|
export function dirname(path: string): string {
|
|
if (nodePath) {
|
|
return nodePath.dirname(path)
|
|
}
|
|
|
|
// Browser fallback implementation
|
|
const parts = path.split('/').filter(p => p)
|
|
if (parts.length <= 1) return '.'
|
|
return parts.slice(0, -1).join('/')
|
|
}
|
|
|
|
export function basename(path: string, ext?: string): string {
|
|
if (nodePath) {
|
|
return nodePath.basename(path, ext)
|
|
}
|
|
|
|
// Browser fallback implementation
|
|
const parts = path.split('/')
|
|
let name = parts[parts.length - 1]
|
|
|
|
if (ext && name.endsWith(ext)) {
|
|
name = name.slice(0, -ext.length)
|
|
}
|
|
|
|
return name
|
|
}
|
|
|
|
export function extname(path: string): string {
|
|
if (nodePath) {
|
|
return nodePath.extname(path)
|
|
}
|
|
|
|
// Browser fallback implementation
|
|
const name = basename(path)
|
|
const lastDot = name.lastIndexOf('.')
|
|
return lastDot === -1 ? '' : name.slice(lastDot)
|
|
}
|
|
|
|
export function resolve(...paths: string[]): string {
|
|
if (nodePath) {
|
|
return nodePath.resolve(...paths)
|
|
}
|
|
|
|
// Browser fallback implementation
|
|
let resolved = ''
|
|
let resolvedAbsolute = false
|
|
|
|
for (let i = paths.length - 1; i >= -1 && !resolvedAbsolute; i--) {
|
|
const path = i >= 0 ? paths[i] : '/'
|
|
|
|
if (!path) continue
|
|
|
|
resolved = path + '/' + resolved
|
|
resolvedAbsolute = path.charAt(0) === '/'
|
|
}
|
|
|
|
// Normalize the path
|
|
resolved = normalizeArray(resolved.split('/').filter(p => p), !resolvedAbsolute).join('/')
|
|
|
|
return (resolvedAbsolute ? '/' : '') + resolved
|
|
}
|
|
|
|
export function relative(from: string, to: string): string {
|
|
if (nodePath) {
|
|
return nodePath.relative(from, to)
|
|
}
|
|
|
|
// Browser fallback implementation
|
|
const fromParts = resolve(from).split('/').filter(p => p)
|
|
const toParts = resolve(to).split('/').filter(p => p)
|
|
|
|
let commonLength = 0
|
|
for (let i = 0; i < Math.min(fromParts.length, toParts.length); i++) {
|
|
if (fromParts[i] === toParts[i]) {
|
|
commonLength++
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
const upCount = fromParts.length - commonLength
|
|
const upParts = new Array(upCount).fill('..')
|
|
const downParts = toParts.slice(commonLength)
|
|
|
|
return [...upParts, ...downParts].join('/')
|
|
}
|
|
|
|
export function isAbsolute(path: string): boolean {
|
|
if (nodePath) {
|
|
return nodePath.isAbsolute(path)
|
|
}
|
|
|
|
// Browser fallback implementation
|
|
return path.charAt(0) === '/'
|
|
}
|
|
|
|
/**
|
|
* Normalize array helper function
|
|
*/
|
|
function normalizeArray(parts: string[], allowAboveRoot: boolean): string[] {
|
|
const res: string[] = []
|
|
for (let i = 0; i < parts.length; i++) {
|
|
const p = parts[i]
|
|
|
|
if (!p || p === '.') continue
|
|
|
|
if (p === '..') {
|
|
if (res.length && res[res.length - 1] !== '..') {
|
|
res.pop()
|
|
} else if (allowAboveRoot) {
|
|
res.push('..')
|
|
}
|
|
} else {
|
|
res.push(p)
|
|
}
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
// Path separator (always use forward slash for consistency)
|
|
export const sep = '/'
|
|
export const delimiter = ':'
|
|
|
|
// POSIX path object for compatibility
|
|
export const posix = {
|
|
join,
|
|
dirname,
|
|
basename,
|
|
extname,
|
|
resolve,
|
|
relative,
|
|
isAbsolute,
|
|
sep: '/',
|
|
delimiter: ':'
|
|
}
|
|
|
|
// Default export
|
|
export default {
|
|
join,
|
|
dirname,
|
|
basename,
|
|
extname,
|
|
resolve,
|
|
relative,
|
|
isAbsolute,
|
|
sep,
|
|
delimiter,
|
|
posix
|
|
} |