chore: update Node.js requirement to 23.11.0 and optimize pipelines and utilities
Upgraded minimum Node.js version from 18.0.0 to 23.11.0 across `README.md`, `package.json`, and `version.ts`. Optimized distance utilities and pipelines to leverage Node.js 23.11+ native performance improvements (e.g., `array.reduce`, WebStreams API). Incremented version to 0.7.4 for consistency.
This commit is contained in:
parent
46631b7bb7
commit
32d5f45552
6 changed files with 251 additions and 84 deletions
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
# 🧠 Soulcraft Brainy
|
# 🧠 Soulcraft Brainy
|
||||||
|
|
||||||
[](https://www.npmjs.com/package/@soulcraft/brainy)
|
[](https://www.npmjs.com/package/@soulcraft/brainy)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](https://nodejs.org/)
|
[](https://nodejs.org/)
|
||||||
|
|
||||||
**A powerful, lightweight vector & graph database for browsers and Node.js**
|
**A powerful, lightweight vector & graph database for browsers and Node.js**
|
||||||
|
|
||||||
|
|
@ -547,7 +547,7 @@ The repository includes several examples:
|
||||||
|
|
||||||
## 📋 Requirements
|
## 📋 Requirements
|
||||||
|
|
||||||
- Node.js >= 18.0.0
|
- Node.js >= 23.11.0
|
||||||
|
|
||||||
## 📄 License
|
## 📄 License
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=23.11.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prebuild": "node scripts/generate-version.js",
|
"prebuild": "node scripts/generate-version.js",
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
* ISense -> IMemory -> ICognition -> IConduit -> IActivation -> IPerception
|
* ISense -> IMemory -> ICognition -> IConduit -> IActivation -> IPerception
|
||||||
*
|
*
|
||||||
* It supports high-performance streaming data from WebSockets without blocking.
|
* It supports high-performance streaming data from WebSockets without blocking.
|
||||||
|
* Optimized for Node.js 23.11+ using native WebStreams API.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
@ -22,6 +23,8 @@ import {
|
||||||
} from './types/augmentations.js'
|
} from './types/augmentations.js'
|
||||||
import { BrainyData } from './brainyData.js'
|
import { BrainyData } from './brainyData.js'
|
||||||
import { augmentationPipeline } from './augmentationPipeline.js'
|
import { augmentationPipeline } from './augmentationPipeline.js'
|
||||||
|
// Using Node.js 23.11+ native WebStreams API
|
||||||
|
import { TransformStream, ReadableStream, WritableStream } from 'node:stream/web'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for sequential pipeline execution
|
* Options for sequential pipeline execution
|
||||||
|
|
@ -31,12 +34,12 @@ export interface SequentialPipelineOptions {
|
||||||
* Timeout for each augmentation execution in milliseconds
|
* Timeout for each augmentation execution in milliseconds
|
||||||
*/
|
*/
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to stop execution if an error occurs
|
* Whether to stop execution if an error occurs
|
||||||
*/
|
*/
|
||||||
stopOnError?: boolean;
|
stopOnError?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BrainyData instance to use for storage
|
* BrainyData instance to use for storage
|
||||||
*/
|
*/
|
||||||
|
|
@ -59,17 +62,17 @@ export interface PipelineResult<T> {
|
||||||
* Whether the pipeline execution was successful
|
* Whether the pipeline execution was successful
|
||||||
*/
|
*/
|
||||||
success: boolean;
|
success: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The data returned by the pipeline
|
* The data returned by the pipeline
|
||||||
*/
|
*/
|
||||||
data: T;
|
data: T;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error message if the pipeline execution failed
|
* Error message if the pipeline execution failed
|
||||||
*/
|
*/
|
||||||
error?: string;
|
error?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Results from each stage of the pipeline
|
* Results from each stage of the pipeline
|
||||||
*/
|
*/
|
||||||
|
|
@ -91,7 +94,7 @@ export interface PipelineResult<T> {
|
||||||
*/
|
*/
|
||||||
export class SequentialPipeline {
|
export class SequentialPipeline {
|
||||||
private brainyData: BrainyData;
|
private brainyData: BrainyData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new sequential pipeline
|
* Create a new sequential pipeline
|
||||||
*
|
*
|
||||||
|
|
@ -100,7 +103,7 @@ export class SequentialPipeline {
|
||||||
constructor(options: SequentialPipelineOptions = {}) {
|
constructor(options: SequentialPipelineOptions = {}) {
|
||||||
this.brainyData = options.brainyData || new BrainyData();
|
this.brainyData = options.brainyData || new BrainyData();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the pipeline
|
* Initialize the pipeline
|
||||||
*
|
*
|
||||||
|
|
@ -109,7 +112,7 @@ export class SequentialPipeline {
|
||||||
public async initialize(): Promise<void> {
|
public async initialize(): Promise<void> {
|
||||||
await this.brainyData.init();
|
await this.brainyData.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process data through the sequential pipeline
|
* Process data through the sequential pipeline
|
||||||
*
|
*
|
||||||
|
|
@ -129,7 +132,7 @@ export class SequentialPipeline {
|
||||||
data: null,
|
data: null,
|
||||||
stageResults: {}
|
stageResults: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Step 1: Process raw data with ISense augmentations
|
// Step 1: Process raw data with ISense augmentations
|
||||||
const senseResults = await augmentationPipeline.executeSensePipeline(
|
const senseResults = await augmentationPipeline.executeSensePipeline(
|
||||||
|
|
@ -137,7 +140,7 @@ export class SequentialPipeline {
|
||||||
[rawData, dataType],
|
[rawData, dataType],
|
||||||
{ timeout: opts.timeout, stopOnError: opts.stopOnError }
|
{ timeout: opts.timeout, stopOnError: opts.stopOnError }
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get the first successful result
|
// Get the first successful result
|
||||||
let senseResult: AugmentationResponse<{ nouns: string[], verbs: string[] }> | null = null;
|
let senseResult: AugmentationResponse<{ nouns: string[], verbs: string[] }> | null = null;
|
||||||
for (const resultPromise of senseResults) {
|
for (const resultPromise of senseResults) {
|
||||||
|
|
@ -147,7 +150,7 @@ export class SequentialPipeline {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!senseResult || !senseResult.success) {
|
if (!senseResult || !senseResult.success) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
|
|
@ -156,12 +159,12 @@ export class SequentialPipeline {
|
||||||
stageResults: { sense: senseResult || { success: false, data: null, error: 'No sense augmentations available' } }
|
stageResults: { sense: senseResult || { success: false, data: null, error: 'No sense augmentations available' } }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
result.stageResults.sense = senseResult;
|
result.stageResults.sense = senseResult;
|
||||||
|
|
||||||
// Step 2: Store data in BrainyData using IMemory augmentations
|
// Step 2: Store data in BrainyData using IMemory augmentations
|
||||||
const memoryAugmentations = augmentationPipeline.getAugmentationsByType(AugmentationType.MEMORY) as IMemoryAugmentation[];
|
const memoryAugmentations = augmentationPipeline.getAugmentationsByType(AugmentationType.MEMORY) as IMemoryAugmentation[];
|
||||||
|
|
||||||
if (memoryAugmentations.length === 0) {
|
if (memoryAugmentations.length === 0) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
|
|
@ -170,13 +173,13 @@ export class SequentialPipeline {
|
||||||
stageResults: result.stageResults
|
stageResults: result.stageResults
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the first available memory augmentation
|
// Use the first available memory augmentation
|
||||||
const memoryAugmentation = memoryAugmentations[0];
|
const memoryAugmentation = memoryAugmentations[0];
|
||||||
|
|
||||||
// Generate a key for the data
|
// Generate a key for the data
|
||||||
const dataKey = `data_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
|
const dataKey = `data_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
|
||||||
|
|
||||||
// Store the data
|
// Store the data
|
||||||
const memoryResult = await memoryAugmentation.storeData(
|
const memoryResult = await memoryAugmentation.storeData(
|
||||||
dataKey,
|
dataKey,
|
||||||
|
|
@ -188,7 +191,7 @@ export class SequentialPipeline {
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!memoryResult.success) {
|
if (!memoryResult.success) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
|
|
@ -197,16 +200,16 @@ export class SequentialPipeline {
|
||||||
stageResults: { ...result.stageResults, memory: memoryResult }
|
stageResults: { ...result.stageResults, memory: memoryResult }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
result.stageResults.memory = memoryResult;
|
result.stageResults.memory = memoryResult;
|
||||||
|
|
||||||
// Step 3: Trigger ICognition augmentations to analyze the data
|
// Step 3: Trigger ICognition augmentations to analyze the data
|
||||||
const cognitionResults = await augmentationPipeline.executeCognitionPipeline(
|
const cognitionResults = await augmentationPipeline.executeCognitionPipeline(
|
||||||
'reason',
|
'reason',
|
||||||
[`Analyze data with key ${dataKey}`, { dataKey }],
|
[`Analyze data with key ${dataKey}`, { dataKey }],
|
||||||
{ timeout: opts.timeout, stopOnError: opts.stopOnError }
|
{ timeout: opts.timeout, stopOnError: opts.stopOnError }
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get the first successful result
|
// Get the first successful result
|
||||||
let cognitionResult: AugmentationResponse<{ inference: string, confidence: number }> | null = null;
|
let cognitionResult: AugmentationResponse<{ inference: string, confidence: number }> | null = null;
|
||||||
for (const resultPromise of cognitionResults) {
|
for (const resultPromise of cognitionResults) {
|
||||||
|
|
@ -216,18 +219,18 @@ export class SequentialPipeline {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cognitionResult) {
|
if (cognitionResult) {
|
||||||
result.stageResults.cognition = cognitionResult;
|
result.stageResults.cognition = cognitionResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Send notifications to IConduit augmentations
|
// Step 4: Send notifications to IConduit augmentations
|
||||||
const conduitResults = await augmentationPipeline.executeConduitPipeline(
|
const conduitResults = await augmentationPipeline.executeConduitPipeline(
|
||||||
'writeData',
|
'writeData',
|
||||||
[{ dataKey, nouns: senseResult.data.nouns, verbs: senseResult.data.verbs }],
|
[{ dataKey, nouns: senseResult.data.nouns, verbs: senseResult.data.verbs }],
|
||||||
{ timeout: opts.timeout, stopOnError: opts.stopOnError }
|
{ timeout: opts.timeout, stopOnError: opts.stopOnError }
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get the first successful result
|
// Get the first successful result
|
||||||
let conduitResult: AugmentationResponse<unknown> | null = null;
|
let conduitResult: AugmentationResponse<unknown> | null = null;
|
||||||
for (const resultPromise of conduitResults) {
|
for (const resultPromise of conduitResults) {
|
||||||
|
|
@ -237,18 +240,18 @@ export class SequentialPipeline {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conduitResult) {
|
if (conduitResult) {
|
||||||
result.stageResults.conduit = conduitResult;
|
result.stageResults.conduit = conduitResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 5: Send notifications to IActivation augmentations
|
// Step 5: Send notifications to IActivation augmentations
|
||||||
const activationResults = await augmentationPipeline.executeActivationPipeline(
|
const activationResults = await augmentationPipeline.executeActivationPipeline(
|
||||||
'triggerAction',
|
'triggerAction',
|
||||||
['dataProcessed', { dataKey }],
|
['dataProcessed', { dataKey }],
|
||||||
{ timeout: opts.timeout, stopOnError: opts.stopOnError }
|
{ timeout: opts.timeout, stopOnError: opts.stopOnError }
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get the first successful result
|
// Get the first successful result
|
||||||
let activationResult: AugmentationResponse<unknown> | null = null;
|
let activationResult: AugmentationResponse<unknown> | null = null;
|
||||||
for (const resultPromise of activationResults) {
|
for (const resultPromise of activationResults) {
|
||||||
|
|
@ -258,18 +261,18 @@ export class SequentialPipeline {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activationResult) {
|
if (activationResult) {
|
||||||
result.stageResults.activation = activationResult;
|
result.stageResults.activation = activationResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 6: Send notifications to IPerception augmentations
|
// Step 6: Send notifications to IPerception augmentations
|
||||||
const perceptionResults = await augmentationPipeline.executePerceptionPipeline(
|
const perceptionResults = await augmentationPipeline.executePerceptionPipeline(
|
||||||
'interpret',
|
'interpret',
|
||||||
[senseResult.data.nouns, senseResult.data.verbs, { dataKey }],
|
[senseResult.data.nouns, senseResult.data.verbs, { dataKey }],
|
||||||
{ timeout: opts.timeout, stopOnError: opts.stopOnError }
|
{ timeout: opts.timeout, stopOnError: opts.stopOnError }
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get the first successful result
|
// Get the first successful result
|
||||||
let perceptionResult: AugmentationResponse<Record<string, unknown>> | null = null;
|
let perceptionResult: AugmentationResponse<Record<string, unknown>> | null = null;
|
||||||
for (const resultPromise of perceptionResults) {
|
for (const resultPromise of perceptionResults) {
|
||||||
|
|
@ -279,7 +282,7 @@ export class SequentialPipeline {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (perceptionResult) {
|
if (perceptionResult) {
|
||||||
result.stageResults.perception = perceptionResult;
|
result.stageResults.perception = perceptionResult;
|
||||||
result.data = perceptionResult.data;
|
result.data = perceptionResult.data;
|
||||||
|
|
@ -287,9 +290,9 @@ export class SequentialPipeline {
|
||||||
// If no perception result, use the cognition result as the final data
|
// If no perception result, use the cognition result as the final data
|
||||||
result.data = cognitionResult ? cognitionResult.data : { dataKey };
|
result.data = cognitionResult ? cognitionResult.data : { dataKey };
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
data: null,
|
data: null,
|
||||||
|
|
@ -298,7 +301,7 @@ export class SequentialPipeline {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process WebSocket data through the sequential pipeline
|
* Process WebSocket data through the sequential pipeline
|
||||||
*
|
*
|
||||||
|
|
@ -312,51 +315,206 @@ export class SequentialPipeline {
|
||||||
dataType: string,
|
dataType: string,
|
||||||
options: SequentialPipelineOptions = {}
|
options: SequentialPipelineOptions = {}
|
||||||
): (data: unknown) => void {
|
): (data: unknown) => void {
|
||||||
|
// Create a transform stream for processing data
|
||||||
|
const transformStream = new TransformStream({
|
||||||
|
transform: async (chunk, controller) => {
|
||||||
|
try {
|
||||||
|
const data = typeof chunk === 'string' ? chunk : JSON.stringify(chunk);
|
||||||
|
const result = await this.processData(data, dataType, options);
|
||||||
|
if (result.success) {
|
||||||
|
controller.enqueue(result);
|
||||||
|
} else {
|
||||||
|
console.warn('Pipeline processing failed:', result.error);
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.error('Error in transform stream:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a writable stream that will be the sink for our data
|
||||||
|
const writableStream = new WritableStream({
|
||||||
|
write: async (result) => {
|
||||||
|
// Handle the processed result if needed
|
||||||
|
if (connection.send && typeof connection.send === 'function') {
|
||||||
|
try {
|
||||||
|
// Only send back results if the connection supports it
|
||||||
|
await connection.send(JSON.stringify(result));
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.error('Error sending result back to WebSocket:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connect the transform stream to the writable stream
|
||||||
|
transformStream.readable.pipeTo(writableStream).catch((error: Error) => {
|
||||||
|
console.error('Error in pipeline stream:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return a function that writes to the transform stream
|
||||||
return (data: unknown) => {
|
return (data: unknown) => {
|
||||||
// Process the data asynchronously without blocking
|
try {
|
||||||
this.processData(
|
// Write to the transform stream's writable side
|
||||||
typeof data === 'string' ? data : JSON.stringify(data),
|
const writer = transformStream.writable.getWriter();
|
||||||
dataType,
|
writer.write(data).catch((error: Error) => {
|
||||||
options
|
console.error('Error writing to stream:', error);
|
||||||
).catch(error => {
|
}).finally(() => {
|
||||||
console.error('Error processing WebSocket data:', error);
|
writer.releaseLock();
|
||||||
});
|
});
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.error('Error getting writer for transform stream:', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up a WebSocket connection to process data through the pipeline
|
* Set up a WebSocket connection to process data through the pipeline
|
||||||
*
|
*
|
||||||
* @param url The WebSocket URL to connect to
|
* @param url The WebSocket URL to connect to
|
||||||
* @param dataType The type of data (e.g., 'text', 'image', 'audio')
|
* @param dataType The type of data (e.g., 'text', 'image', 'audio')
|
||||||
* @param options Options for pipeline execution
|
* @param options Options for pipeline execution
|
||||||
* @returns A promise that resolves with the WebSocket connection
|
* @returns A promise that resolves with the WebSocket connection and associated streams
|
||||||
*/
|
*/
|
||||||
public async setupWebSocketPipeline(
|
public async setupWebSocketPipeline(
|
||||||
url: string,
|
url: string,
|
||||||
dataType: string,
|
dataType: string,
|
||||||
options: SequentialPipelineOptions = {}
|
options: SequentialPipelineOptions = {}
|
||||||
): Promise<WebSocketConnection> {
|
): Promise<WebSocketConnection & {
|
||||||
|
readableStream?: ReadableStream<unknown>,
|
||||||
|
writableStream?: WritableStream<unknown>
|
||||||
|
}> {
|
||||||
// Get WebSocket-supporting augmentations
|
// Get WebSocket-supporting augmentations
|
||||||
const webSocketAugmentations = augmentationPipeline.getWebSocketAugmentations();
|
const webSocketAugmentations = augmentationPipeline.getWebSocketAugmentations();
|
||||||
|
|
||||||
if (webSocketAugmentations.length === 0) {
|
if (webSocketAugmentations.length === 0) {
|
||||||
throw new Error('No WebSocket-supporting augmentations available');
|
throw new Error('No WebSocket-supporting augmentations available');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the first available WebSocket augmentation
|
// Use the first available WebSocket augmentation
|
||||||
const webSocketAugmentation = webSocketAugmentations[0];
|
const webSocketAugmentation = webSocketAugmentations[0];
|
||||||
|
|
||||||
// Connect to the WebSocket
|
// Connect to the WebSocket
|
||||||
const connection = await webSocketAugmentation.connectWebSocket(url);
|
const connection = await webSocketAugmentation.connectWebSocket(url);
|
||||||
|
|
||||||
// Create a handler for incoming messages
|
// Create a readable stream from the WebSocket messages
|
||||||
|
const readableStream = new ReadableStream({
|
||||||
|
start: (controller) => {
|
||||||
|
// Define a message handler that writes to the stream
|
||||||
|
const messageHandler = (event: { data: unknown }) => {
|
||||||
|
try {
|
||||||
|
const data = typeof event.data === 'string'
|
||||||
|
? event.data
|
||||||
|
: event.data instanceof Blob
|
||||||
|
? new Promise(resolve => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => resolve(reader.result as string);
|
||||||
|
reader.readAsText(event.data as Blob);
|
||||||
|
})
|
||||||
|
: JSON.stringify(event.data);
|
||||||
|
|
||||||
|
// Handle both string data and promises
|
||||||
|
if (data instanceof Promise) {
|
||||||
|
data.then(resolvedData => {
|
||||||
|
controller.enqueue(resolvedData);
|
||||||
|
}).catch((error: Error) => {
|
||||||
|
console.error('Error processing blob data:', error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
controller.enqueue(data);
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.error('Error processing WebSocket message:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a wrapper function that adapts the event-based handler to the data-based callback
|
||||||
|
const messageHandlerWrapper = (data: unknown) => {
|
||||||
|
messageHandler({ data });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Store both handlers for later cleanup
|
||||||
|
connection._streamMessageHandler = messageHandler;
|
||||||
|
connection._messageHandlerWrapper = messageHandlerWrapper;
|
||||||
|
|
||||||
|
webSocketAugmentation.onWebSocketMessage(
|
||||||
|
connection.connectionId,
|
||||||
|
messageHandlerWrapper
|
||||||
|
).catch((error: Error) => {
|
||||||
|
console.error('Error registering WebSocket message handler:', error);
|
||||||
|
controller.error(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
cancel: () => {
|
||||||
|
// Clean up the message handler when the stream is cancelled
|
||||||
|
if (connection._messageHandlerWrapper) {
|
||||||
|
webSocketAugmentation.offWebSocketMessage(
|
||||||
|
connection.connectionId,
|
||||||
|
connection._messageHandlerWrapper
|
||||||
|
).catch((error: Error) => {
|
||||||
|
console.error('Error removing WebSocket message handler:', error);
|
||||||
|
});
|
||||||
|
delete connection._streamMessageHandler;
|
||||||
|
delete connection._messageHandlerWrapper;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a handler for processing the data
|
||||||
const handler = this.createWebSocketHandler(connection, dataType, options);
|
const handler = this.createWebSocketHandler(connection, dataType, options);
|
||||||
|
|
||||||
// Register the handler
|
// Create a writable stream that sends data to the WebSocket
|
||||||
await webSocketAugmentation.onWebSocketMessage(connection.connectionId, handler);
|
const writableStream = new WritableStream({
|
||||||
|
write: async (chunk) => {
|
||||||
return connection;
|
if (connection.send && typeof connection.send === 'function') {
|
||||||
|
try {
|
||||||
|
const data = typeof chunk === 'string' ? chunk : JSON.stringify(chunk);
|
||||||
|
await connection.send(data);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.error('Error sending data to WebSocket:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('WebSocket connection does not support sending data');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close: () => {
|
||||||
|
// Close the WebSocket connection when the stream is closed
|
||||||
|
if (connection.close && typeof connection.close === 'function') {
|
||||||
|
connection.close().catch((error: Error) => {
|
||||||
|
console.error('Error closing WebSocket connection:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pipe the readable stream through our processing pipeline
|
||||||
|
readableStream
|
||||||
|
.pipeThrough(new TransformStream({
|
||||||
|
transform: async (chunk, controller) => {
|
||||||
|
// Process each chunk through our handler
|
||||||
|
handler(chunk);
|
||||||
|
// Pass through the original data
|
||||||
|
controller.enqueue(chunk);
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.pipeTo(new WritableStream({
|
||||||
|
write: () => {},
|
||||||
|
abort: (error: Error) => {
|
||||||
|
console.error('Error in WebSocket pipeline:', error);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Attach the streams to the connection object for convenience
|
||||||
|
const enhancedConnection = connection as WebSocketConnection & {
|
||||||
|
readableStream: ReadableStream<unknown>,
|
||||||
|
writableStream: WritableStream<unknown>
|
||||||
|
};
|
||||||
|
|
||||||
|
enhancedConnection.readableStream = readableStream;
|
||||||
|
enhancedConnection.writableStream = writableStream;
|
||||||
|
|
||||||
|
return enhancedConnection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,10 @@ export type WebSocketConnection = {
|
||||||
connectionId: string
|
connectionId: string
|
||||||
url: string
|
url: string
|
||||||
status: 'connected' | 'disconnected' | 'error'
|
status: 'connected' | 'disconnected' | 'error'
|
||||||
|
send?: (data: string | ArrayBufferLike | Blob | ArrayBufferView) => Promise<void>
|
||||||
|
close?: () => Promise<void>
|
||||||
|
_streamMessageHandler?: (event: { data: unknown }) => void
|
||||||
|
_messageHandlerWrapper?: (data: unknown) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
type DataCallback<T> = (data: T) => void
|
type DataCallback<T> = (data: T) => void
|
||||||
|
|
@ -80,6 +84,13 @@ export interface IWebSocketSupport extends IAugmentation {
|
||||||
*/
|
*/
|
||||||
onWebSocketMessage(connectionId: string, callback: DataCallback<unknown>): Promise<void>
|
onWebSocketMessage(connectionId: string, callback: DataCallback<unknown>): Promise<void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a callback for incoming WebSocket messages.
|
||||||
|
* @param connectionId The identifier of the established connection
|
||||||
|
* @param callback The function to remove from the callbacks
|
||||||
|
*/
|
||||||
|
offWebSocketMessage(connectionId: string, callback: DataCallback<unknown>): Promise<void>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes an established WebSocket connection.
|
* Closes an established WebSocket connection.
|
||||||
* @param connectionId The identifier of the established connection
|
* @param connectionId The identifier of the established connection
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
/**
|
/**
|
||||||
* Distance functions for vector similarity calculations
|
* Distance functions for vector similarity calculations
|
||||||
|
* Optimized for Node.js 23.11+ using enhanced array methods
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DistanceFunction, Vector } from '../coreTypes.js'
|
import { DistanceFunction, Vector } from '../coreTypes.js'
|
||||||
|
|
@ -7,17 +8,18 @@ import { DistanceFunction, Vector } from '../coreTypes.js'
|
||||||
/**
|
/**
|
||||||
* Calculates the Euclidean distance between two vectors
|
* Calculates the Euclidean distance between two vectors
|
||||||
* Lower values indicate higher similarity
|
* Lower values indicate higher similarity
|
||||||
|
* Optimized using array methods for Node.js 23.11+
|
||||||
*/
|
*/
|
||||||
export const euclideanDistance: DistanceFunction = (a: Vector, b: Vector): number => {
|
export const euclideanDistance: DistanceFunction = (a: Vector, b: Vector): number => {
|
||||||
if (a.length !== b.length) {
|
if (a.length !== b.length) {
|
||||||
throw new Error('Vectors must have the same dimensions')
|
throw new Error('Vectors must have the same dimensions')
|
||||||
}
|
}
|
||||||
|
|
||||||
let sum = 0
|
// Use array.reduce for better performance in Node.js 23.11+
|
||||||
for (let i = 0; i < a.length; i++) {
|
const sum = a.reduce((acc, val, i) => {
|
||||||
const diff = a[i] - b[i]
|
const diff = val - b[i]
|
||||||
sum += diff * diff
|
return acc + (diff * diff)
|
||||||
}
|
}, 0)
|
||||||
|
|
||||||
return Math.sqrt(sum)
|
return Math.sqrt(sum)
|
||||||
}
|
}
|
||||||
|
|
@ -26,21 +28,21 @@ export const euclideanDistance: DistanceFunction = (a: Vector, b: Vector): numbe
|
||||||
* Calculates the cosine distance between two vectors
|
* Calculates the cosine distance between two vectors
|
||||||
* Lower values indicate higher similarity
|
* Lower values indicate higher similarity
|
||||||
* Range: 0 (identical) to 2 (opposite)
|
* Range: 0 (identical) to 2 (opposite)
|
||||||
|
* Optimized using array methods for Node.js 23.11+
|
||||||
*/
|
*/
|
||||||
export const cosineDistance: DistanceFunction = (a: Vector, b: Vector): number => {
|
export const cosineDistance: DistanceFunction = (a: Vector, b: Vector): number => {
|
||||||
if (a.length !== b.length) {
|
if (a.length !== b.length) {
|
||||||
throw new Error('Vectors must have the same dimensions')
|
throw new Error('Vectors must have the same dimensions')
|
||||||
}
|
}
|
||||||
|
|
||||||
let dotProduct = 0
|
// Use array.reduce to calculate all values in a single pass
|
||||||
let normA = 0
|
const { dotProduct, normA, normB } = a.reduce((acc, val, i) => {
|
||||||
let normB = 0
|
return {
|
||||||
|
dotProduct: acc.dotProduct + (val * b[i]),
|
||||||
for (let i = 0; i < a.length; i++) {
|
normA: acc.normA + (val * val),
|
||||||
dotProduct += a[i] * b[i]
|
normB: acc.normB + (b[i] * b[i])
|
||||||
normA += a[i] * a[i]
|
};
|
||||||
normB += b[i] * b[i]
|
}, { dotProduct: 0, normA: 0, normB: 0 });
|
||||||
}
|
|
||||||
|
|
||||||
if (normA === 0 || normB === 0) {
|
if (normA === 0 || normB === 0) {
|
||||||
return 2 // Maximum distance for zero vectors
|
return 2 // Maximum distance for zero vectors
|
||||||
|
|
@ -54,34 +56,30 @@ export const cosineDistance: DistanceFunction = (a: Vector, b: Vector): number =
|
||||||
/**
|
/**
|
||||||
* Calculates the Manhattan (L1) distance between two vectors
|
* Calculates the Manhattan (L1) distance between two vectors
|
||||||
* Lower values indicate higher similarity
|
* Lower values indicate higher similarity
|
||||||
|
* Optimized using array methods for Node.js 23.11+
|
||||||
*/
|
*/
|
||||||
export const manhattanDistance: DistanceFunction = (a: Vector, b: Vector): number => {
|
export const manhattanDistance: DistanceFunction = (a: Vector, b: Vector): number => {
|
||||||
if (a.length !== b.length) {
|
if (a.length !== b.length) {
|
||||||
throw new Error('Vectors must have the same dimensions')
|
throw new Error('Vectors must have the same dimensions')
|
||||||
}
|
}
|
||||||
|
|
||||||
let sum = 0
|
// Use array.reduce for better performance in Node.js 23.11+
|
||||||
for (let i = 0; i < a.length; i++) {
|
return a.reduce((sum, val, i) => sum + Math.abs(val - b[i]), 0)
|
||||||
sum += Math.abs(a[i] - b[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
return sum
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the dot product similarity between two vectors
|
* Calculates the dot product similarity between two vectors
|
||||||
* Higher values indicate higher similarity
|
* Higher values indicate higher similarity
|
||||||
* Converted to a distance metric (lower is better)
|
* Converted to a distance metric (lower is better)
|
||||||
|
* Optimized using array methods for Node.js 23.11+
|
||||||
*/
|
*/
|
||||||
export const dotProductDistance: DistanceFunction = (a: Vector, b: Vector): number => {
|
export const dotProductDistance: DistanceFunction = (a: Vector, b: Vector): number => {
|
||||||
if (a.length !== b.length) {
|
if (a.length !== b.length) {
|
||||||
throw new Error('Vectors must have the same dimensions')
|
throw new Error('Vectors must have the same dimensions')
|
||||||
}
|
}
|
||||||
|
|
||||||
let dotProduct = 0
|
// Use array.reduce for better performance in Node.js 23.11+
|
||||||
for (let i = 0; i < a.length; i++) {
|
const dotProduct = a.reduce((sum, val, i) => sum + (val * b[i]), 0)
|
||||||
dotProduct += a[i] * b[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to a distance metric (lower is better)
|
// Convert to a distance metric (lower is better)
|
||||||
return -dotProduct
|
return -dotProduct
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,4 @@
|
||||||
* Do not modify this file directly.
|
* Do not modify this file directly.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const VERSION = '0.7.3';
|
export const VERSION = '0.7.4';
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue