commit 7b53e80217839e2be8f18ea9bb204316796208e1 Author: David Snelling Date: Mon Jun 23 14:46:26 2025 -0700 Initial commit diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..91035681 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,32 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '[BUG] ' +labels: bug +assignees: '' +--- + +## Bug Description +A clear and concise description of what the bug is. + +## Reproduction Steps +Steps to reproduce the behavior: +1. Initialize BrainyData with '...' +2. Call method '....' +3. See error + +## Expected Behavior +A clear and concise description of what you expected to happen. + +## Environment +- Brainy version: [e.g. 0.9.4] +- Environment: [e.g. Browser, Node.js, serverless] +- Browser (if applicable): [e.g. Chrome, Safari] +- Node.js version (if applicable): [e.g. 23.11.0] +- Operating System: [e.g. Windows 10, macOS Monterey, Ubuntu 22.04] + +## Additional Context +Add any other context about the problem here. If applicable, include code snippets, error messages, or screenshots. + +## Possible Solution +If you have suggestions on how to fix the issue, please describe them here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..647a54e7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,22 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '[FEATURE] ' +labels: enhancement +assignees: '' +--- + +## Problem Statement +A clear and concise description of what problem this feature would solve. For example: "I'm always frustrated when [...]" + +## Proposed Solution +A clear and concise description of what you want to happen. + +## Alternative Solutions +A clear and concise description of any alternative solutions or features you've considered. + +## Use Case +Describe a concrete use case that highlights the value of this feature. + +## Additional Context +Add any other context, code examples, or references about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..7bc8b5bb --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,27 @@ +## Description +Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. + +Fixes # (issue) + +## Type of change +Please delete options that are not relevant. + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Documentation update +- [ ] Performance improvement +- [ ] Code refactoring (no functional changes) + +## How Has This Been Tested? +Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. + +## Checklist: +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published in downstream modules diff --git a/.github/workflows/deploy-demo.yml b/.github/workflows/deploy-demo.yml new file mode 100644 index 00000000..cb91fde1 --- /dev/null +++ b/.github/workflows/deploy-demo.yml @@ -0,0 +1,44 @@ +name: Deploy Demo to GitHub Pages + +on: + push: + branches: [ main ] + workflow_dispatch: + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout 🛎️ + uses: actions/checkout@v3 + + - name: Setup Node.js 🔧 + uses: actions/setup-node@v3 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies 📦 + run: npm install --legacy-peer-deps + + - name: Build project 🏗️ + run: | + npm run build + npm run build:browser + + - name: Prepare deployment 📦 + run: | + mkdir -p deploy + mkdir -p deploy/demo + cp index.html deploy/ + cp demo/index.html deploy/demo/ + cp -r dist deploy/ + cp brainy.png deploy/ + + - name: Deploy to GitHub Pages 🚀 + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: deploy + branch: gh-pages diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..6fa7bc6f --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# Build output +/tmp +/out-tsc +/dist +/cloud-wrapper/dist + +# Dependencies +/node_modules +/cloud-wrapper/node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* +/.pnp +.pnp.js + +# Coverage directory +/coverage + +# Environment files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# IDE files +.vscode/ +.idea/ +*.iml +*.iws +*.ipr +*.sublime-workspace +*.sublime-project + +# OS files +.DS_Store +Thumbs.db + +# Data directories created by FileSystemStorage +/brainy-data +/custom-data diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000..fc9e8b71 --- /dev/null +++ b/.npmignore @@ -0,0 +1,40 @@ +# Exclude source maps +*.map +**/*.map + +# Development files +node_modules/ +src/ +tests/ +examples/ +.github/ +.vscode/ +.idea/ +cloud-wrapper/ +scripts/ + +# Configuration files +.eslintrc +.prettierrc +tsconfig*.json +rollup.config.js +jest.config.js + +# Build artifacts +emocoverage/ +.nyc_output/ + +# Large files +# Include the logo but exclude other PNGs +!brainy.png +*.png +encoded-image.* +README.demo.md +scalingStrategy.md + +# Misc +.DS_Store +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..77c73d96 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Project maintainers are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned with this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the project maintainers responsible for enforcement at +conduct@soulcraft.com. +All complaints will be reviewed and investigated promptly and fairly. + +All project maintainers are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Project maintainers will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from project maintainers, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..ebbb2db6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,89 @@ +
+Brainy Logo + +# Contributing to Brainy + +
+ +Thank you for your interest in contributing to Brainy! This document provides guidelines and instructions for +contributing to the project. + +We welcome contributions of all kinds, including bug fixes, feature additions, documentation improvements, and more. +By participating in this project, you agree to abide by our [Code of Conduct](CODE_OF_CONDUCT.md). + +## Commit Message Guidelines + +When contributing to this project, please write clear and descriptive commit messages that explain the purpose of your +changes. Good commit messages help maintainers understand your contributions and make the review process smoother. + +### Best Practices + +- Keep the first line concise (ideally under 50 characters) +- Use the imperative mood ("Add feature" not "Added feature") +- Reference issues and pull requests where appropriate +- When necessary, provide more detailed explanations in the commit body + +### Examples + +``` +Add vector normalization option +Fix distance calculation in HNSW search +Update API documentation +Add support for IndexedDB storage +Change API parameter order +Simplify vector comparison logic +Add tests for metadata filtering +Update build dependencies +``` + +## Pull Request Process + +1. Ensure your code follows the project's coding standards +2. Update the documentation if necessary +3. Make sure all tests pass +4. Use conventional commit messages in your PR +5. Your PR will be reviewed by maintainers and merged if approved + +## Development Setup + +1. Fork and clone the repository +2. Install dependencies: `npm install` +3. Build the project: `npm run build` +4. Run tests: `npm test` + +## Code Style + +This project uses ESLint and Prettier for code formatting and style checking. The configuration can be found in the `package.json` file. Please ensure your code follows these standards: + +- Use 2 spaces for indentation +- Use single quotes for strings +- No semicolons +- Trailing commas are not used +- Maximum line length is 80 characters + +You can check your code style by running: +```bash +npx eslint src/ +``` + +## Branching Strategy + +- `main` - The main branch contains the latest stable release +- `develop` - The development branch contains the latest development changes +- Feature branches - Create a branch from `develop` for your feature or fix + +When working on a new feature or fix: +1. Create a new branch from `develop` with a descriptive name (e.g., `feature/add-vector-normalization` or `fix/distance-calculation`) +2. Make your changes in that branch +3. Submit a pull request to merge your branch into `develop` + +## Issue Reporting + +Before submitting a new issue, please search existing issues to avoid duplicates. + +- For bugs, use the bug report template +- For feature requests, use the feature request template +- Be as detailed as possible in your description +- Include code examples, error messages, and screenshots if applicable + +Thank you for contributing to Brainy! diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..681d9d76 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Soulcraft Research + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.demo.md b/README.demo.md new file mode 100644 index 00000000..9a706bb8 --- /dev/null +++ b/README.demo.md @@ -0,0 +1,59 @@ +# Running the Brainy Demo + +The Brainy interactive demo showcases the library's features in a web browser. Follow these steps to run it: + +## Prerequisites + +- Make sure you have Node.js installed (version 23.11.0 or higher) +- Ensure the project is built (`npm run build:all`) + +## Running the Demo + +### Option 1: Using the npm script (recommended) + +Run the following command from the project root: + +```bash +npm run demo +``` + +This will start an HTTP server and automatically open the demo in your default browser. + +### Option 2: Manual setup + +1. Start an HTTP server in the project root: + +```bash +npx http-server +``` + +2. Open your browser and navigate to: + http://localhost:8080/demo/index.html + +## Troubleshooting + +If you see the error "Could not load Brainy library. Please ensure the project is built and served over HTTP", check the +following: + +1. Make sure you've built the project with `npm run build:all` +2. Ensure you're accessing the demo through HTTP (not by opening the file directly) +3. Check your browser's console for additional error messages + +If issues persist, try clearing your browser cache or using a private/incognito window. + +## Build Process + +The Brainy library uses a two-step build process: + +1. `npm run build` - Compiles TypeScript files to JavaScript (used for Node.js environments) +2. `npm run build:browser` - Creates a browser-compatible bundle using Rollup + +You can run both steps together with: + +```bash +npm run build:all +``` + +The browser bundle is created from `src/unified.ts`, which provides environment detection and adapts to browser, +Node.js, or serverless environments. This unified approach ensures that the library works correctly across all +environments. diff --git a/README.md b/README.md new file mode 100644 index 00000000..67e1c663 --- /dev/null +++ b/README.md @@ -0,0 +1,1257 @@ +
+Brainy Logo +

+ +[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) +[![Node.js](https://img.shields.io/badge/node-%3E%3D23.11.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) +[![Cartographer](https://img.shields.io/badge/Cartographer-Official%20Standard-brightgreen)](https://github.com/sodal-project/cartographer) + +**A powerful graph & vector data platform for AI applications across any environment** + +
+ +## ✨ Overview + +Brainy combines the power of vector search with graph relationships in a lightweight, cross-platform database. Whether +you're building AI applications, recommendation systems, or knowledge graphs, Brainy provides the tools you need to +store, connect, and retrieve your data intelligently. + +What makes Brainy special? It intelligently adapts to your environment! Brainy automatically detects your platform, +adjusts its storage strategy, and optimizes performance based on your usage patterns. The more you use it, the smarter +it gets - learning from your data to provide increasingly relevant results and connections. + +### 🚀 Key Features + +- **Run Everywhere** - Works in browsers, Node.js, serverless functions, and containers +- **Vector Search** - Find semantically similar content using embeddings +- **Graph Relationships** - Connect data with meaningful relationships +- **Streaming Pipeline** - Process data in real-time as it flows through the system +- **Extensible Augmentations** - Customize and extend functionality with pluggable components +- **Built-in Conduits** - Sync and scale across instances with WebSocket and WebRTC +- **TensorFlow Integration** - Use TensorFlow.js for high-quality embeddings +- **Adaptive Intelligence** - Automatically optimizes for your environment and usage patterns +- **Persistent Storage** - Data persists across sessions and scales to any size +- **TypeScript Support** - Fully typed API with generics +- **CLI Tools** - Powerful command-line interface for data management +- **Model Control Protocol (MCP)** - Allow external AI models to access Brainy data and use augmentation pipeline as + tools + +## 🚀 Live Demo + +**[Try the live demo](https://soulcraft-research.github.io/brainy/demo/index.html)** - Check out the interactive demo on +GitHub Pages that showcases Brainy's main features. + +## 📊 What Can You Build? + +- **Semantic Search Engines** - Find content based on meaning, not just keywords +- **Recommendation Systems** - Suggest similar items based on vector similarity +- **Knowledge Graphs** - Build connected data structures with relationships +- **AI Applications** - Store and retrieve embeddings for machine learning models +- **AI-Enhanced Applications** - Build applications that leverage vector embeddings for intelligent data processing +- **Data Organization Tools** - Automatically categorize and connect related information +- **Adaptive Experiences** - Create applications that learn and evolve with your users +- **Model-Integrated Systems** - Connect external AI models to Brainy data and tools using MCP + +## 🔧 Installation + +```bash +npm install @soulcraft/brainy +``` + +TensorFlow.js packages are included as required dependencies and will be automatically installed. If you encounter +dependency conflicts, you may need to use the `--legacy-peer-deps` flag: + +```bash +npm install @soulcraft/brainy --legacy-peer-deps +``` + +## 🏁 Quick Start + +Brainy uses a unified build that automatically adapts to your environment (Node.js, browser, or serverless): + +```typescript +import { BrainyData, NounType, VerbType } from '@soulcraft/brainy' + +// Create and initialize the database +const db = new BrainyData() +await db.init() + +// Add data (automatically converted to vectors) +const catId = await db.add("Cats are independent pets", { + noun: NounType.Thing, + category: 'animal' +}) + +const dogId = await db.add("Dogs are loyal companions", { + noun: NounType.Thing, + category: 'animal' +}) + +// Search for similar items +const results = await db.searchText("feline pets", 2) +console.log(results) + +// Add a relationship between items +await db.addVerb(catId, dogId, { + verb: VerbType.RelatedTo, + description: 'Both are common household pets' +}) +``` + +### Import Options + +```typescript +// Standard import - automatically adapts to any environment +import { BrainyData } from '@soulcraft/brainy' + +// Minified version for production +import { BrainyData } from '@soulcraft/brainy/min' +``` + +### Browser Usage + +```html + + +``` + +Modern bundlers like Webpack, Rollup, and Vite will automatically use the unified build which adapts to any environment. + +## 🧩 How It Works + +Brainy combines four key technologies to create its adaptive intelligence: + +1. **Vector Embeddings** - Converts data (text, images, etc.) into numerical vectors that capture semantic meaning +2. **HNSW Algorithm** - Enables fast similarity search through a hierarchical graph structure +3. **Adaptive Environment Detection** - Automatically senses your platform and optimizes accordingly: + - Detects browser, Node.js, and serverless environments + - Adjusts performance parameters based on available resources + - Learns from query patterns to optimize future searches + - Tunes itself for your specific use cases +4. **Intelligent Storage Selection** - Uses the best available storage option for your environment: + - Browser: Origin Private File System (OPFS) + - Node.js: File system + - Server: S3-compatible storage (optional) + - Serverless: In-memory storage with optional cloud persistence + - Fallback: In-memory storage + - Automatically migrates between storage types as needed + +## 🚀 The Brainy Pipeline + +Brainy's data processing pipeline transforms raw data into searchable, connected knowledge that gets smarter over time: + +``` +Raw Data → Embedding → Vector Storage → Graph Connections → Adaptive Learning → Query & Retrieval +``` + +Each time data flows through this pipeline, Brainy learns more about your usage patterns and environment, making future +operations faster and more relevant. + +### Pipeline Stages + +1. **Data Ingestion** + - Raw text or pre-computed vectors enter the pipeline + - Data is validated and prepared for processing + +2. **Embedding Generation** + - Text is transformed into numerical vectors using embedding models + - Uses TensorFlow Universal Sentence Encoder for high-quality text embeddings + - Custom embedding functions can be plugged in for specialized domains + +3. **Vector Indexing** + - Vectors are indexed using the HNSW algorithm + - Hierarchical structure enables fast similarity search + - Configurable parameters for precision vs. performance tradeoffs + +4. **Graph Construction** + - Nouns (entities) become nodes in the knowledge graph + - Verbs (relationships) connect related entities + - Typed relationships add semantic meaning to connections + +5. **Adaptive Learning** + - Analyzes usage patterns to optimize future operations + - Tunes performance parameters based on your environment + - Adjusts search strategies based on query history + - Becomes more efficient and relevant the more you use it + +6. **Intelligent Storage** + - Data is saved using the optimal storage for your environment + - Automatic selection between OPFS, filesystem, S3, or memory + - Migrates between storage types as your application's needs evolve + - Scales from tiny datasets to massive data collections + - Configurable storage adapters for custom persistence needs + +### Augmentation Types + +Brainy uses a powerful augmentation system to extend functionality. Augmentations are processed in the following order: + +1. **SENSE** + - Ingests and processes raw, unstructured data into nouns and verbs + - Handles text, images, audio streams, and other input formats + - Example: Converting raw text into structured entities + +2. **MEMORY** + - Provides storage capabilities for data in different formats + - Manages persistence across sessions + - Example: Storing vectors in OPFS or filesystem + +3. **COGNITION** + - Enables advanced reasoning, inference, and logical operations + - Analyzes relationships between entities + - Examples: + - Inferring new connections between existing data + - Deriving insights from graph relationships + +4. **CONDUIT** + - Establishes channels for structured data exchange + - Connects with external systems and syncs between Brainy instances + - Two built-in iConduit augmentations for scaling out and syncing: + - **WebSocket iConduit** - Syncs data between browsers and servers + - **WebRTC iConduit** - Direct peer-to-peer syncing between browsers + - Examples: + - Integrating with third-party APIs + - Syncing Brainy instances between browsers using WebSockets + - Peer-to-peer syncing between browsers using WebRTC + +5. **ACTIVATION** + - Initiates actions, responses, or data manipulations + - Triggers events based on data changes + - Example: Sending notifications when new data is processed + +6. **PERCEPTION** + - Interprets, contextualizes, and visualizes identified nouns and verbs + - Creates meaningful representations of data + - Example: Generating visualizations of graph relationships + +7. **DIALOG** + - Facilitates natural language understanding and generation + - Enables conversational interactions + - Example: Processing user queries and generating responses + +8. **WEBSOCKET** + - Enables real-time communication via WebSockets + - Can be combined with other augmentation types + - Example: Streaming data processing in real-time + +### Streaming Data Support + +Brainy's pipeline is designed to handle streaming data efficiently: + +1. **WebSocket Integration** + - Built-in support for WebSocket connections + - Process data as it arrives without blocking + - Example: `setupWebSocketPipeline(url, dataType, options)` + +2. **Asynchronous Processing** + - Non-blocking architecture for real-time data handling + - Parallel processing of incoming streams + - Example: `createWebSocketHandler(connection, dataType, options)` + +3. **Event-Based Architecture** + - Augmentations can listen to data feeds and streams + - Real-time updates propagate through the pipeline + - Example: `listenToFeed(feedUrl, callback)` + +4. **Threaded Execution** + - Comprehensive multi-threading for high-performance operations + - Parallel processing for batch operations, vector calculations, and embedding generation + - Configurable execution modes (SEQUENTIAL, PARALLEL, THREADED) + - Automatic thread management based on environment capabilities + - Example: `executeTypedPipeline(augmentations, method, args, { mode: ExecutionMode.THREADED })` + +### Build System + +Brainy uses a modern build system that optimizes for both Node.js and browser environments: + +1. **ES Modules** + - Built as ES modules for maximum compatibility + - Works in modern browsers and Node.js environments + - Separate optimized builds for browser and Node.js + +2. **Environment-Specific Builds** + - **Node.js Build**: Optimized for server environments with full functionality + - **Browser Build**: Optimized for browser environments with reduced bundle size + - Conditional exports in package.json for automatic environment detection + +3. **Environment Detection** + - Automatically detects whether it's running in a browser or Node.js + - Loads appropriate dependencies and functionality based on the environment + - Provides consistent API across all environments + +4. **TypeScript** + - Written in TypeScript for type safety and better developer experience + - Generates type definitions for TypeScript users + - Compiled to ES2020 for modern JavaScript environments + +5. **Build Scripts** + - `npm run build`: Builds the Node.js version + - `npm run build:browser`: Builds the browser-optimized version + - `npm run build:all`: Builds both versions + - `npm run demo`: Builds all versions and starts a demo server + - `npm run deploy:demo`: Deploys the examples directory to GitHub Pages + +### Running the Pipeline + +The pipeline runs automatically when you: + +```typescript +// Add data (runs embedding → indexing → storage) +const id = await db.add("Your text data here", { metadata }) + +// Search (runs embedding → similarity search) +const results = await db.searchText("Your query here", 5) + +// Connect entities (runs graph construction → storage) +await db.addVerb(sourceId, targetId, { verb: VerbType.RelatedTo }) +``` + +Using the CLI: + +```bash +# Add data through the CLI pipeline +brainy add "Your text data here" '{"noun":"Thing"}' + +# Search through the CLI pipeline +brainy search "Your query here" --limit 5 + +# Connect entities through the CLI +brainy addVerb RelatedTo +``` + +### Extending the Pipeline + +Brainy's pipeline is designed for extensibility at every stage: + +1. **Custom Embedding** + ```typescript + // Create your own embedding function + const myEmbedder = async (text) => { + // Your custom embedding logic here + return [0.1, 0.2, 0.3, ...] // Return a vector + } + + // Use it in Brainy + const db = new BrainyData({ + embeddingFunction: myEmbedder + }) + ``` + +2. **Custom Distance Functions** + ```typescript + // Define your own distance function + const myDistance = (a, b) => { + // Your custom distance calculation + return Math.sqrt(a.reduce((sum, val, i) => sum + Math.pow(val - b[i], 2), 0)) + } + + // Use it in Brainy + const db = new BrainyData({ + distanceFunction: myDistance + }) + ``` + +3. **Custom Storage Adapters** + ```typescript + // Implement the StorageAdapter interface + class MyStorage implements StorageAdapter { + // Your storage implementation + } + + // Use it in Brainy + const db = new BrainyData({ + storageAdapter: new MyStorage() + }) + ``` + +4. **Augmentations System** + ```typescript + // Create custom augmentations to extend functionality + const myAugmentation = { + type: 'memory', + name: 'my-custom-storage', + // Implementation details + } + + // Register with Brainy + db.registerAugmentation(myAugmentation) + ``` + +## Data Model + +Brainy uses a graph-based data model with two primary concepts: + +### Nouns (Entities) + +The main entities in your data (nodes in the graph): + +- Each noun has a unique ID, vector representation, and metadata +- Nouns can be categorized by type (Person, Place, Thing, Event, Concept, etc.) +- Nouns are automatically vectorized for similarity search + +### Verbs (Relationships) + +Connections between nouns (edges in the graph): + +- Each verb connects a source noun to a target noun +- Verbs have types that define the relationship (RelatedTo, Controls, Contains, etc.) +- Verbs can have their own metadata to describe the relationship + +## Command Line Interface + +Brainy includes a powerful CLI for managing your data: + +```bash +# Install globally +npm install -g @soulcraft/brainy --legacy-peer-deps + +# Initialize a database +brainy init + +# Add some data +brainy add "Cats are independent pets" '{"noun":"Thing","category":"animal"}' +brainy add "Dogs are loyal companions" '{"noun":"Thing","category":"animal"}' + +# Search for similar items +brainy search "feline pets" 5 + +# Add relationships between items +brainy addVerb RelatedTo '{"description":"Both are pets"}' + +# Visualize the graph structure +brainy visualize +brainy visualize --root --depth 3 +``` + +### Development Usage + +```bash +# Run the CLI directly from the source +npm run cli help + +# Generate a random graph for testing +npm run cli generate-random-graph --noun-count 20 --verb-count 40 +``` + +### Available Commands + +#### Basic Database Operations: + +- `init` - Initialize a new database +- `add [metadata]` - Add a new noun with text and optional metadata +- `search [limit]` - Search for nouns similar to the query +- `get ` - Get a noun by ID +- `delete ` - Delete a noun by ID +- `addVerb [metadata]` - Add a relationship +- `getVerbs ` - Get all relationships for a noun +- `status` - Show database status +- `clear` - Clear all data from the database +- `generate-random-graph` - Generate test data +- `visualize` - Visualize the graph structure +- `completion-setup` - Setup shell autocomplete + +#### Pipeline and Augmentation Commands: + +- `list-augmentations` - List all available augmentation types and registered augmentations +- `augmentation-info ` - Get detailed information about a specific augmentation type +- `test-pipeline [text]` - Test the sequential pipeline with sample data + - `-t, --data-type ` - Type of data to process (default: 'text') + - `-m, --mode ` - Execution mode: sequential, parallel, threaded (default: 'sequential') + - `-s, --stop-on-error` - Stop execution if an error occurs + - `-v, --verbose` - Show detailed output +- `stream-test` - Test streaming data through the pipeline (simulated) + - `-c, --count ` - Number of data items to stream (default: 5) + - `-i, --interval ` - Interval between data items in milliseconds (default: 1000) + - `-t, --data-type ` - Type of data to process (default: 'text') + - `-v, --verbose` - Show detailed output + +## API Reference + +### Database Management + +```typescript +// Initialize the database +await db.init() + +// Clear all data +await db.clear() + +// Get database status +const status = await db.status() + +// Backup all data from the database +const backupData = await db.backup() + +// Restore data into the database +const restoreResult = await db.restore(backupData, { clearExisting: true }) +``` + +### Working with Nouns (Entities) + +```typescript +// Add a noun (automatically vectorized) +const id = await db.add(textOrVector, { + noun: NounType.Thing, + // other metadata... +}) + +// Add multiple nouns in parallel (with multithreading) +const ids = await db.addBatch([ + { + vectorOrData: "First item to add", + metadata: { noun: NounType.Thing, category: 'example' } + }, + { + vectorOrData: "Second item to add", + metadata: { noun: NounType.Thing, category: 'example' } + }, + // More items... +], { + forceEmbed: false, + concurrency: 4 // Control the level of parallelism (default: 4) +}) + +// Retrieve a noun +const noun = await db.get(id) + +// Update noun metadata +await db.updateMetadata(id, { + noun: NounType.Thing, + // updated metadata... +}) + +// Delete a noun +await db.delete(id) + +// Search for similar nouns +const results = await db.search(vectorOrText, numResults) +const textResults = await db.searchText("query text", numResults) + +// Search by noun type +const thingNouns = await db.searchByNounTypes([NounType.Thing], numResults) +``` + +### Working with Verbs (Relationships) + +```typescript +// Add a relationship between nouns +await db.addVerb(sourceId, targetId, { + verb: VerbType.RelatedTo, + // other metadata... +}) + +// Get all relationships +const verbs = await db.getAllVerbs() + +// Get relationships by source noun +const outgoingVerbs = await db.getVerbsBySource(sourceId) + +// Get relationships by target noun +const incomingVerbs = await db.getVerbsByTarget(targetId) + +// Get relationships by type +const containsVerbs = await db.getVerbsByType(VerbType.Contains) + +// Get a specific relationship +const verb = await db.getVerb(verbId) + +// Delete a relationship +await db.deleteVerb(verbId) +``` + +## Advanced Configuration + +### Embedding + +```typescript +import { + BrainyData, + createTensorFlowEmbeddingFunction, + createThreadedEmbeddingFunction +} from '@soulcraft/brainy' + +// Use the standard TensorFlow Universal Sentence Encoder embedding function +const db = new BrainyData({ + embeddingFunction: createTensorFlowEmbeddingFunction() +}) +await db.init() + +// Or use the threaded embedding function for better performance +const threadedDb = new BrainyData({ + embeddingFunction: createThreadedEmbeddingFunction({ + fallbackToMain: true // Fall back to main thread if threading fails + }) +}) +await threadedDb.init() + +// Directly embed text to vectors +const vector = await db.embed("Some text to convert to a vector") +``` + +The threaded embedding function runs in a separate thread (Web Worker in browsers, Worker Thread in Node.js) to improve performance, especially for CPU-intensive embedding operations. It automatically falls back to the main thread if threading is not available in the current environment. + +### Performance Tuning + +Brainy now includes comprehensive multithreading support to improve performance across all environments: + +1. **Parallel Batch Processing**: Add multiple items concurrently with controlled parallelism +2. **Multithreaded Vector Search**: Perform distance calculations in parallel for faster search operations +3. **Threaded Embedding Generation**: Generate embeddings in separate threads to avoid blocking the main thread +4. **Automatic Environment Detection**: Adapts to browser (Web Workers) and Node.js (Worker Threads) environments + +```typescript +import { BrainyData, euclideanDistance } from '@soulcraft/brainy' + +// Configure with custom options +const db = new BrainyData({ + // Use Euclidean distance instead of default cosine distance + distanceFunction: euclideanDistance, + + // HNSW index configuration for search performance + hnsw: { + M: 16, // Max connections per noun + efConstruction: 200, // Construction candidate list size + efSearch: 50, // Search candidate list size + }, + + // Multithreading options + threading: { + useParallelization: true, // Enable multithreaded search operations + }, + + // Noun and Verb type validation + typeValidation: { + enforceNounTypes: true, // Validate noun types against NounType enum + enforceVerbTypes: true, // Validate verb types against VerbType enum + }, + + // Storage configuration + storage: { + requestPersistentStorage: true, + // Example configuration for cloud storage (replace with your own values): + // s3Storage: { + // bucketName: 'your-s3-bucket-name', + // region: 'your-aws-region' + // // Credentials should be provided via environment variables + // // AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY + // } + } +}) +``` + +### Optimized HNSW for Large Datasets + +Brainy includes an optimized HNSW index implementation for large datasets that may not fit entirely in memory, using a +hybrid approach: + +1. **Product Quantization** - Reduces vector dimensionality while preserving similarity relationships +2. **Disk-Based Storage** - Offloads vectors to disk when memory usage exceeds a threshold +3. **Memory-Efficient Indexing** - Optimizes memory usage for large-scale vector collections + +```typescript +import { BrainyData } from '@soulcraft/brainy' + +// Configure with optimized HNSW index for large datasets +const db = new BrainyData({ + hnswOptimized: { + // Standard HNSW parameters + M: 16, // Max connections per noun + efConstruction: 200, // Construction candidate list size + efSearch: 50, // Search candidate list size + + // Memory threshold in bytes - when exceeded, will use disk-based approach + memoryThreshold: 1024 * 1024 * 1024, // 1GB default threshold + + // Product quantization settings for dimensionality reduction + productQuantization: { + enabled: true, // Enable product quantization + numSubvectors: 16, // Number of subvectors to split the vector into + numCentroids: 256 // Number of centroids per subvector + }, + + // Whether to use disk-based storage for the index + useDiskBasedIndex: true // Enable disk-based storage + }, + + // Storage configuration (required for disk-based index) + storage: { + requestPersistentStorage: true + } +}) + +// The optimized index automatically adapts based on dataset size: +// 1. For small datasets: Uses standard in-memory approach +// 2. For medium datasets: Applies product quantization to reduce memory usage +// 3. For large datasets: Combines product quantization with disk-based storage + +// Check status to see memory usage and optimization details +const status = await db.status() +console.log(status.details.index) +``` + +## Distance Functions + +- `cosineDistance` (default) +- `euclideanDistance` +- `manhattanDistance` +- `dotProductDistance` + +## Backup and Restore + +Brainy provides backup and restore capabilities that allow you to: + +- Back up your data +- Transfer data between Brainy instances +- Restore existing data into Brainy for vectorization and indexing +- Backup data for analysis or visualization in other tools + +### Backing Up Data + +```typescript +// Backup all data from the database +const backupData = await db.backup() + +// The backup data includes: +// - All nouns (entities) with their vectors and metadata +// - All verbs (relationships) between nouns +// - Noun types and verb types +// - HNSW index data for fast similarity search +// - Version information + +// Save the backup data to a file (Node.js environment) +import fs from 'fs' + +fs.writeFileSync('brainy-backup.json', JSON.stringify(backupData, null, 2)) +``` + +### Restoring Data + +Brainy's restore functionality can handle: + +1. Complete backups with vectors and index data +2. Sparse data without vectors (vectors will be created during restore) +3. Data without HNSW index (index will be reconstructed if needed) + +```typescript +// Restore data with all options +const restoreResult = await db.restore(backupData, { + clearExisting: true // Whether to clear existing data before restore +}) + +// Import sparse data (without vectors) +// Vectors will be automatically created using the embedding function +const sparseData = { + nouns: [ + { + id: '123', + // No vector field - will be created during import + metadata: { + noun: 'Thing', + text: 'This text will be used to generate a vector' + } + } + ], + verbs: [], + version: '1.0.0' +} + +const sparseImportResult = await db.importSparseData(sparseData) +``` + +### CLI Backup/Restore + +```bash +# Backup data to a file +brainy backup --output brainy-backup.json + +# Restore data from a file +brainy restore --input brainy-backup.json --clear-existing + +# Import sparse data (without vectors) +brainy import-sparse --input sparse-data.json +``` + +## Embedding + +Brainy uses the following embedding approach: + +- TensorFlow Universal Sentence Encoder (high-quality text embeddings) +- Custom embedding functions can be plugged in for specialized domains + +## Extensions + +Brainy includes an augmentation system for extending functionality: + +- **Memory Augmentations**: Different storage backends +- **Sense Augmentations**: Process raw data +- **Cognition Augmentations**: Reasoning and inference +- **Dialog Augmentations**: Text processing and interaction +- **Perception Augmentations**: Data interpretation and visualization +- **Activation Augmentations**: Trigger actions + +### Simplified Augmentation System + +Brainy provides a simplified factory system for creating, importing, and executing augmentations with minimal +boilerplate: + +```typescript +import { + createMemoryAugmentation, + createConduitAugmentation, + createSenseAugmentation, + addWebSocketSupport, + executeStreamlined, + processStaticData, + processStreamingData, + createPipeline +} from '@soulcraft/brainy' + +// Create a memory augmentation with minimal code +const memoryAug = createMemoryAugmentation({ + name: 'simple-memory', + description: 'A simple in-memory storage augmentation', + autoRegister: true, + autoInitialize: true, + + // Implement only the methods you need + storeData: async (key, data) => { + // Your implementation here + return { + success: true, + data: true + } + }, + + retrieveData: async (key) => { + // Your implementation here + return { + success: true, + data: { example: 'data', key } + } + } +}) + +// Add WebSocket support to any augmentation +const wsAugmentation = addWebSocketSupport(memoryAug, { + connectWebSocket: async (url) => { + // Your implementation here + return { + connectionId: 'ws-1', + url, + status: 'connected' + } + } +}) + +// Process static data through a pipeline +const result = await processStaticData( + 'Input data', + [ + { + augmentation: senseAug, + method: 'processRawData', + transformArgs: (data) => [data, 'text'] + }, + { + augmentation: memoryAug, + method: 'storeData', + transformArgs: (data) => ['processed-data', data] + } + ] +) + +// Create a reusable pipeline +const pipeline = createPipeline([ + { + augmentation: senseAug, + method: 'processRawData', + transformArgs: (data) => [data, 'text'] + }, + { + augmentation: memoryAug, + method: 'storeData', + transformArgs: (data) => ['processed-data', data] + } +]) + +// Use the pipeline +const result = await pipeline('New input data') + +// Dynamically load augmentations at runtime +const loadedAugmentations = await loadAugmentationModule( + import('./my-augmentations.js'), + { + autoRegister: true, + autoInitialize: true + } +) +``` + +The simplified augmentation system provides: + +1. **Factory Functions** - Create augmentations with minimal boilerplate +2. **WebSocket Support** - Add WebSocket capabilities to any augmentation +3. **Streamlined Pipeline** - Process data through augmentations more efficiently +4. **Dynamic Loading** - Load augmentations at runtime when needed +5. **Static & Streaming Data** - Handle both static and streaming data with the same API + +### Model Control Protocol (MCP) + +Brainy includes a Model Control Protocol (MCP) implementation that allows external models to access Brainy data and use +the augmentation pipeline as tools: + +- **BrainyMCPAdapter**: Provides access to Brainy data through MCP +- **MCPAugmentationToolset**: Exposes the augmentation pipeline as tools +- **BrainyMCPService**: Integrates the adapter and toolset, providing WebSocket and REST server implementations + +Environment compatibility: + +- **BrainyMCPAdapter** and **MCPAugmentationToolset** can run in any environment (browser, Node.js, server) +- **BrainyMCPService** core functionality works in any environment, but server functionality (WebSocket/REST) is in the + cloud-wrapper project + +For detailed documentation and usage examples, see the [MCP documentation](src/mcp/README.md). + +## Cross-Environment Compatibility + +Brainy is designed to run seamlessly in any environment, from browsers to Node.js to serverless functions and +containers. All Brainy data, functions, and augmentations are environment-agnostic, allowing you to use the same code +everywhere. + +### Environment Detection + +Brainy automatically detects the environment it's running in: + +```typescript +import { environment } from '@soulcraft/brainy' + +// Check which environment we're running in +console.log(`Running in ${ + environment.isBrowser ? 'browser' : + environment.isNode ? 'Node.js' : + 'serverless/unknown' +} environment`) +``` + +### Adaptive Storage + +Storage adapters are automatically selected based on the environment: + +- **Browser**: Uses Origin Private File System (OPFS) when available, falls back to in-memory storage +- **Node.js**: Uses file system storage by default, with options for S3-compatible cloud storage +- **Serverless**: Uses in-memory storage with options for cloud persistence +- **Container**: Automatically detects and uses the appropriate storage based on available capabilities + +### Dynamic Imports + +Brainy uses dynamic imports to load environment-specific dependencies only when needed, keeping the bundle size small +and ensuring compatibility across environments. + +### Browser Support + +Works in all modern browsers: + +- Chrome 86+ +- Edge 86+ +- Opera 72+ +- Chrome for Android 86+ + +For browsers without OPFS support, falls back to in-memory storage. + +## Cloud Deployment + +Brainy can be deployed as a standalone web service on various cloud platforms using the included cloud wrapper: + +- **AWS Lambda and API Gateway**: Deploy as a serverless function with API Gateway +- **Google Cloud Run**: Deploy as a containerized service +- **Cloudflare Workers**: Deploy as a serverless function on the edge + +The cloud wrapper provides both RESTful and WebSocket APIs for all Brainy operations, enabling both request-response and +real-time communication patterns. It supports multiple storage backends and can be configured via environment variables. + +Key features of the cloud wrapper: + +- RESTful API for standard CRUD operations +- WebSocket API for real-time updates and subscriptions +- Model Control Protocol (MCP) service for external model access +- Support for multiple storage backends (Memory, FileSystem, S3) +- Configurable via environment variables +- Deployment scripts for AWS, Google Cloud, and Cloudflare + +### Deploying to the Cloud + +You can deploy the cloud wrapper to various cloud platforms using the following npm scripts from the root directory: + +```bash +# Deploy to AWS Lambda and API Gateway +npm run deploy:cloud:aws + +# Deploy to Google Cloud Run +npm run deploy:cloud:gcp + +# Deploy to Cloudflare Workers +npm run deploy:cloud:cloudflare + +# Show available deployment options +npm run deploy:cloud +``` + +Before deploying, make sure to configure the appropriate environment variables in the `cloud-wrapper/.env` file. See +the [Cloud Wrapper README](cloud-wrapper/README.md) for detailed configuration instructions and API documentation. + +## Related Projects + +- **[Cartographer](https://github.com/sodal-project/cartographer)** - A companion project that provides standardized + interfaces for interacting with Brainy + +## Demo + +The repository includes a comprehensive demo that showcases Brainy's main features: + +- `examples/demo.html` - A single demo page with animations demonstrating Brainy's features. + - **[Try the live demo](https://soulcraft-research.github.io/brainy/demo/index.html)** - Check out the + interactive demo on + GitHub Pages + - Or run it locally with `npm run demo` (see [demo instructions](README.demo.md) for details) + - To deploy your own version to GitHub Pages, run `npm run deploy:demo` + - To deploy automatically when pushing to the main branch, a GitHub Actions workflow is included in + `.github/workflows/deploy-demo.yml` + - To use a custom domain (like www.soulcraft.com): + 1. A CNAME file is already included in the demo directory + 2. In your GitHub repository settings, go to Pages > Custom domain and enter your domain + 3. Configure your domain's DNS settings to point to GitHub Pages: + - Add a CNAME record for www pointing to `.github.io` (e.g., `soulcraft-research.github.io`) + - Or for an apex domain (soulcraft.com), add A records pointing to GitHub Pages IP addresses + +The demo showcases: + +- How Brainy runs in different environments (browser, Node.js, server, cloud) +- How the noun-verb data model works +- How HNSW search works + +## Syncing Brainy Instances + +You can use the conduit augmentations to sync Brainy instances: + +- **WebSocket iConduit**: For syncing between browsers and servers, or between servers. WebSockets cannot be used for + direct browser-to-browser communication without a server in the middle. +- **WebRTC iConduit**: For direct peer-to-peer syncing between browsers. This is the recommended approach for + browser-to-browser communication. + +#### WebSocket Sync Example + +```typescript +import { + BrainyData, + pipeline, + createConduitAugmentation +} from '@soulcraft/brainy' + +// Create and initialize the database +const db = new BrainyData() +await db.init() + +// Create a WebSocket conduit augmentation +const wsConduit = await createConduitAugmentation('websocket', 'my-websocket-sync') + +// Register the augmentation with the pipeline +pipeline.register(wsConduit) + +// Connect to another Brainy instance (server or browser) +// Replace the example URL below with your actual WebSocket server URL +const connectionResult = await pipeline.executeConduitPipeline( + 'establishConnection', + ['wss://example-websocket-server.com/brainy-sync', { protocols: 'brainy-sync' }] +) + +if (connectionResult[0] && (await connectionResult[0]).success) { + const connection = (await connectionResult[0]).data + + // Read data from the remote instance + const readResult = await pipeline.executeConduitPipeline( + 'readData', + [{ connectionId: connection.connectionId, query: { type: 'getAllNouns' } }] + ) + + // Process and add the received data to the local instance + if (readResult[0] && (await readResult[0]).success) { + const remoteNouns = (await readResult[0]).data + for (const noun of remoteNouns) { + await db.add(noun.vector, noun.metadata) + } + } + + // Set up real-time sync by monitoring the stream + await wsConduit.monitorStream(connection.connectionId, async (data) => { + // Handle incoming data (e.g., new nouns, verbs, updates) + if (data.type === 'newNoun') { + await db.add(data.vector, data.metadata) + } else if (data.type === 'newVerb') { + await db.addVerb(data.sourceId, data.targetId, data.vector, data.options) + } + }) +} +``` + +#### WebRTC Peer-to-Peer Sync Example + +```typescript +import { + BrainyData, + pipeline, + createConduitAugmentation +} from '@soulcraft/brainy' + +// Create and initialize the database +const db = new BrainyData() +await db.init() + +// Create a WebRTC conduit augmentation +const webrtcConduit = await createConduitAugmentation('webrtc', 'my-webrtc-sync') + +// Register the augmentation with the pipeline +pipeline.register(webrtcConduit) + +// Connect to a peer using a signaling server +// Replace the example values below with your actual configuration +const connectionResult = await pipeline.executeConduitPipeline( + 'establishConnection', + [ + 'peer-id-to-connect-to', // Replace with actual peer ID + { + signalServerUrl: 'wss://example-signal-server.com', // Replace with your signal server + localPeerId: 'my-local-peer-id', // Replace with your local peer ID + iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] // Public STUN server + } + ] +) + +if (connectionResult[0] && (await connectionResult[0]).success) { + const connection = (await connectionResult[0]).data + + // Set up real-time sync by monitoring the stream + await webrtcConduit.monitorStream(connection.connectionId, async (data) => { + // Handle incoming data (e.g., new nouns, verbs, updates) + if (data.type === 'newNoun') { + await db.add(data.vector, data.metadata) + } else if (data.type === 'newVerb') { + await db.addVerb(data.sourceId, data.targetId, data.vector, data.options) + } + }) + + // When adding new data locally, also send to the peer + const nounId = await db.add("New data to sync", { noun: "Thing" }) + + // Send the new noun to the peer + await pipeline.executeConduitPipeline( + 'writeData', + [ + { + connectionId: connection.connectionId, + data: { + type: 'newNoun', + id: nounId, + vector: (await db.get(nounId)).vector, + metadata: (await db.get(nounId)).metadata + } + } + ] + ) +} +``` + +#### Browser-Server Search Example + +Brainy supports searching a server-hosted instance from a browser, storing results locally, and performing further +searches against the local instance: + +```typescript +import { BrainyData } from '@soulcraft/brainy' + +// Create and initialize the database with remote server configuration +// Replace the example URL below with your actual Brainy server URL +const db = new BrainyData({ + remoteServer: { + url: 'wss://example-brainy-server.com/ws', // Replace with your server URL + protocols: 'brainy-sync', + autoConnect: true // Connect automatically during initialization + } +}) +await db.init() + +// Or connect manually after initialization +if (!db.isConnectedToRemoteServer()) { + // Replace the example URL below with your actual Brainy server URL + await db.connectToRemoteServer('wss://example-brainy-server.com/ws', 'brainy-sync') +} + +// Search the remote server (results are stored locally) +const remoteResults = await db.searchText('machine learning', 5, { searchMode: 'remote' }) + +// Search the local database (includes previously stored results) +const localResults = await db.searchText('machine learning', 5, { searchMode: 'local' }) + +// Perform a combined search (local first, then remote if needed) +const combinedResults = await db.searchText('neural networks', 5, { searchMode: 'combined' }) + +// Add data to both local and remote instances +const id = await db.addToBoth('Deep learning is a subset of machine learning', { + noun: 'Concept', + category: 'AI', + tags: ['deep learning', 'neural networks'] +}) + +// Clean up when done +await db.shutDown() +``` + +For a complete demonstration of Brainy's features, see the [demo page](index.html). + +## Requirements + +- Node.js >= 23.11.0 + +## Contributing + +For detailed contribution guidelines, please see [CONTRIBUTING.md](CONTRIBUTING.md). + +We have a [Code of Conduct](CODE_OF_CONDUCT.md) that all contributors are expected to follow. + +### Reporting Issues + +We use GitHub issues to track bugs and feature requests. Please use the provided issue templates when creating a new issue: +- [Bug Report Template](.github/ISSUE_TEMPLATE/bug_report.md) +- [Feature Request Template](.github/ISSUE_TEMPLATE/feature_request.md) + +### Code Style Guidelines + +Brainy follows a specific code style to maintain consistency throughout the codebase: + +1. **No Semicolons**: All code in the project should avoid using semicolons wherever possible +2. **Formatting**: The project uses Prettier for code formatting +3. **Linting**: ESLint is configured with specific rules for the project +4. **TypeScript Configuration**: Strict type checking enabled with ES2020 target +5. **Commit Messages**: Use the imperative mood and keep the first line concise + +### Development Workflow + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Run tests with `npm test` +5. Submit a pull request + +## License + +[MIT](LICENSE) diff --git a/brainy.png b/brainy.png new file mode 100644 index 00000000..01dfc901 Binary files /dev/null and b/brainy.png differ diff --git a/clean-history.sh b/clean-history.sh new file mode 100755 index 00000000..3a18ee76 --- /dev/null +++ b/clean-history.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +# Script to remove all git history from a GitHub repository +# This script implements Method 1 from the instructions.md file + +echo "This script will remove all git history from your GitHub repository." +echo "This process is IRREVERSIBLE. Make sure you have a backup if needed." +echo "All commit history, branches, and tags will be lost." +echo "" +read -p "Are you sure you want to continue? (y/n): " confirm + +if [[ $confirm != "y" && $confirm != "Y" ]]; then + echo "Operation cancelled." + exit 0 +fi + +echo "" +echo "Starting the process to remove git history..." + +# Get the current branch name +current_branch=$(git rev-parse --abbrev-ref HEAD) +echo "Current branch: $current_branch" + +# Create a new orphan branch +echo "Creating new orphan branch 'temp_branch'..." +git checkout --orphan temp_branch + +# Add all files to the new branch +echo "Adding all files to the new branch..." +git add . + +# Commit the changes +echo "Committing changes..." +git commit -m "Initial commit" + +# Delete the main branch +echo "Deleting the '$current_branch' branch..." +git branch -D $current_branch + +# Rename the current branch to the original branch name +echo "Renaming 'temp_branch' to '$current_branch'..." +git branch -m $current_branch + +# Force push to GitHub +echo "Force pushing to GitHub (this may take a while)..." +git push -f origin $current_branch + +# Reset the upstream branch +echo "Resetting the upstream branch..." +git push origin --set-upstream $current_branch + +echo "" +echo "Git history has been successfully removed!" +echo "Please check the instructions.md file for additional steps you may need to take." +echo "These include reconfiguring GitHub Pages, GitHub Actions workflows, and npm package settings." diff --git a/cli-wrapper.js b/cli-wrapper.js new file mode 100755 index 00000000..169e2c96 --- /dev/null +++ b/cli-wrapper.js @@ -0,0 +1,56 @@ +#!/usr/bin/env node + +/** + * CLI Wrapper Script + * + * This script serves as a wrapper for the Brainy CLI, ensuring that command-line arguments + * are properly passed to the CLI when invoked through npm scripts. + */ + +import { spawn } from 'child_process' +import { fileURLToPath } from 'url' +import { dirname, join } from 'path' +import fs from 'fs' + +// Get the directory of the current module +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +// Path to the actual CLI script +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('Make sure you have built the project with "npm run build"') + process.exit(1) +} + +// Special handling for version flags +if (process.argv.includes('--version') || process.argv.includes('-V')) { + // Read version directly from package.json to ensure it's always correct + try { + const packageJsonPath = join(__dirname, 'package.json') + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) + console.log(packageJson.version) + process.exit(0) + } catch (error) { + console.error('Error loading version information:', error.message) + process.exit(1) + } +} + +// Forward all arguments to the CLI script +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')) { + args.push('--force') +} + +const cli = spawn('node', [cliPath, ...args], { stdio: 'inherit' }) + +cli.on('close', (code) => { + process.exit(code) +}) diff --git a/cloud-wrapper/.env.example b/cloud-wrapper/.env.example new file mode 100644 index 00000000..c7614644 --- /dev/null +++ b/cloud-wrapper/.env.example @@ -0,0 +1,24 @@ +# Server configuration +PORT=3000 + +# Storage configuration +# Options: 'filesystem', 'memory', 's3' +STORAGE_TYPE=filesystem +STORAGE_ROOT_DIR=./data + +# S3 Storage configuration (only used if STORAGE_TYPE=s3) +S3_BUCKET_NAME=your-bucket-name +S3_ACCESS_KEY_ID=your-access-key +S3_SECRET_ACCESS_KEY=your-secret-key +S3_REGION=us-east-1 +# Optional: For custom S3-compatible services like MinIO, DigitalOcean Spaces, etc. +# S3_ENDPOINT=https://your-custom-endpoint + +# Embedding configuration +# Set to 'true' to use simple embedding (faster but less accurate) +USE_SIMPLE_EMBEDDING=false + +# HNSW index configuration (optional) +# HNSW_M=16 # Max connections per node +# HNSW_EF_CONSTRUCTION=200 # Construction candidate list size +# HNSW_EF_SEARCH=50 # Search candidate list size diff --git a/cloud-wrapper/README.md b/cloud-wrapper/README.md new file mode 100644 index 00000000..436691cb --- /dev/null +++ b/cloud-wrapper/README.md @@ -0,0 +1,341 @@ +
+Brainy Logo + +# Brainy Cloud Wrapper +
+ +A standalone web service wrapper for the [Brainy](https://github.com/soulcraft/brainy) vector graph database. This wrapper allows you to deploy Brainy as a RESTful API service on various cloud platforms including AWS, Google Cloud, and Cloudflare. + +## Features + +- RESTful API for all Brainy operations +- WebSocket API for real-time updates and subscriptions +- Model Control Protocol (MCP) service for external model access +- Support for multiple storage backends (Memory, FileSystem, S3) +- Configurable via environment variables +- Deployment scripts for AWS, Google Cloud, and Cloudflare +- Secure by default with Helmet middleware +- Cross-origin resource sharing (CORS) support + +## Prerequisites + +- Node.js 23.11.0 or higher +- npm or yarn +- For cloud deployments: + - AWS: AWS CLI installed and configured + - Google Cloud: Google Cloud SDK installed and configured + - Cloudflare: Wrangler CLI installed and configured + +## Installation + +1. Clone the repository: + ```bash + git clone https://github.com/soulcraft/brainy.git + cd brainy/cloud-wrapper + ``` + +2. Install dependencies: + ```bash + npm install --legacy-peer-deps + ``` + +3. Create a `.env` file based on the example: + ```bash + cp .env.example .env + ``` + +4. Edit the `.env` file to configure your environment. + +## Configuration + +The cloud wrapper can be configured using environment variables. See the `.env.example` file for available options. + +### Storage Options + +- `STORAGE_TYPE`: The type of storage to use. Options: + - `memory`: In-memory storage (default for Cloudflare) + - `filesystem`: File system storage (default for local and AWS/GCP) + - `s3`: S3-compatible storage (AWS S3, MinIO, etc.) + - `r2`: Cloudflare R2 storage (Cloudflare only) + +### S3 Storage Configuration + +When using `STORAGE_TYPE=s3`, the following environment variables are required: + +- `S3_BUCKET_NAME`: The name of the S3 bucket +- `S3_ACCESS_KEY_ID`: Your S3 access key ID +- `S3_SECRET_ACCESS_KEY`: Your S3 secret access key +- `S3_REGION`: The S3 region (default: `us-east-1`) +- `S3_ENDPOINT` (optional): Custom endpoint for S3-compatible services + +### MCP Service Configuration + +The Model Control Protocol (MCP) service can be configured using the following environment variables: + +- `MCP_WS_PORT`: Port for the MCP WebSocket server (if not set, WebSocket server is disabled) +- `MCP_REST_PORT`: Port for the MCP REST server (if not set, REST server is disabled) +- `MCP_ENABLE_AUTH`: Enable authentication for MCP requests (`true` or `false`) +- `MCP_API_KEYS`: Comma-separated list of API keys for authentication +- `MCP_RATE_LIMIT_REQUESTS`: Maximum number of requests per time window +- `MCP_RATE_LIMIT_WINDOW_MS`: Time window for rate limiting in milliseconds (default: `60000`) +- `MCP_ENABLE_CORS`: Enable CORS for MCP REST server (`true` or `false`) + +## Local Development + +1. Build the project: + ```bash + npm run build + ``` + +2. Start the development server: + ```bash + npm run dev + ``` + +3. The API will be available at `http://localhost:3000`. + +## API Endpoints + +Brainy Cloud Wrapper provides REST API, WebSocket API, and Model Control Protocol (MCP) API for interacting with the database. + +### REST API + +#### Status + +- `GET /api/status`: Get database status + +#### Nouns (Entities) + +- `POST /api/nouns`: Add a new noun + - Body: `{ "text": "Your text", "metadata": { ... } }` +- `GET /api/nouns/:id`: Get a noun by ID +- `PUT /api/nouns/:id`: Update noun metadata + - Body: `{ "metadata": { ... } }` +- `DELETE /api/nouns/:id`: Delete a noun + +#### Search + +- `POST /api/search`: Search for similar nouns + - Body: `{ "query": "Your search query", "limit": 10 }` + +#### Verbs (Relationships) + +- `POST /api/verbs`: Add a relationship between nouns + - Body: `{ "sourceId": "...", "targetId": "...", "metadata": { ... } }` +- `GET /api/verbs`: Get all relationships +- `GET /api/verbs/source/:id`: Get relationships by source +- `GET /api/verbs/target/:id`: Get relationships by target +- `DELETE /api/verbs/:id`: Delete a relationship + +#### Database Management + +- `DELETE /api/clear`: Clear all data + +### WebSocket API + +The WebSocket API provides real-time communication with the Brainy database. Connect to the WebSocket server at `ws://your-server:port`. + +#### Message Format + +All WebSocket messages follow this format: + +```json +{ + "type": "messageType", + "id": "unique-message-id", + "payload": { + // Message-specific data + } +} +``` + +#### Available Message Types + +##### Status +- Request: `{ "type": "status" }` +- Response: `{ "type": "status", "id": "...", "payload": { /* database status */ } }` + +##### Nouns (Entities) +- Add a noun: + - Request: `{ "type": "addNoun", "payload": { "text": "Your text", "metadata": { ... } } }` + - Response: `{ "type": "addNoun", "id": "...", "payload": { "id": "new-noun-id" } }` + +- Get a noun: + - Request: `{ "type": "getNoun", "payload": { "id": "noun-id" } }` + - Response: `{ "type": "getNoun", "id": "...", "payload": { /* noun data */ } }` + +- Update a noun: + - Request: `{ "type": "updateNoun", "payload": { "id": "noun-id", "metadata": { ... } } }` + - Response: `{ "type": "updateNoun", "id": "...", "payload": { "success": true } }` + +- Delete a noun: + - Request: `{ "type": "deleteNoun", "payload": { "id": "noun-id" } }` + - Response: `{ "type": "deleteNoun", "id": "...", "payload": { "success": true } }` + +##### Search +- Search for similar nouns: + - Request: `{ "type": "search", "payload": { "query": "Your search query", "limit": 10 } }` + - Response: `{ "type": "search", "id": "...", "payload": [ /* search results */ ] }` + +##### Verbs (Relationships) +- Add a verb: + - Request: `{ "type": "addVerb", "payload": { "sourceId": "...", "targetId": "...", "metadata": { ... } } }` + - Response: `{ "type": "addVerb", "id": "...", "payload": { "success": true } }` + +- Get all verbs: + - Request: `{ "type": "getVerbs" }` + - Response: `{ "type": "getVerbs", "id": "...", "payload": [ /* all verbs */ ] }` + +- Get verbs by source: + - Request: `{ "type": "getVerbsBySource", "payload": { "id": "source-id" } }` + - Response: `{ "type": "getVerbsBySource", "id": "...", "payload": [ /* verbs */ ] }` + +- Get verbs by target: + - Request: `{ "type": "getVerbsByTarget", "payload": { "id": "target-id" } }` + - Response: `{ "type": "getVerbsByTarget", "id": "...", "payload": [ /* verbs */ ] }` + +- Delete a verb: + - Request: `{ "type": "deleteVerb", "payload": { "id": "verb-id" } }` + - Response: `{ "type": "deleteVerb", "id": "...", "payload": { "success": true } }` + +##### Database Management +- Clear all data: + - Request: `{ "type": "clear" }` + - Response: `{ "type": "clear", "id": "...", "payload": { "success": true } }` + +#### Real-time Subscriptions + +The WebSocket API supports subscribing to real-time updates: + +- Subscribe to updates: + - Request: `{ "type": "subscribe", "payload": { "type": "nouns" } }` + - Response: `{ "type": "subscribe", "id": "...", "payload": { "success": true, "type": "nouns" } }` + +- Unsubscribe from updates: + - Request: `{ "type": "unsubscribe", "payload": { "type": "nouns" } }` + - Response: `{ "type": "unsubscribe", "id": "...", "payload": { "success": true, "type": "nouns" } }` + +Available subscription types: +- `nouns`: Updates about nouns (added, updated, deleted) +- `verbs`: Updates about verbs (added, deleted) +- `searchResults`: Updates about search results + +When subscribed, you'll receive messages when relevant events occur: + +```json +{ + "type": "subscribe", + "payload": { + "type": "nouns", + "data": { + "type": "added", + "id": "noun-id", + "data": { /* noun data */ } + } + } +} +``` + +### MCP API + +The Model Control Protocol (MCP) API provides a standardized interface for external models to access Brainy data and use the augmentation pipeline as tools. The MCP API is available through both WebSocket and REST endpoints. + +#### MCP REST API Endpoints + +- `POST /mcp/data`: Access Brainy data (search, get, add, etc.) +- `POST /mcp/tools`: Execute augmentation pipeline tools +- `POST /mcp/system`: Get system information +- `POST /mcp/auth`: Authenticate with the MCP service +- `GET /mcp/tools`: Get available tools + +#### MCP WebSocket + +Connect to the MCP WebSocket server at `ws://your-server:MCP_WS_PORT` and send JSON messages in the MCP format: + +```json +{ + "type": "DATA_ACCESS", + "requestId": "unique-request-id", + "version": "1.0", + "operation": "search", + "parameters": { + "query": "Your search query", + "k": 5 + } +} +``` + +For detailed documentation on the MCP API, see the [MCP documentation](../src/mcp/README.md). + +## Cloud Deployment + +### AWS Lambda and API Gateway + +1. Configure AWS-specific environment variables: + ``` + AWS_REGION=us-east-1 + AWS_FUNCTION_NAME=brainy-cloud-service + AWS_API_GATEWAY_NAME=brainy-api + AWS_STAGE_NAME=prod + AWS_ACCOUNT_ID=your-account-id + ``` + +2. Deploy to AWS: + ```bash + npm run deploy:aws + ``` + +### Google Cloud Run + +1. Configure GCP-specific environment variables: + ``` + GCP_PROJECT_ID=your-project-id + GCP_REGION=us-central1 + GCP_SERVICE_NAME=brainy-cloud-service + GCP_IMAGE_NAME=brainy-cloud-service + GCP_MEMORY=512Mi + GCP_CPU=1 + GCP_MAX_INSTANCES=10 + GCP_MIN_INSTANCES=0 + ``` + +2. Deploy to Google Cloud: + ```bash + npm run deploy:gcp + ``` + +### Cloudflare Workers + +1. Configure Cloudflare-specific environment variables: + ``` + CF_ACCOUNT_ID=your-account-id + CF_WORKER_NAME=brainy-cloud-service + CF_KV_NAMESPACE=BRAINY_STORAGE + CF_R2_BUCKET=brainy-storage + ``` + +2. Deploy to Cloudflare: + ```bash + npm run deploy:cloudflare + ``` + +## Storage Considerations + +### AWS Lambda + +When deploying to AWS Lambda, it's recommended to use S3 storage for persistence. The filesystem storage option will work but data will be lost when the Lambda function is recycled. + +### Google Cloud Run + +For Google Cloud Run, you can use either filesystem storage (for ephemeral storage) or S3-compatible storage (like Google Cloud Storage with an S3 compatibility layer). + +### Cloudflare Workers + +Cloudflare Workers have limited storage options. The recommended approach is to use: +- Cloudflare KV for small datasets +- Cloudflare R2 for larger datasets +- Memory storage for temporary data + +## License + +MIT diff --git a/cloud-wrapper/package-lock.json b/cloud-wrapper/package-lock.json new file mode 100644 index 00000000..be1b9252 --- /dev/null +++ b/cloud-wrapper/package-lock.json @@ -0,0 +1,4082 @@ +{ + "name": "@soulcraft/brainy-cloud", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@soulcraft/brainy-cloud", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@soulcraft/brainy": "^0.9.2", + "cors": "^2.8.5", + "dotenv": "^16.3.1", + "express": "^4.18.2", + "helmet": "^7.1.0", + "morgan": "^1.10.0", + "uuid": "^9.0.1", + "ws": "^8.16.0" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/morgan": "^1.9.9", + "@types/node": "^20.10.5", + "@types/uuid": "^9.0.7", + "@types/ws": "^8.5.10", + "nodemon": "^3.0.2", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" + }, + "engines": { + "node": ">=23.11.0" + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.832.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.832.0.tgz", + "integrity": "sha512-S+md1zCe71SEuaRDuLHq4mzhYYkVxR1ENa8NwrgInfYoC4xo8/pESoR6i0ZZpcLs0Jw4EyVInWYs4GgDHW70qQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/credential-provider-node": "3.830.0", + "@aws-sdk/middleware-bucket-endpoint": "3.830.0", + "@aws-sdk/middleware-expect-continue": "3.821.0", + "@aws-sdk/middleware-flexible-checksums": "3.826.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-location-constraint": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-sdk-s3": "3.826.0", + "@aws-sdk/middleware-ssec": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.828.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/signature-v4-multi-region": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.828.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.3", + "@smithy/eventstream-serde-browser": "^4.0.4", + "@smithy/eventstream-serde-config-resolver": "^4.1.2", + "@smithy/eventstream-serde-node": "^4.0.4", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-blob-browser": "^4.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/hash-stream-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/md5-js": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.11", + "@smithy/middleware-retry": "^4.1.12", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.19", + "@smithy/util-defaults-mode-node": "^4.0.19", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "@smithy/util-waiter": "^4.0.5", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.830.0.tgz", + "integrity": "sha512-5zCEpfI+zwX2SIa258L+TItNbBoAvQQ6w74qdFM6YJufQ1F9tvwjTX8T+eSTT9nsFIvfYnUaGalWwJVfmJUgVQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.828.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.828.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.3", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.11", + "@smithy/middleware-retry": "^4.1.12", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.19", + "@smithy/util-defaults-mode-node": "^4.0.19", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.826.0.tgz", + "integrity": "sha512-BGbQYzWj3ps+dblq33FY5tz/SsgJCcXX0zjQlSC07tYvU1jHTUvsefphyig+fY38xZ4wdKjbTop+KUmXUYrOXw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/core": "^3.5.3", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-utf8": "^4.0.0", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.826.0.tgz", + "integrity": "sha512-DK3pQY8+iKK3MGDdC3uOZQ2psU01obaKlTYhEwNu4VWzgwQL4Vi3sWj4xSWGEK41vqZxiRLq6fOq7ysRI+qEZA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.826.0.tgz", + "integrity": "sha512-N+IVZBh+yx/9GbMZTKO/gErBi/FYZQtcFRItoLbY+6WU+0cSWyZYfkoeOxHmQV3iX9k65oljERIWUmL9x6OSQg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.830.0.tgz", + "integrity": "sha512-zeQenzvh8JRY5nULd8izdjVGoCM1tgsVVsrLSwDkHxZTTW0hW/bmOmXfvdaE0wDdomXW7m2CkQDSmP7XdvNXZg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/credential-provider-env": "3.826.0", + "@aws-sdk/credential-provider-http": "3.826.0", + "@aws-sdk/credential-provider-process": "3.826.0", + "@aws-sdk/credential-provider-sso": "3.830.0", + "@aws-sdk/credential-provider-web-identity": "3.830.0", + "@aws-sdk/nested-clients": "3.830.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.830.0.tgz", + "integrity": "sha512-X/2LrTgwtK1pkWrvofxQBI8VTi6QVLtSMpsKKPPnJQ0vgqC0e4czSIs3ZxiEsOkCBaQ2usXSiKyh0ccsQ6k2OA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.826.0", + "@aws-sdk/credential-provider-http": "3.826.0", + "@aws-sdk/credential-provider-ini": "3.830.0", + "@aws-sdk/credential-provider-process": "3.826.0", + "@aws-sdk/credential-provider-sso": "3.830.0", + "@aws-sdk/credential-provider-web-identity": "3.830.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.826.0.tgz", + "integrity": "sha512-kURrc4amu3NLtw1yZw7EoLNEVhmOMRUTs+chaNcmS+ERm3yK0nKjaJzmKahmwlTQTSl3wJ8jjK7x962VPo+zWw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.830.0.tgz", + "integrity": "sha512-+VdRpZmfekzpySqZikAKx6l5ndnLGluioIgUG4ZznrButgFD/iogzFtGmBDFB3ZLViX1l4pMXru0zFwJEZT21Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.830.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/token-providers": "3.830.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.830.0.tgz", + "integrity": "sha512-hPYrKsZeeOdLROJ59T6Y8yZ0iwC/60L3qhZXjapBFjbqBtMaQiMTI645K6xVXBioA6vxXq7B4aLOhYqk6Fy/Ww==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/nested-clients": "3.830.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.830.0.tgz", + "integrity": "sha512-ElVeCReZSH5Ds+/pkL5ebneJjuo8f49e9JXV1cYizuH0OAOQfYaBU9+M+7+rn61pTttOFE8W//qKzrXBBJhfMg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-arn-parser": "3.804.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.821.0.tgz", + "integrity": "sha512-zAOoSZKe1njOrtynvK6ZORU57YGv5I7KP4+rwOvUN3ZhJbQ7QPf8gKtFUCYAPRMegaXCKF/ADPtDZBAmM+zZ9g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.826.0.tgz", + "integrity": "sha512-Fz9w8CFYPfSlHEB6feSsi06hdS+s+FB8k5pO4L7IV0tUa78mlhxF/VNlAJaVWYyOkZXl4HPH2K48aapACSQOXw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.821.0.tgz", + "integrity": "sha512-xSMR+sopSeWGx5/4pAGhhfMvGBHioVBbqGvDs6pG64xfNwM5vq5s5v6D04e2i+uSTj4qGa71dLUs5I0UzAK3sw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.821.0.tgz", + "integrity": "sha512-sKrm80k0t3R0on8aA/WhWFoMaAl4yvdk+riotmMElLUpcMcRXAd1+600uFVrxJqZdbrKQ0mjX0PjT68DlkYXLg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.821.0.tgz", + "integrity": "sha512-0cvI0ipf2tGx7fXYEEN5fBeZDz2RnHyb9xftSgUsEq7NBxjV0yTZfLJw6Za5rjE6snC80dRN8+bTNR1tuG89zA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.821.0.tgz", + "integrity": "sha512-efmaifbhBoqKG3bAoEfDdcM8hn1psF+4qa7ykWuYmfmah59JBeqHLfz5W9m9JoTwoKPkFcVLWZxnyZzAnVBOIg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.826.0.tgz", + "integrity": "sha512-8F0qWaYKfvD/de1AKccXuigM+gb/IZSncCqxdnFWqd+TFzo9qI9Hh+TpUhWOMYSgxsMsYQ8ipmLzlD/lDhjrmA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-arn-parser": "3.804.0", + "@smithy/core": "^3.5.3", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.821.0.tgz", + "integrity": "sha512-YYi1Hhr2AYiU/24cQc8HIB+SWbQo6FBkMYojVuz/zgrtkFmALxENGF/21OPg7f/QWd+eadZJRxCjmRwh5F2Cxg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.828.0.tgz", + "integrity": "sha512-nixvI/SETXRdmrVab4D9LvXT3lrXkwAWGWk2GVvQvzlqN1/M/RfClj+o37Sn4FqRkGH9o9g7Fqb1YqZ4mqDAtA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@smithy/core": "^3.5.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.830.0.tgz", + "integrity": "sha512-5N5YTlBr1vtxf7+t+UaIQ625KEAmm7fY9o1e3MgGOi/paBoI0+axr3ud24qLIy0NSzFlAHEaxUSWxcERNjIoZw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.828.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.828.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.3", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.11", + "@smithy/middleware-retry": "^4.1.12", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.19", + "@smithy/util-defaults-mode-node": "^4.0.19", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.821.0.tgz", + "integrity": "sha512-t8og+lRCIIy5nlId0bScNpCkif8sc0LhmtaKsbm0ZPm3sCa/WhCbSZibjbZ28FNjVCV+p0D9RYZx0VDDbtWyjw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.826.0.tgz", + "integrity": "sha512-3fEi/zy6tpMzomYosksGtu7jZqGFcdBXoL7YRsG7OEeQzBbOW9B+fVaQZ4jnsViSjzA/yKydLahMrfPnt+iaxg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.830.0.tgz", + "integrity": "sha512-aJ4guFwj92nV9D+EgJPaCFKK0I3y2uMchiDfh69Zqnmwfxxxfxat6F79VA7PS0BdbjRfhLbn+Ghjftnomu2c1g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/nested-clients": "3.830.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.821.0.tgz", + "integrity": "sha512-Znroqdai1a90TlxGaJ+FK1lwC0fHpo97Xjsp5UKGR5JODYm7f9+/fF17ebO1KdoBr/Rm0UIFiF5VmI8ts9F1eA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.804.0.tgz", + "integrity": "sha512-wmBJqn1DRXnZu3b4EkE6CWnoWMo1ZMvlfkqU5zPz67xx1GMaXlDCchFvKAXMjk4jn/L1O3tKnoFDNsoLV1kgNQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.828.0.tgz", + "integrity": "sha512-RvKch111SblqdkPzg3oCIdlGxlQs+k+P7Etory9FmxPHyPDvsP1j1c74PmgYqtzzMWmoXTjd+c9naUHh9xG8xg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "@smithy/util-endpoints": "^3.0.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.804.0.tgz", + "integrity": "sha512-zVoRfpmBVPodYlnMjgVjfGoEZagyRF5IPn3Uo6ZvOZp24chnW/FRstH7ESDHDDRga4z3V+ElUQHKpFDXWyBW5A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.821.0.tgz", + "integrity": "sha512-irWZHyM0Jr1xhC+38OuZ7JB6OXMLPZlj48thElpsO1ZSLRkLZx5+I7VV6k3sp2yZ7BYbKz/G2ojSv4wdm7XTLw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.828.0.tgz", + "integrity": "sha512-LdN6fTBzTlQmc8O8f1wiZN0qF3yBWVGis7NwpWK7FUEzP9bEZRxYfIkV9oV9zpt6iNRze1SedK3JQVB/udxBoA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.828.0", + "@aws-sdk/types": "3.821.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.821.0.tgz", + "integrity": "sha512-DIIotRnefVL6DiaHtO6/21DhJ4JZnnIwdNbpwiAhdt/AVbttcE4yw925gsjur0OGv5BTYXQXU3YnANBYnZjuQA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.0.0.tgz", + "integrity": "sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.0.0.tgz", + "integrity": "sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.4.tgz", + "integrity": "sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.5.3.tgz", + "integrity": "sha512-xa5byV9fEguZNofCclv6v9ra0FYh5FATQW/da7FQUVTic94DfrN/NvmKZjrMyzbpqfot9ZjBaO8U1UeTbmSLuA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.0.8", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz", + "integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.0.4.tgz", + "integrity": "sha512-7XoWfZqWb/QoR/rAU4VSi0mWnO2vu9/ltS6JZ5ZSZv0eovLVfDfu0/AX4ub33RsJTOth3TiFWSHS5YdztvFnig==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.3.1", + "@smithy/util-hex-encoding": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.0.4.tgz", + "integrity": "sha512-3fb/9SYaYqbpy/z/H3yIi0bYKyAa89y6xPmIqwr2vQiUT2St+avRt8UKwsWt9fEdEasc5d/V+QjrviRaX1JRFA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.1.2.tgz", + "integrity": "sha512-JGtambizrWP50xHgbzZI04IWU7LdI0nh/wGbqH3sJesYToMi2j/DcoElqyOcqEIG/D4tNyxgRuaqBXWE3zOFhQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.0.4.tgz", + "integrity": "sha512-RD6UwNZ5zISpOWPuhVgRz60GkSIp0dy1fuZmj4RYmqLVRtejFqQ16WmfYDdoSoAjlp1LX+FnZo+/hkdmyyGZ1w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.0.4.tgz", + "integrity": "sha512-UeJpOmLGhq1SLox79QWw/0n2PFX+oPRE1ZyRMxPIaFEfCqWaqpB7BU9C8kpPOGEhLF7AwEqfFbtwNxGy4ReENA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.4.tgz", + "integrity": "sha512-AMtBR5pHppYMVD7z7G+OlHHAcgAN7v0kVKEpHuTO4Gb199Gowh0taYi9oDStFeUhetkeP55JLSVlTW1n9rFtUw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-blob-browser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.0.4.tgz", + "integrity": "sha512-WszRiACJiQV3QG6XMV44i5YWlkrlsM5Yxgz4jvsksuu7LDXA6wAtypfPajtNTadzpJy3KyJPoWehYpmZGKUFIQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/chunked-blob-reader": "^5.0.0", + "@smithy/chunked-blob-reader-native": "^4.0.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.4.tgz", + "integrity": "sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-stream-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.0.4.tgz", + "integrity": "sha512-wHo0d8GXyVmpmMh/qOR0R7Y46/G1y6OR8U+bSTB4ppEzRxd1xVAQ9xOE9hOc0bSjhz0ujCPAbfNLkLrpa6cevg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.4.tgz", + "integrity": "sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/md5-js": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.0.4.tgz", + "integrity": "sha512-uGLBVqcOwrLvGh/v/jw423yWHq/ofUGK1W31M2TNspLQbUV1Va0F5kTxtirkoHawODAZcjXTSGi7JwbnPcDPJg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.4.tgz", + "integrity": "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.11.tgz", + "integrity": "sha512-zDogwtRLzKl58lVS8wPcARevFZNBOOqnmzWWxVe9XiaXU2CADFjvJ9XfNibgkOWs08sxLuSr81NrpY4mgp9OwQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.5.3", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.12.tgz", + "integrity": "sha512-wvIH70c4e91NtRxdaLZF+mbLZ/HcC6yg7ySKUiufL6ESp6zJUSnJucZ309AvG9nqCFHSRB5I6T3Ez1Q9wCh0Ww==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/service-error-classification": "^4.0.5", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", + "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz", + "integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", + "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", + "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", + "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", + "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", + "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.5.tgz", + "integrity": "sha512-LvcfhrnCBvCmTee81pRlh1F39yTS/+kYleVeLCwNtkY8wtGg8V/ca9rbZZvYIl8OjlMtL6KIjaiL/lgVqHD2nA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.2.tgz", + "integrity": "sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.3.tgz", + "integrity": "sha512-xxzNYgA0HD6ETCe5QJubsxP0hQH3QK3kbpJz3QrosBCuIWyEXLR/CO5hFb2OeawEKUxMNhz3a1nuJNN2np2RMA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.5.3", + "@smithy/middleware-endpoint": "^4.1.11", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", + "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.19", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.19.tgz", + "integrity": "sha512-mvLMh87xSmQrV5XqnUYEPoiFFeEGYeAKIDDKdhE2ahqitm8OHM3aSvhqL6rrK6wm1brIk90JhxDf5lf2hbrLbQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.19", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.19.tgz", + "integrity": "sha512-8tYnx+LUfj6m+zkUUIrIQJxPM1xVxfRBvoGHua7R/i6qAxOMjqR6CpEpDwKoIs1o0+hOjGvkKE23CafKL0vJ9w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.1.4", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", + "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", + "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.5.tgz", + "integrity": "sha512-V7MSjVDTlEt/plmOFBn1762Dyu5uqMrV2Pl2X0dYk4XvWfdWJNe9Bs5Bzb56wkCuiWjSfClVMGcsuKrGj7S/yg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.0.5", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.2.tgz", + "integrity": "sha512-aI+GLi7MJoVxg24/3J1ipwLoYzgkB4kUfogZfnslcYlynj3xsQ0e7vk4TnTro9hhsS5PvX1mwmkRqqHQjwcU7w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.0.5.tgz", + "integrity": "sha512-4QvC49HTteI1gfemu0I1syWovJgPvGn7CVUoN9ZFkdvr/cCFkrEL7qNCdx/2eICqDWEGnnr68oMdSIPCLAriSQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@soulcraft/brainy": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@soulcraft/brainy/-/brainy-0.9.2.tgz", + "integrity": "sha512-xIDpurMOv7vWRpeFP9GTDtYMGDHJsJ+XlysvbCM5aeAXu1ajNOToTU+ghsPIN3UYaWXn1FBBcoBfYgMtJh4vmw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@aws-sdk/client-s3": "^3.427.0", + "@tensorflow-models/universal-sentence-encoder": "^1.3.3", + "@tensorflow/tfjs": "^4.22.0", + "@tensorflow/tfjs-backend-cpu": "^4.22.0", + "@tensorflow/tfjs-converter": "^4.22.0", + "@tensorflow/tfjs-core": "^4.22.0", + "@tensorflow/tfjs-layers": "^4.22.0", + "buffer": "^6.0.3", + "commander": "^14.0.0", + "omelette": "^0.4.17", + "uuid": "^9.0.0" + }, + "bin": { + "brainy": "cli-wrapper.js" + }, + "engines": { + "node": ">=23.11.0" + } + }, + "node_modules/@soulcraft/brainy/node_modules/@tensorflow/tfjs-backend-cpu": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.22.0.tgz", + "integrity": "sha512-1u0FmuLGuRAi8D2c3cocHTASGXOmHc/4OvoVDENJayjYkS119fcTcQf4iHrtLthWyDIPy3JiPhRrZQC9EwnhLw==", + "license": "Apache-2.0", + "dependencies": { + "@types/seedrandom": "^2.4.28", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, + "node_modules/@soulcraft/brainy/node_modules/@tensorflow/tfjs-converter": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.22.0.tgz", + "integrity": "sha512-PT43MGlnzIo+YfbsjM79Lxk9lOq6uUwZuCc8rrp0hfpLjF6Jv8jS84u2jFb+WpUeuF4K33ZDNx8CjiYrGQ2trQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, + "node_modules/@soulcraft/brainy/node_modules/@tensorflow/tfjs-core": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.22.0.tgz", + "integrity": "sha512-LEkOyzbknKFoWUwfkr59vSB68DMJ4cjwwHgicXN0DUi3a0Vh1Er3JQqCI1Hl86GGZQvY8ezVrtDIvqR1ZFW55A==", + "license": "Apache-2.0", + "dependencies": { + "@types/long": "^4.0.1", + "@types/offscreencanvas": "~2019.7.0", + "@types/seedrandom": "^2.4.28", + "@webgpu/types": "0.1.38", + "long": "4.0.0", + "node-fetch": "~2.6.1", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + } + }, + "node_modules/@soulcraft/brainy/node_modules/@tensorflow/tfjs-layers": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-4.22.0.tgz", + "integrity": "sha512-lybPj4ZNj9iIAPUj7a8ZW1hg8KQGfqWLlCZDi9eM/oNKCCAgchiyzx8OrYoWmRrB+AM6VNEeIT+2gZKg5ReihA==", + "license": "Apache-2.0 AND MIT", + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, + "node_modules/@soulcraft/brainy/node_modules/@types/offscreencanvas": { + "version": "2019.7.3", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", + "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==", + "license": "MIT" + }, + "node_modules/@soulcraft/brainy/node_modules/@webgpu/types": { + "version": "0.1.38", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.38.tgz", + "integrity": "sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA==", + "license": "BSD-3-Clause" + }, + "node_modules/@tensorflow-models/universal-sentence-encoder": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@tensorflow-models/universal-sentence-encoder/-/universal-sentence-encoder-1.3.3.tgz", + "integrity": "sha512-mipL7ad0CW6uQ68FUkNgkNj/zgA4qgBnNcnMMkNTdL9MUMnzCxu3AE8pWnx2ReKHwdqEG4e8IpaYKfH4B8bojg==", + "license": "Apache-2.0", + "peerDependencies": { + "@tensorflow/tfjs-converter": "^3.6.0", + "@tensorflow/tfjs-core": "^3.6.0" + } + }, + "node_modules/@tensorflow/tfjs": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-4.22.0.tgz", + "integrity": "sha512-0TrIrXs6/b7FLhLVNmfh8Sah6JgjBPH4mZ8JGb7NU6WW+cx00qK5BcAZxw7NCzxj6N8MRAIfHq+oNbPUNG5VAg==", + "license": "Apache-2.0", + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "4.22.0", + "@tensorflow/tfjs-backend-webgl": "4.22.0", + "@tensorflow/tfjs-converter": "4.22.0", + "@tensorflow/tfjs-core": "4.22.0", + "@tensorflow/tfjs-data": "4.22.0", + "@tensorflow/tfjs-layers": "4.22.0", + "argparse": "^1.0.10", + "chalk": "^4.1.0", + "core-js": "3.29.1", + "regenerator-runtime": "^0.13.5", + "yargs": "^16.0.3" + }, + "bin": { + "tfjs-custom-module": "dist/tools/custom_module/cli.js" + } + }, + "node_modules/@tensorflow/tfjs-converter": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-3.21.0.tgz", + "integrity": "sha512-12Y4zVDq3yW+wSjSDpSv4HnpL2sDZrNiGSg8XNiDE4HQBdjdA+a+Q3sZF/8NV9y2yoBhL5L7V4mMLDdbZBd9/Q==", + "license": "Apache-2.0", + "peer": true, + "peerDependencies": { + "@tensorflow/tfjs-core": "3.21.0" + } + }, + "node_modules/@tensorflow/tfjs-core": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-3.21.0.tgz", + "integrity": "sha512-YSfsswOqWfd+M4bXIhT3hwtAb+IV8+ODwIxwdFR/7jTAPZP1wMVnSlpKnXHAN64HFOiP+Tm3HmKusEZ0+09A0w==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/long": "^4.0.1", + "@types/offscreencanvas": "~2019.3.0", + "@types/seedrandom": "^2.4.28", + "@types/webgl-ext": "0.0.30", + "@webgpu/types": "0.1.16", + "long": "4.0.0", + "node-fetch": "~2.6.1", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + } + }, + "node_modules/@tensorflow/tfjs/node_modules/@tensorflow/tfjs-backend-cpu": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.22.0.tgz", + "integrity": "sha512-1u0FmuLGuRAi8D2c3cocHTASGXOmHc/4OvoVDENJayjYkS119fcTcQf4iHrtLthWyDIPy3JiPhRrZQC9EwnhLw==", + "license": "Apache-2.0", + "dependencies": { + "@types/seedrandom": "^2.4.28", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, + "node_modules/@tensorflow/tfjs/node_modules/@tensorflow/tfjs-backend-webgl": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-4.22.0.tgz", + "integrity": "sha512-H535XtZWnWgNwSzv538czjVlbJebDl5QTMOth4RXr2p/kJ1qSIXE0vZvEtO+5EC9b00SvhplECny2yDewQb/Yg==", + "license": "Apache-2.0", + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "4.22.0", + "@types/offscreencanvas": "~2019.3.0", + "@types/seedrandom": "^2.4.28", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, + "node_modules/@tensorflow/tfjs/node_modules/@tensorflow/tfjs-converter": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.22.0.tgz", + "integrity": "sha512-PT43MGlnzIo+YfbsjM79Lxk9lOq6uUwZuCc8rrp0hfpLjF6Jv8jS84u2jFb+WpUeuF4K33ZDNx8CjiYrGQ2trQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, + "node_modules/@tensorflow/tfjs/node_modules/@tensorflow/tfjs-core": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.22.0.tgz", + "integrity": "sha512-LEkOyzbknKFoWUwfkr59vSB68DMJ4cjwwHgicXN0DUi3a0Vh1Er3JQqCI1Hl86GGZQvY8ezVrtDIvqR1ZFW55A==", + "license": "Apache-2.0", + "dependencies": { + "@types/long": "^4.0.1", + "@types/offscreencanvas": "~2019.7.0", + "@types/seedrandom": "^2.4.28", + "@webgpu/types": "0.1.38", + "long": "4.0.0", + "node-fetch": "~2.6.1", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + } + }, + "node_modules/@tensorflow/tfjs/node_modules/@tensorflow/tfjs-core/node_modules/@types/offscreencanvas": { + "version": "2019.7.3", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", + "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==", + "license": "MIT" + }, + "node_modules/@tensorflow/tfjs/node_modules/@tensorflow/tfjs-data": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-4.22.0.tgz", + "integrity": "sha512-dYmF3LihQIGvtgJrt382hSRH4S0QuAp2w1hXJI2+kOaEqo5HnUPG0k5KA6va+S1yUhx7UBToUKCBHeLHFQRV4w==", + "license": "Apache-2.0", + "dependencies": { + "@types/node-fetch": "^2.1.2", + "node-fetch": "~2.6.1", + "string_decoder": "^1.3.0" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0", + "seedrandom": "^3.0.5" + } + }, + "node_modules/@tensorflow/tfjs/node_modules/@tensorflow/tfjs-layers": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-4.22.0.tgz", + "integrity": "sha512-lybPj4ZNj9iIAPUj7a8ZW1hg8KQGfqWLlCZDi9eM/oNKCCAgchiyzx8OrYoWmRrB+AM6VNEeIT+2gZKg5ReihA==", + "license": "Apache-2.0 AND MIT", + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, + "node_modules/@tensorflow/tfjs/node_modules/@webgpu/types": { + "version": "0.1.38", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.38.tgz", + "integrity": "sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA==", + "license": "BSD-3-Clause" + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", + "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/morgan": { + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.10.tgz", + "integrity": "sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "20.19.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.1.tgz", + "integrity": "sha512-jJD50LtlD2dodAEO653i3YF04NWak6jN3ky+Ri3Em3mGR39/glWiboM/IePaRbgwSfqM1TpGXfAg8ohn/4dTgA==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/offscreencanvas": { + "version": "2019.3.0", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz", + "integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==", + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/seedrandom": { + "version": "2.4.34", + "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.34.tgz", + "integrity": "sha512-ytDiArvrn/3Xk6/vtylys5tlY6eo7Ane0hvcx++TKo6RxQXuVfW0AF/oeWqAj9dN29SyhtawuXstgmPlwNcv/A==", + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "license": "MIT" + }, + "node_modules/@types/webgl-ext": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/webgl-ext/-/webgl-ext-0.0.30.tgz", + "integrity": "sha512-LKVgNmBxN0BbljJrVUwkxwRYqzsAEPcZOe6S2T6ZaBDIrFp0qu4FNlpc5sM1tGbXUYFgdVQIoeLk1Y1UoblyEg==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@webgpu/types": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.16.tgz", + "integrity": "sha512-9E61voMP4+Rze02jlTXud++Htpjyyk8vw5Hyw9FGRrmhHQg2GqbuOfwf5Klrb8vTxc2XWI3EfO7RUHMpxTj26A==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", + "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/core-js": { + "version": "3.29.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.29.1.tgz", + "integrity": "sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/form-data": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/helmet": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.2.0.tgz", + "integrity": "sha512-ZRiwvN089JfMXokizgqEPXsl2Guk094yExfoDXR0cBYWxtBbaSww/w+vT4WEJsBW2iTUi1GgZ6swmoug3Oy4Xw==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "license": "Apache-2.0" + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/omelette": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/omelette/-/omelette-0.4.17.tgz", + "integrity": "sha512-UlU69G6Bhu0XFjw3tjFZ0qyiMUjAOR+rdzblA1nLQ8xiqFtxOVlkhM39BlgTpLFx9fxkm6rnxNNRsS5GxE/yww==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + } + } +} diff --git a/cloud-wrapper/package.json b/cloud-wrapper/package.json new file mode 100644 index 00000000..80ff4b20 --- /dev/null +++ b/cloud-wrapper/package.json @@ -0,0 +1,92 @@ +{ + "name": "@soulcraft/brainy-cloud", + "version": "0.1.0", + "description": "Cloud deployment wrapper for Brainy vector graph database", + "main": "dist/index.js", + "type": "module", + "scripts": { + "build": "tsc", + "start": "node dist/index.js", + "dev": "nodemon --exec node --experimental-specifier-resolution=node --loader ts-node/esm src/index.ts", + "deploy:aws": "node scripts/deploy-aws.js", + "deploy:gcp": "node scripts/deploy-gcp.js", + "deploy:cloudflare": "node scripts/deploy-cloudflare.js" + }, + "keywords": [ + "brainy", + "vector-database", + "cloud", + "aws", + "google-cloud", + "cloudflare" + ], + "author": "David Snelling (david@soulcraft.com)", + "license": "MIT", + "dependencies": { + "@soulcraft/brainy": "^0.9.2", + "cors": "^2.8.5", + "dotenv": "^16.3.1", + "express": "^4.18.2", + "helmet": "^7.1.0", + "morgan": "^1.10.0", + "uuid": "^9.0.1", + "ws": "^8.16.0" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/morgan": "^1.9.9", + "@types/node": "^20.10.5", + "@types/uuid": "^9.0.7", + "@types/ws": "^8.5.10", + "nodemon": "^3.0.2", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" + }, + "engines": { + "node": ">=23.11.0" + }, + "prettier": { + "arrowParens": "always", + "bracketSameLine": true, + "bracketSpacing": true, + "htmlWhitespaceSensitivity": "css", + "printWidth": 80, + "proseWrap": "preserve", + "semi": false, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "none", + "useTabs": false + }, + "eslintConfig": { + "root": true, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "@typescript-eslint/no-explicit-any": "off", + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ], + "rules": { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "args": "after-used", + "argsIgnorePattern": "^_" + } + ], + "semi": [ + "error", + "never" + ], + "@typescript-eslint/semi": [ + "error", + "never" + ] + } + } +} diff --git a/cloud-wrapper/scripts/deploy-aws.js b/cloud-wrapper/scripts/deploy-aws.js new file mode 100644 index 00000000..57a0ec50 --- /dev/null +++ b/cloud-wrapper/scripts/deploy-aws.js @@ -0,0 +1,205 @@ +#!/usr/bin/env node + +/** + * AWS Deployment Script for Brainy Cloud Wrapper + * + * This script helps deploy the Brainy cloud wrapper to AWS Lambda and API Gateway. + * It uses the AWS SDK to create and configure the necessary resources. + * + * Prerequisites: + * - AWS CLI installed and configured with appropriate credentials + * - Node.js 23.11.0 or higher + * - Brainy cloud wrapper built (npm run build) + */ + +import { execSync } from 'child_process'; +import fs from 'fs'; +import path from 'path'; +import dotenv from 'dotenv'; + +// Load environment variables +dotenv.config(); + +// Configuration +const config = { + region: process.env.AWS_REGION || 'us-east-1', + functionName: process.env.AWS_FUNCTION_NAME || 'brainy-cloud-service', + s3Bucket: process.env.S3_BUCKET_NAME, + s3Key: process.env.S3_ACCESS_KEY_ID, + s3Secret: process.env.S3_SECRET_ACCESS_KEY, + apiGatewayName: process.env.AWS_API_GATEWAY_NAME || 'brainy-api', + stageName: process.env.AWS_STAGE_NAME || 'prod' +}; + +// Validate configuration +if (!config.s3Bucket && process.env.STORAGE_TYPE === 's3') { + console.error('Error: S3 bucket name is required when using S3 storage'); + process.exit(1); +} + +// Create deployment package +function createDeploymentPackage() { + console.log('Creating deployment package...'); + + try { + // Create a temporary directory for the deployment package + if (!fs.existsSync('deploy')) { + fs.mkdirSync('deploy'); + } + + // Copy necessary files + execSync('cp -r dist deploy/'); + execSync('cp package.json deploy/'); + execSync('cp .env deploy/'); + + // Create a zip file + execSync('cd deploy && zip -r ../deployment.zip .'); + + console.log('Deployment package created successfully'); + } catch (error) { + console.error('Error creating deployment package:', error); + process.exit(1); + } +} + +// Deploy to AWS Lambda +function deployToLambda() { + console.log('Deploying to AWS Lambda...'); + + try { + // Check if function exists + try { + execSync(`aws lambda get-function --function-name ${config.functionName} --region ${config.region}`); + + // Update existing function + execSync(`aws lambda update-function-code --function-name ${config.functionName} --zip-file fileb://deployment.zip --region ${config.region}`); + console.log(`Lambda function ${config.functionName} updated successfully`); + } catch (error) { + // Create new function + execSync(`aws lambda create-function --function-name ${config.functionName} --runtime nodejs20.x --handler dist/index.handler --zip-file fileb://deployment.zip --role arn:aws:iam::${process.env.AWS_ACCOUNT_ID}:role/lambda-basic-execution --region ${config.region}`); + console.log(`Lambda function ${config.functionName} created successfully`); + } + + // Configure environment variables + const envVars = { + Variables: { + NODE_ENV: 'production', + STORAGE_TYPE: process.env.STORAGE_TYPE || 'filesystem', + S3_BUCKET_NAME: config.s3Bucket, + S3_ACCESS_KEY_ID: config.s3Key, + S3_SECRET_ACCESS_KEY: config.s3Secret, + S3_REGION: config.region + } + }; + + execSync(`aws lambda update-function-configuration --function-name ${config.functionName} --environment '${JSON.stringify(envVars)}' --region ${config.region}`); + console.log('Lambda function environment variables configured'); + + } catch (error) { + console.error('Error deploying to AWS Lambda:', error); + process.exit(1); + } +} + +// Create API Gateway +function createApiGateway() { + console.log('Creating API Gateway...'); + + try { + // Check if API Gateway exists + let apiId; + try { + const result = execSync(`aws apigateway get-rest-apis --region ${config.region}`).toString(); + const apis = JSON.parse(result).items; + const api = apis.find(api => api.name === config.apiGatewayName); + + if (api) { + apiId = api.id; + console.log(`Using existing API Gateway: ${apiId}`); + } else { + throw new Error('API Gateway not found'); + } + } catch (error) { + // Create new API Gateway + const result = execSync(`aws apigateway create-rest-api --name ${config.apiGatewayName} --region ${config.region}`).toString(); + apiId = JSON.parse(result).id; + console.log(`API Gateway created: ${apiId}`); + } + + // Get root resource ID + const resourcesResult = execSync(`aws apigateway get-resources --rest-api-id ${apiId} --region ${config.region}`).toString(); + const rootResourceId = JSON.parse(resourcesResult).items.find(resource => resource.path === '/').id; + + // Create proxy resource + let proxyResourceId; + try { + const proxyResource = JSON.parse(resourcesResult).items.find(resource => resource.path === '/{proxy+}'); + if (proxyResource) { + proxyResourceId = proxyResource.id; + } else { + throw new Error('Proxy resource not found'); + } + } catch (error) { + const proxyResult = execSync(`aws apigateway create-resource --rest-api-id ${apiId} --parent-id ${rootResourceId} --path-part {proxy+} --region ${config.region}`).toString(); + proxyResourceId = JSON.parse(proxyResult).id; + } + + // Create ANY method + try { + execSync(`aws apigateway put-method --rest-api-id ${apiId} --resource-id ${proxyResourceId} --http-method ANY --authorization-type NONE --region ${config.region}`); + } catch (error) { + console.log('Method already exists, skipping...'); + } + + // Create integration + try { + execSync(`aws apigateway put-integration --rest-api-id ${apiId} --resource-id ${proxyResourceId} --http-method ANY --type AWS_PROXY --integration-http-method POST --uri arn:aws:apigateway:${config.region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${config.region}:${process.env.AWS_ACCOUNT_ID}:function:${config.functionName}/invocations --region ${config.region}`); + } catch (error) { + console.log('Integration already exists, skipping...'); + } + + // Deploy API + execSync(`aws apigateway create-deployment --rest-api-id ${apiId} --stage-name ${config.stageName} --region ${config.region}`); + + console.log(`API Gateway deployed: https://${apiId}.execute-api.${config.region}.amazonaws.com/${config.stageName}`); + + } catch (error) { + console.error('Error creating API Gateway:', error); + process.exit(1); + } +} + +// Clean up +function cleanup() { + console.log('Cleaning up...'); + + try { + execSync('rm -rf deploy'); + execSync('rm -f deployment.zip'); + + console.log('Cleanup completed'); + } catch (error) { + console.error('Error during cleanup:', error); + } +} + +// Main function +async function main() { + try { + console.log('Starting AWS deployment...'); + + createDeploymentPackage(); + deployToLambda(); + createApiGateway(); + cleanup(); + + console.log('Deployment completed successfully'); + } catch (error) { + console.error('Deployment failed:', error); + cleanup(); + process.exit(1); + } +} + +// Run the script +main(); diff --git a/cloud-wrapper/scripts/deploy-cloudflare.js b/cloud-wrapper/scripts/deploy-cloudflare.js new file mode 100644 index 00000000..be27f626 --- /dev/null +++ b/cloud-wrapper/scripts/deploy-cloudflare.js @@ -0,0 +1,245 @@ +#!/usr/bin/env node + +/** + * Cloudflare Deployment Script for Brainy Cloud Wrapper + * + * This script helps deploy the Brainy cloud wrapper to Cloudflare Workers. + * It uses the Wrangler CLI to create and configure the necessary resources. + * + * Prerequisites: + * - Wrangler CLI installed and configured with appropriate credentials + * - Node.js 23.11.0 or higher + * - Brainy cloud wrapper built (npm run build) + */ + +import { execSync } from 'child_process'; +import fs from 'fs'; +import path from 'path'; +import dotenv from 'dotenv'; + +// Load environment variables +dotenv.config(); + +// Configuration +const config = { + workerName: process.env.CF_WORKER_NAME || 'brainy-cloud-service', + accountId: process.env.CF_ACCOUNT_ID, + kvNamespace: process.env.CF_KV_NAMESPACE || 'BRAINY_STORAGE', + r2Bucket: process.env.CF_R2_BUCKET || 'brainy-storage' +}; + +// Validate configuration +if (!config.accountId) { + console.error('Error: CF_ACCOUNT_ID environment variable is required'); + process.exit(1); +} + +// Create wrangler.toml configuration +function createWranglerConfig() { + console.log('Creating wrangler.toml configuration...'); + + const wranglerContent = ` +name = "${config.workerName}" +main = "dist/worker.js" +compatibility_date = "2023-12-01" +node_compat = true + +account_id = "${config.accountId}" + +[build] +command = "npm run build" + +[vars] +NODE_ENV = "production" +STORAGE_TYPE = "${process.env.STORAGE_TYPE || 'memory'}" +USE_SIMPLE_EMBEDDING = "${process.env.USE_SIMPLE_EMBEDDING || 'false'}" + +# KV Namespace for storage +[[kv_namespaces]] +binding = "BRAINY_KV" +id = "${process.env.CF_KV_NAMESPACE_ID || 'create_kv_namespace_and_add_id_here'}" + +# R2 Bucket for storage (if using R2) +[[r2_buckets]] +binding = "BRAINY_R2" +bucket_name = "${config.r2Bucket}" +`; + + try { + fs.writeFileSync('wrangler.toml', wranglerContent); + console.log('wrangler.toml created successfully'); + } catch (error) { + console.error('Error creating wrangler.toml:', error); + process.exit(1); + } +} + +// Create Cloudflare Worker adapter +function createWorkerAdapter() { + console.log('Creating Cloudflare Worker adapter...'); + + const workerContent = ` +import { createServer } from '@cloudflare/workers-adapter'; +import app from './index.js'; + +// Create a fetch handler for the worker +export default { + async fetch(request, env, ctx) { + // Add environment variables to process.env + process.env = { + ...process.env, + ...env.vars, + CF_KV_NAMESPACE: env.BRAINY_KV, + CF_R2_BUCKET: env.BRAINY_R2 + }; + + // Create a server adapter + const server = createServer(app); + + // Handle the request + return server.fetch(request, env, ctx); + } +}; +`; + + try { + // Create dist directory if it doesn't exist + if (!fs.existsSync('dist')) { + fs.mkdirSync('dist'); + } + + fs.writeFileSync('dist/worker.js', workerContent); + console.log('Worker adapter created successfully'); + } catch (error) { + console.error('Error creating worker adapter:', error); + process.exit(1); + } +} + +// Update package.json to include Cloudflare Workers dependencies +function updatePackageJson() { + console.log('Updating package.json...'); + + try { + const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8')); + + // Add Cloudflare Workers dependencies + packageJson.dependencies = { + ...packageJson.dependencies, + '@cloudflare/workers-adapter': '^1.1.0', + '@cloudflare/kv-asset-handler': '^0.3.0' + }; + + // Add Cloudflare Workers scripts + packageJson.scripts = { + ...packageJson.scripts, + 'deploy:cloudflare': 'wrangler deploy' + }; + + fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2)); + console.log('package.json updated successfully'); + + // Install new dependencies + execSync('npm install --legacy-peer-deps'); + } catch (error) { + console.error('Error updating package.json:', error); + process.exit(1); + } +} + +// Create KV namespace if it doesn't exist +function createKVNamespace() { + console.log('Creating KV namespace...'); + + try { + // Check if KV namespace ID is provided + if (process.env.CF_KV_NAMESPACE_ID) { + console.log(`Using existing KV namespace: ${process.env.CF_KV_NAMESPACE_ID}`); + return; + } + + // Create KV namespace + const result = execSync(`wrangler kv:namespace create "${config.kvNamespace}"`).toString(); + const match = result.match(/id = "([^"]+)"/); + + if (match && match[1]) { + const namespaceId = match[1]; + console.log(`KV namespace created with ID: ${namespaceId}`); + + // Update wrangler.toml with the new namespace ID + let wranglerContent = fs.readFileSync('wrangler.toml', 'utf8'); + wranglerContent = wranglerContent.replace(/id = "create_kv_namespace_and_add_id_here"/, `id = "${namespaceId}"`); + fs.writeFileSync('wrangler.toml', wranglerContent); + } else { + console.error('Failed to extract KV namespace ID from wrangler output'); + } + } catch (error) { + console.error('Error creating KV namespace:', error); + console.log('You may need to create the KV namespace manually and update wrangler.toml'); + } +} + +// Create R2 bucket if it doesn't exist +function createR2Bucket() { + console.log('Creating R2 bucket...'); + + try { + // Only create R2 bucket if using R2 storage + if (process.env.STORAGE_TYPE !== 'r2') { + console.log('Skipping R2 bucket creation (not using R2 storage)'); + return; + } + + // Create R2 bucket + execSync(`wrangler r2 bucket create ${config.r2Bucket}`); + console.log(`R2 bucket created: ${config.r2Bucket}`); + } catch (error) { + console.error('Error creating R2 bucket:', error); + console.log('You may need to create the R2 bucket manually'); + } +} + +// Deploy to Cloudflare Workers +function deployToCloudflare() { + console.log('Deploying to Cloudflare Workers...'); + + try { + execSync('wrangler deploy', { stdio: 'inherit' }); + console.log('Deployed to Cloudflare Workers successfully'); + } catch (error) { + console.error('Error deploying to Cloudflare Workers:', error); + process.exit(1); + } +} + +// Clean up +function cleanup() { + console.log('Cleaning up...'); + + // No cleanup needed for now + console.log('Cleanup completed'); +} + +// Main function +async function main() { + try { + console.log('Starting Cloudflare deployment...'); + + createWranglerConfig(); + createWorkerAdapter(); + updatePackageJson(); + createKVNamespace(); + createR2Bucket(); + deployToCloudflare(); + cleanup(); + + console.log('Deployment completed successfully'); + } catch (error) { + console.error('Deployment failed:', error); + cleanup(); + process.exit(1); + } +} + +// Run the script +main(); diff --git a/cloud-wrapper/scripts/deploy-gcp.js b/cloud-wrapper/scripts/deploy-gcp.js new file mode 100644 index 00000000..8f388720 --- /dev/null +++ b/cloud-wrapper/scripts/deploy-gcp.js @@ -0,0 +1,196 @@ +#!/usr/bin/env node + +/** + * Google Cloud Platform Deployment Script for Brainy Cloud Wrapper + * + * This script helps deploy the Brainy cloud wrapper to Google Cloud Run. + * It uses the Google Cloud SDK to create and configure the necessary resources. + * + * Prerequisites: + * - Google Cloud SDK installed and configured with appropriate credentials + * - Node.js 23.11.0 or higher + * - Brainy cloud wrapper built (npm run build) + * - Docker installed (for building container images) + */ + +import { execSync } from 'child_process'; +import fs from 'fs'; +import path from 'path'; +import dotenv from 'dotenv'; + +// Load environment variables +dotenv.config(); + +// Configuration +const config = { + projectId: process.env.GCP_PROJECT_ID, + region: process.env.GCP_REGION || 'us-central1', + serviceName: process.env.GCP_SERVICE_NAME || 'brainy-cloud-service', + imageName: process.env.GCP_IMAGE_NAME || 'brainy-cloud-service', + memory: process.env.GCP_MEMORY || '512Mi', + cpu: process.env.GCP_CPU || '1', + maxInstances: process.env.GCP_MAX_INSTANCES || '10', + minInstances: process.env.GCP_MIN_INSTANCES || '0' +}; + +// Validate configuration +if (!config.projectId) { + console.error('Error: GCP_PROJECT_ID environment variable is required'); + process.exit(1); +} + +// Create Dockerfile +function createDockerfile() { + console.log('Creating Dockerfile...'); + + const dockerfileContent = ` +FROM node:23-slim + +WORKDIR /app + +# Copy package.json and package-lock.json +COPY package*.json ./ + +# Install dependencies +RUN npm install --production --legacy-peer-deps + +# Copy built application +COPY dist/ ./dist/ + +# Copy environment variables +COPY .env ./ + +# Expose port +EXPOSE 8080 + +# Start the application +CMD ["node", "dist/index.js"] +`; + + try { + fs.writeFileSync('Dockerfile', dockerfileContent); + console.log('Dockerfile created successfully'); + } catch (error) { + console.error('Error creating Dockerfile:', error); + process.exit(1); + } +} + +// Build and push Docker image +function buildAndPushImage() { + console.log('Building and pushing Docker image...'); + + try { + // Set Google Cloud project + execSync(`gcloud config set project ${config.projectId}`); + + // Build the Docker image + execSync(`docker build -t gcr.io/${config.projectId}/${config.imageName} .`); + + // Configure Docker to use gcloud as a credential helper + execSync('gcloud auth configure-docker'); + + // Push the image to Google Container Registry + execSync(`docker push gcr.io/${config.projectId}/${config.imageName}`); + + console.log('Docker image built and pushed successfully'); + } catch (error) { + console.error('Error building and pushing Docker image:', error); + process.exit(1); + } +} + +// Deploy to Google Cloud Run +function deployToCloudRun() { + console.log('Deploying to Google Cloud Run...'); + + try { + // Create environment variables string + const envVars = [ + `NODE_ENV=production`, + `PORT=8080`, + `STORAGE_TYPE=${process.env.STORAGE_TYPE || 'filesystem'}` + ]; + + // Add S3 environment variables if using S3 storage + if (process.env.STORAGE_TYPE === 's3') { + envVars.push(`S3_BUCKET_NAME=${process.env.S3_BUCKET_NAME}`); + envVars.push(`S3_ACCESS_KEY_ID=${process.env.S3_ACCESS_KEY_ID}`); + envVars.push(`S3_SECRET_ACCESS_KEY=${process.env.S3_SECRET_ACCESS_KEY}`); + envVars.push(`S3_REGION=${process.env.S3_REGION || 'us-east-1'}`); + + if (process.env.S3_ENDPOINT) { + envVars.push(`S3_ENDPOINT=${process.env.S3_ENDPOINT}`); + } + } + + // Add embedding and HNSW configuration if provided + if (process.env.USE_SIMPLE_EMBEDDING) { + envVars.push(`USE_SIMPLE_EMBEDDING=${process.env.USE_SIMPLE_EMBEDDING}`); + } + + if (process.env.HNSW_M) { + envVars.push(`HNSW_M=${process.env.HNSW_M}`); + envVars.push(`HNSW_EF_CONSTRUCTION=${process.env.HNSW_EF_CONSTRUCTION || '200'}`); + envVars.push(`HNSW_EF_SEARCH=${process.env.HNSW_EF_SEARCH || '50'}`); + } + + // Deploy to Cloud Run + const envVarsString = envVars.map(env => `--set-env-vars="${env}"`).join(' '); + + execSync(`gcloud run deploy ${config.serviceName} \ + --image=gcr.io/${config.projectId}/${config.imageName} \ + --platform=managed \ + --region=${config.region} \ + --memory=${config.memory} \ + --cpu=${config.cpu} \ + --max-instances=${config.maxInstances} \ + --min-instances=${config.minInstances} \ + --allow-unauthenticated \ + ${envVarsString}`); + + console.log('Deployed to Google Cloud Run successfully'); + + // Get the service URL + const serviceUrl = execSync(`gcloud run services describe ${config.serviceName} --region=${config.region} --format="value(status.url)"`).toString().trim(); + console.log(`Service URL: ${serviceUrl}`); + } catch (error) { + console.error('Error deploying to Google Cloud Run:', error); + process.exit(1); + } +} + +// Clean up +function cleanup() { + console.log('Cleaning up...'); + + try { + // Remove Dockerfile + fs.unlinkSync('Dockerfile'); + + console.log('Cleanup completed'); + } catch (error) { + console.error('Error during cleanup:', error); + } +} + +// Main function +async function main() { + try { + console.log('Starting Google Cloud Platform deployment...'); + + createDockerfile(); + buildAndPushImage(); + deployToCloudRun(); + cleanup(); + + console.log('Deployment completed successfully'); + } catch (error) { + console.error('Deployment failed:', error); + cleanup(); + process.exit(1); + } +} + +// Run the script +main(); diff --git a/cloud-wrapper/src/index.ts b/cloud-wrapper/src/index.ts new file mode 100644 index 00000000..f820daff --- /dev/null +++ b/cloud-wrapper/src/index.ts @@ -0,0 +1,106 @@ +import express from 'express'; +import cors from 'cors'; +import helmet from 'helmet'; +import morgan from 'morgan'; +import dotenv from 'dotenv'; +import http from 'http'; +import { WebSocketServer } from 'ws'; +import { BrainyData } from '@soulcraft/brainy'; +import { setupRoutes } from './routes.js'; +import { initializeBrainy } from './services/brainyService.js'; +import { setupWebSocketHandlers } from './websocket.js'; +import { initializeMCPService } from './services/mcpService.js'; + +// Load environment variables +dotenv.config(); + +// Initialize Express app +const app = express(); +const port = process.env.PORT || 3000; + +// Middleware +app.use(helmet()); // Security headers +app.use(cors()); // Enable CORS +app.use(morgan('combined')); // Logging +app.use(express.json()); // Parse JSON bodies + +// Initialize Brainy +let brainyInstance: BrainyData; + +// Health check endpoint +app.get('/health', (req, res) => { + res.status(200).json({ status: 'ok' }); +}); + +// Setup API routes +const apiRouter = setupRoutes(); +app.use('/api', (req, res, next) => { + // Attach brainy instance to request + (req as any).brainy = brainyInstance; + next(); +}, apiRouter); + +// Start the server +async function startServer() { + try { + // Initialize Brainy + brainyInstance = await initializeBrainy(); + console.log('Brainy initialized successfully'); + + // Create HTTP server + const server = http.createServer(app); + + // Create WebSocket server + const wss = new WebSocketServer({ server }); + + // Setup WebSocket handlers + setupWebSocketHandlers(wss, brainyInstance); + console.log('WebSocket server initialized'); + + // Initialize MCP service + const mcpWsPort = process.env.MCP_WS_PORT ? parseInt(process.env.MCP_WS_PORT, 10) : undefined; + const mcpRestPort = process.env.MCP_REST_PORT ? parseInt(process.env.MCP_REST_PORT, 10) : undefined; + + if (mcpWsPort || mcpRestPort) { + initializeMCPService(brainyInstance, { + wsPort: mcpWsPort, + restPort: mcpRestPort, + enableAuth: process.env.MCP_ENABLE_AUTH === 'true', + apiKeys: process.env.MCP_API_KEYS ? process.env.MCP_API_KEYS.split(',') : undefined, + rateLimit: process.env.MCP_RATE_LIMIT_REQUESTS ? { + windowMs: parseInt(process.env.MCP_RATE_LIMIT_WINDOW_MS || '60000', 10), + maxRequests: parseInt(process.env.MCP_RATE_LIMIT_REQUESTS, 10) + } : undefined, + cors: process.env.MCP_ENABLE_CORS === 'true' ? {} : undefined + }); + console.log('MCP service initialized'); + } + + // Start HTTP server + server.listen(port, () => { + console.log(`Server running on port ${port}`); + console.log(`WebSocket server running on ws://localhost:${port}`); + }); + } catch (error) { + console.error('Failed to start server:', error); + process.exit(1); + } +} + +// Handle graceful shutdown +process.on('SIGTERM', async () => { + console.log('SIGTERM received, shutting down gracefully'); + // Cleanup Brainy resources + await brainyInstance?.clear(); + process.exit(0); +}); + +process.on('SIGINT', async () => { + console.log('SIGINT received, shutting down gracefully'); + // Cleanup Brainy resources + await brainyInstance?.clear(); + process.exit(0); +}); + +// Start the server +startServer(); diff --git a/cloud-wrapper/src/routes.ts b/cloud-wrapper/src/routes.ts new file mode 100644 index 00000000..735a498d --- /dev/null +++ b/cloud-wrapper/src/routes.ts @@ -0,0 +1,186 @@ +import { Router, Request, Response, NextFunction, RequestHandler } from 'express'; +import { BrainyData, NounType, VerbType } from '@soulcraft/brainy'; + +// Define a custom request type that includes the Brainy instance +interface BrainyRequest extends Request { + brainy: BrainyData; +} + +// Helper function to handle async route handlers +const asyncHandler = (fn: (req: BrainyRequest, res: Response, next: NextFunction) => Promise) => + (req: Request, res: Response, next: NextFunction) => { + Promise.resolve(fn(req as BrainyRequest, res, next)).catch(next); + }; + +export function setupRoutes() { + const router = Router(); + + // Get database status + router.get('/status', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { + try { + const status = await req.brainy.status(); + res.status(200).json(status); + } catch (error) { + console.error('Error getting status:', error); + res.status(500).json({ error: 'Failed to get database status' }); + } + })); + + // Add a noun (entity) + router.post('/nouns', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { + try { + const { text, metadata } = req.body; + + if (!text) { + return res.status(400).json({ error: 'Text is required' }); + } + + const id = await req.brainy.add(text, metadata || {}); + res.status(201).json({ id }); + } catch (error) { + console.error('Error adding noun:', error); + res.status(500).json({ error: 'Failed to add noun' }); + } + })); + + // Get a noun by ID + router.get('/nouns/:id', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { + try { + const { id } = req.params; + const noun = await req.brainy.get(id); + + if (!noun) { + return res.status(404).json({ error: 'Noun not found' }); + } + + res.status(200).json(noun); + } catch (error) { + console.error('Error getting noun:', error); + res.status(500).json({ error: 'Failed to get noun' }); + } + })); + + // Update noun metadata + router.put('/nouns/:id', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { + try { + const { id } = req.params; + const { metadata } = req.body; + + if (!metadata) { + return res.status(400).json({ error: 'Metadata is required' }); + } + + await req.brainy.updateMetadata(id, metadata); + res.status(200).json({ success: true }); + } catch (error) { + console.error('Error updating noun:', error); + res.status(500).json({ error: 'Failed to update noun' }); + } + })); + + // Delete a noun + router.delete('/nouns/:id', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { + try { + const { id } = req.params; + await req.brainy.delete(id); + res.status(200).json({ success: true }); + } catch (error) { + console.error('Error deleting noun:', error); + res.status(500).json({ error: 'Failed to delete noun' }); + } + })); + + // Search for similar nouns + router.post('/search', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { + try { + const { query, limit = 10 } = req.body; + + if (!query) { + return res.status(400).json({ error: 'Query is required' }); + } + + const results = await req.brainy.searchText(query, limit); + res.status(200).json(results); + } catch (error) { + console.error('Error searching:', error); + res.status(500).json({ error: 'Failed to search' }); + } + })); + + // Add a verb (relationship) + router.post('/verbs', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { + try { + const { sourceId, targetId, metadata } = req.body; + + if (!sourceId || !targetId) { + return res.status(400).json({ error: 'Source ID and Target ID are required' }); + } + + await req.brainy.addVerb(sourceId, targetId, metadata || {}); + res.status(201).json({ success: true }); + } catch (error) { + console.error('Error adding verb:', error); + res.status(500).json({ error: 'Failed to add verb' }); + } + })); + + // Get all verbs + router.get('/verbs', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { + try { + const verbs = await req.brainy.getAllVerbs(); + res.status(200).json(verbs); + } catch (error) { + console.error('Error getting verbs:', error); + res.status(500).json({ error: 'Failed to get verbs' }); + } + })); + + // Get verbs by source + router.get('/verbs/source/:id', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { + try { + const { id } = req.params; + const verbs = await req.brainy.getVerbsBySource(id); + res.status(200).json(verbs); + } catch (error) { + console.error('Error getting verbs by source:', error); + res.status(500).json({ error: 'Failed to get verbs by source' }); + } + })); + + // Get verbs by target + router.get('/verbs/target/:id', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { + try { + const { id } = req.params; + const verbs = await req.brainy.getVerbsByTarget(id); + res.status(200).json(verbs); + } catch (error) { + console.error('Error getting verbs by target:', error); + res.status(500).json({ error: 'Failed to get verbs by target' }); + } + })); + + // Delete a verb + router.delete('/verbs/:id', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { + try { + const { id } = req.params; + await req.brainy.deleteVerb(id); + res.status(200).json({ success: true }); + } catch (error) { + console.error('Error deleting verb:', error); + res.status(500).json({ error: 'Failed to delete verb' }); + } + })); + + // Clear all data + router.delete('/clear', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { + try { + await req.brainy.clear(); + res.status(200).json({ success: true }); + } catch (error) { + console.error('Error clearing data:', error); + res.status(500).json({ error: 'Failed to clear data' }); + } + })); + + return router; +} diff --git a/cloud-wrapper/src/services/brainyService.ts b/cloud-wrapper/src/services/brainyService.ts new file mode 100644 index 00000000..8fa21853 --- /dev/null +++ b/cloud-wrapper/src/services/brainyService.ts @@ -0,0 +1,97 @@ +import { BrainyData, BrainyDataConfig } from '@soulcraft/brainy' +import dotenv from 'dotenv' + +// Load environment variables +dotenv.config() + +/** + * Extended configuration interface that includes rootDirectory + */ +interface ExtendedBrainyDataConfig extends BrainyDataConfig { + storage: BrainyDataConfig['storage'] & { + rootDirectory?: string + } +} + +/** + * Initialize the Brainy instance with appropriate configuration + * based on environment variables + */ +export async function initializeBrainy(): Promise { + // Get storage configuration from environment variables + const storageType = process.env.STORAGE_TYPE || 'filesystem' + + // Create configuration object + const config: ExtendedBrainyDataConfig = { + storage: {} + } + + // Configure storage based on type + switch (storageType) { + case 's3': + // Configure S3 storage + if (process.env.S3_ENDPOINT) { + // Use customS3Storage for S3-compatible services with custom endpoints + config.storage.customS3Storage = { + bucketName: process.env.S3_BUCKET_NAME, + accessKeyId: process.env.S3_ACCESS_KEY_ID, + secretAccessKey: process.env.S3_SECRET_ACCESS_KEY, + region: process.env.S3_REGION || 'us-east-1', + endpoint: process.env.S3_ENDPOINT + } + } else { + // Use standard S3 storage for AWS S3 + config.storage.s3Storage = { + bucketName: process.env.S3_BUCKET_NAME, + accessKeyId: process.env.S3_ACCESS_KEY_ID, + secretAccessKey: process.env.S3_SECRET_ACCESS_KEY, + region: process.env.S3_REGION || 'us-east-1' + } + } + break + + case 'filesystem': + // Configure filesystem storage + config.storage.rootDirectory = process.env.STORAGE_ROOT_DIR || './data' + break + + case 'memory': + // No additional configuration needed for memory storage + break + + default: + // Default to filesystem storage + config.storage.rootDirectory = process.env.STORAGE_ROOT_DIR || './data' + break + } + + // Note: Universal Sentence Encoder is now the only embedding option + // TensorFlow.js is required for embedding to work + + // Configure HNSW index parameters if provided + if (process.env.HNSW_M) { + config.hnsw = { + M: parseInt(process.env.HNSW_M, 10), + efConstruction: process.env.HNSW_EF_CONSTRUCTION + ? parseInt(process.env.HNSW_EF_CONSTRUCTION, 10) + : 200, + efSearch: process.env.HNSW_EF_SEARCH + ? parseInt(process.env.HNSW_EF_SEARCH, 10) + : 50 + } + } + + // Create and initialize the Brainy instance + const brainy = new BrainyData(config) + await brainy.init() + + return brainy +} + +/** + * Get the current storage type being used by Brainy + */ +export async function getStorageType(brainy: BrainyData): Promise { + const status = await brainy.status() + return status.type || 'unknown' +} diff --git a/cloud-wrapper/src/services/mcpService.ts b/cloud-wrapper/src/services/mcpService.ts new file mode 100644 index 00000000..15809564 --- /dev/null +++ b/cloud-wrapper/src/services/mcpService.ts @@ -0,0 +1,204 @@ +import { WebSocketServer } from 'ws' +import express from 'express' +import cors from 'cors' +import { BrainyData, BrainyMCPService, MCPRequestType } from '@soulcraft/brainy' +import { v4 as uuidv4 } from 'uuid' + +/** + * Initialize the MCP service with WebSocket and REST API servers + */ +export function initializeMCPService( + brainy: BrainyData, + options: { + wsPort?: number + restPort?: number + enableAuth?: boolean + apiKeys?: string[] + rateLimit?: { + windowMs: number + maxRequests: number + } + cors?: any + } +) { + // Create the MCP service + const mcpService = new BrainyMCPService(brainy, options) + + // Start WebSocket server if port is provided + if (options.wsPort) { + startWebSocketServer(mcpService, options.wsPort) + } + + // Start REST server if port is provided + if (options.restPort) { + startRESTServer(mcpService, options.restPort, options.cors) + } + + return mcpService +} + +/** + * Start a WebSocket server for the MCP service + */ +function startWebSocketServer(mcpService: BrainyMCPService, port: number) { + const wss = new WebSocketServer({ port }) + + wss.on('connection', (ws: any) => { + ws.on('message', async (message: string) => { + try { + const request = JSON.parse(message) + + // Handle the request using the MCP service + const response = await mcpService.handleMCPRequest(request) + + // Send the response + ws.send(JSON.stringify(response)) + } catch (error) { + // Send error response + ws.send( + JSON.stringify({ + success: false, + requestId: uuidv4(), + error: { + code: 'INTERNAL_ERROR', + message: error instanceof Error ? error.message : String(error) + } + }) + ) + } + }) + }) + + console.log(`MCP WebSocket server started on port ${port}`) + + return wss +} + +/** + * Start a REST server for the MCP service + */ +function startRESTServer( + mcpService: BrainyMCPService, + port: number, + corsOptions?: any +) { + const app = express() + + // Parse JSON request bodies + app.use(express.json()) + + // Enable CORS if configured + if (corsOptions) { + app.use(cors(corsOptions)) + } + + // MCP endpoints + app.post('/mcp/data', async (req: any, res: any) => { + try { + const response = await mcpService.handleMCPRequest({ + ...req.body, + type: 'DATA_ACCESS' + }) + + res.json(response) + } catch (error) { + res.status(500).json({ + success: false, + requestId: uuidv4(), + error: { + code: 'INTERNAL_ERROR', + message: error instanceof Error ? error.message : String(error) + } + }) + } + }) + + app.post('/mcp/tools', async (req: any, res: any) => { + try { + const response = await mcpService.handleMCPRequest({ + ...req.body, + type: 'TOOL_EXECUTION' + }) + + res.json(response) + } catch (error) { + res.status(500).json({ + success: false, + requestId: uuidv4(), + error: { + code: 'INTERNAL_ERROR', + message: error instanceof Error ? error.message : String(error) + } + }) + } + }) + + app.post('/mcp/system', async (req: any, res: any) => { + try { + const response = await mcpService.handleMCPRequest({ + ...req.body, + type: 'SYSTEM_INFO' + }) + + res.json(response) + } catch (error) { + res.status(500).json({ + success: false, + requestId: uuidv4(), + error: { + code: 'INTERNAL_ERROR', + message: error instanceof Error ? error.message : String(error) + } + }) + } + }) + + app.post('/mcp/auth', async (req: any, res: any) => { + try { + const response = await mcpService.handleMCPRequest({ + ...req.body, + type: 'AUTHENTICATION' + }) + + res.json(response) + } catch (error) { + res.status(500).json({ + success: false, + requestId: uuidv4(), + error: { + code: 'INTERNAL_ERROR', + message: error instanceof Error ? error.message : String(error) + } + }) + } + }) + + // Get available tools + app.get('/mcp/tools', async (req: any, res: any) => { + try { + const response = await mcpService.handleMCPRequest({ + type: MCPRequestType.SYSTEM_INFO, + requestId: uuidv4(), + version: '1.0' + }) + + res.json(response) + } catch (error) { + res.status(500).json({ + success: false, + requestId: uuidv4(), + error: { + code: 'INTERNAL_ERROR', + message: error instanceof Error ? error.message : String(error) + } + }) + } + }) + + // Start the server + const server = app.listen(port, () => { + console.log(`MCP REST API server started on port ${port}`) + }) + + return server +} diff --git a/cloud-wrapper/src/websocket.ts b/cloud-wrapper/src/websocket.ts new file mode 100644 index 00000000..5e6b5714 --- /dev/null +++ b/cloud-wrapper/src/websocket.ts @@ -0,0 +1,433 @@ +import { WebSocketServer, WebSocket } from 'ws'; +import { BrainyData } from '@soulcraft/brainy'; +import { v4 as uuidv4 } from 'uuid'; + +// Define message types +enum MessageType { + STATUS = 'status', + ADD_NOUN = 'addNoun', + GET_NOUN = 'getNoun', + UPDATE_NOUN = 'updateNoun', + DELETE_NOUN = 'deleteNoun', + SEARCH = 'search', + ADD_VERB = 'addVerb', + GET_VERBS = 'getVerbs', + GET_VERBS_BY_SOURCE = 'getVerbsBySource', + GET_VERBS_BY_TARGET = 'getVerbsByTarget', + DELETE_VERB = 'deleteVerb', + CLEAR = 'clear', + SUBSCRIBE = 'subscribe', + UNSUBSCRIBE = 'unsubscribe', + ERROR = 'error' +} + +// Define subscription types +enum SubscriptionType { + NOUNS = 'nouns', + VERBS = 'verbs', + SEARCH_RESULTS = 'searchResults' +} + +// Define message interface +interface WebSocketMessage { + type: MessageType; + id?: string; + payload?: any; +} + +// Define client interface +interface WebSocketClient { + id: string; + socket: WebSocket; + subscriptions: Set; +} + +// Store connected clients +const clients: Map = new Map(); + +// Setup WebSocket handlers +export function setupWebSocketHandlers(wss: WebSocketServer, brainy: BrainyData) { + // Handle new connections + wss.on('connection', (socket: WebSocket) => { + const clientId = uuidv4(); + + // Create client object + const client: WebSocketClient = { + id: clientId, + socket, + subscriptions: new Set() + }; + + // Store client + clients.set(clientId, client); + + console.log(`WebSocket client connected: ${clientId}`); + + // Send welcome message + sendMessage(socket, { + type: MessageType.STATUS, + id: uuidv4(), + payload: { + clientId, + message: 'Connected to Brainy WebSocket API', + status: 'connected' + } + }); + + // Handle messages + socket.on('message', async (data: WebSocket.Data) => { + try { + const message: WebSocketMessage = JSON.parse(data.toString()); + + // Ensure message has an ID + const messageId = message.id || uuidv4(); + + console.log(`Received message: ${message.type} (${messageId})`); + + // Process message based on type + await processMessage(client, message, messageId, brainy); + } catch (error) { + console.error('Error processing WebSocket message:', error); + sendMessage(socket, { + type: MessageType.ERROR, + id: uuidv4(), + payload: { + message: 'Invalid message format', + error: (error as Error).message + } + }); + } + }); + + // Handle disconnection + socket.on('close', () => { + // Remove client + clients.delete(clientId); + console.log(`WebSocket client disconnected: ${clientId}`); + }); + }); +} + +// Process incoming messages +async function processMessage( + client: WebSocketClient, + message: WebSocketMessage, + messageId: string, + brainy: BrainyData +) { + const { socket } = client; + + try { + switch (message.type) { + case MessageType.STATUS: + // Return database status + const status = await brainy.status(); + sendMessage(socket, { + type: MessageType.STATUS, + id: messageId, + payload: status + }); + break; + + case MessageType.ADD_NOUN: + // Add a noun + if (!message.payload?.text) { + throw new Error('Text is required'); + } + + const nounId = await brainy.add( + message.payload.text, + message.payload.metadata || {} + ); + + sendMessage(socket, { + type: MessageType.ADD_NOUN, + id: messageId, + payload: { id: nounId } + }); + + // Notify subscribers + notifySubscribers(SubscriptionType.NOUNS, { + type: 'added', + id: nounId, + data: await brainy.get(nounId) + }); + + break; + + case MessageType.GET_NOUN: + // Get a noun by ID + if (!message.payload?.id) { + throw new Error('Noun ID is required'); + } + + const noun = await brainy.get(message.payload.id); + + if (!noun) { + throw new Error('Noun not found'); + } + + sendMessage(socket, { + type: MessageType.GET_NOUN, + id: messageId, + payload: noun + }); + break; + + case MessageType.UPDATE_NOUN: + // Update noun metadata + if (!message.payload?.id || !message.payload?.metadata) { + throw new Error('Noun ID and metadata are required'); + } + + await brainy.updateMetadata(message.payload.id, message.payload.metadata); + + sendMessage(socket, { + type: MessageType.UPDATE_NOUN, + id: messageId, + payload: { success: true } + }); + + // Notify subscribers + notifySubscribers(SubscriptionType.NOUNS, { + type: 'updated', + id: message.payload.id, + data: await brainy.get(message.payload.id) + }); + + break; + + case MessageType.DELETE_NOUN: + // Delete a noun + if (!message.payload?.id) { + throw new Error('Noun ID is required'); + } + + await brainy.delete(message.payload.id); + + sendMessage(socket, { + type: MessageType.DELETE_NOUN, + id: messageId, + payload: { success: true } + }); + + // Notify subscribers + notifySubscribers(SubscriptionType.NOUNS, { + type: 'deleted', + id: message.payload.id + }); + + break; + + case MessageType.SEARCH: + // Search for similar nouns + if (!message.payload?.query) { + throw new Error('Query is required'); + } + + const limit = message.payload.limit || 10; + const results = await brainy.searchText(message.payload.query, limit); + + sendMessage(socket, { + type: MessageType.SEARCH, + id: messageId, + payload: results + }); + + // Notify subscribers + notifySubscribers(SubscriptionType.SEARCH_RESULTS, { + type: 'search', + query: message.payload.query, + results + }); + + break; + + case MessageType.ADD_VERB: + // Add a verb (relationship) + if (!message.payload?.sourceId || !message.payload?.targetId) { + throw new Error('Source ID and Target ID are required'); + } + + await brainy.addVerb( + message.payload.sourceId, + message.payload.targetId, + message.payload.metadata || {} + ); + + sendMessage(socket, { + type: MessageType.ADD_VERB, + id: messageId, + payload: { success: true } + }); + + // Notify subscribers + notifySubscribers(SubscriptionType.VERBS, { + type: 'added', + sourceId: message.payload.sourceId, + targetId: message.payload.targetId, + metadata: message.payload.metadata + }); + + break; + + case MessageType.GET_VERBS: + // Get all verbs + const verbs = await brainy.getAllVerbs(); + + sendMessage(socket, { + type: MessageType.GET_VERBS, + id: messageId, + payload: verbs + }); + break; + + case MessageType.GET_VERBS_BY_SOURCE: + // Get verbs by source + if (!message.payload?.id) { + throw new Error('Source ID is required'); + } + + const sourceVerbs = await brainy.getVerbsBySource(message.payload.id); + + sendMessage(socket, { + type: MessageType.GET_VERBS_BY_SOURCE, + id: messageId, + payload: sourceVerbs + }); + break; + + case MessageType.GET_VERBS_BY_TARGET: + // Get verbs by target + if (!message.payload?.id) { + throw new Error('Target ID is required'); + } + + const targetVerbs = await brainy.getVerbsByTarget(message.payload.id); + + sendMessage(socket, { + type: MessageType.GET_VERBS_BY_TARGET, + id: messageId, + payload: targetVerbs + }); + break; + + case MessageType.DELETE_VERB: + // Delete a verb + if (!message.payload?.id) { + throw new Error('Verb ID is required'); + } + + await brainy.deleteVerb(message.payload.id); + + sendMessage(socket, { + type: MessageType.DELETE_VERB, + id: messageId, + payload: { success: true } + }); + + // Notify subscribers + notifySubscribers(SubscriptionType.VERBS, { + type: 'deleted', + id: message.payload.id + }); + + break; + + case MessageType.CLEAR: + // Clear all data + await brainy.clear(); + + sendMessage(socket, { + type: MessageType.CLEAR, + id: messageId, + payload: { success: true } + }); + + // Notify all subscribers + notifySubscribers(SubscriptionType.NOUNS, { type: 'cleared' }); + notifySubscribers(SubscriptionType.VERBS, { type: 'cleared' }); + + break; + + case MessageType.SUBSCRIBE: + // Subscribe to events + if (!message.payload?.type) { + throw new Error('Subscription type is required'); + } + + const subscriptionType = message.payload.type as SubscriptionType; + + // Add subscription + client.subscriptions.add(subscriptionType); + + sendMessage(socket, { + type: MessageType.SUBSCRIBE, + id: messageId, + payload: { + success: true, + type: subscriptionType + } + }); + break; + + case MessageType.UNSUBSCRIBE: + // Unsubscribe from events + if (!message.payload?.type) { + throw new Error('Subscription type is required'); + } + + const unsubscribeType = message.payload.type as SubscriptionType; + + // Remove subscription + client.subscriptions.delete(unsubscribeType); + + sendMessage(socket, { + type: MessageType.UNSUBSCRIBE, + id: messageId, + payload: { + success: true, + type: unsubscribeType + } + }); + break; + + default: + throw new Error(`Unknown message type: ${message.type}`); + } + } catch (error) { + console.error(`Error processing message ${message.type}:`, error); + + sendMessage(socket, { + type: MessageType.ERROR, + id: messageId, + payload: { + originalType: message.type, + message: 'Error processing message', + error: (error as Error).message + } + }); + } +} + +// Send a message to a client +function sendMessage(socket: WebSocket, message: WebSocketMessage) { + if (socket.readyState === WebSocket.OPEN) { + socket.send(JSON.stringify(message)); + } +} + +// Notify subscribers of events +function notifySubscribers(type: SubscriptionType, data: any) { + for (const client of clients.values()) { + if (client.subscriptions.has(type)) { + sendMessage(client.socket, { + type: MessageType.SUBSCRIBE, + payload: { + type, + data + } + }); + } + } +} diff --git a/cloud-wrapper/tsconfig.json b/cloud-wrapper/tsconfig.json new file mode 100644 index 00000000..371697c0 --- /dev/null +++ b/cloud-wrapper/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "esModuleInterop": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "declaration": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/demo/index.html b/demo/index.html new file mode 100644 index 00000000..3ebc1de7 --- /dev/null +++ b/demo/index.html @@ -0,0 +1,4240 @@ + + + + + + Brainy Interactive Demo - A Lightweight Graph & Vector Data Platform + + + + + + + + + +
+ + +
+ Brainy Logo +
+

Brainy Interactive Demo

+

A powerful graph & vector data platform for AI applications across any environment

+
+
+ + +
+

🚀 Key Features

+

Explore all of Brainy's powerful capabilities in this interactive demo:

+ +
+
+

🏃‍♂️ Run Everywhere

+

Run Brainy in a browser, container, serverless cloud service or the terminal!

+
+
+

🔍 Vector Search

+

Find semantically similar content using embeddings (like having ESP for your data!)

+
+
+

🌐 Graph Database

+

Connect data with meaningful relationships (your data's social network)

+
+
+

🌊 Streaming Pipeline

+

Process data in real-time as it flows through the system (like a data waterslide!)

+
+
+

🧩 Extensible Augmentations

+

Customize and extend functionality with pluggable components (LEGO blocks for your data!)

+
+
+

🔌 Built-in Conduits

+

Sync and scale across instances with WebSocket and WebRTC (your data's teleportation system!)

+
+
+

🤖 TensorFlow Integration

+

Use TensorFlow.js for high-quality embeddings (included as a required dependency)

+
+
+

🧠 Adaptive Intelligence

+

Automatically optimizes for your environment and usage patterns

+
+
+

🌍 Cross-Platform

+

Works everywhere you do: browsers, Node.js and server environments

+
+
+

💾 Persistent Storage

+

Data persists across sessions and scales to any size (no memory loss here!)

+
+
+

🔧 TypeScript Support

+

Fully typed API with generics (for those who like their code tidy)

+
+
+

📱 CLI Tools

+

Powerful command-line interface for data management (command line wizardry)

+
+
+

📊 Data Visualization

+

Interactive graph and vector space visualizations

+
+
+

☁️ Cloud Ready

+

Easy deployment to Google, AWS, Azure and other cloud platforms

+
+
+

🎮 Model Control Protocol (MCP)

+

Allow external AI models to access Brainy data and use augmentation pipeline as tools

+
+
+
+ + +
+

📊 What Can You Build?

+
+
+

🔍 Semantic Search Engines

+

Find content based on meaning, not just keywords

+
+
+

👍 Recommendation Systems

+

Suggest similar items based on vector similarity

+
+
+

🧠 Knowledge Graphs

+

Build connected data structures with relationships

+
+
+

🤖 AI Applications

+

Store and retrieve embeddings for machine learning models

+
+
+

💡 AI-Enhanced Applications

+

Build applications that leverage vector embeddings for intelligent data processing

+
+
+

📁 Data Organization Tools

+

Automatically categorize and connect related information

+
+
+

🔄 Adaptive Experiences

+

Create applications that learn and evolve with your users

+
+
+

🔌 Model-Integrated Systems

+

Connect external AI models to Brainy data and tools using MCP

+
+
+
+ +
+

Interactive Brainy Explorer

+

This interactive demo guides you through Brainy's features step by step, from basic operations to advanced + capabilities.

+ + +
+

Step 1: Getting Started with Brainy

+

Let's start by initializing the database and exploring basic operations.

+ +
+ + + + + +
+ +
+ Database not initialized. Click "Initialize Database" to start. +
+ +
+
+ +
+
+
+ Database visualization will appear here after initialization +
+
+
+
+ + +
+

Step 2: Semantic Search

+

Search your data using vector embeddings for semantic understanding.

+ + +
+
+ +
+ +
+
+ +
+
+ + +
+
+
+ +

+ Try searching for topics in the sample data like "travel", "AI", "fitness", "food", "design", + "sustainability", or specific concepts like "workout challenge", "social media", "photography", etc. +

+ +
+
+ Search results will appear here... +
+
+ +
+
+ + + +
+
+
+ Search results visualization +
+
+
+
+ + +
+

Step 3: Adding Entities (Nouns)

+

Now that your database is initialized, let's add some entities (nouns) to it.

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ Entity operations will appear here... +
+
+ + +
+

Step 4: Creating Relationships (Verbs)

+

Connect your entities with meaningful relationships to build a knowledge graph.

+ +
+ + + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+

Step 5: Advanced Features

+

Explore Brainy's advanced capabilities for more complex use cases.

+ +
+
+

🔍 Vector Search

+

Explore how HNSW makes semantic search fast and efficient.

+ +
+ +
+

🌐 Graph Traversal

+

Navigate through connected entities in your knowledge graph.

+ +
+ +
+

💾 Data Management

+

Backup, restore and manage your data.

+
+ + +
+
+ +
+

🧠 Embedding Pipeline

+

See how text is converted to vectors for semantic understanding.

+ +
+
+ +
+ Select a feature above to see a detailed demonstration... +
+
+ + +
+

Step 6: Command Line Interface

+

Try Brainy's CLI commands for scripting and automation.

+ +
+
+
+ brainy$ + +
+
+
+ +
+ + + + + +
+ +
+ Common Commands:
+ brainy init - Initialize a new database
+ brainy add <data> - Add data to the database
+ brainy search <query> - Search for entities
+ brainy relate <from> <to> - Create relationships
+ brainy status - Show database status
+ brainy help - Show all commands +
+
+
+ + +
+

🏁 Quick Start

+ +
+
+

Node.js Environment

+
+import { BrainyData, NounType, VerbType } from '@soulcraft/brainy'
+
+// Create and initialize the database
+const db = new BrainyData()
+await db.init()
+
+// Add data (automatically converted to vectors)
+const catId = await db.add("Cats are independent pets", {
+    noun: NounType.Thing,
+    category: 'animal'
+})
+
+const dogId = await db.add("Dogs are loyal companions", {
+    noun: NounType.Thing,
+    category: 'animal'
+})
+
+// Search for similar items
+const results = await db.searchText("feline pets", 2)
+console.log(results)
+// Returns items similar to "feline pets" with similarity scores
+
+// Add a relationship between items
+await db.addVerb(catId, dogId, {
+    verb: VerbType.RelatedTo,
+    description: 'Both are common household pets'
+})
+        
+
+ +
+

Browser Environment

+

Brainy uses a unified build that automatically adapts to your environment:

+ +

Option 1: Standard Import

+
+// Standard import - automatically adapts to any environment
+import { BrainyData, NounType, VerbType } from '@soulcraft/brainy'
+
+// Use the same API as in Node.js
+const db = new BrainyData()
+await db.init()
+// ...
+        
+ +

Option 2: Using a script tag in HTML

+
+<script type="module">
+  // Use local files instead of CDN
+  import { BrainyData, NounType, VerbType } from '../dist/unified.js'
+
+  // Or minified version
+  // import { BrainyData, NounType, VerbType } from '../dist/unified.min.js'
+
+  const db = new BrainyData()
+  await db.init()
+  // ...
+</script>
+        
+

Modern bundlers like Webpack, Rollup and Vite will automatically use the unified build which adapts to any + environment.

+
+
+
+ + +
+ + + + diff --git a/encoded-image.html b/encoded-image.html new file mode 100644 index 00000000..cfc1a5e2 --- /dev/null +++ b/encoded-image.html @@ -0,0 +1 @@ +Brainy Logo \ No newline at end of file diff --git a/encoded-image.txt b/encoded-image.txt new file mode 100644 index 00000000..00a5b8c4 --- /dev/null +++ b/encoded-image.txt @@ -0,0 +1 @@ +Brainy Logo diff --git a/index.html b/index.html new file mode 100644 index 00000000..1018e443 --- /dev/null +++ b/index.html @@ -0,0 +1,17 @@ + + + + + + Brainy Interactive Demo - Redirecting... + + + + + +

Redirecting to Brainy Interactive Demo...

+

If you are not redirected automatically, please click the link above.

+ + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..ffdbe268 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,8315 @@ +{ + "name": "@soulcraft/brainy", + "version": "0.9.4", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@soulcraft/brainy", + "version": "0.9.4", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@aws-sdk/client-s3": "^3.427.0", + "@tensorflow-models/universal-sentence-encoder": "^1.3.3", + "@tensorflow/tfjs": "^4.22.0", + "@tensorflow/tfjs-backend-cpu": "^4.22.0", + "@tensorflow/tfjs-converter": "^4.22.0", + "@tensorflow/tfjs-core": "^4.22.0", + "@tensorflow/tfjs-layers": "^4.22.0", + "buffer": "^6.0.3", + "commander": "^14.0.0", + "omelette": "^0.4.17", + "uuid": "^9.0.0" + }, + "bin": { + "brainy": "cli-wrapper.js" + }, + "devDependencies": { + "@rollup/plugin-commonjs": "^25.0.7", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-replace": "^5.0.5", + "@rollup/plugin-typescript": "^11.1.6", + "@types/jest": "^29.5.3", + "@types/node": "^20.4.5", + "@types/omelette": "^0.4.5", + "@types/uuid": "^10.0.0", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "eslint": "^8.45.0", + "gh-pages": "^6.3.0", + "jest": "^29.6.2", + "rollup": "^4.12.0", + "rollup-plugin-terser": "^7.0.2", + "ts-jest": "^29.1.1", + "tslib": "^2.8.1", + "typescript": "^5.1.6" + }, + "engines": { + "node": ">=23.11.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.824.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.824.0.tgz", + "integrity": "sha512-7neTQIdSVP/F4RTWG5T87LDpB955iQD6lxg9nJ00fdkIPczDcRtAEXow44NjF4fEdpQ1A9jokUtBSVE+GMXZ/A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.823.0", + "@aws-sdk/credential-provider-node": "3.823.0", + "@aws-sdk/middleware-bucket-endpoint": "3.821.0", + "@aws-sdk/middleware-expect-continue": "3.821.0", + "@aws-sdk/middleware-flexible-checksums": "3.823.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-location-constraint": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-sdk-s3": "3.823.0", + "@aws-sdk/middleware-ssec": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.823.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/signature-v4-multi-region": "3.824.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.821.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.823.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.1", + "@smithy/eventstream-serde-browser": "^4.0.4", + "@smithy/eventstream-serde-config-resolver": "^4.1.2", + "@smithy/eventstream-serde-node": "^4.0.4", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-blob-browser": "^4.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/hash-stream-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/md5-js": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.9", + "@smithy/middleware-retry": "^4.1.10", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.17", + "@smithy/util-defaults-mode-node": "^4.0.17", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "@smithy/util-waiter": "^4.0.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.823.0.tgz", + "integrity": "sha512-dBWdsbyGw8rPfdCsZySNtTOGQK4EZ8lxB/CneSQWRBPHgQ+Ys88NXxImO8xfWO7Itt1eh8O7UDTZ9+smcvw2pw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.823.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.823.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.821.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.823.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.1", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.9", + "@smithy/middleware-retry": "^4.1.10", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.17", + "@smithy/util-defaults-mode-node": "^4.0.17", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.823.0.tgz", + "integrity": "sha512-1Cf4w8J7wYexz0KU3zpaikHvldGXQEjFldHOhm0SBGRy7qfYNXecfJAamccF7RdgLxKGgkv5Pl9zX/Z/DcW9zg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/core": "^3.5.1", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-utf8": "^4.0.0", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.823.0.tgz", + "integrity": "sha512-AIrLLwumObge+U1klN4j5ToIozI+gE9NosENRyHe0GIIZgTLOG/8jxrMFVYFeNHs7RUtjDTxxewislhFyGxJ/w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.823.0.tgz", + "integrity": "sha512-u4DXvB/J/o2bcvP1JP6n3ch7V3/NngmiJFPsM0hKUyRlLuWM37HEDEdjPRs3/uL/soTxrEhWKTA9//YVkvzI0w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.823.0.tgz", + "integrity": "sha512-C0o63qviK5yFvjH9zKWAnCUBkssJoQ1A1XAHe0IAQkurzoNBSmu9oVemqwnKKHA4H6QrmusaEERfL00yohIkJA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.823.0", + "@aws-sdk/credential-provider-env": "3.823.0", + "@aws-sdk/credential-provider-http": "3.823.0", + "@aws-sdk/credential-provider-process": "3.823.0", + "@aws-sdk/credential-provider-sso": "3.823.0", + "@aws-sdk/credential-provider-web-identity": "3.823.0", + "@aws-sdk/nested-clients": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.823.0.tgz", + "integrity": "sha512-nfSxXVuZ+2GJDpVFlflNfh55Yb4BtDsXLGNssXF5YU6UgSPsi8j2YkaE92Jv2s7dlUK07l0vRpLyPuXMaGeiRQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.823.0", + "@aws-sdk/credential-provider-http": "3.823.0", + "@aws-sdk/credential-provider-ini": "3.823.0", + "@aws-sdk/credential-provider-process": "3.823.0", + "@aws-sdk/credential-provider-sso": "3.823.0", + "@aws-sdk/credential-provider-web-identity": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.823.0.tgz", + "integrity": "sha512-U/A10/7zu2FbMFFVpIw95y0TZf+oYyrhZTBn9eL8zgWcrYRqxrxdqtPj/zMrfIfyIvQUhuJSENN4dx4tfpCMWQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.823.0.tgz", + "integrity": "sha512-ff8IM80Wqz1V7VVMaMUqO2iR417jggfGWLPl8j2l7uCgwpEyop1ZZl5CFVYEwSupRBtwp+VlW1gTCk7ke56MUw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.823.0", + "@aws-sdk/core": "3.823.0", + "@aws-sdk/token-providers": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.823.0.tgz", + "integrity": "sha512-lzoZdJMQq9w7i4lXVka30cVBe/dZoUDZST8Xz/soEd73gg7RTKgG+0szL4xFWgdBDgcJDWLfZfJzlbyIVyAyOA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.823.0", + "@aws-sdk/nested-clients": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.821.0.tgz", + "integrity": "sha512-cebgeytKlWOgGczLo3BPvNY9XlzAzGZQANSysgJ2/8PSldmUpXRIF+GKPXDVhXeInWYHIfB8zZi3RqrPoXcNYQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-arn-parser": "3.804.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.821.0.tgz", + "integrity": "sha512-zAOoSZKe1njOrtynvK6ZORU57YGv5I7KP4+rwOvUN3ZhJbQ7QPf8gKtFUCYAPRMegaXCKF/ADPtDZBAmM+zZ9g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.823.0.tgz", + "integrity": "sha512-Elt6G1ryEEdkrppqbyJON0o2x4x9xKknimJtMLdfG1b4YfO9X+UB31pk4R2SHvMYfrJ+p8DE2jRAhvV4g/dwIQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.821.0.tgz", + "integrity": "sha512-xSMR+sopSeWGx5/4pAGhhfMvGBHioVBbqGvDs6pG64xfNwM5vq5s5v6D04e2i+uSTj4qGa71dLUs5I0UzAK3sw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.821.0.tgz", + "integrity": "sha512-sKrm80k0t3R0on8aA/WhWFoMaAl4yvdk+riotmMElLUpcMcRXAd1+600uFVrxJqZdbrKQ0mjX0PjT68DlkYXLg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.821.0.tgz", + "integrity": "sha512-0cvI0ipf2tGx7fXYEEN5fBeZDz2RnHyb9xftSgUsEq7NBxjV0yTZfLJw6Za5rjE6snC80dRN8+bTNR1tuG89zA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.821.0.tgz", + "integrity": "sha512-efmaifbhBoqKG3bAoEfDdcM8hn1psF+4qa7ykWuYmfmah59JBeqHLfz5W9m9JoTwoKPkFcVLWZxnyZzAnVBOIg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.823.0.tgz", + "integrity": "sha512-UV755wt2HDru8PbxLn2S0Fvwgdn9mYamexn31Q6wyUGQ6rkpjKNEzL+oNDGQQmDQAOcQO+nLubKFsCwtBM02fQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-arn-parser": "3.804.0", + "@smithy/core": "^3.5.1", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.821.0.tgz", + "integrity": "sha512-YYi1Hhr2AYiU/24cQc8HIB+SWbQo6FBkMYojVuz/zgrtkFmALxENGF/21OPg7f/QWd+eadZJRxCjmRwh5F2Cxg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.823.0.tgz", + "integrity": "sha512-TKRQK09ld1LrIPExC9rIDpqnMsWcv+eq8ABKFHVo8mDLTSuWx/IiQ4eCh9T5zDuEZcLY4nNYCSzXKqw6XKcMCA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.821.0", + "@smithy/core": "^3.5.1", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.823.0.tgz", + "integrity": "sha512-/BcyOBubrJnd2gxlbbmNJR1w0Z3OVN/UE8Yz20e+ou+Mijjv7EbtVwmWvio1e3ZjphwdA8tVfPYZKwXmrvHKmQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.823.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.823.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.821.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.823.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.1", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.9", + "@smithy/middleware-retry": "^4.1.10", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.17", + "@smithy/util-defaults-mode-node": "^4.0.17", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.821.0.tgz", + "integrity": "sha512-t8og+lRCIIy5nlId0bScNpCkif8sc0LhmtaKsbm0ZPm3sCa/WhCbSZibjbZ28FNjVCV+p0D9RYZx0VDDbtWyjw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.824.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.824.0.tgz", + "integrity": "sha512-HBjuWeN6Z1pvJjUvGXdMNLwEypKKB4km6zXj9jsbOOwP8NTL6J5rY+JmlX/mfBTmvzmI0kMu2bxlQ4ME2CIRbA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.823.0.tgz", + "integrity": "sha512-vz6onCb/+g4y+owxGGPMEMdN789dTfBOgz/c9pFv0f01840w9Rrt46l+gjQlnXnx+0KG6wNeBIVhFdbCfV3HyQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.823.0", + "@aws-sdk/nested-clients": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.821.0.tgz", + "integrity": "sha512-Znroqdai1a90TlxGaJ+FK1lwC0fHpo97Xjsp5UKGR5JODYm7f9+/fF17ebO1KdoBr/Rm0UIFiF5VmI8ts9F1eA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.804.0.tgz", + "integrity": "sha512-wmBJqn1DRXnZu3b4EkE6CWnoWMo1ZMvlfkqU5zPz67xx1GMaXlDCchFvKAXMjk4jn/L1O3tKnoFDNsoLV1kgNQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.821.0.tgz", + "integrity": "sha512-Uknt/zUZnLE76zaAAPEayOeF5/4IZ2puTFXvcSCWHsi9m3tqbb9UozlnlVqvCZLCRWfQryZQoG2W4XSS3qgk5A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "@smithy/util-endpoints": "^3.0.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.804.0.tgz", + "integrity": "sha512-zVoRfpmBVPodYlnMjgVjfGoEZagyRF5IPn3Uo6ZvOZp24chnW/FRstH7ESDHDDRga4z3V+ElUQHKpFDXWyBW5A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.821.0.tgz", + "integrity": "sha512-irWZHyM0Jr1xhC+38OuZ7JB6OXMLPZlj48thElpsO1ZSLRkLZx5+I7VV6k3sp2yZ7BYbKz/G2ojSv4wdm7XTLw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.823.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.823.0.tgz", + "integrity": "sha512-WvNeRz7HV3JLBVGTXW4Qr5QvvWY0vtggH5jW/NqHFH+ZEliVQaUIJ/HNLMpMoCSiu/DlpQAyAjRZXAptJ0oqbw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.823.0", + "@aws-sdk/types": "3.821.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.821.0.tgz", + "integrity": "sha512-DIIotRnefVL6DiaHtO6/21DhJ4JZnnIwdNbpwiAhdt/AVbttcE4yw925gsjur0OGv5BTYXQXU3YnANBYnZjuQA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", + "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", + "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.4", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.4", + "@babel/types": "^7.27.3", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.5", + "@babel/types": "^7.27.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", + "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", + "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "25.0.8", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.8.tgz", + "integrity": "sha512-ZEZWTK5n6Qde0to4vS9Mr5x/0UZoqCxPVR9KRUjU4kA2sO7GEUn1fop0DAwpO6z0Nw/kJON9bDmSxdWxO/TT1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "glob": "^8.0.3", + "is-reference": "1.2.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz", + "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.7.tgz", + "integrity": "sha512-PqxSfuorkHz/SPpyngLyg5GCEkOcee9M1bkxiVDr41Pd61mqP1PLOoDPbpl44SB2mQGKwV/In74gqQmGITOhEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-typescript": { + "version": "11.1.6", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.6.tgz", + "integrity": "sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.14.0||^3.0.0||^4.0.0", + "tslib": "*", + "typescript": ">=3.7.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + }, + "tslib": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.2.0.tgz", + "integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.0.tgz", + "integrity": "sha512-xEiEE5oDW6tK4jXCAyliuntGR+amEMO7HLtdSshVuhFnKTYoeYMyXQK7pLouAJJj5KHdwdn87bfHAR2nSdNAUA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.0.tgz", + "integrity": "sha512-uNSk/TgvMbskcHxXYHzqwiyBlJ/lGcv8DaUfcnNwict8ba9GTTNxfn3/FAoFZYgkaXXAdrAA+SLyKplyi349Jw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.0.tgz", + "integrity": "sha512-VGF3wy0Eq1gcEIkSCr8Ke03CWT+Pm2yveKLaDvq51pPpZza3JX/ClxXOCmTYYq3us5MvEuNRTaeyFThCKRQhOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.0.tgz", + "integrity": "sha512-fBkyrDhwquRvrTxSGH/qqt3/T0w5Rg0L7ZIDypvBPc1/gzjJle6acCpZ36blwuwcKD/u6oCE/sRWlUAcxLWQbQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.0.tgz", + "integrity": "sha512-u5AZzdQJYJXByB8giQ+r4VyfZP+walV+xHWdaFx/1VxsOn6eWJhK2Vl2eElvDJFKQBo/hcYIBg/jaKS8ZmKeNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.0.tgz", + "integrity": "sha512-qC0kS48c/s3EtdArkimctY7h3nHicQeEUdjJzYVJYR3ct3kWSafmn6jkNCA8InbUdge6PVx6keqjk5lVGJf99g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.0.tgz", + "integrity": "sha512-x+e/Z9H0RAWckn4V2OZZl6EmV0L2diuX3QB0uM1r6BvhUIv6xBPL5mrAX2E3e8N8rEHVPwFfz/ETUbV4oW9+lQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.0.tgz", + "integrity": "sha512-1exwiBFf4PU/8HvI8s80icyCcnAIB86MCBdst51fwFmH5dyeoWVPVgmQPcKrMtBQ0W5pAs7jBCWuRXgEpRzSCg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.0.tgz", + "integrity": "sha512-ZTR2mxBHb4tK4wGf9b8SYg0Y6KQPjGpR4UWwTFdnmjB4qRtoATZ5dWn3KsDwGa5Z2ZBOE7K52L36J9LueKBdOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.0.tgz", + "integrity": "sha512-GFWfAhVhWGd4r6UxmnKRTBwP1qmModHtd5gkraeW2G490BpFOZkFtem8yuX2NyafIP/mGpRJgTJ2PwohQkUY/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.0.tgz", + "integrity": "sha512-xw+FTGcov/ejdusVOqKgMGW3c4+AgqrfvzWEVXcNP6zq2ue+lsYUgJ+5Rtn/OTJf7e2CbgTFvzLW2j0YAtj0Gg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.0.tgz", + "integrity": "sha512-bKGibTr9IdF0zr21kMvkZT4K6NV+jjRnBoVMt2uNMG0BYWm3qOVmYnXKzx7UhwrviKnmK46IKMByMgvpdQlyJQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.0.tgz", + "integrity": "sha512-vV3cL48U5kDaKZtXrti12YRa7TyxgKAIDoYdqSIOMOFBXqFj2XbChHAtXquEn2+n78ciFgr4KIqEbydEGPxXgA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.0.tgz", + "integrity": "sha512-TDKO8KlHJuvTEdfw5YYFBjhFts2TR0VpZsnLLSYmB7AaohJhM8ctDSdDnUGq77hUh4m/djRafw+9zQpkOanE2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.0.tgz", + "integrity": "sha512-8541GEyktXaw4lvnGp9m84KENcxInhAt6vPWJ9RodsB/iGjHoMB2Pp5MVBCiKIRxrxzJhGCxmNzdu+oDQ7kwRA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.0.tgz", + "integrity": "sha512-iUVJc3c0o8l9Sa/qlDL2Z9UP92UZZW1+EmQ4xfjTc1akr0iUFZNfxrXJ/R1T90h/ILm9iXEY6+iPrmYB3pXKjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.0.tgz", + "integrity": "sha512-PQUobbhLTQT5yz/SPg116VJBgz+XOtXt8D1ck+sfJJhuEsMj2jSej5yTdp8CvWBSceu+WW+ibVL6dm0ptG5fcA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.0.tgz", + "integrity": "sha512-M0CpcHf8TWn+4oTxJfh7LQuTuaYeXGbk0eageVjQCKzYLsajWS/lFC94qlRqOlyC2KvRT90ZrfXULYmukeIy7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.0.tgz", + "integrity": "sha512-3XJ0NQtMAXTWFW8FqZKcw3gOQwBtVWP/u8TpHP3CRPXD7Pd6s8lLdH3sHWh8vqKCyyiI8xW5ltJScQmBU9j7WA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.0.tgz", + "integrity": "sha512-Q2Mgwt+D8hd5FIPUuPDsvPR7Bguza6yTkJxspDGkZj7tBRn2y4KSWYuIXpftFSjBra76TbKerCV7rgFPQrn+wQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.0.0.tgz", + "integrity": "sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.0.0.tgz", + "integrity": "sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.4.tgz", + "integrity": "sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.5.3.tgz", + "integrity": "sha512-xa5byV9fEguZNofCclv6v9ra0FYh5FATQW/da7FQUVTic94DfrN/NvmKZjrMyzbpqfot9ZjBaO8U1UeTbmSLuA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.0.8", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz", + "integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.0.4.tgz", + "integrity": "sha512-7XoWfZqWb/QoR/rAU4VSi0mWnO2vu9/ltS6JZ5ZSZv0eovLVfDfu0/AX4ub33RsJTOth3TiFWSHS5YdztvFnig==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.3.1", + "@smithy/util-hex-encoding": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.0.4.tgz", + "integrity": "sha512-3fb/9SYaYqbpy/z/H3yIi0bYKyAa89y6xPmIqwr2vQiUT2St+avRt8UKwsWt9fEdEasc5d/V+QjrviRaX1JRFA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.1.2.tgz", + "integrity": "sha512-JGtambizrWP50xHgbzZI04IWU7LdI0nh/wGbqH3sJesYToMi2j/DcoElqyOcqEIG/D4tNyxgRuaqBXWE3zOFhQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.0.4.tgz", + "integrity": "sha512-RD6UwNZ5zISpOWPuhVgRz60GkSIp0dy1fuZmj4RYmqLVRtejFqQ16WmfYDdoSoAjlp1LX+FnZo+/hkdmyyGZ1w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.0.4.tgz", + "integrity": "sha512-UeJpOmLGhq1SLox79QWw/0n2PFX+oPRE1ZyRMxPIaFEfCqWaqpB7BU9C8kpPOGEhLF7AwEqfFbtwNxGy4ReENA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.4.tgz", + "integrity": "sha512-AMtBR5pHppYMVD7z7G+OlHHAcgAN7v0kVKEpHuTO4Gb199Gowh0taYi9oDStFeUhetkeP55JLSVlTW1n9rFtUw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-blob-browser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.0.4.tgz", + "integrity": "sha512-WszRiACJiQV3QG6XMV44i5YWlkrlsM5Yxgz4jvsksuu7LDXA6wAtypfPajtNTadzpJy3KyJPoWehYpmZGKUFIQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/chunked-blob-reader": "^5.0.0", + "@smithy/chunked-blob-reader-native": "^4.0.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.4.tgz", + "integrity": "sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-stream-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.0.4.tgz", + "integrity": "sha512-wHo0d8GXyVmpmMh/qOR0R7Y46/G1y6OR8U+bSTB4ppEzRxd1xVAQ9xOE9hOc0bSjhz0ujCPAbfNLkLrpa6cevg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.4.tgz", + "integrity": "sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/md5-js": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.0.4.tgz", + "integrity": "sha512-uGLBVqcOwrLvGh/v/jw423yWHq/ofUGK1W31M2TNspLQbUV1Va0F5kTxtirkoHawODAZcjXTSGi7JwbnPcDPJg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.4.tgz", + "integrity": "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.11.tgz", + "integrity": "sha512-zDogwtRLzKl58lVS8wPcARevFZNBOOqnmzWWxVe9XiaXU2CADFjvJ9XfNibgkOWs08sxLuSr81NrpY4mgp9OwQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.5.3", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.12.tgz", + "integrity": "sha512-wvIH70c4e91NtRxdaLZF+mbLZ/HcC6yg7ySKUiufL6ESp6zJUSnJucZ309AvG9nqCFHSRB5I6T3Ez1Q9wCh0Ww==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/service-error-classification": "^4.0.5", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", + "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz", + "integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", + "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", + "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", + "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", + "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", + "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.5.tgz", + "integrity": "sha512-LvcfhrnCBvCmTee81pRlh1F39yTS/+kYleVeLCwNtkY8wtGg8V/ca9rbZZvYIl8OjlMtL6KIjaiL/lgVqHD2nA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.2.tgz", + "integrity": "sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.3.tgz", + "integrity": "sha512-xxzNYgA0HD6ETCe5QJubsxP0hQH3QK3kbpJz3QrosBCuIWyEXLR/CO5hFb2OeawEKUxMNhz3a1nuJNN2np2RMA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.5.3", + "@smithy/middleware-endpoint": "^4.1.11", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", + "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.19", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.19.tgz", + "integrity": "sha512-mvLMh87xSmQrV5XqnUYEPoiFFeEGYeAKIDDKdhE2ahqitm8OHM3aSvhqL6rrK6wm1brIk90JhxDf5lf2hbrLbQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.19", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.19.tgz", + "integrity": "sha512-8tYnx+LUfj6m+zkUUIrIQJxPM1xVxfRBvoGHua7R/i6qAxOMjqR6CpEpDwKoIs1o0+hOjGvkKE23CafKL0vJ9w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.1.4", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", + "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", + "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.5.tgz", + "integrity": "sha512-V7MSjVDTlEt/plmOFBn1762Dyu5uqMrV2Pl2X0dYk4XvWfdWJNe9Bs5Bzb56wkCuiWjSfClVMGcsuKrGj7S/yg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.0.5", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.2.tgz", + "integrity": "sha512-aI+GLi7MJoVxg24/3J1ipwLoYzgkB4kUfogZfnslcYlynj3xsQ0e7vk4TnTro9hhsS5PvX1mwmkRqqHQjwcU7w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.0.5.tgz", + "integrity": "sha512-4QvC49HTteI1gfemu0I1syWovJgPvGn7CVUoN9ZFkdvr/cCFkrEL7qNCdx/2eICqDWEGnnr68oMdSIPCLAriSQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@tensorflow-models/universal-sentence-encoder": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@tensorflow-models/universal-sentence-encoder/-/universal-sentence-encoder-1.3.3.tgz", + "integrity": "sha512-mipL7ad0CW6uQ68FUkNgkNj/zgA4qgBnNcnMMkNTdL9MUMnzCxu3AE8pWnx2ReKHwdqEG4e8IpaYKfH4B8bojg==", + "license": "Apache-2.0", + "peerDependencies": { + "@tensorflow/tfjs-converter": "^3.6.0", + "@tensorflow/tfjs-core": "^3.6.0" + } + }, + "node_modules/@tensorflow/tfjs": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-4.22.0.tgz", + "integrity": "sha512-0TrIrXs6/b7FLhLVNmfh8Sah6JgjBPH4mZ8JGb7NU6WW+cx00qK5BcAZxw7NCzxj6N8MRAIfHq+oNbPUNG5VAg==", + "license": "Apache-2.0", + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "4.22.0", + "@tensorflow/tfjs-backend-webgl": "4.22.0", + "@tensorflow/tfjs-converter": "4.22.0", + "@tensorflow/tfjs-core": "4.22.0", + "@tensorflow/tfjs-data": "4.22.0", + "@tensorflow/tfjs-layers": "4.22.0", + "argparse": "^1.0.10", + "chalk": "^4.1.0", + "core-js": "3.29.1", + "regenerator-runtime": "^0.13.5", + "yargs": "^16.0.3" + }, + "bin": { + "tfjs-custom-module": "dist/tools/custom_module/cli.js" + } + }, + "node_modules/@tensorflow/tfjs-backend-cpu": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.22.0.tgz", + "integrity": "sha512-1u0FmuLGuRAi8D2c3cocHTASGXOmHc/4OvoVDENJayjYkS119fcTcQf4iHrtLthWyDIPy3JiPhRrZQC9EwnhLw==", + "license": "Apache-2.0", + "dependencies": { + "@types/seedrandom": "^2.4.28", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, + "node_modules/@tensorflow/tfjs-backend-webgl": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-4.22.0.tgz", + "integrity": "sha512-H535XtZWnWgNwSzv538czjVlbJebDl5QTMOth4RXr2p/kJ1qSIXE0vZvEtO+5EC9b00SvhplECny2yDewQb/Yg==", + "license": "Apache-2.0", + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "4.22.0", + "@types/offscreencanvas": "~2019.3.0", + "@types/seedrandom": "^2.4.28", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, + "node_modules/@tensorflow/tfjs-converter": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.22.0.tgz", + "integrity": "sha512-PT43MGlnzIo+YfbsjM79Lxk9lOq6uUwZuCc8rrp0hfpLjF6Jv8jS84u2jFb+WpUeuF4K33ZDNx8CjiYrGQ2trQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, + "node_modules/@tensorflow/tfjs-core": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.22.0.tgz", + "integrity": "sha512-LEkOyzbknKFoWUwfkr59vSB68DMJ4cjwwHgicXN0DUi3a0Vh1Er3JQqCI1Hl86GGZQvY8ezVrtDIvqR1ZFW55A==", + "license": "Apache-2.0", + "dependencies": { + "@types/long": "^4.0.1", + "@types/offscreencanvas": "~2019.7.0", + "@types/seedrandom": "^2.4.28", + "@webgpu/types": "0.1.38", + "long": "4.0.0", + "node-fetch": "~2.6.1", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + } + }, + "node_modules/@tensorflow/tfjs-core/node_modules/@types/offscreencanvas": { + "version": "2019.7.3", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", + "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==", + "license": "MIT" + }, + "node_modules/@tensorflow/tfjs-data": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-4.22.0.tgz", + "integrity": "sha512-dYmF3LihQIGvtgJrt382hSRH4S0QuAp2w1hXJI2+kOaEqo5HnUPG0k5KA6va+S1yUhx7UBToUKCBHeLHFQRV4w==", + "license": "Apache-2.0", + "dependencies": { + "@types/node-fetch": "^2.1.2", + "node-fetch": "~2.6.1", + "string_decoder": "^1.3.0" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0", + "seedrandom": "^3.0.5" + } + }, + "node_modules/@tensorflow/tfjs-layers": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-4.22.0.tgz", + "integrity": "sha512-lybPj4ZNj9iIAPUj7a8ZW1hg8KQGfqWLlCZDi9eM/oNKCCAgchiyzx8OrYoWmRrB+AM6VNEeIT+2gZKg5ReihA==", + "license": "Apache-2.0 AND MIT", + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.17.57", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.57.tgz", + "integrity": "sha512-f3T4y6VU4fVQDKVqJV4Uppy8c1p/sVvS3peyqxyWnzkqXFJLRU7Y1Bl7rMS1Qe9z0v4M6McY0Fp9yBsgHJUsWQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/offscreencanvas": { + "version": "2019.3.0", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz", + "integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==", + "license": "MIT" + }, + "node_modules/@types/omelette": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@types/omelette/-/omelette-0.4.5.tgz", + "integrity": "sha512-zUCJpVRwfMcZfkxSCGp73mgd3/xesvPz5tQJIORlfP/zkYEyp9KUfF7IP3RRjyZR3DwxkPs96/IFf70GmYZYHQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/seedrandom": { + "version": "2.4.34", + "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.34.tgz", + "integrity": "sha512-ytDiArvrn/3Xk6/vtylys5tlY6eo7Ane0hvcx++TKo6RxQXuVfW0AF/oeWqAj9dN29SyhtawuXstgmPlwNcv/A==", + "license": "MIT" + }, + "node_modules/@types/semver": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", + "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@webgpu/types": { + "version": "0.1.38", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.38.tgz", + "integrity": "sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA==", + "license": "BSD-3-Clause" + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001721", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz", + "integrity": "sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", + "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-js": { + "version": "3.29.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.29.1.tgz", + "integrity": "sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", + "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.165", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.165.tgz", + "integrity": "sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw==", + "dev": true, + "license": "ISC" + }, + "node_modules/email-addresses": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-5.0.0.tgz", + "integrity": "sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-cache-dir/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-cache-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/form-data": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gh-pages": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-6.3.0.tgz", + "integrity": "sha512-Ot5lU6jK0Eb+sszG8pciXdjMXdBJ5wODvgjR+imihTqsUWF2K6dJ9HST55lgqcs8wWcw6o6wAsUzfcYRhJPXbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^3.2.4", + "commander": "^13.0.0", + "email-addresses": "^5.0.0", + "filenamify": "^4.3.0", + "find-cache-dir": "^3.3.1", + "fs-extra": "^11.1.1", + "globby": "^11.1.0" + }, + "bin": { + "gh-pages": "bin/gh-pages.js", + "gh-pages-clean": "bin/gh-pages-clean.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gh-pages/node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jake/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-cli/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js-yaml/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/omelette": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/omelette/-/omelette-0.4.17.tgz", + "integrity": "sha512-UlU69G6Bhu0XFjw3tjFZ0qyiMUjAOR+rdzblA1nLQ8xiqFtxOVlkhM39BlgTpLFx9fxkm6rnxNNRsS5GxE/yww==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.0.tgz", + "integrity": "sha512-qHcdEzLCiktQIfwBq420pn2dP+30uzqYxv9ETm91wdt2R9AFcWfjNAmje4NWlnCIQ5RMTzVf0ZyisOKqHR6RwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.44.0", + "@rollup/rollup-android-arm64": "4.44.0", + "@rollup/rollup-darwin-arm64": "4.44.0", + "@rollup/rollup-darwin-x64": "4.44.0", + "@rollup/rollup-freebsd-arm64": "4.44.0", + "@rollup/rollup-freebsd-x64": "4.44.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.44.0", + "@rollup/rollup-linux-arm-musleabihf": "4.44.0", + "@rollup/rollup-linux-arm64-gnu": "4.44.0", + "@rollup/rollup-linux-arm64-musl": "4.44.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.44.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.44.0", + "@rollup/rollup-linux-riscv64-gnu": "4.44.0", + "@rollup/rollup-linux-riscv64-musl": "4.44.0", + "@rollup/rollup-linux-s390x-gnu": "4.44.0", + "@rollup/rollup-linux-x64-gnu": "4.44.0", + "@rollup/rollup-linux-x64-musl": "4.44.0", + "@rollup/rollup-win32-arm64-msvc": "4.44.0", + "@rollup/rollup-win32-ia32-msvc": "4.44.0", + "@rollup/rollup-win32-x64-msvc": "4.44.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-terser": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", + "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "jest-worker": "^26.2.1", + "serialize-javascript": "^4.0.0", + "terser": "^5.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0" + } + }, + "node_modules/rollup-plugin-terser/node_modules/jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-outer/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/terser": { + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.0.tgz", + "integrity": "sha512-CqNNxKSGKSZCunSvwKLTs8u8sGGlp27sxNZ4quGh0QeNuyHM0JSEM/clM9Mf4zUp6J+tO2gUXhgXT2YMMkwfKQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.14.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/trim-repeated/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-jest": { + "version": "29.3.4", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.3.4.tgz", + "integrity": "sha512-Iqbrm8IXOmV+ggWHOTEbjwyCf2xZlUMv5npExksXohL+tk8va4Fjhb+X2+Rt9NBmgO7bJ8WpnMLOwih/DnMlFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.2", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..88a68f6d --- /dev/null +++ b/package.json @@ -0,0 +1,156 @@ +{ + "name": "@soulcraft/brainy", + "version": "0.9.4", + "description": "A vector graph database using HNSW indexing with Origin Private File System storage", + "main": "dist/unified.js", + "module": "dist/unified.js", + "types": "dist/unified.d.ts", + "type": "module", + "sideEffects": false, + "exports": { + ".": { + "import": "./dist/unified.js", + "types": "./dist/unified.d.ts" + }, + "./min": { + "import": "./dist/unified.min.js" + }, + "./types/graphTypes": { + "import": "./dist/types/graphTypes.js", + "types": "./dist/types/graphTypes.d.ts" + }, + "./types/augmentations": { + "import": "./dist/types/augmentations.js", + "types": "./dist/types/augmentations.d.ts" + } + }, + "engines": { + "node": ">=23.11.0" + }, + "scripts": { + "prebuild": "node scripts/generate-version.js", + "build": "BUILD_TYPE=unified rollup -c rollup.config.js", + "build:browser": "BUILD_TYPE=browser rollup -c rollup.config.js", + "start": "node dist/unified.js", + "demo": "npm run build && npm run build:browser && npx http-server -o /index.html", + "cli": "node ./cli-wrapper.js", + "version:patch": "npm version patch", + "version:minor": "npm version minor", + "version:major": "npm version major", + "deploy": "npm run build && npm publish", + "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", + "deploy:cloud": "echo 'Please use one of the following commands to deploy to a specific cloud provider:' && echo ' npm run deploy:cloud:aws' && echo ' npm run deploy:cloud:gcp' && echo ' npm run deploy:cloud:cloudflare'", + "postinstall": "echo 'Note: If you encounter dependency conflicts with TensorFlow.js packages, please use: npm install --legacy-peer-deps'" + }, + "bin": { + "brainy": "cli-wrapper.js" + }, + "keywords": [ + "vector-database", + "hnsw", + "opfs", + "origin-private-file-system", + "embeddings", + "graph-database", + "streaming-data" + ], + "author": "David Snelling (david@soulcraft.com)", + "license": "MIT", + "private": false, + "publishConfig": { + "access": "public" + }, + "homepage": "https://github.com/soulcraft-research/brainy", + "bugs": { + "url": "https://github.com/soulcraft-research/brainy/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/soulcraft-research/brainy.git" + }, + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "dist/types/", + "LICENSE", + "README.md", + "cli-wrapper.js", + "brainy.png" + ], + "devDependencies": { + "@rollup/plugin-commonjs": "^25.0.7", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-replace": "^5.0.5", + "@rollup/plugin-typescript": "^11.1.6", + "@types/jest": "^29.5.3", + "@types/node": "^20.4.5", + "@types/omelette": "^0.4.5", + "@types/uuid": "^10.0.0", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "eslint": "^8.45.0", + "jest": "^29.6.2", + "rollup": "^4.12.0", + "rollup-plugin-terser": "^7.0.2", + "ts-jest": "^29.1.1", + "tslib": "^2.8.1", + "typescript": "^5.1.6" + }, + "dependencies": { + "@aws-sdk/client-s3": "^3.427.0", + "@tensorflow-models/universal-sentence-encoder": "^1.3.3", + "@tensorflow/tfjs": "^4.22.0", + "@tensorflow/tfjs-backend-cpu": "^4.22.0", + "@tensorflow/tfjs-converter": "^4.22.0", + "@tensorflow/tfjs-core": "^4.22.0", + "@tensorflow/tfjs-layers": "^4.22.0", + "buffer": "^6.0.3", + "commander": "^14.0.0", + "omelette": "^0.4.17", + "uuid": "^9.0.0" + }, + "prettier": { + "arrowParens": "always", + "bracketSameLine": true, + "bracketSpacing": true, + "htmlWhitespaceSensitivity": "css", + "printWidth": 80, + "proseWrap": "preserve", + "semi": false, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "none", + "useTabs": false + }, + "eslintConfig": { + "root": true, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ], + "rules": { + "@typescript-eslint/no-explicit-any": "off", + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "args": "after-used", + "argsIgnorePattern": "^_" + } + ], + "semi": "off", + "@typescript-eslint/semi": [ + "error", + "never" + ], + "no-extra-semi": "off" + } + } +} diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 00000000..3b34c456 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,179 @@ +import typescript from '@rollup/plugin-typescript' +import resolve from '@rollup/plugin-node-resolve' +import commonjs from '@rollup/plugin-commonjs' +import json from '@rollup/plugin-json' +import { terser } from 'rollup-plugin-terser' +import replace from '@rollup/plugin-replace' + +// Custom plugin to provide empty shims for Node.js built-in modules in browser environments +const nodeModuleShims = () => { + return { + name: 'node-module-shims', + resolveId(source) { + // List of Node.js built-in modules to shim + const nodeBuiltins = ['fs', 'path', 'util', 'child_process'] + + if (nodeBuiltins.includes(source)) { + // Return a virtual module ID for the shim + return `\0${source}-shim` + } + return null + }, + load(id) { + // If this is one of our shims, return an empty module + if (id.startsWith('\0') && id.endsWith('-shim')) { + console.log( + `Providing empty shim for Node.js module: ${id.slice(1, -5)}` + ) + return 'export default {}; export const promises = {};' + } + return null + } + } +} + +// Custom plugin to provide Buffer polyfill for browser environments +const bufferPolyfill = () => { + return { + name: 'buffer-polyfill', + // This will run before the bundle is generated + buildStart() { + console.log('Setting up Buffer polyfill for the bundle') + }, + // This will run for each module + transform(code, id) { + // Only transform the entry point + if (id.includes('src/unified.ts')) { + // Add import for buffer at the top of the file + const bufferImport = ` +// Import Buffer polyfill +import { Buffer as BufferPolyfill } from 'buffer'; + +// Make Buffer available globally +if (typeof window !== 'undefined' && typeof globalThis.Buffer === 'undefined') { + globalThis.Buffer = BufferPolyfill; +} +`; + return { + code: bufferImport + code, + map: { mappings: '' } // Provide an empty sourcemap to avoid warnings + }; + } + return null; // Return null to let Rollup handle other files normally + } + } +} + +// Custom plugin to fix 'this' references in specific files +const fixThisReferences = () => { + return { + name: 'fix-this-references', + transform(code, id) { + // Only transform the specific files that have issues with 'this' + if ( + id.includes( + '@tensorflow/tfjs-layers/dist/layers/convolutional_recurrent.js' + ) + ) { + // Replace 'this' with 'globalThis' in the problematic code + return { + code: code.replace(/\bthis\b/g, 'globalThis'), + map: { mappings: '' } // Provide an empty sourcemap to avoid warnings + } + } + return null // Return null to let Rollup handle other files normally + } + } +} + +// Get build type from environment variable or default to 'unified' +const buildType = process.env.BUILD_TYPE || 'unified' + +// Configuration based on build type +const config = { + unified: { + input: 'src/unified.ts', + outputPrefix: 'unified', + tsconfig: './tsconfig.unified.json', + declaration: true, + declarationMap: true, + intro: ` +// Buffer polyfill is now included via the buffer-polyfill plugin + ` + }, + browser: { + input: 'src/unified.ts', + outputPrefix: 'brainy', + tsconfig: './tsconfig.browser.json', + declaration: false, + declarationMap: false, + intro: ` +// Set global for compatibility +var global = typeof window !== "undefined" ? window : this; +// Buffer polyfill is now included via the buffer-polyfill plugin +` + } +} + +// Get configuration for the current build type +const buildConfig = config[buildType] + +// Create the rollup configuration +export default { + input: buildConfig.input, + output: [ + { + dir: 'dist', + entryFileNames: `${buildConfig.outputPrefix}.js`, + format: 'es', + sourcemap: true, + inlineDynamicImports: true, + intro: buildConfig.intro + }, + { + dir: 'dist', + entryFileNames: `${buildConfig.outputPrefix}.min.js`, + format: 'es', + sourcemap: true, + inlineDynamicImports: true, + intro: buildConfig.intro, + plugins: [terser()] + } + ], + plugins: [ + // Add environment replacement for unified build + ...(buildType === 'unified' + ? [ + replace({ + preventAssignment: true, + 'process.env.NODE_ENV': JSON.stringify('production') + }) + ] + : []), + // Add our custom plugins + fixThisReferences(), + nodeModuleShims(), + bufferPolyfill(), // Add Buffer polyfill + resolve({ + browser: true, + preferBuiltins: false + }), + commonjs({ + transformMixedEsModules: true + }), + json(), + typescript({ + tsconfig: buildConfig.tsconfig, + declaration: buildConfig.declaration, + declarationMap: buildConfig.declarationMap + }) + ], + external: [ + // Add any dependencies you want to exclude from the bundle + '@aws-sdk/client-s3', + '@smithy/util-stream', + '@smithy/node-http-handler', + '@aws-crypto/crc32c', + 'node:stream/web' + ] +} diff --git a/scripts/encode-image.js b/scripts/encode-image.js new file mode 100644 index 00000000..bb9e1dfb --- /dev/null +++ b/scripts/encode-image.js @@ -0,0 +1,47 @@ +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); + +// Path to the image file +const imagePath = path.join(__dirname, '..', 'brainy.png'); + +// Read the image file +const imageBuffer = fs.readFileSync(imagePath); + +// Convert the image to base64 +const base64Image = imageBuffer.toString('base64'); + +// Get the MIME type based on file extension +const getMimeType = (filePath) => { + const ext = path.extname(filePath).toLowerCase(); + switch (ext) { + case '.png': + return 'image/png'; + case '.jpg': + case '.jpeg': + return 'image/jpeg'; + case '.gif': + return 'image/gif'; + case '.svg': + return 'image/svg+xml'; + default: + return 'application/octet-stream'; + } +}; + +const mimeType = getMimeType(imagePath); + +// Create the data URL +const dataUrl = `data:${mimeType};base64,${base64Image}`; + +// Output the complete HTML img tag +const imgTag = `Brainy Logo`; + +// Write to a file instead of console.log to avoid truncation +fs.writeFileSync(path.join(__dirname, '..', 'encoded-image.html'), imgTag); + +console.log('Base64 encoded image has been saved to encoded-image.html'); diff --git a/scripts/generate-version.js b/scripts/generate-version.js new file mode 100755 index 00000000..2a462529 --- /dev/null +++ b/scripts/generate-version.js @@ -0,0 +1,74 @@ +#!/usr/bin/env node + +/** + * Generate Version Script + * + * This script generates a version.js file that exports the version from package.json. + * This allows the CLI to access the version without having to read package.json directly, + * which can be problematic when the package is installed globally. + * + * It also updates the version in the README.md file to ensure it stays in sync with package.json. + */ + +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) + +// Path to the root directory +const rootDir = path.join(__dirname, '..') + +// Path to package.json +const packageJsonPath = path.join(rootDir, 'package.json') + +// Path to the output directory +const outputDir = path.join(rootDir, 'src', 'utils') + +// Path to the output file +const outputFile = path.join(outputDir, 'version.ts') + +// Path to README.md +const readmePath = path.join(rootDir, 'README.md') + +// Read package.json +const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) +const version = packageJson.version + +// Create the output directory if it doesn't exist +if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }) +} + +// Generate the version.ts file +const content = `/** + * This file is auto-generated during the build process. + * Do not modify this file directly. + */ + +export const VERSION = '${version}'; +` + +// Write the file +fs.writeFileSync(outputFile, content) + +console.log(`Generated version.ts with version ${version}`) + +// Update README.md with the current version +try { + let readmeContent = fs.readFileSync(readmePath, 'utf8') + + // Replace the version in the badge URL + const updatedReadme = readmeContent.replace( + /\[\!\[Version\]\(https:\/\/img\.shields\.io\/badge\/version-[0-9]+\.[0-9]+\.[0-9]+-blue\.svg\)\]/g, + `[![Version](https://img.shields.io/badge/version-${version}-blue.svg)]` + ) + + // Write the updated README back to disk + fs.writeFileSync(readmePath, updatedReadme) + console.log(`Updated README.md with version ${version}`) +} catch (error) { + console.error('Error updating README.md:', error) +} diff --git a/scripts/update-readme.js b/scripts/update-readme.js new file mode 100644 index 00000000..ac369338 --- /dev/null +++ b/scripts/update-readme.js @@ -0,0 +1,26 @@ +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); + +// Paths to the files +const readmePath = path.join(__dirname, '..', 'README.md'); +const encodedImagePath = path.join(__dirname, '..', 'encoded-image.html'); + +// Read the files +const readmeContent = fs.readFileSync(readmePath, 'utf8'); +const encodedImageTag = fs.readFileSync(encodedImagePath, 'utf8'); + +// Replace the image tag in the README +const updatedReadme = readmeContent.replace( + /Brainy Logo/, + encodedImageTag +); + +// Write the updated README +fs.writeFileSync(readmePath, updatedReadme); + +console.log('README.md has been updated with the base64-encoded image.'); diff --git a/src/augmentationFactory.ts b/src/augmentationFactory.ts new file mode 100644 index 00000000..1e77f070 --- /dev/null +++ b/src/augmentationFactory.ts @@ -0,0 +1,628 @@ +/** + * Augmentation Factory + * + * This module provides a simplified factory for creating augmentations with minimal boilerplate. + * It reduces the complexity of creating and using augmentations by providing a fluent API + * and handling common patterns automatically. + */ + +import { + IAugmentation, + AugmentationType, + AugmentationResponse, + ISenseAugmentation, + IConduitAugmentation, + ICognitionAugmentation, + IMemoryAugmentation, + IPerceptionAugmentation, + IDialogAugmentation, + IActivationAugmentation, + IWebSocketSupport, + WebSocketConnection +} from './types/augmentations.js' +import { registerAugmentation } from './augmentationRegistry.js' + +/** + * Options for creating an augmentation + */ +export interface AugmentationOptions { + name: string + description?: string + enabled?: boolean + autoRegister?: boolean + autoInitialize?: boolean +} + +/** + * Base class for all augmentations created with the factory + * Handles common functionality like initialization, shutdown, and status + */ +class BaseAugmentation implements IAugmentation { + readonly name: string + readonly description: string + enabled: boolean = true + protected isInitialized: boolean = false + + constructor(options: AugmentationOptions) { + this.name = options.name + this.description = options.description || `${options.name} augmentation` + this.enabled = options.enabled !== false + } + + async initialize(): Promise { + if (this.isInitialized) return + this.isInitialized = true + } + + async shutDown(): Promise { + this.isInitialized = false + } + + async getStatus(): Promise<'active' | 'inactive' | 'error'> { + return this.isInitialized ? 'active' : 'inactive' + } + + protected async ensureInitialized(): Promise { + if (!this.isInitialized) { + await this.initialize() + } + } +} + +/** + * Factory for creating sense augmentations + */ +export function createSenseAugmentation( + options: AugmentationOptions & { + processRawData?: ( + rawData: Buffer | string, + dataType: string + ) => + | Promise> + | AugmentationResponse<{ + nouns: string[] + verbs: string[] + }> + listenToFeed?: ( + feedUrl: string, + callback: (data: { nouns: string[]; verbs: string[] }) => void + ) => Promise + } +): ISenseAugmentation { + const augmentation = new BaseAugmentation(options) as unknown as ISenseAugmentation + + // Implement the sense augmentation methods + augmentation.processRawData = async ( + rawData: Buffer | string, + dataType: string + ) => { + await augmentation.ensureInitialized() + + if (options.processRawData) { + const result = options.processRawData(rawData, dataType) + return result instanceof Promise ? await result : result + } + + return { + success: false, + data: { nouns: [], verbs: [] }, + error: 'processRawData not implemented' + } + } + + augmentation.listenToFeed = async ( + feedUrl: string, + callback: (data: { nouns: string[]; verbs: string[] }) => void + ) => { + await augmentation.ensureInitialized() + + if (options.listenToFeed) { + return options.listenToFeed(feedUrl, callback) + } + + throw new Error('listenToFeed not implemented') + } + + // Auto-register if requested + if (options.autoRegister) { + registerAugmentation(augmentation) + + // Auto-initialize if requested + if (options.autoInitialize) { + augmentation.initialize().catch((error) => { + console.error( + `Failed to initialize augmentation ${augmentation.name}:`, + error + ) + }) + } + } + + return augmentation +} + +/** + * Factory for creating conduit augmentations + */ +export function createConduitAugmentation( + options: AugmentationOptions & { + establishConnection?: ( + targetSystemId: string, + config: Record + ) => + | Promise> + | AugmentationResponse + readData?: ( + query: Record, + options?: Record + ) => Promise> | AugmentationResponse + writeData?: ( + data: Record, + options?: Record + ) => Promise> | AugmentationResponse + monitorStream?: ( + streamId: string, + callback: (data: unknown) => void + ) => Promise + } +): IConduitAugmentation { + const augmentation = new BaseAugmentation(options) as unknown as IConduitAugmentation + + // Implement the conduit augmentation methods + augmentation.establishConnection = async ( + targetSystemId: string, + config: Record + ) => { + await augmentation.ensureInitialized() + + if (options.establishConnection) { + const result = options.establishConnection(targetSystemId, config) + return result instanceof Promise ? await result : result + } + + return { + success: false, + data: null as any, + error: 'establishConnection not implemented' + } + } + + augmentation.readData = async ( + query: Record, + opts?: Record + ) => { + await augmentation.ensureInitialized() + + if (options.readData) { + const result = options.readData(query, opts) + return result instanceof Promise ? await result : result + } + + return { + success: false, + data: null, + error: 'readData not implemented' + } + } + + augmentation.writeData = async ( + data: Record, + opts?: Record + ) => { + await augmentation.ensureInitialized() + + if (options.writeData) { + const result = options.writeData(data, opts) + return result instanceof Promise ? await result : result + } + + return { + success: false, + data: null, + error: 'writeData not implemented' + } + } + + augmentation.monitorStream = async ( + streamId: string, + callback: (data: unknown) => void + ) => { + await augmentation.ensureInitialized() + + if (options.monitorStream) { + return options.monitorStream(streamId, callback) + } + + throw new Error('monitorStream not implemented') + } + + // Auto-register if requested + if (options.autoRegister) { + registerAugmentation(augmentation) + + // Auto-initialize if requested + if (options.autoInitialize) { + augmentation.initialize().catch((error) => { + console.error( + `Failed to initialize augmentation ${augmentation.name}:`, + error + ) + }) + } + } + + return augmentation +} + +/** + * Factory for creating memory augmentations + */ +export function createMemoryAugmentation( + options: AugmentationOptions & { + storeData?: ( + key: string, + data: unknown, + options?: Record + ) => Promise> | AugmentationResponse + retrieveData?: ( + key: string, + options?: Record + ) => Promise> | AugmentationResponse + updateData?: ( + key: string, + data: unknown, + options?: Record + ) => Promise> | AugmentationResponse + deleteData?: ( + key: string, + options?: Record + ) => Promise> | AugmentationResponse + listDataKeys?: ( + pattern?: string, + options?: Record + ) => + | Promise> + | AugmentationResponse + search?: ( + query: unknown, + k?: number, + options?: Record + ) => + | Promise< + AugmentationResponse< + Array<{ id: string; score: number; data: unknown }> + > + > + | AugmentationResponse< + Array<{ id: string; score: number; data: unknown }> + > + } +): IMemoryAugmentation { + const augmentation = new BaseAugmentation(options) as unknown as IMemoryAugmentation + + // Implement the memory augmentation methods + augmentation.storeData = async ( + key: string, + data: unknown, + opts?: Record + ) => { + await augmentation.ensureInitialized() + + if (options.storeData) { + const result = options.storeData(key, data, opts) + return result instanceof Promise ? await result : result + } + + return { + success: false, + data: false, + error: 'storeData not implemented' + } + } + + augmentation.retrieveData = async ( + key: string, + opts?: Record + ) => { + await augmentation.ensureInitialized() + + if (options.retrieveData) { + const result = options.retrieveData(key, opts) + return result instanceof Promise ? await result : result + } + + return { + success: false, + data: null, + error: 'retrieveData not implemented' + } + } + + augmentation.updateData = async ( + key: string, + data: unknown, + opts?: Record + ) => { + await augmentation.ensureInitialized() + + if (options.updateData) { + const result = options.updateData(key, data, opts) + return result instanceof Promise ? await result : result + } + + return { + success: false, + data: false, + error: 'updateData not implemented' + } + } + + augmentation.deleteData = async ( + key: string, + opts?: Record + ) => { + await augmentation.ensureInitialized() + + if (options.deleteData) { + const result = options.deleteData(key, opts) + return result instanceof Promise ? await result : result + } + + return { + success: false, + data: false, + error: 'deleteData not implemented' + } + } + + augmentation.listDataKeys = async ( + pattern?: string, + opts?: Record + ) => { + await augmentation.ensureInitialized() + + if (options.listDataKeys) { + const result = options.listDataKeys(pattern, opts) + return result instanceof Promise ? await result : result + } + + return { + success: false, + data: [], + error: 'listDataKeys not implemented' + } + } + + augmentation.search = async ( + query: unknown, + k?: number, + opts?: Record + ) => { + await augmentation.ensureInitialized() + + if (options.search) { + const result = options.search(query, k, opts) + return result instanceof Promise ? await result : result + } + + return { + success: false, + data: [], + error: 'search not implemented' + } + } + + // Auto-register if requested + if (options.autoRegister) { + registerAugmentation(augmentation) + + // Auto-initialize if requested + if (options.autoInitialize) { + augmentation.initialize().catch((error) => { + console.error( + `Failed to initialize augmentation ${augmentation.name}:`, + error + ) + }) + } + } + + return augmentation +} + +/** + * Factory for creating WebSocket-enabled augmentations + * This can be combined with other augmentation factories to create WebSocket-enabled versions + */ +export function addWebSocketSupport( + augmentation: T, + options: { + connectWebSocket?: ( + url: string, + protocols?: string | string[] + ) => Promise + sendWebSocketMessage?: ( + connectionId: string, + data: unknown + ) => Promise + onWebSocketMessage?: ( + connectionId: string, + callback: (data: unknown) => void + ) => Promise + offWebSocketMessage?: ( + connectionId: string, + callback: (data: unknown) => void + ) => Promise + closeWebSocket?: ( + connectionId: string, + code?: number, + reason?: string + ) => Promise + } +): T & IWebSocketSupport { + const wsAugmentation = augmentation as T & IWebSocketSupport + + // Add WebSocket methods + wsAugmentation.connectWebSocket = async ( + url: string, + protocols?: string | string[] + ) => { + await (augmentation as any).ensureInitialized?.() + + if (options.connectWebSocket) { + return options.connectWebSocket(url, protocols) + } + + throw new Error('connectWebSocket not implemented') + } + + wsAugmentation.sendWebSocketMessage = async ( + connectionId: string, + data: unknown + ) => { + await (augmentation as any).ensureInitialized?.() + + if (options.sendWebSocketMessage) { + return options.sendWebSocketMessage(connectionId, data) + } + + throw new Error('sendWebSocketMessage not implemented') + } + + wsAugmentation.onWebSocketMessage = async ( + connectionId: string, + callback: (data: unknown) => void + ) => { + await (augmentation as any).ensureInitialized?.() + + if (options.onWebSocketMessage) { + return options.onWebSocketMessage(connectionId, callback) + } + + throw new Error('onWebSocketMessage not implemented') + } + + wsAugmentation.offWebSocketMessage = async ( + connectionId: string, + callback: (data: unknown) => void + ) => { + await (augmentation as any).ensureInitialized?.() + + if (options.offWebSocketMessage) { + return options.offWebSocketMessage(connectionId, callback) + } + + throw new Error('offWebSocketMessage not implemented') + } + + wsAugmentation.closeWebSocket = async ( + connectionId: string, + code?: number, + reason?: string + ) => { + await (augmentation as any).ensureInitialized?.() + + if (options.closeWebSocket) { + return options.closeWebSocket(connectionId, code, reason) + } + + throw new Error('closeWebSocket not implemented') + } + + return wsAugmentation +} + +/** + * Simplified function to execute an augmentation method with automatic error handling + * This provides a more concise way to execute augmentation methods compared to the full pipeline + */ +export async function executeAugmentation( + augmentation: IAugmentation, + method: string, + ...args: any[] +): Promise> { + try { + if (!augmentation.enabled) { + return { + success: false, + data: null as any, + error: `Augmentation ${augmentation.name} is disabled` + } + } + + if (typeof (augmentation as any)[method] !== 'function') { + return { + success: false, + data: null as any, + error: `Method ${method} not found on augmentation ${augmentation.name}` + } + } + + const result = await (augmentation as any)[method](...args) + return result + } catch (error) { + console.error(`Error executing ${method} on ${augmentation.name}:`, error) + return { + success: false, + data: null as any, + error: error instanceof Error ? error.message : String(error) + } + } +} + +/** + * Dynamically load augmentations from a module at runtime + * This allows for lazy-loading augmentations when needed instead of at build time + */ +export async function loadAugmentationModule( + modulePromise: Promise, + options: { + autoRegister?: boolean + autoInitialize?: boolean + } = {} +): Promise { + try { + const module = await modulePromise + const augmentations: IAugmentation[] = [] + + // Extract augmentations from the module + for (const key in module) { + const exported = module[key] + + // Skip non-objects and null + if (!exported || typeof exported !== 'object') { + continue + } + + // Check if it's an augmentation + if ( + typeof exported.name === 'string' && + typeof exported.initialize === 'function' && + typeof exported.shutDown === 'function' && + typeof exported.getStatus === 'function' + ) { + augmentations.push(exported) + + // Auto-register if requested + if (options.autoRegister) { + registerAugmentation(exported) + + // Auto-initialize if requested + if (options.autoInitialize) { + exported.initialize().catch((error: Error) => { + console.error( + `Failed to initialize augmentation ${exported.name}:`, + error + ) + }) + } + } + } + } + + return augmentations + } catch (error) { + console.error('Error loading augmentation module:', error) + return [] + } +} diff --git a/src/augmentationPipeline.ts b/src/augmentationPipeline.ts new file mode 100644 index 00000000..bf46f1d5 --- /dev/null +++ b/src/augmentationPipeline.ts @@ -0,0 +1,709 @@ +/** + * Augmentation Event Pipeline + * + * This module provides a pipeline for managing and executing multiple augmentations + * of each type. It allows registering multiple augmentations and executing them + * in sequence or in parallel. + */ + +import { + BrainyAugmentations, + IAugmentation, + IWebSocketSupport, + AugmentationResponse, + AugmentationType +} from './types/augmentations.js' +import { isThreadingAvailable, isBrowser, isNode } from './utils/environment.js' +import { executeInThread } from './utils/workerUtils.js' + +/** + * Type definitions for the augmentation registry + */ +type AugmentationRegistry = { + sense: BrainyAugmentations.ISenseAugmentation[]; + conduit: BrainyAugmentations.IConduitAugmentation[]; + cognition: BrainyAugmentations.ICognitionAugmentation[]; + memory: BrainyAugmentations.IMemoryAugmentation[]; + perception: BrainyAugmentations.IPerceptionAugmentation[]; + dialog: BrainyAugmentations.IDialogAugmentation[]; + activation: BrainyAugmentations.IActivationAugmentation[]; + webSocket: IWebSocketSupport[]; +} + +/** + * Execution mode for the pipeline + */ +export enum ExecutionMode { + SEQUENTIAL = 'sequential', + PARALLEL = 'parallel', + FIRST_SUCCESS = 'firstSuccess', + FIRST_RESULT = 'firstResult', + THREADED = 'threaded' // Execute in separate threads when available +} + +/** + * Options for pipeline execution + */ +export interface PipelineOptions { + mode?: ExecutionMode; + timeout?: number; + stopOnError?: boolean; + forceThreading?: boolean; // Force threading even if not in THREADED mode + disableThreading?: boolean; // Disable threading even if in THREADED mode +} + +/** + * Default pipeline options + */ +const DEFAULT_PIPELINE_OPTIONS: PipelineOptions = { + mode: ExecutionMode.SEQUENTIAL, + timeout: 30000, + stopOnError: false, + forceThreading: false, + disableThreading: false +} + +/** + * AugmentationPipeline class + * + * Manages multiple augmentations of each type and provides methods to execute them. + */ +export class AugmentationPipeline { + private registry: AugmentationRegistry = { + sense: [], + conduit: [], + cognition: [], + memory: [], + perception: [], + dialog: [], + activation: [], + webSocket: [] + } + + /** + * Register an augmentation with the pipeline + * + * @param augmentation The augmentation to register + * @returns The pipeline instance for chaining + */ + public register(augmentation: T): AugmentationPipeline { + let registered = false + + // Check for specific augmentation types + if (this.isAugmentationType( + augmentation, + 'processRawData', + 'listenToFeed' + )) { + this.registry.sense.push(augmentation) + registered = true + } else if (this.isAugmentationType( + augmentation, + 'establishConnection', + 'readData', + 'writeData', + 'monitorStream' + )) { + this.registry.conduit.push(augmentation) + registered = true + } else if (this.isAugmentationType( + augmentation, + 'reason', + 'infer', + 'executeLogic' + )) { + this.registry.cognition.push(augmentation) + registered = true + } else if (this.isAugmentationType( + augmentation, + 'storeData', + 'retrieveData', + 'updateData', + 'deleteData', + 'listDataKeys' + )) { + this.registry.memory.push(augmentation) + registered = true + } else if (this.isAugmentationType( + augmentation, + 'interpret', + 'organize', + 'generateVisualization' + )) { + this.registry.perception.push(augmentation) + registered = true + } else if (this.isAugmentationType( + augmentation, + 'processUserInput', + 'generateResponse', + 'manageContext' + )) { + this.registry.dialog.push(augmentation) + registered = true + } else if (this.isAugmentationType( + augmentation, + 'triggerAction', + 'generateOutput', + 'interactExternal' + )) { + this.registry.activation.push(augmentation) + registered = true + } + + // Check if the augmentation supports WebSocket + if (this.isAugmentationType( + augmentation, + 'connectWebSocket', + 'sendWebSocketMessage', + 'onWebSocketMessage', + 'closeWebSocket' + )) { + this.registry.webSocket.push(augmentation as IWebSocketSupport) + registered = true + } + + // If the augmentation wasn't registered as any known type, throw an error + if (!registered) { + throw new Error(`Unknown augmentation type: ${augmentation.name}`) + } + + return this + } + + /** + * Unregister an augmentation from the pipeline + * + * @param augmentationName The name of the augmentation to unregister + * @returns The pipeline instance for chaining + */ + public unregister(augmentationName: string): AugmentationPipeline { + let found = false + + // Remove from all registries + for (const type in this.registry) { + const typedRegistry = this.registry[type as keyof AugmentationRegistry] + const index = typedRegistry.findIndex(aug => aug.name === augmentationName) + + if (index !== -1) { + typedRegistry.splice(index, 1) + found = true + } + } + + return this + } + + /** + * Initialize all registered augmentations + * + * @returns A promise that resolves when all augmentations are initialized + */ + public async initialize(): Promise { + const allAugmentations = this.getAllAugmentations() + + await Promise.all( + allAugmentations.map(augmentation => + augmentation.initialize().catch(error => { + console.error(`Failed to initialize augmentation ${augmentation.name}:`, error) + }) + ) + ) + } + + /** + * Shut down all registered augmentations + * + * @returns A promise that resolves when all augmentations are shut down + */ + public async shutDown(): Promise { + const allAugmentations = this.getAllAugmentations() + + await Promise.all( + allAugmentations.map(augmentation => + augmentation.shutDown().catch(error => { + console.error(`Failed to shut down augmentation ${augmentation.name}:`, error) + }) + ) + ) + } + + /** + * Execute a sense pipeline + * + * @param method The method to execute on each sense augmentation + * @param args The arguments to pass to the method + * @param options The pipeline execution options + * @returns A promise that resolves with the results from all augmentations + */ + public async executeSensePipeline< + M extends keyof BrainyAugmentations.ISenseAugmentation & string, + R extends BrainyAugmentations.ISenseAugmentation[M] extends (...args: any[]) => AugmentationResponse ? U : never + >( + method: M & (BrainyAugmentations.ISenseAugmentation[M] extends (...args: any[]) => any ? M : never), + args: Parameters any>>, + options: PipelineOptions = {} + ): Promise[]> { + const opts = { ...DEFAULT_PIPELINE_OPTIONS, ...options } + return this.executeTypedPipeline( + this.registry.sense, + method, + args, + opts + ) + } + + /** + * Execute a conduit pipeline + * + * @param method The method to execute on each conduit augmentation + * @param args The arguments to pass to the method + * @param options The pipeline execution options + * @returns A promise that resolves with the results from all augmentations + */ + public async executeConduitPipeline< + M extends keyof BrainyAugmentations.IConduitAugmentation & string, + R extends BrainyAugmentations.IConduitAugmentation[M] extends (...args: any[]) => AugmentationResponse ? U : never + >( + method: M & (BrainyAugmentations.IConduitAugmentation[M] extends (...args: any[]) => any ? M : never), + args: Parameters any>>, + options: PipelineOptions = {} + ): Promise[]> { + const opts = { ...DEFAULT_PIPELINE_OPTIONS, ...options } + return this.executeTypedPipeline( + this.registry.conduit, + method, + args, + opts + ) + } + + /** + * Execute a cognition pipeline + * + * @param method The method to execute on each cognition augmentation + * @param args The arguments to pass to the method + * @param options The pipeline execution options + * @returns A promise that resolves with the results from all augmentations + */ + public async executeCognitionPipeline< + M extends keyof BrainyAugmentations.ICognitionAugmentation & string, + R extends BrainyAugmentations.ICognitionAugmentation[M] extends (...args: any[]) => AugmentationResponse ? U : never + >( + method: M & (BrainyAugmentations.ICognitionAugmentation[M] extends (...args: any[]) => any ? M : never), + args: Parameters any>>, + options: PipelineOptions = {} + ): Promise[]> { + const opts = { ...DEFAULT_PIPELINE_OPTIONS, ...options } + return this.executeTypedPipeline( + this.registry.cognition, + method, + args, + opts + ) + } + + /** + * Execute a memory pipeline + * + * @param method The method to execute on each memory augmentation + * @param args The arguments to pass to the method + * @param options The pipeline execution options + * @returns A promise that resolves with the results from all augmentations + */ + public async executeMemoryPipeline< + M extends keyof BrainyAugmentations.IMemoryAugmentation & string, + R extends BrainyAugmentations.IMemoryAugmentation[M] extends (...args: any[]) => AugmentationResponse ? U : never + >( + method: M & (BrainyAugmentations.IMemoryAugmentation[M] extends (...args: any[]) => any ? M : never), + args: Parameters any>>, + options: PipelineOptions = {} + ): Promise[]> { + const opts = { ...DEFAULT_PIPELINE_OPTIONS, ...options } + return this.executeTypedPipeline( + this.registry.memory, + method, + args, + opts + ) + } + + /** + * Execute a perception pipeline + * + * @param method The method to execute on each perception augmentation + * @param args The arguments to pass to the method + * @param options The pipeline execution options + * @returns A promise that resolves with the results from all augmentations + */ + public async executePerceptionPipeline< + M extends keyof BrainyAugmentations.IPerceptionAugmentation & string, + R extends BrainyAugmentations.IPerceptionAugmentation[M] extends (...args: any[]) => AugmentationResponse ? U : never + >( + method: M & (BrainyAugmentations.IPerceptionAugmentation[M] extends (...args: any[]) => any ? M : never), + args: Parameters any>>, + options: PipelineOptions = {} + ): Promise[]> { + const opts = { ...DEFAULT_PIPELINE_OPTIONS, ...options } + return this.executeTypedPipeline( + this.registry.perception, + method, + args, + opts + ) + } + + /** + * Execute a dialog pipeline + * + * @param method The method to execute on each dialog augmentation + * @param args The arguments to pass to the method + * @param options The pipeline execution options + * @returns A promise that resolves with the results from all augmentations + */ + public async executeDialogPipeline< + M extends keyof BrainyAugmentations.IDialogAugmentation & string, + R extends BrainyAugmentations.IDialogAugmentation[M] extends (...args: any[]) => AugmentationResponse ? U : never + >( + method: M & (BrainyAugmentations.IDialogAugmentation[M] extends (...args: any[]) => any ? M : never), + args: Parameters any>>, + options: PipelineOptions = {} + ): Promise[]> { + const opts = { ...DEFAULT_PIPELINE_OPTIONS, ...options } + return this.executeTypedPipeline( + this.registry.dialog, + method, + args, + opts + ) + } + + /** + * Execute an activation pipeline + * + * @param method The method to execute on each activation augmentation + * @param args The arguments to pass to the method + * @param options The pipeline execution options + * @returns A promise that resolves with the results from all augmentations + */ + public async executeActivationPipeline< + M extends keyof BrainyAugmentations.IActivationAugmentation & string, + R extends BrainyAugmentations.IActivationAugmentation[M] extends (...args: any[]) => AugmentationResponse ? U : never + >( + method: M & (BrainyAugmentations.IActivationAugmentation[M] extends (...args: any[]) => any ? M : never), + args: Parameters any>>, + options: PipelineOptions = {} + ): Promise[]> { + const opts = { ...DEFAULT_PIPELINE_OPTIONS, ...options } + return this.executeTypedPipeline( + this.registry.activation, + method, + args, + opts + ) + } + + /** + * Get all registered augmentations + * + * @returns An array of all registered augmentations + */ + public getAllAugmentations(): IAugmentation[] { + // Create a Set to avoid duplicates (an augmentation might be in multiple registries) + const allAugmentations = new Set([ + ...this.registry.sense, + ...this.registry.conduit, + ...this.registry.cognition, + ...this.registry.memory, + ...this.registry.perception, + ...this.registry.dialog, + ...this.registry.activation, + ...this.registry.webSocket + ]) + + // Convert back to array + return Array.from(allAugmentations) + } + + /** + * Get all augmentations of a specific type + * + * @param type The type of augmentation to get + * @returns An array of all augmentations of the specified type + */ + public getAugmentationsByType(type: AugmentationType): IAugmentation[] { + switch (type) { + case AugmentationType.SENSE: + return [...this.registry.sense] + case AugmentationType.CONDUIT: + return [...this.registry.conduit] + case AugmentationType.COGNITION: + return [...this.registry.cognition] + case AugmentationType.MEMORY: + return [...this.registry.memory] + case AugmentationType.PERCEPTION: + return [...this.registry.perception] + case AugmentationType.DIALOG: + return [...this.registry.dialog] + case AugmentationType.ACTIVATION: + return [...this.registry.activation] + case AugmentationType.WEBSOCKET: + return [...this.registry.webSocket] + default: + return [] + } + } + + /** + * Get all available augmentation types + * + * @returns An array of all augmentation types that have at least one registered augmentation + */ + public getAvailableAugmentationTypes(): AugmentationType[] { + const availableTypes: AugmentationType[] = [] + + if (this.registry.sense.length > 0) availableTypes.push(AugmentationType.SENSE) + if (this.registry.conduit.length > 0) availableTypes.push(AugmentationType.CONDUIT) + if (this.registry.cognition.length > 0) availableTypes.push(AugmentationType.COGNITION) + if (this.registry.memory.length > 0) availableTypes.push(AugmentationType.MEMORY) + if (this.registry.perception.length > 0) availableTypes.push(AugmentationType.PERCEPTION) + if (this.registry.dialog.length > 0) availableTypes.push(AugmentationType.DIALOG) + if (this.registry.activation.length > 0) availableTypes.push(AugmentationType.ACTIVATION) + if (this.registry.webSocket.length > 0) availableTypes.push(AugmentationType.WEBSOCKET) + + return availableTypes + } + + /** + * Get all WebSocket-supporting augmentations + * + * @returns An array of all augmentations that support WebSocket connections + */ + public getWebSocketAugmentations(): IWebSocketSupport[] { + return [...this.registry.webSocket] + } + + /** + * Check if an augmentation is of a specific type + * + * @param augmentation The augmentation to check + * @param methods The methods that should be present on the augmentation + * @returns True if the augmentation is of the specified type + */ + private isAugmentationType( + augmentation: IAugmentation, + ...methods: (keyof T)[] + ): augmentation is T { + // First check that the augmentation has all the required base methods + const baseMethodsExist = [ + 'initialize', + 'shutDown', + 'getStatus' + ].every(method => typeof (augmentation as any)[method] === 'function') + + if (!baseMethodsExist) { + return false + } + + // Then check that it has all the specific methods for this type + return methods.every(method => typeof (augmentation as any)[method] === 'function') + } + + /** + * Determines if threading should be used based on options and environment + * + * @param options The pipeline options + * @returns True if threading should be used, false otherwise + */ + private shouldUseThreading(options: PipelineOptions): boolean { + // If threading is explicitly disabled, don't use it + if (options.disableThreading) { + return false; + } + + // If threading is explicitly forced, use it if available + if (options.forceThreading) { + return isThreadingAvailable(); + } + + // If in THREADED mode, use threading if available + if (options.mode === ExecutionMode.THREADED) { + return isThreadingAvailable(); + } + + // Otherwise, don't use threading + return false; + } + + /** + * Execute a pipeline for a specific augmentation type + * + * @param augmentations The augmentations to execute + * @param method The method to execute on each augmentation + * @param args The arguments to pass to the method + * @param options The pipeline execution options + * @returns A promise that resolves with the results from all augmentations + */ + private async executeTypedPipeline< + T extends IAugmentation, + M extends keyof T & string, + R extends T[M] extends (...args: any[]) => AugmentationResponse ? U : never + >( + augmentations: T[], + method: M & (T[M] extends (...args: any[]) => any ? M : never), + args: Parameters any>>, + options: PipelineOptions + ): Promise[]> { + // Filter out disabled augmentations + const enabledAugmentations = augmentations.filter(aug => aug.enabled !== false) + + if (enabledAugmentations.length === 0) { + return [] + } + + // Create a function to execute the method on an augmentation + const executeMethod = async (augmentation: T): Promise<{ + success: boolean + data: R + error?: string + }> => { + try { + // Create a timeout promise if a timeout is specified + const timeoutPromise = options.timeout + ? new Promise<{ + success: boolean + data: R + error?: string + }>((_, reject) => { + setTimeout(() => { + reject(new Error(`Timeout executing ${String(method)} on ${augmentation.name}`)) + }, options.timeout) + }) + : null + + // Check if threading should be used + const useThreading = this.shouldUseThreading(options); + + // Execute the method on the augmentation, using threading if appropriate + let methodPromise: Promise>; + + if (useThreading) { + // Execute in a separate thread + try { + // Create a function that can be serialized and executed in a worker + const workerFn = (...workerArgs: any[]) => { + // This function will be stringified and executed in the worker + // It needs to be self-contained + const augFn = augmentation[method as string] as Function; + return augFn.apply(augmentation, workerArgs); + }; + + methodPromise = executeInThread>(workerFn, ...args); + } catch (threadError) { + console.warn(`Failed to execute in thread, falling back to main thread: ${threadError}`); + // Fall back to executing in the main thread + methodPromise = Promise.resolve((augmentation[method] as Function)(...args) as AugmentationResponse); + } + } else { + // Execute in the main thread + methodPromise = Promise.resolve((augmentation[method] as Function)(...args) as AugmentationResponse); + } + + // Race the method promise against the timeout promise if a timeout is specified + const result = timeoutPromise + ? await Promise.race([methodPromise, timeoutPromise]) + : await methodPromise + + return result + } catch (error) { + console.error(`Error executing ${String(method)} on ${augmentation.name}:`, error) + return { + success: false, + data: null as unknown as R, + error: error instanceof Error ? error.message : String(error) + } + } + } + + // Execute the pipeline based on the specified mode + switch (options.mode) { + case ExecutionMode.PARALLEL: + // Execute all augmentations in parallel + return enabledAugmentations.map(executeMethod) + + case ExecutionMode.THREADED: + // Execute all augmentations in parallel with threading enabled + // Force threading for this mode + const threadedOptions = { ...options, forceThreading: true }; + + // Create a new executeMethod function that uses the threaded options + const executeMethodThreaded = async (augmentation: T) => { + // Save the original options + const originalOptions = options; + + // Set the options to the threaded options + options = threadedOptions; + + // Execute the method + const result = await executeMethod(augmentation); + + // Restore the original options + options = originalOptions; + + return result; + }; + + return enabledAugmentations.map(executeMethodThreaded); + + case ExecutionMode.FIRST_SUCCESS: + // Execute augmentations sequentially until one succeeds + for (const augmentation of enabledAugmentations) { + const resultPromise = executeMethod(augmentation) + const result = await resultPromise + if (result.success) { + return [resultPromise] + } + } + return [] + + case ExecutionMode.FIRST_RESULT: + // Execute augmentations sequentially until one returns a result + for (const augmentation of enabledAugmentations) { + const resultPromise = executeMethod(augmentation) + const result = await resultPromise + if (result.success && result.data) { + return [resultPromise] + } + } + return [] + + case ExecutionMode.SEQUENTIAL: + default: + // Execute augmentations sequentially + const results: Promise<{ + success: boolean + data: R + error?: string + }>[] = [] + for (const augmentation of enabledAugmentations) { + const resultPromise = executeMethod(augmentation) + results.push(resultPromise) + + // Check if we need to stop on error + if (options.stopOnError) { + const result = await resultPromise + if (!result.success) { + break + } + } + } + return results + } + } +} + +// Create and export a default instance of the pipeline +export const augmentationPipeline = new AugmentationPipeline() diff --git a/src/augmentationRegistry.ts b/src/augmentationRegistry.ts new file mode 100644 index 00000000..b8199758 --- /dev/null +++ b/src/augmentationRegistry.ts @@ -0,0 +1,122 @@ +/** + * Augmentation Registry + * + * This module provides a registry for augmentations that are loaded at build time. + * It replaces the dynamic loading mechanism in pluginLoader.ts. + */ + +import { IPipeline } from './types/pipelineTypes.js' +import { AugmentationType, IAugmentation } from './types/augmentations.js' + +// Forward declaration of the pipeline instance to avoid circular dependency +// The actual pipeline will be provided when initializeAugmentationPipeline is called +let defaultPipeline: IPipeline | null = null + +/** + * Sets the default pipeline instance + * This function should be called from pipeline.ts after the pipeline is created + */ +export function setDefaultPipeline(pipeline: IPipeline): void { + defaultPipeline = pipeline +} + +/** + * Registry of all available augmentations + */ +export const availableAugmentations: IAugmentation[] = [] + +/** + * Registers an augmentation with the registry + * + * @param augmentation The augmentation to register + * @returns The augmentation that was registered + */ +export function registerAugmentation(augmentation: T): T { + // Set enabled to true by default if not specified + if (augmentation.enabled === undefined) { + augmentation.enabled = true + } + + // Add to the registry + availableAugmentations.push(augmentation) + + return augmentation +} + +/** + * Initializes the augmentation pipeline with all registered augmentations + * + * @param pipeline Optional custom pipeline to use instead of the default + * @returns The pipeline that was initialized + * @throws Error if no pipeline is provided and the default pipeline hasn't been set + */ +export function initializeAugmentationPipeline( + pipelineInstance?: IPipeline +): IPipeline { + // Use the provided pipeline or fall back to the default + const pipeline = pipelineInstance || defaultPipeline + + if (!pipeline) { + throw new Error('No pipeline provided and default pipeline not set. Call setDefaultPipeline first.') + } + + // Register all augmentations with the pipeline + for (const augmentation of availableAugmentations) { + if (augmentation.enabled) { + pipeline.register(augmentation) + } + } + + return pipeline +} + +/** + * Enables or disables an augmentation by name + * + * @param name The name of the augmentation to enable/disable + * @param enabled Whether to enable or disable the augmentation + * @returns True if the augmentation was found and updated, false otherwise + */ +export function setAugmentationEnabled(name: string, enabled: boolean): boolean { + const augmentation = availableAugmentations.find(aug => aug.name === name) + + if (augmentation) { + augmentation.enabled = enabled + return true + } + + return false +} + +/** + * Gets all augmentations of a specific type + * + * @param type The type of augmentation to get + * @returns An array of all augmentations of the specified type + */ +export function getAugmentationsByType(type: AugmentationType): IAugmentation[] { + return availableAugmentations.filter(aug => { + // Check if the augmentation is of the specified type + // This is a simplified check and may need to be updated based on how types are determined + switch (type) { + case AugmentationType.SENSE: + return 'processRawData' in aug && 'listenToFeed' in aug + case AugmentationType.CONDUIT: + return 'establishConnection' in aug && 'readData' in aug && 'writeData' in aug + case AugmentationType.COGNITION: + return 'reason' in aug && 'infer' in aug && 'executeLogic' in aug + case AugmentationType.MEMORY: + return 'storeData' in aug && 'retrieveData' in aug && 'updateData' in aug + case AugmentationType.PERCEPTION: + return 'interpret' in aug && 'organize' in aug && 'generateVisualization' in aug + case AugmentationType.DIALOG: + return 'processUserInput' in aug && 'generateResponse' in aug && 'manageContext' in aug + case AugmentationType.ACTIVATION: + return 'triggerAction' in aug && 'generateOutput' in aug && 'interactExternal' in aug + case AugmentationType.WEBSOCKET: + return 'connectWebSocket' in aug && 'sendWebSocketMessage' in aug && 'onWebSocketMessage' in aug + default: + return false + } + }) +} diff --git a/src/augmentationRegistryLoader.ts b/src/augmentationRegistryLoader.ts new file mode 100644 index 00000000..21493126 --- /dev/null +++ b/src/augmentationRegistryLoader.ts @@ -0,0 +1,291 @@ +/** + * Augmentation Registry Loader + * + * This module provides functionality for loading augmentation registrations + * at build time. It's designed to be used with build tools like webpack or rollup + * to automatically discover and register augmentations. + */ + +import { IAugmentation } from './types/augmentations.js' +import { registerAugmentation } from './augmentationRegistry.js' + +/** + * Options for the augmentation registry loader + */ +export interface AugmentationRegistryLoaderOptions { + /** + * Whether to automatically initialize the augmentations after loading + * @default false + */ + autoInitialize?: boolean; + + /** + * Whether to log debug information during loading + * @default false + */ + debug?: boolean; +} + +/** + * Default options for the augmentation registry loader + */ +const DEFAULT_OPTIONS: AugmentationRegistryLoaderOptions = { + autoInitialize: false, + debug: false +} + +/** + * Result of loading augmentations + */ +export interface AugmentationLoadResult { + /** + * The augmentations that were loaded + */ + augmentations: IAugmentation[]; + + /** + * Any errors that occurred during loading + */ + errors: Error[]; +} + +/** + * Loads augmentations from the specified modules + * + * This function is designed to be used with build tools like webpack or rollup + * to automatically discover and register augmentations. + * + * @param modules An object containing modules with augmentations to register + * @param options Options for the loader + * @returns A promise that resolves with the result of loading the augmentations + * + * @example + * ```typescript + * // webpack.config.js + * const { AugmentationRegistryPlugin } = require('brainy/dist/webpack'); + * + * module.exports = { + * // ... other webpack config + * plugins: [ + * new AugmentationRegistryPlugin({ + * // Pattern to match files containing augmentations + * pattern: /augmentation\.js$/, + * // Options for the loader + * options: { + * autoInitialize: true, + * debug: true + * } + * }) + * ] + * }; + * ``` + */ +export async function loadAugmentationsFromModules( + modules: Record, + options: AugmentationRegistryLoaderOptions = {} +): Promise { + const opts = { ...DEFAULT_OPTIONS, ...options } + const result: AugmentationLoadResult = { + augmentations: [], + errors: [] + } + + if (opts.debug) { + console.log(`[AugmentationRegistryLoader] Loading augmentations from ${Object.keys(modules).length} modules`) + } + + // Process each module + for (const [modulePath, module] of Object.entries(modules)) { + try { + if (opts.debug) { + console.log(`[AugmentationRegistryLoader] Processing module: ${modulePath}`) + } + + // Extract augmentations from the module + const augmentations = extractAugmentationsFromModule(module) + + if (augmentations.length === 0) { + if (opts.debug) { + console.log(`[AugmentationRegistryLoader] No augmentations found in module: ${modulePath}`) + } + continue + } + + // Register each augmentation + for (const augmentation of augmentations) { + try { + const registered = registerAugmentation(augmentation) + result.augmentations.push(registered) + + if (opts.debug) { + console.log(`[AugmentationRegistryLoader] Registered augmentation: ${registered.name}`) + } + } catch (error) { + const err = error instanceof Error ? error : new Error(String(error)) + result.errors.push(err) + + if (opts.debug) { + console.error(`[AugmentationRegistryLoader] Failed to register augmentation: ${err.message}`) + } + } + } + } catch (error) { + const err = error instanceof Error ? error : new Error(String(error)) + result.errors.push(err) + + if (opts.debug) { + console.error(`[AugmentationRegistryLoader] Error processing module ${modulePath}: ${err.message}`) + } + } + } + + if (opts.debug) { + console.log(`[AugmentationRegistryLoader] Loaded ${result.augmentations.length} augmentations with ${result.errors.length} errors`) + } + + return result +} + +/** + * Extracts augmentations from a module + * + * @param module The module to extract augmentations from + * @returns An array of augmentations found in the module + */ +function extractAugmentationsFromModule(module: any): IAugmentation[] { + const augmentations: IAugmentation[] = [] + + // If the module itself is an augmentation, add it + if (isAugmentation(module)) { + augmentations.push(module) + } + + // Check for exported augmentations + if (module && typeof module === 'object') { + for (const key of Object.keys(module)) { + const exported = module[key] + + // Skip non-objects and null + if (!exported || typeof exported !== 'object') { + continue + } + + // If the exported value is an augmentation, add it + if (isAugmentation(exported)) { + augmentations.push(exported) + } + + // If the exported value is an array of augmentations, add them + if (Array.isArray(exported) && exported.every(isAugmentation)) { + augmentations.push(...exported) + } + } + } + + return augmentations +} + +/** + * Checks if an object is an augmentation + * + * @param obj The object to check + * @returns True if the object is an augmentation + */ +function isAugmentation(obj: any): obj is IAugmentation { + return ( + obj && + typeof obj === 'object' && + typeof obj.name === 'string' && + typeof obj.initialize === 'function' && + typeof obj.shutDown === 'function' && + typeof obj.getStatus === 'function' + ) +} + +/** + * Creates a webpack plugin for automatically loading augmentations + * + * @param options Options for the plugin + * @returns A webpack plugin + * + * @example + * ```typescript + * // webpack.config.js + * const { createAugmentationRegistryPlugin } = require('brainy/dist/webpack'); + * + * module.exports = { + * // ... other webpack config + * plugins: [ + * createAugmentationRegistryPlugin({ + * pattern: /augmentation\.js$/, + * options: { + * autoInitialize: true, + * debug: true + * } + * }) + * ] + * }; + * ``` + */ +export function createAugmentationRegistryPlugin(options: { + /** + * Pattern to match files containing augmentations + */ + pattern: RegExp; + + /** + * Options for the loader + */ + options?: AugmentationRegistryLoaderOptions; +}) { + // This is just a placeholder - the actual implementation would depend on the build tool + return { + name: 'AugmentationRegistryPlugin', + pattern: options.pattern, + options: options.options || {} + } +} + +/** + * Creates a rollup plugin for automatically loading augmentations + * + * @param options Options for the plugin + * @returns A rollup plugin + * + * @example + * ```typescript + * // rollup.config.js + * import { createAugmentationRegistryRollupPlugin } from 'brainy/dist/rollup'; + * + * export default { + * // ... other rollup config + * plugins: [ + * createAugmentationRegistryRollupPlugin({ + * pattern: /augmentation\.js$/, + * options: { + * autoInitialize: true, + * debug: true + * } + * }) + * ] + * }; + * ``` + */ +export function createAugmentationRegistryRollupPlugin(options: { + /** + * Pattern to match files containing augmentations + */ + pattern: RegExp; + + /** + * Options for the loader + */ + options?: AugmentationRegistryLoaderOptions; +}) { + // This is just a placeholder - the actual implementation would depend on the build tool + return { + name: 'augmentation-registry-rollup-plugin', + pattern: options.pattern, + options: options.options || {} + } +} diff --git a/src/augmentations/README.md b/src/augmentations/README.md new file mode 100644 index 00000000..8eb6a3c9 --- /dev/null +++ b/src/augmentations/README.md @@ -0,0 +1,251 @@ +
+Brainy Logo + +# Brainy Augmentations + +
+ +This directory contains the augmentation implementations for Brainy. Augmentations are pluggable components that extend +Brainy's functionality in various ways. + +## Available Augmentations + +### Conduit Augmentations + +Conduit augmentations provide data synchronization between Brainy instances. + +#### WebSocketConduitAugmentation + +A conduit augmentation that syncs Brainy instances using WebSockets. This is used for syncing between browsers and +servers, or between servers. + +```javascript +import { createConduitAugmentation, augmentationPipeline } from '@soulcraft/brainy' + +// Create a WebSocket conduit augmentation +const wsConduit = await createConduitAugmentation('websocket', 'my-websocket-sync') + +// Register the augmentation with the pipeline +augmentationPipeline.register(wsConduit) + +// Connect to another Brainy instance +const connectionResult = await wsConduit.establishConnection( + 'wss://your-websocket-server.com/brainy-sync', + { protocols: 'brainy-sync' } +) +``` + +#### WebRTCConduitAugmentation + +A conduit augmentation that syncs Brainy instances using WebRTC. This is used for direct peer-to-peer syncing between +browsers. + +```javascript +import { createConduitAugmentation, augmentationPipeline } from '@soulcraft/brainy' + +// Create a WebRTC conduit augmentation +const webrtcConduit = await createConduitAugmentation('webrtc', 'my-webrtc-sync') + +// Register the augmentation with the pipeline +augmentationPipeline.register(webrtcConduit) + +// Connect to a peer +const connectionResult = await webrtcConduit.establishConnection( + 'peer-id-to-connect-to', + { + signalServerUrl: 'wss://your-signal-server.com', + localPeerId: 'my-peer-id', + iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] + } +) +``` + +#### ServerSearchConduitAugmentation + +A specialized conduit augmentation that provides functionality for searching a server-hosted Brainy instance and storing +results locally. This allows you to: + +- Search a server-hosted Brainy instance from a browser +- Store the search results in a local Brainy instance +- Perform further searches against the local instance without needing to query the server again +- Add data to both local and server instances + +```javascript +import { + ServerSearchConduitAugmentation, + createServerSearchAugmentations, + augmentationPipeline +} from '@soulcraft/brainy' + +// Using the factory function (recommended) +const { conduit, activation, connection } = await createServerSearchAugmentations( + 'wss://your-brainy-server.com/ws', + { protocols: 'brainy-sync' } +) + +// Register the augmentations with the pipeline +augmentationPipeline.register(conduit) +augmentationPipeline.register(activation) + +// Search the server and store results locally +const serverSearchResult = await conduit.searchServer( + connection.connectionId, + 'your search query', + 5 // limit +) + +// Search the local instance +const localSearchResult = await conduit.searchLocal('your search query', 5) + +// Perform a combined search (local first, then server if needed) +const combinedSearchResult = await conduit.searchCombined( + connection.connectionId, + 'your search query', + 5 +) + +// Add data to both local and server +const addResult = await conduit.addToBoth( + connection.connectionId, + 'Text to add', + { /* metadata */ } +) +``` + +### Activation Augmentations + +Activation augmentations dictate how Brainy initiates actions, responses, or data manipulations. + +#### ServerSearchActivationAugmentation + +An activation augmentation that provides actions for server search functionality. This works in conjunction with the +ServerSearchConduitAugmentation to provide a complete solution for browser-server search. + +```javascript +import { + ServerSearchActivationAugmentation, + createServerSearchAugmentations, + augmentationPipeline +} from '@soulcraft/brainy' + +// Using the factory function (recommended) +const { conduit, activation, connection } = await createServerSearchAugmentations( + 'wss://your-brainy-server.com/ws', + { protocols: 'brainy-sync' } +) + +// Register the augmentations with the pipeline +augmentationPipeline.register(conduit) +augmentationPipeline.register(activation) + +// Use the activation augmentation to search the server +const serverSearchAction = activation.triggerAction('searchServer', { + connectionId: connection.connectionId, + query: 'your search query', + limit: 5 +}) + +if (serverSearchAction.success) { + // The data property contains a promise that will resolve to the search results + const serverSearchResult = await serverSearchAction.data + console.log('Server search results:', serverSearchResult) +} + +// Other available actions: +// - 'connectToServer': Connect to a server +// - 'searchLocal': Search the local instance +// - 'searchCombined': Search both local and server +// - 'addToBoth': Add data to both local and server +``` + +## Using the Augmentation Pipeline + +The augmentation pipeline provides a way to execute augmentations based on their type. + +```javascript +import { augmentationPipeline } from '@soulcraft/brainy' + +// Execute a conduit augmentation +const conduitResults = await augmentationPipeline.executeConduitPipeline( + 'methodName', + [arg1, arg2, ...], + { /* options */ } +) + +// Execute an activation augmentation +const activationResults = await augmentationPipeline.executeActivationPipeline( + 'methodName', + [arg1, arg2, ...], + { /* options */ } +) +``` + +## Creating Custom Augmentations + +To create a custom augmentation, implement one of the augmentation interfaces: + +- `ISenseAugmentation`: For processing raw data +- `IConduitAugmentation`: For data synchronization +- `ICognitionAugmentation`: For reasoning and inference +- `IMemoryAugmentation`: For data storage +- `IPerceptionAugmentation`: For data interpretation and visualization +- `IDialogAugmentation`: For natural language processing +- `IActivationAugmentation`: For triggering actions + +Example: + +```javascript +import { AugmentationType, IActivationAugmentation } from '@soulcraft/brainy' + +class MyCustomActivation implements IActivationAugmentation { + readonly + name = 'my-custom-activation' + readonly + description = 'My custom activation augmentation' + enabled = true + + getType(): AugmentationType { + return AugmentationType.ACTIVATION + } + + async initialize(): Promise { + // Initialization code + } + + async shutDown(): Promise { + // Cleanup code + } + + async getStatus(): Promise<'active' | 'inactive' | 'error'> { + return 'active' + } + + triggerAction(actionName: string, parameters + +?: + + Record + +): + + AugmentationResponse { + // Implementation + } + + generateOutput(knowledgeId: string, format: string): AugmentationResponse> { + // Implementation +} + +interactExternal(systemId +: +string, payload +: +Record < string, unknown > +): +AugmentationResponse < unknown > { + // Implementation +} +} +``` diff --git a/src/augmentations/conduitAugmentations.ts b/src/augmentations/conduitAugmentations.ts new file mode 100644 index 00000000..ba94e647 --- /dev/null +++ b/src/augmentations/conduitAugmentations.ts @@ -0,0 +1,1409 @@ +import { + AugmentationType, + IConduitAugmentation, + IWebSocketSupport, + AugmentationResponse, + WebSocketConnection +} from '../types/augmentations.js' +import { v4 as uuidv4 } from 'uuid' + +/** + * Base class for conduit augmentations that provide data synchronization between Brainy instances + */ +abstract class BaseConduitAugmentation implements IConduitAugmentation { + readonly name: string + readonly description: string = 'Base conduit augmentation' + enabled: boolean = true + protected isInitialized = false + protected connections: Map = new Map() + + constructor(name: string) { + this.name = name + } + + async initialize(): Promise { + if (this.isInitialized) { + return + } + + try { + this.isInitialized = true + } catch (error) { + console.error(`Failed to initialize ${this.name}:`, error) + throw new Error(`Failed to initialize ${this.name}: ${error}`) + } + } + + async shutDown(): Promise { + // Close all connections + for (const [connectionId, connection] of this.connections.entries()) { + try { + if (connection.close) { + await connection.close() + } + } catch (error) { + console.error(`Failed to close connection ${connectionId}:`, error) + } + } + + this.connections.clear() + this.isInitialized = false + } + + async getStatus(): Promise<'active' | 'inactive' | 'error'> { + return this.isInitialized ? 'active' : 'inactive' + } + + abstract establishConnection( + targetSystemId: string, + config: Record + ): Promise> + + abstract readData( + query: Record, + options?: Record + ): Promise> + + abstract writeData( + data: Record, + options?: Record + ): Promise> + + abstract monitorStream( + streamId: string, + callback: (data: unknown) => void + ): Promise + + protected async ensureInitialized(): Promise { + if (!this.isInitialized) { + await this.initialize() + } + } +} + +/** + * WebSocket conduit augmentation for syncing Brainy instances using WebSockets + * + * This conduit is for syncing between browsers and servers, or between servers. + * WebSockets cannot be used for direct browser-to-browser communication without a server in the middle. + */ +export class WebSocketConduitAugmentation extends BaseConduitAugmentation implements IWebSocketSupport { + readonly description = 'Conduit augmentation that syncs Brainy instances using WebSockets' + private webSocketConnections: Map = new Map() + private messageCallbacks: Map void>> = new Map() + + constructor(name: string = 'websocket-conduit') { + super(name) + } + + getType(): AugmentationType { + return AugmentationType.CONDUIT + } + + /** + * Establishes a connection to another Brainy instance + * @param targetSystemId The URL or identifier of the target system + * @param config Configuration options for the connection + */ + async establishConnection( + targetSystemId: string, + config: Record + ): Promise> { + await this.ensureInitialized() + + try { + // For WebSocket connections, targetSystemId should be a WebSocket URL + const url = targetSystemId + const protocols = config.protocols as string | string[] | undefined + + // Create a WebSocket connection + const connection = await this.connectWebSocket(url, protocols) + + // Store the connection + this.connections.set(connection.connectionId, connection) + + return { + success: true, + data: connection + } + } catch (error) { + console.error(`Failed to establish connection to ${targetSystemId}:`, error) + return { + success: false, + data: null as any, + error: `Failed to establish connection: ${error}` + } + } + } + + /** + * Reads data from a connected Brainy instance + * @param query Query parameters for reading data + * @param options Additional options + */ + async readData( + query: Record, + options?: Record + ): Promise> { + await this.ensureInitialized() + + try { + const connectionId = query.connectionId as string + + if (!connectionId) { + throw new Error('connectionId is required for reading data') + } + + const connection = this.webSocketConnections.get(connectionId) + + if (!connection) { + throw new Error(`Connection ${connectionId} not found`) + } + + // Create a request message + const requestMessage = { + type: 'read', + query: query.query || {}, + requestId: uuidv4(), + options + } + + // Send the request + await this.sendWebSocketMessage(connectionId, requestMessage) + + // Return a promise that will be resolved when the response is received + return new Promise((resolve) => { + const responseHandler = (data: unknown) => { + // Check if this is the response to our request + const response = data as any + if (response && response.type === 'readResponse' && response.requestId === requestMessage.requestId) { + // Remove the handler + this.offWebSocketMessage(connectionId, responseHandler) + + // Resolve with the response data + resolve({ + success: response.success, + data: response.data, + error: response.error + }) + } + } + + // Register the response handler + this.onWebSocketMessage(connectionId, responseHandler) + + // Set a timeout to prevent hanging + setTimeout(() => { + this.offWebSocketMessage(connectionId, responseHandler) + resolve({ + success: false, + data: null, + error: 'Timeout waiting for read response' + }) + }, 30000) // 30 second timeout + }) + } catch (error) { + console.error(`Failed to read data:`, error) + return { + success: false, + data: null, + error: `Failed to read data: ${error}` + } + } + } + + /** + * Writes data to a connected Brainy instance + * @param data The data to write + * @param options Additional options + */ + async writeData( + data: Record, + options?: Record + ): Promise> { + await this.ensureInitialized() + + try { + const connectionId = data.connectionId as string + + if (!connectionId) { + throw new Error('connectionId is required for writing data') + } + + const connection = this.webSocketConnections.get(connectionId) + + if (!connection) { + throw new Error(`Connection ${connectionId} not found`) + } + + // Create a write message + const writeMessage = { + type: 'write', + data: data.data || {}, + requestId: uuidv4(), + options + } + + // Send the write message + await this.sendWebSocketMessage(connectionId, writeMessage) + + // Return a promise that will be resolved when the response is received + return new Promise((resolve) => { + const responseHandler = (data: unknown) => { + // Check if this is the response to our request + const response = data as any + if (response && response.type === 'writeResponse' && response.requestId === writeMessage.requestId) { + // Remove the handler + this.offWebSocketMessage(connectionId, responseHandler) + + // Resolve with the response data + resolve({ + success: response.success, + data: response.data, + error: response.error + }) + } + } + + // Register the response handler + this.onWebSocketMessage(connectionId, responseHandler) + + // Set a timeout to prevent hanging + setTimeout(() => { + this.offWebSocketMessage(connectionId, responseHandler) + resolve({ + success: false, + data: null, + error: 'Timeout waiting for write response' + }) + }, 30000) // 30 second timeout + }) + } catch (error) { + console.error(`Failed to write data:`, error) + return { + success: false, + data: null, + error: `Failed to write data: ${error}` + } + } + } + + /** + * Monitors a data stream from a connected Brainy instance + * @param streamId The ID of the stream to monitor (usually a connection ID) + * @param callback Function to call when new data is received + */ + async monitorStream( + streamId: string, + callback: (data: unknown) => void + ): Promise { + await this.ensureInitialized() + + try { + const connection = this.webSocketConnections.get(streamId) + + if (!connection) { + throw new Error(`Connection ${streamId} not found`) + } + + // Register the callback for all messages on this connection + await this.onWebSocketMessage(streamId, callback) + + } catch (error) { + console.error(`Failed to monitor stream ${streamId}:`, error) + throw new Error(`Failed to monitor stream: ${error}`) + } + } + + /** + * Establishes a WebSocket connection + * @param url The WebSocket server URL to connect to + * @param protocols Optional subprotocols + */ + async connectWebSocket( + url: string, + protocols?: string | string[] + ): Promise { + await this.ensureInitialized() + + return new Promise((resolve, reject) => { + try { + // Check if WebSocket is available + if (typeof WebSocket === 'undefined') { + throw new Error('WebSocket is not available in this environment') + } + + // Create a new WebSocket connection + const ws = new WebSocket(url, protocols) + const connectionId = uuidv4() + + // Create a connection object + const connection: WebSocketConnection = { + connectionId, + url, + status: 'disconnected', + send: async (data) => { + if (ws.readyState !== WebSocket.OPEN) { + throw new Error('WebSocket is not open') + } + ws.send(data) + }, + close: async () => { + ws.close() + } + } + + // Set up event handlers + ws.onopen = () => { + connection.status = 'connected' + resolve(connection) + } + + ws.onerror = (error) => { + connection.status = 'error' + console.error(`WebSocket error for ${url}:`, error) + if (ws.readyState !== WebSocket.OPEN) { + reject(new Error(`WebSocket connection failed: ${error}`)) + } + } + + ws.onclose = () => { + connection.status = 'disconnected' + // Remove from connections map + this.webSocketConnections.delete(connectionId) + // Remove all callbacks + this.messageCallbacks.delete(connectionId) + } + + // Create a message handler wrapper that will call all registered callbacks + const messageHandlerWrapper = (data: unknown) => { + const callbacks = this.messageCallbacks.get(connectionId) + if (callbacks) { + for (const callback of callbacks) { + try { + callback(data) + } catch (error) { + console.error(`Error in WebSocket message callback:`, error) + } + } + } + } + + // Store the message handler wrapper + connection._messageHandlerWrapper = messageHandlerWrapper + + // Set up the message handler + ws.onmessage = (event) => { + try { + // Parse the message if it's a string + let data = event.data + if (typeof data === 'string') { + try { + data = JSON.parse(data) + } catch { + // If parsing fails, use the raw string + } + } + + // Call the message handler wrapper + messageHandlerWrapper(data) + } catch (error) { + console.error(`Error handling WebSocket message:`, error) + } + } + + // Store the stream message handler + connection._streamMessageHandler = (event) => ws.onmessage && ws.onmessage(event as any) + + // Store the connection + this.webSocketConnections.set(connectionId, connection) + + // Initialize the callbacks set + this.messageCallbacks.set(connectionId, new Set()) + + } catch (error) { + reject(error) + } + }) + } + + /** + * Sends data through an established WebSocket connection + * @param connectionId The identifier of the established connection + * @param data The data to send (will be serialized if not a string) + */ + async sendWebSocketMessage( + connectionId: string, + data: unknown + ): Promise { + await this.ensureInitialized() + + const connection = this.webSocketConnections.get(connectionId) + + if (!connection) { + throw new Error(`WebSocket connection ${connectionId} not found`) + } + + if (!connection.send) { + throw new Error(`WebSocket connection ${connectionId} does not support sending messages`) + } + + // Serialize the data if it's not already a string or binary + let serializedData: string | ArrayBufferLike | Blob | ArrayBufferView + + if (typeof data === 'string' || + data instanceof ArrayBuffer || + data instanceof Blob || + ArrayBuffer.isView(data)) { + serializedData = data as any + } else { + // Convert to JSON string + serializedData = JSON.stringify(data) + } + + // Send the data + await connection.send(serializedData) + } + + /** + * Registers a callback for incoming WebSocket messages + * @param connectionId The identifier of the established connection + * @param callback The function to call when a message is received + */ + async onWebSocketMessage( + connectionId: string, + callback: (data: unknown) => void + ): Promise { + await this.ensureInitialized() + + const connection = this.webSocketConnections.get(connectionId) + + if (!connection) { + throw new Error(`WebSocket connection ${connectionId} not found`) + } + + // Get or create the callbacks set for this connection + let callbacks = this.messageCallbacks.get(connectionId) + + if (!callbacks) { + callbacks = new Set() + this.messageCallbacks.set(connectionId, callbacks) + } + + // Add the callback + callbacks.add(callback) + } + + /** + * Removes a callback for incoming WebSocket messages + * @param connectionId The identifier of the established connection + * @param callback The function to remove from the callbacks + */ + async offWebSocketMessage( + connectionId: string, + callback: (data: unknown) => void + ): Promise { + await this.ensureInitialized() + + const callbacks = this.messageCallbacks.get(connectionId) + + if (callbacks) { + callbacks.delete(callback) + } + } + + /** + * Closes an established WebSocket connection + * @param connectionId The identifier of the established connection + * @param code Optional close code + * @param reason Optional close reason + */ + async closeWebSocket( + connectionId: string, + code?: number, + reason?: string + ): Promise { + await this.ensureInitialized() + + const connection = this.webSocketConnections.get(connectionId) + + if (!connection) { + throw new Error(`WebSocket connection ${connectionId} not found`) + } + + if (!connection.close) { + throw new Error(`WebSocket connection ${connectionId} does not support closing`) + } + + // Close the connection + await connection.close() + + // Remove from connections map + this.webSocketConnections.delete(connectionId) + + // Remove all callbacks + this.messageCallbacks.delete(connectionId) + } +} + +/** + * WebRTC conduit augmentation for syncing Brainy instances using WebRTC + * + * This conduit is for direct peer-to-peer syncing between browsers. + * It is the recommended approach for browser-to-browser communication. + */ +export class WebRTCConduitAugmentation extends BaseConduitAugmentation implements IWebSocketSupport { + readonly description = 'Conduit augmentation that syncs Brainy instances using WebRTC' + private peerConnections: Map = new Map() + private dataChannels: Map = new Map() + private webSocketConnections: Map = new Map() + private messageCallbacks: Map void>> = new Map() + private signalServer: WebSocketConnection | null = null + + constructor(name: string = 'webrtc-conduit') { + super(name) + } + + getType(): AugmentationType { + return AugmentationType.CONDUIT + } + + async initialize(): Promise { + if (this.isInitialized) { + return + } + + try { + // Check if WebRTC is available + if (typeof RTCPeerConnection === 'undefined') { + throw new Error('WebRTC is not available in this environment') + } + + this.isInitialized = true + } catch (error) { + console.error(`Failed to initialize ${this.name}:`, error) + throw new Error(`Failed to initialize ${this.name}: ${error}`) + } + } + + /** + * Establishes a connection to another Brainy instance using WebRTC + * @param targetSystemId The peer ID or signal server URL + * @param config Configuration options for the connection + */ + async establishConnection( + targetSystemId: string, + config: Record + ): Promise> { + await this.ensureInitialized() + + try { + // For WebRTC, we need to: + // 1. Connect to a signaling server (if not already connected) + // 2. Create a peer connection + // 3. Create a data channel + // 4. Exchange ICE candidates and SDP offers/answers + + // Check if we need to connect to a signaling server + if (!this.signalServer && config.signalServerUrl) { + // Connect to the signaling server + this.signalServer = await this.connectWebSocket(config.signalServerUrl as string) + + // Set up message handling for the signaling server + await this.onWebSocketMessage(this.signalServer.connectionId, async (data) => { + // Handle signaling messages + const message = data as any + + if (message.type === 'ice-candidate' && message.targetPeerId === config.localPeerId) { + // Add ICE candidate to the appropriate peer connection + const peerConnection = this.peerConnections.get(message.sourcePeerId) + if (peerConnection) { + try { + await peerConnection.addIceCandidate(new RTCIceCandidate(message.candidate)) + } catch (error) { + console.error(`Failed to add ICE candidate:`, error) + } + } + } else if (message.type === 'offer' && message.targetPeerId === config.localPeerId) { + // Handle incoming offer + await this.handleOffer(message.sourcePeerId, message.offer, config) + } else if (message.type === 'answer' && message.targetPeerId === config.localPeerId) { + // Handle incoming answer + const peerConnection = this.peerConnections.get(message.sourcePeerId) + if (peerConnection) { + try { + await peerConnection.setRemoteDescription(new RTCSessionDescription(message.answer)) + } catch (error) { + console.error(`Failed to set remote description:`, error) + } + } + } + }) + } + + // Create a peer connection + const peerConnection = new RTCPeerConnection({ + iceServers: (config.iceServers as RTCIceServer[]) || [ + { urls: 'stun:stun.l.google.com:19302' } + ] + }) + + // Generate a connection ID + const connectionId = uuidv4() + + // Store the peer connection + this.peerConnections.set(targetSystemId, peerConnection) + + // Create a data channel + const dataChannel = peerConnection.createDataChannel('brainy-sync', { + ordered: true + }) + + // Set up data channel event handlers + dataChannel.onopen = () => { + console.log(`Data channel to ${targetSystemId} opened`) + } + + dataChannel.onclose = () => { + console.log(`Data channel to ${targetSystemId} closed`) + // Clean up + this.dataChannels.delete(targetSystemId) + this.peerConnections.delete(targetSystemId) + this.webSocketConnections.delete(connectionId) + this.messageCallbacks.delete(connectionId) + } + + dataChannel.onerror = (error) => { + console.error(`Data channel error:`, error) + } + + // Create a message handler wrapper that will call all registered callbacks + const messageHandlerWrapper = (data: unknown) => { + const callbacks = this.messageCallbacks.get(connectionId) + if (callbacks) { + for (const callback of callbacks) { + try { + callback(data) + } catch (error) { + console.error(`Error in WebRTC message callback:`, error) + } + } + } + } + + dataChannel.onmessage = (event) => { + try { + // Parse the message if it's a string + let data = event.data + if (typeof data === 'string') { + try { + data = JSON.parse(data) + } catch { + // If parsing fails, use the raw string + } + } + + // Call the message handler wrapper + messageHandlerWrapper(data) + } catch (error) { + console.error(`Error handling WebRTC message:`, error) + } + } + + // Store the data channel + this.dataChannels.set(targetSystemId, dataChannel) + + // Set up ICE candidate handling + peerConnection.onicecandidate = (event) => { + if (event.candidate && this.signalServer) { + // Send the ICE candidate to the peer via the signaling server + this.sendWebSocketMessage(this.signalServer.connectionId, { + type: 'ice-candidate', + sourcePeerId: config.localPeerId, + targetPeerId: targetSystemId, + candidate: event.candidate + }) + } + } + + // Create a WebSocket-like connection object for the WebRTC connection + const connection: WebSocketConnection = { + connectionId, + url: `webrtc://${targetSystemId}`, + status: 'disconnected', + send: async (data) => { + const dc = this.dataChannels.get(targetSystemId) + if (!dc || dc.readyState !== 'open') { + throw new Error('WebRTC data channel is not open') + } + + // Send the data + if (typeof data === 'string') { + dc.send(data) + } else if (data instanceof Blob) { + dc.send(data) + } else if (data instanceof ArrayBuffer) { + dc.send(new Uint8Array(data)) + } else if (ArrayBuffer.isView(data)) { + dc.send(data) + } else { + // Convert to JSON string + dc.send(JSON.stringify(data)) + } + }, + close: async () => { + const dc = this.dataChannels.get(targetSystemId) + if (dc) { + dc.close() + } + + const pc = this.peerConnections.get(targetSystemId) + if (pc) { + pc.close() + } + + // Clean up + this.dataChannels.delete(targetSystemId) + this.peerConnections.delete(targetSystemId) + this.webSocketConnections.delete(connectionId) + this.messageCallbacks.delete(connectionId) + }, + _messageHandlerWrapper: messageHandlerWrapper + } + + // Store the connection + this.webSocketConnections.set(connectionId, connection) + + // Initialize the callbacks set + this.messageCallbacks.set(connectionId, new Set()) + + // Create and send an offer + const offer = await peerConnection.createOffer() + await peerConnection.setLocalDescription(offer) + + // Send the offer to the peer via the signaling server + if (this.signalServer) { + await this.sendWebSocketMessage(this.signalServer.connectionId, { + type: 'offer', + sourcePeerId: config.localPeerId, + targetPeerId: targetSystemId, + offer + }) + } + + // Return the connection + return { + success: true, + data: connection + } + } catch (error) { + console.error(`Failed to establish WebRTC connection to ${targetSystemId}:`, error) + return { + success: false, + data: null as any, + error: `Failed to establish WebRTC connection: ${error}` + } + } + } + + /** + * Handles an incoming WebRTC offer + * @param peerId The ID of the peer sending the offer + * @param offer The SDP offer + * @param config Configuration options + */ + private async handleOffer( + peerId: string, + offer: RTCSessionDescriptionInit, + config: Record + ): Promise { + try { + // Create a peer connection if it doesn't exist + let peerConnection = this.peerConnections.get(peerId) + + if (!peerConnection) { + peerConnection = new RTCPeerConnection({ + iceServers: (config.iceServers as RTCIceServer[]) || [ + { urls: 'stun:stun.l.google.com:19302' } + ] + }) + + // Store the peer connection + this.peerConnections.set(peerId, peerConnection) + + // Set up ICE candidate handling + peerConnection.onicecandidate = (event) => { + if (event.candidate && this.signalServer) { + // Send the ICE candidate to the peer via the signaling server + this.sendWebSocketMessage(this.signalServer.connectionId, { + type: 'ice-candidate', + sourcePeerId: config.localPeerId, + targetPeerId: peerId, + candidate: event.candidate + }) + } + } + + // Handle data channel creation by the remote peer + peerConnection.ondatachannel = (event) => { + const dataChannel = event.channel + + // Generate a connection ID + const connectionId = uuidv4() + + // Store the data channel + this.dataChannels.set(peerId, dataChannel) + + // Set up data channel event handlers + dataChannel.onopen = () => { + console.log(`Data channel from ${peerId} opened`) + } + + dataChannel.onclose = () => { + console.log(`Data channel from ${peerId} closed`) + // Clean up + this.dataChannels.delete(peerId) + this.peerConnections.delete(peerId) + this.webSocketConnections.delete(connectionId) + this.messageCallbacks.delete(connectionId) + } + + dataChannel.onerror = (error) => { + console.error(`Data channel error:`, error) + } + + // Create a message handler wrapper that will call all registered callbacks + const messageHandlerWrapper = (data: unknown) => { + const callbacks = this.messageCallbacks.get(connectionId) + if (callbacks) { + for (const callback of callbacks) { + try { + callback(data) + } catch (error) { + console.error(`Error in WebRTC message callback:`, error) + } + } + } + } + + dataChannel.onmessage = (event) => { + try { + // Parse the message if it's a string + let data = event.data + if (typeof data === 'string') { + try { + data = JSON.parse(data) + } catch { + // If parsing fails, use the raw string + } + } + + // Call the message handler wrapper + messageHandlerWrapper(data) + } catch (error) { + console.error(`Error handling WebRTC message:`, error) + } + } + + // Create a WebSocket-like connection object for the WebRTC connection + const connection: WebSocketConnection = { + connectionId, + url: `webrtc://${peerId}`, + status: 'disconnected', + send: async (data) => { + if (dataChannel.readyState !== 'open') { + throw new Error('WebRTC data channel is not open') + } + + // Send the data + if (typeof data === 'string') { + dataChannel.send(data) + } else if (data instanceof Blob) { + dataChannel.send(data) + } else if (data instanceof ArrayBuffer) { + dataChannel.send(new Uint8Array(data)) + } else if (ArrayBuffer.isView(data)) { + dataChannel.send(data) + } else { + // Convert to JSON string + dataChannel.send(JSON.stringify(data)) + } + }, + close: async () => { + dataChannel.close() + + const pc = this.peerConnections.get(peerId) + if (pc) { + pc.close() + } + + // Clean up + this.dataChannels.delete(peerId) + this.peerConnections.delete(peerId) + this.webSocketConnections.delete(connectionId) + this.messageCallbacks.delete(connectionId) + }, + _messageHandlerWrapper: messageHandlerWrapper + } + + // Store the connection + this.webSocketConnections.set(connectionId, connection) + + // Initialize the callbacks set + this.messageCallbacks.set(connectionId, new Set()) + } + } + + // Set the remote description (the offer) + await peerConnection.setRemoteDescription(new RTCSessionDescription(offer)) + + // Create an answer + const answer = await peerConnection.createAnswer() + await peerConnection.setLocalDescription(answer) + + // Send the answer to the peer via the signaling server + if (this.signalServer) { + await this.sendWebSocketMessage(this.signalServer.connectionId, { + type: 'answer', + sourcePeerId: config.localPeerId, + targetPeerId: peerId, + answer + }) + } + } catch (error) { + console.error(`Failed to handle WebRTC offer:`, error) + throw new Error(`Failed to handle WebRTC offer: ${error}`) + } + } + + /** + * Reads data from a connected Brainy instance + * @param query Query parameters for reading data + * @param options Additional options + */ + async readData( + query: Record, + options?: Record + ): Promise> { + await this.ensureInitialized() + + try { + const connectionId = query.connectionId as string + + if (!connectionId) { + throw new Error('connectionId is required for reading data') + } + + const connection = this.webSocketConnections.get(connectionId) + + if (!connection) { + throw new Error(`Connection ${connectionId} not found`) + } + + // Create a request message + const requestMessage = { + type: 'read', + query: query.query || {}, + requestId: uuidv4(), + options + } + + // Send the request + await this.sendWebSocketMessage(connectionId, requestMessage) + + // Return a promise that will be resolved when the response is received + return new Promise((resolve) => { + const responseHandler = (data: unknown) => { + // Check if this is the response to our request + const response = data as any + if (response && response.type === 'readResponse' && response.requestId === requestMessage.requestId) { + // Remove the handler + this.offWebSocketMessage(connectionId, responseHandler) + + // Resolve with the response data + resolve({ + success: response.success, + data: response.data, + error: response.error + }) + } + } + + // Register the response handler + this.onWebSocketMessage(connectionId, responseHandler) + + // Set a timeout to prevent hanging + setTimeout(() => { + this.offWebSocketMessage(connectionId, responseHandler) + resolve({ + success: false, + data: null, + error: 'Timeout waiting for read response' + }) + }, 30000) // 30 second timeout + }) + } catch (error) { + console.error(`Failed to read data:`, error) + return { + success: false, + data: null, + error: `Failed to read data: ${error}` + } + } + } + + /** + * Writes data to a connected Brainy instance + * @param data The data to write + * @param options Additional options + */ + async writeData( + data: Record, + options?: Record + ): Promise> { + await this.ensureInitialized() + + try { + const connectionId = data.connectionId as string + + if (!connectionId) { + throw new Error('connectionId is required for writing data') + } + + const connection = this.webSocketConnections.get(connectionId) + + if (!connection) { + throw new Error(`Connection ${connectionId} not found`) + } + + // Create a write message + const writeMessage = { + type: 'write', + data: data.data || {}, + requestId: uuidv4(), + options + } + + // Send the write message + await this.sendWebSocketMessage(connectionId, writeMessage) + + // Return a promise that will be resolved when the response is received + return new Promise((resolve) => { + const responseHandler = (data: unknown) => { + // Check if this is the response to our request + const response = data as any + if (response && response.type === 'writeResponse' && response.requestId === writeMessage.requestId) { + // Remove the handler + this.offWebSocketMessage(connectionId, responseHandler) + + // Resolve with the response data + resolve({ + success: response.success, + data: response.data, + error: response.error + }) + } + } + + // Register the response handler + this.onWebSocketMessage(connectionId, responseHandler) + + // Set a timeout to prevent hanging + setTimeout(() => { + this.offWebSocketMessage(connectionId, responseHandler) + resolve({ + success: false, + data: null, + error: 'Timeout waiting for write response' + }) + }, 30000) // 30 second timeout + }) + } catch (error) { + console.error(`Failed to write data:`, error) + return { + success: false, + data: null, + error: `Failed to write data: ${error}` + } + } + } + + /** + * Monitors a data stream from a connected Brainy instance + * @param streamId The ID of the stream to monitor (usually a connection ID) + * @param callback Function to call when new data is received + */ + async monitorStream( + streamId: string, + callback: (data: unknown) => void + ): Promise { + await this.ensureInitialized() + + try { + const connection = this.webSocketConnections.get(streamId) + + if (!connection) { + throw new Error(`Connection ${streamId} not found`) + } + + // Register the callback for all messages on this connection + await this.onWebSocketMessage(streamId, callback) + + } catch (error) { + console.error(`Failed to monitor stream ${streamId}:`, error) + throw new Error(`Failed to monitor stream: ${error}`) + } + } + + /** + * Establishes a WebSocket connection (used for signaling in WebRTC) + * @param url The WebSocket server URL to connect to + * @param protocols Optional subprotocols + */ + async connectWebSocket( + url: string, + protocols?: string | string[] + ): Promise { + await this.ensureInitialized() + + return new Promise((resolve, reject) => { + try { + // Check if WebSocket is available + if (typeof WebSocket === 'undefined') { + throw new Error('WebSocket is not available in this environment') + } + + // Create a new WebSocket connection + const ws = new WebSocket(url, protocols) + const connectionId = uuidv4() + + // Create a connection object + const connection: WebSocketConnection = { + connectionId, + url, + status: 'disconnected', + send: async (data) => { + if (ws.readyState !== WebSocket.OPEN) { + throw new Error('WebSocket is not open') + } + ws.send(data) + }, + close: async () => { + ws.close() + } + } + + // Set up event handlers + ws.onopen = () => { + connection.status = 'connected' + resolve(connection) + } + + ws.onerror = (error) => { + connection.status = 'error' + console.error(`WebSocket error for ${url}:`, error) + if (ws.readyState !== WebSocket.OPEN) { + reject(new Error(`WebSocket connection failed: ${error}`)) + } + } + + ws.onclose = () => { + connection.status = 'disconnected' + // Remove from connections map + this.webSocketConnections.delete(connectionId) + // Remove all callbacks + this.messageCallbacks.delete(connectionId) + } + + // Create a message handler wrapper that will call all registered callbacks + const messageHandlerWrapper = (data: unknown) => { + const callbacks = this.messageCallbacks.get(connectionId) + if (callbacks) { + for (const callback of callbacks) { + try { + callback(data) + } catch (error) { + console.error(`Error in WebSocket message callback:`, error) + } + } + } + } + + // Store the message handler wrapper + connection._messageHandlerWrapper = messageHandlerWrapper + + // Set up the message handler + ws.onmessage = (event) => { + try { + // Parse the message if it's a string + let data = event.data + if (typeof data === 'string') { + try { + data = JSON.parse(data) + } catch { + // If parsing fails, use the raw string + } + } + + // Call the message handler wrapper + messageHandlerWrapper(data) + } catch (error) { + console.error(`Error handling WebSocket message:`, error) + } + } + + // Store the stream message handler + connection._streamMessageHandler = (event) => ws.onmessage && ws.onmessage(event as any) + + // Store the connection + this.webSocketConnections.set(connectionId, connection) + + // Initialize the callbacks set + this.messageCallbacks.set(connectionId, new Set()) + + } catch (error) { + reject(error) + } + }) + } + + /** + * Sends data through an established WebSocket or WebRTC connection + * @param connectionId The identifier of the established connection + * @param data The data to send (will be serialized if not a string) + */ + async sendWebSocketMessage( + connectionId: string, + data: unknown + ): Promise { + await this.ensureInitialized() + + const connection = this.webSocketConnections.get(connectionId) + + if (!connection) { + throw new Error(`Connection ${connectionId} not found`) + } + + if (!connection.send) { + throw new Error(`Connection ${connectionId} does not support sending messages`) + } + + // Serialize the data if it's not already a string or binary + let serializedData: string | ArrayBufferLike | Blob | ArrayBufferView + + if (typeof data === 'string' || + data instanceof ArrayBuffer || + data instanceof Blob || + ArrayBuffer.isView(data)) { + serializedData = data as any + } else { + // Convert to JSON string + serializedData = JSON.stringify(data) + } + + // Send the data + await connection.send(serializedData) + } + + /** + * Registers a callback for incoming WebSocket or WebRTC messages + * @param connectionId The identifier of the established connection + * @param callback The function to call when a message is received + */ + async onWebSocketMessage( + connectionId: string, + callback: (data: unknown) => void + ): Promise { + await this.ensureInitialized() + + const connection = this.webSocketConnections.get(connectionId) + + if (!connection) { + throw new Error(`Connection ${connectionId} not found`) + } + + // Get or create the callbacks set for this connection + let callbacks = this.messageCallbacks.get(connectionId) + + if (!callbacks) { + callbacks = new Set() + this.messageCallbacks.set(connectionId, callbacks) + } + + // Add the callback + callbacks.add(callback) + } + + /** + * Removes a callback for incoming WebSocket or WebRTC messages + * @param connectionId The identifier of the established connection + * @param callback The function to remove from the callbacks + */ + async offWebSocketMessage( + connectionId: string, + callback: (data: unknown) => void + ): Promise { + await this.ensureInitialized() + + const callbacks = this.messageCallbacks.get(connectionId) + + if (callbacks) { + callbacks.delete(callback) + } + } + + /** + * Closes an established WebSocket or WebRTC connection + * @param connectionId The identifier of the established connection + * @param code Optional close code + * @param reason Optional close reason + */ + async closeWebSocket( + connectionId: string, + code?: number, + reason?: string + ): Promise { + await this.ensureInitialized() + + const connection = this.webSocketConnections.get(connectionId) + + if (!connection) { + throw new Error(`Connection ${connectionId} not found`) + } + + if (!connection.close) { + throw new Error(`Connection ${connectionId} does not support closing`) + } + + // Close the connection + await connection.close() + + // Remove from connections map + this.webSocketConnections.delete(connectionId) + + // Remove all callbacks + this.messageCallbacks.delete(connectionId) + } +} + +/** + * Factory function to create the appropriate conduit augmentation based on the type + */ +export async function createConduitAugmentation( + type: 'websocket' | 'webrtc', + name?: string, + options: Record = {} +): Promise { + switch (type) { + case 'websocket': + const wsAugmentation = new WebSocketConduitAugmentation(name || 'websocket-conduit') + await wsAugmentation.initialize() + return wsAugmentation + case 'webrtc': + const webrtcAugmentation = new WebRTCConduitAugmentation(name || 'webrtc-conduit') + await webrtcAugmentation.initialize() + return webrtcAugmentation + default: + throw new Error(`Unknown conduit augmentation type: ${type}`) + } +} diff --git a/src/augmentations/memoryAugmentations.ts b/src/augmentations/memoryAugmentations.ts new file mode 100644 index 00000000..a23f7c6e --- /dev/null +++ b/src/augmentations/memoryAugmentations.ts @@ -0,0 +1,333 @@ +import { + AugmentationType, + IMemoryAugmentation, + AugmentationResponse +} from '../types/augmentations.js' +import { StorageAdapter, Vector } from '../coreTypes.js' +import { MemoryStorage } from '../storage/opfsStorage.js' +import { FileSystemStorage } from '../storage/fileSystemStorage.js' +import { OPFSStorage } from '../storage/opfsStorage.js' +import { cosineDistance } from '../utils/distance.js' + +/** + * Base class for memory augmentations that wrap a StorageAdapter + */ +abstract class BaseMemoryAugmentation implements IMemoryAugmentation { + readonly name: string + readonly description: string = 'Base memory augmentation' + enabled: boolean = true + protected storage: StorageAdapter + protected isInitialized = false + + constructor(name: string, storage: StorageAdapter) { + this.name = name + this.storage = storage + } + + async initialize(): Promise { + if (this.isInitialized) { + return + } + + try { + await this.storage.init() + this.isInitialized = true + } catch (error) { + console.error(`Failed to initialize ${this.name}:`, error) + throw new Error(`Failed to initialize ${this.name}: ${error}`) + } + } + + async shutDown(): Promise { + this.isInitialized = false + } + + async getStatus(): Promise<'active' | 'inactive' | 'error'> { + return this.isInitialized ? 'active' : 'inactive' + } + + async storeData( + key: string, + data: unknown, + options?: Record + ): Promise> { + await this.ensureInitialized() + + try { + await this.storage.saveMetadata(key, data) + return { success: true, data: true } + } catch (error) { + console.error(`Failed to store data for key ${key}:`, error) + return { + success: false, + data: false, + error: `Failed to store data: ${error}` + } + } + } + + async retrieveData( + key: string, + options?: Record + ): Promise> { + await this.ensureInitialized() + + try { + const data = await this.storage.getMetadata(key) + return { + success: true, + data + } + } catch (error) { + console.error(`Failed to retrieve data for key ${key}:`, error) + return { + success: false, + data: null, + error: `Failed to retrieve data: ${error}` + } + } + } + + async updateData( + key: string, + data: unknown, + options?: Record + ): Promise> { + await this.ensureInitialized() + + try { + await this.storage.saveMetadata(key, data) + return { success: true, data: true } + } catch (error) { + console.error(`Failed to update data for key ${key}:`, error) + return { + success: false, + data: false, + error: `Failed to update data: ${error}` + } + } + } + + async deleteData( + key: string, + options?: Record + ): Promise> { + await this.ensureInitialized() + + try { + // There's no direct deleteMetadata method, so we save null + await this.storage.saveMetadata(key, null) + return { success: true, data: true } + } catch (error) { + console.error(`Failed to delete data for key ${key}:`, error) + return { + success: false, + data: false, + error: `Failed to delete data: ${error}` + } + } + } + + async listDataKeys( + pattern?: string, + options?: Record + ): Promise> { + // This is a limitation of the current StorageAdapter interface + // It doesn't provide a way to list all metadata keys + // We could implement this in the future by extending the StorageAdapter interface + return { + success: false, + data: [], + error: 'listDataKeys is not supported by this storage adapter' + } + } + + /** + * Searches for data in the storage using vector similarity. + * Implements the findNearest functionality by calculating distances client-side. + * @param query The query vector or data to search for + * @param k Number of results to return (default: 10) + * @param options Optional search options + */ + async search( + query: unknown, + k: number = 10, + options?: Record + ): Promise>> { + await this.ensureInitialized() + + try { + // Check if query is a vector + let queryVector: Vector + + if (Array.isArray(query) && query.every(item => typeof item === 'number')) { + queryVector = query as Vector + } else { + // If query is not a vector, we can't perform vector search + return { + success: false, + data: [], + error: 'Query must be a vector (array of numbers) for vector search' + } + } + + // Get all nodes from storage + const nodes = await this.storage.getAllNouns() + + // Calculate distances and prepare results + const results: Array<{ + id: string; + score: number; + data: unknown; + }> = [] + + for (const node of nodes) { + // Skip nodes that don't have a vector + if (!node.vector || !Array.isArray(node.vector)) { + continue + } + + // Get metadata for the node + const metadata = await this.storage.getMetadata(node.id) + + // Calculate distance between query vector and node vector + const distance = cosineDistance(queryVector, node.vector) + + // Convert distance to similarity score (1 - distance for cosine) + // This way higher scores are better (more similar) + const score = 1 - distance + + results.push({ + id: node.id, + score, + data: metadata + }) + } + + // Sort results by score (descending) and take top k + results.sort((a, b) => b.score - a.score) + const topResults = results.slice(0, k) + + return { + success: true, + data: topResults + } + } catch (error) { + console.error(`Failed to search in storage:`, error) + return { + success: false, + data: [], + error: `Failed to search in storage: ${error}` + } + } + } + + protected async ensureInitialized(): Promise { + if (!this.isInitialized) { + await this.initialize() + } + } +} + +/** + * Memory augmentation that uses in-memory storage + */ +export class MemoryStorageAugmentation extends BaseMemoryAugmentation { + readonly description = 'Memory augmentation that stores data in memory' + enabled = true + + constructor(name: string) { + super(name, new MemoryStorage()) + } + + getType(): AugmentationType { + return AugmentationType.MEMORY + } +} + +/** + * Memory augmentation that uses file system storage + */ +export class FileSystemStorageAugmentation extends BaseMemoryAugmentation { + readonly description = 'Memory augmentation that stores data in the file system' + enabled = true + + constructor(name: string, rootDirectory?: string) { + super(name, new FileSystemStorage(rootDirectory)) + } + + getType(): AugmentationType { + return AugmentationType.MEMORY + } +} + +/** + * Memory augmentation that uses OPFS (Origin Private File System) storage + */ +export class OPFSStorageAugmentation extends BaseMemoryAugmentation { + readonly description = 'Memory augmentation that stores data in the Origin Private File System' + enabled = true + + constructor(name: string) { + super(name, new OPFSStorage()) + } + + getType(): AugmentationType { + return AugmentationType.MEMORY + } +} + +/** + * Factory function to create the appropriate memory augmentation based on the environment + */ +export async function createMemoryAugmentation( + name: string, + options: { + storageType?: 'memory' | 'filesystem' | 'opfs' + rootDirectory?: string + requestPersistentStorage?: boolean + } = {} +): Promise { + // If a specific storage type is requested, use that + if (options.storageType) { + switch (options.storageType) { + case 'memory': + return new MemoryStorageAugmentation(name) + case 'filesystem': + return new FileSystemStorageAugmentation(name, options.rootDirectory) + case 'opfs': + return new OPFSStorageAugmentation(name) + } + } + + // Otherwise, select based on environment + // Use the global isNode variable from the environment detection + const isNodeEnv = globalThis.__ENV__?.isNode || ( + typeof process !== 'undefined' && + process.versions != null && + process.versions.node != null + ) + + if (isNodeEnv) { + // In Node.js, use FileSystemStorage + return new FileSystemStorageAugmentation(name, options.rootDirectory) + } else { + // In browser, try OPFS first + const opfsStorage = new OPFSStorage() + + if (opfsStorage.isOPFSAvailable()) { + // Request persistent storage if specified + if (options.requestPersistentStorage) { + await opfsStorage.requestPersistentStorage() + } + return new OPFSStorageAugmentation(name) + } else { + // Fall back to memory storage + return new MemoryStorageAugmentation(name) + } + } +} diff --git a/src/augmentations/serverSearchAugmentations.ts b/src/augmentations/serverSearchAugmentations.ts new file mode 100644 index 00000000..93d593e3 --- /dev/null +++ b/src/augmentations/serverSearchAugmentations.ts @@ -0,0 +1,665 @@ +/** + * Server Search Augmentations + * + * This file implements conduit and activation augmentations for browser-server search functionality. + * It allows Brainy to search a server-hosted instance and store results locally. + */ + +import { + AugmentationType, + IConduitAugmentation, + IActivationAugmentation, + IWebSocketSupport, + AugmentationResponse, + WebSocketConnection +} from '../types/augmentations.js' +import { WebSocketConduitAugmentation } from './conduitAugmentations.js' +import { v4 as uuidv4 } from 'uuid' +import { BrainyDataInterface } from '../types/brainyDataInterface.js' + +/** + * ServerSearchConduitAugmentation + * + * A specialized conduit augmentation that provides functionality for searching + * a server-hosted Brainy instance and storing results locally. + */ +export class ServerSearchConduitAugmentation extends WebSocketConduitAugmentation { + private localDb: BrainyDataInterface | null = null + + constructor(name: string = 'server-search-conduit') { + super(name) + // this.description = 'Conduit augmentation for server-hosted Brainy search' + } + + /** + * Initialize the augmentation + */ + async initialize(): Promise { + if (this.isInitialized) { + return + } + + try { + // Initialize the base conduit + await super.initialize() + + // Local DB must be set before initialization + if (!this.localDb) { + throw new Error('Local database not set. Call setLocalDb before initializing.') + } + + this.isInitialized = true + } catch (error) { + console.error(`Failed to initialize ${this.name}:`, error) + throw new Error(`Failed to initialize ${this.name}: ${error}`) + } + } + + /** + * Set the local Brainy instance + * @param db The Brainy instance to use for local storage + */ + setLocalDb(db: BrainyDataInterface): void { + this.localDb = db + } + + /** + * Get the local Brainy instance + * @returns The local Brainy instance + */ + getLocalDb(): BrainyDataInterface | null { + return this.localDb + } + + /** + * Search the server-hosted Brainy instance and store results locally + * @param connectionId The ID of the established connection + * @param query The search query + * @param limit Maximum number of results to return + * @returns Search results + */ + async searchServer( + connectionId: string, + query: string, + limit: number = 10 + ): Promise> { + await this.ensureInitialized() + + try { + // Create a search request + const readResult = await this.readData({ + connectionId, + query: { + type: 'search', + query, + limit + } + }) + + if (readResult.success && readResult.data) { + const searchResults = readResult.data as any[] + + // Store the results in the local Brainy instance + if (this.localDb) { + for (const result of searchResults) { + // Check if the noun already exists in the local database + const existingNoun = await this.localDb.get(result.id) + + if (!existingNoun) { + // Add the noun to the local database + await this.localDb.add(result.vector, result.metadata) + } + } + } + + return { + success: true, + data: searchResults + } + } else { + return { + success: false, + data: null, + error: readResult.error || 'Unknown error searching server' + } + } + } catch (error) { + console.error('Error searching server:', error) + return { + success: false, + data: null, + error: `Error searching server: ${error}` + } + } + } + + /** + * Search the local Brainy instance + * @param query The search query + * @param limit Maximum number of results to return + * @returns Search results + */ + async searchLocal( + query: string, + limit: number = 10 + ): Promise> { + await this.ensureInitialized() + + try { + if (!this.localDb) { + return { + success: false, + data: null, + error: 'Local database not initialized' + } + } + + const results = await this.localDb.searchText(query, limit) + + return { + success: true, + data: results + } + } catch (error) { + console.error('Error searching local database:', error) + return { + success: false, + data: null, + error: `Error searching local database: ${error}` + } + } + } + + /** + * Search both server and local instances, combine results, and store server results locally + * @param connectionId The ID of the established connection + * @param query The search query + * @param limit Maximum number of results to return + * @returns Combined search results + */ + async searchCombined( + connectionId: string, + query: string, + limit: number = 10 + ): Promise> { + await this.ensureInitialized() + + try { + // Search local first + const localSearchResult = await this.searchLocal(query, limit) + + if (!localSearchResult.success) { + return localSearchResult + } + + const localResults = localSearchResult.data as any[] + + // If we have enough local results, return them + if (localResults.length >= limit) { + return localSearchResult + } + + // Otherwise, search server for additional results + const serverSearchResult = await this.searchServer( + connectionId, + query, + limit - localResults.length + ) + + if (!serverSearchResult.success) { + // If server search fails, return local results + return localSearchResult + } + + const serverResults = serverSearchResult.data as any[] + + // Combine results, removing duplicates + const combinedResults = [...localResults] + const localIds = new Set(localResults.map(r => r.id)) + + for (const result of serverResults) { + if (!localIds.has(result.id)) { + combinedResults.push(result) + } + } + + return { + success: true, + data: combinedResults + } + } catch (error) { + console.error('Error performing combined search:', error) + return { + success: false, + data: null, + error: `Error performing combined search: ${error}` + } + } + } + + /** + * Add data to both local and server instances + * @param connectionId The ID of the established connection + * @param data Text or vector to add + * @param metadata Metadata for the data + * @returns ID of the added data + */ + async addToBoth( + connectionId: string, + data: string | any[], + metadata: any = {} + ): Promise> { + await this.ensureInitialized() + + try { + if (!this.localDb) { + return { + success: false, + data: '', + error: 'Local database not initialized' + } + } + + // Add to local first + const id = await this.localDb.add(data, metadata) + + // Get the vector and metadata + const noun = await this.localDb.get(id) as import('../coreTypes.js').VectorDocument + + if (!noun) { + return { + success: false, + data: '', + error: 'Failed to retrieve newly created noun' + } + } + + // Add to server + const writeResult = await this.writeData({ + connectionId, + data: { + type: 'addNoun', + vector: noun.vector, + metadata: noun.metadata + } + }) + + if (!writeResult.success) { + return { + success: true, + data: id, + error: `Added locally but failed to add to server: ${writeResult.error}` + } + } + + return { + success: true, + data: id + } + } catch (error) { + console.error('Error adding data to both:', error) + return { + success: false, + data: '', + error: `Error adding data to both: ${error}` + } + } + } +} + +/** + * ServerSearchActivationAugmentation + * + * An activation augmentation that provides actions for server search functionality. + */ +export class ServerSearchActivationAugmentation implements IActivationAugmentation { + readonly name: string + readonly description: string + enabled: boolean = true + private isInitialized = false + private conduitAugmentation: ServerSearchConduitAugmentation | null = null + private connections: Map = new Map() + + constructor(name: string = 'server-search-activation') { + this.name = name + this.description = 'Activation augmentation for server-hosted Brainy search' + } + + getType(): AugmentationType { + return AugmentationType.ACTIVATION + } + + /** + * Initialize the augmentation + */ + async initialize(): Promise { + if (this.isInitialized) { + return + } + + this.isInitialized = true + } + + /** + * Shut down the augmentation + */ + async shutDown(): Promise { + this.isInitialized = false + } + + /** + * Get the status of the augmentation + */ + async getStatus(): Promise<'active' | 'inactive' | 'error'> { + return this.isInitialized ? 'active' : 'inactive' + } + + /** + * Set the conduit augmentation to use for server search + * @param conduit The ServerSearchConduitAugmentation to use + */ + setConduitAugmentation(conduit: ServerSearchConduitAugmentation): void { + this.conduitAugmentation = conduit + } + + /** + * Store a connection for later use + * @param connectionId The ID to use for the connection + * @param connection The WebSocket connection + */ + storeConnection(connectionId: string, connection: WebSocketConnection): void { + this.connections.set(connectionId, connection) + } + + /** + * Get a stored connection + * @param connectionId The ID of the connection to retrieve + * @returns The WebSocket connection + */ + getConnection(connectionId: string): WebSocketConnection | undefined { + return this.connections.get(connectionId) + } + + /** + * Trigger an action based on a processed command or internal state + * @param actionName The name of the action to trigger + * @param parameters Optional parameters for the action + */ + triggerAction( + actionName: string, + parameters?: Record + ): AugmentationResponse { + if (!this.conduitAugmentation) { + return { + success: false, + data: null, + error: 'Conduit augmentation not set' + } + } + + // Handle different actions + switch (actionName) { + case 'connectToServer': + return this.handleConnectToServer(parameters || {}) + case 'searchServer': + return this.handleSearchServer(parameters || {}) + case 'searchLocal': + return this.handleSearchLocal(parameters || {}) + case 'searchCombined': + return this.handleSearchCombined(parameters || {}) + case 'addToBoth': + return this.handleAddToBoth(parameters || {}) + default: + return { + success: false, + data: null, + error: `Unknown action: ${actionName}` + } + } + } + + /** + * Handle the connectToServer action + * @param parameters Action parameters + */ + private handleConnectToServer( + parameters: Record + ): AugmentationResponse { + const serverUrl = parameters.serverUrl as string + const protocols = parameters.protocols as string | string[] | undefined + + if (!serverUrl) { + return { + success: false, + data: null, + error: 'serverUrl parameter is required' + } + } + + // Return a promise that will be resolved when the connection is established + return { + success: true, + data: this.conduitAugmentation!.establishConnection(serverUrl, { + protocols + }) + } + } + + /** + * Handle the searchServer action + * @param parameters Action parameters + */ + private handleSearchServer( + parameters: Record + ): AugmentationResponse { + const connectionId = parameters.connectionId as string + const query = parameters.query as string + const limit = parameters.limit as number || 10 + + if (!connectionId) { + return { + success: false, + data: null, + error: 'connectionId parameter is required' + } + } + + if (!query) { + return { + success: false, + data: null, + error: 'query parameter is required' + } + } + + // Return a promise that will be resolved when the search is complete + return { + success: true, + data: this.conduitAugmentation!.searchServer(connectionId, query, limit) + } + } + + /** + * Handle the searchLocal action + * @param parameters Action parameters + */ + private handleSearchLocal( + parameters: Record + ): AugmentationResponse { + const query = parameters.query as string + const limit = parameters.limit as number || 10 + + if (!query) { + return { + success: false, + data: null, + error: 'query parameter is required' + } + } + + // Return a promise that will be resolved when the search is complete + return { + success: true, + data: this.conduitAugmentation!.searchLocal(query, limit) + } + } + + /** + * Handle the searchCombined action + * @param parameters Action parameters + */ + private handleSearchCombined( + parameters: Record + ): AugmentationResponse { + const connectionId = parameters.connectionId as string + const query = parameters.query as string + const limit = parameters.limit as number || 10 + + if (!connectionId) { + return { + success: false, + data: null, + error: 'connectionId parameter is required' + } + } + + if (!query) { + return { + success: false, + data: null, + error: 'query parameter is required' + } + } + + // Return a promise that will be resolved when the search is complete + return { + success: true, + data: this.conduitAugmentation!.searchCombined(connectionId, query, limit) + } + } + + /** + * Handle the addToBoth action + * @param parameters Action parameters + */ + private handleAddToBoth( + parameters: Record + ): AugmentationResponse { + const connectionId = parameters.connectionId as string + const data = parameters.data + const metadata = parameters.metadata || {} + + if (!connectionId) { + return { + success: false, + data: null, + error: 'connectionId parameter is required' + } + } + + if (!data) { + return { + success: false, + data: null, + error: 'data parameter is required' + } + } + + // Return a promise that will be resolved when the add is complete + return { + success: true, + data: this.conduitAugmentation!.addToBoth(connectionId, data as any, metadata as any) + } + } + + /** + * Generates an expressive output or response from Brainy + * @param knowledgeId The identifier of the knowledge to express + * @param format The desired output format (e.g., 'text', 'json') + */ + generateOutput( + knowledgeId: string, + format: string + ): AugmentationResponse> { + // This method is not used for server search functionality + return { + success: false, + data: '', + error: 'generateOutput is not implemented for ServerSearchActivationAugmentation' + } + } + + /** + * Interacts with an external system or API + * @param systemId The identifier of the external system + * @param payload The data to send to the external system + */ + interactExternal( + systemId: string, + payload: Record + ): AugmentationResponse { + // This method is not used for server search functionality + return { + success: false, + data: null, + error: 'interactExternal is not implemented for ServerSearchActivationAugmentation' + } + } +} + +/** + * Factory function to create server search augmentations + * @param serverUrl The URL of the server to connect to + * @param options Additional options + * @returns An object containing the created augmentations + */ +export async function createServerSearchAugmentations( + serverUrl: string, + options: { + conduitName?: string, + activationName?: string, + protocols?: string | string[], + localDb?: BrainyDataInterface + } = {} +): Promise<{ + conduit: ServerSearchConduitAugmentation, + activation: ServerSearchActivationAugmentation, + connection: WebSocketConnection +}> { + // Create the conduit augmentation + const conduit = new ServerSearchConduitAugmentation(options.conduitName) + await conduit.initialize() + + // Set the local database if provided + if (options.localDb) { + conduit.setLocalDb(options.localDb) + } + + // Create the activation augmentation + const activation = new ServerSearchActivationAugmentation(options.activationName) + await activation.initialize() + + // Link the augmentations + activation.setConduitAugmentation(conduit) + + // Connect to the server + const connectionResult = await conduit.establishConnection( + serverUrl, + { protocols: options.protocols } + ) + + if (!connectionResult.success || !connectionResult.data) { + throw new Error(`Failed to connect to server: ${connectionResult.error}`) + } + + const connection = connectionResult.data + + // Store the connection in the activation augmentation + activation.storeConnection(connection.connectionId, connection) + + return { + conduit, + activation, + connection + } +} diff --git a/src/brainyData.ts b/src/brainyData.ts new file mode 100644 index 00000000..348f1e78 --- /dev/null +++ b/src/brainyData.ts @@ -0,0 +1,2252 @@ +/** + * BrainyData + * Main class that provides the vector database functionality + */ + +import { v4 as uuidv4 } from 'uuid' +import { HNSWIndex } from './hnsw/hnswIndex.js' +import { HNSWIndexOptimized, HNSWOptimizedConfig } from './hnsw/hnswIndexOptimized.js' +import { createStorage } from './storage/opfsStorage.js' +import { + DistanceFunction, + GraphVerb, + EmbeddingFunction, + HNSWConfig, + HNSWNoun, + SearchResult, + StorageAdapter, + Vector, + VectorDocument +} from './coreTypes.js' +import { + cosineDistance, + defaultEmbeddingFunction, + euclideanDistance +} from './utils/index.js' +import { NounType, VerbType, GraphNoun } from './types/graphTypes.js' +import { + ServerSearchConduitAugmentation, + createServerSearchAugmentations +} from './augmentations/serverSearchAugmentations.js' +import { WebSocketConnection } from './types/augmentations.js' +import { BrainyDataInterface } from './types/brainyDataInterface.js' + +export interface BrainyDataConfig { + /** + * HNSW index configuration + */ + hnsw?: Partial + + /** + * Optimized HNSW index configuration + * If provided, will use the optimized HNSW index instead of the standard one + */ + hnswOptimized?: Partial + + /** + * Distance function to use for similarity calculations + */ + distanceFunction?: DistanceFunction + + /** + * Custom storage adapter (if not provided, will use OPFS or memory storage) + */ + storageAdapter?: StorageAdapter + + /** + * Storage configuration options + * These will be passed to createStorage if storageAdapter is not provided + */ + storage?: { + requestPersistentStorage?: boolean + r2Storage?: { + bucketName?: string + accountId?: string + accessKeyId?: string + secretAccessKey?: string + } + s3Storage?: { + bucketName?: string + accessKeyId?: string + secretAccessKey?: string + region?: string + } + gcsStorage?: { + bucketName?: string + accessKeyId?: string + secretAccessKey?: string + endpoint?: string + } + customS3Storage?: { + bucketName?: string + accessKeyId?: string + secretAccessKey?: string + endpoint?: string + region?: string + } + forceFileSystemStorage?: boolean + forceMemoryStorage?: boolean + } + + /** + * Embedding function to convert data to vectors + */ + embeddingFunction?: EmbeddingFunction + + + /** + * Set the database to read-only mode + * When true, all write operations will throw an error + */ + readOnly?: boolean + + /** + * Remote server configuration for search operations + */ + remoteServer?: { + /** + * WebSocket URL of the remote Brainy server + */ + url: string + + /** + * WebSocket protocols to use for the connection + */ + protocols?: string | string[] + + /** + * Whether to automatically connect to the remote server on initialization + */ + autoConnect?: boolean + } +} + +export class BrainyData implements BrainyDataInterface { + private index: HNSWIndex | HNSWIndexOptimized + private storage: StorageAdapter | null = null + private isInitialized = false + private embeddingFunction: EmbeddingFunction + private distanceFunction: DistanceFunction + private requestPersistentStorage: boolean + private readOnly: boolean + private storageConfig: BrainyDataConfig['storage'] = {} + private useOptimizedIndex: boolean = false + + // Remote server properties + private remoteServerConfig: BrainyDataConfig['remoteServer'] | null = null + private serverSearchConduit: ServerSearchConduitAugmentation | null = null + private serverConnection: WebSocketConnection | null = null + + /** + * Create a new vector database + */ + constructor(config: BrainyDataConfig = {}) { + // Set distance function + this.distanceFunction = config.distanceFunction || cosineDistance + + // Check if optimized HNSW index configuration is provided + if (config.hnswOptimized) { + // Initialize optimized HNSW index + this.index = new HNSWIndexOptimized( + config.hnswOptimized, + this.distanceFunction, + config.storageAdapter || null + ) + this.useOptimizedIndex = true + } else { + // Initialize standard HNSW index + this.index = new HNSWIndex(config.hnsw, this.distanceFunction) + } + + // Set storage if provided, otherwise it will be initialized in init() + this.storage = config.storageAdapter || null + + // Set embedding function if provided, otherwise use default + this.embeddingFunction = + config.embeddingFunction || defaultEmbeddingFunction + + // Set persistent storage request flag + this.requestPersistentStorage = config.storage?.requestPersistentStorage || false + + // Set read-only flag + this.readOnly = config.readOnly || false + + // Store storage configuration for later use in init() + this.storageConfig = config.storage || {} + + // Store remote server configuration if provided + if (config.remoteServer) { + this.remoteServerConfig = config.remoteServer + } + } + + /** + * Check if the database is in read-only mode and throw an error if it is + * @throws Error if the database is in read-only mode + */ + private checkReadOnly(): void { + if (this.readOnly) { + throw new Error( + 'Cannot perform write operation: database is in read-only mode' + ) + } + } + + /** + * Initialize the database + * Loads existing data from storage if available + */ + public async init(): Promise { + if (this.isInitialized) { + return + } + + try { + // Initialize storage if not provided in constructor + if (!this.storage) { + // Combine storage config with requestPersistentStorage for backward compatibility + const storageOptions = { + ...this.storageConfig, + requestPersistentStorage: this.requestPersistentStorage + } + + this.storage = await createStorage(storageOptions) + } + + // Initialize storage + await this.storage!.init() + + // If using optimized index, set the storage adapter + if (this.useOptimizedIndex && this.index instanceof HNSWIndexOptimized) { + this.index.setStorage(this.storage!) + } + + // Load all nouns from storage + const nouns: HNSWNoun[] = await this.storage!.getAllNouns() + + // Clear the index and add all nouns + this.index.clear() + for (const noun of nouns) { + // Add to index + await this.index.addItem({ + id: noun.id, + vector: noun.vector + }) + } + + // Connect to remote server if configured with autoConnect + if (this.remoteServerConfig && this.remoteServerConfig.autoConnect) { + try { + await this.connectToRemoteServer( + this.remoteServerConfig.url, + this.remoteServerConfig.protocols + ) + } catch (remoteError) { + console.warn('Failed to auto-connect to remote server:', remoteError) + // Continue initialization even if remote connection fails + } + } + + this.isInitialized = true + } catch (error) { + console.error('Failed to initialize BrainyData:', error) + throw new Error(`Failed to initialize BrainyData: ${error}`) + } + } + + /** + * Connect to a remote Brainy server for search operations + * @param serverUrl WebSocket URL of the remote Brainy server + * @param protocols Optional WebSocket protocols to use + * @returns The connection object + */ + public async connectToRemoteServer( + serverUrl: string, + protocols?: string | string[] + ): Promise { + await this.ensureInitialized() + + try { + // Create server search augmentations + const { conduit, connection } = await createServerSearchAugmentations( + serverUrl, + { + protocols, + localDb: this + } + ) + + // Store the conduit and connection + this.serverSearchConduit = conduit + this.serverConnection = connection + + return connection + } catch (error) { + console.error('Failed to connect to remote server:', error) + throw new Error(`Failed to connect to remote server: ${error}`) + } + } + + /** + * Add a vector or data to the database + * If the input is not a vector, it will be converted using the embedding function + * @param vectorOrData Vector or data to add + * @param metadata Optional metadata to associate with the vector + * @param options Additional options + * @returns The ID of the added vector + */ + public async add( + vectorOrData: Vector | any, + metadata?: T, + options: { + forceEmbed?: boolean // Force using the embedding function even if input is a vector + addToRemote?: boolean // Whether to also add to the remote server if connected + id?: string // Optional ID to use instead of generating a new one + } = {} + ): Promise { + await this.ensureInitialized() + + // Check if database is in read-only mode + this.checkReadOnly() + + try { + let vector: Vector + + // Check if input is already a vector + if ( + Array.isArray(vectorOrData) && + vectorOrData.every((item) => typeof item === 'number') && + !options.forceEmbed + ) { + // Input is already a vector + vector = vectorOrData + } else { + // Input needs to be vectorized + try { + vector = await this.embeddingFunction(vectorOrData) + } catch (embedError) { + throw new Error(`Failed to vectorize data: ${embedError}`) + } + } + + // Check if vector is defined + if (!vector) { + throw new Error('Vector is undefined or null') + } + + // Use ID from options if it exists, otherwise from metadata, otherwise generate a new UUID + const id = + options.id || + (metadata && typeof metadata === 'object' && 'id' in metadata + ? (metadata as any).id + : uuidv4()) + + // Add to index + await this.index.addItem({ id, vector }) + + // Get the noun from the index + const noun = this.index.getNouns().get(id) + + if (!noun) { + throw new Error(`Failed to retrieve newly created noun with ID ${id}`) + } + + // Save noun to storage + await this.storage!.saveNoun(noun) + + // Save metadata if provided + if (metadata !== undefined) { + // Validate noun type if metadata is for a GraphNoun + if (metadata && typeof metadata === 'object' && 'noun' in metadata) { + const nounType = (metadata as unknown as GraphNoun).noun + + // Check if the noun type is valid + const isValidNounType = Object.values(NounType).includes(nounType) + + if (!isValidNounType) { + console.warn( + `Invalid noun type: ${nounType}. Falling back to GraphNoun.` + ) + // Set a default noun type + ;(metadata as unknown as GraphNoun).noun = NounType.Concept + } + } + + await this.storage!.saveMetadata(id, metadata) + } + + // If addToRemote is true and we're connected to a remote server, add to remote as well + if (options.addToRemote && this.isConnectedToRemoteServer()) { + try { + await this.addToRemote(id, vector, metadata) + } catch (remoteError) { + console.warn( + `Failed to add to remote server: ${remoteError}. Continuing with local add.` + ) + } + } + + return id + } catch (error) { + console.error('Failed to add vector:', error) + throw new Error(`Failed to add vector: ${error}`) + } + } + + /** + * Add data to both local and remote Brainy instances + * @param vectorOrData Vector or data to add + * @param metadata Optional metadata to associate with the vector + * @param options Additional options + * @returns The ID of the added vector + */ + public async addToBoth( + vectorOrData: Vector | any, + metadata?: T, + options: { + forceEmbed?: boolean // Force using the embedding function even if input is a vector + } = {} + ): Promise { + // Check if connected to a remote server + if (!this.isConnectedToRemoteServer()) { + throw new Error( + 'Not connected to a remote server. Call connectToRemoteServer() first.' + ) + } + + // Add to local with addToRemote option + return this.add(vectorOrData, metadata, { ...options, addToRemote: true }) + } + + /** + * Add a vector to the remote server + * @param id ID of the vector to add + * @param vector Vector to add + * @param metadata Optional metadata to associate with the vector + * @returns True if successful, false otherwise + * @private + */ + private async addToRemote( + id: string, + vector: Vector, + metadata?: T + ): Promise { + if (!this.isConnectedToRemoteServer()) { + return false + } + + try { + if (!this.serverSearchConduit || !this.serverConnection) { + throw new Error( + 'Server search conduit or connection is not initialized' + ) + } + + // Add to remote server + const addResult = await this.serverSearchConduit.addToBoth( + this.serverConnection.connectionId, + vector, + metadata + ) + + if (!addResult.success) { + throw new Error(`Remote add failed: ${addResult.error}`) + } + + return true + } catch (error) { + console.error('Failed to add to remote server:', error) + throw new Error(`Failed to add to remote server: ${error}`) + } + } + + /** + * Add multiple vectors or data items to the database + * @param items Array of items to add + * @param options Additional options + * @returns Array of IDs for the added items + */ + public async addBatch( + items: Array<{ + vectorOrData: Vector | any + metadata?: T + }>, + options: { + forceEmbed?: boolean // Force using the embedding function even if input is a vector + addToRemote?: boolean // Whether to also add to the remote server if connected + concurrency?: number // Maximum number of concurrent operations (default: 4) + } = {} + ): Promise { + await this.ensureInitialized() + + // Check if database is in read-only mode + this.checkReadOnly() + + // Default concurrency to 4 if not specified + const concurrency = options.concurrency || 4 + + try { + // Process items in batches to control concurrency + const ids: string[] = [] + const itemsToProcess = [...items] // Create a copy to avoid modifying the original array + + while (itemsToProcess.length > 0) { + // Take up to 'concurrency' items to process in parallel + const batch = itemsToProcess.splice(0, concurrency) + + // Process this batch in parallel + const batchResults = await Promise.all( + batch.map(item => this.add(item.vectorOrData, item.metadata, options)) + ) + + // Add the results to our ids array + ids.push(...batchResults) + } + + return ids + } catch (error) { + console.error('Failed to add batch of items:', error) + throw new Error(`Failed to add batch of items: ${error}`) + } + } + + /** + * Add multiple vectors or data items to both local and remote databases + * @param items Array of items to add + * @param options Additional options + * @returns Array of IDs for the added items + */ + public async addBatchToBoth( + items: Array<{ + vectorOrData: Vector | any + metadata?: T + }>, + options: { + forceEmbed?: boolean // Force using the embedding function even if input is a vector + concurrency?: number // Maximum number of concurrent operations (default: 4) + } = {} + ): Promise { + // Check if connected to a remote server + if (!this.isConnectedToRemoteServer()) { + throw new Error( + 'Not connected to a remote server. Call connectToRemoteServer() first.' + ) + } + + // Add to local with addToRemote option + return this.addBatch(items, { ...options, addToRemote: true }) + } + + /** + * Search for similar vectors within specific noun types + * @param queryVectorOrData Query vector or data to search for + * @param k Number of results to return + * @param nounTypes Array of noun types to search within, or null to search all + * @param options Additional options + * @returns Array of search results + */ + public async searchByNounTypes( + queryVectorOrData: Vector | any, + k: number = 10, + nounTypes: string[] | null = null, + options: { + forceEmbed?: boolean // Force using the embedding function even if input is a vector + } = {} + ): Promise[]> { + await this.ensureInitialized() + + try { + let queryVector: Vector + + // Check if input is already a vector + if ( + Array.isArray(queryVectorOrData) && + queryVectorOrData.every((item) => typeof item === 'number') && + !options.forceEmbed + ) { + // Input is already a vector + queryVector = queryVectorOrData + } else { + // Input needs to be vectorized + try { + queryVector = await this.embeddingFunction(queryVectorOrData) + } catch (embedError) { + throw new Error(`Failed to vectorize query data: ${embedError}`) + } + } + + // Check if query vector is defined + if (!queryVector) { + throw new Error('Query vector is undefined or null') + } + + // If no noun types specified, search all nouns + if (!nounTypes || nounTypes.length === 0) { + // Search in the index + const results = await this.index.search(queryVector, k) + + // Get metadata for each result + const searchResults: SearchResult[] = [] + + for (const [id, score] of results) { + const noun = this.index.getNouns().get(id) + if (!noun) { + continue + } + + const metadata = await this.storage!.getMetadata(id) + + searchResults.push({ + id, + score, + vector: noun.vector, + metadata: metadata as T | undefined + }) + } + + return searchResults + } else { + // Get nouns for each noun type in parallel + const nounPromises = nounTypes.map((nounType) => + this.storage!.getNounsByNounType(nounType) + ) + const nounArrays = await Promise.all(nounPromises) + + // Combine all nouns + const nouns: HNSWNoun[] = [] + for (const nounArray of nounArrays) { + nouns.push(...nounArray) + } + + // Calculate distances for each noun + const results: Array<[string, number]> = [] + for (const noun of nouns) { + const distance = this.index.getDistanceFunction()( + queryVector, + noun.vector + ) + results.push([noun.id, distance]) + } + + // Sort by distance (ascending) + results.sort((a, b) => a[1] - b[1]) + + // Take top k results + const topResults = results.slice(0, k) + + // Get metadata for each result + const searchResults: SearchResult[] = [] + + for (const [id, score] of topResults) { + const noun = nouns.find((n) => n.id === id) + if (!noun) { + continue + } + + const metadata = await this.storage!.getMetadata(id) + + searchResults.push({ + id, + score, + vector: noun.vector, + metadata: metadata as T | undefined + }) + } + + return searchResults + } + } catch (error) { + console.error('Failed to search vectors by noun types:', error) + throw new Error(`Failed to search vectors by noun types: ${error}`) + } + } + + /** + * Search for similar vectors + * @param queryVectorOrData Query vector or data to search for + * @param k Number of results to return + * @param options Additional options + * @returns Array of search results + */ + public async search( + queryVectorOrData: Vector | any, + k: number = 10, + options: { + forceEmbed?: boolean // Force using the embedding function even if input is a vector + nounTypes?: string[] // Optional array of noun types to search within + includeVerbs?: boolean // Whether to include associated GraphVerbs in the results + searchMode?: 'local' | 'remote' | 'combined' // Where to search: local, remote, or both + searchVerbs?: boolean // Whether to search for verbs directly instead of nouns + verbTypes?: string[] // Optional array of verb types to search within or filter by + searchConnectedNouns?: boolean // Whether to search for nouns connected by verbs + verbDirection?: 'outgoing' | 'incoming' | 'both' // Direction of verbs to consider when searching connected nouns + } = {} + ): Promise[]> { + // If searching for verbs directly + if (options.searchVerbs) { + const verbResults = await this.searchVerbs(queryVectorOrData, k, { + forceEmbed: options.forceEmbed, + verbTypes: options.verbTypes + }); + + // Convert verb results to SearchResult format + return verbResults.map(verb => ({ + id: verb.id, + score: verb.similarity, + vector: verb.embedding || [], + metadata: { + verb: verb.verb, + source: verb.source, + target: verb.target, + ...verb.data + } as unknown as T + })); + } + + // If searching for nouns connected by verbs + if (options.searchConnectedNouns) { + return this.searchNounsByVerbs(queryVectorOrData, k, { + forceEmbed: options.forceEmbed, + verbTypes: options.verbTypes, + direction: options.verbDirection + }); + } + + // If a specific search mode is specified, use the appropriate search method + if (options.searchMode === 'local') { + return this.searchLocal(queryVectorOrData, k, options) + } else if (options.searchMode === 'remote') { + return this.searchRemote(queryVectorOrData, k, options) + } else if (options.searchMode === 'combined') { + return this.searchCombined(queryVectorOrData, k, options) + } + + // Default behavior (backward compatible): search locally + return this.searchLocal(queryVectorOrData, k, options) + } + + /** + * Search the local database for similar vectors + * @param queryVectorOrData Query vector or data to search for + * @param k Number of results to return + * @param options Additional options + * @returns Array of search results + */ + public async searchLocal( + queryVectorOrData: Vector | any, + k: number = 10, + options: { + forceEmbed?: boolean // Force using the embedding function even if input is a vector + nounTypes?: string[] // Optional array of noun types to search within + includeVerbs?: boolean // Whether to include associated GraphVerbs in the results + } = {} + ): Promise[]> { + // If input is a string and not a vector, automatically vectorize it + let queryToUse = queryVectorOrData + if (typeof queryVectorOrData === 'string' && !options.forceEmbed) { + queryToUse = await this.embed(queryVectorOrData) + options.forceEmbed = false // Already embedded, don't force again + } + + // If noun types are specified, use searchByNounTypes + let searchResults + if (options.nounTypes && options.nounTypes.length > 0) { + searchResults = await this.searchByNounTypes( + queryToUse, + k, + options.nounTypes, + { + forceEmbed: options.forceEmbed + } + ) + } else { + // Otherwise, search all GraphNouns + searchResults = await this.searchByNounTypes(queryToUse, k, null, { + forceEmbed: options.forceEmbed + }) + } + + // If includeVerbs is true, retrieve associated GraphVerbs for each result + if (options.includeVerbs && this.storage) { + for (const result of searchResults) { + try { + // Get outgoing verbs for this noun + const outgoingVerbs = await this.storage.getVerbsBySource(result.id) + + // Get incoming verbs for this noun + const incomingVerbs = await this.storage.getVerbsByTarget(result.id) + + // Combine all verbs + const allVerbs = [...outgoingVerbs, ...incomingVerbs] + + // Add verbs to the result metadata + if (!result.metadata) { + result.metadata = {} as T + } + + // Add the verbs to the metadata + ;(result.metadata as Record).associatedVerbs = allVerbs + } catch (error) { + console.warn(`Failed to retrieve verbs for noun ${result.id}:`, error) + } + } + } + + return searchResults + } + + /** + * Find entities similar to a given entity ID + * @param id ID of the entity to find similar entities for + * @param options Additional options + * @returns Array of search results with similarity scores + */ + public async findSimilar( + id: string, + options: { + limit?: number // Number of results to return + nounTypes?: string[] // Optional array of noun types to search within + includeVerbs?: boolean // Whether to include associated GraphVerbs in the results + searchMode?: 'local' | 'remote' | 'combined' // Where to search: local, remote, or both + } = {} + ): Promise[]> { + await this.ensureInitialized() + + // Get the entity by ID + const entity = await this.get(id) + if (!entity) { + throw new Error(`Entity with ID ${id} not found`) + } + + // Use the entity's vector to search for similar entities + const k = (options.limit || 10) + 1 // Add 1 to account for the original entity + const searchResults = await this.search(entity.vector, k, { + forceEmbed: false, + nounTypes: options.nounTypes, + includeVerbs: options.includeVerbs, + searchMode: options.searchMode + }) + + // Filter out the original entity and limit to the requested number + return searchResults + .filter(result => result.id !== id) + .slice(0, options.limit || 10) + } + + /** + * Get a vector by ID + */ + public async get(id: string): Promise | null> { + await this.ensureInitialized() + + try { + // Get noun from index + const noun = this.index.getNouns().get(id) + if (!noun) { + return null + } + + // Get metadata + const metadata = await this.storage!.getMetadata(id) + + return { + id, + vector: noun.vector, + metadata: metadata as T | undefined + } + } catch (error) { + console.error(`Failed to get vector ${id}:`, error) + throw new Error(`Failed to get vector ${id}: ${error}`) + } + } + + /** + * Get all nouns in the database + * @returns Array of vector documents + */ + public async getAllNouns(): Promise[]> { + await this.ensureInitialized() + + try { + const nouns = this.index.getNouns() + const result: VectorDocument[] = [] + + for (const [id, noun] of nouns.entries()) { + const metadata = await this.storage!.getMetadata(id) + result.push({ + id, + vector: noun.vector, + metadata: metadata as T | undefined + }) + } + + return result + } catch (error) { + console.error('Failed to get all nouns:', error) + throw new Error(`Failed to get all nouns: ${error}`) + } + } + + /** + * Delete a vector by ID + */ + public async delete(id: string): Promise { + await this.ensureInitialized() + + // Check if database is in read-only mode + this.checkReadOnly() + + try { + // Remove from index + const removed = this.index.removeItem(id) + if (!removed) { + return false + } + + // Remove from storage + await this.storage!.deleteNoun(id) + + // Try to remove metadata (ignore errors) + try { + await this.storage!.saveMetadata(id, null) + } catch (error) { + // Ignore + } + + return true + } catch (error) { + console.error(`Failed to delete vector ${id}:`, error) + throw new Error(`Failed to delete vector ${id}: ${error}`) + } + } + + /** + * Update metadata for a vector + */ + public async updateMetadata(id: string, metadata: T): Promise { + await this.ensureInitialized() + + // Check if database is in read-only mode + this.checkReadOnly() + + try { + // Check if a vector exists + const noun = this.index.getNouns().get(id) + if (!noun) { + return false + } + + // Validate noun type if metadata is for a GraphNoun + if (metadata && typeof metadata === 'object' && 'noun' in metadata) { + const nounType = (metadata as unknown as GraphNoun).noun + + // Check if the noun type is valid + const isValidNounType = Object.values(NounType).includes(nounType) + + if (!isValidNounType) { + console.warn( + `Invalid noun type: ${nounType}. Falling back to GraphNoun.` + ) + // Set a default noun type + ;(metadata as unknown as GraphNoun).noun = NounType.Concept + } + } + + // Update metadata + await this.storage!.saveMetadata(id, metadata) + + return true + } catch (error) { + console.error(`Failed to update metadata for vector ${id}:`, error) + throw new Error(`Failed to update metadata for vector ${id}: ${error}`) + } + } + + /** + * Create a relationship between two entities + * This is a convenience wrapper around addVerb + */ + public async relate( + sourceId: string, + targetId: string, + relationType: string, + metadata?: any + ): Promise { + return this.addVerb(sourceId, targetId, undefined, { + type: relationType, + metadata: metadata + }) + } + + /** + * Add a verb between two nouns + * If metadata is provided and vector is not, the metadata will be vectorized using the embedding function + */ + public async addVerb( + sourceId: string, + targetId: string, + vector?: Vector, + options: { + type?: string + weight?: number + metadata?: any + forceEmbed?: boolean // Force using the embedding function for metadata even if vector is provided + id?: string // Optional ID to use instead of generating a new one + } = {} + ): Promise { + await this.ensureInitialized() + + // Check if database is in read-only mode + this.checkReadOnly() + + try { + // Check if source and target nouns exist + const sourceNoun = this.index.getNouns().get(sourceId) + const targetNoun = this.index.getNouns().get(targetId) + + if (!sourceNoun) { + throw new Error(`Source noun with ID ${sourceId} not found`) + } + + if (!targetNoun) { + throw new Error(`Target noun with ID ${targetId} not found`) + } + + // Use provided ID or generate a new one + const id = options.id || uuidv4() + + let verbVector: Vector + + // If metadata is provided and no vector is provided or forceEmbed is true, vectorize the metadata + if (options.metadata && (!vector || options.forceEmbed)) { + try { + // Extract a string representation from metadata for embedding + let textToEmbed: string + if (typeof options.metadata === 'string') { + textToEmbed = options.metadata + } else if ( + options.metadata.description && + typeof options.metadata.description === 'string' + ) { + textToEmbed = options.metadata.description + } else { + // Convert to JSON string as fallback + textToEmbed = JSON.stringify(options.metadata) + } + + // Ensure textToEmbed is a string + if (typeof textToEmbed !== 'string') { + textToEmbed = String(textToEmbed) + } + + verbVector = await this.embeddingFunction(textToEmbed) + } catch (embedError) { + throw new Error(`Failed to vectorize verb metadata: ${embedError}`) + } + } else { + // Use a provided vector or average of source and target vectors + if (vector) { + verbVector = vector + } else { + // Ensure both source and target vectors have the same dimension + if ( + !sourceNoun.vector || + !targetNoun.vector || + sourceNoun.vector.length === 0 || + targetNoun.vector.length === 0 || + sourceNoun.vector.length !== targetNoun.vector.length + ) { + throw new Error( + `Cannot average vectors: source or target vector is invalid or dimensions don't match` + ) + } + + // Average the vectors + verbVector = sourceNoun.vector.map( + (val, i) => (val + targetNoun.vector[i]) / 2 + ) + } + } + + // Validate verb type if provided + let verbType = options.type + if (verbType) { + // Check if the verb type is valid + const isValidVerbType = Object.values(VerbType).includes( + verbType as VerbType + ) + + if (!isValidVerbType) { + console.warn( + `Invalid verb type: ${verbType}. Using RelatedTo as default.` + ) + // Set a default verb type + verbType = VerbType.RelatedTo + } + } + + // Create verb + const verb: GraphVerb = { + id, + vector: verbVector, + connections: new Map(), + sourceId, + targetId, + type: verbType, + weight: options.weight, + metadata: options.metadata + } + + // Add to index + await this.index.addItem({ id, vector: verbVector }) + + // Get the noun from the index + const indexNoun = this.index.getNouns().get(id) + + if (!indexNoun) { + throw new Error( + `Failed to retrieve newly created verb noun with ID ${id}` + ) + } + + // Update verb connections from index + verb.connections = indexNoun.connections + + // Save verb to storage + await this.storage!.saveVerb(verb) + + return id + } catch (error) { + console.error('Failed to add verb:', error) + throw new Error(`Failed to add verb: ${error}`) + } + } + + /** + * Get a verb by ID + */ + public async getVerb(id: string): Promise { + await this.ensureInitialized() + + try { + return await this.storage!.getVerb(id) + } catch (error) { + console.error(`Failed to get verb ${id}:`, error) + throw new Error(`Failed to get verb ${id}: ${error}`) + } + } + + /** + * Get all verbs + */ + public async getAllVerbs(): Promise { + await this.ensureInitialized() + + try { + return await this.storage!.getAllVerbs() + } catch (error) { + console.error('Failed to get all verbs:', error) + throw new Error(`Failed to get all verbs: ${error}`) + } + } + + /** + * Get verbs by source noun ID + */ + public async getVerbsBySource(sourceId: string): Promise { + await this.ensureInitialized() + + try { + return await this.storage!.getVerbsBySource(sourceId) + } catch (error) { + console.error(`Failed to get verbs by source ${sourceId}:`, error) + throw new Error(`Failed to get verbs by source ${sourceId}: ${error}`) + } + } + + /** + * Get verbs by target noun ID + */ + public async getVerbsByTarget(targetId: string): Promise { + await this.ensureInitialized() + + try { + return await this.storage!.getVerbsByTarget(targetId) + } catch (error) { + console.error(`Failed to get verbs by target ${targetId}:`, error) + throw new Error(`Failed to get verbs by target ${targetId}: ${error}`) + } + } + + /** + * Get verbs by type + */ + public async getVerbsByType(type: string): Promise { + await this.ensureInitialized() + + try { + return await this.storage!.getVerbsByType(type) + } catch (error) { + console.error(`Failed to get verbs by type ${type}:`, error) + throw new Error(`Failed to get verbs by type ${type}: ${error}`) + } + } + + /** + * Delete a verb + */ + public async deleteVerb(id: string): Promise { + await this.ensureInitialized() + + // Check if database is in read-only mode + this.checkReadOnly() + + try { + // Remove from index + const removed = this.index.removeItem(id) + if (!removed) { + return false + } + + // Remove from storage + await this.storage!.deleteVerb(id) + + return true + } catch (error) { + console.error(`Failed to delete verb ${id}:`, error) + throw new Error(`Failed to delete verb ${id}: ${error}`) + } + } + + /** + * Clear the database + */ + public async clear(): Promise { + await this.ensureInitialized() + + // Check if database is in read-only mode + this.checkReadOnly() + + try { + // Clear index + this.index.clear() + + // Clear storage + await this.storage!.clear() + } catch (error) { + console.error('Failed to clear vector database:', error) + throw new Error(`Failed to clear vector database: ${error}`) + } + } + + /** + * Get the number of vectors in the database + */ + public size(): number { + return this.index.size() + } + + /** + * Check if the database is in read-only mode + * @returns True if the database is in read-only mode, false otherwise + */ + public isReadOnly(): boolean { + return this.readOnly + } + + /** + * Set the database to read-only mode + * @param readOnly True to set the database to read-only mode, false to allow writes + */ + public setReadOnly(readOnly: boolean): void { + this.readOnly = readOnly + } + + /** + * Embed text or data into a vector using the same embedding function used by this instance + * This allows clients to use the same TensorFlow Universal Sentence Encoder throughout their application + * + * @param data Text or data to embed + * @returns A promise that resolves to the embedded vector + */ + public async embed(data: string | string[]): Promise { + await this.ensureInitialized() + + try { + return await this.embeddingFunction(data) + } catch (error) { + console.error('Failed to embed data:', error) + throw new Error(`Failed to embed data: ${error}`) + } + } + + /** + * Search for verbs by type and/or vector similarity + * @param queryVectorOrData Query vector or data to search for + * @param k Number of results to return + * @param options Additional options + * @returns Array of verbs with similarity scores + */ + public async searchVerbs( + queryVectorOrData: Vector | any, + k: number = 10, + options: { + forceEmbed?: boolean // Force using the embedding function even if input is a vector + verbTypes?: string[] // Optional array of verb types to search within + } = {} + ): Promise> { + await this.ensureInitialized() + + try { + let queryVector: Vector + + // Check if input is already a vector + if ( + Array.isArray(queryVectorOrData) && + queryVectorOrData.every((item) => typeof item === 'number') && + !options.forceEmbed + ) { + // Input is already a vector + queryVector = queryVectorOrData + } else { + // Input needs to be vectorized + try { + queryVector = await this.embeddingFunction(queryVectorOrData) + } catch (embedError) { + throw new Error(`Failed to vectorize query data: ${embedError}`) + } + } + + // Get verbs to search through + let verbs: GraphVerb[] = [] + + // If verb types are specified, get verbs of those types + if (options.verbTypes && options.verbTypes.length > 0) { + // Get verbs for each verb type in parallel + const verbPromises = options.verbTypes.map((verbType) => + this.getVerbsByType(verbType) + ) + const verbArrays = await Promise.all(verbPromises) + + // Combine all verbs + for (const verbArray of verbArrays) { + verbs.push(...verbArray) + } + } else { + // Get all verbs + verbs = await this.storage!.getAllVerbs() + } + + // Filter out verbs without embeddings + verbs = verbs.filter(verb => verb.embedding && verb.embedding.length > 0) + + // Calculate similarity for each verb + const results: Array = [] + for (const verb of verbs) { + if (verb.embedding) { + const distance = this.index.getDistanceFunction()( + queryVector, + verb.embedding + ) + results.push({ + ...verb, + similarity: distance + }) + } + } + + // Sort by similarity (ascending distance) + results.sort((a, b) => a.similarity - b.similarity) + + // Take top k results + return results.slice(0, k) + } catch (error) { + console.error('Failed to search verbs:', error) + throw new Error(`Failed to search verbs: ${error}`) + } + } + + /** + * Search for nouns connected by specific verb types + * @param queryVectorOrData Query vector or data to search for + * @param k Number of results to return + * @param options Additional options + * @returns Array of search results + */ + public async searchNounsByVerbs( + queryVectorOrData: Vector | any, + k: number = 10, + options: { + forceEmbed?: boolean // Force using the embedding function even if input is a vector + verbTypes?: string[] // Optional array of verb types to filter by + direction?: 'outgoing' | 'incoming' | 'both' // Direction of verbs to consider + } = {} + ): Promise[]> { + await this.ensureInitialized() + + try { + // First, search for nouns + const nounResults = await this.searchByNounTypes( + queryVectorOrData, + k * 2, // Get more results initially to account for filtering + null, + { forceEmbed: options.forceEmbed } + ) + + // If no verb types specified, return the noun results directly + if (!options.verbTypes || options.verbTypes.length === 0) { + return nounResults.slice(0, k) + } + + // For each noun, get connected nouns through specified verb types + const connectedNounIds = new Set() + const direction = options.direction || 'both' + + for (const result of nounResults) { + // Get verbs connected to this noun + let connectedVerbs: GraphVerb[] = [] + + if (direction === 'outgoing' || direction === 'both') { + // Get outgoing verbs + const outgoingVerbs = await this.storage!.getVerbsBySource(result.id) + connectedVerbs.push(...outgoingVerbs) + } + + if (direction === 'incoming' || direction === 'both') { + // Get incoming verbs + const incomingVerbs = await this.storage!.getVerbsByTarget(result.id) + connectedVerbs.push(...incomingVerbs) + } + + // Filter by verb types if specified + if (options.verbTypes && options.verbTypes.length > 0) { + connectedVerbs = connectedVerbs.filter(verb => + verb.verb && options.verbTypes!.includes(verb.verb) + ) + } + + // Add connected noun IDs to the set + for (const verb of connectedVerbs) { + if (verb.source && verb.source !== result.id) { + connectedNounIds.add(verb.source) + } + if (verb.target && verb.target !== result.id) { + connectedNounIds.add(verb.target) + } + } + } + + // Get the connected nouns + const connectedNouns: SearchResult[] = [] + for (const id of connectedNounIds) { + try { + const noun = this.index.getNouns().get(id) + if (noun) { + const metadata = await this.storage!.getMetadata(id) + + // Calculate similarity score + let queryVector: Vector + if ( + Array.isArray(queryVectorOrData) && + queryVectorOrData.every((item) => typeof item === 'number') && + !options.forceEmbed + ) { + queryVector = queryVectorOrData + } else { + queryVector = await this.embeddingFunction(queryVectorOrData) + } + + const distance = this.index.getDistanceFunction()( + queryVector, + noun.vector + ) + + connectedNouns.push({ + id, + score: distance, + vector: noun.vector, + metadata: metadata as T | undefined + }) + } + } catch (error) { + console.warn(`Failed to retrieve noun ${id}:`, error) + } + } + + // Sort by similarity score + connectedNouns.sort((a, b) => a.score - b.score) + + // Return top k results + return connectedNouns.slice(0, k) + } catch (error) { + console.error('Failed to search nouns by verbs:', error) + throw new Error(`Failed to search nouns by verbs: ${error}`) + } + } + + /** + * Search for similar documents using a text query + * This is a convenience method that embeds the query text and performs a search + * + * @param query Text query to search for + * @param k Number of results to return + * @param options Additional options + * @returns Array of search results + */ + public async searchText( + query: string, + k: number = 10, + options: { + nounTypes?: string[] + includeVerbs?: boolean + searchMode?: 'local' | 'remote' | 'combined' + } = {} + ): Promise[]> { + await this.ensureInitialized() + + try { + // Embed the query text + const queryVector = await this.embed(query) + + // Search using the embedded vector + return await this.search(queryVector, k, { + nounTypes: options.nounTypes, + includeVerbs: options.includeVerbs, + searchMode: options.searchMode + }) + } catch (error) { + console.error('Failed to search with text query:', error) + throw new Error(`Failed to search with text query: ${error}`) + } + } + + /** + * Search a remote Brainy server for similar vectors + * @param queryVectorOrData Query vector or data to search for + * @param k Number of results to return + * @param options Additional options + * @returns Array of search results + */ + public async searchRemote( + queryVectorOrData: Vector | any, + k: number = 10, + options: { + forceEmbed?: boolean // Force using the embedding function even if input is a vector + nounTypes?: string[] // Optional array of noun types to search within + includeVerbs?: boolean // Whether to include associated GraphVerbs in the results + storeResults?: boolean // Whether to store the results in the local database (default: true) + } = {} + ): Promise[]> { + await this.ensureInitialized() + + // Check if connected to a remote server + if (!this.isConnectedToRemoteServer()) { + throw new Error( + 'Not connected to a remote server. Call connectToRemoteServer() first.' + ) + } + + try { + // If input is a string, convert it to a query string for the server + let query: string + if (typeof queryVectorOrData === 'string') { + query = queryVectorOrData + } else { + // For vectors, we need to embed them as a string query + // This is a simplification - ideally we would send the vector directly + query = 'vector-query' // Placeholder, would need a better approach for vector queries + } + + if (!this.serverSearchConduit || !this.serverConnection) { + throw new Error( + 'Server search conduit or connection is not initialized' + ) + } + + // Search the remote server + const searchResult = await this.serverSearchConduit.searchServer( + this.serverConnection.connectionId, + query, + k + ) + + if (!searchResult.success) { + throw new Error(`Remote search failed: ${searchResult.error}`) + } + + return searchResult.data as SearchResult[] + } catch (error) { + console.error('Failed to search remote server:', error) + throw new Error(`Failed to search remote server: ${error}`) + } + } + + /** + * Search both local and remote Brainy instances, combining the results + * @param queryVectorOrData Query vector or data to search for + * @param k Number of results to return + * @param options Additional options + * @returns Array of search results + */ + public async searchCombined( + queryVectorOrData: Vector | any, + k: number = 10, + options: { + forceEmbed?: boolean // Force using the embedding function even if input is a vector + nounTypes?: string[] // Optional array of noun types to search within + includeVerbs?: boolean // Whether to include associated GraphVerbs in the results + localFirst?: boolean // Whether to search local first (default: true) + } = {} + ): Promise[]> { + await this.ensureInitialized() + + // Check if connected to a remote server + if (!this.isConnectedToRemoteServer()) { + // If not connected to a remote server, just search locally + return this.searchLocal(queryVectorOrData, k, options) + } + + try { + // Default to searching local first + const localFirst = options.localFirst !== false + + if (localFirst) { + // Search local first + const localResults = await this.searchLocal( + queryVectorOrData, + k, + options + ) + + // If we have enough local results, return them + if (localResults.length >= k) { + return localResults + } + + // Otherwise, search remote for additional results + const remoteResults = await this.searchRemote( + queryVectorOrData, + k - localResults.length, + { ...options, storeResults: true } + ) + + // Combine results, removing duplicates + const combinedResults = [...localResults] + const localIds = new Set(localResults.map((r) => r.id)) + + for (const result of remoteResults) { + if (!localIds.has(result.id)) { + combinedResults.push(result) + } + } + + return combinedResults + } else { + // Search remote first + const remoteResults = await this.searchRemote(queryVectorOrData, k, { + ...options, + storeResults: true + }) + + // If we have enough remote results, return them + if (remoteResults.length >= k) { + return remoteResults + } + + // Otherwise, search local for additional results + const localResults = await this.searchLocal( + queryVectorOrData, + k - remoteResults.length, + options + ) + + // Combine results, removing duplicates + const combinedResults = [...remoteResults] + const remoteIds = new Set(remoteResults.map((r) => r.id)) + + for (const result of localResults) { + if (!remoteIds.has(result.id)) { + combinedResults.push(result) + } + } + + return combinedResults + } + } catch (error) { + console.error('Failed to perform combined search:', error) + throw new Error(`Failed to perform combined search: ${error}`) + } + } + + /** + * Check if the instance is connected to a remote server + * @returns True if connected to a remote server, false otherwise + */ + public isConnectedToRemoteServer(): boolean { + return !!(this.serverSearchConduit && this.serverConnection) + } + + /** + * Disconnect from the remote server + * @returns True if successfully disconnected, false if not connected + */ + public async disconnectFromRemoteServer(): Promise { + if (!this.isConnectedToRemoteServer()) { + return false + } + + try { + if (!this.serverSearchConduit || !this.serverConnection) { + throw new Error( + 'Server search conduit or connection is not initialized' + ) + } + + // Close the WebSocket connection + await this.serverSearchConduit.closeWebSocket( + this.serverConnection.connectionId + ) + + // Clear the connection information + this.serverSearchConduit = null + this.serverConnection = null + + return true + } catch (error) { + console.error('Failed to disconnect from remote server:', error) + throw new Error(`Failed to disconnect from remote server: ${error}`) + } + } + + /** + * Ensure the database is initialized + */ + private async ensureInitialized(): Promise { + if (!this.isInitialized) { + await this.init() + } + } + + /** + * Get information about the current storage usage and capacity + * @returns Object containing the storage type, used space, quota, and additional details + */ + public async status(): Promise<{ + type: string + used: number + quota: number | null + details?: Record + }> { + await this.ensureInitialized() + + if (!this.storage) { + return { + type: 'any', + used: 0, + quota: null, + details: { error: 'Storage not initialized' } + } + } + + try { + // Check if the storage adapter has a getStorageStatus method + if (typeof this.storage.getStorageStatus !== 'function') { + // If not, determine the storage type based on the constructor name + const storageType = this.storage.constructor.name + .toLowerCase() + .replace('storage', '') + return { + type: storageType || 'any', + used: 0, + quota: null, + details: { + error: 'Storage adapter does not implement getStorageStatus method', + storageAdapter: this.storage.constructor.name, + indexSize: this.size() + } + } + } + + // Get storage status from the storage adapter + const storageStatus = await this.storage.getStorageStatus() + + // Add index information to the details + let indexInfo: Record = { + indexSize: this.size() + } + + // Add optimized index information if using optimized index + if (this.useOptimizedIndex && this.index instanceof HNSWIndexOptimized) { + const optimizedIndex = this.index as HNSWIndexOptimized + indexInfo = { + ...indexInfo, + optimized: true, + memoryUsage: optimizedIndex.getMemoryUsage(), + productQuantization: optimizedIndex.getUseProductQuantization(), + diskBasedIndex: optimizedIndex.getUseDiskBasedIndex() + } + } else { + indexInfo.optimized = false + } + + // Ensure all required fields are present + return { + type: storageStatus.type || 'any', + used: storageStatus.used || 0, + quota: storageStatus.quota || null, + details: { + ...(storageStatus.details || {}), + index: indexInfo + } + } + } catch (error) { + console.error('Failed to get storage status:', error) + + // Determine the storage type based on the constructor name + const storageType = this.storage.constructor.name + .toLowerCase() + .replace('storage', '') + + return { + type: storageType || 'any', + used: 0, + quota: null, + details: { + error: String(error), + storageAdapter: this.storage.constructor.name, + indexSize: this.size() + } + } + } + } + + /** + * Shut down the database and clean up resources + * This should be called when the database is no longer needed + */ + public async shutDown(): Promise { + try { + // Disconnect from remote server if connected + if (this.isConnectedToRemoteServer()) { + await this.disconnectFromRemoteServer() + } + + // Additional cleanup could be added here in the future + + this.isInitialized = false + } catch (error) { + console.error('Failed to shut down BrainyData:', error) + throw new Error(`Failed to shut down BrainyData: ${error}`) + } + } + + /** + * Backup all data from the database to a JSON-serializable format + * @returns Object containing all nouns, verbs, noun types, verb types, HNSW index, and other related data + * + * The HNSW index data includes: + * - entryPointId: The ID of the entry point for the graph + * - maxLevel: The maximum level in the hierarchical structure + * - dimension: The dimension of the vectors + * - config: Configuration parameters for the HNSW algorithm + * - connections: A serialized representation of the connections between nouns + */ + public async backup(): Promise<{ + nouns: VectorDocument[] + verbs: GraphVerb[] + nounTypes: string[] + verbTypes: string[] + version: string + hnswIndex?: { + entryPointId: string | null + maxLevel: number + dimension: number | null + config: HNSWConfig + connections: Record> + } + }> { + await this.ensureInitialized() + + try { + // Get all nouns + const nouns = await this.getAllNouns() + + // Get all verbs + const verbs = await this.getAllVerbs() + + // Get all noun types + const nounTypes = Object.values(NounType) + + // Get all verb types + const verbTypes = Object.values(VerbType) + + // Get HNSW index data + const hnswIndexData = { + entryPointId: this.index.getEntryPointId(), + maxLevel: this.index.getMaxLevel(), + dimension: this.index.getDimension(), + config: this.index.getConfig(), + connections: {} as Record> + } + + // Convert Map> to a serializable format + const indexNouns = this.index.getNouns() + for (const [id, noun] of indexNouns.entries()) { + hnswIndexData.connections[id] = {} + for (const [level, connections] of noun.connections.entries()) { + hnswIndexData.connections[id][level] = Array.from(connections) + } + } + + // Return the data with version information + return { + nouns, + verbs, + nounTypes, + verbTypes, + hnswIndex: hnswIndexData, + version: '1.0.0' // Version of the backup format + } + } catch (error) { + console.error('Failed to backup data:', error) + throw new Error(`Failed to backup data: ${error}`) + } + } + + /** + * Import sparse data into the database + * @param data The sparse data to import + * If vectors are not present for nouns, they will be created using the embedding function + * @param options Import options + * @returns Object containing counts of imported items + */ + public async importSparseData( + data: { + nouns: VectorDocument[] + verbs: GraphVerb[] + nounTypes?: string[] + verbTypes?: string[] + hnswIndex?: { + entryPointId: string | null + maxLevel: number + dimension: number | null + config: HNSWConfig + connections: Record> + } + version: string + }, + options: { + clearExisting?: boolean + } = {} + ): Promise<{ + nounsRestored: number + verbsRestored: number + }> { + return this.restore(data, options); + } + + /** + * Restore data into the database from a previously backed up format + * @param data The data to restore, in the format returned by backup() + * This can include HNSW index data if it was included in the backup + * If vectors are not present for nouns, they will be created using the embedding function + * @param options Restore options + * @returns Object containing counts of restored items + */ + public async restore( + data: { + nouns: VectorDocument[] + verbs: GraphVerb[] + nounTypes?: string[] + verbTypes?: string[] + hnswIndex?: { + entryPointId: string | null + maxLevel: number + dimension: number | null + config: HNSWConfig + connections: Record> + } + version: string + }, + options: { + clearExisting?: boolean + } = {} + ): Promise<{ + nounsRestored: number + verbsRestored: number + }> { + await this.ensureInitialized() + + // Check if database is in read-only mode + this.checkReadOnly() + + try { + // Clear existing data if requested + if (options.clearExisting) { + await this.clear() + } + + // Validate the data format + if (!data || !data.nouns || !data.verbs || !data.version) { + throw new Error('Invalid restore data format') + } + + // Log additional data if present + if (data.nounTypes) { + console.log(`Found ${data.nounTypes.length} noun types in restore data`) + } + + if (data.verbTypes) { + console.log(`Found ${data.verbTypes.length} verb types in restore data`) + } + + if (data.hnswIndex) { + console.log('Found HNSW index data in backup') + } + + // Restore nouns + let nounsRestored = 0 + for (const noun of data.nouns) { + try { + // Check if the noun has a vector + if (!noun.vector || noun.vector.length === 0) { + // If no vector, create one using the embedding function + if ( + noun.metadata && + typeof noun.metadata === 'object' && + 'text' in noun.metadata + ) { + // If the metadata has a text field, use it for embedding + noun.vector = await this.embeddingFunction(noun.metadata.text) + } else { + // Otherwise, use the entire metadata for embedding + noun.vector = await this.embeddingFunction(noun.metadata) + } + } + + // Add the noun with its vector and metadata + await this.add(noun.vector, noun.metadata, { id: noun.id }) + nounsRestored++ + } catch (error) { + console.error(`Failed to restore noun ${noun.id}:`, error) + // Continue with other nouns + } + } + + // Restore verbs + let verbsRestored = 0 + for (const verb of data.verbs) { + try { + // Check if the verb has a vector + if (!verb.vector || verb.vector.length === 0) { + // If no vector, create one using the embedding function + if ( + verb.metadata && + typeof verb.metadata === 'object' && + 'text' in verb.metadata + ) { + // If the metadata has a text field, use it for embedding + verb.vector = await this.embeddingFunction(verb.metadata.text) + } else { + // Otherwise, use the entire metadata for embedding + verb.vector = await this.embeddingFunction(verb.metadata) + } + } + + // Add the verb + await this.addVerb(verb.sourceId, verb.targetId, verb.vector, { + id: verb.id, + type: verb.metadata?.verb || VerbType.RelatedTo, + metadata: verb.metadata + }) + verbsRestored++ + } catch (error) { + console.error(`Failed to restore verb ${verb.id}:`, error) + // Continue with other verbs + } + } + + // If HNSW index data is provided and we've restored nouns, reconstruct the index + if (data.hnswIndex && nounsRestored > 0) { + try { + console.log('Reconstructing HNSW index from backup data...') + + // Create a new index with the restored configuration + this.index = new HNSWIndex( + data.hnswIndex.config, + this.distanceFunction + ) + + // Re-add all nouns to the index + for (const noun of data.nouns) { + if (noun.vector && noun.vector.length > 0) { + await this.index.addItem({ id: noun.id, vector: noun.vector }) + } + } + + console.log('HNSW index reconstruction complete') + } catch (error) { + console.error('Failed to reconstruct HNSW index:', error) + console.log('Continuing with standard restore process...') + } + } + + return { + nounsRestored, + verbsRestored + } + } catch (error) { + console.error('Failed to restore data:', error) + throw new Error(`Failed to restore data: ${error}`) + } + } + + /** + * Generate a random graph of data with typed nouns and verbs for testing and experimentation + * @param options Configuration options for the random graph + * @returns Object containing the IDs of the generated nouns and verbs + */ + public async generateRandomGraph( + options: { + nounCount?: number // Number of nouns to generate (default: 10) + verbCount?: number // Number of verbs to generate (default: 20) + nounTypes?: NounType[] // Types of nouns to generate (default: all types) + verbTypes?: VerbType[] // Types of verbs to generate (default: all types) + clearExisting?: boolean // Whether to clear existing data before generating (default: false) + seed?: string // Seed for random generation (default: random) + } = {} + ): Promise<{ + nounIds: string[] + verbIds: string[] + }> { + await this.ensureInitialized() + + // Check if database is in read-only mode + this.checkReadOnly() + + // Set default options + const nounCount = options.nounCount || 10 + const verbCount = options.verbCount || 20 + const nounTypes = options.nounTypes || Object.values(NounType) + const verbTypes = options.verbTypes || Object.values(VerbType) + const clearExisting = options.clearExisting || false + + // Clear existing data if requested + if (clearExisting) { + await this.clear() + } + + try { + // Generate random nouns + const nounIds: string[] = [] + const nounDescriptions: Record = { + [NounType.Person]: 'A person with unique characteristics', + [NounType.Place]: 'A location with specific attributes', + [NounType.Thing]: 'An object with distinct properties', + [NounType.Event]: 'An occurrence with temporal aspects', + [NounType.Concept]: 'An abstract idea or notion', + [NounType.Content]: 'A piece of content or information', + [NounType.Group]: 'A collection of related entities', + [NounType.List]: 'An ordered sequence of items', + [NounType.Category]: 'A classification or grouping' + } + + for (let i = 0; i < nounCount; i++) { + // Select a random noun type + const nounType = nounTypes[Math.floor(Math.random() * nounTypes.length)] + + // Generate a random label + const label = `Random ${nounType} ${i + 1}` + + // Create metadata + const metadata = { + noun: nounType, + label, + description: nounDescriptions[nounType] || `A random ${nounType}`, + randomAttributes: { + value: Math.random() * 100, + priority: Math.floor(Math.random() * 5) + 1, + tags: [`tag-${i % 5}`, `category-${i % 3}`] + } + } + + // Add the noun + const id = await this.add(metadata.description, metadata as T) + nounIds.push(id) + } + + // Generate random verbs between nouns + const verbIds: string[] = [] + const verbDescriptions: Record = { + [VerbType.AttributedTo]: 'Attribution relationship', + [VerbType.Controls]: 'Control relationship', + [VerbType.Created]: 'Creation relationship', + [VerbType.Earned]: 'Achievement relationship', + [VerbType.Owns]: 'Ownership relationship', + [VerbType.MemberOf]: 'Membership relationship', + [VerbType.RelatedTo]: 'General relationship', + [VerbType.WorksWith]: 'Collaboration relationship', + [VerbType.FriendOf]: 'Friendship relationship', + [VerbType.ReportsTo]: 'Reporting relationship', + [VerbType.Supervises]: 'Supervision relationship', + [VerbType.Mentors]: 'Mentorship relationship' + } + + for (let i = 0; i < verbCount; i++) { + // Select random source and target nouns + const sourceIndex = Math.floor(Math.random() * nounIds.length) + let targetIndex = Math.floor(Math.random() * nounIds.length) + + // Ensure source and target are different + while (targetIndex === sourceIndex && nounIds.length > 1) { + targetIndex = Math.floor(Math.random() * nounIds.length) + } + + const sourceId = nounIds[sourceIndex] + const targetId = nounIds[targetIndex] + + // Select a random verb type + const verbType = verbTypes[Math.floor(Math.random() * verbTypes.length)] + + // Create metadata + const metadata = { + verb: verbType, + description: + verbDescriptions[verbType] || `A random ${verbType} relationship`, + weight: Math.random(), + confidence: Math.random(), + randomAttributes: { + strength: Math.random() * 100, + duration: Math.floor(Math.random() * 365) + 1, + tags: [`relation-${i % 5}`, `strength-${i % 3}`] + } + } + + // Add the verb + const id = await this.addVerb(sourceId, targetId, undefined, { + type: verbType, + weight: metadata.weight, + metadata + }) + + verbIds.push(id) + } + + return { + nounIds, + verbIds + } + } catch (error) { + console.error('Failed to generate random graph:', error) + throw new Error(`Failed to generate random graph: ${error}`) + } + } +} + +// Export distance functions for convenience +export { + euclideanDistance, + cosineDistance, + manhattanDistance, + dotProductDistance +} from './utils/index.js' diff --git a/src/cli.ts b/src/cli.ts new file mode 100644 index 00000000..4ce4df09 --- /dev/null +++ b/src/cli.ts @@ -0,0 +1,1286 @@ +#!/usr/bin/env node + +/** + * Brainy CLI + * A command-line interface for interacting with the Brainy vector database + */ + +import { BrainyData, NounType, VerbType, FileSystemStorage } from './index.js' +import { fileURLToPath } from 'url' +import { dirname, join } from 'path' +import fs from 'fs' +import { Command } from 'commander' +import omelette from 'omelette' +import { VERSION } from './utils/version.js' +import { sequentialPipeline } from './sequentialPipeline.js' +import { augmentationPipeline, ExecutionMode } from './augmentationPipeline.js' +import { AugmentationType } from './types/augmentations.js' + +// Get the directory of the current module +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +// Helper function to parse JSON safely +function parseJSON(str: string): any { + try { + return JSON.parse(str) + } catch (e) { + console.error('Error parsing JSON:', (e as Error).message) + return {} + } +} + +// Helper function to resolve noun type +function resolveNounType(type: string | number | undefined): NounType { + if (!type) return NounType.Thing + + // If it's a string, try to match it to a NounType + if (typeof type === 'string') { + const nounTypeKey = Object.keys(NounType).find( + (key) => key.toLowerCase() === type.toLowerCase() + ) + return nounTypeKey + ? NounType[nounTypeKey as keyof typeof NounType] + : NounType.Thing + } + + // Convert number to string type for safety + return Object.values(NounType)[type as number] || NounType.Thing +} + +// Helper function to resolve verb type +function resolveVerbType(type: string | number | undefined): VerbType { + if (!type) return VerbType.RelatedTo + + // If it's a string, try to match it to a VerbType + if (typeof type === 'string') { + const verbTypeKey = Object.keys(VerbType).find( + (key) => key.toLowerCase() === type.toLowerCase() + ) + return verbTypeKey + ? VerbType[verbTypeKey as keyof typeof VerbType] + : VerbType.RelatedTo + } + + // Convert number to string type for safety + return Object.values(VerbType)[type as number] || VerbType.RelatedTo +} + +// Create a new Command instance +const program = new Command() + +// Configure the program +program + .name('@soulcraft/brainy') + .description( + 'A vector database using HNSW indexing with Origin Private File System storage' + ) + .version(VERSION, '-V, --version', 'Output the current version') + +// Create data directory if it doesn't exist +const dataDir = join(__dirname, '..', 'data') +if (!fs.existsSync(dataDir)) { + fs.mkdirSync(dataDir, { recursive: true }) +} + +// Create a database instance with file system storage +const createDb = () => { + return new BrainyData({ + storageAdapter: new FileSystemStorage(dataDir) + }) +} + +// Define commands +program + .command('init') + .description('Initialize a new database') + .action(async () => { + try { + const db = createDb() + await db.init() + console.log('Database initialized successfully') + } catch (error) { + console.error('Error:', (error as Error).message) + process.exit(1) + } + }) + +program + .command('add') + .description('Add a new noun with the given text and optional metadata') + .argument('', 'Text to add as a noun') + .argument('[metadata]', 'Optional metadata as JSON string') + .action(async (text, metadataStr) => { + try { + const db = createDb() + await db.init() + + const metadata = metadataStr ? parseJSON(metadataStr) : {} + + // Process metadata to handle noun type + if (metadata.noun) { + metadata.noun = resolveNounType(metadata.noun) + } + + const id = await db.add(text, metadata) + console.log(`Added noun with ID: ${id}`) + } catch (error) { + console.error('Error:', (error as Error).message) + process.exit(1) + } + }) + +program + .command('search') + .description('Search for nouns similar to the query') + .argument('', 'Search query text') + .option('-l, --limit ', 'Maximum number of results to return', '5') + .action(async (query, options) => { + try { + const db = createDb() + await db.init() + + const limit = parseInt(options.limit, 10) + const results = await db.searchText(query, limit) + + console.log(`Search results for "${query}":`) + results.forEach((result, index) => { + console.log(`${index + 1}. ID: ${result.id}`) + console.log(` Score: ${result.score.toFixed(4)}`) + console.log(` Metadata: ${JSON.stringify(result.metadata)}`) + console.log( + ` Vector: [${result.vector + .slice(0, 3) + .map((v) => v.toFixed(2)) + .join(', ')}...]` + ) + console.log() + }) + } catch (error) { + console.error('Error:', (error as Error).message) + process.exit(1) + } + }) + +program + .command('get') + .description('Get a noun by ID') + .argument('', 'ID of the noun to get') + .action(async (id) => { + try { + const db = createDb() + await db.init() + + const noun = await db.get(id) + if (noun) { + console.log(`Noun ID: ${noun.id}`) + console.log(`Metadata: ${JSON.stringify(noun.metadata)}`) + console.log( + `Vector: [${noun.vector + .slice(0, 5) + .map((v) => v.toFixed(2)) + .join(', ')}...]` + ) + } else { + console.log(`No noun found with ID: ${id}`) + } + } catch (error) { + console.error('Error:', (error as Error).message) + process.exit(1) + } + }) + +program + .command('delete') + .description('Delete a noun by ID') + .argument('', 'ID of the noun to delete') + .action(async (id) => { + try { + const db = createDb() + await db.init() + + await db.delete(id) + console.log(`Deleted noun with ID: ${id}`) + } catch (error) { + console.error('Error:', (error as Error).message) + process.exit(1) + } + }) + +program + .command('addVerb') + .description('Add a relationship between nouns') + .argument('', 'ID of the source noun') + .argument('', 'ID of the target noun') + .argument('', 'Type of relationship') + .argument('[metadata]', 'Optional metadata as JSON string') + .action(async (sourceId, targetId, verbTypeStr, metadataStr) => { + try { + const db = createDb() + await db.init() + + const verbType = resolveVerbType(verbTypeStr) + const verbMetadata = metadataStr ? parseJSON(metadataStr) : {} + + // Add verb type to metadata + verbMetadata.verb = verbType + + const verbId = await db.addVerb(sourceId, targetId, undefined, { + type: verbType, + metadata: verbMetadata + }) + console.log(`Added verb with ID: ${verbId}`) + } catch (error) { + console.error('Error:', (error as Error).message) + process.exit(1) + } + }) + +program + .command('getVerbs') + .description('Get all relationships for a noun') + .argument('', 'ID of the noun to get relationships for') + .action(async (id) => { + try { + const db = createDb() + await db.init() + + const verbs = await db.getVerbsBySource(id) + + console.log(`Relationships for noun ${id}:`) + if (verbs.length === 0) { + console.log('No relationships found') + } else { + verbs.forEach((verb, index) => { + console.log(`${index + 1}. ID: ${verb.id}`) + console.log( + ` Type: ${Object.keys(VerbType).find((key) => VerbType[key as keyof typeof VerbType] === verb.metadata.verb) || verb.metadata.verb}` + ) + console.log(` Target: ${verb.targetId}`) + console.log(` Metadata: ${JSON.stringify(verb.metadata)}`) + console.log() + }) + } + } catch (error) { + console.error('Error:', (error as Error).message) + process.exit(1) + } + }) + +program + .command('status') + .description('Show database status') + .action(async () => { + try { + const db = createDb() + await db.init() + + const status = await db.status() + console.log('Database Status:') + console.log(`Storage type: ${status.type}`) + console.log(`Storage used: ${status.used} bytes`) + console.log( + `Storage quota: ${status.quota !== null ? `${status.quota} bytes` : 'unlimited'}` + ) + + // Display additional details if available + if (status.details) { + console.log('Additional details:') + Object.entries(status.details).forEach(([key, value]) => { + console.log(` ${key}: ${JSON.stringify(value)}`) + }) + } + } catch (error) { + console.error('Error:', (error as Error).message) + process.exit(1) + } + }) + +program + .command('clear') + .description('Clear all data from the database') + .option('-f, --force', 'Skip confirmation prompt', false) + .action(async (options) => { + try { + // Confirm unless --force is used + if (!options.force) { + console.log( + 'WARNING: This will permanently delete ALL data in the database.' + ) + console.log('To proceed without confirmation, use the --force option.') + + // Exit without doing anything + console.log('Operation cancelled. No data was deleted.') + console.log('To clear all data, use: brainy clear --force') + return + } + + const db = createDb() + await db.init() + + await db.clear() + console.log('Database cleared successfully. All data has been removed.') + } catch (error) { + console.error('Error:', (error as Error).message) + process.exit(1) + } + }) + +program + .command('backup') + .description('Backup all data from the database to a JSON file') + .argument('[filename]', 'Output filename (default: brainy-backup.json)') + .action(async (filename) => { + try { + const db = createDb() + await db.init() + + // Default filename if not provided + const outputFile = filename || 'brainy-backup.json' + + // Backup the data + const data = await db.backup() + + // Write to file + fs.writeFileSync(outputFile, JSON.stringify(data, null, 2)) + + console.log(`Data backed up successfully to ${outputFile}`) + console.log( + `Backed up ${data.nouns.length} nouns and ${data.verbs.length} verbs` + ) + } catch (error) { + console.error('Error:', (error as Error).message) + process.exit(1) + } + }) + +program + .command('restore') + .description('Restore data from a JSON file into the database') + .argument('', 'Input JSON file') + .option('-c, --clear', 'Clear existing data before restoring', false) + .action(async (filename, options) => { + try { + const db = createDb() + await db.init() + + // Read the file + if (!fs.existsSync(filename)) { + console.error(`File not found: ${filename}`) + process.exit(1) + } + + const fileContent = fs.readFileSync(filename, 'utf8') + const data = JSON.parse(fileContent) + + // Restore the data + const result = await db.restore(data, { clearExisting: options.clear }) + + console.log(`Data restored successfully from ${filename}`) + console.log( + `Restored ${result.nounsRestored} nouns and ${result.verbsRestored} verbs` + ) + } catch (error) { + console.error('Error:', (error as Error).message) + process.exit(1) + } + }) + +program + .command('import-sparse') + .description( + 'Import sparse data (without vectors) from a JSON file into the database' + ) + .argument('', 'Input JSON file') + .option('-c, --clear', 'Clear existing data before importing', false) + .action(async (filename, options) => { + try { + const db = createDb() + await db.init() + + // Read the file + if (!fs.existsSync(filename)) { + console.error(`File not found: ${filename}`) + process.exit(1) + } + + const fileContent = fs.readFileSync(filename, 'utf8') + const data = JSON.parse(fileContent) + + // Import the sparse data + const result = await db.importSparseData(data, { + clearExisting: options.clear + }) + + console.log(`Sparse data imported successfully from ${filename}`) + console.log( + `Imported ${result.nounsRestored} nouns and ${result.verbsRestored} verbs` + ) + } catch (error) { + console.error('Error:', (error as Error).message) + process.exit(1) + } + }) + +program + .command('visualize') + .description('Visualize the graph structure in ASCII format') + .option('-r, --root ', 'ID of the root noun to start visualization from') + .option( + '-d, --depth ', + 'Maximum depth of the graph to visualize', + '2' + ) + .option('-t, --type ', 'Filter by noun type') + .option( + '-l, --limit ', + 'Maximum number of nodes to display per level', + '10' + ) + .action(async (options) => { + try { + const db = createDb() + await db.init() + + // Parse options + const depth = parseInt(options.depth, 10) + const limit = parseInt(options.limit, 10) + const rootId = options.root + const nounType = options.type ? resolveNounType(options.type) : undefined + + // Get all nouns if no root is specified + if (!rootId && !nounType) { + // Get all nouns (limited by the limit option) + const allNouns = [] + let count = 0 + + // Since there's no direct method to get all nouns, we'll use search with a high limit + const searchResults = await db.search('', 1000, { + forceEmbed: true + }) + + for (const result of searchResults) { + if (count >= limit) break + allNouns.push(result) + count++ + } + + if (allNouns.length === 0) { + console.log('No nouns found in the database.') + return + } + + console.log(`Graph Overview (showing ${allNouns.length} nouns):\n`) + + for (const noun of allNouns) { + // Get outgoing verbs + const outgoingVerbs = await db.getVerbsBySource(noun.id) + // Get incoming verbs + const incomingVerbs = await db.getVerbsByTarget(noun.id) + + const nounType = noun.metadata?.noun || 'Unknown' + const label = noun.metadata?.label || noun.id.substring(0, 8) + + console.log(`[${nounType}] ${label} (${noun.id})`) + + if (outgoingVerbs.length > 0) { + console.log(' Outgoing:') + for (const verb of outgoingVerbs.slice(0, limit)) { + const targetNoun = await db.get(verb.targetId) + const targetLabel = + targetNoun?.metadata?.label || verb.targetId.substring(0, 8) + console.log( + ` --(${verb.metadata?.verb || 'relates to'})--→ [${targetNoun?.metadata?.noun || 'Unknown'}] ${targetLabel}` + ) + } + if (outgoingVerbs.length > limit) { + console.log(` ... and ${outgoingVerbs.length - limit} more`) + } + } + + if (incomingVerbs.length > 0) { + console.log(' Incoming:') + for (const verb of incomingVerbs.slice(0, limit)) { + const sourceNoun = await db.get(verb.sourceId) + const sourceLabel = + sourceNoun?.metadata?.label || verb.sourceId.substring(0, 8) + console.log( + ` ←--(${verb.metadata?.verb || 'relates to'})-- [${sourceNoun?.metadata?.noun || 'Unknown'}] ${sourceLabel}` + ) + } + if (incomingVerbs.length > limit) { + console.log(` ... and ${incomingVerbs.length - limit} more`) + } + } + + console.log('') + } + + return + } + + // If noun type is specified but no root, show all nouns of that type + if (!rootId && nounType) { + console.log(`Visualizing nouns of type: ${nounType}\n`) + + // Search for nouns of the specified type + const searchResults = await db.search('', 1000, { + nounTypes: [nounType], + forceEmbed: true + }) + + const filteredNouns = searchResults.slice(0, limit) + + if (filteredNouns.length === 0) { + console.log(`No nouns found with type: ${nounType}`) + return + } + + for (const noun of filteredNouns) { + const label = noun.metadata?.label || noun.id.substring(0, 8) + + console.log(`[${nounType}] ${label} (${noun.id})`) + + // Get outgoing verbs + const outgoingVerbs = await db.getVerbsBySource(noun.id) + if (outgoingVerbs.length > 0) { + console.log(' Outgoing:') + for (const verb of outgoingVerbs.slice(0, limit)) { + const targetNoun = await db.get(verb.targetId) + const targetLabel = + targetNoun?.metadata?.label || verb.targetId.substring(0, 8) + console.log( + ` --(${verb.metadata?.verb || 'relates to'})--→ [${targetNoun?.metadata?.noun || 'Unknown'}] ${targetLabel}` + ) + } + if (outgoingVerbs.length > limit) { + console.log(` ... and ${outgoingVerbs.length - limit} more`) + } + } + + console.log('') + } + + return + } + + // If root is specified, visualize the graph starting from that root + if (rootId) { + const rootNoun = await db.get(rootId) + if (!rootNoun) { + console.error(`Root noun with ID ${rootId} not found`) + return + } + + console.log( + `Visualizing graph from root: ${rootNoun.metadata?.label || rootId}\n` + ) + + // Use a breadth-first search to visualize the graph + const visited = new Set() + const queue: Array<{ id: string; level: number; path: string }> = [ + { id: rootId, level: 0, path: '' } + ] + + while (queue.length > 0) { + const { id, level, path } = queue.shift()! + + if (visited.has(id) || level > depth) { + continue + } + + visited.add(id) + + const noun = await db.get(id) + if (!noun) { + console.warn(`Noun with ID ${id} not found`) + continue + } + + const nounType = noun.metadata?.noun || 'Unknown' + const label = noun.metadata?.label || id.substring(0, 8) + + // Print the current noun with proper indentation + console.log( + `${' '.repeat(level * 2)}${path}[${nounType}] ${label} (${id})` + ) + + // Get outgoing verbs + const outgoingVerbs = await db.getVerbsBySource(id) + + // Add target nouns to the queue for the next level + let verbCount = 0 + for (const verb of outgoingVerbs) { + if (verbCount >= limit) { + console.log( + `${' '.repeat((level + 1) * 2)}... and ${outgoingVerbs.length - limit} more` + ) + break + } + + const verbType = verb.metadata?.verb || 'relates to' + queue.push({ + id: verb.targetId, + level: level + 1, + path: `--(${verbType})--→ ` + }) + verbCount++ + } + } + } + } catch (error) { + console.error('Error:', (error as Error).message) + process.exit(1) + } + }) + +program + .command('generate-random-graph') + .description( + 'Generate a random graph of data with typed nouns and verbs for testing' + ) + .option('-n, --noun-count ', 'Number of nouns to generate', '10') + .option('-v, --verb-count ', 'Number of verbs to generate', '20') + .option('-c, --clear', 'Clear existing data before generating', false) + .option( + '-t, --noun-types ', + 'Comma-separated list of noun types to use' + ) + .option( + '-r, --verb-types ', + 'Comma-separated list of verb types to use' + ) + .action(async (options) => { + try { + const db = createDb() + await db.init() + + // Parse options + const nounCount = parseInt(options.nounCount, 10) + const verbCount = parseInt(options.verbCount, 10) + const clearExisting = options.clear + + // Parse noun types if provided + let nounTypes: NounType[] | undefined + if (options.nounTypes) { + const typeNames = options.nounTypes + .split(',') + .map((t: string) => t.trim()) + nounTypes = typeNames + .map((name: string) => { + // Try to match by key name (case insensitive) + const key = Object.keys(NounType).find( + (k) => k.toLowerCase() === name.toLowerCase() + ) + if (key) return NounType[key as keyof typeof NounType] + + // If not found by key, check if it's a valid value + if (Object.values(NounType).includes(name as NounType)) { + return name as NounType + } + + console.warn(`Warning: Unknown noun type "${name}", ignoring`) + return null + }) + .filter(Boolean) as NounType[] + } + + // Parse verb types if provided + let verbTypes: VerbType[] | undefined + if (options.verbTypes) { + const typeNames = options.verbTypes + .split(',') + .map((t: string) => t.trim()) + verbTypes = typeNames + .map((name: string) => { + // Try to match by key name (case insensitive) + const key = Object.keys(VerbType).find( + (k) => k.toLowerCase() === name.toLowerCase() + ) + if (key) return VerbType[key as keyof typeof VerbType] + + // If not found by key, check if it's a valid value + if (Object.values(VerbType).includes(name as VerbType)) { + return name as VerbType + } + + console.warn(`Warning: Unknown verb type "${name}", ignoring`) + return null + }) + .filter(Boolean) as VerbType[] + } + + console.log( + `Generating random graph with ${nounCount} nouns and ${verbCount} verbs...` + ) + if (clearExisting) { + console.log('Clearing existing data first...') + } + + const result = await db.generateRandomGraph({ + nounCount, + verbCount, + nounTypes, + verbTypes, + clearExisting + }) + + console.log('Random graph generated successfully!') + console.log( + `Created ${result.nounIds.length} nouns and ${result.verbIds.length} verbs` + ) + + // Print some sample IDs + if (result.nounIds.length > 0) { + console.log('\nSample noun IDs:') + result.nounIds.slice(0, 3).forEach((id) => console.log(`- ${id}`)) + if (result.nounIds.length > 3) { + console.log(`... and ${result.nounIds.length - 3} more`) + } + } + + if (result.verbIds.length > 0) { + console.log('\nSample verb IDs:') + result.verbIds.slice(0, 3).forEach((id) => console.log(`- ${id}`)) + if (result.verbIds.length > 3) { + console.log(`... and ${result.verbIds.length - 3} more`) + } + } + + console.log( + '\nUse the search, get, or visualize commands to explore the generated graph' + ) + } catch (error) { + console.error('Error:', (error as Error).message) + process.exit(1) + } + }) + +// Add demo to help text +program.addHelpText( + 'after', + ` +Examples: + $ brainy init + $ brainy add "Cats are independent pets" '{"noun":"Thing","category":"animal"}' + $ brainy search "feline pets" --limit 5 + $ brainy addVerb id1 id2 RelatedTo '{"description":"Both are pets"}' + $ brainy clear --force + $ brainy generate-random-graph --noun-count 20 --verb-count 30 --clear + $ brainy generate-random-graph --noun-types Person,Thing --verb-types RelatedTo,Owns + $ brainy visualize --type Thing --limit 10 + $ brainy visualize --root id1 --depth 3 + $ brainy backup my-database-backup.json + $ brainy restore my-database-backup.json --clear + + # Augmentation commands + $ brainy augment list + $ brainy augment info cognition + $ brainy augment test-pipeline "Test data" --data-type text --mode sequential + $ brainy augment stream-test --count 3 --interval 500 + +` +) + +// Setup autocomplete +const completion = omelette('brainy') + +// Helper function to get all noun types +const getNounTypes = () => Object.keys(NounType) + +// Helper function to get all verb types +const getVerbTypes = () => Object.keys(VerbType) + +// Define autocomplete handlers +completion.tree({ + // First level commands - suggest all available commands + _: () => [ + 'add', + 'addVerb', + 'search', + 'get', + 'delete', + 'getVerbs', + 'status', + 'clear', + 'visualize', + 'generate-random-graph', + 'backup', + 'restore', + 'import-sparse', + 'completion-setup', + 'init', + 'help', + 'augment' + ], + // Command-specific completions + add: { + // For the second argument of 'add' command (metadata) + _: () => { + // Generate templates for each noun type + return getNounTypes().map( + (type) => `{"noun":"${type}","category":"example"}` + ) + } + }, + addVerb: { + // First two arguments are IDs, third is verb type + '': { + '': { + _: () => { + // Suggest all available verb types + return getVerbTypes() + } + } + } + }, + // Add autocomplete for other commands + search: {}, + get: {}, + delete: {}, + getVerbs: {}, + status: {}, + clear: { + _: () => ['--force'] + }, + backup: { + _: () => ['brainy-backup.json', 'database-backup.json'] + }, + restore: { + _: () => ['--clear'] + }, + 'import-sparse': { + _: () => ['--clear'] + }, + 'generate-random-graph': { + _: () => [ + '--noun-count 10', + '--verb-count 20', + '--clear', + `--noun-types ${getNounTypes().join(',')}`, + `--verb-types ${getVerbTypes().join(',')}` + ] + }, + augment: { + _: () => ['list', 'info', 'test-pipeline', 'stream-test'], + info: { + _: () => [ + 'sense', + 'memory', + 'cognition', + 'conduit', + 'activation', + 'perception', + 'dialog', + 'websocket' + ] + }, + 'test-pipeline': { + _: () => [ + '--data-type text', + '--mode sequential', + '--mode parallel', + '--mode threaded', + '--stop-on-error', + '--verbose' + ] + }, + 'stream-test': { + _: () => ['--count 5', '--interval 1000', '--data-type text', '--verbose'] + } + }, + 'completion-setup': {}, + init: {}, + help: {} +}) + +// Initialize autocomplete +completion.init() + +// If this script is run with --completion-setup flag, set up the autocomplete +if (process.argv.includes('--completion-setup')) { + completion.setupShellInitFile() + console.log('Autocomplete setup complete. Please restart your shell.') + process.exit(0) +} + +// Pipeline and Augmentation Commands +const augmentCommand = new Command('augment').description( + 'Augmentation pipeline operations' +) + +augmentCommand + .command('list') + .description( + 'List all available augmentation types and registered augmentations' + ) + .action(async () => { + try { + // Initialize the pipeline + await augmentationPipeline.initialize() + + // Get available augmentation types + const availableTypes = + augmentationPipeline.getAvailableAugmentationTypes() + + console.log('Available Augmentation Types:') + if (availableTypes.length === 0) { + console.log(' No augmentation types available') + } else { + availableTypes.forEach((type) => { + const augmentations = + augmentationPipeline.getAugmentationsByType(type) + console.log( + `\n${type.toUpperCase()} (${augmentations.length} registered):` + ) + + if (augmentations.length === 0) { + console.log(' No augmentations registered for this type') + } else { + augmentations.forEach((aug) => { + console.log(` - ${aug.name}: ${aug.description}`) + console.log(` Status: ${aug.enabled ? 'Enabled' : 'Disabled'}`) + }) + } + }) + } + + // Show WebSocket augmentations separately + const webSocketAugs = augmentationPipeline.getWebSocketAugmentations() + console.log('\nWebSocket-Enabled Augmentations:') + if (webSocketAugs.length === 0) { + console.log(' No WebSocket-enabled augmentations available') + } else { + webSocketAugs.forEach((aug) => { + console.log(` - ${aug.name}: ${aug.description}`) + console.log(` Status: ${aug.enabled ? 'Enabled' : 'Disabled'}`) + }) + } + } catch (error) { + console.error('Error:', (error as Error).message) + process.exit(1) + } + }) + +augmentCommand + .command('test-pipeline') + .description('Test the sequential pipeline with sample data') + .argument( + '[text]', + 'Sample text to process through the pipeline', + 'This is a test of the Brainy pipeline' + ) + .option('-t, --data-type ', 'Type of data to process', 'text') + .option( + '-m, --mode ', + 'Execution mode (sequential, parallel, threaded)', + 'sequential' + ) + .option('-s, --stop-on-error', 'Stop execution if an error occurs', false) + .option('-v, --verbose', 'Show detailed output', false) + .action(async (text, options) => { + try { + // Initialize the pipeline + await sequentialPipeline.initialize() + + console.log(`Processing data: "${text}"`) + console.log(`Data type: ${options.dataType}`) + console.log(`Execution mode: ${options.mode}`) + console.log(`Stop on error: ${options.stopOnError}`) + console.log() + + // Set execution mode + let executionMode = ExecutionMode.SEQUENTIAL + switch (options.mode.toLowerCase()) { + case 'parallel': + executionMode = ExecutionMode.PARALLEL + break + case 'threaded': + executionMode = ExecutionMode.THREADED + break + default: + executionMode = ExecutionMode.SEQUENTIAL + } + + // Process the data + const result = await sequentialPipeline.processData( + text, + options.dataType, + { + stopOnError: options.stopOnError, + timeout: 30000 + } + ) + + console.log('Pipeline Execution Result:') + console.log(`Success: ${result.success}`) + + if (result.error) { + console.log(`Error: ${result.error}`) + } + + console.log('\nStage Results:') + + // Display stage results + Object.entries(result.stageResults).forEach(([stage, stageResult]) => { + console.log(`\n${stage.toUpperCase()}:`) + console.log(` Success: ${stageResult?.success}`) + + if (stageResult?.error) { + console.log(` Error: ${stageResult.error}`) + } + + if (stageResult?.data && options.verbose) { + console.log(' Data:') + console.log( + JSON.stringify(stageResult.data, null, 2) + .split('\n') + .map((line) => ` ${line}`) + .join('\n') + ) + } + }) + + console.log('\nFinal Result Data:') + console.log( + JSON.stringify(result.data, null, 2) + .split('\n') + .map((line) => ` ${line}`) + .join('\n') + ) + } catch (error) { + console.error('Error:', (error as Error).message) + process.exit(1) + } + }) + +augmentCommand + .command('stream-test') + .description('Test streaming data through the pipeline (simulated)') + .option('-c, --count ', 'Number of data items to stream', '5') + .option( + '-i, --interval ', + 'Interval between data items in milliseconds', + '1000' + ) + .option('-t, --data-type ', 'Type of data to process', 'text') + .option('-v, --verbose', 'Show detailed output', false) + .action(async (options) => { + try { + // Initialize the pipeline + await sequentialPipeline.initialize() + + const count = parseInt(options.count, 10) + const interval = parseInt(options.interval, 10) + + console.log( + `Simulating stream of ${count} data items at ${interval}ms intervals` + ) + console.log(`Data type: ${options.dataType}`) + console.log() + + // Create a handler function similar to what would be used with WebSockets + const handler = (data: string) => { + // Process the data asynchronously without blocking + sequentialPipeline + .processData(data, options.dataType, { stopOnError: false }) + .then((result) => { + console.log(`\nProcessed: "${data}"`) + console.log(`Success: ${result.success}`) + + if (options.verbose) { + console.log('Stage Results:') + Object.entries(result.stageResults).forEach( + ([stage, stageResult]) => { + if (stageResult?.success) { + console.log(` ${stage}: Success`) + } else { + console.log( + ` ${stage}: Failed - ${stageResult?.error || 'Unknown error'}` + ) + } + } + ) + } + + if (result.data) { + console.log('Result Data:') + console.log( + JSON.stringify(result.data, null, 2) + .split('\n') + .map((line) => ` ${line}`) + .join('\n') + ) + } + }) + .catch((error) => { + console.error(`Error processing "${data}":`, error.message) + }) + } + + // Generate sample data items + const sampleTexts = [ + 'The quick brown fox jumps over the lazy dog', + 'Artificial intelligence is transforming how we interact with data', + 'Vector databases enable semantic search capabilities', + 'Graph relationships connect entities in meaningful ways', + 'Streaming data requires efficient real-time processing', + 'WebSockets provide bidirectional communication channels', + 'Augmentations extend the functionality of the core system', + 'Sequential pipelines process data in defined stages', + 'Parallel execution improves throughput for large datasets', + 'Threaded operations utilize multiple CPU cores efficiently' + ] + + // Simulate streaming data + console.log('Starting simulated data stream...') + + for (let i = 0; i < count; i++) { + // Use modulo to cycle through sample texts if count > samples + const text = sampleTexts[i % sampleTexts.length] + + // Wait for the specified interval + if (i > 0) { + await new Promise((resolve) => setTimeout(resolve, interval)) + } + + console.log(`\nStreaming item ${i + 1}/${count}: "${text}"`) + + // Process the data + handler(text) + } + + console.log( + '\nSimulated stream complete. Some processing may still be ongoing.' + ) + console.log( + 'In a real WebSocket scenario, the connection would remain open for continuous data.' + ) + } catch (error) { + console.error('Error:', (error as Error).message) + process.exit(1) + } + }) + +augmentCommand + .command('info') + .description('Get detailed information about a specific augmentation type') + .argument( + '', + 'Augmentation type (sense, memory, cognition, conduit, activation, perception, dialog, websocket)' + ) + .action(async (typeArg) => { + try { + // Initialize the pipeline + await augmentationPipeline.initialize() + + // Resolve the augmentation type + let augType: AugmentationType | undefined + + // Convert input to proper enum value + const normalizedType = typeArg.toLowerCase() + switch (normalizedType) { + case 'sense': + augType = AugmentationType.SENSE + break + case 'memory': + augType = AugmentationType.MEMORY + break + case 'cognition': + augType = AugmentationType.COGNITION + break + case 'conduit': + augType = AugmentationType.CONDUIT + break + case 'activation': + augType = AugmentationType.ACTIVATION + break + case 'perception': + augType = AugmentationType.PERCEPTION + break + case 'dialog': + augType = AugmentationType.DIALOG + break + case 'websocket': + augType = AugmentationType.WEBSOCKET + break + default: + console.error(`Unknown augmentation type: ${typeArg}`) + console.log( + 'Available types: sense, memory, cognition, conduit, activation, perception, dialog, websocket' + ) + process.exit(1) + } + + // Get augmentations of the specified type + const augmentations = augmentationPipeline.getAugmentationsByType(augType) + + console.log(`\n${augType.toUpperCase()} Augmentation Details:`) + + if (augmentations.length === 0) { + console.log(' No augmentations registered for this type') + } else { + // Display information about each augmentation + augmentations.forEach((aug, index) => { + console.log(`\n${index + 1}. ${aug.name}`) + console.log(` Description: ${aug.description}`) + console.log(` Status: ${aug.enabled ? 'Enabled' : 'Disabled'}`) + + // List available methods + console.log(' Available Methods:') + + // Get all methods that aren't from Object.prototype + const methods = Object.getOwnPropertyNames( + Object.getPrototypeOf(aug) + ).filter( + (method) => + method !== 'constructor' && + typeof (aug as any)[method] === 'function' && + !['initialize', 'shutDown', 'getStatus'].includes(method) + ) + + if (methods.length === 0) { + console.log(' No custom methods available') + } else { + methods.forEach((method) => { + console.log(` - ${method}`) + }) + } + }) + } + + // Show pipeline order information + console.log('\nPipeline Execution Order:') + console.log( + ' 1. SENSE - Process raw data into structured nouns and verbs' + ) + console.log(' 2. MEMORY - Store and retrieve data') + console.log(' 3. COGNITION - Analyze and reason about data') + console.log(' 4. CONDUIT - Exchange data with external systems') + console.log(' 5. ACTIVATION - Trigger actions based on data') + console.log(' 6. PERCEPTION - Interpret and visualize data') + console.log(' 7. DIALOG - Process natural language interactions') + console.log( + ' * WEBSOCKET - Enable real-time communication (can be combined with other types)' + ) + } catch (error) { + console.error('Error:', (error as Error).message) + process.exit(1) + } + }) + +// Add the augment command to the program +program.addCommand(augmentCommand) + +// Add a command for setting up autocomplete +program + .command('completion-setup') + .description('Setup shell autocomplete for the Brainy CLI') + .action(() => { + completion.setupShellInitFile() + console.log('Autocomplete setup complete. Please restart your shell.') + }) + +// Parse command line arguments + +program.parse() diff --git a/src/coreTypes.ts b/src/coreTypes.ts new file mode 100644 index 00000000..a85f0c75 --- /dev/null +++ b/src/coreTypes.ts @@ -0,0 +1,163 @@ +/** + * Type definitions for the Soulcraft Brainy + */ + +/** + * Vector representation - an array of numbers + */ +export type Vector = number[] + +/** + * A document with a vector embedding and optional metadata + */ +export interface VectorDocument { + id: string + vector: Vector + metadata?: T +} + +/** + * Search result with similarity score + */ +export interface SearchResult { + id: string + score: number + vector: Vector + metadata?: T +} + +/** + * Distance function for comparing vectors + */ +export type DistanceFunction = (a: Vector, b: Vector) => number + +/** + * Embedding function for converting data to vectors + */ +export type EmbeddingFunction = (data: any) => Promise + +/** + * Embedding model interface + */ +export interface EmbeddingModel { + /** + * Initialize the embedding model + */ + init(): Promise + + /** + * Embed data into a vector + */ + embed(data: any): Promise + + /** + * Dispose of the model resources + */ + dispose(): Promise +} + +/** + * HNSW graph noun + */ +export interface HNSWNoun { + id: string + vector: Vector + connections: Map> // level -> set of connected noun ids +} + +/** + * Verb representing a relationship between nouns + * Extends HNSWNoun to allow verbs to be first-class entities in the data model + */ +export interface GraphVerb extends HNSWNoun { + sourceId: string // ID of the source noun + targetId: string // ID of the target noun + type?: string // Optional type of the relationship + weight?: number // Optional weight of the relationship + metadata?: any // Optional metadata for the verb + + // Additional properties used in the codebase + source?: string // Alias for sourceId + target?: string // Alias for targetId + verb?: string // Alias for type + data?: Record // Additional flexible data storage + embedding?: Vector // Vector representation of the relationship +} + +/** + * HNSW index configuration + */ +export interface HNSWConfig { + M: number // Maximum number of connections per noun + efConstruction: number // Size of the dynamic candidate list during construction + efSearch: number // Size of the dynamic candidate list during search + ml: number // Maximum level +} + +/** + * Storage interface for persistence + */ +export interface StorageAdapter { + init(): Promise + + saveNoun(noun: HNSWNoun): Promise + + getNoun(id: string): Promise + + getAllNouns(): Promise + + /** + * Get nouns by noun type + * @param nounType The noun type to filter by + * @returns Promise that resolves to an array of nouns of the specified noun type + */ + getNounsByNounType(nounType: string): Promise + + deleteNoun(id: string): Promise + + saveVerb(verb: GraphVerb): Promise + + getVerb(id: string): Promise + + getAllVerbs(): Promise + + getVerbsBySource(sourceId: string): Promise + + getVerbsByTarget(targetId: string): Promise + + getVerbsByType(type: string): Promise + + deleteVerb(id: string): Promise + + saveMetadata(id: string, metadata: any): Promise + + getMetadata(id: string): Promise + + clear(): Promise + + /** + * Get information about storage usage and capacity + * @returns Promise that resolves to an object containing storage status information + */ + getStorageStatus(): Promise<{ + /** + * The type of storage being used (e.g., 'filesystem', 'opfs', 'memory') + */ + type: string + + /** + * The amount of storage being used in bytes + */ + used: number + + /** + * The total amount of storage available in bytes, or null if unknown + */ + quota: number | null + + /** + * Additional storage-specific information + */ + details?: Record + }> +} diff --git a/src/examples/basicUsage.ts b/src/examples/basicUsage.ts new file mode 100644 index 00000000..83f881c4 --- /dev/null +++ b/src/examples/basicUsage.ts @@ -0,0 +1,163 @@ +/** + * Basic usage example for the Soulcraft Brainy database + */ + +import { BrainyData } from '../brainyData.js' + +// Example data - word embeddings +const wordEmbeddings = { + cat: [0.2, 0.3, 0.4, 0.1], + dog: [0.3, 0.2, 0.4, 0.2], + fish: [0.1, 0.1, 0.8, 0.2], + bird: [0.1, 0.4, 0.2, 0.5], + tiger: [0.3, 0.4, 0.3, 0.1], + lion: [0.4, 0.3, 0.2, 0.1], + shark: [0.2, 0.1, 0.7, 0.3], + eagle: [0.2, 0.5, 0.1, 0.4] +} + +// Example metadata +const metadata = { + cat: { type: 'mammal', domesticated: true }, + dog: { type: 'mammal', domesticated: true }, + fish: { type: 'fish', domesticated: false }, + bird: { type: 'bird', domesticated: false }, + tiger: { type: 'mammal', domesticated: false }, + lion: { type: 'mammal', domesticated: false }, + shark: { type: 'fish', domesticated: false }, + eagle: { type: 'bird', domesticated: false } +} + +/** + * Run the example + */ +async function runExample() { + console.log('Initializing vector database...') + + // Create a new vector database + const db = new BrainyData() + await db.init() + + console.log('Adding vectors to the database...') + + // Add vectors to the database + const ids: Record = {} + const metadata: Record = { + cat: { type: 'mammal', domesticated: true }, + dog: { type: 'mammal', domesticated: true }, + fish: { type: 'fish', domesticated: false }, + bird: { type: 'bird', domesticated: false }, + tiger: { type: 'mammal', domesticated: false }, + lion: { type: 'mammal', domesticated: false }, + shark: { type: 'fish', domesticated: false }, + eagle: { type: 'bird', domesticated: false } + } + for (const [word, vector] of Object.entries(wordEmbeddings)) { + ids[word] = await db.add(vector, metadata[word]) + + console.log(`Added "${word}" with ID: ${ids[word]}`) + } + + console.log('\nDatabase size:', db.size()) + + // Search for similar vectors + console.log('\nSearching for vectors similar to "cat"...') + const catResults = await db.search(wordEmbeddings['cat'], 3) + console.log('Results:') + for (const result of catResults) { + const word = + Object.entries(ids).find(([_, id]) => id === result.id)?.[0] || 'unknown' + console.log( + `- ${word} (score: ${result.score.toFixed(4)}, metadata:`, + result.metadata, + ')' + ) + } + + // Search for similar vectors + console.log('\nSearching for vectors similar to "fish"...') + const fishResults = await db.search(wordEmbeddings['fish'], 3) + console.log('Results:') + for (const result of fishResults) { + const word = + Object.entries(ids).find(([_, id]) => id === result.id)?.[0] || 'unknown' + console.log( + `- ${word} (score: ${result.score.toFixed(4)}, metadata:`, + result.metadata, + ')' + ) + } + + // Update metadata + console.log('\nUpdating metadata for "bird"...') + await db.updateMetadata(ids['bird'], { + ...metadata['bird'], + notes: 'Can fly' + }) + + // Get the updated document + const birdDoc = await db.get(ids['bird']) + console.log('Updated bird document:', birdDoc) + + // Delete a vector + console.log('\nDeleting "shark"...') + await db.delete(ids['shark']) + console.log('Database size after deletion:', db.size()) + + // Search again to verify shark is gone + console.log('\nSearching for vectors similar to "fish" after deletion...') + const fishResultsAfterDeletion = await db.search(wordEmbeddings['fish'], 3) + console.log('Results:') + for (const result of fishResultsAfterDeletion) { + const word = + Object.entries(ids).find(([_, id]) => id === result.id)?.[0] || 'unknown' + console.log( + `- ${word} (score: ${result.score.toFixed(4)}, metadata:`, + result.metadata, + ')' + ) + } + + console.log('\nExample completed successfully!') +} + +// Check if we're in a browser or Node.js environment +if (typeof window !== 'undefined') { + // Browser environment + document.addEventListener('DOMContentLoaded', () => { + const button = document.createElement('button') + button.textContent = 'Run BrainyData Example' + button.addEventListener('click', async () => { + const output = document.createElement('pre') + document.body.appendChild(output) + + // Redirect console.log to the output element + const originalLog = console.log + console.log = (...args) => { + originalLog(...args) + output.textContent += + args + .map((arg) => + typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg + ) + .join(' ') + '\n' + } + + try { + await runExample() + } catch (error) { + console.error('Error running example:', error) + } + + // Restore console.log + console.log = originalLog + }) + + document.body.appendChild(button) + }) +} else { + // Node.js environment + runExample().catch((error) => { + console.error('Error running example:', error) + }) +} diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 00000000..5b172476 --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1,15 @@ +/** + * 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; + }; +} + +// This export is needed to make this file a module +export {}; diff --git a/src/hnsw/hnswIndex.ts b/src/hnsw/hnswIndex.ts new file mode 100644 index 00000000..f489fcf9 --- /dev/null +++ b/src/hnsw/hnswIndex.ts @@ -0,0 +1,702 @@ +/** + * HNSW (Hierarchical Navigable Small World) Index implementation + * Based on the paper: "Efficient and robust approximate nearest neighbor search using Hierarchical Navigable Small World graphs" + */ + +import { DistanceFunction, HNSWConfig, HNSWNoun, Vector, VectorDocument } from '../coreTypes.js' +import { euclideanDistance } from '../utils/index.js' +import { executeInThread } from '../utils/workerUtils.js' + +// Default HNSW parameters +const DEFAULT_CONFIG: HNSWConfig = { + M: 16, // Max number of connections per noun + efConstruction: 200, // Size of a dynamic candidate list during construction + efSearch: 50, // Size of a dynamic candidate list during search + ml: 16 // Max level +} + +export class HNSWIndex { + private nouns: Map = new Map() + private entryPointId: string | null = null + private maxLevel = 0 + private config: HNSWConfig + private distanceFunction: DistanceFunction + private dimension: number | null = null + private useParallelization: boolean = true // Whether to use parallelization for performance-critical operations + + constructor( + config: Partial = {}, + distanceFunction: DistanceFunction = euclideanDistance, + options: { useParallelization?: boolean } = {} + ) { + this.config = { ...DEFAULT_CONFIG, ...config } + this.distanceFunction = distanceFunction + this.useParallelization = options.useParallelization !== undefined ? options.useParallelization : true + } + + /** + * Set whether to use parallelization for performance-critical operations + */ + public setUseParallelization(useParallelization: boolean): void { + this.useParallelization = useParallelization + } + + /** + * Get whether parallelization is enabled + */ + public getUseParallelization(): boolean { + return this.useParallelization + } + + /** + * Calculate distances between a query vector and multiple vectors in parallel + * This is used to optimize performance for search operations + * @param queryVector The query vector + * @param vectors Array of vectors to compare against + * @returns Array of distances + */ + private async calculateDistancesInParallel( + queryVector: Vector, + vectors: Array<{ id: string; vector: Vector }> + ): Promise> { + // If parallelization is disabled or there are very few vectors, use sequential processing + if (!this.useParallelization || vectors.length < 10) { + return vectors.map(item => ({ + id: item.id, + distance: this.distanceFunction(queryVector, item.vector) + })) + } + + // Function to be executed in a worker thread + const distanceCalculator = ( + query: Vector, + items: Array<{ id: string; vector: Vector }>, + distanceFn: string + ) => { + // Recreate the distance function from its string representation + const distanceFunction = new Function('return ' + distanceFn)() as DistanceFunction + + // Calculate distances for all items + return items.map(item => ({ + id: item.id, + distance: distanceFunction(query, item.vector) + })) + } + + try { + // Convert the distance function to a string for serialization + const distanceFnString = this.distanceFunction.toString() + + // Execute the distance calculation in a separate thread + return await executeInThread>( + distanceCalculator, + queryVector, + vectors, + distanceFnString + ) + } catch (error) { + console.error('Error in parallel distance calculation, falling back to sequential:', error) + // Fall back to sequential processing if parallel execution fails + return vectors.map(item => ({ + id: item.id, + distance: this.distanceFunction(queryVector, item.vector) + })) + } + } + + /** + * Add a vector to the index + */ + public async addItem(item: VectorDocument): Promise { + // Check if item is defined + if (!item) { + throw new Error('Item is undefined or null') + } + + const { id, vector } = item + + // Check if vector is defined + if (!vector) { + throw new Error('Vector is undefined or null') + } + + // Set dimension on first insert + if (this.dimension === null) { + this.dimension = vector.length + } else if (vector.length !== this.dimension) { + throw new Error( + `Vector dimension mismatch: expected ${this.dimension}, got ${vector.length}` + ) + } + + // Generate random level for this noun + const nounLevel = this.getRandomLevel() + + // Create new noun + const noun: HNSWNoun = { + id, + vector, + connections: new Map() + } + + // Initialize empty connection sets for each level + for (let level = 0; level <= nounLevel; level++) { + noun.connections.set(level, new Set()) + } + + // If this is the first noun, make it the entry point + if (this.nouns.size === 0) { + this.entryPointId = id + this.maxLevel = nounLevel + this.nouns.set(id, noun) + return id + } + + // Find entry point + if (!this.entryPointId) { + console.error('Entry point ID is null') + // If there's no entry point, this is the first noun, so we should have returned earlier + // This is a safety check + this.entryPointId = id + this.maxLevel = nounLevel + this.nouns.set(id, noun) + return id + } + + const entryPoint = this.nouns.get(this.entryPointId) + if (!entryPoint) { + console.error(`Entry point with ID ${this.entryPointId} not found`) + // If the entry point doesn't exist, treat this as the first noun + this.entryPointId = id + this.maxLevel = nounLevel + this.nouns.set(id, noun) + return id + } + + let currObj = entryPoint + let currDist = this.distanceFunction(vector, entryPoint.vector) + + // Traverse the graph from top to bottom to find the closest noun + for (let level = this.maxLevel; level > nounLevel; level--) { + let changed = true + while (changed) { + changed = false + + // Check all neighbors at current level + const connections = currObj.connections.get(level) || new Set() + + for (const neighborId of connections) { + const neighbor = this.nouns.get(neighborId) + if (!neighbor) { + // Skip neighbors that don't exist (expected during rapid additions/deletions) + continue + } + const distToNeighbor = this.distanceFunction(vector, neighbor.vector) + + if (distToNeighbor < currDist) { + currDist = distToNeighbor + currObj = neighbor + changed = true + } + } + } + } + + // For each level from nounLevel down to 0 + for (let level = Math.min(nounLevel, this.maxLevel); level >= 0; level--) { + // Find ef nearest elements using greedy search + const nearestNouns = await this.searchLayer( + vector, + currObj, + this.config.efConstruction, + level + ) + + // Select M nearest neighbors + const neighbors = this.selectNeighbors( + vector, + nearestNouns, + this.config.M + ) + + // Add bidirectional connections + for (const [neighborId, _] of neighbors) { + const neighbor = this.nouns.get(neighborId) + if (!neighbor) { + // Skip neighbors that don't exist (expected during rapid additions/deletions) + continue + } + + noun.connections.get(level)!.add(neighborId) + + // Add reverse connection + if (!neighbor.connections.has(level)) { + neighbor.connections.set(level, new Set()) + } + neighbor.connections.get(level)!.add(id) + + // Ensure neighbor doesn't have too many connections + if (neighbor.connections.get(level)!.size > this.config.M) { + this.pruneConnections(neighbor, level) + } + } + + // Update entry point for the next level + if (nearestNouns.size > 0) { + const [nearestId, nearestDist] = [...nearestNouns][0] + if (nearestDist < currDist) { + currDist = nearestDist + const nearestNoun = this.nouns.get(nearestId) + if (!nearestNoun) { + console.error(`Nearest noun with ID ${nearestId} not found in addItem`) + // Keep the current object as is + } else { + currObj = nearestNoun + } + } + } + } + + // Update max level and entry point if needed + if (nounLevel > this.maxLevel) { + this.maxLevel = nounLevel + this.entryPointId = id + } + + // Add noun to the index + this.nouns.set(id, noun) + return id + } + + /** + * Search for nearest neighbors + */ + public async search(queryVector: Vector, k: number = 10): Promise> { + if (this.nouns.size === 0) { + return [] + } + + // Check if query vector is defined + if (!queryVector) { + throw new Error('Query vector is undefined or null') + } + + if (this.dimension !== null && queryVector.length !== this.dimension) { + throw new Error( + `Query vector dimension mismatch: expected ${this.dimension}, got ${queryVector.length}` + ) + } + + // Start from the entry point + if (!this.entryPointId) { + console.error('Entry point ID is null') + return [] + } + + const entryPoint = this.nouns.get(this.entryPointId) + if (!entryPoint) { + console.error(`Entry point with ID ${this.entryPointId} not found`) + return [] + } + + let currObj = entryPoint + let currDist = this.distanceFunction(queryVector, currObj.vector) + + // Traverse the graph from top to bottom to find the closest noun + for (let level = this.maxLevel; level > 0; level--) { + let changed = true + while (changed) { + changed = false + + // Check all neighbors at current level + const connections = currObj.connections.get(level) || new Set() + + // If we have enough connections, use parallel distance calculation + if (this.useParallelization && connections.size >= 10) { + // Prepare vectors for parallel calculation + const vectors: Array<{ id: string; vector: Vector }> = [] + for (const neighborId of connections) { + const neighbor = this.nouns.get(neighborId) + if (!neighbor) continue + vectors.push({ id: neighborId, vector: neighbor.vector }) + } + + // Calculate distances in parallel + const distances = await this.calculateDistancesInParallel(queryVector, vectors) + + // Find the closest neighbor + for (const { id, distance } of distances) { + if (distance < currDist) { + currDist = distance + const neighbor = this.nouns.get(id) + if (neighbor) { + currObj = neighbor + changed = true + } + } + } + } else { + // Use sequential processing for small number of connections + for (const neighborId of connections) { + const neighbor = this.nouns.get(neighborId) + if (!neighbor) { + // Skip neighbors that don't exist (expected during rapid additions/deletions) + continue + } + const distToNeighbor = this.distanceFunction( + queryVector, + neighbor.vector + ) + + if (distToNeighbor < currDist) { + currDist = distToNeighbor + currObj = neighbor + changed = true + } + } + } + } + } + + // Search at level 0 with ef = k + const nearestNouns = await this.searchLayer( + queryVector, + currObj, + Math.max(this.config.efSearch, k), + 0 + ) + + // Convert to array and sort by distance + return [...nearestNouns].slice(0, k) + } + + /** + * Remove an item from the index + */ + public removeItem(id: string): boolean { + if (!this.nouns.has(id)) { + return false + } + + const noun = this.nouns.get(id)! + + // Remove connections to this noun from all neighbors + for (const [level, connections] of noun.connections.entries()) { + for (const neighborId of connections) { + const neighbor = this.nouns.get(neighborId) + if (!neighbor) { + // Skip neighbors that don't exist (expected during rapid additions/deletions) + continue + } + if (neighbor.connections.has(level)) { + neighbor.connections.get(level)!.delete(id) + + // Prune connections after removing this noun to ensure consistency + this.pruneConnections(neighbor, level) + } + } + } + + // Also check all other nouns for references to this noun and remove them + for (const [nounId, otherNoun] of this.nouns.entries()) { + if (nounId === id) continue // Skip the noun being removed + + for (const [level, connections] of otherNoun.connections.entries()) { + if (connections.has(id)) { + connections.delete(id) + + // Prune connections after removing this reference + this.pruneConnections(otherNoun, level) + } + } + } + + // Remove the noun + this.nouns.delete(id) + + // If we removed the entry point, find a new one + if (this.entryPointId === id) { + if (this.nouns.size === 0) { + this.entryPointId = null + this.maxLevel = 0 + } else { + // Find the noun with the highest level + let maxLevel = 0 + let newEntryPointId = null + + for (const [nounId, noun] of this.nouns.entries()) { + if (noun.connections.size === 0) continue // Skip nouns with no connections + + const nounLevel = Math.max(...noun.connections.keys()) + if (nounLevel >= maxLevel) { + maxLevel = nounLevel + newEntryPointId = nounId + } + } + + this.entryPointId = newEntryPointId + this.maxLevel = maxLevel + } + } + + return true + } + + /** + * Get all nouns in the index + */ + public getNouns(): Map { + return new Map(this.nouns) + } + + + /** + * Clear the index + */ + public clear(): void { + this.nouns.clear() + this.entryPointId = null + this.maxLevel = 0 + } + + /** + * Get the size of the index + */ + public size(): number { + return this.nouns.size + } + + /** + * Get the distance function used by the index + */ + public getDistanceFunction(): DistanceFunction { + return this.distanceFunction + } + + /** + * Get the entry point ID + */ + public getEntryPointId(): string | null { + return this.entryPointId + } + + /** + * Get the maximum level + */ + public getMaxLevel(): number { + return this.maxLevel + } + + /** + * Get the dimension + */ + public getDimension(): number | null { + return this.dimension + } + + /** + * Get the configuration + */ + public getConfig(): HNSWConfig { + return { ...this.config } + } + + /** + * Search within a specific layer + * Returns a map of noun IDs to distances, sorted by distance + */ + private async searchLayer( + queryVector: Vector, + entryPoint: HNSWNoun, + ef: number, + level: number + ): Promise> { + // Set of visited nouns + const visited = new Set([entryPoint.id]) + + // Priority queue of candidates (closest first) + const candidates = new Map() + candidates.set( + entryPoint.id, + this.distanceFunction(queryVector, entryPoint.vector) + ) + + // Priority queue of nearest neighbors found so far (closest first) + const nearest = new Map() + nearest.set( + entryPoint.id, + this.distanceFunction(queryVector, entryPoint.vector) + ) + + // While there are candidates to explore + while (candidates.size > 0) { + // Get closest candidate + const [closestId, closestDist] = [...candidates][0] + candidates.delete(closestId) + + // If this candidate is farther than the farthest in our result set, we're done + const farthestInNearest = [...nearest][nearest.size - 1] + if (nearest.size >= ef && closestDist > farthestInNearest[1]) { + break + } + + // Explore neighbors of the closest candidate + const noun = this.nouns.get(closestId) + if (!noun) { + console.error(`Noun with ID ${closestId} not found in searchLayer`) + continue + } + const connections = noun.connections.get(level) || new Set() + + // If we have enough connections and parallelization is enabled, use parallel distance calculation + if (this.useParallelization && connections.size >= 10) { + // Collect unvisited neighbors + const unvisitedNeighbors: Array<{ id: string; vector: Vector }> = [] + for (const neighborId of connections) { + if (!visited.has(neighborId)) { + visited.add(neighborId) + const neighbor = this.nouns.get(neighborId) + if (!neighbor) continue + unvisitedNeighbors.push({ id: neighborId, vector: neighbor.vector }) + } + } + + if (unvisitedNeighbors.length > 0) { + // Calculate distances in parallel + const distances = await this.calculateDistancesInParallel(queryVector, unvisitedNeighbors) + + // Process the results + for (const { id, distance } of distances) { + // If we haven't found ef nearest neighbors yet, or this neighbor is closer than the farthest one we've found + if (nearest.size < ef || distance < farthestInNearest[1]) { + candidates.set(id, distance) + nearest.set(id, distance) + + // If we have more than ef neighbors, remove the farthest one + if (nearest.size > ef) { + const sortedNearest = [...nearest].sort((a, b) => a[1] - b[1]) + nearest.clear() + for (let i = 0; i < ef; i++) { + nearest.set(sortedNearest[i][0], sortedNearest[i][1]) + } + } + } + } + } + } else { + // Use sequential processing for small number of connections + for (const neighborId of connections) { + if (!visited.has(neighborId)) { + visited.add(neighborId) + + const neighbor = this.nouns.get(neighborId) + if (!neighbor) { + // Skip neighbors that don't exist (expected during rapid additions/deletions) + continue + } + const distToNeighbor = this.distanceFunction( + queryVector, + neighbor.vector + ) + + // If we haven't found ef nearest neighbors yet, or this neighbor is closer than the farthest one we've found + if (nearest.size < ef || distToNeighbor < farthestInNearest[1]) { + candidates.set(neighborId, distToNeighbor) + nearest.set(neighborId, distToNeighbor) + + // If we have more than ef neighbors, remove the farthest one + if (nearest.size > ef) { + const sortedNearest = [...nearest].sort((a, b) => a[1] - b[1]) + nearest.clear() + for (let i = 0; i < ef; i++) { + nearest.set(sortedNearest[i][0], sortedNearest[i][1]) + } + } + } + } + } + } + } + + // Sort nearest by distance + return new Map([...nearest].sort((a, b) => a[1] - b[1])) + } + + /** + * Select M nearest neighbors from the candidate set + */ + private selectNeighbors( + queryVector: Vector, + candidates: Map, + M: number + ): Map { + if (candidates.size <= M) { + return candidates + } + + // Simple heuristic: just take the M closest + const sortedCandidates = [...candidates].sort((a, b) => a[1] - b[1]) + const result = new Map() + + for (let i = 0; i < Math.min(M, sortedCandidates.length); i++) { + result.set(sortedCandidates[i][0], sortedCandidates[i][1]) + } + + return result + } + + /** + * Ensure a noun doesn't have too many connections at a given level + */ + private pruneConnections(noun: HNSWNoun, level: number): void { + const connections = noun.connections.get(level)! + if (connections.size <= this.config.M) { + return + } + + // Calculate distances to all neighbors + const distances = new Map() + const validNeighborIds = new Set() + + for (const neighborId of connections) { + const neighbor = this.nouns.get(neighborId) + if (!neighbor) { + // Skip neighbors that don't exist (expected during rapid additions/deletions) + continue + } + + // Only add valid neighbors to the distances map + distances.set( + neighborId, + this.distanceFunction(noun.vector, neighbor.vector) + ) + validNeighborIds.add(neighborId) + } + + // Only proceed if we have valid neighbors + if (distances.size === 0) { + // If no valid neighbors, clear connections at this level + noun.connections.set(level, new Set()) + return + } + + // Select M closest neighbors from valid ones + const selectedNeighbors = this.selectNeighbors( + noun.vector, + distances, + this.config.M + ) + + // Update connections with only valid neighbors + noun.connections.set(level, new Set(selectedNeighbors.keys())) + } + + /** + * Generate a random level for a new noun + * Uses the same distribution as in the original HNSW paper + */ + private getRandomLevel(): number { + const r = Math.random() + return Math.floor(-Math.log(r) * (1.0 / Math.log(this.config.M))) + } +} diff --git a/src/hnsw/hnswIndexOptimized.ts b/src/hnsw/hnswIndexOptimized.ts new file mode 100644 index 00000000..8823486d --- /dev/null +++ b/src/hnsw/hnswIndexOptimized.ts @@ -0,0 +1,586 @@ +/** + * Optimized HNSW (Hierarchical Navigable Small World) Index implementation + * Extends the base HNSW implementation with support for large datasets + * Uses product quantization for dimensionality reduction and disk-based storage when needed + */ + +import { + DistanceFunction, + HNSWConfig, + HNSWNoun, + Vector, + VectorDocument +} from '../coreTypes.js' +import { HNSWIndex } from './hnswIndex.js' +import { StorageAdapter } from '../coreTypes.js' + +// Configuration for the optimized HNSW index +export interface HNSWOptimizedConfig extends HNSWConfig { + // Memory threshold in bytes - when exceeded, will use disk-based approach + memoryThreshold?: number + + // Product quantization settings + productQuantization?: { + // Whether to use product quantization + enabled: boolean + // Number of subvectors to split the vector into + numSubvectors?: number + // Number of centroids per subvector + numCentroids?: number + } + + // Whether to use disk-based storage for the index + useDiskBasedIndex?: boolean +} + +// Default configuration for the optimized HNSW index +const DEFAULT_OPTIMIZED_CONFIG: HNSWOptimizedConfig = { + M: 16, + efConstruction: 200, + efSearch: 50, + ml: 16, + memoryThreshold: 1024 * 1024 * 1024, // 1GB default threshold + productQuantization: { + enabled: false, + numSubvectors: 16, + numCentroids: 256 + }, + useDiskBasedIndex: false +} + +/** + * Product Quantization implementation + * Reduces vector dimensionality by splitting vectors into subvectors + * and quantizing each subvector to the nearest centroid + */ +class ProductQuantizer { + private numSubvectors: number + private numCentroids: number + private centroids: Vector[][] = [] + private subvectorSize: number = 0 + private initialized: boolean = false + private dimension: number = 0 + + constructor(numSubvectors: number = 16, numCentroids: number = 256) { + this.numSubvectors = numSubvectors + this.numCentroids = numCentroids + } + + /** + * Initialize the product quantizer with training data + * @param vectors Training vectors to use for learning centroids + */ + public train(vectors: Vector[]): void { + if (vectors.length === 0) { + throw new Error('Cannot train product quantizer with empty vector set') + } + + this.dimension = vectors[0].length + this.subvectorSize = Math.ceil(this.dimension / this.numSubvectors) + + // Initialize centroids for each subvector + for (let i = 0; i < this.numSubvectors; i++) { + // Extract subvectors from training data + const subvectors: Vector[] = vectors.map((vector) => { + const start = i * this.subvectorSize + const end = Math.min(start + this.subvectorSize, this.dimension) + return vector.slice(start, end) + }) + + // Initialize centroids for this subvector using k-means++ + this.centroids[i] = this.kMeansPlusPlus(subvectors, this.numCentroids) + } + + this.initialized = true + } + + /** + * Quantize a vector using product quantization + * @param vector Vector to quantize + * @returns Array of centroid indices, one for each subvector + */ + public quantize(vector: Vector): number[] { + if (!this.initialized) { + throw new Error('Product quantizer not initialized. Call train() first.') + } + + if (vector.length !== this.dimension) { + throw new Error( + `Vector dimension mismatch: expected ${this.dimension}, got ${vector.length}` + ) + } + + const codes: number[] = [] + + // Quantize each subvector + for (let i = 0; i < this.numSubvectors; i++) { + const start = i * this.subvectorSize + const end = Math.min(start + this.subvectorSize, this.dimension) + const subvector = vector.slice(start, end) + + // Find nearest centroid + let minDist = Number.MAX_VALUE + let nearestCentroidIndex = 0 + + for (let j = 0; j < this.centroids[i].length; j++) { + const centroid = this.centroids[i][j] + const dist = this.euclideanDistanceSquared(subvector, centroid) + + if (dist < minDist) { + minDist = dist + nearestCentroidIndex = j + } + } + + codes.push(nearestCentroidIndex) + } + + return codes + } + + /** + * Reconstruct a vector from its quantized representation + * @param codes Array of centroid indices + * @returns Reconstructed vector + */ + public reconstruct(codes: number[]): Vector { + if (!this.initialized) { + throw new Error('Product quantizer not initialized. Call train() first.') + } + + if (codes.length !== this.numSubvectors) { + throw new Error( + `Code length mismatch: expected ${this.numSubvectors}, got ${codes.length}` + ) + } + + const reconstructed: Vector = [] + + // Reconstruct each subvector + for (let i = 0; i < this.numSubvectors; i++) { + const centroidIndex = codes[i] + const centroid = this.centroids[i][centroidIndex] + + // Add centroid components to reconstructed vector + for (const component of centroid) { + reconstructed.push(component) + } + } + + // Trim to original dimension if needed + return reconstructed.slice(0, this.dimension) + } + + /** + * Compute squared Euclidean distance between two vectors + * @param a First vector + * @param b Second vector + * @returns Squared Euclidean distance + */ + private euclideanDistanceSquared(a: Vector, b: Vector): number { + let sum = 0 + const length = Math.min(a.length, b.length) + + for (let i = 0; i < length; i++) { + const diff = a[i] - b[i] + sum += diff * diff + } + + return sum + } + + /** + * Implement k-means++ algorithm to initialize centroids + * @param vectors Vectors to cluster + * @param k Number of clusters + * @returns Array of centroids + */ + private kMeansPlusPlus(vectors: Vector[], k: number): Vector[] { + if (vectors.length < k) { + // If we have fewer vectors than centroids, use the vectors as centroids + return [...vectors] + } + + const centroids: Vector[] = [] + + // Choose first centroid randomly + const firstIndex = Math.floor(Math.random() * vectors.length) + centroids.push([...vectors[firstIndex]]) + + // Choose remaining centroids + for (let i = 1; i < k; i++) { + // Compute distances to nearest centroid for each vector + const distances: number[] = vectors.map((vector) => { + let minDist = Number.MAX_VALUE + + for (const centroid of centroids) { + const dist = this.euclideanDistanceSquared(vector, centroid) + minDist = Math.min(minDist, dist) + } + + return minDist + }) + + // Compute sum of distances + const distSum = distances.reduce((sum, dist) => sum + dist, 0) + + // Choose next centroid with probability proportional to distance + let r = Math.random() * distSum + let nextIndex = 0 + + for (let j = 0; j < distances.length; j++) { + r -= distances[j] + if (r <= 0) { + nextIndex = j + break + } + } + + centroids.push([...vectors[nextIndex]]) + } + + return centroids + } + + /** + * Get the centroids for each subvector + * @returns Array of centroid arrays + */ + public getCentroids(): Vector[][] { + return this.centroids + } + + /** + * Set the centroids for each subvector + * @param centroids Array of centroid arrays + */ + public setCentroids(centroids: Vector[][]): void { + this.centroids = centroids + this.numSubvectors = centroids.length + this.numCentroids = centroids[0].length + this.initialized = true + } + + /** + * Get the dimension of the vectors + * @returns Dimension + */ + public getDimension(): number { + return this.dimension + } + + /** + * Set the dimension of the vectors + * @param dimension Dimension + */ + public setDimension(dimension: number): void { + this.dimension = dimension + this.subvectorSize = Math.ceil(dimension / this.numSubvectors) + } +} + +/** + * Optimized HNSW Index implementation + * Extends the base HNSW implementation with support for large datasets + * Uses product quantization for dimensionality reduction and disk-based storage when needed + */ +export class HNSWIndexOptimized extends HNSWIndex { + private optimizedConfig: HNSWOptimizedConfig + private productQuantizer: ProductQuantizer | null = null + private storage: StorageAdapter | null = null + private useDiskBasedIndex: boolean = false + private useProductQuantization: boolean = false + private quantizedVectors: Map = new Map() + private memoryUsage: number = 0 + private vectorCount: number = 0 + + constructor( + config: Partial = {}, + distanceFunction: DistanceFunction, + storage: StorageAdapter | null = null + ) { + // Initialize base HNSW index with standard config + super(config, distanceFunction) + + // Set optimized config + this.optimizedConfig = { ...DEFAULT_OPTIMIZED_CONFIG, ...config } + + // Set storage adapter + this.storage = storage + + // Initialize product quantizer if enabled + if (this.optimizedConfig.productQuantization?.enabled) { + this.useProductQuantization = true + this.productQuantizer = new ProductQuantizer( + this.optimizedConfig.productQuantization.numSubvectors, + this.optimizedConfig.productQuantization.numCentroids + ) + } + + // Set disk-based index flag + this.useDiskBasedIndex = this.optimizedConfig.useDiskBasedIndex || false + } + + /** + * Add a vector to the index + * Uses product quantization if enabled and memory threshold is exceeded + */ + public override async addItem(item: VectorDocument): Promise { + // Check if item is defined + if (!item) { + throw new Error('Item is undefined or null') + } + + const { id, vector } = item + + // Check if vector is defined + if (!vector) { + throw new Error('Vector is undefined or null') + } + + // Estimate memory usage for this vector + const vectorMemory = vector.length * 8 // 8 bytes per number (Float64) + const connectionsMemory = this.optimizedConfig.M * this.optimizedConfig.ml * 16 // Estimate for connections + const totalMemory = vectorMemory + connectionsMemory + + // Update memory usage estimate + this.memoryUsage += totalMemory + this.vectorCount++ + + // Check if we should switch to product quantization + if ( + this.useProductQuantization && + this.memoryUsage > this.optimizedConfig.memoryThreshold! && + this.productQuantizer && + !this.productQuantizer.getDimension() + ) { + // Initialize product quantizer with existing vectors + this.initializeProductQuantizer() + } + + // If product quantization is active, quantize the vector + if ( + this.useProductQuantization && + this.productQuantizer && + this.productQuantizer.getDimension() > 0 + ) { + // Quantize the vector + const codes = this.productQuantizer.quantize(vector) + + // Store the quantized vector + this.quantizedVectors.set(id, codes) + + // Reconstruct the vector for indexing + const reconstructedVector = this.productQuantizer.reconstruct(codes) + + // Add the reconstructed vector to the index + return await super.addItem({ id, vector: reconstructedVector }) + } + + // If disk-based index is active and storage is available, store the vector + if (this.useDiskBasedIndex && this.storage) { + // Create a noun object + const noun: HNSWNoun = { + id, + vector, + connections: new Map() + } + + // Store the noun + this.storage.saveNoun(noun).catch((error) => { + console.error(`Failed to save noun ${id} to storage:`, error) + }) + } + + // Add the vector to the in-memory index + return await super.addItem(item) + } + + /** + * Search for nearest neighbors + * Uses product quantization if enabled + */ + public override async search( + queryVector: Vector, + k: number = 10 + ): Promise> { + // Check if query vector is defined + if (!queryVector) { + throw new Error('Query vector is undefined or null') + } + + // If product quantization is active, quantize the query vector + if ( + this.useProductQuantization && + this.productQuantizer && + this.productQuantizer.getDimension() > 0 + ) { + // Quantize the query vector + const codes = this.productQuantizer.quantize(queryVector) + + // Reconstruct the query vector + const reconstructedVector = this.productQuantizer.reconstruct(codes) + + // Search with the reconstructed vector + return await super.search(reconstructedVector, k) + } + + // Otherwise, use the standard search + return await super.search(queryVector, k) + } + + /** + * Remove an item from the index + */ + public override removeItem(id: string): boolean { + // If product quantization is active, remove the quantized vector + if (this.useProductQuantization) { + this.quantizedVectors.delete(id) + } + + // If disk-based index is active and storage is available, remove the vector from storage + if (this.useDiskBasedIndex && this.storage) { + this.storage.deleteNoun(id).catch((error) => { + console.error(`Failed to delete noun ${id} from storage:`, error) + }) + } + + // Update memory usage estimate + if (this.vectorCount > 0) { + this.memoryUsage = Math.max( + 0, + this.memoryUsage - this.memoryUsage / this.vectorCount + ) + this.vectorCount-- + } + + // Remove the item from the in-memory index + return super.removeItem(id) + } + + /** + * Clear the index + */ + public override clear(): void { + // Clear product quantization data + if (this.useProductQuantization) { + this.quantizedVectors.clear() + this.productQuantizer = new ProductQuantizer( + this.optimizedConfig.productQuantization!.numSubvectors, + this.optimizedConfig.productQuantization!.numCentroids + ) + } + + // Reset memory usage + this.memoryUsage = 0 + this.vectorCount = 0 + + // Clear the in-memory index + super.clear() + } + + /** + * Initialize product quantizer with existing vectors + */ + private initializeProductQuantizer(): void { + if (!this.productQuantizer) { + return + } + + // Get all vectors from the index + const nouns = super.getNouns() + const vectors: Vector[] = [] + + // Extract vectors + for (const [_, noun] of nouns) { + vectors.push(noun.vector) + } + + // Train the product quantizer + if (vectors.length > 0) { + this.productQuantizer.train(vectors) + + // Quantize all existing vectors + for (const [id, noun] of nouns) { + const codes = this.productQuantizer.quantize(noun.vector) + this.quantizedVectors.set(id, codes) + } + + console.log( + `Initialized product quantizer with ${vectors.length} vectors` + ) + } + } + + /** + * Get the product quantizer + * @returns Product quantizer or null if not enabled + */ + public getProductQuantizer(): ProductQuantizer | null { + return this.productQuantizer + } + + /** + * Get the optimized configuration + * @returns Optimized configuration + */ + public getOptimizedConfig(): HNSWOptimizedConfig { + return { ...this.optimizedConfig } + } + + /** + * Get the estimated memory usage + * @returns Estimated memory usage in bytes + */ + public getMemoryUsage(): number { + return this.memoryUsage + } + + /** + * Set the storage adapter + * @param storage Storage adapter + */ + public setStorage(storage: StorageAdapter): void { + this.storage = storage + } + + /** + * Get the storage adapter + * @returns Storage adapter or null if not set + */ + public getStorage(): StorageAdapter | null { + return this.storage + } + + /** + * Set whether to use disk-based index + * @param useDiskBasedIndex Whether to use disk-based index + */ + public setUseDiskBasedIndex(useDiskBasedIndex: boolean): void { + this.useDiskBasedIndex = useDiskBasedIndex + } + + /** + * Get whether disk-based index is used + * @returns Whether disk-based index is used + */ + public getUseDiskBasedIndex(): boolean { + return this.useDiskBasedIndex + } + + /** + * Set whether to use product quantization + * @param useProductQuantization Whether to use product quantization + */ + public setUseProductQuantization(useProductQuantization: boolean): void { + this.useProductQuantization = useProductQuantization + } + + /** + * Get whether product quantization is used + * @returns Whether product quantization is used + */ + public getUseProductQuantization(): boolean { + return this.useProductQuantization + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..a638d5f7 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,351 @@ +/** + * OPFS BrainyData + * A vector database using HNSW indexing with Origin Private File System storage + */ + +// Export main BrainyData class and related types +import { BrainyData, BrainyDataConfig } from './brainyData.js' + +export { BrainyData } +export type { BrainyDataConfig } + +// Export distance functions for convenience +import { + euclideanDistance, + cosineDistance, + manhattanDistance, + dotProductDistance +} from './utils/index.js' + +export { + euclideanDistance, + cosineDistance, + manhattanDistance, + dotProductDistance +} + +// Export embedding functionality +import { + UniversalSentenceEncoder, + createEmbeddingFunction, + createTensorFlowEmbeddingFunction, + createThreadedEmbeddingFunction, + defaultEmbeddingFunction +} from './utils/embedding.js' + +export { + UniversalSentenceEncoder, + createEmbeddingFunction, + createTensorFlowEmbeddingFunction, + createThreadedEmbeddingFunction, + defaultEmbeddingFunction +} + +// Export storage adapters +import { + OPFSStorage, + MemoryStorage, + createStorage +} from './storage/opfsStorage.js' +import { FileSystemStorage } from './storage/fileSystemStorage.js' +import { R2Storage, S3CompatibleStorage } from './storage/s3CompatibleStorage.js' + +export { + OPFSStorage, + MemoryStorage, + FileSystemStorage, + R2Storage, + S3CompatibleStorage, + createStorage +} + +// Export unified pipeline +import { + Pipeline, + pipeline, + augmentationPipeline, + ExecutionMode, + PipelineOptions, + PipelineResult, + executeStreamlined, + executeByType, + executeSingle, + processStaticData, + processStreamingData, + createPipeline, + createStreamingPipeline, + StreamlinedExecutionMode, + StreamlinedPipelineOptions, + StreamlinedPipelineResult +} from './pipeline.js' + +// Export sequential pipeline (for backward compatibility) +import { + SequentialPipeline, + sequentialPipeline, + SequentialPipelineOptions +} from './sequentialPipeline.js' + +// Export augmentation factory +import { + createSenseAugmentation, + addWebSocketSupport, + executeAugmentation, + loadAugmentationModule, + AugmentationOptions +} from './augmentationFactory.js' + +export { + // Unified pipeline exports + Pipeline, + pipeline, + augmentationPipeline, + ExecutionMode, + SequentialPipeline, + sequentialPipeline, + + // Streamlined pipeline exports (now part of unified pipeline) + executeStreamlined, + executeByType, + executeSingle, + processStaticData, + processStreamingData, + createPipeline, + createStreamingPipeline, + StreamlinedExecutionMode, + + // Augmentation factory exports + createSenseAugmentation, + addWebSocketSupport, + executeAugmentation, + loadAugmentationModule +} +export type { + PipelineOptions, + PipelineResult, + SequentialPipelineOptions, + StreamlinedPipelineOptions, + StreamlinedPipelineResult, + AugmentationOptions +} + +// Export augmentation registry for build-time loading +import { + availableAugmentations, + registerAugmentation, + initializeAugmentationPipeline, + setAugmentationEnabled, + getAugmentationsByType +} from './augmentationRegistry.js' + +export { + availableAugmentations, + registerAugmentation, + initializeAugmentationPipeline, + setAugmentationEnabled, + getAugmentationsByType +} + +// Export augmentation registry loader for build tools +import { + loadAugmentationsFromModules, + createAugmentationRegistryPlugin, + createAugmentationRegistryRollupPlugin +} from './augmentationRegistryLoader.js' +import type { + AugmentationRegistryLoaderOptions, + AugmentationLoadResult +} from './augmentationRegistryLoader.js' + +export { + loadAugmentationsFromModules, + createAugmentationRegistryPlugin, + createAugmentationRegistryRollupPlugin +} +export type { + AugmentationRegistryLoaderOptions, + AugmentationLoadResult +} + +// Export augmentation implementations +import { + MemoryStorageAugmentation, + FileSystemStorageAugmentation, + OPFSStorageAugmentation, + createMemoryAugmentation +} from './augmentations/memoryAugmentations.js' +import { + WebSocketConduitAugmentation, + WebRTCConduitAugmentation, + createConduitAugmentation +} from './augmentations/conduitAugmentations.js' +import { + ServerSearchConduitAugmentation, + ServerSearchActivationAugmentation, + createServerSearchAugmentations +} from './augmentations/serverSearchAugmentations.js' + +// Non-LLM exports +export { + MemoryStorageAugmentation, + FileSystemStorageAugmentation, + OPFSStorageAugmentation, + createMemoryAugmentation, + WebSocketConduitAugmentation, + WebRTCConduitAugmentation, + createConduitAugmentation, + ServerSearchConduitAugmentation, + ServerSearchActivationAugmentation, + createServerSearchAugmentations +} + +// LLM augmentations are optional and not imported by default +// They can be imported directly from their module if needed: +// import { LLMCognitionAugmentation, LLMActivationAugmentation, createLLMAugmentations } from './augmentations/llmAugmentations.js' + + +// Export types +import type { + Vector, + VectorDocument, + SearchResult, + DistanceFunction, + EmbeddingFunction, + EmbeddingModel, + HNSWNoun, + GraphVerb, + HNSWConfig, + StorageAdapter +} from './coreTypes.js' + +// Export HNSW index and optimized version +import { HNSWIndex } from './hnsw/hnswIndex.js' +import { HNSWIndexOptimized, HNSWOptimizedConfig } from './hnsw/hnswIndexOptimized.js' + +export { + HNSWIndex, + HNSWIndexOptimized +} + +export type { + Vector, + VectorDocument, + SearchResult, + DistanceFunction, + EmbeddingFunction, + EmbeddingModel, + HNSWNoun, + GraphVerb, + HNSWConfig, + HNSWOptimizedConfig, + StorageAdapter +} + +// Export augmentation types +import type { + IAugmentation, + AugmentationResponse, + IWebSocketSupport, + ISenseAugmentation, + IConduitAugmentation, + ICognitionAugmentation, + IMemoryAugmentation, + IPerceptionAugmentation, + IDialogAugmentation, + IActivationAugmentation +} from './types/augmentations.js' +import { AugmentationType, BrainyAugmentations } from './types/augmentations.js' + +export type { + IAugmentation, + AugmentationResponse, + IWebSocketSupport +} +export { + AugmentationType, + BrainyAugmentations, + ISenseAugmentation, + IConduitAugmentation, + ICognitionAugmentation, + IMemoryAugmentation, + IPerceptionAugmentation, + IDialogAugmentation, + IActivationAugmentation +} + +// Export combined WebSocket augmentation interfaces +export type { + IWebSocketCognitionAugmentation, + IWebSocketSenseAugmentation, + IWebSocketPerceptionAugmentation, + IWebSocketActivationAugmentation, + IWebSocketDialogAugmentation, + IWebSocketConduitAugmentation, + IWebSocketMemoryAugmentation +} from './types/augmentations.js' + +// Export graph types +import type { + GraphNoun, + EmbeddedGraphVerb, + Person, + Place, + Thing, + Event, + Concept, + Content +} from './types/graphTypes.js' +import { NounType, VerbType } from './types/graphTypes.js' + +export type { + GraphNoun, + EmbeddedGraphVerb, + Person, + Place, + Thing, + Event, + Concept, + Content +} +export { NounType, VerbType } + +// Export MCP (Model Control Protocol) components +import { + BrainyMCPAdapter, + MCPAugmentationToolset, + BrainyMCPService +} from './mcp/index.js' // Import from mcp/index.js +import { + MCPRequest, + MCPResponse, + MCPDataAccessRequest, + MCPToolExecutionRequest, + MCPSystemInfoRequest, + MCPAuthenticationRequest, + MCPRequestType, + MCPServiceOptions, + MCPTool, + MCP_VERSION +} from './types/mcpTypes.js' + +export { + // MCP classes + BrainyMCPAdapter, + MCPAugmentationToolset, + BrainyMCPService, + + // MCP types + MCPRequestType, + MCP_VERSION +} + +export type { + MCPRequest, + MCPResponse, + MCPDataAccessRequest, + MCPToolExecutionRequest, + MCPSystemInfoRequest, + MCPAuthenticationRequest, + MCPServiceOptions, + MCPTool +} diff --git a/src/mcp/README.md b/src/mcp/README.md new file mode 100644 index 00000000..051fa368 --- /dev/null +++ b/src/mcp/README.md @@ -0,0 +1,180 @@ +# Model Control Protocol (MCP) for Brainy + +This document provides information about the Model Control Protocol (MCP) implementation in Brainy, which allows external models to access Brainy data and use the augmentation pipeline as tools. + +## Components + +The MCP implementation consists of three main components: + +1. **BrainyMCPAdapter**: Provides access to Brainy data through MCP +2. **MCPAugmentationToolset**: Exposes the augmentation pipeline as tools +3. **BrainyMCPService**: Integrates the adapter and toolset, providing WebSocket and REST server implementations for external model access + +## Environment Compatibility + +### BrainyMCPAdapter + +The `BrainyMCPAdapter` has no environment-specific dependencies and can run in any environment where Brainy itself runs, including: + +- Browser environments +- Node.js environments +- Server environments + +### MCPAugmentationToolset + +The `MCPAugmentationToolset` also has no environment-specific dependencies and can run in any environment where Brainy itself runs, including: + +- Browser environments +- Node.js environments +- Server environments + +### BrainyMCPService + +The `BrainyMCPService` has been refactored to separate the core functionality from the Node.js-specific server functionality: + +1. **Core Functionality**: The core request handling functionality (`handleMCPRequest`) can run in any environment where Brainy itself runs. This is what remains in the main Brainy package. + +2. **Server Functionality**: The WebSocket and REST server functionality has been moved to the cloud-wrapper project to avoid including Node.js-specific dependencies in the browser bundle: + - `ws` for WebSocket server + - `express` for REST API + - `cors` for Cross-Origin Resource Sharing + +This separation ensures that the browser bundle remains lightweight and doesn't include unnecessary Node.js-specific dependencies. In browser or other environments, you can still use the core functionality through the `handleMCPRequest` method. + +## Usage + +### In Any Environment (Browser, Node.js, Server) + +```typescript +import { BrainyData, BrainyMCPAdapter, MCPAugmentationToolset } from '@soulcraft/brainy' + +// Create a BrainyData instance +const brainyData = new BrainyData() +await brainyData.init() + +// Create an MCP adapter +const adapter = new BrainyMCPAdapter(brainyData) + +// Create a toolset +const toolset = new MCPAugmentationToolset() + +// Use the adapter to access Brainy data +const response = await adapter.handleRequest({ + type: 'data_access', + operation: 'search', + requestId: adapter.generateRequestId(), + version: '1.0.0', + parameters: { + query: 'example query', + k: 5 + } +}) + +// Use the toolset to execute augmentation pipeline tools +const toolResponse = await toolset.handleRequest({ + type: 'tool_execution', + toolName: 'brainy_memory_storeData', + requestId: toolset.generateRequestId(), + version: '1.0.0', + parameters: { + args: ['key1', { some: 'data' }] + } +}) +``` + +### In Node.js Environment with Server Functionality + +To use the MCP service with WebSocket and REST server functionality, you should use the cloud-wrapper project: + +```typescript +import { BrainyData } from '@soulcraft/brainy' +import { initializeBrainy } from './services/brainyService.js' +import { initializeMCPService } from './services/mcpService.js' + +// Initialize Brainy +const brainyData = await initializeBrainy() + +// Initialize MCP service with WebSocket and REST server functionality +const mcpService = initializeMCPService(brainyData, { + wsPort: 8080, + restPort: 3000, + enableAuth: true, + apiKeys: ['your-api-key'], + rateLimit: { + maxRequests: 100, + windowMs: 60000 // 1 minute + }, + cors: { + origin: '*', + credentials: true + } +}) +``` + +Alternatively, you can configure the MCP service using environment variables in the cloud-wrapper: + +``` +# MCP configuration +MCP_WS_PORT=8080 +MCP_REST_PORT=3000 +MCP_ENABLE_AUTH=true +MCP_API_KEYS=your-api-key,another-key +MCP_RATE_LIMIT_REQUESTS=100 +MCP_RATE_LIMIT_WINDOW_MS=60000 +MCP_ENABLE_CORS=true +``` + +### In Browser Environment (Core Functionality Only) + +```typescript +import { BrainyData, BrainyMCPService } from '@soulcraft/brainy' + +// Create a BrainyData instance +const brainyData = new BrainyData() +await brainyData.init() + +// Create an MCP service (server functionality will be disabled in browser) +const mcpService = new BrainyMCPService(brainyData) + +// Use the core functionality +const response = await mcpService.handleMCPRequest({ + type: 'data_access', + operation: 'search', + requestId: mcpService.generateRequestId(), + version: '1.0.0', + parameters: { + query: 'example query', + k: 5 + } +}) +``` + +## Cloud Wrapper Integration + +The MCP service's server functionality has been integrated directly into the cloud-wrapper project. The cloud-wrapper automatically initializes the MCP service if the appropriate environment variables are set: + +``` +# MCP configuration +MCP_WS_PORT=8080 +MCP_REST_PORT=3000 +MCP_ENABLE_AUTH=true +MCP_API_KEYS=your-api-key,another-key +MCP_RATE_LIMIT_REQUESTS=100 +MCP_RATE_LIMIT_WINDOW_MS=60000 +MCP_ENABLE_CORS=true +``` + +You can deploy the cloud wrapper to various cloud platforms using the npm scripts from the root directory: + +```bash +# Deploy to AWS Lambda and API Gateway +npm run deploy:cloud:aws + +# Deploy to Google Cloud Run +npm run deploy:cloud:gcp + +# Deploy to Cloudflare Workers +npm run deploy:cloud:cloudflare +``` + +The cloud wrapper is specifically designed for server environments and includes additional features like logging, security headers, and deployment scripts for various cloud providers. See the [Cloud Wrapper README](../../cloud-wrapper/README.md) for detailed configuration instructions and API documentation. diff --git a/src/mcp/brainyMCPAdapter.ts b/src/mcp/brainyMCPAdapter.ts new file mode 100644 index 00000000..1ffcd853 --- /dev/null +++ b/src/mcp/brainyMCPAdapter.ts @@ -0,0 +1,203 @@ +/** + * BrainyMCPAdapter + * + * This class provides an adapter for accessing Brainy data through the Model Control Protocol (MCP). + * It wraps a BrainyData instance and exposes methods for getting vectors, searching similar items, + * and getting relationships. + */ + +import { v4 as uuidv4 } from 'uuid' +import { BrainyDataInterface } from '../types/brainyDataInterface.js' +import { + MCPRequest, + MCPResponse, + MCPDataAccessRequest, + MCPRequestType, + MCP_VERSION +} from '../types/mcpTypes.js' + +export class BrainyMCPAdapter { + private brainyData: BrainyDataInterface + + /** + * Creates a new BrainyMCPAdapter + * @param brainyData The BrainyData instance to wrap + */ + constructor(brainyData: BrainyDataInterface) { + this.brainyData = brainyData + } + + /** + * Handles an MCP data access request + * @param request The MCP request + * @returns An MCP response + */ + async handleRequest(request: MCPDataAccessRequest): Promise { + try { + switch (request.operation) { + case 'get': + return await this.handleGetRequest(request) + case 'search': + return await this.handleSearchRequest(request) + case 'add': + return await this.handleAddRequest(request) + case 'getRelationships': + return await this.handleGetRelationshipsRequest(request) + default: + return this.createErrorResponse( + request.requestId, + 'UNSUPPORTED_OPERATION', + `Operation ${request.operation} is not supported` + ) + } + } catch (error) { + return this.createErrorResponse( + request.requestId, + 'INTERNAL_ERROR', + error instanceof Error ? error.message : String(error) + ) + } + } + + /** + * Handles a get request + * @param request The MCP request + * @returns An MCP response + */ + private async handleGetRequest(request: MCPDataAccessRequest): Promise { + const { id } = request.parameters + + if (!id) { + return this.createErrorResponse( + request.requestId, + 'MISSING_PARAMETER', + 'Parameter "id" is required' + ) + } + + const noun = await this.brainyData.get(id) + + if (!noun) { + return this.createErrorResponse( + request.requestId, + 'NOT_FOUND', + `No noun found with id ${id}` + ) + } + + return this.createSuccessResponse(request.requestId, noun) + } + + /** + * Handles a search request + * @param request The MCP request + * @returns An MCP response + */ + private async handleSearchRequest(request: MCPDataAccessRequest): Promise { + const { query, k = 10 } = request.parameters + + if (!query) { + return this.createErrorResponse( + request.requestId, + 'MISSING_PARAMETER', + 'Parameter "query" is required' + ) + } + + const results = await this.brainyData.searchText(query, k) + return this.createSuccessResponse(request.requestId, results) + } + + /** + * Handles an add request + * @param request The MCP request + * @returns An MCP response + */ + private async handleAddRequest(request: MCPDataAccessRequest): Promise { + const { text, metadata } = request.parameters + + if (!text) { + return this.createErrorResponse( + request.requestId, + 'MISSING_PARAMETER', + 'Parameter "text" is required' + ) + } + + const id = await this.brainyData.add(text, metadata) + return this.createSuccessResponse(request.requestId, { id }) + } + + /** + * Handles a getRelationships request + * @param request The MCP request + * @returns An MCP response + */ + private async handleGetRelationshipsRequest(request: MCPDataAccessRequest): Promise { + const { id } = request.parameters + + if (!id) { + return this.createErrorResponse( + request.requestId, + 'MISSING_PARAMETER', + 'Parameter "id" is required' + ) + } + + // This is a simplified implementation - in a real implementation, we would + // need to check if these methods exist on the BrainyDataInterface + const outgoing = await (this.brainyData as any).getVerbsBySource?.(id) || [] + const incoming = await (this.brainyData as any).getVerbsByTarget?.(id) || [] + + return this.createSuccessResponse(request.requestId, { outgoing, incoming }) + } + + /** + * Creates a success response + * @param requestId The request ID + * @param data The response data + * @returns An MCP response + */ + private createSuccessResponse(requestId: string, data: any): MCPResponse { + return { + success: true, + requestId, + version: MCP_VERSION, + data + } + } + + /** + * Creates an error response + * @param requestId The request ID + * @param code The error code + * @param message The error message + * @param details Optional error details + * @returns An MCP response + */ + private createErrorResponse( + requestId: string, + code: string, + message: string, + details?: any + ): MCPResponse { + return { + success: false, + requestId, + version: MCP_VERSION, + error: { + code, + message, + details + } + } + } + + /** + * Creates a new request ID + * @returns A new UUID + */ + generateRequestId(): string { + return uuidv4() + } +} diff --git a/src/mcp/brainyMCPService.ts b/src/mcp/brainyMCPService.ts new file mode 100644 index 00000000..a68a3955 --- /dev/null +++ b/src/mcp/brainyMCPService.ts @@ -0,0 +1,347 @@ +/** + * BrainyMCPService + * + * This class provides a unified service for accessing Brainy data and augmentations + * through the Model Control Protocol (MCP). It integrates the BrainyMCPAdapter and + * MCPAugmentationToolset classes and provides WebSocket and REST server implementations + * for external model access. + */ + +import { v4 as uuidv4 } from 'uuid' +import { BrainyDataInterface } from '../types/brainyDataInterface.js' +import { + MCPRequest, + MCPResponse, + MCPDataAccessRequest, + MCPToolExecutionRequest, + MCPSystemInfoRequest, + MCPAuthenticationRequest, + MCPRequestType, + MCPServiceOptions, + MCP_VERSION, + MCPTool +} from '../types/mcpTypes.js' +import { BrainyMCPAdapter } from './brainyMCPAdapter.js' +import { MCPAugmentationToolset } from './mcpAugmentationToolset.js' +import { isBrowser, isNode } from '../utils/environment.js' + +export class BrainyMCPService { + private dataAdapter: BrainyMCPAdapter + private toolset: MCPAugmentationToolset + private options: MCPServiceOptions + private authTokens: Map + private rateLimits: Map + + /** + * Creates a new BrainyMCPService + * @param brainyData The BrainyData instance to wrap + * @param options Configuration options for the service + */ + constructor( + brainyData: BrainyDataInterface, + options: MCPServiceOptions = {} + ) { + this.dataAdapter = new BrainyMCPAdapter(brainyData) + this.toolset = new MCPAugmentationToolset() + this.options = options + this.authTokens = new Map() + this.rateLimits = new Map() + } + + /** + * Handles an MCP request + * @param request The MCP request + * @returns An MCP response + */ + async handleRequest(request: MCPRequest): Promise { + try { + switch (request.type) { + case MCPRequestType.DATA_ACCESS: + return await this.dataAdapter.handleRequest( + request as MCPDataAccessRequest + ) + + case MCPRequestType.TOOL_EXECUTION: + return await this.toolset.handleRequest( + request as MCPToolExecutionRequest + ) + + case MCPRequestType.SYSTEM_INFO: + return await this.handleSystemInfoRequest( + request as MCPSystemInfoRequest + ) + + case MCPRequestType.AUTHENTICATION: + return await this.handleAuthenticationRequest( + request as MCPAuthenticationRequest + ) + + default: + return this.createErrorResponse( + request.requestId, + 'UNSUPPORTED_REQUEST_TYPE', + `Request type ${request.type} is not supported` + ) + } + } catch (error) { + return this.createErrorResponse( + request.requestId, + 'INTERNAL_ERROR', + error instanceof Error ? error.message : String(error) + ) + } + } + + /** + * Handles a system info request + * @param request The MCP request + * @returns An MCP response + */ + private async handleSystemInfoRequest( + request: MCPSystemInfoRequest + ): Promise { + try { + switch (request.infoType) { + case 'status': + return this.createSuccessResponse(request.requestId, { + status: 'active', + version: MCP_VERSION, + environment: isBrowser() ? 'browser' : isNode() ? 'node' : 'unknown' + }) + + case 'availableTools': + const tools: MCPTool[] = await this.toolset.getAvailableTools() + return this.createSuccessResponse(request.requestId, tools) + + case 'version': + return this.createSuccessResponse(request.requestId, { + version: MCP_VERSION + }) + + default: + return this.createErrorResponse( + request.requestId, + 'UNSUPPORTED_INFO_TYPE', + `Info type ${request.infoType} is not supported` + ) + } + } catch (error) { + return this.createErrorResponse( + request.requestId, + 'INTERNAL_ERROR', + error instanceof Error ? error.message : String(error) + ) + } + } + + /** + * Handles an authentication request + * @param request The MCP request + * @returns An MCP response + */ + private async handleAuthenticationRequest( + request: MCPAuthenticationRequest + ): Promise { + try { + if (!this.options.enableAuth) { + return this.createSuccessResponse(request.requestId, { + authenticated: true, + message: 'Authentication is not enabled' + }) + } + + const { credentials } = request + + // Check API key authentication + if ( + credentials.apiKey && + this.options.apiKeys?.includes(credentials.apiKey) + ) { + const token = this.generateAuthToken('api-user') + return this.createSuccessResponse(request.requestId, { + authenticated: true, + token + }) + } + + // Check username/password authentication + // This is a placeholder - in a real implementation, you would check against a database + if ( + credentials.username === 'admin' && + credentials.password === 'password' + ) { + const token = this.generateAuthToken(credentials.username) + return this.createSuccessResponse(request.requestId, { + authenticated: true, + token + }) + } + + return this.createErrorResponse( + request.requestId, + 'INVALID_CREDENTIALS', + 'Invalid credentials' + ) + } catch (error) { + return this.createErrorResponse( + request.requestId, + 'INTERNAL_ERROR', + error instanceof Error ? error.message : String(error) + ) + } + } + + /** + * Checks if a request is valid + * @param request The request to check + * @returns Whether the request is valid + */ + private isValidRequest(request: any): boolean { + return ( + request && + typeof request === 'object' && + request.type && + request.requestId && + request.version + ) + } + + /** + * Checks if a request is authenticated + * @param request The request to check + * @returns Whether the request is authenticated + */ + private isAuthenticated(request: MCPRequest): boolean { + if (!this.options.enableAuth) { + return true + } + + return request.authToken ? this.isValidToken(request.authToken) : false + } + + /** + * Checks if a token is valid + * @param token The token to check + * @returns Whether the token is valid + */ + private isValidToken(token: string): boolean { + const tokenInfo = this.authTokens.get(token) + if (!tokenInfo) { + return false + } + + if (tokenInfo.expires < Date.now()) { + this.authTokens.delete(token) + return false + } + + return true + } + + /** + * Generates an authentication token + * @param userId The user ID to associate with the token + * @returns The generated token + */ + private generateAuthToken(userId: string): string { + const token = uuidv4() + const expires = Date.now() + 24 * 60 * 60 * 1000 // 24 hours + + this.authTokens.set(token, { userId, expires }) + + return token + } + + /** + * Checks if a client has exceeded the rate limit + * @param clientId The client ID to check + * @returns Whether the client is within the rate limit + */ + private checkRateLimit(clientId: string): boolean { + if (!this.options.rateLimit) { + return true + } + + const now = Date.now() + const limit = this.rateLimits.get(clientId) + + if (!limit) { + this.rateLimits.set(clientId, { + count: 1, + resetTime: now + this.options.rateLimit.windowMs + }) + return true + } + + if (limit.resetTime < now) { + limit.count = 1 + limit.resetTime = now + this.options.rateLimit.windowMs + return true + } + + if (limit.count >= this.options.rateLimit.maxRequests) { + return false + } + + limit.count++ + return true + } + + /** + * Creates a success response + * @param requestId The request ID + * @param data The response data + * @returns An MCP response + */ + private createSuccessResponse(requestId: string, data: any): MCPResponse { + return { + success: true, + requestId, + version: MCP_VERSION, + data + } + } + + /** + * Creates an error response + * @param requestId The request ID + * @param code The error code + * @param message The error message + * @param details Optional error details + * @returns An MCP response + */ + private createErrorResponse( + requestId: string, + code: string, + message: string, + details?: any + ): MCPResponse { + return { + success: false, + requestId, + version: MCP_VERSION, + error: { + code, + message, + details + } + } + } + + /** + * Creates a new request ID + * @returns A new UUID + */ + generateRequestId(): string { + return uuidv4() + } + + /** + * Handles an MCP request directly (for in-process models) + * @param request The MCP request + * @returns An MCP response + */ + async handleMCPRequest(request: MCPRequest): Promise { + return await this.handleRequest(request) + } +} diff --git a/src/mcp/index.ts b/src/mcp/index.ts new file mode 100644 index 00000000..a7d89dea --- /dev/null +++ b/src/mcp/index.ts @@ -0,0 +1,19 @@ +/** + * Model Control Protocol (MCP) for Brainy + * + * This module provides a Model Control Protocol (MCP) implementation for Brainy, + * allowing external models to access Brainy data and use the augmentation pipeline as tools. + */ + +// Import and re-export the MCP components +import { BrainyMCPAdapter } from './brainyMCPAdapter.js' +import { MCPAugmentationToolset } from './mcpAugmentationToolset.js' +import { BrainyMCPService } from './brainyMCPService.js' + +// Export the MCP components +export { BrainyMCPAdapter } +export { MCPAugmentationToolset } +export { BrainyMCPService } + +// Export the MCP types +export * from '../types/mcpTypes.js' diff --git a/src/mcp/mcpAugmentationToolset.ts b/src/mcp/mcpAugmentationToolset.ts new file mode 100644 index 00000000..1c118e99 --- /dev/null +++ b/src/mcp/mcpAugmentationToolset.ts @@ -0,0 +1,225 @@ +/** + * MCPAugmentationToolset + * + * This class exposes the Brainy augmentation pipeline as tools through the Model Control Protocol (MCP). + * It provides methods for getting available tools and executing tools. + */ + +import { v4 as uuidv4 } from 'uuid' +import { + MCPResponse, + MCPToolExecutionRequest, + MCPTool, + MCP_VERSION +} from '../types/mcpTypes.js' +import { AugmentationType } from '../types/augmentations.js' + +// Import the augmentation pipeline +import { augmentationPipeline } from '../augmentationPipeline.js' + +export class MCPAugmentationToolset { + /** + * Creates a new MCPAugmentationToolset + */ + constructor() { + // No initialization needed + } + + /** + * Handles an MCP tool execution request + * @param request The MCP request + * @returns An MCP response + */ + async handleRequest(request: MCPToolExecutionRequest): Promise { + try { + const { toolName, parameters } = request + + // Extract the augmentation type and method from the tool name + // Tool names are in the format: brainy_{augmentationType}_{method} + const parts = toolName.split('_') + + if (parts.length < 3 || parts[0] !== 'brainy') { + return this.createErrorResponse( + request.requestId, + 'INVALID_TOOL', + `Invalid tool name: ${toolName}. Tool names should be in the format: brainy_{augmentationType}_{method}` + ) + } + + const augmentationType = parts[1] + const method = parts.slice(2).join('_') + + // Validate the augmentation type + if (!this.isValidAugmentationType(augmentationType)) { + return this.createErrorResponse( + request.requestId, + 'INVALID_AUGMENTATION_TYPE', + `Invalid augmentation type: ${augmentationType}` + ) + } + + // Execute the appropriate pipeline based on the augmentation type + const result = await this.executePipeline(augmentationType, method, parameters) + + return this.createSuccessResponse(request.requestId, result) + } catch (error) { + return this.createErrorResponse( + request.requestId, + 'INTERNAL_ERROR', + error instanceof Error ? error.message : String(error) + ) + } + } + + /** + * Gets all available tools + * @returns An array of MCP tools + */ + async getAvailableTools(): Promise { + const tools: MCPTool[] = [] + + // Get all available augmentation types + const augmentationTypes = augmentationPipeline.getAvailableAugmentationTypes() + + for (const type of augmentationTypes) { + // Get all augmentations of this type + const augmentations = augmentationPipeline.getAugmentationsByType(type) + + for (const augmentation of augmentations) { + // Get all methods of this augmentation (excluding private methods and base methods) + const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(augmentation)) + .filter(method => + !method.startsWith('_') && + method !== 'constructor' && + method !== 'initialize' && + method !== 'shutDown' && + method !== 'getStatus' && + typeof augmentation[method] === 'function' + ) + + // Create a tool for each method + for (const method of methods) { + tools.push(this.createToolDefinition(type, augmentation.name, method)) + } + } + } + + return tools + } + + /** + * Creates a tool definition + * @param type The augmentation type + * @param augmentationName The augmentation name + * @param method The method name + * @returns An MCP tool definition + */ + private createToolDefinition(type: string, augmentationName: string, method: string): MCPTool { + return { + name: `brainy_${type}_${method}`, + description: `Access to Brainy's ${type} augmentation '${augmentationName}' method '${method}'`, + parameters: { + type: 'object', + properties: { + args: { + type: 'array', + description: `Arguments for the ${method} method` + }, + options: { + type: 'object', + description: 'Optional execution options' + } + }, + required: ['args'] + } + } + } + + /** + * Executes the appropriate pipeline based on the augmentation type + * @param type The augmentation type + * @param method The method to execute + * @param parameters The parameters for the method + * @returns The result of the pipeline execution + */ + private async executePipeline(type: string, method: string, parameters: any): Promise { + const { args = [], options = {} } = parameters + + switch (type) { + case AugmentationType.SENSE: + return await augmentationPipeline.executeSensePipeline(method, args, options) + case AugmentationType.CONDUIT: + return await augmentationPipeline.executeConduitPipeline(method, args, options) + case AugmentationType.COGNITION: + return await augmentationPipeline.executeCognitionPipeline(method, args, options) + case AugmentationType.MEMORY: + return await augmentationPipeline.executeMemoryPipeline(method, args, options) + case AugmentationType.PERCEPTION: + return await augmentationPipeline.executePerceptionPipeline(method, args, options) + case AugmentationType.DIALOG: + return await augmentationPipeline.executeDialogPipeline(method, args, options) + case AugmentationType.ACTIVATION: + return await augmentationPipeline.executeActivationPipeline(method, args, options) + default: + throw new Error(`Unsupported augmentation type: ${type}`) + } + } + + /** + * Checks if an augmentation type is valid + * @param type The augmentation type to check + * @returns Whether the augmentation type is valid + */ + private isValidAugmentationType(type: string): boolean { + return Object.values(AugmentationType).includes(type as AugmentationType) + } + + /** + * Creates a success response + * @param requestId The request ID + * @param data The response data + * @returns An MCP response + */ + private createSuccessResponse(requestId: string, data: any): MCPResponse { + return { + success: true, + requestId, + version: MCP_VERSION, + data + } + } + + /** + * Creates an error response + * @param requestId The request ID + * @param code The error code + * @param message The error message + * @param details Optional error details + * @returns An MCP response + */ + private createErrorResponse( + requestId: string, + code: string, + message: string, + details?: any + ): MCPResponse { + return { + success: false, + requestId, + version: MCP_VERSION, + error: { + code, + message, + details + } + } + } + + /** + * Creates a new request ID + * @returns A new UUID + */ + generateRequestId(): string { + return uuidv4() + } +} diff --git a/src/pipeline.ts b/src/pipeline.ts new file mode 100644 index 00000000..e27441c8 --- /dev/null +++ b/src/pipeline.ts @@ -0,0 +1,919 @@ +/** + * Unified Pipeline + * + * This module combines the functionality of the primary augmentation pipeline and the streamlined pipeline + * into a single, unified pipeline system. It provides both the registry functionality of the primary pipeline + * and the simplified execution API of the streamlined pipeline. + */ + +import { + IAugmentation, + AugmentationType, + AugmentationResponse, + IWebSocketSupport, + BrainyAugmentations +} from './types/augmentations.js' +import { AugmentationRegistry, IPipeline } from './types/pipelineTypes.js' +import { isThreadingAvailable } from './utils/environment.js' +import { executeInThread } from './utils/workerUtils.js' +import { executeAugmentation } from './augmentationFactory.js' +import { setDefaultPipeline } from './augmentationRegistry.js' + +/** + * Execution mode for the pipeline + */ +export enum ExecutionMode { + SEQUENTIAL = 'sequential', + PARALLEL = 'parallel', + FIRST_SUCCESS = 'firstSuccess', + FIRST_RESULT = 'firstResult', + THREADED = 'threaded' // Execute in separate threads when available +} + +/** + * Options for pipeline execution + */ +export interface PipelineOptions { + mode?: ExecutionMode; + timeout?: number; + stopOnError?: boolean; + forceThreading?: boolean; // Force threading even if not in THREADED mode + disableThreading?: boolean; // Disable threading even if in THREADED mode +} + +/** + * Default pipeline options + */ +const DEFAULT_PIPELINE_OPTIONS: PipelineOptions = { + mode: ExecutionMode.SEQUENTIAL, + timeout: 30000, + stopOnError: false, + forceThreading: false, + disableThreading: false +} + +/** + * Result of a pipeline execution + */ +export interface PipelineResult { + results: AugmentationResponse[]; + errors: Error[]; + successful: AugmentationResponse[]; +} + +/** + * Pipeline class + * + * Manages multiple augmentations of each type and provides methods to execute them. + * Implements the IPipeline interface to avoid circular dependencies. + */ +export class Pipeline implements IPipeline { + private registry: AugmentationRegistry = { + sense: [], + conduit: [], + cognition: [], + memory: [], + perception: [], + dialog: [], + activation: [], + webSocket: [] + } + + /** + * Register an augmentation with the pipeline + * + * @param augmentation The augmentation to register + * @returns The pipeline instance for chaining + */ + public register(augmentation: T): Pipeline { + let registered = false + + // Check for specific augmentation types + if (this.isAugmentationType( + augmentation, + 'processRawData', + 'listenToFeed' + )) { + this.registry.sense.push(augmentation) + registered = true + } else if (this.isAugmentationType( + augmentation, + 'establishConnection', + 'readData', + 'writeData', + 'monitorStream' + )) { + this.registry.conduit.push(augmentation) + registered = true + } else if (this.isAugmentationType( + augmentation, + 'reason', + 'infer', + 'executeLogic' + )) { + this.registry.cognition.push(augmentation) + registered = true + } else if (this.isAugmentationType( + augmentation, + 'storeData', + 'retrieveData', + 'updateData', + 'deleteData', + 'listDataKeys' + )) { + this.registry.memory.push(augmentation) + registered = true + } else if (this.isAugmentationType( + augmentation, + 'interpret', + 'organize', + 'generateVisualization' + )) { + this.registry.perception.push(augmentation) + registered = true + } else if (this.isAugmentationType( + augmentation, + 'processUserInput', + 'generateResponse', + 'manageContext' + )) { + this.registry.dialog.push(augmentation) + registered = true + } else if (this.isAugmentationType( + augmentation, + 'triggerAction', + 'generateOutput', + 'interactExternal' + )) { + this.registry.activation.push(augmentation) + registered = true + } + + // Check if the augmentation supports WebSocket + if (this.isAugmentationType( + augmentation, + 'connectWebSocket', + 'sendWebSocketMessage', + 'onWebSocketMessage', + 'closeWebSocket' + )) { + this.registry.webSocket.push(augmentation as IWebSocketSupport) + registered = true + } + + // If the augmentation wasn't registered as any known type, throw an error + if (!registered) { + throw new Error(`Unknown augmentation type: ${augmentation.name}`) + } + + return this + } + + /** + * Unregister an augmentation from the pipeline + * + * @param augmentationName The name of the augmentation to unregister + * @returns The pipeline instance for chaining + */ + public unregister(augmentationName: string): Pipeline { + let found = false + + // Remove from all registries + for (const type in this.registry) { + const typedRegistry = this.registry[type as keyof AugmentationRegistry] + const index = typedRegistry.findIndex(aug => aug.name === augmentationName) + + if (index !== -1) { + typedRegistry.splice(index, 1) + found = true + } + } + + return this + } + + /** + * Initialize all registered augmentations + * + * @returns A promise that resolves when all augmentations are initialized + */ + public async initialize(): Promise { + const allAugmentations = this.getAllAugmentations() + + await Promise.all( + allAugmentations.map(augmentation => + augmentation.initialize().catch(error => { + console.error(`Failed to initialize augmentation ${augmentation.name}:`, error) + }) + ) + ) + } + + /** + * Shut down all registered augmentations + * + * @returns A promise that resolves when all augmentations are shut down + */ + public async shutDown(): Promise { + const allAugmentations = this.getAllAugmentations() + + await Promise.all( + allAugmentations.map(augmentation => + augmentation.shutDown().catch(error => { + console.error(`Failed to shut down augmentation ${augmentation.name}:`, error) + }) + ) + ) + } + + /** + * Get all registered augmentations + * + * @returns An array of all registered augmentations + */ + public getAllAugmentations(): IAugmentation[] { + // Create a Set to avoid duplicates (an augmentation might be in multiple registries) + const allAugmentations = new Set([ + ...this.registry.sense, + ...this.registry.conduit, + ...this.registry.cognition, + ...this.registry.memory, + ...this.registry.perception, + ...this.registry.dialog, + ...this.registry.activation, + ...this.registry.webSocket + ]) + + // Convert back to array + return Array.from(allAugmentations) + } + + /** + * Get all augmentations of a specific type + * + * @param type The type of augmentation to get + * @returns An array of all augmentations of the specified type + */ + public getAugmentationsByType(type: AugmentationType): IAugmentation[] { + switch (type) { + case AugmentationType.SENSE: + return [...this.registry.sense] + case AugmentationType.CONDUIT: + return [...this.registry.conduit] + case AugmentationType.COGNITION: + return [...this.registry.cognition] + case AugmentationType.MEMORY: + return [...this.registry.memory] + case AugmentationType.PERCEPTION: + return [...this.registry.perception] + case AugmentationType.DIALOG: + return [...this.registry.dialog] + case AugmentationType.ACTIVATION: + return [...this.registry.activation] + case AugmentationType.WEBSOCKET: + return [...this.registry.webSocket] + default: + return [] + } + } + + /** + * Get all available augmentation types + * + * @returns An array of all augmentation types that have at least one registered augmentation + */ + public getAvailableAugmentationTypes(): AugmentationType[] { + const availableTypes: AugmentationType[] = [] + + if (this.registry.sense.length > 0) availableTypes.push(AugmentationType.SENSE) + if (this.registry.conduit.length > 0) availableTypes.push(AugmentationType.CONDUIT) + if (this.registry.cognition.length > 0) availableTypes.push(AugmentationType.COGNITION) + if (this.registry.memory.length > 0) availableTypes.push(AugmentationType.MEMORY) + if (this.registry.perception.length > 0) availableTypes.push(AugmentationType.PERCEPTION) + if (this.registry.dialog.length > 0) availableTypes.push(AugmentationType.DIALOG) + if (this.registry.activation.length > 0) availableTypes.push(AugmentationType.ACTIVATION) + if (this.registry.webSocket.length > 0) availableTypes.push(AugmentationType.WEBSOCKET) + + return availableTypes + } + + /** + * Get all WebSocket-supporting augmentations + * + * @returns An array of all augmentations that support WebSocket connections + */ + public getWebSocketAugmentations(): IWebSocketSupport[] { + return [...this.registry.webSocket] + } + + /** + * Check if an augmentation is of a specific type + * + * @param augmentation The augmentation to check + * @param methods The methods that should be present on the augmentation + * @returns True if the augmentation is of the specified type + */ + private isAugmentationType( + augmentation: IAugmentation, + ...methods: (keyof T)[] + ): augmentation is T { + // First check that the augmentation has all the required base methods + const baseMethodsExist = [ + 'initialize', + 'shutDown', + 'getStatus' + ].every(method => typeof (augmentation as any)[method] === 'function') + + if (!baseMethodsExist) { + return false + } + + // Then check that it has all the specific methods for this type + return methods.every(method => typeof (augmentation as any)[method] === 'function') + } + + /** + * Determines if threading should be used based on options and environment + * + * @param options The pipeline options + * @returns True if threading should be used, false otherwise + */ + private shouldUseThreading(options: PipelineOptions): boolean { + // If threading is explicitly disabled, don't use it + if (options.disableThreading) { + return false + } + + // If threading is explicitly forced, use it if available + if (options.forceThreading) { + return isThreadingAvailable() + } + + // If in THREADED mode, use threading if available + if (options.mode === ExecutionMode.THREADED) { + return isThreadingAvailable() + } + + // Otherwise, don't use threading + return false + } + + /** + * Executes a method on multiple augmentations using the specified execution mode + * + * @param augmentations The augmentations to execute the method on + * @param method The method to execute + * @param args The arguments to pass to the method + * @param options Options for the execution + * @returns A promise that resolves with the results + */ + public async execute( + augmentations: IAugmentation[], + method: string, + args: any[] = [], + options: PipelineOptions = {} + ): Promise> { + const opts = { ...DEFAULT_PIPELINE_OPTIONS, ...options } + const enabledAugmentations = augmentations.filter(aug => aug.enabled !== false) + + if (enabledAugmentations.length === 0) { + return { results: [], errors: [], successful: [] } + } + + const result: PipelineResult = { + results: [], + errors: [], + successful: [] + } + + // Create a function to execute with timeout + const executeWithTimeout = async ( + augmentation: IAugmentation + ): Promise> => { + try { + // Create a timeout promise if a timeout is specified + if (opts.timeout) { + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => { + reject( + new Error(`Timeout executing ${method} on ${augmentation.name}`) + ) + }, opts.timeout) + }) + + // Check if threading should be used + const useThreading = this.shouldUseThreading(opts) + + // Execute the method on the augmentation, using threading if appropriate + let methodPromise: Promise> + + if (useThreading) { + // Execute in a separate thread + try { + // Create a function that can be serialized and executed in a worker + const workerFn = (...workerArgs: any[]) => { + // This function will be stringified and executed in the worker + // It needs to be self-contained + const augFn = augmentation[method as string] as Function + return augFn.apply(augmentation, workerArgs) + } + + methodPromise = executeInThread>(workerFn, ...args) + } catch (threadError) { + console.warn(`Failed to execute in thread, falling back to main thread: ${threadError}`) + // Fall back to executing in the main thread + methodPromise = Promise.resolve((augmentation[method] as Function)(...args) as AugmentationResponse) + } + } else { + // Execute in the main thread + methodPromise = Promise.resolve((augmentation[method] as Function)(...args) as AugmentationResponse) + } + + // Race the method promise against the timeout promise + return await Promise.race([methodPromise, timeoutPromise]) + } else { + // No timeout, just execute the method + return await executeAugmentation(augmentation, method, ...args) + } + } catch (error) { + result.errors.push( + error instanceof Error ? error : new Error(String(error)) + ) + return { + success: false, + data: null as any, + error: error instanceof Error ? error.message : String(error) + } + } + } + + // Execute based on the specified mode + switch (opts.mode) { + case ExecutionMode.PARALLEL: + case ExecutionMode.THREADED: + // Execute all augmentations in parallel + result.results = await Promise.all( + enabledAugmentations.map((aug) => executeWithTimeout(aug)) + ) + break + + case ExecutionMode.FIRST_SUCCESS: + // Execute augmentations sequentially until one succeeds + for (const augmentation of enabledAugmentations) { + const response = await executeWithTimeout(augmentation) + result.results.push(response) + + if (response.success) { + break + } + } + break + + case ExecutionMode.FIRST_RESULT: + // Execute augmentations sequentially until one returns a non-null result + for (const augmentation of enabledAugmentations) { + const response = await executeWithTimeout(augmentation) + result.results.push(response) + + if ( + response.success && + response.data !== null && + response.data !== undefined + ) { + break + } + } + break + + case ExecutionMode.SEQUENTIAL: + default: + // Execute augmentations sequentially + for (const augmentation of enabledAugmentations) { + const response = await executeWithTimeout(augmentation) + result.results.push(response) + + // Check if we need to stop on error + if (opts.stopOnError && !response.success) { + break + } + } + break + } + + // Filter successful results + result.successful = result.results.filter((r) => r.success) + + return result + } + + /** + * Executes a method on augmentations of a specific type + * + * @param type The type of augmentations to execute the method on + * @param method The method to execute + * @param args The arguments to pass to the method + * @param options Options for the execution + * @returns A promise that resolves with the results + */ + public async executeByType( + type: AugmentationType, + method: string, + args: any[] = [], + options: PipelineOptions = {} + ): Promise> { + const augmentations = this.getAugmentationsByType(type) + return this.execute(augmentations, method, args, options) + } + + /** + * Executes a method on a single augmentation with automatic error handling + * + * @param augmentation The augmentation to execute the method on + * @param method The method to execute + * @param args The arguments to pass to the method + * @returns A promise that resolves with the result + */ + public async executeSingle( + augmentation: IAugmentation, + method: string, + ...args: any[] + ): Promise> { + return executeAugmentation(augmentation, method, ...args) + } + + /** + * Process static data through a pipeline of augmentations + * + * @param data The data to process + * @param pipeline An array of processing steps, each with an augmentation, method, and optional args transformer + * @param options Options for the execution + * @returns A promise that resolves with the final result + */ + public async processStaticData( + data: T, + pipeline: Array<{ + augmentation: IAugmentation + method: string + transformArgs?: (data: any, prevResult?: any) => any[] + }>, + options: PipelineOptions = {} + ): Promise> { + let currentData = data + let prevResult: any = undefined + + for (const step of pipeline) { + // Transform args if a transformer is provided, otherwise use the current data as the only arg + const args = step.transformArgs + ? step.transformArgs(currentData, prevResult) + : [currentData] + + // Execute the method + const result = await this.executeSingle( + step.augmentation, + step.method, + ...args + ) + + // If the step failed, return the error + if (!result.success) { + return result as AugmentationResponse + } + + // Update the current data for the next step + currentData = result.data + prevResult = result + } + + // Return the final result + return prevResult as AugmentationResponse + } + + /** + * Process streaming data through a pipeline of augmentations + * + * @param source The source augmentation that provides the data stream + * @param sourceMethod The method on the source augmentation that provides the data stream + * @param sourceArgs The arguments to pass to the source method + * @param pipeline An array of processing steps, each with an augmentation, method, and optional args transformer + * @param callback Function to call with the results of processing each data item + * @param options Options for the execution + * @returns A promise that resolves when the pipeline is set up + */ + public async processStreamingData( + source: IAugmentation, + sourceMethod: string, + sourceArgs: any[], + pipeline: Array<{ + augmentation: IAugmentation + method: string + transformArgs?: (data: any, prevResult?: any) => any[] + }>, + callback: (result: AugmentationResponse) => void, + options: PipelineOptions = {} + ): Promise { + // Create a chain of processors + const processData = async (data: any) => { + let currentData = data + let prevResult: any = undefined + + for (const step of pipeline) { + // Transform args if a transformer is provided, otherwise use the current data as the only arg + const args = step.transformArgs + ? step.transformArgs(currentData, prevResult) + : [currentData] + + // Execute the method + const result = await this.executeSingle( + step.augmentation, + step.method, + ...args + ) + + // If the step failed, return the error + if (!result.success) { + callback(result as AugmentationResponse) + return + } + + // Update the current data for the next step + currentData = result.data + prevResult = result + } + + // Call the callback with the final result + callback(prevResult as AugmentationResponse) + } + + // The last argument to the source method should be a callback that receives the data + const dataCallback = (data: any) => { + processData(data).catch((error) => { + console.error('Error processing streaming data:', error) + callback({ + success: false, + data: null as any, + error: error instanceof Error ? error.message : String(error) + }) + }) + } + + // Execute the source method with the provided args and the data callback + await this.executeSingle(source, sourceMethod, ...sourceArgs, dataCallback) + } + + /** + * Create a reusable pipeline for processing data + * + * @param pipeline An array of processing steps + * @param options Options for the execution + * @returns A function that processes data through the pipeline + */ + public createPipeline( + pipeline: Array<{ + augmentation: IAugmentation + method: string + transformArgs?: (data: any, prevResult?: any) => any[] + }>, + options: PipelineOptions = {} + ): (data: T) => Promise> { + return (data: T) => this.processStaticData(data, pipeline, options) + } + + /** + * Create a reusable streaming pipeline + * + * @param source The source augmentation + * @param sourceMethod The method on the source augmentation + * @param pipeline An array of processing steps + * @param options Options for the execution + * @returns A function that sets up the streaming pipeline + */ + public createStreamingPipeline( + source: IAugmentation, + sourceMethod: string, + pipeline: Array<{ + augmentation: IAugmentation + method: string + transformArgs?: (data: any, prevResult?: any) => any[] + }>, + options: PipelineOptions = {} + ): ( + sourceArgs: any[], + callback: (result: AugmentationResponse) => void + ) => Promise { + return ( + sourceArgs: any[], + callback: (result: AugmentationResponse) => void + ) => + this.processStreamingData( + source, + sourceMethod, + sourceArgs, + pipeline, + callback, + options + ) + } + + // Legacy methods for backward compatibility + + /** + * Execute a sense pipeline (legacy method) + */ + public async executeSensePipeline< + M extends keyof BrainyAugmentations.ISenseAugmentation & string, + R extends BrainyAugmentations.ISenseAugmentation[M] extends (...args: any[]) => AugmentationResponse ? U : never + >( + method: M & (BrainyAugmentations.ISenseAugmentation[M] extends (...args: any[]) => any ? M : never), + args: Parameters any>>, + options: PipelineOptions = {} + ): Promise[]> { + const result = await this.executeByType(AugmentationType.SENSE, method, args, options) + return result.results.map(r => Promise.resolve(r)) + } + + /** + * Execute a conduit pipeline (legacy method) + */ + public async executeConduitPipeline< + M extends keyof BrainyAugmentations.IConduitAugmentation & string, + R extends BrainyAugmentations.IConduitAugmentation[M] extends (...args: any[]) => AugmentationResponse ? U : never + >( + method: M & (BrainyAugmentations.IConduitAugmentation[M] extends (...args: any[]) => any ? M : never), + args: Parameters any>>, + options: PipelineOptions = {} + ): Promise[]> { + const result = await this.executeByType(AugmentationType.CONDUIT, method, args, options) + return result.results.map(r => Promise.resolve(r)) + } + + /** + * Execute a cognition pipeline (legacy method) + */ + public async executeCognitionPipeline< + M extends keyof BrainyAugmentations.ICognitionAugmentation & string, + R extends BrainyAugmentations.ICognitionAugmentation[M] extends (...args: any[]) => AugmentationResponse ? U : never + >( + method: M & (BrainyAugmentations.ICognitionAugmentation[M] extends (...args: any[]) => any ? M : never), + args: Parameters any>>, + options: PipelineOptions = {} + ): Promise[]> { + const result = await this.executeByType(AugmentationType.COGNITION, method, args, options) + return result.results.map(r => Promise.resolve(r)) + } + + /** + * Execute a memory pipeline (legacy method) + */ + public async executeMemoryPipeline< + M extends keyof BrainyAugmentations.IMemoryAugmentation & string, + R extends BrainyAugmentations.IMemoryAugmentation[M] extends (...args: any[]) => AugmentationResponse ? U : never + >( + method: M & (BrainyAugmentations.IMemoryAugmentation[M] extends (...args: any[]) => any ? M : never), + args: Parameters any>>, + options: PipelineOptions = {} + ): Promise[]> { + const result = await this.executeByType(AugmentationType.MEMORY, method, args, options) + return result.results.map(r => Promise.resolve(r)) + } + + /** + * Execute a perception pipeline (legacy method) + */ + public async executePerceptionPipeline< + M extends keyof BrainyAugmentations.IPerceptionAugmentation & string, + R extends BrainyAugmentations.IPerceptionAugmentation[M] extends (...args: any[]) => AugmentationResponse ? U : never + >( + method: M & (BrainyAugmentations.IPerceptionAugmentation[M] extends (...args: any[]) => any ? M : never), + args: Parameters any>>, + options: PipelineOptions = {} + ): Promise[]> { + const result = await this.executeByType(AugmentationType.PERCEPTION, method, args, options) + return result.results.map(r => Promise.resolve(r)) + } + + /** + * Execute a dialog pipeline (legacy method) + */ + public async executeDialogPipeline< + M extends keyof BrainyAugmentations.IDialogAugmentation & string, + R extends BrainyAugmentations.IDialogAugmentation[M] extends (...args: any[]) => AugmentationResponse ? U : never + >( + method: M & (BrainyAugmentations.IDialogAugmentation[M] extends (...args: any[]) => any ? M : never), + args: Parameters any>>, + options: PipelineOptions = {} + ): Promise[]> { + const result = await this.executeByType(AugmentationType.DIALOG, method, args, options) + return result.results.map(r => Promise.resolve(r)) + } + + /** + * Execute an activation pipeline (legacy method) + */ + public async executeActivationPipeline< + M extends keyof BrainyAugmentations.IActivationAugmentation & string, + R extends BrainyAugmentations.IActivationAugmentation[M] extends (...args: any[]) => AugmentationResponse ? U : never + >( + method: M & (BrainyAugmentations.IActivationAugmentation[M] extends (...args: any[]) => any ? M : never), + args: Parameters any>>, + options: PipelineOptions = {} + ): Promise[]> { + const result = await this.executeByType(AugmentationType.ACTIVATION, method, args, options) + return result.results.map(r => Promise.resolve(r)) + } +} + +// Create and export a default instance of the pipeline +export const pipeline = new Pipeline() + +// Set the default pipeline instance for the augmentation registry +// This breaks the circular dependency between pipeline.ts and augmentationRegistry.ts +setDefaultPipeline(pipeline) + +// Re-export the legacy pipeline for backward compatibility +export const augmentationPipeline = pipeline + +// Re-export the streamlined execution functions for backward compatibility +export const executeStreamlined = ( + augmentations: IAugmentation[], + method: string, + args: any[] = [], + options: PipelineOptions = {} +): Promise> => { + return pipeline.execute(augmentations, method, args, options) +} + +export const executeByType = ( + type: AugmentationType, + method: string, + args: any[] = [], + options: PipelineOptions = {} +): Promise> => { + return pipeline.executeByType(type, method, args, options) +} + +export const executeSingle = ( + augmentation: IAugmentation, + method: string, + ...args: any[] +): Promise> => { + return pipeline.executeSingle(augmentation, method, ...args) +} + +export const processStaticData = ( + data: T, + pipelineSteps: Array<{ + augmentation: IAugmentation + method: string + transformArgs?: (data: any, prevResult?: any) => any[] + }>, + options: PipelineOptions = {} +): Promise> => { + return pipeline.processStaticData(data, pipelineSteps, options) +} + +export const processStreamingData = ( + source: IAugmentation, + sourceMethod: string, + sourceArgs: any[], + pipelineSteps: Array<{ + augmentation: IAugmentation + method: string + transformArgs?: (data: any, prevResult?: any) => any[] + }>, + callback: (result: AugmentationResponse) => void, + options: PipelineOptions = {} +): Promise => { + return pipeline.processStreamingData(source, sourceMethod, sourceArgs, pipelineSteps, callback, options) +} + +export const createPipeline = ( + pipelineSteps: Array<{ + augmentation: IAugmentation + method: string + transformArgs?: (data: any, prevResult?: any) => any[] + }>, + options: PipelineOptions = {} +): (data: T) => Promise> => { + return pipeline.createPipeline(pipelineSteps, options) +} + +export const createStreamingPipeline = ( + source: IAugmentation, + sourceMethod: string, + pipelineSteps: Array<{ + augmentation: IAugmentation + method: string + transformArgs?: (data: any, prevResult?: any) => any[] + }>, + options: PipelineOptions = {} +): ( + sourceArgs: any[], + callback: (result: AugmentationResponse) => void +) => Promise => { + return pipeline.createStreamingPipeline(source, sourceMethod, pipelineSteps, options) +} + +// For backward compatibility with StreamlinedExecutionMode +export const StreamlinedExecutionMode = ExecutionMode +export type StreamlinedPipelineOptions = PipelineOptions +export type StreamlinedPipelineResult = PipelineResult diff --git a/src/sequentialPipeline.ts b/src/sequentialPipeline.ts new file mode 100644 index 00000000..7d5e727d --- /dev/null +++ b/src/sequentialPipeline.ts @@ -0,0 +1,572 @@ +/** + * Sequential Augmentation Pipeline + * + * This module provides a pipeline for executing augmentations in a specific sequence: + * ISense -> IMemory -> ICognition -> IConduit -> IActivation -> IPerception + * + * It supports high-performance streaming data from WebSockets without blocking. + * Optimized for Node.js 23.11+ using native WebStreams API. + */ + +import { + AugmentationType, + IAugmentation, + IWebSocketSupport, + ISenseAugmentation, + IMemoryAugmentation, + ICognitionAugmentation, + IConduitAugmentation, + IActivationAugmentation, + IPerceptionAugmentation, + AugmentationResponse, + WebSocketConnection +} from './types/augmentations.js' +import { BrainyData } from './brainyData.js' +import { augmentationPipeline } from './augmentationPipeline.js' +// Use the browser's built-in WebStreams API or Node.js native WebStreams API +// This approach ensures compatibility with both environments +let TransformStream: any, ReadableStream: any, WritableStream: any; + +// Function to initialize the stream classes +const initializeStreamClasses = () => { + // Try to use the browser's built-in WebStreams API first + if (typeof globalThis.TransformStream !== 'undefined' && + typeof globalThis.ReadableStream !== 'undefined' && + typeof globalThis.WritableStream !== 'undefined') { + TransformStream = globalThis.TransformStream; + ReadableStream = globalThis.ReadableStream; + WritableStream = globalThis.WritableStream; + return Promise.resolve(); + } else { + // In Node.js environment, try to import from node:stream/web + // This will be executed in Node.js but not in browsers + return import('node:stream/web') + .then(streamWebModule => { + TransformStream = streamWebModule.TransformStream; + ReadableStream = streamWebModule.ReadableStream; + WritableStream = streamWebModule.WritableStream; + }) + .catch(error => { + console.error('Failed to import WebStreams API:', error); + // Provide fallback implementations or throw a more helpful error + throw new Error('WebStreams API is not available in this environment. Please use a modern browser or Node.js 18+.'); + }); + } +}; + +// Initialize immediately but don't block module execution +const streamClassesPromise = initializeStreamClasses(); + +/** + * Options for sequential pipeline execution + */ +export interface SequentialPipelineOptions { + /** + * Timeout for each augmentation execution in milliseconds + */ + timeout?: number; + + /** + * Whether to stop execution if an error occurs + */ + stopOnError?: boolean; + + /** + * BrainyData instance to use for storage + */ + brainyData?: BrainyData; +} + +/** + * Default pipeline options + */ +const DEFAULT_SEQUENTIAL_PIPELINE_OPTIONS: SequentialPipelineOptions = { + timeout: 30000, + stopOnError: false +} + +/** + * Result of a pipeline execution + */ +export interface PipelineResult { + /** + * Whether the pipeline execution was successful + */ + success: boolean; + + /** + * The data returned by the pipeline + */ + data: T; + + /** + * Error message if the pipeline execution failed + */ + error?: string; + + /** + * Results from each stage of the pipeline + */ + stageResults: { + sense?: AugmentationResponse; + memory?: AugmentationResponse; + cognition?: AugmentationResponse; + conduit?: AugmentationResponse; + activation?: AugmentationResponse; + perception?: AugmentationResponse; + } +} + +/** + * SequentialPipeline class + * + * Executes augmentations in a specific sequence: + * ISense -> IMemory -> ICognition -> IConduit -> IActivation -> IPerception + */ +export class SequentialPipeline { + private brainyData: BrainyData; + + /** + * Create a new sequential pipeline + * + * @param options Options for the pipeline + */ + constructor(options: SequentialPipelineOptions = {}) { + this.brainyData = options.brainyData || new BrainyData(); + } + + /** + * Ensure stream classes are initialized + * @private + */ + private async ensureStreamClassesInitialized(): Promise { + await streamClassesPromise; + } + + /** + * Initialize the pipeline + * + * @returns A promise that resolves when initialization is complete + */ + public async initialize(): Promise { + // Initialize stream classes and BrainyData in parallel + await Promise.all([ + this.ensureStreamClassesInitialized(), + this.brainyData.init() + ]); + } + + /** + * Process data through the sequential pipeline + * + * @param rawData The raw data to process + * @param dataType The type of data (e.g., 'text', 'image', 'audio') + * @param options Options for pipeline execution + * @returns A promise that resolves with the pipeline result + */ + public async processData( + rawData: Buffer | string, + dataType: string, + options: SequentialPipelineOptions = {} + ): Promise> { + const opts = { ...DEFAULT_SEQUENTIAL_PIPELINE_OPTIONS, ...options }; + const result: PipelineResult = { + success: true, + data: null, + stageResults: {} + }; + + try { + // Step 1: Process raw data with ISense augmentations + const senseResults = await augmentationPipeline.executeSensePipeline( + 'processRawData', + [rawData, dataType], + { timeout: opts.timeout, stopOnError: opts.stopOnError } + ); + + // Get the first successful result + let senseResult: AugmentationResponse<{ nouns: string[], verbs: string[] }> | null = null; + for (const resultPromise of senseResults) { + const res = await resultPromise; + if (res.success) { + senseResult = res; + break; + } + } + + if (!senseResult || !senseResult.success) { + return { + success: false, + data: null, + error: 'Failed to process raw data with ISense augmentations', + stageResults: { sense: senseResult || { success: false, data: null, error: 'No sense augmentations available' } } + }; + } + + result.stageResults.sense = senseResult; + + // Step 2: Store data in BrainyData using IMemory augmentations + const memoryAugmentations = augmentationPipeline.getAugmentationsByType(AugmentationType.MEMORY) as IMemoryAugmentation[]; + + if (memoryAugmentations.length === 0) { + return { + success: false, + data: null, + error: 'No memory augmentations available', + stageResults: result.stageResults + }; + } + + // Use the first available memory augmentation + const memoryAugmentation = memoryAugmentations[0]; + + // Generate a key for the data + const dataKey = `data_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`; + + // Store the data + const memoryResult = await memoryAugmentation.storeData( + dataKey, + { + rawData, + dataType, + nouns: senseResult.data.nouns, + verbs: senseResult.data.verbs, + timestamp: Date.now() + } + ); + + if (!memoryResult.success) { + return { + success: false, + data: null, + error: `Failed to store data: ${memoryResult.error}`, + stageResults: { ...result.stageResults, memory: memoryResult } + }; + } + + result.stageResults.memory = memoryResult; + + // Step 3: Trigger ICognition augmentations to analyze the data + const cognitionResults = await augmentationPipeline.executeCognitionPipeline( + 'reason', + [`Analyze data with key ${dataKey}`, { dataKey }], + { timeout: opts.timeout, stopOnError: opts.stopOnError } + ); + + // Get the first successful result + let cognitionResult: AugmentationResponse<{ inference: string, confidence: number }> | null = null; + for (const resultPromise of cognitionResults) { + const res = await resultPromise; + if (res.success) { + cognitionResult = res; + break; + } + } + + if (cognitionResult) { + result.stageResults.cognition = cognitionResult; + } + + // Step 4: Send notifications to IConduit augmentations + const conduitResults = await augmentationPipeline.executeConduitPipeline( + 'writeData', + [{ dataKey, nouns: senseResult.data.nouns, verbs: senseResult.data.verbs }], + { timeout: opts.timeout, stopOnError: opts.stopOnError } + ); + + // Get the first successful result + let conduitResult: AugmentationResponse | null = null; + for (const resultPromise of conduitResults) { + const res = await resultPromise; + if (res.success) { + conduitResult = res; + break; + } + } + + if (conduitResult) { + result.stageResults.conduit = conduitResult; + } + + // Step 5: Send notifications to IActivation augmentations + const activationResults = await augmentationPipeline.executeActivationPipeline( + 'triggerAction', + ['dataProcessed', { dataKey }], + { timeout: opts.timeout, stopOnError: opts.stopOnError } + ); + + // Get the first successful result + let activationResult: AugmentationResponse | null = null; + for (const resultPromise of activationResults) { + const res = await resultPromise; + if (res.success) { + activationResult = res; + break; + } + } + + if (activationResult) { + result.stageResults.activation = activationResult; + } + + // Step 6: Send notifications to IPerception augmentations + const perceptionResults = await augmentationPipeline.executePerceptionPipeline( + 'interpret', + [senseResult.data.nouns, senseResult.data.verbs, { dataKey }], + { timeout: opts.timeout, stopOnError: opts.stopOnError } + ); + + // Get the first successful result + let perceptionResult: AugmentationResponse> | null = null; + for (const resultPromise of perceptionResults) { + const res = await resultPromise; + if (res.success) { + perceptionResult = res; + break; + } + } + + if (perceptionResult) { + result.stageResults.perception = perceptionResult; + result.data = perceptionResult.data; + } else { + // If no perception result, use the cognition result as the final data + result.data = cognitionResult ? cognitionResult.data : { dataKey }; + } + + return result; + } catch (error: unknown) { + return { + success: false, + data: null, + error: `Pipeline execution failed: ${error}`, + stageResults: result.stageResults + }; + } + } + + /** + * Process WebSocket data through the sequential pipeline + * + * @param connection The WebSocket connection + * @param dataType The type of data (e.g., 'text', 'image', 'audio') + * @param options Options for pipeline execution + * @returns A function to handle incoming WebSocket messages + */ + public async createWebSocketHandler( + connection: WebSocketConnection, + dataType: string, + options: SequentialPipelineOptions = {} + ): Promise<(data: unknown) => void> { + // Ensure stream classes are initialized + await this.ensureStreamClassesInitialized(); + + // Create a transform stream for processing data + const transformStream = new TransformStream({ + transform: async (chunk: unknown, controller: TransformStreamDefaultController) => { + 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: PipelineResult) => { + // 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) => { + try { + // Write to the transform stream's writable side + const writer = transformStream.writable.getWriter(); + writer.write(data).catch((error: Error) => { + console.error('Error writing to stream:', error); + }).finally(() => { + 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 + * + * @param url The WebSocket URL to connect to + * @param dataType The type of data (e.g., 'text', 'image', 'audio') + * @param options Options for pipeline execution + * @returns A promise that resolves with the WebSocket connection and associated streams + */ + public async setupWebSocketPipeline( + url: string, + dataType: string, + options: SequentialPipelineOptions = {} + ): Promise, + writableStream?: WritableStream + }> { + // Ensure stream classes are initialized + await this.ensureStreamClassesInitialized(); + + // Get WebSocket-supporting augmentations + const webSocketAugmentations = augmentationPipeline.getWebSocketAugmentations(); + + if (webSocketAugmentations.length === 0) { + throw new Error('No WebSocket-supporting augmentations available'); + } + + // Use the first available WebSocket augmentation + const webSocketAugmentation = webSocketAugmentations[0]; + + // Connect to the WebSocket + const connection = await webSocketAugmentation.connectWebSocket(url); + + // Create a readable stream from the WebSocket messages + const readableStream = new ReadableStream({ + start: (controller: ReadableStreamDefaultController) => { + // 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 handlerPromise = this.createWebSocketHandler(connection, dataType, options); + + // Create a writable stream that sends data to the WebSocket + const writableStream = new WritableStream({ + write: async (chunk: unknown) => { + 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: unknown, controller: TransformStreamDefaultController) => { + // Process each chunk through our handler + const handler = await handlerPromise; + 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, + writableStream: WritableStream + }; + + enhancedConnection.readableStream = readableStream; + enhancedConnection.writableStream = writableStream; + + return enhancedConnection; + } +} + +// Create and export a default instance of the sequential pipeline +export const sequentialPipeline = new SequentialPipeline(); diff --git a/src/storage/fileSystemStorage.ts b/src/storage/fileSystemStorage.ts new file mode 100644 index 00000000..ecd9f35e --- /dev/null +++ b/src/storage/fileSystemStorage.ts @@ -0,0 +1,931 @@ +import { GraphVerb, HNSWNoun, StorageAdapter } from '../coreTypes.js' + +// We'll dynamically import Node.js built-in modules +let fs: any +let path: any + +// Constants for directory and file names +const ROOT_DIR = 'brainy-data' +const NOUNS_DIR = 'nouns' +const VERBS_DIR = 'verbs' +const METADATA_DIR = 'metadata' + +// Constants for noun type directories +const PERSON_DIR = 'person' +const PLACE_DIR = 'place' +const THING_DIR = 'thing' +const EVENT_DIR = 'event' +const CONCEPT_DIR = 'concept' +const CONTENT_DIR = 'content' +const DEFAULT_DIR = 'default' // For nodes without a noun type + +/** + * File system storage adapter for Node.js environments + */ +export class FileSystemStorage implements StorageAdapter { + private rootDir: string + private nounsDir: string + private verbsDir: string + private metadataDir: string + private personDir: string + private placeDir: string + private thingDir: string + private eventDir: string + private conceptDir: string + private contentDir: string + private defaultDir: string + private isInitialized = false + + constructor(rootDirectory?: string) { + // We'll set the paths in the init method after dynamically importing the modules + this.rootDir = rootDirectory || '' + this.nounsDir = '' + this.verbsDir = '' + this.metadataDir = '' + this.personDir = '' + this.placeDir = '' + this.thingDir = '' + this.eventDir = '' + this.conceptDir = '' + this.contentDir = '' + this.defaultDir = '' + } + + /** + * Initialize the storage adapter + */ + public async init(): Promise { + if (this.isInitialized) { + return + } + + 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.`) + } + + // Create directories if they don't exist + await this.ensureDirectoryExists(this.rootDir) + await this.ensureDirectoryExists(this.nounsDir) + await this.ensureDirectoryExists(this.verbsDir) + await this.ensureDirectoryExists(this.metadataDir) + + // Create noun type directories + await this.ensureDirectoryExists(this.personDir) + await this.ensureDirectoryExists(this.placeDir) + await this.ensureDirectoryExists(this.thingDir) + await this.ensureDirectoryExists(this.eventDir) + await this.ensureDirectoryExists(this.conceptDir) + await this.ensureDirectoryExists(this.contentDir) + await this.ensureDirectoryExists(this.defaultDir) + + this.isInitialized = true + } catch (error) { + console.error('Failed to initialize file system storage:', error) + throw new Error(`Failed to initialize file system storage: ${error}`) + } + } + + /** + * Save a node to storage + */ + public async saveNoun(noun: HNSWNoun): Promise { + await this.ensureInitialized() + + try { + // Convert connections Map to a serializable format + const serializableNode = { + ...noun, + connections: this.mapToObject(noun.connections, (set) => Array.from(set as Set)) + } + + // Get the appropriate directory based on the node's metadata + const nodeDir = await this.getNodeDirectory(noun.id) + + const filePath = path.join(nodeDir, `${noun.id}.json`) + await fs.promises.writeFile( + filePath, + JSON.stringify(serializableNode, null, 2), + 'utf8' + ) + } catch (error) { + console.error(`Failed to save node ${noun.id}:`, error) + throw new Error(`Failed to save node ${noun.id}: ${error}`) + } + } + + /** + * Get a node from storage + */ + public async getNoun(id: string): Promise { + await this.ensureInitialized() + + try { + // Get the appropriate directory based on the node's metadata + const nodeDir = await this.getNodeDirectory(id) + + const filePath = path.join(nodeDir, `${id}.json`) + + // Check if a file exists + try { + await fs.promises.access(filePath) + } catch { + // If the file doesn't exist in the expected directory, try the default directory + if (nodeDir !== this.defaultDir) { + const defaultFilePath = path.join(this.defaultDir, `${id}.json`) + try { + await fs.promises.access(defaultFilePath) + // If found in default directory, use that path + const data = await fs.promises.readFile(defaultFilePath, 'utf8') + const parsedNode = JSON.parse(data) + + // Convert serialized connections back to Map> + const connections = new Map>() + for (const [level, nodeIds] of Object.entries(parsedNode.connections)) { + connections.set(Number(level), new Set(nodeIds as string[])) + } + + return { + id: parsedNode.id, + vector: parsedNode.vector, + connections + } + } catch { + // If not found in default directory either, try all noun type directories + const directories = [ + this.personDir, + this.placeDir, + this.thingDir, + this.eventDir, + this.conceptDir, + this.contentDir + ] + + for (const dir of directories) { + if (dir === nodeDir) continue // Skip the already checked directory + + const dirFilePath = path.join(dir, `${id}.json`) + try { + await fs.promises.access(dirFilePath) + // If found in this directory, use that path + const data = await fs.promises.readFile(dirFilePath, 'utf8') + const parsedNode = JSON.parse(data) + + // Convert serialized connections back to Map> + const connections = new Map>() + for (const [level, nodeIds] of Object.entries(parsedNode.connections)) { + connections.set(Number(level), new Set(nodeIds as string[])) + } + + return { + id: parsedNode.id, + vector: parsedNode.vector, + connections + } + } catch { + // Continue to the next directory + } + } + + return null // File doesn't exist in any directory + } + } + return null // File doesn't exist + } + + const data = await fs.promises.readFile(filePath, 'utf8') + const parsedNode = JSON.parse(data) + + // Convert serialized connections back to Map> + const connections = new Map>() + for (const [level, nodeIds] of Object.entries(parsedNode.connections)) { + connections.set(Number(level), new Set(nodeIds as string[])) + } + + return { + id: parsedNode.id, + vector: parsedNode.vector, + connections + } + } catch (error) { + console.error(`Failed to get node ${id}:`, error) + return null + } + } + + /** + * Get nodes by noun type + * @param nounType The noun type to filter by + * @returns Promise that resolves to an array of nodes of the specified noun type + */ + public async getNounsByNounType(nounType: string): Promise { + await this.ensureInitialized() + + try { + // Determine the directory based on the noun type + let dir: string + switch (nounType) { + case 'person': + dir = this.personDir + break + case 'place': + dir = this.placeDir + break + case 'thing': + dir = this.thingDir + break + case 'event': + dir = this.eventDir + break + case 'concept': + dir = this.conceptDir + break + case 'content': + dir = this.contentDir + break + default: + dir = this.defaultDir + } + + const nodes: HNSWNoun[] = [] + + try { + const files = await fs.promises.readdir(dir) + const nodePromises = files + .filter((file: string) => file.endsWith('.json')) + .map((file: string) => { + // Use the file path directly instead of getNode to avoid redundant searches + return this.readNodeFromFile(path.join(dir, file)) + }) + + const dirNodes = await Promise.all(nodePromises) + nodes.push(...dirNodes.filter((node): node is HNSWNoun => node !== null)) + } catch (dirError) { + // If directory doesn't exist or can't be read, log a warning + console.warn(`Could not read directory for noun type ${nounType}:`, dirError) + } + + return nodes + } catch (error) { + console.error(`Failed to get nodes for noun type ${nounType}:`, error) + throw new Error(`Failed to get nodes for noun type ${nounType}: ${error}`) + } + } + + /** + * Get all nodes from storage + */ + public async getAllNouns(): Promise { + await this.ensureInitialized() + + try { + // Get all noun types + const nounTypes = [ + 'person', + 'place', + 'thing', + 'event', + 'concept', + 'content', + 'default' + ] + + // Run searches in parallel for all noun types + const nodePromises = nounTypes.map(nounType => this.getNounsByNounType(nounType)) + const nodeArrays = await Promise.all(nodePromises) + + // Combine all results + const allNodes: HNSWNoun[] = [] + for (const nodes of nodeArrays) { + allNodes.push(...nodes) + } + + return allNodes + } catch (error) { + console.error('Failed to get all nodes:', error) + throw new Error(`Failed to get all nodes: ${error}`) + } + } + + /** + * Read a node from a file + */ + private async readNodeFromFile(filePath: string): Promise { + try { + const data = await fs.promises.readFile(filePath, 'utf8') + const parsedNode = JSON.parse(data) + + // Convert serialized connections back to Map> + const connections = new Map>() + for (const [level, nodeIds] of Object.entries(parsedNode.connections)) { + connections.set(Number(level), new Set(nodeIds as string[])) + } + + return { + id: parsedNode.id, + vector: parsedNode.vector, + connections + } + } catch (error) { + console.error(`Failed to read node from file ${filePath}:`, error) + return null + } + } + + /** + * Delete a node from storage + */ + public async deleteNoun(id: string): Promise { + await this.ensureInitialized() + + try { + // Get the appropriate directory based on the node's metadata + const nodeDir = await this.getNodeDirectory(id) + + const filePath = path.join(nodeDir, `${id}.json`) + + // Check if a file exists before attempting to delete + try { + await fs.promises.access(filePath) + await fs.promises.unlink(filePath) + return // File found and deleted + } catch { + // If the file doesn't exist in the expected directory, try the default directory + if (nodeDir !== this.defaultDir) { + const defaultFilePath = path.join(this.defaultDir, `${id}.json`) + try { + await fs.promises.access(defaultFilePath) + await fs.promises.unlink(defaultFilePath) + return // File found and deleted + } catch { + // If not found in default directory either, try all noun type directories + const directories = [ + this.personDir, + this.placeDir, + this.thingDir, + this.eventDir, + this.conceptDir, + this.contentDir + ] + + for (const dir of directories) { + if (dir === nodeDir) continue // Skip the already checked directory + + const dirFilePath = path.join(dir, `${id}.json`) + try { + await fs.promises.access(dirFilePath) + await fs.promises.unlink(dirFilePath) + return // File found and deleted + } catch { + // Continue to the next directory + } + } + + return // File doesn't exist in any directory, nothing to delete + } + } + return // File doesn't exist, nothing to delete + } + } catch (error) { + console.error(`Failed to delete node ${id}:`, error) + throw new Error(`Failed to delete node ${id}: ${error}`) + } + } + + /** + * Save an edge to storage + */ + public async saveVerb(verb: GraphVerb): Promise { + await this.ensureInitialized() + + try { + // Convert connections Map to a serializable format + const serializableEdge = { + ...verb, + connections: this.mapToObject(verb.connections, (set) => Array.from(set as Set)) + } + + const filePath = path.join(this.verbsDir, `${verb.id}.json`) + await fs.promises.writeFile( + filePath, + JSON.stringify(serializableEdge, null, 2), + 'utf8' + ) + } catch (error) { + console.error(`Failed to save edge ${verb.id}:`, error) + throw new Error(`Failed to save edge ${verb.id}: ${error}`) + } + } + + /** + * Get an edge from storage + */ + public async getVerb(id: string): Promise { + await this.ensureInitialized() + + try { + const filePath = path.join(this.verbsDir, `${id}.json`) + + // Check if a file exists + try { + await fs.promises.access(filePath) + } catch { + return null // File doesn't exist + } + + const data = await fs.promises.readFile(filePath, 'utf8') + const parsedEdge = JSON.parse(data) + + // Convert serialized connections back to Map> + const connections = new Map>() + for (const [level, nodeIds] of Object.entries(parsedEdge.connections)) { + connections.set(Number(level), new Set(nodeIds as string[])) + } + + return { + id: parsedEdge.id, + vector: parsedEdge.vector, + connections, + sourceId: parsedEdge.sourceId, + targetId: parsedEdge.targetId, + type: parsedEdge.type, + weight: parsedEdge.weight, + metadata: parsedEdge.metadata + } + } catch (error) { + console.error(`Failed to get edge ${id}:`, error) + return null + } + } + + /** + * Get all edges from storage + */ + public async getAllVerbs(): Promise { + await this.ensureInitialized() + + try { + const files = await fs.promises.readdir(this.verbsDir) + const edgePromises = files + .filter((file: string) => file.endsWith('.json')) + .map((file: string) => { + const id = path.basename(file, '.json') + return this.getVerb(id) + }) + + const edges = await Promise.all(edgePromises) + return edges.filter((edge): edge is GraphVerb => edge !== null) + } catch (error) { + console.error('Failed to get all edges:', error) + throw new Error(`Failed to get all edges: ${error}`) + } + } + + /** + * Get edges by source node ID + */ + public async getVerbsBySource(sourceId: string): Promise { + await this.ensureInitialized() + + try { + const allEdges = await this.getAllVerbs() + return allEdges.filter(edge => edge.sourceId === sourceId) + } catch (error) { + console.error(`Failed to get edges by source ${sourceId}:`, error) + throw new Error(`Failed to get edges by source ${sourceId}: ${error}`) + } + } + + /** + * Get edges by target node ID + */ + public async getVerbsByTarget(targetId: string): Promise { + await this.ensureInitialized() + + try { + const allEdges = await this.getAllVerbs() + return allEdges.filter(edge => edge.targetId === targetId) + } catch (error) { + console.error(`Failed to get edges by target ${targetId}:`, error) + throw new Error(`Failed to get edges by target ${targetId}: ${error}`) + } + } + + /** + * Get edges by type + */ + public async getVerbsByType(type: string): Promise { + await this.ensureInitialized() + + try { + const allEdges = await this.getAllVerbs() + return allEdges.filter(edge => edge.type === type) + } catch (error) { + console.error(`Failed to get edges by type ${type}:`, error) + throw new Error(`Failed to get edges by type ${type}: ${error}`) + } + } + + /** + * Delete an edge from storage + */ + public async deleteVerb(id: string): Promise { + await this.ensureInitialized() + + try { + const filePath = path.join(this.verbsDir, `${id}.json`) + + // Check if a file exists before attempting to delete + try { + await fs.promises.access(filePath) + } catch { + return // File doesn't exist, nothing to delete + } + + await fs.promises.unlink(filePath) + } catch (error) { + console.error(`Failed to delete edge ${id}:`, error) + throw new Error(`Failed to delete edge ${id}: ${error}`) + } + } + + /** + * Save metadata to storage + */ + public async saveMetadata(id: string, metadata: any): Promise { + await this.ensureInitialized() + + try { + const filePath = path.join(this.metadataDir, `${id}.json`) + await fs.promises.writeFile( + filePath, + JSON.stringify(metadata, null, 2), + 'utf8' + ) + } catch (error) { + console.error(`Failed to save metadata for ${id}:`, error) + throw new Error(`Failed to save metadata for ${id}: ${error}`) + } + } + + /** + * Get metadata from storage + */ + public async getMetadata(id: string): Promise { + await this.ensureInitialized() + + try { + const filePath = path.join(this.metadataDir, `${id}.json`) + + // Check if a file exists + try { + await fs.promises.access(filePath) + } catch { + return null // File doesn't exist + } + + const data = await fs.promises.readFile(filePath, 'utf8') + return JSON.parse(data) + } catch (error) { + console.error(`Failed to get metadata for ${id}:`, error) + return null + } + } + + /** + * Clear all data from storage + */ + public async clear(): Promise { + await this.ensureInitialized() + + try { + // Delete and recreate the nodes, edges, and metadata directories + await this.deleteDirectory(this.nounsDir) + await this.deleteDirectory(this.verbsDir) + await this.deleteDirectory(this.metadataDir) + + await this.ensureDirectoryExists(this.nounsDir) + await this.ensureDirectoryExists(this.verbsDir) + await this.ensureDirectoryExists(this.metadataDir) + + // Create noun type directories + await this.ensureDirectoryExists(this.personDir) + await this.ensureDirectoryExists(this.placeDir) + await this.ensureDirectoryExists(this.thingDir) + await this.ensureDirectoryExists(this.eventDir) + await this.ensureDirectoryExists(this.conceptDir) + await this.ensureDirectoryExists(this.contentDir) + await this.ensureDirectoryExists(this.defaultDir) + } catch (error) { + console.error('Failed to clear storage:', error) + throw new Error(`Failed to clear storage: ${error}`) + } + } + + /** + * Ensure the storage adapter is initialized + */ + private async ensureInitialized(): Promise { + if (!this.isInitialized) { + await this.init() + } + } + + /** + * Ensure a directory exists, creating it if necessary + */ + private async ensureDirectoryExists(dirPath: string): Promise { + try { + await fs.promises.access(dirPath) + } catch { + // Directory doesn't exist, create it + await fs.promises.mkdir(dirPath, { recursive: true }) + } + } + + /** + * Delete a directory and all its contents recursively + */ + private async deleteDirectory(dirPath: string): Promise { + try { + const files = await fs.promises.readdir(dirPath) + + for (const file of files) { + const filePath = path.join(dirPath, file) + const stats = await fs.promises.stat(filePath) + + if (stats.isDirectory()) { + // Recursively delete subdirectories + await this.deleteDirectory(filePath) + } else { + // Delete files + await fs.promises.unlink(filePath) + } + } + + // After all contents are deleted, remove the directory itself + await fs.promises.rmdir(dirPath) + } catch (error) { + // If the directory doesn't exist, that's fine + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { + throw error + } + } + } + + /** + * Count the number of JSON files in a directory + */ + private async countFilesInDirectory(dirPath: string): Promise { + try { + const files = await fs.promises.readdir(dirPath) + return files.filter((file: string) => file.endsWith('.json')).length + } catch (error) { + // If the directory doesn't exist, return 0 + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { + return 0 + } + throw error + } + } + + /** + * Convert a Map to a plain object for serialization + */ + private mapToObject( + map: Map, + valueTransformer: (value: V) => any = (v) => v + ): Record { + const obj: Record = {} + for (const [key, value] of map.entries()) { + obj[key.toString()] = valueTransformer(value) + } + return obj + } + + /** + * Get the appropriate directory for a node based on its metadata + */ + private async getNodeDirectory(id: string): Promise { + try { + // Try to get the metadata for the node + const metadata = await this.getMetadata(id) + + // If metadata exists and has a noun field, use the corresponding directory + if (metadata && metadata.noun) { + switch (metadata.noun) { + case 'person': + return this.personDir + case 'place': + return this.placeDir + case 'thing': + return this.thingDir + case 'event': + return this.eventDir + case 'concept': + return this.conceptDir + case 'content': + return this.contentDir + default: + return this.defaultDir + } + } + + // If no metadata or no noun field, use the default directory + return this.defaultDir + } catch (error) { + // If there's an error getting the metadata, use the default directory + return this.defaultDir + } + } + + /** + * Get information about storage usage and capacity + */ + public async getStorageStatus(): Promise<{ + type: string; + used: number; + quota: number | null; + details?: Record; + }> { + await this.ensureInitialized() + + try { + // Calculate the total size of all files in the storage directories + let totalSize = 0 + + // Helper function to calculate directory size + const calculateDirSize = async (dirPath: string): Promise => { + let size = 0 + try { + const files = await fs.promises.readdir(dirPath) + + for (const file of files) { + const filePath = path.join(dirPath, file) + const stats = await fs.promises.stat(filePath) + + if (stats.isDirectory()) { + size += await calculateDirSize(filePath) + } else { + size += stats.size + } + } + } catch (error) { + console.warn(`Error calculating size for ${dirPath}:`, error) + } + return size + } + + // Calculate size for each directory + const nodesDirSize = await calculateDirSize(this.nounsDir) + const edgesDirSize = await calculateDirSize(this.verbsDir) + const metadataDirSize = await calculateDirSize(this.metadataDir) + + // Calculate sizes of noun type directories + const personDirSize = await calculateDirSize(this.personDir) + const placeDirSize = await calculateDirSize(this.placeDir) + const thingDirSize = await calculateDirSize(this.thingDir) + const eventDirSize = await calculateDirSize(this.eventDir) + const conceptDirSize = await calculateDirSize(this.conceptDir) + const contentDirSize = await calculateDirSize(this.contentDir) + const defaultDirSize = await calculateDirSize(this.defaultDir) + + // Note: The noun type directories are subdirectories of the nodes directory, + // so their sizes are already included in nodesDirSize. + // We don't need to add them again to avoid double counting. + totalSize = nodesDirSize + edgesDirSize + metadataDirSize + + // Get filesystem information + let quota = null + let details: { + nounTypes?: { + person: { size: number; count: number }; + place: { size: number; count: number }; + thing: { size: number; count: number }; + event: { size: number; count: number }; + concept: { size: number; count: number }; + content: { size: number; count: number }; + default: { size: number; count: number }; + }; + availableSpace?: number; + totalSpace?: number; + freePercentage?: number; + } = { + nounTypes: { + person: { + size: personDirSize, + count: await this.countFilesInDirectory(this.personDir) + }, + place: { + size: placeDirSize, + count: await this.countFilesInDirectory(this.placeDir) + }, + thing: { + size: thingDirSize, + count: await this.countFilesInDirectory(this.thingDir) + }, + event: { + size: eventDirSize, + count: await this.countFilesInDirectory(this.eventDir) + }, + concept: { + size: conceptDirSize, + count: await this.countFilesInDirectory(this.conceptDir) + }, + content: { + size: contentDirSize, + count: await this.countFilesInDirectory(this.contentDir) + }, + default: { + size: defaultDirSize, + count: await this.countFilesInDirectory(this.defaultDir) + } + } + } + + try { + // Try to get disk space information + const stats = await fs.promises.statfs(this.rootDir) + if (stats) { + const availableSpace = stats.bavail * stats.bsize + const totalSpace = stats.blocks * stats.bsize + + quota = totalSpace + details = { + availableSpace, + totalSpace, + freePercentage: (availableSpace / totalSpace) * 100 + } + } + } catch (error) { + console.warn('Unable to get filesystem stats:', error) + // If statfs is not available, try to use df command on Unix-like systems + try { + const { exec } = await import('child_process') + const util = await import('util') + const execPromise = util.promisify(exec) + + const { stdout } = await execPromise(`df -k "${this.rootDir}"`) + const lines = stdout.trim().split('\n') + if (lines.length > 1) { + const parts = lines[1].split(/\s+/) + if (parts.length >= 4) { + const totalKB = parseInt(parts[1], 10) + const usedKB = parseInt(parts[2], 10) + const availableKB = parseInt(parts[3], 10) + + quota = totalKB * 1024 + details = { + availableSpace: availableKB * 1024, + totalSpace: totalKB * 1024, + freePercentage: (availableKB / totalKB) * 100 + } + } + } + } catch (dfError) { + console.warn('Unable to get disk space using df command:', dfError) + } + } + + return { + type: 'filesystem', + used: totalSize, + quota, + details + } + } catch (error) { + console.error('Failed to get storage status:', error) + return { + type: 'filesystem', + used: 0, + quota: null, + details: { error: String(error) } + } + } + } +} diff --git a/src/storage/opfsStorage.ts b/src/storage/opfsStorage.ts new file mode 100644 index 00000000..11914918 --- /dev/null +++ b/src/storage/opfsStorage.ts @@ -0,0 +1,1890 @@ +/** + * OPFS (Origin Private File System) Storage Adapter + * Provides persistent storage for the vector database using the Origin Private File System API + */ + +import { GraphVerb, HNSWNoun, StorageAdapter } from '../coreTypes.js' +import '../types/fileSystemTypes.js' +// Make sure TypeScript recognizes the FileSystemDirectoryHandle interface extension +declare global { + interface FileSystemDirectoryHandle { + entries(): AsyncIterableIterator<[string, FileSystemHandle]> + } +} + +// Type aliases for compatibility +type HNSWNode = HNSWNoun +type Edge = GraphVerb + +// Directory and file names +const ROOT_DIR = 'opfs-vector-db' +const NOUNS_DIR = 'nouns' +const VERBS_DIR = 'verbs' +const METADATA_DIR = 'metadata' +const DB_INFO_FILE = 'db-info.json' + +// Constants for noun type directories +const PERSON_DIR = 'person' +const PLACE_DIR = 'place' +const THING_DIR = 'thing' +const EVENT_DIR = 'event' +const CONCEPT_DIR = 'concept' +const CONTENT_DIR = 'content' +const DEFAULT_DIR = 'default' // For nodes without a noun type + +export class OPFSStorage implements StorageAdapter { + private rootDir: FileSystemDirectoryHandle | null = null + private nounsDir: FileSystemDirectoryHandle | null = null + private verbsDir: FileSystemDirectoryHandle | null = null + private metadataDir: FileSystemDirectoryHandle | null = null + private personDir: FileSystemDirectoryHandle | null = null + private placeDir: FileSystemDirectoryHandle | null = null + private thingDir: FileSystemDirectoryHandle | null = null + private eventDir: FileSystemDirectoryHandle | null = null + private conceptDir: FileSystemDirectoryHandle | null = null + private contentDir: FileSystemDirectoryHandle | null = null + private defaultDir: FileSystemDirectoryHandle | null = null + private isInitialized = false + private isAvailable = false + private isPersistentRequested = false + private isPersistentGranted = false + + constructor() { + // Check if OPFS is available + this.isAvailable = + typeof navigator !== 'undefined' && + 'storage' in navigator && + 'getDirectory' in navigator.storage + } + + /** + * Initialize the storage adapter + */ + public async init(): Promise { + if (this.isInitialized) { + return + } + + if (!this.isAvailable) { + throw new Error( + 'Origin Private File System is not available in this environment' + ) + } + + try { + // Get the root directory + const root = await navigator.storage.getDirectory() + + // Create or get our app's root directory + this.rootDir = await root.getDirectoryHandle(ROOT_DIR, { create: true }) + + // Create or get nouns directory + this.nounsDir = await this.rootDir.getDirectoryHandle(NOUNS_DIR, { + create: true + }) + + // Create or get verbs directory + this.verbsDir = await this.rootDir.getDirectoryHandle(VERBS_DIR, { + create: true + }) + + // Create or get metadata directory + this.metadataDir = await this.rootDir.getDirectoryHandle(METADATA_DIR, { + create: true + }) + + // Create or get noun type directories + this.personDir = await this.nounsDir.getDirectoryHandle(PERSON_DIR, { + create: true + }) + this.placeDir = await this.nounsDir.getDirectoryHandle(PLACE_DIR, { + create: true + }) + this.thingDir = await this.nounsDir.getDirectoryHandle(THING_DIR, { + create: true + }) + this.eventDir = await this.nounsDir.getDirectoryHandle(EVENT_DIR, { + create: true + }) + this.conceptDir = await this.nounsDir.getDirectoryHandle(CONCEPT_DIR, { + create: true + }) + this.contentDir = await this.nounsDir.getDirectoryHandle(CONTENT_DIR, { + create: true + }) + this.defaultDir = await this.nounsDir.getDirectoryHandle(DEFAULT_DIR, { + create: true + }) + + this.isInitialized = true + } catch (error) { + console.error('Failed to initialize OPFS storage:', error) + throw new Error(`Failed to initialize OPFS storage: ${error}`) + } + } + + /** + * Check if OPFS is available in the current environment + */ + public isOPFSAvailable(): boolean { + return this.isAvailable + } + + /** + * Request persistent storage permission from the user + * @returns Promise that resolves to true if permission was granted, false otherwise + */ + public async requestPersistentStorage(): Promise { + if (!this.isAvailable) { + console.warn('Cannot request persistent storage: OPFS is not available') + return false + } + + try { + // Check if persistence is already granted + this.isPersistentGranted = await navigator.storage.persisted() + + if (!this.isPersistentGranted) { + // Request permission for persistent storage + this.isPersistentGranted = await navigator.storage.persist() + } + + this.isPersistentRequested = true + return this.isPersistentGranted + } catch (error) { + console.warn('Failed to request persistent storage:', error) + return false + } + } + + /** + * Check if persistent storage is granted + * @returns Promise that resolves to true if persistent storage is granted, false otherwise + */ + public async isPersistent(): Promise { + if (!this.isAvailable) { + return false + } + + try { + this.isPersistentGranted = await navigator.storage.persisted() + return this.isPersistentGranted + } catch (error) { + console.warn('Failed to check persistent storage status:', error) + return false + } + } + + /** + * Save a noun to storage + */ + public async saveNoun(noun: HNSWNoun): Promise { + await this.ensureInitialized() + + try { + // Convert connections Map to a serializable format + const serializableNoun = { + ...noun, + connections: this.mapToObject(noun.connections, (set) => + Array.from(set as Set) + ) + } + + // Get the appropriate directory based on the noun's metadata + const nounDir = await this.getNodeDirectory(noun.id) + + // Create or get the file for this noun + const fileHandle = await nounDir.getFileHandle(noun.id, { + create: true + }) + + // Write the noun data to the file + const writable = await fileHandle.createWritable() + await writable.write(JSON.stringify(serializableNoun)) + await writable.close() + } catch (error) { + console.error(`Failed to save noun ${noun.id}:`, error) + throw new Error(`Failed to save noun ${noun.id}: ${error}`) + } + } + + /** + * Get a noun from storage + */ + public async getNoun(id: string): Promise { + await this.ensureInitialized() + + try { + // Get the appropriate directory based on the node's metadata + const nodeDir = await this.getNodeDirectory(id) + + try { + // Get the file handle for this node + const fileHandle = await nodeDir.getFileHandle(id) + + // Read the node data from the file + const file = await fileHandle.getFile() + const text = await file.text() + const data = JSON.parse(text) + + // Convert serialized connections back to Map> + const connections = new Map>() + for (const [level, nodeIds] of Object.entries(data.connections)) { + connections.set(Number(level), new Set(nodeIds as string[])) + } + + return { + id: data.id, + vector: data.vector, + connections + } + } catch (dirError) { + // If the file doesn't exist in the expected directory, try the default directory + if (nodeDir !== this.defaultDir) { + try { + // Get the file handle from the default directory + const fileHandle = await this.defaultDir!.getFileHandle(id) + + // Read the node data from the file + const file = await fileHandle.getFile() + const text = await file.text() + const data = JSON.parse(text) + + // Convert serialized connections back to Map> + const connections = new Map>() + for (const [level, nodeIds] of Object.entries(data.connections)) { + connections.set(Number(level), new Set(nodeIds as string[])) + } + + return { + id: data.id, + vector: data.vector, + connections + } + } catch (defaultDirError) { + // If not found in default directory either, try all noun type directories + const directories = [ + this.personDir!, + this.placeDir!, + this.thingDir!, + this.eventDir!, + this.conceptDir!, + this.contentDir! + ] + + for (const dir of directories) { + if (dir === nodeDir) continue // Skip the already checked directory + + try { + // Get the file handle from this directory + const fileHandle = await dir.getFileHandle(id) + + // Read the node data from the file + const file = await fileHandle.getFile() + const text = await file.text() + const data = JSON.parse(text) + + // Convert serialized connections back to Map> + const connections = new Map>() + for (const [level, nodeIds] of Object.entries( + data.connections + )) { + connections.set(Number(level), new Set(nodeIds as string[])) + } + + return { + id: data.id, + vector: data.vector, + connections + } + } catch { + // Continue to the next directory + } + } + + return null // File doesn't exist in any directory + } + } + return null // File doesn't exist + } + } catch (error) { + console.error(`Failed to get node ${id}:`, error) + throw new Error(`Failed to get node ${id}: ${error}`) + } + } + + /** + * Get nouns by noun type + * @param nounType The noun type to filter by + * @returns Promise that resolves to an array of nouns of the specified noun type + */ + public async getNounsByNounType(nounType: string): Promise { + await this.ensureInitialized() + + try { + const nouns: HNSWNoun[] = [] + + // Determine the directory based on the noun type + let dir: FileSystemDirectoryHandle + switch (nounType) { + case 'person': + dir = this.personDir! + break + case 'place': + dir = this.placeDir! + break + case 'thing': + dir = this.thingDir! + break + case 'event': + dir = this.eventDir! + break + case 'concept': + dir = this.conceptDir! + break + case 'content': + dir = this.contentDir! + break + default: + dir = this.defaultDir! + } + + try { + // Get all entries (filename and handle pairs) in this directory + const entries = dir.entries() + + // Iterate through all entries and get the corresponding nodes + for await (const [name, handle] of entries) { + try { + // The handle is already a FileSystemHandle, but we need to ensure it's a file + if (handle.kind !== 'file') continue + + // Cast to FileSystemFileHandle + const fileHandle = handle as FileSystemFileHandle + + // Read the node data from the file + const file = await fileHandle.getFile() + const text = await file.text() + const data = JSON.parse(text) + + // Convert serialized connections back to Map> + const connections = new Map>() + for (const [level, nodeIds] of Object.entries(data.connections)) { + connections.set(Number(level), new Set(nodeIds as string[])) + } + + nouns.push({ + id: data.id, + vector: data.vector, + connections + }) + } catch (nodeError) { + console.warn( + `Failed to read node ${name} from directory:`, + nodeError + ) + // Continue to the next node + } + } + } catch (dirError) { + console.warn( + `Failed to read directory for noun type ${nounType}:`, + dirError + ) + } + + return nouns + } catch (error) { + console.error(`Failed to get nouns for noun type ${nounType}:`, error) + throw new Error(`Failed to get nouns for noun type ${nounType}: ${error}`) + } + } + + /** + * Get all nouns from storage + */ + public async getAllNouns(): Promise { + await this.ensureInitialized() + + try { + // Get all noun types + const nounTypes = [ + 'person', + 'place', + 'thing', + 'event', + 'concept', + 'content', + 'default' + ] + + // Run searches in parallel for all noun types + const nounPromises = nounTypes.map((nounType) => + this.getNounsByNounType(nounType) + ) + const nounArrays = await Promise.all(nounPromises) + + // Combine all results + const allNouns: HNSWNoun[] = [] + for (const nouns of nounArrays) { + allNouns.push(...nouns) + } + + return allNouns + } catch (error) { + console.error('Failed to get all nouns:', error) + throw new Error(`Failed to get all nouns: ${error}`) + } + } + + /** + * Delete a noun from storage + */ + public async deleteNoun(id: string): Promise { + await this.ensureInitialized() + + try { + // Get the appropriate directory based on the noun's metadata + const nounDir = await this.getNodeDirectory(id) + + try { + // Try to delete the noun from the appropriate directory + await nounDir.removeEntry(id) + return // Noun deleted successfully + } catch (dirError) { + // If the file doesn't exist in the expected directory, try the default directory + if (nounDir !== this.defaultDir) { + try { + await this.defaultDir!.removeEntry(id) + return // Noun deleted successfully + } catch (defaultDirError) { + // If not found in default directory either, try all noun type directories + const directories = [ + this.personDir!, + this.placeDir!, + this.thingDir!, + this.eventDir!, + this.conceptDir!, + this.contentDir! + ] + + for (const dir of directories) { + if (dir === nounDir) continue // Skip the already checked directory + + try { + await dir.removeEntry(id) + return // Node deleted successfully + } catch { + // Continue to the next directory + } + } + + // If we get here, the node wasn't found in any directory + return + } + } + // If the file doesn't exist, that's fine + return + } + } catch (error) { + console.error(`Failed to delete noun ${id}:`, error) + throw new Error(`Failed to delete noun ${id}: ${error}`) + } + } + + /** + * Save a verb to storage + */ + public async saveVerb(verb: GraphVerb): Promise { + await this.ensureInitialized() + + try { + // Convert connections Map to a serializable format + const serializableVerb = { + ...verb, + connections: this.mapToObject(verb.connections, (set) => + Array.from(set as Set) + ) + } + + // Create or get the file for this verb + const fileHandle = await this.verbsDir!.getFileHandle(verb.id, { + create: true + }) + + // Write the verb data to the file + const writable = await fileHandle.createWritable() + await writable.write(JSON.stringify(serializableVerb)) + await writable.close() + } catch (error) { + console.error(`Failed to save verb ${verb.id}:`, error) + throw new Error(`Failed to save verb ${verb.id}: ${error}`) + } + } + + /** + * Get a verb from storage + */ + public async getVerb(id: string): Promise { + await this.ensureInitialized() + + try { + // Get the file handle for this verb + const fileHandle = await this.verbsDir!.getFileHandle(id) + + // Read the verb data from the file + const file = await fileHandle.getFile() + const text = await file.text() + const data = JSON.parse(text) + + // Convert serialized connections back to Map> + const connections = new Map>() + for (const [level, nodeIds] of Object.entries(data.connections)) { + connections.set(Number(level), new Set(nodeIds as string[])) + } + + return { + id: data.id, + vector: data.vector, + connections, + sourceId: data.sourceId, + targetId: data.targetId, + type: data.type, + weight: data.weight, + metadata: data.metadata + } + } catch (error) { + // If the file doesn't exist, return null + if ((error as any).name === 'NotFoundError') { + return null + } + + console.error(`Failed to get verb ${id}:`, error) + throw new Error(`Failed to get verb ${id}: ${error}`) + } + } + + /** + * Get all edges from storage + */ + public async getAllEdges(): Promise { + await this.ensureInitialized() + + try { + const edges: Edge[] = [] + + // Get all entries (filename and handle pairs) in the verbs directory + const entries = this.verbsDir!.entries() + + // Iterate through all entries and get the corresponding edges + for await (const [name, handle] of entries) { + // Skip if not a file + if (handle.kind !== 'file') continue + + const edge = await this.getVerb(name) + if (edge) { + edges.push(edge) + } + } + + return edges + } catch (error) { + console.error('Failed to get all edges:', error) + throw new Error(`Failed to get all edges: ${error}`) + } + } + + /** + * Get all verbs from storage (alias for getAllEdges) + */ + public async getAllVerbs(): Promise { + return this.getAllEdges() + } + + /** + * Delete an edge from storage + */ + public async deleteEdge(id: string): Promise { + await this.ensureInitialized() + + try { + await this.verbsDir!.removeEntry(id) + } catch (error) { + // Ignore if the file doesn't exist + if ((error as any).name !== 'NotFoundError') { + console.error(`Failed to delete edge ${id}:`, error) + throw new Error(`Failed to delete edge ${id}: ${error}`) + } + } + } + + /** + * Delete a verb from storage (alias for deleteEdge) + */ + public async deleteVerb(id: string): Promise { + return this.deleteEdge(id) + } + + /** + * Get edges by source node ID + */ + public async getEdgesBySource(sourceId: string): Promise { + await this.ensureInitialized() + + try { + const allEdges = await this.getAllEdges() + return allEdges.filter((edge) => edge.sourceId === sourceId) + } catch (error) { + console.error(`Failed to get edges by source ${sourceId}:`, error) + throw new Error(`Failed to get edges by source ${sourceId}: ${error}`) + } + } + + /** + * Get verbs by source node ID (alias for getEdgesBySource) + */ + public async getVerbsBySource(sourceId: string): Promise { + return this.getEdgesBySource(sourceId) + } + + /** + * Get edges by target node ID + */ + public async getEdgesByTarget(targetId: string): Promise { + await this.ensureInitialized() + + try { + const allEdges = await this.getAllEdges() + return allEdges.filter((edge) => edge.targetId === targetId) + } catch (error) { + console.error(`Failed to get edges by target ${targetId}:`, error) + throw new Error(`Failed to get edges by target ${targetId}: ${error}`) + } + } + + /** + * Get verbs by target node ID (alias for getEdgesByTarget) + */ + public async getVerbsByTarget(targetId: string): Promise { + return this.getEdgesByTarget(targetId) + } + + /** + * Get edges by type + */ + public async getEdgesByType(type: string): Promise { + await this.ensureInitialized() + + try { + const allEdges = await this.getAllEdges() + return allEdges.filter((edge) => edge.type === type) + } catch (error) { + console.error(`Failed to get edges by type ${type}:`, error) + throw new Error(`Failed to get edges by type ${type}: ${error}`) + } + } + + /** + * Get verbs by type (alias for getEdgesByType) + */ + public async getVerbsByType(type: string): Promise { + return this.getEdgesByType(type) + } + + /** + * Save metadata for a node + */ + public async saveMetadata(id: string, metadata: any): Promise { + await this.ensureInitialized() + + try { + // Create or get the file for this metadata + const fileHandle = await this.metadataDir!.getFileHandle(id, { + create: true + }) + + // Write the metadata to the file + const writable = await fileHandle.createWritable() + await writable.write(JSON.stringify(metadata)) + await writable.close() + } catch (error) { + console.error(`Failed to save metadata for ${id}:`, error) + throw new Error(`Failed to save metadata for ${id}: ${error}`) + } + } + + /** + * Get metadata for a node + */ + public async getMetadata(id: string): Promise { + await this.ensureInitialized() + + try { + // Get the file handle for this metadata + const fileHandle = await this.metadataDir!.getFileHandle(id) + + // Read the metadata from the file + const file = await fileHandle.getFile() + const text = await file.text() + return JSON.parse(text) + } catch (error) { + // If the file doesn't exist, return null + if ((error as any).name === 'NotFoundError') { + return null + } + + console.error(`Failed to get metadata for ${id}:`, error) + throw new Error(`Failed to get metadata for ${id}: ${error}`) + } + } + + /** + * Clear all data from storage + */ + public async clear(): Promise { + await this.ensureInitialized() + + try { + // Delete and recreate the nouns directory + await this.rootDir!.removeEntry(NOUNS_DIR, { recursive: true }) + this.nounsDir = await this.rootDir!.getDirectoryHandle(NOUNS_DIR, { + create: true + }) + + // Create noun type directories + this.personDir = await this.nounsDir.getDirectoryHandle(PERSON_DIR, { + create: true + }) + this.placeDir = await this.nounsDir.getDirectoryHandle(PLACE_DIR, { + create: true + }) + this.thingDir = await this.nounsDir.getDirectoryHandle(THING_DIR, { + create: true + }) + this.eventDir = await this.nounsDir.getDirectoryHandle(EVENT_DIR, { + create: true + }) + this.conceptDir = await this.nounsDir.getDirectoryHandle(CONCEPT_DIR, { + create: true + }) + this.contentDir = await this.nounsDir.getDirectoryHandle(CONTENT_DIR, { + create: true + }) + this.defaultDir = await this.nounsDir.getDirectoryHandle(DEFAULT_DIR, { + create: true + }) + + // Delete and recreate the verbs directory + await this.rootDir!.removeEntry(VERBS_DIR, { recursive: true }) + this.verbsDir = await this.rootDir!.getDirectoryHandle(VERBS_DIR, { + create: true + }) + + // Delete and recreate the metadata directory + await this.rootDir!.removeEntry(METADATA_DIR, { recursive: true }) + this.metadataDir = await this.rootDir!.getDirectoryHandle(METADATA_DIR, { + create: true + }) + } catch (error) { + console.error('Failed to clear storage:', error) + throw new Error(`Failed to clear storage: ${error}`) + } + } + + /** + * Ensure the storage adapter is initialized + */ + private async ensureInitialized(): Promise { + if (!this.isInitialized) { + await this.init() + } + } + + /** + * Convert a Map to a plain object for serialization + */ + private mapToObject( + map: Map, + valueTransformer: (value: V) => any = (v) => v + ): Record { + const obj: Record = {} + for (const [key, value] of map.entries()) { + obj[key.toString()] = valueTransformer(value) + } + return obj + } + + /** + * Get the appropriate directory for a node based on its metadata + */ + private async getNodeDirectory( + id: string + ): Promise { + try { + // Try to get the metadata for the node + const metadata = await this.getMetadata(id) + + // If metadata exists and has a noun field, use the corresponding directory + if (metadata && metadata.noun) { + switch (metadata.noun) { + case 'person': + return this.personDir! + case 'place': + return this.placeDir! + case 'thing': + return this.thingDir! + case 'event': + return this.eventDir! + case 'concept': + return this.conceptDir! + case 'content': + return this.contentDir! + default: + return this.defaultDir! + } + } + + // If no metadata or no noun field, use the default directory + return this.defaultDir! + } catch (error) { + // If there's an error getting the metadata, use the default directory + return this.defaultDir! + } + } + + /** + * Get information about storage usage and capacity + */ + public async getStorageStatus(): Promise<{ + type: string + used: number + quota: number | null + details?: Record + }> { + await this.ensureInitialized() + + try { + // Calculate the total size of all files in the storage directories + let totalSize = 0 + + // Helper function to calculate directory size + const calculateDirSize = async ( + dirHandle: FileSystemDirectoryHandle + ): Promise => { + let size = 0 + try { + for await (const [name, handle] of dirHandle.entries()) { + if (handle.kind === 'file') { + const file = await (handle as FileSystemFileHandle).getFile() + size += file.size + } else if (handle.kind === 'directory') { + size += await calculateDirSize( + handle as FileSystemDirectoryHandle + ) + } + } + } catch (error) { + console.warn(`Error calculating size for directory:`, error) + } + return size + } + + // Helper function to count files in a directory + const countFilesInDirectory = async ( + dirHandle: FileSystemDirectoryHandle + ): Promise => { + let count = 0 + try { + for await (const [name, handle] of dirHandle.entries()) { + if (handle.kind === 'file') { + count++ + } + } + } catch (error) { + console.warn(`Error counting files in directory:`, error) + } + return count + } + + // Calculate size for each directory + if (this.nounsDir) { + totalSize += await calculateDirSize(this.nounsDir) + } + if (this.verbsDir) { + totalSize += await calculateDirSize(this.verbsDir) + } + if (this.metadataDir) { + totalSize += await calculateDirSize(this.metadataDir) + } + + // Calculate sizes of noun type directories + const personDirSize = this.personDir + ? await calculateDirSize(this.personDir) + : 0 + const placeDirSize = this.placeDir + ? await calculateDirSize(this.placeDir) + : 0 + const thingDirSize = this.thingDir + ? await calculateDirSize(this.thingDir) + : 0 + const eventDirSize = this.eventDir + ? await calculateDirSize(this.eventDir) + : 0 + const conceptDirSize = this.conceptDir + ? await calculateDirSize(this.conceptDir) + : 0 + const contentDirSize = this.contentDir + ? await calculateDirSize(this.contentDir) + : 0 + const defaultDirSize = this.defaultDir + ? await calculateDirSize(this.defaultDir) + : 0 + + // Get storage quota information using the Storage API + let quota = null + let details: Record = { + isPersistent: await this.isPersistent(), + nounTypes: { + person: { + size: personDirSize, + count: this.personDir + ? await countFilesInDirectory(this.personDir) + : 0 + }, + place: { + size: placeDirSize, + count: this.placeDir + ? await countFilesInDirectory(this.placeDir) + : 0 + }, + thing: { + size: thingDirSize, + count: this.thingDir + ? await countFilesInDirectory(this.thingDir) + : 0 + }, + event: { + size: eventDirSize, + count: this.eventDir + ? await countFilesInDirectory(this.eventDir) + : 0 + }, + concept: { + size: conceptDirSize, + count: this.conceptDir + ? await countFilesInDirectory(this.conceptDir) + : 0 + }, + content: { + size: contentDirSize, + count: this.contentDir + ? await countFilesInDirectory(this.contentDir) + : 0 + }, + default: { + size: defaultDirSize, + count: this.defaultDir + ? await countFilesInDirectory(this.defaultDir) + : 0 + } + } + } + + try { + if (navigator.storage && navigator.storage.estimate) { + const estimate = await navigator.storage.estimate() + quota = estimate.quota || null + details = { + ...details, + usage: estimate.usage, + quota: estimate.quota, + freePercentage: estimate.quota + ? ((estimate.quota - (estimate.usage || 0)) / estimate.quota) * + 100 + : null + } + } + } catch (error) { + console.warn('Unable to get storage estimate:', error) + } + + return { + type: 'opfs', + used: totalSize, + quota, + details + } + } catch (error) { + console.error('Failed to get storage status:', error) + return { + type: 'opfs', + used: 0, + quota: null, + details: { error: String(error) } + } + } + } +} + +/** + * In-memory storage adapter for environments where OPFS is not available + */ +export class MemoryStorage implements StorageAdapter { + // Map of noun type to Map of noun ID to noun + private nouns: Map> = new Map() + private verbs: Map = new Map() + private metadata: Map = new Map() + + // Initialize maps for each noun type + constructor() { + this.nouns.set(PERSON_DIR, new Map()) + this.nouns.set(PLACE_DIR, new Map()) + this.nouns.set(THING_DIR, new Map()) + this.nouns.set(EVENT_DIR, new Map()) + this.nouns.set(CONCEPT_DIR, new Map()) + this.nouns.set(CONTENT_DIR, new Map()) + this.nouns.set(DEFAULT_DIR, new Map()) + } + + // Alias methods to match StorageAdapter interface + public async saveNoun(noun: HNSWNoun): Promise { + return this.saveNode(noun) + } + + public async getNoun(id: string): Promise { + return this.getNode(id) + } + + public async getAllNouns(): Promise { + return this.getAllNodes() + } + + public async getNounsByNounType(nounType: string): Promise { + return this.getNodesByNounType(nounType) + } + + public async deleteNoun(id: string): Promise { + return this.deleteNode(id) + } + + public async saveVerb(verb: GraphVerb): Promise { + return this.saveEdge(verb) + } + + public async getVerb(id: string): Promise { + return this.getEdge(id) + } + + public async getAllVerbs(): Promise { + return this.getAllEdges() + } + + public async getVerbsBySource(sourceId: string): Promise { + return this.getEdgesBySource(sourceId) + } + + public async getVerbsByTarget(targetId: string): Promise { + return this.getEdgesByTarget(targetId) + } + + public async getVerbsByType(type: string): Promise { + return this.getEdgesByType(type) + } + + public async deleteVerb(id: string): Promise { + return this.deleteEdge(id) + } + + public async init(): Promise { + // Nothing to initialize for in-memory storage + } + + /** + * Get the appropriate node type for a node based on its metadata + */ + private async getNodeType(id: string): Promise { + try { + // Try to get the metadata for the node + const metadata = await this.getMetadata(id) + + // If metadata exists and has a noun field, use the corresponding type + if (metadata && metadata.noun) { + switch (metadata.noun) { + case 'person': + return PERSON_DIR + case 'place': + return PLACE_DIR + case 'thing': + return THING_DIR + case 'event': + return EVENT_DIR + case 'concept': + return CONCEPT_DIR + case 'content': + return CONTENT_DIR + default: + return DEFAULT_DIR + } + } + + // If no metadata or no noun field, use the default type + return DEFAULT_DIR + } catch (error) { + // If there's an error getting the metadata, use the default type + return DEFAULT_DIR + } + } + + public async saveNode(node: HNSWNode): Promise { + // Create a deep copy to avoid reference issues + const nodeCopy: HNSWNode = { + id: node.id, + vector: [...node.vector], + connections: new Map() + } + + // Copy connections + for (const [level, connections] of node.connections.entries()) { + nodeCopy.connections.set(level, new Set(connections)) + } + + // Get the appropriate node type based on the node's metadata + const nodeType = await this.getNodeType(node.id) + + // Get the map for this node type + const nodeMap = this.nouns.get(nodeType)! + + // Save the node in the appropriate map + nodeMap.set(node.id, nodeCopy) + } + + public async getNode(id: string): Promise { + // Get the appropriate node type based on the node's metadata + const nodeType = await this.getNodeType(id) + + // Get the map for this node type + const nodeMap = this.nouns.get(nodeType)! + + // Try to get the node from the appropriate map + let node = nodeMap.get(id) + + // If not found in the expected map, try other maps + if (!node) { + // If the node type is not the default type, try the default map + if (nodeType !== DEFAULT_DIR) { + const defaultMap = this.nouns.get(DEFAULT_DIR)! + node = defaultMap.get(id) + + // If still not found, try all other maps + if (!node) { + const nodeTypes = [ + PERSON_DIR, + PLACE_DIR, + THING_DIR, + EVENT_DIR, + CONCEPT_DIR, + CONTENT_DIR + ] + for (const type of nodeTypes) { + if (type === nodeType) continue // Skip the already checked map + + const typeMap = this.nouns.get(type)! + node = typeMap.get(id) + if (node) break // Found the node, exit the loop + } + } + } + + // If still not found, return null + if (!node) { + return null + } + } + + // Return a deep copy to avoid reference issues + const nodeCopy: HNSWNode = { + id: node.id, + vector: [...node.vector], + connections: new Map() + } + + // Copy connections + for (const [level, connections] of node.connections.entries()) { + nodeCopy.connections.set(level, new Set(connections)) + } + + return nodeCopy + } + + /** + * Get nodes by noun type + * @param nounType The noun type to filter by + * @returns Promise that resolves to an array of nodes of the specified noun type + */ + public async getNodesByNounType(nounType: string): Promise { + const nodes: HNSWNode[] = [] + + // Get the map for this noun type + let typeMap: Map + switch (nounType) { + case 'person': + typeMap = this.nouns.get(PERSON_DIR)! + break + case 'place': + typeMap = this.nouns.get(PLACE_DIR)! + break + case 'thing': + typeMap = this.nouns.get(THING_DIR)! + break + case 'event': + typeMap = this.nouns.get(EVENT_DIR)! + break + case 'concept': + typeMap = this.nouns.get(CONCEPT_DIR)! + break + case 'content': + typeMap = this.nouns.get(CONTENT_DIR)! + break + default: + typeMap = this.nouns.get(DEFAULT_DIR)! + } + + // Get all nodes from this map + for (const nodeId of typeMap.keys()) { + const node = await this.getNode(nodeId) + if (node) { + nodes.push(node) + } + } + + return nodes + } + + public async getAllNodes(): Promise { + // Get all noun types + const nounTypes = [ + 'person', + 'place', + 'thing', + 'event', + 'concept', + 'content', + 'default' + ] + + // Run searches in parallel for all noun types + const nodePromises = nounTypes.map((nounType) => + this.getNodesByNounType(nounType) + ) + const nodeArrays = await Promise.all(nodePromises) + + // Combine all results + const allNodes: HNSWNode[] = [] + for (const nodes of nodeArrays) { + allNodes.push(...nodes) + } + + return allNodes + } + + public async deleteNode(id: string): Promise { + // Get the appropriate node type based on the node's metadata + const nodeType = await this.getNodeType(id) + + // Get the map for this node type + const nodeMap = this.nouns.get(nodeType)! + + // Try to delete the node from the appropriate map + const deleted = nodeMap.delete(id) + + // If not found in the expected map, try other maps + if (!deleted) { + // If the node type is not the default type, try the default map + if (nodeType !== DEFAULT_DIR) { + const defaultMap = this.nouns.get(DEFAULT_DIR)! + const deletedFromDefault = defaultMap.delete(id) + + // If still not found, try all other maps + if (!deletedFromDefault) { + const nodeTypes = [ + PERSON_DIR, + PLACE_DIR, + THING_DIR, + EVENT_DIR, + CONCEPT_DIR, + CONTENT_DIR + ] + for (const type of nodeTypes) { + if (type === nodeType) continue // Skip the already checked map + + const typeMap = this.nouns.get(type)! + const deletedFromType = typeMap.delete(id) + if (deletedFromType) break // Node deleted, exit the loop + } + } + } + } + } + + public async saveMetadata(id: string, metadata: any): Promise { + this.metadata.set(id, JSON.parse(JSON.stringify(metadata))) + } + + public async getMetadata(id: string): Promise { + const metadata = this.metadata.get(id) + if (!metadata) { + return null + } + + return JSON.parse(JSON.stringify(metadata)) + } + + public async saveEdge(edge: Edge): Promise { + // Create a deep copy to avoid reference issues + const edgeCopy: Edge = { + id: edge.id, + vector: [...edge.vector], + connections: new Map(), + sourceId: edge.sourceId, + targetId: edge.targetId, + type: edge.type, + weight: edge.weight, + metadata: edge.metadata + ? JSON.parse(JSON.stringify(edge.metadata)) + : undefined + } + + // Copy connections + for (const [level, connections] of edge.connections.entries()) { + edgeCopy.connections.set(level, new Set(connections)) + } + + this.verbs.set(edge.id, edgeCopy) + } + + public async getEdge(id: string): Promise { + const edge = this.verbs.get(id) + if (!edge) { + return null + } + + // Return a deep copy to avoid reference issues + const edgeCopy: Edge = { + id: edge.id, + vector: [...edge.vector], + connections: new Map(), + sourceId: edge.sourceId, + targetId: edge.targetId, + type: edge.type, + weight: edge.weight, + metadata: edge.metadata + ? JSON.parse(JSON.stringify(edge.metadata)) + : undefined + } + + // Copy connections + for (const [level, connections] of edge.connections.entries()) { + edgeCopy.connections.set(level, new Set(connections)) + } + + return edgeCopy + } + + public async getAllEdges(): Promise { + const edges: Edge[] = [] + + for (const edgeId of this.verbs.keys()) { + const edge = await this.getEdge(edgeId) + if (edge) { + edges.push(edge) + } + } + + return edges + } + + public async getEdgesBySource(sourceId: string): Promise { + const edges: Edge[] = [] + + for (const edge of this.verbs.values()) { + if (edge.sourceId === sourceId) { + const edgeCopy = await this.getEdge(edge.id) + if (edgeCopy) { + edges.push(edgeCopy) + } + } + } + + return edges + } + + public async getEdgesByTarget(targetId: string): Promise { + const edges: Edge[] = [] + + for (const edge of this.verbs.values()) { + if (edge.targetId === targetId) { + const edgeCopy = await this.getEdge(edge.id) + if (edgeCopy) { + edges.push(edgeCopy) + } + } + } + + return edges + } + + public async getEdgesByType(type: string): Promise { + const edges: Edge[] = [] + + for (const edge of this.verbs.values()) { + if (edge.type === type) { + const edgeCopy = await this.getEdge(edge.id) + if (edgeCopy) { + edges.push(edgeCopy) + } + } + } + + return edges + } + + public async deleteEdge(id: string): Promise { + this.verbs.delete(id) + } + + public async clear(): Promise { + // Clear all noun type maps + const nodeTypes = [ + PERSON_DIR, + PLACE_DIR, + THING_DIR, + EVENT_DIR, + CONCEPT_DIR, + CONTENT_DIR, + DEFAULT_DIR + ] + for (const type of nodeTypes) { + const typeMap = this.nouns.get(type)! + typeMap.clear() + } + + this.verbs.clear() + this.metadata.clear() + } + + /** + * Get information about storage usage and capacity + */ + public async getStorageStatus(): Promise<{ + type: string + used: number + quota: number | null + details?: Record + }> { + try { + // Estimate the size of data in memory + let totalSize = 0 + + // Helper function to estimate object size in bytes + const estimateSize = (obj: any): number => { + if (obj === null || obj === undefined) return 0 + + const type = typeof obj + + // Handle primitive types + if (type === 'number') return 8 + if (type === 'string') return obj.length * 2 + if (type === 'boolean') return 4 + + // Handle arrays and objects + if (Array.isArray(obj)) { + return obj.reduce((size, item) => size + estimateSize(item), 0) + } + + if (type === 'object') { + if (obj instanceof Map) { + let mapSize = 0 + for (const [key, value] of obj.entries()) { + mapSize += estimateSize(key) + estimateSize(value) + } + return mapSize + } + + if (obj instanceof Set) { + let setSize = 0 + for (const item of obj) { + setSize += estimateSize(item) + } + return setSize + } + + // Regular object + return Object.entries(obj).reduce( + (size, [key, value]) => size + key.length * 2 + estimateSize(value), + 0 + ) + } + + return 0 + } + + // Calculate sizes and counts for each noun type + const nounTypeDetails: Record = + {} + const nodeTypes = [ + PERSON_DIR, + PLACE_DIR, + THING_DIR, + EVENT_DIR, + CONCEPT_DIR, + CONTENT_DIR, + DEFAULT_DIR + ] + + let totalNodeCount = 0 + let nodesSize = 0 + + for (const type of nodeTypes) { + const typeMap = this.nouns.get(type)! + let typeSize = 0 + + for (const node of typeMap.values()) { + typeSize += estimateSize(node) + } + + nounTypeDetails[type] = { + size: typeSize, + count: typeMap.size + } + + nodesSize += typeSize + totalNodeCount += typeMap.size + } + + totalSize += nodesSize + + // Estimate size of edges + let edgesSize = 0 + for (const edge of this.verbs.values()) { + edgesSize += estimateSize(edge) + } + totalSize += edgesSize + + // Estimate size of metadata + let metadataSize = 0 + for (const meta of this.metadata.values()) { + metadataSize += estimateSize(meta) + } + totalSize += metadataSize + + // Get memory information if available + let quota = null + let details: Record = { + nodeCount: totalNodeCount, + edgeCount: this.verbs.size, + metadataCount: this.metadata.size, + nounTypes: nounTypeDetails, + nodesSize, + edgesSize, + metadataSize + } + + // Try to get memory information if in a browser environment + if ( + typeof window !== 'undefined' && + (window as any).performance && + (window as any).performance.memory + ) { + const memory = (window as any).performance.memory + quota = memory.jsHeapSizeLimit + details = { + ...details, + totalJSHeapSize: memory.totalJSHeapSize, + usedJSHeapSize: memory.usedJSHeapSize, + jsHeapSizeLimit: memory.jsHeapSizeLimit + } + } + + return { + type: 'memory', + used: totalSize, + quota, + details + } + } catch (error) { + console.error('Failed to get memory storage status:', error) + return { + type: 'memory', + used: 0, + quota: null, + details: { error: String(error) } + } + } + } +} + +/** + * Factory function to create the appropriate storage adapter based on the environment + * @param options Configuration options for storage + * @returns Promise that resolves to a StorageAdapter instance + */ +export async function createStorage( + options: { + requestPersistentStorage?: boolean + r2Storage?: { + bucketName?: string + accountId?: string + accessKeyId?: string + secretAccessKey?: string + } + s3Storage?: { + bucketName?: string + accessKeyId?: string + secretAccessKey?: string + region?: string + } + gcsStorage?: { + bucketName?: string + accessKeyId?: string + secretAccessKey?: string + endpoint?: string + } + customS3Storage?: { + bucketName?: string + accessKeyId?: string + secretAccessKey?: string + endpoint?: string + region?: string + } + forceFileSystemStorage?: boolean + forceMemoryStorage?: boolean + } = {} +): Promise { + // Detect environment + const environment = { + isBrowser: typeof window !== 'undefined', + isNode: + typeof process !== 'undefined' && + process.versions != null && + process.versions.node != null, + isServerless: + typeof window === 'undefined' && + (typeof process === 'undefined' || + !process.versions || + !process.versions.node) + } + + // If force memory storage is specified, use MemoryStorage regardless of environment + if (options.forceMemoryStorage) { + console.log('Using in-memory storage (forced by configuration)') + return new MemoryStorage() + } + + // Default empty values for environment variables + const defaultEnvStorage = { + bucketName: undefined, + accountId: undefined, + accessKeyId: undefined, + secretAccessKey: undefined, + region: undefined, + endpoint: undefined + } + + // Try to use cloud storage if configured + if ( + options.r2Storage || + options.s3Storage || + options.gcsStorage || + options.customS3Storage || + (environment.isNode && + (process.env.R2_BUCKET_NAME || + process.env.S3_BUCKET_NAME || + process.env.GCS_BUCKET_NAME)) + ) { + try { + // Only try to access process.env in Node.js environment + const envR2Storage = environment.isNode + ? { + bucketName: process.env.R2_BUCKET_NAME, + accountId: process.env.R2_ACCOUNT_ID, + accessKeyId: process.env.R2_ACCESS_KEY_ID, + secretAccessKey: process.env.R2_SECRET_ACCESS_KEY + } + : defaultEnvStorage + + const envS3Storage = environment.isNode + ? { + bucketName: process.env.S3_BUCKET_NAME, + accessKeyId: + process.env.S3_ACCESS_KEY_ID || process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: + process.env.S3_SECRET_ACCESS_KEY || + process.env.AWS_SECRET_ACCESS_KEY, + region: process.env.S3_REGION || process.env.AWS_REGION + } + : defaultEnvStorage + + const envGCSStorage = environment.isNode + ? { + bucketName: process.env.GCS_BUCKET_NAME, + accessKeyId: process.env.GCS_ACCESS_KEY_ID, + secretAccessKey: process.env.GCS_SECRET_ACCESS_KEY, + endpoint: process.env.GCS_ENDPOINT + } + : defaultEnvStorage + + // Merge environment variables with provided options + const mergedOptions = { + ...options, + r2Storage: options.r2Storage + ? { + ...envR2Storage, + ...options.r2Storage + } + : // Only use environment variables if they have the required fields + envR2Storage.bucketName && + envR2Storage.accessKeyId && + envR2Storage.secretAccessKey + ? envR2Storage + : undefined, + s3Storage: options.s3Storage + ? { + ...envS3Storage, + ...options.s3Storage + } + : // Only use environment variables if they have the required fields + envS3Storage.bucketName && + envS3Storage.accessKeyId && + envS3Storage.secretAccessKey + ? envS3Storage + : undefined, + gcsStorage: options.gcsStorage + ? { + ...envGCSStorage, + ...options.gcsStorage + } + : // Only use environment variables if they have the required fields + envGCSStorage.bucketName && + envGCSStorage.accessKeyId && + envGCSStorage.secretAccessKey + ? envGCSStorage + : undefined + } + + const s3Module = await import('./s3CompatibleStorage.js') + + if ( + mergedOptions.r2Storage && + mergedOptions.r2Storage.bucketName && + mergedOptions.r2Storage.accountId && + mergedOptions.r2Storage.accessKeyId && + mergedOptions.r2Storage.secretAccessKey + ) { + console.log('Using Cloudflare R2 storage') + return new s3Module.R2Storage({ + bucketName: mergedOptions.r2Storage.bucketName, + accountId: mergedOptions.r2Storage.accountId, + accessKeyId: mergedOptions.r2Storage.accessKeyId, + secretAccessKey: mergedOptions.r2Storage.secretAccessKey, + serviceType: 'r2' + }) + } else if ( + mergedOptions.s3Storage && + mergedOptions.s3Storage.bucketName && + mergedOptions.s3Storage.accessKeyId && + mergedOptions.s3Storage.secretAccessKey + ) { + console.log('Using Amazon S3 storage') + return new s3Module.S3CompatibleStorage({ + bucketName: mergedOptions.s3Storage.bucketName, + accessKeyId: mergedOptions.s3Storage.accessKeyId, + secretAccessKey: mergedOptions.s3Storage.secretAccessKey, + region: mergedOptions.s3Storage.region, + serviceType: 's3' + }) + } else if ( + mergedOptions.gcsStorage && + mergedOptions.gcsStorage.bucketName && + mergedOptions.gcsStorage.accessKeyId && + mergedOptions.gcsStorage.secretAccessKey + ) { + console.log('Using Google Cloud Storage') + return new s3Module.S3CompatibleStorage({ + bucketName: mergedOptions.gcsStorage.bucketName, + accessKeyId: mergedOptions.gcsStorage.accessKeyId, + secretAccessKey: mergedOptions.gcsStorage.secretAccessKey, + endpoint: mergedOptions.gcsStorage.endpoint, + serviceType: 'gcs' + }) + } else if ( + mergedOptions.customS3Storage && + mergedOptions.customS3Storage.bucketName && + mergedOptions.customS3Storage.accessKeyId && + mergedOptions.customS3Storage.secretAccessKey + ) { + console.log('Using custom S3-compatible storage') + return new s3Module.S3CompatibleStorage({ + bucketName: mergedOptions.customS3Storage.bucketName, + accessKeyId: mergedOptions.customS3Storage.accessKeyId, + secretAccessKey: mergedOptions.customS3Storage.secretAccessKey, + endpoint: mergedOptions.customS3Storage.endpoint, + region: mergedOptions.customS3Storage.region, + serviceType: 'custom' + }) + } + } catch (error) { + console.warn( + 'Failed to load S3CompatibleStorage, falling back to environment-specific storage:', + error + ) + // Continue to environment-specific storage selection + } + } + + // Environment-specific storage selection + if (environment.isBrowser) { + // In browser environments + if (!options.forceFileSystemStorage) { + try { + // Try OPFS first (Origin Private File System - browser-specific) + const opfsStorage = new OPFSStorage() + + if (opfsStorage.isOPFSAvailable()) { + // Request persistent storage if specified + if (options.requestPersistentStorage) { + const isPersistentGranted = + await opfsStorage.requestPersistentStorage() + if (isPersistentGranted) { + console.log('Persistent storage permission granted') + } else { + console.warn('Persistent storage permission denied') + } + } + console.log('Using Origin Private File System (OPFS) storage') + return opfsStorage + } + } catch (error) { + console.warn('OPFS storage initialization failed:', error) + } + } + + // Fall back to memory storage for browser environments + console.log('Using in-memory storage for browser environment') + return new MemoryStorage() + } else if (environment.isNode) { + // In Node.js environments + try { + const fileSystemModule = await import('./fileSystemStorage.js') + console.log('Using file system storage for Node.js environment') + return new fileSystemModule.FileSystemStorage() + } catch (error) { + console.warn('Failed to load FileSystemStorage:', error) + console.log('Using in-memory storage for Node.js environment') + return new MemoryStorage() + } + } else { + // In serverless or other environments + console.log('Using in-memory storage for serverless/unknown environment') + return new MemoryStorage() + } +} diff --git a/src/storage/s3CompatibleStorage.ts b/src/storage/s3CompatibleStorage.ts new file mode 100644 index 00000000..681827ca --- /dev/null +++ b/src/storage/s3CompatibleStorage.ts @@ -0,0 +1,1106 @@ +import { GraphVerb, HNSWNoun, StorageAdapter } from '../coreTypes.js' + +// Type aliases for compatibility +type HNSWNode = HNSWNoun +type Edge = GraphVerb + +// Export R2Storage as an alias for S3CompatibleStorage +export { S3CompatibleStorage as R2Storage } + +// Constants for S3 bucket prefixes +const NOUNS_PREFIX = 'nouns/' +const VERBS_PREFIX = 'verbs/' +const EDGES_PREFIX = 'verbs/' // Alias for VERBS_PREFIX for edge operations +const METADATA_PREFIX = 'metadata/' + +// Constants for noun type prefixes +const PERSON_PREFIX = 'nouns/person/' +const PLACE_PREFIX = 'place/' +const THING_PREFIX = 'thing/' +const EVENT_PREFIX = 'event/' +const CONCEPT_PREFIX = 'concept/' +const CONTENT_PREFIX = 'content/' +const DEFAULT_PREFIX = 'default/' // For nodes without a noun type + +/** + * S3-compatible storage adapter for server environments + * Uses the AWS S3 client to interact with S3-compatible storage services + * including Amazon S3, Cloudflare R2, and Google Cloud Storage + * + * To use this adapter with Cloudflare R2, you need to provide: + * - bucketName: Your bucket name + * - accountId: Your Cloudflare account ID + * - accessKeyId: R2 access key ID + * - secretAccessKey: R2 secret access key + * - serviceType: 'r2' + * + * To use this adapter with Amazon S3, you need to provide: + * - bucketName: Your S3 bucket name + * - accessKeyId: AWS access key ID + * - secretAccessKey: AWS secret access key + * - region: AWS region (e.g., 'us-east-1') + * - serviceType: 's3' + * + * To use this adapter with Google Cloud Storage, you need to provide: + * - bucketName: Your GCS bucket name + * - accessKeyId: HMAC access key + * - secretAccessKey: HMAC secret + * - endpoint: GCS endpoint (e.g., 'https://storage.googleapis.com') + * - serviceType: 'gcs' + * + * For other S3-compatible services, provide: + * - bucketName: Your bucket name + * - accessKeyId: Access key ID + * - secretAccessKey: Secret access key + * - endpoint: Service endpoint URL + * - region: Region (if required) + * - serviceType: 'custom' + */ +export class S3CompatibleStorage implements StorageAdapter { + private bucketName: string + private accessKeyId: string + private secretAccessKey: string + private endpoint?: string + private region?: string + private accountId?: string + private serviceType: 'r2' | 's3' | 'gcs' | 'custom' + private s3Client: any // Will be initialized in init() + private isInitialized = false + + // Alias methods to match StorageAdapter interface + public async saveNoun(noun: HNSWNoun): Promise { + return this.saveNode(noun) + } + + public async getNoun(id: string): Promise { + return this.getNode(id) + } + + public async getAllNouns(): Promise { + return this.getAllNodes() + } + + public async getNounsByNounType(nounType: string): Promise { + return this.getNodesByNounType(nounType) + } + + public async deleteNoun(id: string): Promise { + return this.deleteNode(id) + } + + public async saveVerb(verb: GraphVerb): Promise { + return this.saveEdge(verb) + } + + public async getVerb(id: string): Promise { + return this.getEdge(id) + } + + public async getAllVerbs(): Promise { + return this.getAllEdges() + } + + public async getVerbsBySource(sourceId: string): Promise { + return this.getEdgesBySource(sourceId) + } + + public async getVerbsByTarget(targetId: string): Promise { + return this.getEdgesByTarget(targetId) + } + + public async getVerbsByType(type: string): Promise { + return this.getEdgesByType(type) + } + + public async deleteVerb(id: string): Promise { + return this.deleteEdge(id) + } + + constructor(options: { + bucketName: string + accessKeyId: string + secretAccessKey: string + serviceType: 'r2' | 's3' | 'gcs' | 'custom' + accountId?: string + region?: string + endpoint?: string + }) { + this.bucketName = options.bucketName + this.accessKeyId = options.accessKeyId + this.secretAccessKey = options.secretAccessKey + this.serviceType = options.serviceType + this.accountId = options.accountId + this.region = options.region + this.endpoint = options.endpoint + } + + /** + * Initialize the storage adapter + */ + public async init(): Promise { + if (this.isInitialized) { + return + } + + try { + // Dynamically import the AWS SDK to avoid bundling it in browser environments + try { + const { S3Client } = await import('@aws-sdk/client-s3') + + // Configure the S3 client based on the service type + const clientConfig: any = { + credentials: { + accessKeyId: this.accessKeyId, + secretAccessKey: this.secretAccessKey + } + } + + switch (this.serviceType) { + case 'r2': + if (!this.accountId) { + throw new Error('accountId is required for Cloudflare R2') + } + clientConfig.region = 'auto' // R2 uses 'auto' as the region + clientConfig.endpoint = `https://${this.accountId}.r2.cloudflarestorage.com` + break + + case 's3': + if (!this.region) { + throw new Error('region is required for Amazon S3') + } + clientConfig.region = this.region + // No endpoint needed for standard S3 + break + + case 'gcs': + if (!this.endpoint) { + // Default GCS endpoint if not provided + this.endpoint = 'https://storage.googleapis.com' + } + clientConfig.endpoint = this.endpoint + clientConfig.region = this.region || 'auto' + break + + case 'custom': + if (!this.endpoint) { + throw new Error( + 'endpoint is required for custom S3-compatible services' + ) + } + clientConfig.endpoint = this.endpoint + if (this.region) { + clientConfig.region = this.region + } + break + + default: + throw new Error(`Unsupported service type: ${this.serviceType}`) + } + + // Initialize the S3 client with the configured options + this.s3Client = new S3Client(clientConfig) + + this.isInitialized = true + } catch (importError) { + throw new Error( + `Failed to import AWS SDK: ${importError}. Make sure @aws-sdk/client-s3 is installed.` + ) + } + } catch (error) { + console.error(`Failed to initialize ${this.serviceType} storage:`, error) + throw new Error( + `Failed to initialize ${this.serviceType} storage: ${error}` + ) + } + } + + /** + * Save a node to storage + */ + public async saveNode(node: HNSWNode): Promise { + await this.ensureInitialized() + + try { + // Convert connections Map to a serializable format + const serializableNode = { + ...node, + connections: this.mapToObject(node.connections, (set) => + Array.from(set as Set) + ) + } + + // Get the appropriate prefix based on the node's metadata + const nodePrefix = await this.getNodePrefix(node.id) + + // Import the PutObjectCommand only when needed + const { PutObjectCommand } = await import('@aws-sdk/client-s3') + + // Save the node to S3-compatible storage + await this.s3Client.send( + new PutObjectCommand({ + Bucket: this.bucketName, + Key: `${nodePrefix}${node.id}.json`, + Body: JSON.stringify(serializableNode, null, 2), + ContentType: 'application/json' + }) + ) + } catch (error) { + console.error(`Failed to save node ${node.id}:`, error) + throw new Error(`Failed to save node ${node.id}: ${error}`) + } + } + + /** + * Get a node from storage + */ + public async getNode(id: string): Promise { + await this.ensureInitialized() + + try { + // Get the appropriate prefix based on the node's metadata + const nodePrefix = await this.getNodePrefix(id) + + // Import the GetObjectCommand only when needed + const { GetObjectCommand } = await import('@aws-sdk/client-s3') + + try { + // Try to get the node from S3-compatible storage + const response = await this.s3Client.send( + new GetObjectCommand({ + Bucket: this.bucketName, + Key: `${nodePrefix}${id}.json` + }) + ) + + // Convert the response body to a string + const bodyContents = await response.Body.transformToString() + const parsedNode = JSON.parse(bodyContents) + + // Convert serialized connections back to Map> + const connections = new Map>() + for (const [level, nodeIds] of Object.entries(parsedNode.connections)) { + connections.set(Number(level), new Set(nodeIds as string[])) + } + + return { + id: parsedNode.id, + vector: parsedNode.vector, + connections + } + } catch (error) { + // If the node is not found in the expected prefix, try other prefixes + if (nodePrefix !== DEFAULT_PREFIX) { + // Try the default prefix + try { + const response = await this.s3Client.send( + new GetObjectCommand({ + Bucket: this.bucketName, + Key: `${NOUNS_PREFIX}${DEFAULT_PREFIX}${id}.json` + }) + ) + + const bodyContents = await response.Body.transformToString() + const parsedNode = JSON.parse(bodyContents) + + // Convert serialized connections back to Map> + const connections = new Map>() + for (const [level, nodeIds] of Object.entries( + parsedNode.connections + )) { + connections.set(Number(level), new Set(nodeIds as string[])) + } + + return { + id: parsedNode.id, + vector: parsedNode.vector, + connections + } + } catch { + // If not found in default prefix, try all other prefixes + const prefixes = [ + PERSON_PREFIX, + PLACE_PREFIX, + THING_PREFIX, + EVENT_PREFIX, + CONCEPT_PREFIX, + CONTENT_PREFIX + ] + + for (const prefix of prefixes) { + if (prefix === nodePrefix) continue // Skip the already checked prefix + + try { + const response = await this.s3Client.send( + new GetObjectCommand({ + Bucket: this.bucketName, + Key: `${NOUNS_PREFIX}${prefix}${id}.json` + }) + ) + + const bodyContents = await response.Body.transformToString() + const parsedNode = JSON.parse(bodyContents) + + // Convert serialized connections back to Map> + const connections = new Map>() + for (const [level, nodeIds] of Object.entries( + parsedNode.connections + )) { + connections.set(Number(level), new Set(nodeIds as string[])) + } + + return { + id: parsedNode.id, + vector: parsedNode.vector, + connections + } + } catch { + // Continue to the next prefix + } + } + } + } + + return null // Node not found in any prefix + } + } catch (error) { + console.error(`Failed to get node ${id}:`, error) + return null + } + } + + /** + * Get nodes by noun type + * @param nounType The noun type to filter by + * @returns Promise that resolves to an array of nodes of the specified noun type + */ + public async getNodesByNounType(nounType: string): Promise { + await this.ensureInitialized() + + try { + // Determine the prefix based on the noun type + let prefix: string + switch (nounType) { + case 'person': + prefix = PERSON_PREFIX + break + case 'place': + prefix = PLACE_PREFIX + break + case 'thing': + prefix = THING_PREFIX + break + case 'event': + prefix = EVENT_PREFIX + break + case 'concept': + prefix = CONCEPT_PREFIX + break + case 'content': + prefix = CONTENT_PREFIX + break + default: + prefix = DEFAULT_PREFIX + } + + // Import the ListObjectsV2Command and GetObjectCommand only when needed + const { ListObjectsV2Command, GetObjectCommand } = await import( + '@aws-sdk/client-s3' + ) + + // List all objects with the specified prefix + const listResponse = await this.s3Client.send( + new ListObjectsV2Command({ + Bucket: this.bucketName, + Prefix: `${NOUNS_PREFIX}${prefix}` + }) + ) + + const nodes: HNSWNode[] = [] + + // If there are no objects, return an empty array + if (!listResponse.Contents || listResponse.Contents.length === 0) { + return nodes + } + + // Get each node + const nodePromises = listResponse.Contents.map( + async (object: { Key: string }) => { + try { + const response = await this.s3Client.send( + new GetObjectCommand({ + Bucket: this.bucketName, + Key: object.Key + }) + ) + + const bodyContents = await response.Body.transformToString() + const parsedNode = JSON.parse(bodyContents) + + // Convert serialized connections back to Map> + const connections = new Map>() + for (const [level, nodeIds] of Object.entries( + parsedNode.connections + )) { + connections.set(Number(level), new Set(nodeIds as string[])) + } + + return { + id: parsedNode.id, + vector: parsedNode.vector, + connections + } + } catch (error) { + console.error(`Failed to get node from ${object.Key}:`, error) + return null + } + } + ) + + const nodeResults = await Promise.all(nodePromises) + return nodeResults.filter((node): node is HNSWNode => node !== null) + } catch (error) { + console.error(`Failed to get nodes for noun type ${nounType}:`, error) + throw new Error(`Failed to get nodes for noun type ${nounType}: ${error}`) + } + } + + /** + * Get all nodes from storage + */ + public async getAllNodes(): Promise { + await this.ensureInitialized() + + try { + // Get all noun types + const nounTypes = [ + 'person', + 'place', + 'thing', + 'event', + 'concept', + 'content', + 'default' + ] + + // Run searches in parallel for all noun types + const nodePromises = nounTypes.map((nounType) => + this.getNodesByNounType(nounType) + ) + const nodeArrays = await Promise.all(nodePromises) + + // Combine all results + const allNodes: HNSWNode[] = [] + for (const nodes of nodeArrays) { + allNodes.push(...nodes) + } + + return allNodes + } catch (error) { + console.error('Failed to get all nodes:', error) + throw new Error(`Failed to get all nodes: ${error}`) + } + } + + /** + * Delete a node from storage + */ + public async deleteNode(id: string): Promise { + await this.ensureInitialized() + + try { + // Get the appropriate prefix based on the node's metadata + const nodePrefix = await this.getNodePrefix(id) + + // Import the DeleteObjectCommand only when needed + const { DeleteObjectCommand, GetObjectCommand } = await import( + '@aws-sdk/client-s3' + ) + + try { + // Check if the node exists before deleting + await this.s3Client.send( + new GetObjectCommand({ + Bucket: this.bucketName, + Key: `${nodePrefix}${id}.json` + }) + ) + + // Delete the node + await this.s3Client.send( + new DeleteObjectCommand({ + Bucket: this.bucketName, + Key: `${nodePrefix}${id}.json` + }) + ) + return // Node found and deleted + } catch { + // If the node is not found in the expected prefix, try other prefixes + if (nodePrefix !== DEFAULT_PREFIX) { + try { + // Try the default prefix + await this.s3Client.send( + new GetObjectCommand({ + Bucket: this.bucketName, + Key: `${NOUNS_PREFIX}${DEFAULT_PREFIX}${id}.json` + }) + ) + + // Delete the node + await this.s3Client.send( + new DeleteObjectCommand({ + Bucket: this.bucketName, + Key: `${NOUNS_PREFIX}${DEFAULT_PREFIX}${id}.json` + }) + ) + return // Node found and deleted + } catch { + // If not found in default prefix, try all other prefixes + const prefixes = [ + PERSON_PREFIX, + PLACE_PREFIX, + THING_PREFIX, + EVENT_PREFIX, + CONCEPT_PREFIX, + CONTENT_PREFIX + ] + + for (const prefix of prefixes) { + if (prefix === nodePrefix) continue // Skip the already checked prefix + + try { + await this.s3Client.send( + new GetObjectCommand({ + Bucket: this.bucketName, + Key: `${NOUNS_PREFIX}${prefix}${id}.json` + }) + ) + + // Delete the node + await this.s3Client.send( + new DeleteObjectCommand({ + Bucket: this.bucketName, + Key: `${NOUNS_PREFIX}${prefix}${id}.json` + }) + ) + return // Node found and deleted + } catch { + // Continue to the next prefix + } + } + } + } + + return // Node not found in any prefix, nothing to delete + } + } catch (error) { + console.error(`Failed to delete node ${id}:`, error) + throw new Error(`Failed to delete node ${id}: ${error}`) + } + } + + /** + * Save an edge to storage + */ + public async saveEdge(edge: Edge): Promise { + await this.ensureInitialized() + + try { + // Convert connections Map to a serializable format + const serializableEdge = { + ...edge, + connections: this.mapToObject(edge.connections, (set) => + Array.from(set as Set) + ) + } + + // Import the PutObjectCommand only when needed + const { PutObjectCommand } = await import('@aws-sdk/client-s3') + + // Save the edge to S3-compatible storage + await this.s3Client.send( + new PutObjectCommand({ + Bucket: this.bucketName, + Key: `${VERBS_PREFIX}${edge.id}.json`, + Body: JSON.stringify(serializableEdge, null, 2), + ContentType: 'application/json' + }) + ) + } catch (error) { + console.error(`Failed to save edge ${edge.id}:`, error) + throw new Error(`Failed to save edge ${edge.id}: ${error}`) + } + } + + /** + * Get an edge from storage + */ + public async getEdge(id: string): Promise { + await this.ensureInitialized() + + try { + // Import the GetObjectCommand only when needed + const { GetObjectCommand } = await import('@aws-sdk/client-s3') + + try { + // Try to get the edge from S3-compatible storage + const response = await this.s3Client.send( + new GetObjectCommand({ + Bucket: this.bucketName, + Key: `${VERBS_PREFIX}${id}.json` + }) + ) + + // Convert the response body to a string + const bodyContents = await response.Body.transformToString() + const parsedEdge = JSON.parse(bodyContents) + + // Convert serialized connections back to Map> + const connections = new Map>() + for (const [level, nodeIds] of Object.entries(parsedEdge.connections)) { + connections.set(Number(level), new Set(nodeIds as string[])) + } + + return { + id: parsedEdge.id, + vector: parsedEdge.vector, + connections, + sourceId: parsedEdge.sourceId, + targetId: parsedEdge.targetId, + type: parsedEdge.type, + weight: parsedEdge.weight, + metadata: parsedEdge.metadata + } + } catch { + return null // Edge not found + } + } catch (error) { + console.error(`Failed to get edge ${id}:`, error) + return null + } + } + + /** + * Get all edges from storage + */ + public async getAllEdges(): Promise { + await this.ensureInitialized() + + try { + // Import the ListObjectsV2Command and GetObjectCommand only when needed + const { ListObjectsV2Command, GetObjectCommand } = await import( + '@aws-sdk/client-s3' + ) + + // List all objects with the edges prefix + const listResponse = await this.s3Client.send( + new ListObjectsV2Command({ + Bucket: this.bucketName, + Prefix: VERBS_PREFIX + }) + ) + + const edges: Edge[] = [] + + // If there are no objects, return an empty array + if (!listResponse.Contents || listResponse.Contents.length === 0) { + return edges + } + + // Get each edge + const edgePromises = listResponse.Contents.map( + async (object: { Key: string }) => { + try { + const response = await this.s3Client.send( + new GetObjectCommand({ + Bucket: this.bucketName, + Key: object.Key + }) + ) + + const bodyContents = await response.Body.transformToString() + const parsedEdge = JSON.parse(bodyContents) + + // Convert serialized connections back to Map> + const connections = new Map>() + for (const [level, nodeIds] of Object.entries( + parsedEdge.connections + )) { + connections.set(Number(level), new Set(nodeIds as string[])) + } + + return { + id: parsedEdge.id, + vector: parsedEdge.vector, + connections, + sourceId: parsedEdge.sourceId, + targetId: parsedEdge.targetId, + type: parsedEdge.type, + weight: parsedEdge.weight, + metadata: parsedEdge.metadata + } + } catch (error) { + console.error(`Failed to get edge from ${object.Key}:`, error) + return null + } + } + ) + + const edgeResults = await Promise.all(edgePromises) + return edgeResults.filter((edge): edge is Edge => edge !== null) + } catch (error) { + console.error('Failed to get all edges:', error) + throw new Error(`Failed to get all edges: ${error}`) + } + } + + /** + * Get edges by source node ID + */ + public async getEdgesBySource(sourceId: string): Promise { + await this.ensureInitialized() + + try { + const allEdges = await this.getAllEdges() + return allEdges.filter((edge) => edge.sourceId === sourceId) + } catch (error) { + console.error(`Failed to get edges by source ${sourceId}:`, error) + throw new Error(`Failed to get edges by source ${sourceId}: ${error}`) + } + } + + /** + * Get edges by target node ID + */ + public async getEdgesByTarget(targetId: string): Promise { + await this.ensureInitialized() + + try { + const allEdges = await this.getAllEdges() + return allEdges.filter((edge) => edge.targetId === targetId) + } catch (error) { + console.error(`Failed to get edges by target ${targetId}:`, error) + throw new Error(`Failed to get edges by target ${targetId}: ${error}`) + } + } + + /** + * Get edges by type + */ + public async getEdgesByType(type: string): Promise { + await this.ensureInitialized() + + try { + const allEdges = await this.getAllEdges() + return allEdges.filter((edge) => edge.type === type) + } catch (error) { + console.error(`Failed to get edges by type ${type}:`, error) + throw new Error(`Failed to get edges by type ${type}: ${error}`) + } + } + + /** + * Delete an edge from storage + */ + public async deleteEdge(id: string): Promise { + await this.ensureInitialized() + + try { + // Import the DeleteObjectCommand and GetObjectCommand only when needed + const { DeleteObjectCommand, GetObjectCommand } = await import( + '@aws-sdk/client-s3' + ) + + try { + // Check if the edge exists before deleting + await this.s3Client.send( + new GetObjectCommand({ + Bucket: this.bucketName, + Key: `${EDGES_PREFIX}${id}.json` + }) + ) + + // Delete the edge + await this.s3Client.send( + new DeleteObjectCommand({ + Bucket: this.bucketName, + Key: `${EDGES_PREFIX}${id}.json` + }) + ) + } catch { + // Edge not found, nothing to delete + return + } + } catch (error) { + console.error(`Failed to delete edge ${id}:`, error) + throw new Error(`Failed to delete edge ${id}: ${error}`) + } + } + + /** + * Save metadata to storage + */ + public async saveMetadata(id: string, metadata: any): Promise { + await this.ensureInitialized() + + try { + // Import the PutObjectCommand only when needed + const { PutObjectCommand } = await import('@aws-sdk/client-s3') + + // Save the metadata to S3-compatible storage + await this.s3Client.send( + new PutObjectCommand({ + Bucket: this.bucketName, + Key: `${METADATA_PREFIX}${id}.json`, + Body: JSON.stringify(metadata, null, 2), + ContentType: 'application/json' + }) + ) + } catch (error) { + console.error(`Failed to save metadata for ${id}:`, error) + throw new Error(`Failed to save metadata for ${id}: ${error}`) + } + } + + /** + * Get metadata from storage + */ + public async getMetadata(id: string): Promise { + await this.ensureInitialized() + + try { + // Import the GetObjectCommand only when needed + const { GetObjectCommand } = await import('@aws-sdk/client-s3') + + try { + // Try to get the metadata from S3-compatible storage + const response = await this.s3Client.send( + new GetObjectCommand({ + Bucket: this.bucketName, + Key: `${METADATA_PREFIX}${id}.json` + }) + ) + + // Convert the response body to a string + const bodyContents = await response.Body.transformToString() + return JSON.parse(bodyContents) + } catch { + return null // Metadata not found + } + } catch (error) { + console.error(`Failed to get metadata for ${id}:`, error) + return null + } + } + + /** + * Clear all data from storage + */ + public async clear(): Promise { + await this.ensureInitialized() + + try { + // Import the ListObjectsV2Command and DeleteObjectCommand only when needed + const { ListObjectsV2Command, DeleteObjectCommand } = await import( + '@aws-sdk/client-s3' + ) + + // List all objects in the bucket + const listResponse = await this.s3Client.send( + new ListObjectsV2Command({ + Bucket: this.bucketName + }) + ) + + // If there are no objects, return + if (!listResponse.Contents || listResponse.Contents.length === 0) { + return + } + + // Delete each object + const deletePromises = listResponse.Contents.map( + async (object: { Key: string }) => { + try { + await this.s3Client.send( + new DeleteObjectCommand({ + Bucket: this.bucketName, + Key: object.Key + }) + ) + } catch (error) { + console.error(`Failed to delete object ${object.Key}:`, error) + } + } + ) + + await Promise.all(deletePromises) + } catch (error) { + console.error('Failed to clear storage:', error) + throw new Error(`Failed to clear storage: ${error}`) + } + } + + /** + * Get information about storage usage and capacity + */ + public async getStorageStatus(): Promise<{ + type: string + used: number + quota: number | null + details?: Record + }> { + await this.ensureInitialized() + + try { + // Import the ListObjectsV2Command only when needed + const { ListObjectsV2Command } = await import('@aws-sdk/client-s3') + + // List all objects in the bucket to calculate total size + const listResponse = await this.s3Client.send( + new ListObjectsV2Command({ + Bucket: this.bucketName + }) + ) + + let totalSize = 0 + let nodeCount = 0 + let edgeCount = 0 + let metadataCount = 0 + + // Calculate total size and counts + if (listResponse.Contents) { + for (const object of listResponse.Contents) { + totalSize += object.Size || 0 + + const key = object.Key || '' + if (key.startsWith(NOUNS_PREFIX)) { + nodeCount++ + } else if (key.startsWith(VERBS_PREFIX)) { + edgeCount++ + } else if (key.startsWith(METADATA_PREFIX)) { + metadataCount++ + } + } + } + + // Count nodes by noun type + const nounTypeCounts: Record = { + person: 0, + place: 0, + thing: 0, + event: 0, + concept: 0, + content: 0, + default: 0 + } + + // List objects for each noun type prefix + const nounTypes = [ + { type: 'person', prefix: PERSON_PREFIX }, + { type: 'place', prefix: PLACE_PREFIX }, + { type: 'thing', prefix: THING_PREFIX }, + { type: 'event', prefix: EVENT_PREFIX }, + { type: 'concept', prefix: CONCEPT_PREFIX }, + { type: 'content', prefix: CONTENT_PREFIX }, + { type: 'default', prefix: DEFAULT_PREFIX } + ] + + for (const { type, prefix } of nounTypes) { + const listResponse = await this.s3Client.send( + new ListObjectsV2Command({ + Bucket: this.bucketName, + Prefix: `${NOUNS_PREFIX}${prefix}` + }) + ) + + nounTypeCounts[type] = listResponse.Contents?.length || 0 + } + + return { + type: this.serviceType, + used: totalSize, + quota: null, // S3-compatible services typically don't provide quota information through the API + details: { + nodeCount, + edgeCount, + metadataCount, + nounTypes: { + person: { count: nounTypeCounts.person }, + place: { count: nounTypeCounts.place }, + thing: { count: nounTypeCounts.thing }, + event: { count: nounTypeCounts.event }, + concept: { count: nounTypeCounts.concept }, + content: { count: nounTypeCounts.content }, + default: { count: nounTypeCounts.default } + } + } + } + } catch (error) { + console.error('Failed to get storage status:', error) + return { + type: this.serviceType, + used: 0, + quota: null, + details: { error: String(error) } + } + } + } + + /** + * Ensure the storage adapter is initialized + */ + private async ensureInitialized(): Promise { + if (!this.isInitialized) { + await this.init() + } + } + + /** + * Get the appropriate prefix for a node based on its metadata + */ + private async getNodePrefix(id: string): Promise { + try { + // Try to get the metadata for the node + const metadata = await this.getMetadata(id) + + // If metadata exists and has a noun field, use the corresponding prefix + if (metadata && metadata.noun) { + switch (metadata.noun) { + case 'person': + return PERSON_PREFIX + case 'place': + return PLACE_PREFIX + case 'thing': + return THING_PREFIX + case 'event': + return EVENT_PREFIX + case 'concept': + return CONCEPT_PREFIX + case 'content': + return CONTENT_PREFIX + default: + return DEFAULT_PREFIX + } + } + + // If no metadata or no noun field, use the default prefix + return DEFAULT_PREFIX + } catch (error) { + // If there's an error getting the metadata, use the default prefix + return DEFAULT_PREFIX + } + } + + /** + * Convert a Map to a plain object for serialization + */ + private mapToObject( + map: Map, + valueTransformer: (value: V) => any = (v) => v + ): Record { + const obj: Record = {} + for (const [key, value] of map.entries()) { + obj[key.toString()] = valueTransformer(value) + } + return obj + } +} diff --git a/src/types/augmentations.ts b/src/types/augmentations.ts new file mode 100644 index 00000000..660dadb2 --- /dev/null +++ b/src/types/augmentations.ts @@ -0,0 +1,413 @@ +/** Common types for the augmentation system */ + +/** + * Enum representing all types of augmentations available in the Brainy system. + */ +export enum AugmentationType { + SENSE = 'sense', + CONDUIT = 'conduit', + COGNITION = 'cognition', + MEMORY = 'memory', + PERCEPTION = 'perception', + DIALOG = 'dialog', + ACTIVATION = 'activation', + WEBSOCKET = 'webSocket' +} + +export type WebSocketConnection = { + connectionId: string + url: string + status: 'connected' | 'disconnected' | 'error' + send?: (data: string | ArrayBufferLike | Blob | ArrayBufferView) => Promise + close?: () => Promise + _streamMessageHandler?: (event: { data: unknown }) => void + _messageHandlerWrapper?: (data: unknown) => void +} + +type DataCallback = (data: T) => void +export type AugmentationResponse = { + success: boolean + data: T + error?: string +} + +/** + * Base interface for all Brainy augmentations. + * All augmentations must implement these core properties. + */ +export interface IAugmentation { + /** A unique identifier for the augmentation (e.g., "my-reasoner-v1") */ + readonly name: string + /** A human-readable description of the augmentation's purpose */ + readonly description: string + /** Whether this augmentation is enabled */ + enabled: boolean + + /** + * Initializes the augmentation. This method is called when Brainy starts up. + * @returns A Promise that resolves when initialization is complete + */ + initialize(): Promise + + shutDown(): Promise + + getStatus(): Promise<'active' | 'inactive' | 'error'> + + // Allow string indexing for dynamic method access + [key: string]: any; +} + +/** + * Interface for WebSocket support. + * Augmentations that implement this interface can communicate via WebSockets. + */ +export interface IWebSocketSupport extends IAugmentation { + /** + * Establishes a WebSocket connection. + * @param url The WebSocket server URL to connect to + * @param protocols Optional subprotocols + * @returns A Promise resolving to a connection handle or status + */ + connectWebSocket(url: string, protocols?: string | string[]): Promise + + /** + * Sends data through an established WebSocket connection. + * @param connectionId The identifier of the established connection + * @param data The data to send (will be serialized if not a string) + */ + sendWebSocketMessage(connectionId: string, data: unknown): Promise + + /** + * Registers a callback for incoming WebSocket messages. + * @param connectionId The identifier of the established connection + * @param callback The function to call when a message is received + */ + onWebSocketMessage(connectionId: string, callback: DataCallback): Promise + + /** + * 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): Promise + + /** + * Closes an established WebSocket connection. + * @param connectionId The identifier of the established connection + * @param code Optional close code + * @param reason Optional close reason + */ + closeWebSocket(connectionId: string, code?: number, reason?: string): Promise +} + +export namespace BrainyAugmentations { + /** + * Interface for Senses augmentations. + * These augmentations ingest and process raw, unstructured data into nouns and verbs. + */ + export interface ISenseAugmentation extends IAugmentation { + /** + * Processes raw input data into structured nouns and verbs. + * @param rawData The raw, unstructured data (e.g., text, image buffer, audio stream) + * @param dataType The type of raw data (e.g., 'text', 'image', 'audio') + */ + processRawData(rawData: Buffer | string, dataType: string): Promise> + + /** + * Registers a listener for real-time data feeds. + * @param feedUrl The URL or identifier of the real-time feed + * @param callback A function to call with processed data + */ + listenToFeed( + feedUrl: string, + callback: DataCallback<{ nouns: string[]; verbs: string[] }> + ): Promise + } + + /** + * Interface for Conduits augmentations. + * These augmentations establish and manage high-bandwidth, dedicated channels for structured, programmatic two-way data exchange. + */ + export interface IConduitAugmentation extends IAugmentation { + /** + * Establishes a connection for programmatic data exchange. + * @param targetSystemId The identifier of the external system to connect to + * @param config Configuration details for the connection (e.g., API keys, endpoints) + */ + establishConnection( + targetSystemId: string, + config: Record + ): Promise> + + /** + * Reads structured data directly from Brainy's knowledge graph. + * @param query A structured query (e.g., graph query language, object path) + * @param options Optional query options (e.g., depth, filters) + */ + readData( + query: Record, + options?: Record + ): Promise> + + /** + * Writes or updates structured data directly into Brainy's knowledge graph. + * @param data The structured data to write/update + * @param options Optional write options (e.g., merge, overwrite) + */ + writeData( + data: Record, + options?: Record + ): Promise> + + /** + * Monitors a specific data stream or event within Brainy for external systems. + * @param streamId The identifier of the data stream or event + * @param callback A function to call when new data/events occur + */ + monitorStream(streamId: string, callback: DataCallback): Promise + } + + /** + * Interface for Cognitions augmentations. + * These augmentations enable advanced reasoning, inference, and logical operations. + */ + export interface ICognitionAugmentation extends IAugmentation { + /** + * Performs a reasoning operation based on current knowledge. + * @param query The specific reasoning task or question + * @param context Optional additional context for the reasoning + */ + reason(query: string, context?: Record): AugmentationResponse<{ + inference: string + confidence: number + }> + + /** + * Infers relationships or new facts from existing data. + * @param dataSubset A subset of data to infer from + */ + infer(dataSubset: Record): AugmentationResponse> + + /** + * Executes a logical operation or rule set. + * @param ruleId The identifier of the rule or logic to apply + * @param input Data to apply the logic to + */ + executeLogic(ruleId: string, input: Record): AugmentationResponse + } + + /** + * Interface for Memory augmentations. + * These augmentations provide storage capabilities for data in different formats (e.g., fileSystem, in-memory). + */ + export interface IMemoryAugmentation extends IAugmentation { + /** + * Stores data in the memory system. + * @param key The unique identifier for the data + * @param data The data to store + * @param options Optional storage options (e.g., expiration, format) + */ + storeData( + key: string, + data: unknown, + options?: Record + ): Promise> + + /** + * Retrieves data from the memory system. + * @param key The unique identifier for the data + * @param options Optional retrieval options (e.g., format, version) + */ + retrieveData( + key: string, + options?: Record + ): Promise> + + /** + * Updates existing data in the memory system. + * @param key The unique identifier for the data + * @param data The updated data + * @param options Optional update options (e.g., merge, overwrite) + */ + updateData( + key: string, + data: unknown, + options?: Record + ): Promise> + + /** + * Deletes data from the memory system. + * @param key The unique identifier for the data + * @param options Optional deletion options + */ + deleteData( + key: string, + options?: Record + ): Promise> + + /** + * Lists available data keys in the memory system. + * @param pattern Optional pattern to filter keys (e.g., prefix, regex) + * @param options Optional listing options (e.g., limit, offset) + */ + listDataKeys( + pattern?: string, + options?: Record + ): Promise> + + /** + * Searches for data in the memory system using vector similarity. + * @param query The query vector or data to search for + * @param k Number of results to return + * @param options Optional search options + */ + search( + query: unknown, + k?: number, + options?: Record + ): Promise>> + } + + /** + * Interface for Perceptions augmentations. + * These augmentations interpret, contextualize, and visualize identified nouns and verbs. + */ + export interface IPerceptionAugmentation extends IAugmentation { + /** + * Interprets and contextualizes processed nouns and verbs. + * @param nouns The list of identified nouns + * @param verbs The list of identified verbs + * @param context Optional additional context for interpretation + */ + interpret( + nouns: string[], + verbs: string[], + context?: Record + ): AugmentationResponse> + + /** + * Organizes and filters information. + * @param data The data to organize (e.g., interpreted perceptions) + * @param criteria Optional criteria for filtering/prioritization + */ + organize( + data: Record, + criteria?: Record + ): AugmentationResponse> + + /** + * Generates a visualization based on the provided data. + * @param data The data to visualize (e.g., interpreted patterns) + * @param visualizationType The desired type of visualization (e.g., 'graph', 'chart') + */ + generateVisualization( + data: Record, + visualizationType: string + ): AugmentationResponse> + } + + /** + * Interface for Dialogs augmentations. + * These augmentations facilitate natural language understanding and generation for conversational interaction. + */ + export interface IDialogAugmentation extends IAugmentation { + /** + * Processes a user's natural language input (query). + * @param naturalLanguageQuery The raw text query from the user + * @param sessionId An optional session ID for conversational context + */ + processUserInput(naturalLanguageQuery: string, sessionId?: string): AugmentationResponse<{ + intent: string + nouns: string[] + verbs: string[] + context: Record + }> + + /** + * Generates a natural language response based on Brainy's knowledge and interpreted input. + * @param interpretedInput The output from `processUserInput` or similar + * @param knowledgeContext Relevant knowledge retrieved from Brainy + * @param sessionId An optional session ID for conversational context + */ + generateResponse( + interpretedInput: Record, + knowledgeContext: Record, + sessionId?: string + ): AugmentationResponse + + /** + * Manages and updates conversational context. + * @param sessionId The session ID + * @param contextUpdate The data to update the context with + */ + manageContext(sessionId: string, contextUpdate: Record): Promise + } + + /** + * Interface for Activations augmentations. + * These augmentations dictate how Brainy initiates actions, responses, or data manipulations. + */ + export interface IActivationAugmentation extends IAugmentation { + /** + * Triggers an action based on a processed command or internal state. + * @param actionName The name of the action to trigger + * @param parameters Optional parameters for the action + */ + triggerAction( + actionName: string, + parameters?: Record + ): AugmentationResponse + + /** + * Generates an expressive output or response from Brainy. + * @param knowledgeId The identifier of the knowledge to express + * @param format The desired output format (e.g., 'text', 'json') + */ + generateOutput(knowledgeId: string, format: string): AugmentationResponse> + + /** + * Interacts with an external system or API. + * @param systemId The identifier of the external system + * @param payload The data to send to the external system + */ + interactExternal(systemId: string, payload: Record): AugmentationResponse + } +} + +/** Direct exports of augmentation interfaces for easier imports */ +export interface ISenseAugmentation extends BrainyAugmentations.ISenseAugmentation { +} + +export interface IConduitAugmentation extends BrainyAugmentations.IConduitAugmentation { +} + +export interface ICognitionAugmentation extends BrainyAugmentations.ICognitionAugmentation { +} + +export interface IMemoryAugmentation extends BrainyAugmentations.IMemoryAugmentation { +} + +export interface IPerceptionAugmentation extends BrainyAugmentations.IPerceptionAugmentation { +} + +export interface IDialogAugmentation extends BrainyAugmentations.IDialogAugmentation { +} + +export interface IActivationAugmentation extends BrainyAugmentations.IActivationAugmentation { +} + +/** WebSocket-enabled augmentation interfaces */ +export type IWebSocketSenseAugmentation = BrainyAugmentations.ISenseAugmentation & IWebSocketSupport +export type IWebSocketConduitAugmentation = BrainyAugmentations.IConduitAugmentation & IWebSocketSupport +export type IWebSocketCognitionAugmentation = BrainyAugmentations.ICognitionAugmentation & IWebSocketSupport +export type IWebSocketMemoryAugmentation = BrainyAugmentations.IMemoryAugmentation & IWebSocketSupport +export type IWebSocketPerceptionAugmentation = BrainyAugmentations.IPerceptionAugmentation & IWebSocketSupport +export type IWebSocketDialogAugmentation = BrainyAugmentations.IDialogAugmentation & IWebSocketSupport +export type IWebSocketActivationAugmentation = BrainyAugmentations.IActivationAugmentation & IWebSocketSupport diff --git a/src/types/brainyDataInterface.ts b/src/types/brainyDataInterface.ts new file mode 100644 index 00000000..414932d5 --- /dev/null +++ b/src/types/brainyDataInterface.ts @@ -0,0 +1,55 @@ +/** + * BrainyDataInterface + * + * This interface defines the methods from BrainyData that are used by serverSearchAugmentations.ts. + * It's used to break the circular dependency between brainyData.ts and serverSearchAugmentations.ts. + */ + +import { Vector } from '../coreTypes.js' + +export interface BrainyDataInterface { + /** + * Initialize the database + */ + init(): Promise + + /** + * Get a noun by ID + * @param id The ID of the noun to get + */ + get(id: string): Promise + + /** + * Add a vector or data to the database + * @param vectorOrData Vector or data to add + * @param metadata Optional metadata to associate with the vector + * @returns The ID of the added vector + */ + add(vectorOrData: Vector | unknown, metadata?: T): Promise + + /** + * Search for text in the database + * @param text The text to search for + * @param limit Maximum number of results to return + * @returns Search results + */ + searchText(text: string, limit?: number): Promise + + /** + * Create a relationship between two entities + * @param sourceId The ID of the source entity + * @param targetId The ID of the target entity + * @param relationType The type of relationship + * @param metadata Optional metadata about the relationship + * @returns The ID of the created relationship + */ + relate(sourceId: string, targetId: string, relationType: string, metadata?: unknown): Promise + + /** + * Find entities similar to a given entity ID + * @param id ID of the entity to find similar entities for + * @param options Additional options + * @returns Array of search results with similarity scores + */ + findSimilar(id: string, options?: { limit?: number }): Promise +} diff --git a/src/types/fileSystemTypes.ts b/src/types/fileSystemTypes.ts new file mode 100644 index 00000000..3b488d83 --- /dev/null +++ b/src/types/fileSystemTypes.ts @@ -0,0 +1,14 @@ +/** + * Type declarations for the File System Access API + * Extends the FileSystemDirectoryHandle interface to include the [Symbol.asyncIterator] method + */ + +// Extend the FileSystemDirectoryHandle interface +interface FileSystemDirectoryHandle { + [Symbol.asyncIterator](): AsyncIterableIterator<[string, FileSystemHandle]>; + keys(): AsyncIterableIterator; + entries(): AsyncIterableIterator<[string, FileSystemHandle]>; +} + +// Export something to make this a module +export const fileSystemTypesLoaded = true; diff --git a/src/types/graphTypes.ts b/src/types/graphTypes.ts new file mode 100644 index 00000000..7e1d7b79 --- /dev/null +++ b/src/types/graphTypes.ts @@ -0,0 +1,150 @@ +// Common metadata types +/** + * Represents a high-precision timestamp with seconds and nanoseconds + * Used for tracking creation and update times of graph elements + */ +interface Timestamp { + seconds: number + nanoseconds: number +} + +/** + * Metadata about the creator/source of a graph noun + * Tracks which augmentation and model created the element + */ +interface CreatorMetadata { + augmentation: string // Name of the augmentation that created this element + version: string // Version of the augmentation +} + +/** + * Base interface for nodes (nouns) in the graph + * Represents entities like people, places, things, etc. + */ +export interface GraphNoun { + id: string // Unique identifier for the noun + createdBy: CreatorMetadata // Information about what created this noun + noun: NounType // Type classification of the noun + createdAt: Timestamp // When the noun was created + updatedAt: Timestamp // When the noun was last updated + label?: string // Optional descriptive label + data?: Record // Additional flexible data storage + embeddedVerbs?: EmbeddedGraphVerb[] // Optional embedded relationships + embedding?: number[] // Vector representation of the noun +} + +/** + * Base interface for edges (verbs) in the graph + * Represents relationships between nouns + */ +export interface GraphVerb { + id: string // Unique identifier for the verb + source: string // ID of the source noun + target: string // ID of the target noun + label?: string // Optional descriptive label + verb: VerbType // Type of relationship + createdAt: Timestamp // When the verb was created + updatedAt: Timestamp // When the verb was last updated + data?: Record // Additional flexible data storage + embedding?: number[] // Vector representation of the relationship + confidence?: number // Confidence score (0-1) + weight?: number // Strength/importance of the relationship +} + +/** + * Version of GraphVerb for embedded relationships + * Used when the source is implicit from the parent document + */ +export type EmbeddedGraphVerb = Omit + +// Proper Noun interfaces - extend GraphNoun with specific noun types + +/** + * Represents a person entity in the graph + */ +export interface Person extends GraphNoun { + noun: typeof NounType.Person +} + +/** + * Represents a physical location in the graph + */ +export interface Place extends GraphNoun { + noun: typeof NounType.Place +} + +/** + * Represents a physical or virtual object in the graph + */ +export interface Thing extends GraphNoun { + noun: typeof NounType.Thing +} + +/** + * Represents an event or occurrence in the graph + */ +export interface Event extends GraphNoun { + noun: typeof NounType.Event +} + +/** + * Represents an abstract concept or idea in the graph + */ +export interface Concept extends GraphNoun { + noun: typeof NounType.Concept +} + +export interface Group extends GraphNoun { + noun: typeof NounType.Group +} + +export interface List extends GraphNoun { + noun: typeof NounType.List +} + +/** + * Represents content (text, media, etc.) in the graph + */ +export interface Content extends GraphNoun { + noun: typeof NounType.Content +} + +/** + * Defines valid noun types for graph entities + * Used for categorizing different types of nodes + */ + +export const NounType = { + Person: 'person', // Person entities + Place: 'place', // Physical locations + Thing: 'thing', // Physical or virtual objects + Event: 'event', // Events or occurrences + Concept: 'concept', // Abstract concepts or ideas + Content: 'content', // Content items + Group: 'group', // Groups of related entities + List: 'list', // Ordered collections of entities + Category: 'category' // Categories for content items including tags +} as const +export type NounType = (typeof NounType)[keyof typeof NounType] + +/** + * Defines valid verb types for relationships + * Used for categorizing different types of connections + */ +export const VerbType = { + AttributedTo: 'attributedTo', // Indicates attribution or authorship + Controls: 'controls', // Indicates control or ownership + Created: 'created', // Indicates creation or authorship + Earned: 'earned', // Indicates achievement or acquisition + Owns: 'owns', // Indicates ownership + MemberOf: 'memberOf', // Indicates membership or affiliation + RelatedTo: 'relatedTo', // Indicates family relationship + WorksWith: 'worksWith', // Indicates professional relationship + FriendOf: 'friendOf', // Indicates friendship + ReportsTo: 'reportsTo', // Indicates reporting relationship + Supervises: 'supervises', // Indicates supervisory relationship + Mentors: 'mentors', // Indicates mentorship relationship + Follows: 'follows', // Indicates the following relationship + Likes: 'likes' // Indicates liking relationship +} as const +export type VerbType = (typeof VerbType)[keyof typeof VerbType] diff --git a/src/types/mcpTypes.ts b/src/types/mcpTypes.ts new file mode 100644 index 00000000..965dcc50 --- /dev/null +++ b/src/types/mcpTypes.ts @@ -0,0 +1,149 @@ +/** + * Model Control Protocol (MCP) Types + * + * This file defines the types and interfaces for the Model Control Protocol (MCP) + * implementation in Brainy. MCP allows external models to access Brainy data and + * use the augmentation pipeline as tools. + */ + +/** + * MCP version information + */ +export const MCP_VERSION = '1.0.0' + +/** + * MCP request types + */ +export enum MCPRequestType { + DATA_ACCESS = 'data_access', + TOOL_EXECUTION = 'tool_execution', + SYSTEM_INFO = 'system_info', + AUTHENTICATION = 'authentication' +} + +/** + * Base interface for all MCP requests + */ +export interface MCPRequest { + /** The type of request */ + type: MCPRequestType + /** Request ID for tracking and correlation */ + requestId: string + /** API version */ + version: string + /** Authentication token (if required) */ + authToken?: string +} + +/** + * Interface for data access requests + */ +export interface MCPDataAccessRequest extends MCPRequest { + type: MCPRequestType.DATA_ACCESS + /** The data access operation to perform */ + operation: 'get' | 'search' | 'add' | 'getRelationships' + /** Parameters for the operation */ + parameters: Record +} + +/** + * Interface for tool execution requests + */ +export interface MCPToolExecutionRequest extends MCPRequest { + type: MCPRequestType.TOOL_EXECUTION + /** The name of the tool to execute */ + toolName: string + /** Parameters for the tool */ + parameters: Record +} + +/** + * Interface for system info requests + */ +export interface MCPSystemInfoRequest extends MCPRequest { + type: MCPRequestType.SYSTEM_INFO + /** The type of information to retrieve */ + infoType: 'status' | 'availableTools' | 'version' +} + +/** + * Interface for authentication requests + */ +export interface MCPAuthenticationRequest extends MCPRequest { + type: MCPRequestType.AUTHENTICATION + /** The authentication credentials */ + credentials: { + apiKey?: string + username?: string + password?: string + } +} + +/** + * Base interface for all MCP responses + */ +export interface MCPResponse { + /** Whether the request was successful */ + success: boolean + /** The request ID from the original request */ + requestId: string + /** API version */ + version: string + /** Response data (if successful) */ + data?: any + /** Error information (if unsuccessful) */ + error?: { + code: string + message: string + details?: any + } +} + +/** + * Interface for MCP tool definitions + */ +export interface MCPTool { + /** The name of the tool */ + name: string + /** A description of what the tool does */ + description: string + /** The parameters the tool accepts */ + parameters: { + type: 'object' + properties: Record + required: string[] + } +} + +/** + * Configuration options for MCP services + */ +export interface MCPServiceOptions { + /** Port for the WebSocket server */ + wsPort?: number + /** Port for the REST server */ + restPort?: number + /** Whether to enable authentication */ + enableAuth?: boolean + /** API keys for authentication */ + apiKeys?: string[] + /** Rate limiting configuration */ + rateLimit?: { + /** Maximum number of requests per window */ + maxRequests: number + /** Time window in milliseconds */ + windowMs: number + } + /** CORS configuration for REST API */ + cors?: { + /** Allowed origins */ + origin: string | string[] + /** Whether to allow credentials */ + credentials: boolean + } +} diff --git a/src/types/pipelineTypes.ts b/src/types/pipelineTypes.ts new file mode 100644 index 00000000..9c1755b2 --- /dev/null +++ b/src/types/pipelineTypes.ts @@ -0,0 +1,33 @@ +/** + * Pipeline Types + * + * This module provides shared types for the pipeline system to avoid circular dependencies. + */ + +import { + BrainyAugmentations, + IWebSocketSupport, + IAugmentation +} from './augmentations.js' + +/** + * Type definitions for the augmentation registry + */ +export type AugmentationRegistry = { + sense: BrainyAugmentations.ISenseAugmentation[]; + conduit: BrainyAugmentations.IConduitAugmentation[]; + cognition: BrainyAugmentations.ICognitionAugmentation[]; + memory: BrainyAugmentations.IMemoryAugmentation[]; + perception: BrainyAugmentations.IPerceptionAugmentation[]; + dialog: BrainyAugmentations.IDialogAugmentation[]; + activation: BrainyAugmentations.IActivationAugmentation[]; + webSocket: IWebSocketSupport[]; +} + +/** + * Interface for the Pipeline class + * This is used to break circular dependencies between pipeline.ts and augmentationRegistry.ts + */ +export interface IPipeline { + register(augmentation: T): IPipeline; +} diff --git a/src/types/tensorflowTypes.ts b/src/types/tensorflowTypes.ts new file mode 100644 index 00000000..94a43484 --- /dev/null +++ b/src/types/tensorflowTypes.ts @@ -0,0 +1,14 @@ +// Type declarations for TensorFlow.js models + +// 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; +} + +// Export a dummy constant to make this a proper module +export const tensorflowModelsLoaded = true; diff --git a/src/unified.ts b/src/unified.ts new file mode 100644 index 00000000..a2dbd38f --- /dev/null +++ b/src/unified.ts @@ -0,0 +1,36 @@ +/** + * Unified entry point for Brainy + * This file exports everything from index.ts + * Environment detection is handled here and made available to all components + */ + +// Export environment information +export const environment = { + isBrowser: typeof window !== 'undefined', + isNode: + typeof process !== 'undefined' && process.versions && process.versions.node, + isServerless: + typeof window === 'undefined' && + (typeof process === 'undefined' || + !process.versions || + !process.versions.node) +} + +// Make environment information available globally +if (typeof globalThis !== 'undefined') { + globalThis.__ENV__ = environment +} + +// Log the detected environment +console.log( + `Brainy running in ${ + environment.isBrowser + ? 'browser' + : environment.isNode + ? 'Node.js' + : 'serverless/unknown' + } environment` +) + +// Re-export everything from index.ts +export * from './index.js' diff --git a/src/utils/distance.ts b/src/utils/distance.ts new file mode 100644 index 00000000..a7c98560 --- /dev/null +++ b/src/utils/distance.ts @@ -0,0 +1,86 @@ +/** + * Distance functions for vector similarity calculations + * Optimized for Node.js 23.11+ using enhanced array methods + */ + +import { DistanceFunction, Vector } from '../coreTypes.js' + +/** + * Calculates the Euclidean distance between two vectors + * Lower values indicate higher similarity + * Optimized using array methods for Node.js 23.11+ + */ +export const euclideanDistance: DistanceFunction = (a: Vector, b: Vector): number => { + if (a.length !== b.length) { + throw new Error('Vectors must have the same dimensions') + } + + // Use array.reduce for better performance in Node.js 23.11+ + const sum = a.reduce((acc, val, i) => { + const diff = val - b[i] + return acc + (diff * diff) + }, 0) + + return Math.sqrt(sum) +} + +/** + * Calculates the cosine distance between two vectors + * Lower values indicate higher similarity + * Range: 0 (identical) to 2 (opposite) + * Optimized using array methods for Node.js 23.11+ + */ +export const cosineDistance: DistanceFunction = (a: Vector, b: Vector): number => { + if (a.length !== b.length) { + throw new Error('Vectors must have the same dimensions') + } + + // Use array.reduce to calculate all values in a single pass + const { dotProduct, normA, normB } = a.reduce((acc, val, i) => { + return { + dotProduct: acc.dotProduct + (val * b[i]), + normA: acc.normA + (val * val), + normB: acc.normB + (b[i] * b[i]) + }; + }, { dotProduct: 0, normA: 0, normB: 0 }); + + if (normA === 0 || normB === 0) { + return 2 // Maximum distance for zero vectors + } + + const similarity = dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)) + // Convert cosine similarity (-1 to 1) to distance (0 to 2) + return 1 - similarity +} + +/** + * Calculates the Manhattan (L1) distance between two vectors + * Lower values indicate higher similarity + * Optimized using array methods for Node.js 23.11+ + */ +export const manhattanDistance: DistanceFunction = (a: Vector, b: Vector): number => { + if (a.length !== b.length) { + throw new Error('Vectors must have the same dimensions') + } + + // Use array.reduce for better performance in Node.js 23.11+ + return a.reduce((sum, val, i) => sum + Math.abs(val - b[i]), 0) +} + +/** + * Calculates the dot product similarity between two vectors + * Higher values indicate higher similarity + * 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 => { + if (a.length !== b.length) { + throw new Error('Vectors must have the same dimensions') + } + + // Use array.reduce for better performance in Node.js 23.11+ + const dotProduct = a.reduce((sum, val, i) => sum + (val * b[i]), 0) + + // Convert to a distance metric (lower is better) + return -dotProduct +} diff --git a/src/utils/embedding.ts b/src/utils/embedding.ts new file mode 100644 index 00000000..6e15e204 --- /dev/null +++ b/src/utils/embedding.ts @@ -0,0 +1,284 @@ +/** + * Embedding functions for converting data to vectors + */ + +import { EmbeddingFunction, EmbeddingModel, Vector } from '../coreTypes.js' +import { executeInThread } from './workerUtils.js' + +/** + * TensorFlow Universal Sentence Encoder embedding model + * This model provides high-quality text embeddings using TensorFlow.js + * The required TensorFlow.js dependencies are automatically installed with this package + */ +export class UniversalSentenceEncoder implements EmbeddingModel { + private model: any = null + private initialized = false + private tf: any = null + private use: any = null + + /** + * Initialize the embedding model + */ + public async init(): Promise { + try { + // Save original console.warn + const originalWarn = console.warn + + // Override console.warn to suppress TensorFlow.js Node.js backend message + console.warn = function (message?: any, ...optionalParams: any[]) { + if ( + message && + typeof message === 'string' && + message.includes( + 'Hi, looks like you are running TensorFlow.js in Node.js' + ) + ) { + return // Suppress the specific warning + } + originalWarn(message, ...optionalParams) + } + + // Define EPSILON flag before TensorFlow.js is loaded + // This prevents the "Cannot evaluate flag 'EPSILON': no evaluation function found" error + if (typeof window !== 'undefined') { + ;(window as any).EPSILON = 1e-7 + // Define the flag with an evaluation function for TensorFlow.js + ;(window as any).ENV = (window as any).ENV || {} + ;(window as any).ENV.flagRegistry = + (window as any).ENV.flagRegistry || {} + ;(window as any).ENV.flagRegistry.EPSILON = { + evaluationFn: () => 1e-7 + } + } else if (typeof global !== 'undefined') { + ;(global as any).EPSILON = 1e-7 + // Define the flag with an evaluation function for TensorFlow.js + ;(global as any).ENV = (global as any).ENV || {} + ;(global as any).ENV.flagRegistry = + (global as any).ENV.flagRegistry || {} + ;(global as any).ENV.flagRegistry.EPSILON = { + evaluationFn: () => 1e-7 + } + } + + // Dynamically import TensorFlow.js and Universal Sentence Encoder + // Use type assertions to tell TypeScript these modules exist + this.tf = await import('@tensorflow/tfjs') + this.use = await import('@tensorflow-models/universal-sentence-encoder') + + // Load the model + this.model = await this.use.load() + this.initialized = true + + // Restore original console.warn + console.warn = originalWarn + } catch (error) { + console.error('Failed to initialize Universal Sentence Encoder:', error) + throw new Error( + `Failed to initialize Universal Sentence Encoder: ${error}` + ) + } + } + + /** + * Embed text into a vector using Universal Sentence Encoder + * @param data Text to embed + */ + public async embed(data: string | string[]): Promise { + if (!this.initialized) { + await this.init() + } + + try { + // Handle different input types + let textToEmbed: string[] + if (typeof data === 'string') { + // Handle empty string case + if (data.trim() === '') { + // Return a zero vector of appropriate dimension (512 is the default for USE) + return new Array(512).fill(0) + } + textToEmbed = [data] + } else if ( + Array.isArray(data) && + data.every((item) => typeof item === 'string') + ) { + // Handle empty array or array with empty strings + if (data.length === 0 || data.every((item) => item.trim() === '')) { + return new Array(512).fill(0) + } + // Filter out empty strings + textToEmbed = data.filter((item) => item.trim() !== '') + if (textToEmbed.length === 0) { + return new Array(512).fill(0) + } + } else { + throw new Error( + 'UniversalSentenceEncoder only supports string or string[] data' + ) + } + + // Get embeddings + const embeddings = await this.model.embed(textToEmbed) + + // Convert to array and return the first embedding + const embeddingArray = await embeddings.array() + return embeddingArray[0] + } catch (error) { + console.error( + 'Failed to embed text with Universal Sentence Encoder:', + error + ) + throw new Error( + `Failed to embed text with Universal Sentence Encoder: ${error}` + ) + } + } + + /** + * Dispose of the model resources + */ + public async dispose(): Promise { + if (this.model && this.tf) { + try { + // Dispose of the model and tensors + this.model.dispose() + this.tf.disposeVariables() + this.initialized = false + } catch (error) { + console.error('Failed to dispose Universal Sentence Encoder:', error) + } + } + return Promise.resolve() + } +} + +/** + * Create an embedding function from an embedding model + * @param model Embedding model to use + */ +export function createEmbeddingFunction( + model: EmbeddingModel +): EmbeddingFunction { + return async (data: any): Promise => { + return await model.embed(data) + } +} + +/** + * Creates a TensorFlow-based Universal Sentence Encoder embedding function + * This is the required embedding function for all text embeddings + */ +export function createTensorFlowEmbeddingFunction(): EmbeddingFunction { + // Create a single shared instance of the model + const model = new UniversalSentenceEncoder() + let modelInitialized = false + + return async (data: any): Promise => { + try { + // Initialize the model if it hasn't been initialized yet + if (!modelInitialized) { + await model.init() + modelInitialized = true + } + + return await model.embed(data) + } catch (error) { + console.error('Failed to use TensorFlow embedding:', error) + throw new Error( + `Universal Sentence Encoder is required but failed: ${error}` + ) + } + } +} + +/** + * Creates a TensorFlow-based Universal Sentence Encoder embedding function that runs in a separate thread + * This provides better performance for CPU-intensive embedding operations + * @param options Configuration options + * @returns An embedding function that runs in a separate thread + */ +export function createThreadedEmbeddingFunction( + options: { fallbackToMain?: boolean } = {} +): EmbeddingFunction { + // Create a standard embedding function to use as fallback + const standardEmbedding = createTensorFlowEmbeddingFunction() + + // Flag to track if we've fallen back to main thread + let useFallback = false + + return async (data: any): Promise => { + // If we've already determined that threading doesn't work, use the fallback + if (useFallback) { + return standardEmbedding(data) + } + + try { + // Function to be executed in a worker thread + const embedInWorker = async (inputData: any) => { + // We need to dynamically import TensorFlow.js and USE in the worker + const tf = await import('@tensorflow/tfjs') + const use = await import('@tensorflow-models/universal-sentence-encoder') + + // Load the model + const model = await use.load() + + // Handle different input types + let textToEmbed: string[] + if (typeof inputData === 'string') { + if (inputData.trim() === '') { + return new Array(512).fill(0) + } + textToEmbed = [inputData] + } else if ( + Array.isArray(inputData) && + inputData.every((item) => typeof item === 'string') + ) { + if (inputData.length === 0 || inputData.every((item) => item.trim() === '')) { + return new Array(512).fill(0) + } + textToEmbed = inputData.filter((item) => item.trim() !== '') + if (textToEmbed.length === 0) { + return new Array(512).fill(0) + } + } else { + throw new Error( + 'UniversalSentenceEncoder only supports string or string[] data' + ) + } + + // Get embeddings + const embeddings = await model.embed(textToEmbed) + + // Convert to array and return the first embedding + const embeddingArray = await embeddings.array() + + // Dispose of the tensor to free memory + embeddings.dispose() + + return embeddingArray[0] + } + + // Execute the embedding function in a separate thread + return await executeInThread(embedInWorker, data) + } catch (error) { + // If threading fails and fallback is enabled, use the standard embedding function + if (options.fallbackToMain) { + console.warn('Threaded embedding failed, falling back to main thread:', error) + useFallback = true + return standardEmbedding(data) + } + + // Otherwise, propagate the error + throw new Error(`Threaded embedding failed: ${error}`) + } + } +} + +/** + * Default embedding function + * Uses UniversalSentenceEncoder for all text embeddings + * TensorFlow.js is required for this to work + * Uses threading when available for better performance + */ +export const defaultEmbeddingFunction: EmbeddingFunction = + createThreadedEmbeddingFunction({ fallbackToMain: true }) diff --git a/src/utils/environment.ts b/src/utils/environment.ts new file mode 100644 index 00000000..477b07ca --- /dev/null +++ b/src/utils/environment.ts @@ -0,0 +1,57 @@ +/** + * Utility functions for environment detection + */ + +/** + * Check if code is running in a browser environment + */ +export function isBrowser(): boolean { + return typeof window !== 'undefined' && typeof document !== 'undefined'; +} + +/** + * Check if code is running in a Node.js environment + */ +export function isNode(): boolean { + return typeof process !== 'undefined' && + process.versions != null && + process.versions.node != null; +} + +/** + * Check if code is running in a Web Worker environment + */ +export function isWebWorker(): boolean { + return typeof self === 'object' && + self.constructor && + self.constructor.name === 'DedicatedWorkerGlobalScope'; +} + +/** + * Check if Web Workers are available in the current environment + */ +export function areWebWorkersAvailable(): boolean { + return isBrowser() && typeof Worker !== 'undefined'; +} + +/** + * Check if Worker Threads are available in the current environment (Node.js) + */ +export function areWorkerThreadsAvailable(): boolean { + if (!isNode()) return false; + + try { + // Dynamic import to avoid errors in browser environments + require('worker_threads'); + return true; + } catch (e) { + return false; + } +} + +/** + * Determine if threading is available in the current environment + */ +export function isThreadingAvailable(): boolean { + return areWebWorkersAvailable() || areWorkerThreadsAvailable(); +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 00000000..11db368e --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,2 @@ +export * from './distance.js' +export * from './embedding.js' diff --git a/src/utils/version.ts b/src/utils/version.ts new file mode 100644 index 00000000..3a7c9950 --- /dev/null +++ b/src/utils/version.ts @@ -0,0 +1,6 @@ +/** + * This file is auto-generated during the build process. + * Do not modify this file directly. + */ + +export const VERSION = '0.9.4'; diff --git a/src/utils/workerUtils.ts b/src/utils/workerUtils.ts new file mode 100644 index 00000000..8e500ee3 --- /dev/null +++ b/src/utils/workerUtils.ts @@ -0,0 +1,137 @@ +/** + * Utility functions for working with Web Workers and Worker Threads + */ + +import { isNode, isBrowser } from './environment.js'; + +/** + * Execute a function in a Web Worker (browser environment) + * + * @param fn The function to execute + * @param args The arguments to pass to the function + * @returns A promise that resolves with the result of the function + */ +export function executeInWebWorker(fn: Function, ...args: any[]): Promise { + return new Promise((resolve, reject) => { + // Create a blob URL for the worker script + const fnString = fn.toString(); + const workerScript = ` + self.onmessage = function(e) { + try { + const fn = ${fnString}; + const result = fn(...e.data); + self.postMessage({ success: true, data: result }); + } catch (error) { + self.postMessage({ + success: false, + error: error instanceof Error ? error.message : String(error) + }); + } + }; + `; + + const blob = new Blob([workerScript], { type: 'application/javascript' }); + const blobURL = URL.createObjectURL(blob); + + // Create a worker + const worker = new Worker(blobURL); + + // Set up message handling + worker.onmessage = function(e: MessageEvent<{ success: boolean; data: T; error?: string }>) { + URL.revokeObjectURL(blobURL); // Clean up + worker.terminate(); // Terminate the worker + + if (e.data.success) { + resolve(e.data.data); + } else { + reject(new Error(e.data.error)); + } + }; + + worker.onerror = function(error: ErrorEvent) { + URL.revokeObjectURL(blobURL); // Clean up + worker.terminate(); // Terminate the worker + reject(error); + }; + + // Start the worker + worker.postMessage(args); + }); +} + +/** + * Execute a function in a Worker Thread (Node.js environment) + * + * @param fn The function to execute + * @param args The arguments to pass to the function + * @returns A promise that resolves with the result of the function + */ +export function executeInWorkerThread(fn: Function, ...args: any[]): Promise { + return new Promise((resolve, reject) => { + try { + // Dynamic import to avoid errors in browser environments + const { Worker } = require('worker_threads'); + + // Create a worker script + const fnString = fn.toString(); + const workerScript = ` + const { parentPort } = require('worker_threads'); + + parentPort.once('message', (data) => { + try { + const fn = ${fnString}; + const result = fn(...data); + parentPort.postMessage({ success: true, data: result }); + } catch (error) { + parentPort.postMessage({ + success: false, + error: error instanceof Error ? error.message : String(error) + }); + } + }); + `; + + // Create a worker + const worker = new Worker(workerScript, { eval: true }); + + // Set up message handling + worker.on('message', (data: { success: boolean; data: T; error?: string }) => { + worker.terminate(); // Terminate the worker + + if (data.success) { + resolve(data.data); + } else { + reject(new Error(data.error)); + } + }); + + worker.on('error', (error: Error) => { + worker.terminate(); // Terminate the worker + reject(error); + }); + + // Start the worker + worker.postMessage(args); + } catch (error) { + reject(error); + } + }); +} + +/** + * Execute a function in a separate thread based on the environment + * + * @param fn The function to execute + * @param args The arguments to pass to the function + * @returns A promise that resolves with the result of the function + */ +export function executeInThread(fn: Function, ...args: any[]): Promise { + if (isBrowser()) { + return executeInWebWorker(fn, ...args); + } else if (isNode()) { + return executeInWorkerThread(fn, ...args); + } else { + // Fall back to executing in the main thread + return Promise.resolve(fn(...args) as T); + } +} diff --git a/tsconfig.browser.json b/tsconfig.browser.json new file mode 100644 index 00000000..b8deca5b --- /dev/null +++ b/tsconfig.browser.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "Bundler", + "esModuleInterop": true, + "strict": true, + "declaration": false, + "outDir": "./dist", + "rootDir": ".", + "lib": [ + "DOM", + "ESNext" + ], + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "noEmit": false, + "preserveConstEnums": true, + "sourceMap": true + }, + "include": [ + "src/**/*", + "demo/**/*" + ], + "exclude": [ + "node_modules", + "dist", + "**/*.test.ts" + ] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..a72fcfc0 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "esModuleInterop": true, + "strict": true, + "declaration": true, + "outDir": "./dist", + "rootDir": "./src", + "lib": [ + "DOM", + "ESNext" + ], + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "noEmit": false, + "preserveConstEnums": true, + "sourceMap": true + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist", + "**/*.test.ts" + ] +} diff --git a/tsconfig.unified.json b/tsconfig.unified.json new file mode 100644 index 00000000..a72fcfc0 --- /dev/null +++ b/tsconfig.unified.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "esModuleInterop": true, + "strict": true, + "declaration": true, + "outDir": "./dist", + "rootDir": "./src", + "lib": [ + "DOM", + "ESNext" + ], + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "noEmit": false, + "preserveConstEnums": true, + "sourceMap": true + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist", + "**/*.test.ts" + ] +} diff --git a/verify-package-size.js b/verify-package-size.js new file mode 100755 index 00000000..400369ef --- /dev/null +++ b/verify-package-size.js @@ -0,0 +1,135 @@ +#!/usr/bin/env node + +import fs from 'fs' +import path from 'path' +import { execSync } from 'child_process' +import { fileURLToPath } from 'url' + +// Get the current directory +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) + +// Function to get the size of a file in MB +function getFileSizeInMB(filePath) { + const stats = fs.statSync(filePath) + return stats.size / (1024 * 1024) +} + +// Function to check if a file should be included in the package +function shouldIncludeFile(filePath, npmignorePatterns, includePatterns) { + const relativePath = path.relative('.', filePath) + + // Check if the file matches any npmignore pattern + for (const pattern of npmignorePatterns) { + if (pattern.test(relativePath)) { + return false + } + } + + // If we have explicit include patterns, check if the file matches any + if (includePatterns.length > 0) { + for (const pattern of includePatterns) { + if (pattern.test(relativePath)) { + return true + } + } + return false + } + + return true +} + +// Parse .npmignore file +function parseNpmignore() { + const patterns = [] + if (fs.existsSync('.npmignore')) { + const content = fs.readFileSync('.npmignore', 'utf8') + const lines = content.split('\n') + + for (const line of lines) { + const trimmedLine = line.trim() + if (trimmedLine && !trimmedLine.startsWith('#')) { + // Convert glob pattern to regex + let pattern = trimmedLine + .replace(/\./g, '\\.') + .replace(/\*/g, '.*') + .replace(/\?/g, '.') + + // Handle directory patterns + if (pattern.endsWith('/')) { + pattern = `${pattern}.*` + } + + patterns.push(new RegExp(`^${pattern}$`)) + } + } + } + return patterns +} + +// Parse package.json files array +function parsePackageFiles() { + const patterns = [] + const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8')) + + if (packageJson.files && Array.isArray(packageJson.files)) { + for (const pattern of packageJson.files) { + // Convert glob pattern to regex + let regexPattern = pattern + .replace(/\./g, '\\.') + .replace(/\*/g, '.*') + .replace(/\?/g, '.') + + // Handle directory patterns + if (regexPattern.endsWith('/')) { + regexPattern = `${regexPattern}.*` + } + + patterns.push(new RegExp(`^${regexPattern}$`)) + } + } + + return patterns +} + +// Calculate the total size of files that would be included in the package +function calculatePackageSize() { + const npmignorePatterns = parseNpmignore() + const includePatterns = parsePackageFiles() + + let totalSize = 0 + let includedFiles = [] + + function processDirectory(dirPath) { + const entries = fs.readdirSync(dirPath, { withFileTypes: true }) + + for (const entry of entries) { + const fullPath = path.join(dirPath, entry.name) + + if (entry.isDirectory()) { + processDirectory(fullPath) + } else if (entry.isFile()) { + if (shouldIncludeFile(fullPath, npmignorePatterns, includePatterns)) { + const sizeInMB = getFileSizeInMB(fullPath) + totalSize += sizeInMB + includedFiles.push({ path: fullPath, size: sizeInMB }) + } + } + } + } + + processDirectory('.') + + // Sort files by size (largest first) + includedFiles.sort((a, b) => b.size - a.size) + + console.log('Estimated package size: ' + totalSize.toFixed(2) + ' MB') + console.log('\nLargest files:') + for (let i = 0; i < Math.min(10, includedFiles.length); i++) { + console.log( + `${includedFiles[i].path}: ${includedFiles[i].size.toFixed(2)} MB` + ) + } +} + +calculatePackageSize()