From 4c6475f31ef088767ed9ca1474c8603b456a37c8 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Fri, 16 Jan 2026 11:47:06 +0000 Subject: [PATCH 01/51] use claude --- CLAUDE.md | 144 + server/package.json | 2 +- virtocommerce-adapter/.gitignore | 100 + virtocommerce-adapter/LICENSE | 22 + virtocommerce-adapter/README.md | 360 + virtocommerce-adapter/docs/API.md | 79 + virtocommerce-adapter/docs/CONFIGURATION.md | 55 + virtocommerce-adapter/docs/PUBLISHING.md | 580 ++ virtocommerce-adapter/eslint.config.js | 77 + .../examples/advanced-config.ts | 33 + virtocommerce-adapter/examples/basic-usage.ts | 209 + virtocommerce-adapter/jest.config.js | 45 + virtocommerce-adapter/package-lock.json | 6592 +++++++++++++++++ virtocommerce-adapter/package.json | 51 + virtocommerce-adapter/src/adapter.ts | 284 + virtocommerce-adapter/src/index.ts | 26 + .../src/mappers/filter.mappers.ts | 125 + virtocommerce-adapter/src/mappers/index.ts | 14 + .../src/models/customer-order.ts | 623 ++ virtocommerce-adapter/src/models/index.ts | 1 + .../src/services/base.service.ts | 35 + .../src/services/customer.service.ts | 48 + .../src/services/fulfillment.service.ts | 86 + virtocommerce-adapter/src/services/index.ts | 11 + .../src/services/order.service.ts | 139 + .../src/services/product.service.ts | 112 + .../src/services/return.service.ts | 77 + .../src/transformers/address.transformer.ts | 81 + .../src/transformers/base.ts | 58 + .../src/transformers/customer.transformer.ts | 69 + .../transformers/fulfillment.transformer.ts | 77 + .../src/transformers/index.ts | 11 + .../src/transformers/order.transformer.ts | 210 + .../src/transformers/product.transformer.ts | 93 + virtocommerce-adapter/src/types.ts | 165 + virtocommerce-adapter/src/utils/api-client.ts | 350 + .../src/utils/type-guards.ts | 37 + virtocommerce-adapter/test-integration.js | 97 + virtocommerce-adapter/tests/adapter.test.ts | 524 ++ virtocommerce-adapter/tests/setup.ts | 64 + virtocommerce-adapter/tsconfig.eslint.json | 13 + virtocommerce-adapter/tsconfig.json | 50 + 42 files changed, 11828 insertions(+), 1 deletion(-) create mode 100644 CLAUDE.md create mode 100644 virtocommerce-adapter/.gitignore create mode 100644 virtocommerce-adapter/LICENSE create mode 100644 virtocommerce-adapter/README.md create mode 100644 virtocommerce-adapter/docs/API.md create mode 100644 virtocommerce-adapter/docs/CONFIGURATION.md create mode 100644 virtocommerce-adapter/docs/PUBLISHING.md create mode 100644 virtocommerce-adapter/eslint.config.js create mode 100644 virtocommerce-adapter/examples/advanced-config.ts create mode 100644 virtocommerce-adapter/examples/basic-usage.ts create mode 100644 virtocommerce-adapter/jest.config.js create mode 100644 virtocommerce-adapter/package-lock.json create mode 100644 virtocommerce-adapter/package.json create mode 100644 virtocommerce-adapter/src/adapter.ts create mode 100644 virtocommerce-adapter/src/index.ts create mode 100644 virtocommerce-adapter/src/mappers/filter.mappers.ts create mode 100644 virtocommerce-adapter/src/mappers/index.ts create mode 100644 virtocommerce-adapter/src/models/customer-order.ts create mode 100644 virtocommerce-adapter/src/models/index.ts create mode 100644 virtocommerce-adapter/src/services/base.service.ts create mode 100644 virtocommerce-adapter/src/services/customer.service.ts create mode 100644 virtocommerce-adapter/src/services/fulfillment.service.ts create mode 100644 virtocommerce-adapter/src/services/index.ts create mode 100644 virtocommerce-adapter/src/services/order.service.ts create mode 100644 virtocommerce-adapter/src/services/product.service.ts create mode 100644 virtocommerce-adapter/src/services/return.service.ts create mode 100644 virtocommerce-adapter/src/transformers/address.transformer.ts create mode 100644 virtocommerce-adapter/src/transformers/base.ts create mode 100644 virtocommerce-adapter/src/transformers/customer.transformer.ts create mode 100644 virtocommerce-adapter/src/transformers/fulfillment.transformer.ts create mode 100644 virtocommerce-adapter/src/transformers/index.ts create mode 100644 virtocommerce-adapter/src/transformers/order.transformer.ts create mode 100644 virtocommerce-adapter/src/transformers/product.transformer.ts create mode 100644 virtocommerce-adapter/src/types.ts create mode 100644 virtocommerce-adapter/src/utils/api-client.ts create mode 100644 virtocommerce-adapter/src/utils/type-guards.ts create mode 100644 virtocommerce-adapter/test-integration.js create mode 100644 virtocommerce-adapter/tests/adapter.test.ts create mode 100644 virtocommerce-adapter/tests/setup.ts create mode 100644 virtocommerce-adapter/tsconfig.eslint.json create mode 100644 virtocommerce-adapter/tsconfig.json diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..19cd432 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,144 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Repository Overview + +This is the Commerce Operations Foundation (COF) MCP Reference Server - a monorepo implementing the Order Network eXchange (onX) standard for AI-powered fulfillment operations via the Model Context Protocol. + +## Repository Structure + +``` +/server - Core MCP server implementation (main development target) +/adapter-template - Boilerplate for creating custom fulfillment adapters +/schemas - JSON Schema definitions for domain models +/docs - Specification and architectural documentation +``` + +## Development Commands + +All primary development happens in `/server`. Run commands from that directory: + +```bash +cd server +npm install +npm run build # Compile TypeScript +npm run dev # Development with hot reload +npm test # Build + run all tests +npm run test:unit # Unit tests only +npm run test:integration +npx vitest tests/unit/file.test.ts # Run single test file +npm run lint # ESLint +npm run format # Prettier +``` + +For adapter development in `/adapter-template`: +```bash +cd adapter-template +npm install +npm run build +npm test +npm run dev # Watch mode compilation +``` + +## Architecture + +### Three-Layer Design + +1. **Protocol Layer** (`server/src/server.ts`, `server/src/index.ts`) + - MCP SDK integration with stdio transport + - Request routing (tools/list, tools/call) + +2. **Service Layer** (`server/src/services/`) + - `ServiceOrchestrator` - main facade for all operations + - `AdapterManager`, `HealthMonitor`, `ErrorHandler`, `Transformer`, `Validator` + +3. **Adapter Layer** (`server/src/adapters/`) + - `IFulfillmentAdapter` interface that all adapters implement + - Pluggable: supports built-in, NPM packages, and local file adapters + - `AdapterFactory` handles creation and caching + +### Tool System + +Tools in `server/src/tools/` extend `BaseTool`: +- **Actions**: `create-sales-order`, `update-order`, `cancel-order`, `fulfill-order`, `create-return` +- **Queries**: `get-orders`, `get-customers`, `get-products`, `get-product-variants`, `get-inventory`, `get-fulfillments`, `get-returns` + +New tools: create in `src/tools/[category]/`, extend `BaseTool`, add to `registerTools` function. + +### Adapter Loading + +Configured via environment variables: +- `ADAPTER_TYPE=built-in` + `ADAPTER_NAME=mock` - Use built-in adapter +- `ADAPTER_TYPE=npm` + `ADAPTER_PACKAGE=@company/adapter` - Load from NPM +- `ADAPTER_TYPE=local` + `ADAPTER_PATH=/path/to/adapter.js` - Load local file + +### JSON Schemas + +Domain model schemas in `/schemas/`: +- `order.json`, `customer.json`, `product.json`, `product-variant.json` +- `inventory.json`, `fulfillment.json`, `return.json` +- `tool-inputs/` - Input schemas for each tool + +Generate schemas: `npm run generate:json-schemas` (in server/) + +## Key Implementation Details + +### ES Module Requirements +- All imports use `.js` extension (e.g., `from './file.js'`) +- `type: "module"` in package.json +- Use `import`/`export`, not `require` + +### Test Isolation +- Vitest with `singleThread: true` to prevent race conditions +- Always call `AdapterFactory.clearInstances()` in test cleanup + +### MCP Response Format +```typescript +// Success +{ content: [{ type: "text", text: string }], isError: false } +// Error +{ content: [{ type: "text", text: string }], isError: true } +``` + +### ServiceOrchestrator Lifecycle +```typescript +await serviceOrchestrator.initialize(adapterConfig); // Required before use +``` + +## Creating a Custom Adapter + +1. Copy `/adapter-template` to your project +2. Implement `IFulfillmentAdapter` interface (all 12 operations) +3. For built-in: add to `AdapterFactory.builtInAdapters` map +4. For external: publish as NPM package or load via local path + +The interface requires: `initialize()`, `shutdown()`, plus operations like `createSalesOrder`, `getOrders`, `getCustomers`, etc. + +## Claude Desktop Integration + +```json +{ + "mcpServers": { + "cof-mcp": { + "command": "node", + "args": ["/absolute/path/to/server/dist/index.js"], + "env": { + "ADAPTER_TYPE": "built-in", + "ADAPTER_NAME": "mock" + } + } + } +} +``` + +Config location: `%APPDATA%\Claude\claude_desktop_config.json` (Windows), `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) + +## Detailed Server Documentation + +See `/server/CLAUDE.md` for comprehensive server-specific guidance including: +- Configuration system details +- Error handling patterns +- Health monitoring +- Security considerations +- Common development patterns diff --git a/server/package.json b/server/package.json index f04e808..2300974 100644 --- a/server/package.json +++ b/server/package.json @@ -81,6 +81,6 @@ ], "repository": { "type": "git", - "url": "https://github.com/cof-org/mcp.git" + "url": "https://github.com/commerce-operations-foundation/mcp.git" } } diff --git a/virtocommerce-adapter/.gitignore b/virtocommerce-adapter/.gitignore new file mode 100644 index 0000000..2036277 --- /dev/null +++ b/virtocommerce-adapter/.gitignore @@ -0,0 +1,100 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# Build output +dist/ +build/ +out/ +*.tsbuildinfo + +# Environment files +.env +.env.local +.env.development +.env.test +.env.production +.env*.local + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store +Thumbs.db + +# Test coverage +coverage/ +*.lcov +.nyc_output/ + +# Logs +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids/ +*.pid +*.seed +*.pid.lock + +# Cache +.npm/ +.eslintcache +.stylelintcache +*.tsbuildinfo +.cache/ + +# OS files +.DS_Store +Thumbs.db +desktop.ini + +# Temporary files +tmp/ +temp/ +*.tmp +*.temp + +# Debug files +*.map + +# Package files +*.tgz + +# Lock files (optional - you may want to commit these) +# package-lock.json +# yarn.lock +# pnpm-lock.yaml + +# Documentation build +docs/_build/ +docs/.vuepress/dist/ + +# Backup files +*.backup +*.bak + +# Private keys and certificates +*.pem +*.key +*.crt +*.p12 +*.pfx + +# Database files +*.sqlite +*.sqlite3 +*.db + +# Custom ignore patterns for your OMS +# Add any OMS-specific files to ignore \ No newline at end of file diff --git a/virtocommerce-adapter/LICENSE b/virtocommerce-adapter/LICENSE new file mode 100644 index 0000000..40d19f6 --- /dev/null +++ b/virtocommerce-adapter/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2025 Your Company + +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/virtocommerce-adapter/README.md b/virtocommerce-adapter/README.md new file mode 100644 index 0000000..c36a1ac --- /dev/null +++ b/virtocommerce-adapter/README.md @@ -0,0 +1,360 @@ +# Commerce Operations Foundation Adapter Template + +This template helps fulfillment vendors and retailers create custom adapters for the Commerce Operations Foundation MCP Server. Use this template to build an adapter that connects your fulfillment system to the COF ecosystem. + +## Table of Contents + +- [Quick Start](#quick-start) +- [Template Structure](#template-structure) +- [Implementation Guide](#implementation-guide) +- [Configuration](#configuration) +- [Testing Your Adapter](#testing-your-adapter) +- [Using Your Adapter](#using-your-adapter) +- [Publishing](#publishing) +- [Troubleshooting](#troubleshooting) +- [API Reference](#api-reference) +- [Support](#support) + +## Quick Start + +### Step 1: Set Up Your Adapter Project + +```bash +# Copy this template to create your adapter +cp -r adapter-template/ my-fulfillment-adapter/ +cd my-fulfillment-adapter/ + +# Install dependencies +npm install + +# Rename the adapter in package.json +# Update name to: @yourcompany/cof-adapter-yourfulfillment +``` + +### Step 2: Customize Package Information + +Edit `package.json` to reflect your adapter: + +```json +{ + "name": "@yourcompany/cof-adapter-yourfulfillment", + "version": "1.0.0", + "description": "YourFulfillment adapter for Commerce Operations Foundation", + "author": "Your Company", + "license": "MIT" +} +``` + +### Step 3: Implement the Adapter Interface + +Open `src/adapter.ts` and implement your fulfillment system's logic. The template provides all required methods with TODO comments showing what needs implementation. + +## Template Structure + +``` +adapter-template/ +├── src/ +│ ├── adapter.ts # Main adapter implementation (modify this) +│ ├── index.ts # Module exports (update exports) +│ ├── types.ts # Custom type definitions (add your types) +│ └── utils/ +│ └── api-client.ts # HTTP client wrapper (customize for your API) +├── tests/ +│ └── adapter.test.ts # Unit tests (write your tests) +├── examples/ +│ ├── basic-usage.ts # Basic usage examples +│ └── test-server.ts # Test with local MCP server +├── docs/ +│ ├── API.md # Document your API mappings +│ └── CONFIGURATION.md # Document configuration options +├── package.json # Update name, author, description +├── tsconfig.json # TypeScript configuration +├── .env.example # Example configuration +└── README.md # Replace with your documentation +``` + +## Implementation Guide + +### Required Methods Checklist + +Your adapter must implement these methods from the `IFulfillmentAdapter` interface: + +#### Lifecycle (3 methods) +- [ ] `connect()` - Establish connection to your fulfillment system +- [ ] `disconnect()` - Clean up resources +- [ ] `healthCheck()` - Return system health status + +#### Action Operations (4 methods) +- [ ] `createSalesOrder()` - Create new orders +- [ ] `cancelOrder()` - Cancel existing orders +- [ ] `updateOrder()` - Update order details +- [ ] `fulfillOrder()` - Mark orders as fulfilled / shipped + +#### Query Operations (6 methods) +- [ ] `getOrders()` - Retrieve order details +- [ ] `getCustomers()` - Get customer details +- [ ] `getProducts()` - Fetch product information +- [ ] `getProductVariants()` - Retrieve product variants +- [ ] `getInventory()` - Check inventory levels +- [ ] `getFulfillments()` - Fetch fulfillment records + +### Implementation Steps + +1. **Update adapter.ts with your API logic:** +```typescript +// src/adapter.ts +export class YourFulfillmentAdapter implements IFulfillmentAdapter { + async createSalesOrder(input: CreateSalesOrderInput): Promise { + // TODO: Replace with your API call + const response = await this.apiClient.post('/orders', transform(input)); + + return response.success + ? this.success({ order: mapOrder(response.data) }) + : this.failure('Failed to create order', response.error); + } +} +``` + +2. **Configure your API client:** +```typescript +// src/utils/api-client.ts +export class ApiClient { + constructor(config: YourConfig) { + // Set up authentication, base URL, etc. + } +} +``` + +3. **Define your configuration types:** +```typescript +// src/types.ts +export interface YourAdapterConfig { + apiUrl: string; + apiKey: string; + // Add your specific config options +} + +``` + +## Testing Your Adapter + +### Step 1: Test Locally + +```bash +# Build your adapter +npm run build + +# Run unit tests +npm test + +# Test with the MCP server locally +cd ../server +npm install +ADAPTER_TYPE=local ADAPTER_PATH=../my-fulfillment-adapter/dist/index.js npm start +``` + +### Step 2: Write Unit Tests + +```typescript +// tests/adapter.test.ts +import { YourFulfillmentAdapter } from '../src/adapter'; + +describe('YourFulfillmentAdapter', () => { + let adapter: YourFulfillmentAdapter; + + beforeEach(() => { + adapter = new YourFulfillmentAdapter({ + apiUrl: 'https://sandbox.yourapi.com', + apiKey: 'test-key' + }); + }); + + test('should connect successfully', async () => { + await expect(adapter.connect()).resolves.not.toThrow(); + }); + + test('should create sales order', async () => { + const result = await adapter.createSalesOrder({ + // ... test data + }); + expect(result.success).toBe(true); + }); +}); +``` + +```typescript +// examples/test-server.ts +// Use this to test your adapter with a local MCP server +import { YourFulfillmentAdapter } from '../src/adapter'; + +const adapter = new YourFulfillmentAdapter({ + apiUrl: process.env.API_URL || 'https://sandbox.yourapi.com', + apiKey: process.env.API_KEY || 'test-key' +}); + +async function test() { + await adapter.connect(); + console.log('Connected!'); + + const health = await adapter.healthCheck(); + console.log('Health:', health); +} + +test().catch(console.error); +``` + +## Using Your Adapter + +### Option 1: Local Development (During Development) + +```bash +# In the server directory, configure to use your local adapter +cd ../server +cat > .env << EOF +ADAPTER_TYPE=local +ADAPTER_PATH=../my-fulfillment-adapter/dist/index.js +ADAPTER_CONFIG={"apiUrl":"https://sandbox.yourapi.com","apiKey":"test-key"} +EOF + +npm start +``` + +### Option 2: NPM Package (After Publishing) + +```bash +# Install your published adapter +npm install @yourcompany/cof-adapter-yourfulfillment + +# Configure the server to use it +cat > .env << EOF +ADAPTER_TYPE=npm +ADAPTER_PACKAGE=@yourcompany/cof-adapter-yourfulfillment +ADAPTER_CONFIG={"apiUrl":"https://api.yourfulfillment.com","apiKey":"production-key"} +EOF + +npm start +``` + +## Configuration + +Your adapter should accept configuration through the `ADAPTER_CONFIG` environment variable. Define your configuration interface in `src/types.ts`: + +```typescript +export interface YourAdapterConfig { + apiUrl: string; // Required: Your API endpoint + apiKey: string; // Required: Authentication key + workspace?: string; // Optional: Tenant/workspace ID + timeout?: number; // Optional: Request timeout (ms) + retryAttempts?: number; // Optional: Retry count +} +``` + +## Publishing + +### For Public NPM Packages + +```bash +# 1. Update version +npm version patch # or minor/major + +# 2. Build and test +npm run build +npm test + +# 3. Publish to NPM +npm publish --access public +``` + +### For Private Use + +```bash +# Option 1: Private NPM registry +npm publish --registry https://your-registry.com + +# Option 2: Git repository +git tag v1.0.0 +git push origin v1.0.0 + +# Option 3: Local file reference in package.json +"dependencies": { + "my-adapter": "file:../my-fulfillment-adapter" +} + +## Troubleshooting + +### Adapter Not Loading + +```bash +# Check the adapter can be imported +node -e "import('../my-fulfillment-adapter/dist/index.js')" + +# Verify environment variables +echo $ADAPTER_TYPE +echo $ADAPTER_PATH +``` + +### Connection Issues + +- Verify API credentials are correct +- Check network connectivity to your API +- Test with curl: `curl -X GET https://your-api.com/health` +- Enable debug logging in your adapter + +### Type Errors + +```bash +# Ensure types are installed +npm install --save-dev @types/node + +# Check TypeScript version +npx tsc --version +``` + +## API Reference + +### Required Interfaces + +Your adapter must implement the `IFulfillmentAdapter` interface from the main server. Key types: + +```typescript +interface OrderParams { + extOrderId: string; + customer: Customer; + items: LineItem[]; + shippingAddress?: Address; + billingAddress?: Address; +} + +interface OrderResult { + success: boolean; + orderId: string; + orderNumber?: string; + status?: string; +} +``` + +See the full type definitions in the server's `src/types/` directory. + +## Support + +### Documentation +- [Commerce Operations Foundation Docs](../docs/README.md) +- [For Fulfillment Vendors Guide](../docs/getting-started/for-fulfillment-vendors.md) +- [MCP Protocol Specification](https://modelcontextprotocol.io) + +### Getting Help +- Create an issue in this repository +- Check existing adapters in the `adapters/` directory for examples +- Review the mock adapter implementation for reference + +## Next Steps + +1. **Copy this template** to start your adapter +2. **Implement the interface** methods for your fulfillment system +3. **Test locally** with the MCP server +4. **Publish** to NPM or deploy privately +5. **Document** your configuration options + +--- + +*This template is part of the Commerce Operations Foundation (COF) project.* diff --git a/virtocommerce-adapter/docs/API.md b/virtocommerce-adapter/docs/API.md new file mode 100644 index 0000000..d0ea6d1 --- /dev/null +++ b/virtocommerce-adapter/docs/API.md @@ -0,0 +1,79 @@ +# API Documentation + +## YourFulfillment Adapter Reference + +This document describes how the template adapter maps to the interfaces exposed by `@cof-org/mcp`. + +## Initialization + +```typescript +import { YourFulfillmentAdapter } from '@yourcompany/uois-adapter-yourfulfillment'; + +const adapter = new YourFulfillmentAdapter({ + apiUrl: 'https://api.yourfulfillment.com', + apiKey: 'your-api-key', + workspace: 'your-workspace', + timeout: 30000, + retryAttempts: 3, + debugMode: false +}); +``` + +Configuration options correspond to the `AdapterOptions` type in `src/types.ts` and are provided via the constructor or `ADAPTER_CONFIG` JSON when loaded by the server. + +## Lifecycle Methods + +| Method | Purpose | +| ------ | ------- | +| `connect()` | Authenticate and verify API connectivity. | +| `disconnect()` | Clean up resources and terminate sessions. | +| `healthCheck()` | Return a `HealthStatus` describing adapter health. | + +## Action Operations + +| Method | Input | Result | +| ------ | ----- | ------ | +| `createSalesOrder(input: CreateSalesOrderInput)` | Standard order payload | `OrderResult` with the created order | +| `cancelOrder(input: CancelOrderInput)` | Order ID plus optional metadata | `OrderResult` with the cancelled order | +| `updateOrder(input: UpdateOrderInput)` | Partial order update | `OrderResult` with the updated order | +| `fulfillOrder(input: FulfillOrderInput)` | Fulfillment request | `FulfillmentToolResult<{ fulfillment: Fulfillment }>` | + +Each method should translate between platform types and your API’s payloads, then wrap responses with `success`/`failure` helpers as shown in `src/adapter.ts`. + +## Query Operations + +| Method | Description | +| ------ | ----------- | +| `getOrders(input: GetOrdersInput)` | Returns orders matching filter criteria. | +| `getCustomers(input: GetCustomersInput)` | Retrieves customer summaries. | +| `getProducts(input: GetProductsInput)` | Returns catalog records. | +| `getProductVariants(input: GetProductVariantsInput)` | Fetches variant SKUs. | +| `getInventory(input: GetInventoryInput)` | Reports stock levels. | +| `getFulfillments(input: GetFulfillmentsInput)` | Lists fulfillment records. | + +All responses use the `FulfillmentToolResult` union declared in `@cof-org/mcp`. On failure, include adapter specific error metadata so downstream tools can diagnose issues. + +## Error Handling + +Throw or return `AdapterError` instances with a `code` that callers can interpret. The template defines `ErrorCode` values in `src/types.ts`; extend that enum or supply string literals that make sense for your integration. + +Example: + +```typescript +if (!response.success) { + throw new AdapterError('Order not found', ErrorCode.ORDER_NOT_FOUND, response.error); +} +``` + +## Type Definitions + +Reference `@cof-org/mcp` for shared fulfillment models and `src/types.ts` for YourFulfillment-specific shapes. Maintaining strict typing in adapters ensures the server’s JSON Schema validation continues to succeed. + +## Best Practices + +1. **Always call `connect`** before invoking action/query methods. +2. **Wrap outbound calls** with retry/backoff logic using the provided API client. +3. **Return consistent metadata** (e.g., order numbers, timestamps) so downstream tooling can present clear answers. +4. **Use `ADAPTER_CONFIG`** for credentials and behavior toggles instead of hardcoding values. + +For complete implementation examples, review `src/adapter.ts` within this template. diff --git a/virtocommerce-adapter/docs/CONFIGURATION.md b/virtocommerce-adapter/docs/CONFIGURATION.md new file mode 100644 index 0000000..f4018ec --- /dev/null +++ b/virtocommerce-adapter/docs/CONFIGURATION.md @@ -0,0 +1,55 @@ +# Configuration Guide + +This guide explains common configuration options your adapter should support, and how they map to the MCP server environment variables. + +## MCP Server Environment + +Set these in the MCP server `.env` (or Process Env) to load your adapter: + +``` +ADAPTER_TYPE=npm # or local +ADAPTER_PACKAGE=@yourcompany/uois-adapter-yourfulfillment # when using npm +ADAPTER_EXPORT=YourFulfillmentAdapter # optional, defaults to default export +# For local development +# ADAPTER_TYPE=local +# ADAPTER_PATH=/absolute/path/to/yourfulfillment-adapter/dist/index.js + +# Adapter-specific options (JSON string passed to adapter constructor) +ADAPTER_CONFIG={ + "apiUrl": "https://api.yourfulfillment.com", + "apiKey": "YOUR_API_KEY", + "workspace": "YOUR_WORKSPACE", + "timeout": 30000, + "retryAttempts": 3, + "debugMode": false +} +``` + +## Recommended Adapter Options + +- apiUrl: Base URL for your API +- apiKey: Credential for authentication +- workspace: Tenant or account context if applicable +- timeout: Request timeout (ms) +- retryAttempts: Retries for transient errors +- debugMode: Verbose logging toggle + +## Exporting Your Adapter + +Ensure your package exports the adapter class as the default export (and optionally a named export) from `src/index.ts`: + +```ts +export { YourFulfillmentAdapter as default } from './adapter'; +export { YourFulfillmentAdapter } from './adapter'; +``` + +This lets the MCP server load it via dynamic import: +- NPM: `import(pkg)[exportName || 'default']` +- Local: `import(filePath)[exportName || 'default']` + +## Deployment Notes + +- Do not commit secrets. Use environment variables or a secrets manager. +- Provide a `.env.example` with the variables your adapter needs. +- Document any required scopes, IP allowlists, or network prerequisites. + diff --git a/virtocommerce-adapter/docs/PUBLISHING.md b/virtocommerce-adapter/docs/PUBLISHING.md new file mode 100644 index 0000000..6fe8b8a --- /dev/null +++ b/virtocommerce-adapter/docs/PUBLISHING.md @@ -0,0 +1,580 @@ +# Publishing Guide + +This guide explains how to publish and distribute your Fulfillment adapter. + +## Table of Contents + +- [For Fulfillment Vendors (NPM Package)](#for-fulfillment-vendors-npm-package) +- [For Retailers (Private Adapter)](#for-retailers-private-adapter) +- [Versioning Strategy](#versioning-strategy) +- [Testing Before Release](#testing-before-release) +- [Documentation Requirements](#documentation-requirements) +- [Support and Maintenance](#support-and-maintenance) + +## For Fulfillment Vendors (NPM Package) + +If you're an Fulfillment vendor providing a public adapter package, follow these steps to publish to NPM. + +### Prerequisites + +1. **NPM Account** + + ```bash + # Create account at npmjs.com + # Then login locally + npm login + ``` + +2. **Package Scope** (recommended) + ```bash + # Update package.json name + "name": "@yourcompany/uois-adapter-yourfulfillment" + ``` + +### Step 1: Prepare for Publishing + +1. **Update package.json** + + ```json + { + "name": "@yourcompany/uois-adapter-yourfulfillment", + "version": "1.0.0", + "description": "Order Network eXchange adapter for YourFulfillment", + "keywords": ["uois", "fulfillment", "adapter", "mcp", "yourfulfillment"], + "author": "Your Company ", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/yourcompany/yourfulfillment-adapter" + }, + "bugs": { + "url": "https://github.com/yourcompany/yourfulfillment-adapter/issues" + }, + "homepage": "https://docs.yourfulfillment.com/adapter" + } + ``` + +2. **Verify all tests pass** + + ```bash + npm test + npm run lint + ``` + +3. **Build the package** + + ```bash + npm run build + ``` + +4. **Check package contents** + ```bash + npm pack --dry-run + ``` + +### Step 2: Version Management + +Use semantic versioning (semver): + +```bash +# Patch release (1.0.0 -> 1.0.1) +# Bug fixes, minor updates +npm version patch + +# Minor release (1.0.0 -> 1.1.0) +# New features, backward compatible +npm version minor + +# Major release (1.0.0 -> 2.0.0) +# Breaking changes +npm version major + +# Pre-release versions +npm version prerelease --preid=beta +# Results in: 1.0.1-beta.0 +``` + +### Step 3: Publish to NPM + +1. **First-time publishing (scoped package)** + + ```bash + npm publish --access public + ``` + +2. **Subsequent releases** + + ```bash + npm publish + ``` + +3. **Publish beta/pre-release** + ```bash + npm publish --tag beta + ``` + +### Step 4: Post-Publishing + +1. **Verify installation** + + ```bash + # In a test directory + npm install @yourcompany/uois-adapter-yourfulfillment + + # Check it works + node -e "import('@yourcompany/uois-adapter-yourfulfillment').then(m=>console.log(m.VERSION))" + ``` + +2. **Create GitHub release** + + ```bash + git push origin main + git push origin --tags + # Create release on GitHub with changelog + ``` + +3. **Update documentation** + - Update README with new version + - Add changelog entry + - Update any version-specific docs + +### NPM Scripts for Publishing + +Add these to your package.json: + +```json +{ + "scripts": { + "prepublishOnly": "npm run clean && npm run build && npm test", + "publish:patch": "npm version patch && npm publish", + "publish:minor": "npm version minor && npm publish", + "publish:major": "npm version major && npm publish", + "publish:beta": "npm version prerelease --preid=beta && npm publish --tag beta", + "postpublish": "git push origin main --tags" + } +} +``` + +## For Retailers (Private Adapter) + +If you're a retailer creating a custom adapter for internal use, follow these guidelines. + +### Option 1: Private NPM Registry + +1. **Setup private registry** (e.g., Verdaccio, npm Enterprise, Artifactory) + +2. **Configure NPM** + + ```bash + # Set registry for your scope + npm config set @yourcompany:registry https://npm.yourcompany.com + + # Authenticate + npm login --registry https://npm.yourcompany.com + ``` + +3. **Publish to private registry** + ```bash + npm publish --registry https://npm.yourcompany.com + ``` + +### Option 2: Git Repository + +1. **Use Git tags for versions** + + ```bash + git tag v1.0.0 + git push origin v1.0.0 + ``` + +2. **Install from Git** + + ```bash + # In your MCP server + npm install git+ssh://git@github.com:yourcompany/yourfulfillment-adapter.git#v1.0.0 + + # Or using HTTPS + npm install git+https://github.com/yourcompany/yourfulfillment-adapter.git#v1.0.0 + ``` + +### Option 3: Local File System + +1. **Build the adapter** + + ```bash + cd yourfulfillment-adapter + npm run build + ``` + +2. **Link locally (for development)** + + ```bash + # In adapter directory + npm link + + # In MCP server directory + npm link @yourcompany/uois-adapter-yourfulfillment + ``` + +3. **Or use file path** + ```bash + # In .env + ADAPTER_TYPE=local + ADAPTER_PATH=../yourfulfillment-adapter/dist/index.js + ``` + +### CI/CD Pipeline + +Example GitHub Actions workflow: + +```yaml +name: Build and Deploy Adapter + +on: + push: + tags: + - 'v*' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm test + + - name: Build + run: npm run build + + - name: Package + run: npm pack + + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: adapter-package + path: '*.tgz' +``` + +## Versioning Strategy + +### Semantic Versioning + +Follow semver strictly: + +- **MAJOR.MINOR.PATCH** (e.g., 2.1.3) +- **MAJOR**: Breaking changes +- **MINOR**: New features, backward compatible +- **PATCH**: Bug fixes + +### Breaking Changes + +Increment MAJOR version when: + +- Removing or renaming public methods +- Changing method signatures +- Modifying response formats +- Updating minimum Node.js version +- Changing configuration structure + +### Feature Additions + +Increment MINOR version when: + +- Adding new methods +- Adding optional parameters +- Adding new configuration options +- Performance improvements +- Adding new error types + +### Bug Fixes + +Increment PATCH version when: + +- Fixing bugs +- Updating documentation +- Improving error messages +- Security patches +- Dependency updates (non-breaking) + +### Pre-release Versions + +Use for testing before stable release: + +```bash +1.0.0-alpha.1 # Alpha release +1.0.0-beta.1 # Beta release +1.0.0-rc.1 # Release candidate +``` + +## Testing Before Release + +### 1. Unit Tests + +```bash +# Run all tests +npm test + +# With coverage +npm test -- --coverage + +# Watch mode during development +npm test -- --watch +``` + +### 2. Integration Tests + +Test with actual MCP server: + +```javascript +// test/integration.test.js +const { YourFulfillmentAdapter } = require('../dist/index.js'); + +describe('Integration Tests', () => { + it('should work with MCP server', async () => { + const adapter = new YourFulfillmentAdapter({ + apiUrl: process.env.TEST_API_URL, + apiKey: process.env.TEST_API_KEY, + }); + + await adapter.connect(); + const health = await adapter.healthCheck(); + expect(health.status).toBe('healthy'); + await adapter.disconnect(); + }); +}); +``` + +### 3. Manual Testing Checklist + +- [ ] Adapter connects successfully +- [ ] All CRUD operations work +- [ ] Error handling works correctly +- [ ] Rate limiting is respected +- [ ] Timeout handling works +- [ ] Retry logic functions properly +- [ ] Memory leaks check +- [ ] Performance benchmarks + +### 4. Compatibility Testing + +Test with different versions: + +- Node.js versions (14, 16, 18, 20) +- MCP server versions +- Your Fulfillment API versions + +## Documentation Requirements + +### Required Documentation + +1. **README.md** + + - Installation instructions + - Configuration options + - Basic usage example + - Link to full documentation + +2. **CHANGELOG.md** + + ```markdown + # Changelog + + ## [1.1.0] - 2025-10-31 + + ### Added + + - New split order functionality + - Support for webhook notifications + + ### Fixed + + - Memory leak in connection pooling + - Rate limiting for bulk operations + + ### Changed + + - Improved error messages + - Updated dependencies + ``` + +3. **API Documentation** + + - All public methods + - Parameters and return types + - Error scenarios + - Code examples + +4. **Migration Guides** + + ```markdown + # Migrating from v1.x to v2.0 + + ## Breaking Changes + + - `captureOrder` now requires `currency` field + - Removed deprecated `legacyAuth` option + + ## Migration Steps + + 1. Update configuration... + 2. Change method calls... + ``` + +### Documentation Tools + +Consider using: + +- **TypeDoc** for API documentation +- **Docusaurus** for documentation site +- **Swagger/OpenAPI** for REST API docs + +## Support and Maintenance + +### Version Support Policy + +Define your support timeline: + +```markdown +| Version | Status | Security Updates | End of Life | +| ------- | ------- | ---------------- | ----------- | +| 2.x | Current | ✓ | - | +| 1.x | LTS | ✓ | 2025-01-01 | +| 0.x | EOL | ✗ | 2023-12-31 | +``` + +### Issue Management + +1. **Bug Reports** + + - Use GitHub Issues + - Provide issue templates + - Label system (bug, feature, docs) + +2. **Security Issues** + - Private disclosure process + - Security policy file + - CVE handling + +### Release Notes Template + +````markdown +# Release Notes - v1.1.0 + +## Release Date: 2025-10-31 + +## What's New + +- Split order functionality for multi-warehouse fulfillment +- Webhook support for real-time updates +- Performance improvements (30% faster order processing) + +## Bug Fixes + +- Fixed memory leak in connection pooling (#123) +- Resolved rate limiting issues for bulk operations (#124) +- Corrected timezone handling in order timestamps (#125) + +## Breaking Changes + +None in this release + +## Deprecations + +- `legacyAuth` option will be removed in v2.0.0 + +## Upgrade Instructions + +```bash +npm update @yourcompany/uois-adapter-yourfulfillment +``` +```` + +## Known Issues + +- Bulk update operations limited to 100 items + +## Contributors + +Thanks to @user1, @user2 for their contributions! + +```` + +### Monitoring Published Versions + +1. **NPM Statistics** + ```bash + npm view @yourcompany/uois-adapter-yourfulfillment + npm info @yourcompany/uois-adapter-yourfulfillment versions +```` + +2. **Download Statistics** + + - Check npmjs.com package page + - Use npm-stat.com for graphs + +3. **Deprecating Old Versions** + ```bash + npm deprecate @yourcompany/uois-adapter-yourfulfillment@"< 1.0.0" "Please upgrade to v1.0.0 or higher" + ``` + +## Quick Publish Checklist + +Before publishing, ensure: + +- [ ] All tests pass +- [ ] Documentation is updated +- [ ] CHANGELOG is updated +- [ ] Version number is correct +- [ ] Package.json is complete +- [ ] Build completes successfully +- [ ] No sensitive data in code +- [ ] License file is present +- [ ] Git tag is created +- [ ] GitHub release is drafted + +## Troubleshooting Publishing Issues + +### Common Issues + +1. **Authentication Failed** + + ```bash + npm login + npm whoami + ``` + +2. **Package Name Taken** + + - Use scoped packages: `@yourcompany/package-name` + +3. **Missing Files** + + - Check `files` field in package.json + - Use `npm pack --dry-run` to verify + +4. **Version Already Exists** + + - Can't republish same version + - Increment version number + +5. **Large Package Size** + - Add `.npmignore` file + - Exclude unnecessary files + - Check with `npm pack --dry-run` + +## Resources + +- [NPM Documentation](https://docs.npmjs.com/) +- [Semantic Versioning](https://semver.org/) +- [Keep a Changelog](https://keepachangelog.com/) +- [Conventional Commits](https://www.conventionalcommits.org/) diff --git a/virtocommerce-adapter/eslint.config.js b/virtocommerce-adapter/eslint.config.js new file mode 100644 index 0000000..66fba89 --- /dev/null +++ b/virtocommerce-adapter/eslint.config.js @@ -0,0 +1,77 @@ +// ESLint 9 flat config for adapter template +import js from '@eslint/js'; +import importPlugin from 'eslint-plugin-import'; +import * as tseslint from 'typescript-eslint'; +import globals from 'globals'; + +export default [ + { + ignores: ['dist/', 'node_modules/', 'coverage/', '**/*.d.ts'], + }, + { + languageOptions: { + globals: globals.node, + }, + }, + js.configs.recommended, + ...tseslint.configs.recommended, + { + files: ['src/**/*.ts', 'tests/**/*.ts', 'examples/**/*.ts'], + languageOptions: { + parser: tseslint.parser, + parserOptions: { + project: './tsconfig.eslint.json', + sourceType: 'module', + ecmaVersion: 'latest', + }, + }, + plugins: { import: importPlugin }, + rules: { + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-non-null-assertion': 'warn', + '@typescript-eslint/ban-ts-comment': 'off', + 'no-console': ['warn', { allow: ['warn', 'error', 'info'] }], + 'prefer-const': 'error', + 'no-var': 'error', + eqeqeq: ['error', 'always', { null: 'ignore' }], + curly: ['error', 'all'], + 'brace-style': ['error', '1tbs', { allowSingleLine: true }], + semi: ['error', 'always'], + quotes: ['error', 'single', { avoidEscape: true, allowTemplateLiterals: true }], + 'import/extensions': [ + 'error', + 'always', + { + js: 'always', + ts: 'never', + tsx: 'never', + json: 'always', + ignorePackages: true, + }, + ], + 'import/no-unresolved': [ + 'error', + { + ignore: ['^\\./.*\\.js$', '^\\.\\./.*\\.js$'], + }, + ], + }, + }, + { + files: ['examples/**/*.ts', 'tests/**/*.ts'], + rules: { + 'import/extensions': 'off', + 'import/no-unresolved': 'off', + 'no-console': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-namespace': 'off', + }, + }, + { + // Ignore generated/mock CJS modules under ESM project + ignores: ['src/mocks/**'], + }, +]; diff --git a/virtocommerce-adapter/examples/advanced-config.ts b/virtocommerce-adapter/examples/advanced-config.ts new file mode 100644 index 0000000..8588be1 --- /dev/null +++ b/virtocommerce-adapter/examples/advanced-config.ts @@ -0,0 +1,33 @@ +import VirtoCommerceFulfillmentAdapter from '@virtocommerce/vc-fulfillment-mcp-adapter'; + +async function main() { + const adapter = new VirtoCommerceFulfillmentAdapter({ + type: 'npm', + package: '@virtocommerce/vc-fulfillment-mcp-adapter', + options: { + apiUrl: 'https://api.yourfulfillment.com', + apiKey: process.env.API_KEY || '', + workspace: process.env.WORKSPACE, + timeout: 20_000, + retryAttempts: 5, + debugMode: true, + } + } as any); + + await adapter.connect(); + + // Example: capture an order with custom fields + const result = await adapter.captureOrder({ + extOrderId: `ADV-${Date.now()}`, + customer: { email: 'adv@example.com' }, + lineItems: [{ sku: 'SKU-ADV', quantity: 1, unitPrice: 49.99 }], + currency: 'USD', + customFields: [{ name: 'channel', value: 'partner' }] + } as any); + + console.log('Captured order:', result); + await adapter.disconnect(); +} + +main().catch((e) => { console.error(e); process.exit(1); }); + diff --git a/virtocommerce-adapter/examples/basic-usage.ts b/virtocommerce-adapter/examples/basic-usage.ts new file mode 100644 index 0000000..6e0ad7c --- /dev/null +++ b/virtocommerce-adapter/examples/basic-usage.ts @@ -0,0 +1,209 @@ +/** + * Basic Usage Example for VirtoCommerceFulfillment Adapter + * + * This example demonstrates basic operations with the adapter. + */ + +import { VirtoCommerceFulfillmentAdapter } from '../src/adapter'; + +async function main() { + // Initialize the adapter + const adapter = new VirtoCommerceFulfillmentAdapter({ + apiUrl: process.env.API_URL || 'https://api.yourfulfillment.com', + apiKey: process.env.API_KEY || 'your-api-key', + workspace: process.env.WORKSPACE || 'default', + timeout: 30000, + debugMode: true + }); + + try { + // 1. Connect to the Fulfillment System + console.log('Connecting to VirtoCommerce...'); + await adapter.connect(); + console.log('✓ Connected successfully\n'); + + // 2. Check health status + console.log('Checking health status...'); + const health = await adapter.healthCheck(); + console.log('Health Status:', health.status); + console.log('Checks:', health.checks); + console.log(''); + + // 3. Create an order + console.log('Creating a new order...'); + const orderResult = await adapter.captureOrder({ + extOrderId: 'EXT-' + Date.now(), + customer: { + customerId: 'CUST-001', + email: 'customer@example.com', + firstName: 'John', + lastName: 'Doe', + phone: '+1234567890' + }, + lineItems: [ + { + sku: 'PROD-001', + name: 'Sample Product', + quantity: 2, + price: 29.99, + discount: 5.00, + tax: 2.50 + }, + { + sku: 'PROD-002', + name: 'Another Product', + quantity: 1, + price: 49.99, + tax: 4.00 + } + ], + shippingAddress: { + firstName: 'John', + lastName: 'Doe', + address1: '123 Main Street', + address2: 'Suite 100', + city: 'New York', + state: 'NY', + zip: '10001', + country: 'US', + phone: '+1234567890' + }, + billingAddress: { + firstName: 'John', + lastName: 'Doe', + address1: '123 Main Street', + address2: 'Suite 100', + city: 'New York', + state: 'NY', + zip: '10001', + country: 'US', + phone: '+1234567890' + }, + totalPrice: 111.47, + currency: 'USD', + orderNote: 'Please handle with care', + orderSource: 'website' + }); + + console.log('✓ Order created successfully!'); + console.log(' Order ID:', orderResult.orderId); + console.log(' Order Number:', orderResult.orderNumber); + console.log(' Status:', orderResult.status); + console.log(''); + + // 4. Get order details + console.log('Fetching order details...'); + const order = await adapter.getOrder({ + orderId: orderResult.orderId + }); + console.log('Order Details:'); + console.log(' External ID:', order.extOrderId); + console.log(' Customer:', order.customer?.email); + console.log(' Total:', order.totalPrice, order.currency); + console.log(' Status:', order.status); + console.log(''); + + // 5. Check inventory + console.log('Checking inventory for PROD-001...'); + const inventory = await adapter.getInventory('PROD-001'); + console.log('Inventory:'); + console.log(' Available:', inventory.available); + console.log(' Reserved:', inventory.reserved); + console.log(' Total:', inventory.total); + if (inventory.locations) { + console.log(' Locations:'); + inventory.locations.forEach(loc => { + console.log(` ${loc.locationId}: ${loc.available} available`); + }); + } + console.log(''); + + // 6. Update order + console.log('Updating order notes...'); + const updateResult = await adapter.updateOrder( + orderResult.orderId, + { + notes: 'Updated: Gift wrap requested', + tags: ['gift', 'priority'] + } + ); + console.log('✓ Order updated successfully'); + console.log(' Updated fields:', updateResult.updatedFields.join(', ')); + console.log(''); + + // 7. Reserve inventory + console.log('Reserving inventory...'); + const reservation = await adapter.reserveInventory( + [ + { sku: 'PROD-001', quantity: 2 }, + { sku: 'PROD-002', quantity: 1 } + ], + 15 // 15 minutes + ); + console.log('✓ Inventory reserved'); + console.log(' Reservation ID:', reservation.reservationId); + console.log(' Expires at:', reservation.expiresAt); + console.log(''); + + // 8. Hold order (optional - comment out if not testing) + /* + console.log('Placing order on hold...'); + const holdResult = await adapter.holdOrder( + orderResult.orderId, + { + reason: 'Payment verification required', + until: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), // 24 hours + autoRelease: true, + notes: 'Waiting for payment processor confirmation' + } + ); + console.log('✓ Order placed on hold'); + console.log(' Hold ID:', holdResult.holdId); + */ + + // 9. Ship order (optional - comment out if not testing) + /* + console.log('Creating shipment...'); + const shipmentResult = await adapter.shipOrder( + orderResult.orderId, + { + carrier: 'UPS', + service: 'Ground', + trackingNumber: 'TRK' + Date.now(), + items: [ + { sku: 'PROD-001', quantity: 2 }, + { sku: 'PROD-002', quantity: 1 } + ] + } + ); + console.log('✓ Shipment created'); + console.log(' Shipment ID:', shipmentResult.shipmentId); + console.log(' Tracking:', shipmentResult.trackingNumber); + console.log(' Tracking URL:', shipmentResult.trackingUrl); + */ + + // 10. Disconnect + console.log('\nDisconnecting from YourFulfillment...'); + await adapter.disconnect(); + console.log('✓ Disconnected successfully'); + + } catch (error) { + console.error('Error:', error); + + // Always disconnect on error + try { + await adapter.disconnect(); + } catch (disconnectError) { + console.error('Failed to disconnect:', disconnectError); + } + + process.exit(1); + } +} + +// Run the example +if (require.main === module) { + main().catch(console.error); +} + +export { main }; \ No newline at end of file diff --git a/virtocommerce-adapter/jest.config.js b/virtocommerce-adapter/jest.config.js new file mode 100644 index 0000000..b6969ad --- /dev/null +++ b/virtocommerce-adapter/jest.config.js @@ -0,0 +1,45 @@ +export default { + preset: 'ts-jest/presets/default-esm', + testEnvironment: 'node', + extensionsToTreatAsEsm: ['.ts'], + roots: ['/src', '/tests'], + testMatch: [ + '**/__tests__/**/*.+(ts|tsx|js)', + '**/?(*.)+(spec|test).+(ts|tsx|js)' + ], + transform: { + '^.+\\.(ts|tsx)$': ['ts-jest', { + useESM: true, + tsconfig: { + allowSyntheticDefaultImports: true + } + }] + }, + collectCoverageFrom: [ + 'src/**/*.{ts,tsx}', + '!src/**/*.d.ts', + '!src/index.ts', + '!src/**/*.interface.ts', + '!src/**/*.type.ts' + ], + coverageDirectory: 'coverage', + coverageReporters: ['text', 'lcov', 'html'], + coverageThreshold: { + global: { + branches: 80, + functions: 80, + lines: 80, + statements: 80 + } + }, + moduleNameMapper: { + '^@/(.*)$': '/src/$1', + '^@cof-org/mcp$': '/node_modules/@cof-org/mcp/dist/index.js', + '^(\\.{1,2}/.*)\\.js$': '$1' + }, + moduleDirectories: ['node_modules'], + setupFilesAfterEnv: ['/tests/setup.ts'], + globals: {}, + verbose: true, + testTimeout: 10000 +}; diff --git a/virtocommerce-adapter/package-lock.json b/virtocommerce-adapter/package-lock.json new file mode 100644 index 0000000..3363712 --- /dev/null +++ b/virtocommerce-adapter/package-lock.json @@ -0,0 +1,6592 @@ +{ + "name": "@virtocommerce/vc-fulfillment-mcp-adapter", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@virtocommerce/vc-fulfillment-mcp-adapter", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@cof-org/mcp": "file:../server", + "axios": "^1.6.0" + }, + "devDependencies": { + "@eslint/js": "^9.8.0", + "@types/jest": "^29.5.11", + "@types/node": "^20.19.29", + "@typescript-eslint/eslint-plugin": "^8.7.0", + "@typescript-eslint/parser": "^8.7.0", + "eslint": "^9.8.0", + "eslint-plugin-import": "^2.29.1", + "jest": "^29.7.0", + "ts-jest": "^29.1.1", + "typescript": "^5.3.3", + "typescript-eslint": "^8.7.0" + } + }, + "../server": { + "name": "@cof-org/mcp", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^0.5.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "dotenv": "^16.3.1", + "uuid": "^9.0.1", + "winston": "^3.17.0", + "winston-daily-rotate-file": "^5.0.0", + "zod": "^4.1.12" + }, + "bin": { + "cof-mcp": "dist/index.js" + }, + "devDependencies": { + "@eslint/js": "^9.8.0", + "@types/node": "^20.10.5", + "@types/uuid": "^9.0.7", + "@typescript-eslint/eslint-plugin": "^8.7.0", + "@typescript-eslint/parser": "^8.7.0", + "@vitest/coverage-v8": "^3.2.4", + "@vitest/ui": "^3.2.4", + "eslint": "^9.8.0", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-unused-imports": "^4.2.0", + "prettier": "^3.1.1", + "typescript": "^5.3.3", + "typescript-eslint": "^8.7.0", + "vite": "^7.1.3", + "vite-node": "^3.2.4", + "vitest": "^3.2.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "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/@babel/code-frame": { + "version": "7.27.1", + "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.28.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.3", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2", + "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", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "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", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "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.28.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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.28.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.2", + "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", + "dev": true, + "license": "MIT" + }, + "node_modules/@cof-org/mcp": { + "resolved": "../server", + "link": true + }, + "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/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/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/@eslint/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/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.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": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/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/@eslint/eslintrc/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/@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": "9.34.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.34.0.tgz", + "integrity": "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "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/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "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/argparse": { + "version": "1.0.10", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "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", + "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", + "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", + "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", + "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", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.30", + "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/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "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", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "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", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "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/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.29.tgz", + "integrity": "sha512-YrT9ArrGaHForBaCNwFjoqJWmn8G1Pr7+BH/vwyLHciA9qT/wSiuOhxGCT50JA5xLvFBd6PIiGkE3afxcPE1nw==", + "dev": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.41.0.tgz", + "integrity": "sha512-8fz6oa6wEKZrhXWro/S3n2eRJqlRcIa6SlDh59FXJ5Wp5XRZ8B9ixpJDcjadHq47hMx0u+HW6SNa6LjJQ6NLtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.41.0", + "@typescript-eslint/type-utils": "8.41.0", + "@typescript-eslint/utils": "8.41.0", + "@typescript-eslint/visitor-keys": "8.41.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.41.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.41.0.tgz", + "integrity": "sha512-gTtSdWX9xiMPA/7MV9STjJOOYtWwIJIYxkQxnSV1U3xcE+mnJSH3f6zI0RYP+ew66WSlZ5ed+h0VCxsvdC1jJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.41.0", + "@typescript-eslint/types": "8.41.0", + "@typescript-eslint/typescript-estree": "8.41.0", + "@typescript-eslint/visitor-keys": "8.41.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.41.0.tgz", + "integrity": "sha512-b8V9SdGBQzQdjJ/IO3eDifGpDBJfvrNTp2QD9P2BeqWTGrRibgfgIlBSw6z3b6R7dPzg752tOs4u/7yCLxksSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.41.0", + "@typescript-eslint/types": "^8.41.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.41.0.tgz", + "integrity": "sha512-n6m05bXn/Cd6DZDGyrpXrELCPVaTnLdPToyhBoFkLIMznRUQUEQdSp96s/pcWSQdqOhrgR1mzJ+yItK7T+WPMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.41.0", + "@typescript-eslint/visitor-keys": "8.41.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.41.0.tgz", + "integrity": "sha512-TDhxYFPUYRFxFhuU5hTIJk+auzM/wKvWgoNYOPcOf6i4ReYlOoYN8q1dV5kOTjNQNJgzWN3TUUQMtlLOcUgdUw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.41.0.tgz", + "integrity": "sha512-63qt1h91vg3KsjVVonFJWjgSK7pZHSQFKH6uwqxAH9bBrsyRhO6ONoKyXxyVBzG1lJnFAJcKAcxLS54N1ee1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.41.0", + "@typescript-eslint/typescript-estree": "8.41.0", + "@typescript-eslint/utils": "8.41.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.41.0.tgz", + "integrity": "sha512-9EwxsWdVqh42afLbHP90n2VdHaWU/oWgbH2P0CfcNfdKL7CuKpwMQGjwev56vWu9cSKU7FWSu6r9zck6CVfnag==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.41.0.tgz", + "integrity": "sha512-D43UwUYJmGhuwHfY7MtNKRZMmfd8+p/eNSfFe6tH5mbVDto+VQCayeAt35rOx3Cs6wxD16DQtIKw/YXxt5E0UQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.41.0", + "@typescript-eslint/tsconfig-utils": "8.41.0", + "@typescript-eslint/types": "8.41.0", + "@typescript-eslint/visitor-keys": "8.41.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.41.0.tgz", + "integrity": "sha512-udbCVstxZ5jiPIXrdH+BZWnPatjlYwJuJkDA4Tbo3WyYLh8NvB+h/bKeSZHDOFKfphsZYJQqaFtLeXEqurQn1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.41.0", + "@typescript-eslint/types": "8.41.0", + "@typescript-eslint/typescript-estree": "8.41.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.41.0.tgz", + "integrity": "sha512-+GeGMebMCy0elMNg67LRNoVnUFPIm37iu5CmHESVx56/9Jsfdpsvbv605DQ81Pi/x11IdKUsS5nzgTYbCQU9fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.41.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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-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", + "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", + "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", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "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", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "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/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.11.0", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "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", + "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", + "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", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "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.2.0", + "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 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "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", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.2", + "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.30001733", + "electron-to-chromium": "^1.5.199", + "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", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "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==", + "dev": true, + "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/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001735", + "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", + "dev": true, + "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", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "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", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "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/co": { + "version": "4.6.0", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/create-jest": { + "version": "29.7.0", + "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", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "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", + "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", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "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/electron-to-chromium": { + "version": "1.5.203", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "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/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "dev": true, + "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": "9.34.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.34.0.tgz", + "integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.34.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/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/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/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-plugin-import/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/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.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.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/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/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/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": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "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/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", + "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", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "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", + "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/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", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "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/follow-redirects": { + "version": "1.15.11", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "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.realpath": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "dev": 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", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "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", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "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", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "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.12", + "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", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "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/handlebars": { + "version": "4.7.8", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "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", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "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", + "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", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "dev": true, + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.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-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "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", + "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", + "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.2.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "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", + "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", + "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", + "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-config": { + "version": "29.7.0", + "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", + "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", + "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", + "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", + "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", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "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", + "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", + "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", + "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", + "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", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "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", + "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", + "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", + "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/jsesc": { + "version": "3.1.0", + "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", + "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", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=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", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "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", + "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", + "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/lru-cache": { + "version": "5.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "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", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "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", + "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", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "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/minimist": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/neo-async": { + "version": "2.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "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/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "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", + "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", + "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", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "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", + "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", + "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", + "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", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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", + "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", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "license": "MIT" + }, + "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", + "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/react-is": { + "version": "18.3.1", + "dev": true, + "license": "MIT" + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "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", + "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", + "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", + "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/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-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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/signal-exit": { + "version": "3.0.7", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "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", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "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", + "dev": true, + "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/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "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.12", + "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", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-jest": { + "version": "29.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", + "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 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.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 + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.2", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.41.0.tgz", + "integrity": "sha512-n66rzs5OBXW3SFSnZHr2T685q1i4ODm2nulFJhMZBotaTavsS8TrI3d7bDlRSs9yWo7HmyWrN9qDu14Qv7Y0Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.41.0", + "@typescript-eslint/parser": "8.41.0", + "@typescript-eslint/typescript-estree": "8.41.0", + "@typescript-eslint/utils": "8.41.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "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/v8-to-istanbul": { + "version": "9.3.0", + "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", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/wordwrap": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "dev": true, + "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", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "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", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "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/yargs-parser": { + "version": "21.1.1", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/virtocommerce-adapter/package.json b/virtocommerce-adapter/package.json new file mode 100644 index 0000000..763c9a6 --- /dev/null +++ b/virtocommerce-adapter/package.json @@ -0,0 +1,51 @@ +{ + "name": "@virtocommerce/vc-fulfillment-mcp-adapter", + "version": "1.0.0", + "description": "Commerce Operations Foundation adapter for VirtoCommerce Fulfillment", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "files": [ + "dist", + "README.md" + ], + "scripts": { + "build": "tsc", + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", + "test:integration": "npm run build && node test-integration.js", + "lint": "eslint .", + "prepublishOnly": "npm run build && npm test", + "dev": "tsc --watch" + }, + "keywords": [ + "cof", + "fulfillment", + "adapter", + "mcp" + ], + "author": "Your Company", + "license": "MIT", + "devDependencies": { + "@eslint/js": "^9.8.0", + "@types/jest": "^29.5.11", + "@types/node": "^20.19.29", + "@typescript-eslint/eslint-plugin": "^8.7.0", + "@typescript-eslint/parser": "^8.7.0", + "eslint": "^9.8.0", + "eslint-plugin-import": "^2.29.1", + "jest": "^29.7.0", + "ts-jest": "^29.1.1", + "typescript": "^5.3.3", + "typescript-eslint": "^8.7.0" + }, + "dependencies": { + "@cof-org/mcp": "file:../server", + "axios": "^1.6.0" + } +} \ No newline at end of file diff --git a/virtocommerce-adapter/src/adapter.ts b/virtocommerce-adapter/src/adapter.ts new file mode 100644 index 0000000..7f1790b --- /dev/null +++ b/virtocommerce-adapter/src/adapter.ts @@ -0,0 +1,284 @@ +/** + * VirtoCommerce Fulfillment Adapter Implementation + * + * Main adapter class that implements IFulfillmentAdapter interface. + * Delegates to domain-specific services for actual operations. + */ + +import type { + IFulfillmentAdapter, + AdapterConfig, + AdapterCapabilities, + HealthStatus, + OrderResult, + ReturnResult, + FulfillmentToolResult, + Order, + Fulfillment, + Return, + InventoryItem, + Product, + ProductVariant, + Customer, + CreateSalesOrderInput, + CreateReturnInput, + CancelOrderInput, + UpdateOrderInput, + FulfillOrderInput, + GetOrdersInput, + GetInventoryInput, + GetProductsInput, + GetProductVariantsInput, + GetCustomersInput, + GetFulfillmentsInput, + GetReturnsInput, +} from '@cof-org/mcp'; +import { AdapterError } from '@cof-org/mcp'; +import { ApiClient } from './utils/api-client.js'; +import type { AdapterOptions } from './types.js'; +import { ErrorCode } from './types.js'; +import { getErrorMessage } from './utils/type-guards.js'; +import { + OrderService, + CustomerService, + FulfillmentService, + ProductService, + ReturnService, +} from './services/index.js'; + +export class VirtoCommerceFulfillmentAdapter implements IFulfillmentAdapter { + private client: ApiClient; + private connected = false; + private options: AdapterOptions; + + // Domain services + private orderService: OrderService; + private customerService: CustomerService; + private fulfillmentService: FulfillmentService; + private productService: ProductService; + private returnService: ReturnService; + + constructor(config: any = {}) { + const options = config.options || config; + + this.options = { + apiUrl: options.apiUrl || 'https://api.yourfulfillment.com', + apiKey: options.apiKey || '', + workspace: options.workspace, + timeout: options.timeout || 30000, + retryAttempts: options.retryAttempts || 3, + debugMode: options.debugMode || false, + }; + + this.client = new ApiClient({ + baseUrl: this.options.apiUrl, + apiKey: this.options.apiKey, + timeout: this.options.timeout, + retryAttempts: this.options.retryAttempts, + debugMode: this.options.debugMode, + }); + + const tenantId = this.getTenantId(); + + // Initialize services + this.orderService = new OrderService(this.client, tenantId, this.options.workspace); + this.customerService = new CustomerService(this.client, tenantId); + this.fulfillmentService = new FulfillmentService(this.client, tenantId); + this.productService = new ProductService(this.client, tenantId); + this.returnService = new ReturnService(this.client); + } + + // Lifecycle methods + + async initialize?(config: AdapterConfig): Promise { + this.updateOptions(config.options ?? {}); + } + + async cleanup?(): Promise { + this.connected = false; + } + + async connect(): Promise { + try { + const response = await this.client.get('/health'); + + if (!response.success) { + throw new AdapterError( + 'Failed to connect to VirtoCommerce', + ErrorCode.CONNECTION_FAILED, + response + ); + } + + this.connected = true; + console.info('Successfully connected to VirtoCommerce'); + } catch (error: unknown) { + this.connected = false; + throw new AdapterError( + `Connection failed: ${getErrorMessage(error)}`, + ErrorCode.CONNECTION_FAILED, + error + ); + } + } + + async disconnect(): Promise { + this.connected = false; + console.info('Disconnected from VirtoCommerce'); + } + + async healthCheck(): Promise { + try { + const response = await this.client.get('/health'); + + return { + status: response.success ? 'healthy' : 'unhealthy', + timestamp: this.now(), + checks: [ + { + name: 'api_connection', + status: response.success ? 'pass' : 'fail', + message: response.success ? 'API is reachable' : 'API connection failed', + }, + { + name: 'authentication', + status: this.connected ? 'pass' : 'fail', + message: this.connected ? 'Authenticated' : 'Not authenticated', + }, + ], + version: '1.0.0', + }; + } catch (error: unknown) { + return { + status: 'unhealthy', + timestamp: this.now(), + checks: [ + { + name: 'api_connection', + status: 'fail', + message: `Health check failed: ${getErrorMessage(error)}`, + }, + ], + }; + } + } + + async checkHealth?(): Promise { + return this.healthCheck(); + } + + async getCapabilities?(): Promise { + return { + supportsOrderCapture: true, + supportsShipping: true, + supportsCustomFields: true, + maxBatchSize: 50, + }; + } + + async updateConfig?(config: AdapterConfig): Promise { + this.updateOptions(config.options ?? {}); + } + + // Order operations (delegated to OrderService) + + async createSalesOrder(input: CreateSalesOrderInput): Promise { + return this.orderService.createSalesOrder(input); + } + + async cancelOrder(input: CancelOrderInput): Promise { + return this.orderService.cancelOrder(input); + } + + async updateOrder(input: UpdateOrderInput): Promise { + return this.orderService.updateOrder(input); + } + + async getOrders(input: GetOrdersInput): Promise> { + return this.orderService.getOrders(input); + } + + // Fulfillment operations (delegated to FulfillmentService) + + async fulfillOrder(input: FulfillOrderInput): Promise> { + return this.fulfillmentService.fulfillOrder(input); + } + + async getFulfillments( + input: GetFulfillmentsInput + ): Promise> { + return this.fulfillmentService.getFulfillments(input); + } + + // Customer operations (delegated to CustomerService) + + async getCustomers(input: GetCustomersInput): Promise> { + return this.customerService.getCustomers(input); + } + + // Product operations (delegated to ProductService) + + async getProducts(input: GetProductsInput): Promise> { + return this.productService.getProducts(input); + } + + async getProductVariants( + input: GetProductVariantsInput + ): Promise> { + return this.productService.getProductVariants(input); + } + + async getInventory(input: GetInventoryInput): Promise> { + return this.productService.getInventory(input); + } + + // Return operations (delegated to ReturnService) + + async createReturn(input: CreateReturnInput): Promise { + return this.returnService.createReturn(input); + } + + async getReturns(input: GetReturnsInput): Promise> { + return this.returnService.getReturns(input); + } + + // Private helper methods + + private updateOptions(options: Partial): void { + if (!options) { + return; + } + + this.options = { + ...this.options, + ...options, + }; + + if (options.apiKey) { + this.client.updateApiKey(options.apiKey); + } + + if (typeof options.debugMode === 'boolean') { + this.client.setDebugMode(options.debugMode); + } + + // Update tenant ID in all services + const tenantId = this.getTenantId(); + this.orderService.setTenantId(tenantId); + this.customerService.setTenantId(tenantId); + this.fulfillmentService.setTenantId(tenantId); + this.productService.setTenantId(tenantId); + + if (options.workspace) { + this.orderService.setWorkspace(options.workspace); + } + } + + private getTenantId(): string { + return this.options.workspace ?? 'default-workspace'; + } + + private now(): string { + return new Date().toISOString(); + } +} diff --git a/virtocommerce-adapter/src/index.ts b/virtocommerce-adapter/src/index.ts new file mode 100644 index 0000000..76b2ebf --- /dev/null +++ b/virtocommerce-adapter/src/index.ts @@ -0,0 +1,26 @@ +/** + * Main entry point for VirtoCommerce adapter + * + * The adapter factory expects a default export of the adapter class. + * Additional exports are optional for direct usage. + */ + +// Default export for the adapter factory to load +import { VirtoCommerceFulfillmentAdapter } from './adapter.js'; +export default VirtoCommerceFulfillmentAdapter; + +// Named export for direct usage +export { VirtoCommerceFulfillmentAdapter } from './adapter.js'; + +// Export types +export type { AdapterOptions } from './types.js'; +export { ErrorCode, STATUS_MAP } from './types.js'; + +// Export transformers for extensibility +export * from './transformers/index.js'; + +// Export services for extensibility +export * from './services/index.js'; + +// Export mappers for extensibility +export * from './mappers/index.js'; diff --git a/virtocommerce-adapter/src/mappers/filter.mappers.ts b/virtocommerce-adapter/src/mappers/filter.mappers.ts new file mode 100644 index 0000000..d72a39a --- /dev/null +++ b/virtocommerce-adapter/src/mappers/filter.mappers.ts @@ -0,0 +1,125 @@ +/** + * Filter mapping utilities + * Convert MCP query input filters to VirtoCommerce API query parameters + */ + +import type { + GetOrdersInput, + GetInventoryInput, + GetProductsInput, + GetProductVariantsInput, + GetCustomersInput, + GetFulfillmentsInput, + GetReturnsInput, +} from '@cof-org/mcp'; + +/** + * Map GetOrdersInput to API query parameters + */ +export function mapOrderFilters(input: GetOrdersInput): Record { + return { + ids: input.ids, + external_ids: input.externalIds, + statuses: input.statuses, + updated_at_min: input.updatedAtMin, + updated_at_max: input.updatedAtMax, + created_at_min: input.createdAtMin, + created_at_max: input.createdAtMax, + limit: input.pageSize, + offset: input.skip, + }; +} + +/** + * Map GetInventoryInput to API query parameters + */ +export function mapInventoryFilters(input: GetInventoryInput): Record { + return { + skus: input.skus, + location_ids: input.locationIds, + }; +} + +/** + * Map GetProductsInput to API query parameters + */ +export function mapProductFilters(input: GetProductsInput): Record { + return { + ids: input.ids, + skus: input.skus, + updated_at_min: input.updatedAtMin, + updated_at_max: input.updatedAtMax, + created_at_min: input.createdAtMin, + created_at_max: input.createdAtMax, + limit: input.pageSize, + offset: input.skip, + }; +} + +/** + * Map GetProductVariantsInput to API query parameters + */ +export function mapProductVariantFilters(input: GetProductVariantsInput): Record { + return { + ids: input.ids, + skus: input.skus, + product_ids: input.productIds, + updated_at_min: input.updatedAtMin, + updated_at_max: input.updatedAtMax, + created_at_min: input.createdAtMin, + created_at_max: input.createdAtMax, + limit: input.pageSize, + offset: input.skip, + }; +} + +/** + * Map GetCustomersInput to API query parameters + */ +export function mapCustomerFilters(input: GetCustomersInput): Record { + return { + ids: input.ids, + emails: input.emails, + updated_at_min: input.updatedAtMin, + updated_at_max: input.updatedAtMax, + created_at_min: input.createdAtMin, + created_at_max: input.createdAtMax, + limit: input.pageSize, + offset: input.skip, + }; +} + +/** + * Map GetFulfillmentsInput to API query parameters + */ +export function mapFulfillmentFilters(input: GetFulfillmentsInput): Record { + return { + ids: input.ids, + order_ids: input.orderIds, + updated_at_min: input.updatedAtMin, + updated_at_max: input.updatedAtMax, + created_at_min: input.createdAtMin, + created_at_max: input.createdAtMax, + limit: input.pageSize, + offset: input.skip, + }; +} + +/** + * Map GetReturnsInput to API query parameters + */ +export function mapReturnFilters(input: GetReturnsInput): Record { + return { + ids: input.ids, + order_ids: input.orderIds, + return_numbers: input.returnNumbers, + statuses: input.statuses, + outcomes: input.outcomes, + updated_at_min: input.updatedAtMin, + updated_at_max: input.updatedAtMax, + created_at_min: input.createdAtMin, + created_at_max: input.createdAtMax, + limit: input.pageSize, + offset: input.skip, + }; +} diff --git a/virtocommerce-adapter/src/mappers/index.ts b/virtocommerce-adapter/src/mappers/index.ts new file mode 100644 index 0000000..fd7689f --- /dev/null +++ b/virtocommerce-adapter/src/mappers/index.ts @@ -0,0 +1,14 @@ +/** + * Mappers module + * Handles filter and query parameter mapping + */ + +export { + mapOrderFilters, + mapInventoryFilters, + mapProductFilters, + mapProductVariantFilters, + mapCustomerFilters, + mapFulfillmentFilters, + mapReturnFilters, +} from './filter.mappers.js'; diff --git a/virtocommerce-adapter/src/models/customer-order.ts b/virtocommerce-adapter/src/models/customer-order.ts new file mode 100644 index 0000000..8c79dc7 --- /dev/null +++ b/virtocommerce-adapter/src/models/customer-order.ts @@ -0,0 +1,623 @@ +export interface CustomerOrderSearchResult { + totalCount: number; + results: CustomerOrder[]; +} + +export interface CustomerOrder { + rowVersion: string; + customerId: string; + customerName: string; + channelId: string; + storeId: string; + storeName: string; + organizationId: string; + organizationName: string; + employeeId: string; + employeeName: string; + shoppingCartId: string; + isPrototype: boolean; + purchaseOrderNumber: string; + subscriptionNumber: string; + subscriptionId: string; + objectType: string; + addresses: Address[]; + inPayments: PaymentIn[]; + items: LineItem[]; + shipments: Shipment[]; + feeDetails: FeeDetail[]; + relevanceScore: number; + discounts: Discount[]; + discountAmount: number; + taxDetails: TaxDetail[]; + scopes: string[]; + total: number; + subTotal: number; + subTotalWithTax: number; + subTotalDiscount: number; + subTotalDiscountWithTax: number; + subTotalTaxTotal: number; + shippingTotal: number; + shippingTotalWithTax: number; + shippingSubTotal: number; + shippingSubTotalWithTax: number; + shippingDiscountTotal: number; + shippingDiscountTotalWithTax: number; + shippingTaxTotal: number; + paymentTotal: number; + paymentTotalWithTax: number; + paymentSubTotal: number; + paymentSubTotalWithTax: number; + paymentDiscountTotal: number; + paymentDiscountTotalWithTax: number; + paymentTaxTotal: number; + discountTotal: number; + discountTotalWithTax: number; + fee: number; + feeWithTax: number; + feeTotal: number; + feeTotalWithTax: number; + handlingTotal: number; + handlingTotalWithTax: number; + isAnonymous: boolean; + taxType: string; + taxTotal: number; + taxPercentRate: number; + languageCode: string; + operationType: string; + parentOperationId: string; + number: string; + isApproved: boolean; + status: string; + comment: string; + currency: string; + sum: number; + outerId: string; + cancelledState: string; + isCancelled: boolean; + cancelledDate: string; + cancelReason: string; + dynamicProperties: DynamicProperty[]; + operationsLog: OperationsLog[]; + createdDate: string; + modifiedDate: string; + createdBy: string; + modifiedBy: string; + id: string; +} + +export interface Shipment { + organizationId: string; + organizationName: string; + fulfillmentCenterId: string; + fulfillmentCenterName: string; + employeeId: string; + employeeName: string; + shipmentMethodCode: string; + shipmentMethodOption: string; + shippingMethod: ShippingMethod; + customerOrderId: string; + customerOrder: string; + items: ShipmentItem[]; + packages: ShipmentPackage[]; + inPayments: PaymentIn[]; + feeDetails: FeeDetail[]; + weightUnit: string; + weight: number; + measureUnit: string; + height: number; + length: number; + width: number; + discounts: Discount[]; + deliveryAddress: Address; + price: number; + priceWithTax: number; + total: number; + totalWithTax: number; + discountAmount: number; + discountAmountWithTax: number; + pickupLocationId: string; + fee: number; + feeWithTax: number; + trackingNumber: string; + trackingUrl: string; + deliveryDate: string; + objectType: string; + vendorId: string; + taxType: string; + taxTotal: number; + taxPercentRate: number; + taxDetails: TaxDetail[]; + operationType: string; + parentOperationId: string; + number: string; + isApproved: boolean; + status: string; + comment: string; + currency: string; + sum: number; + outerId: string; + cancelledState: string; + isCancelled: boolean; + cancelledDate: string; + cancelReason: string; + dynamicProperties: DynamicProperty[]; + operationsLog: OperationsLog[]; + createdDate: string; + modifiedDate: string; + createdBy: string; + modifiedBy: string; + id: string; +} + +export interface ShipmentPackage { + barCode: string; + packageType: string; + items: ShipmentItem[]; + weightUnit: string; + weight: number; + measureUnit: string; + height: number; + length: number; + width: number; + createdDate: string; + modifiedDate: string; + createdBy: string; + modifiedBy: string; + id: string; +} + +export interface ShipmentItem { + lineItemId: string; + lineItem: LineItem; + barCode: string; + quantity: number; + outerId: string; + status: string; + createdDate: string; + modifiedDate: string; + createdBy: string; + modifiedBy: string; + id: string; +} + +export interface ShippingMethod { + code: string; + name: string; + description: string; + logoUrl: string; + isActive: boolean; + priority: number; + taxType: string; + storeId: string; + typeName: string; + id: string; +} + +export interface PaymentIn { + orderId: string; + purpose: string; + gatewayCode: string; + paymentMethod: PaymentMethod; + organizationId: string; + organizationName: string; + customerId: string; + customerName: string; + incomingDate: string; + billingAddress: Address; + paymentStatus: string; + authorizedDate: string; + capturedDate: string; + voidedDate: string; + processPaymentResult: ProcessPaymentResult; + price: number; + priceWithTax: number; + total: number; + totalWithTax: number; + discountAmount: number; + discountAmountWithTax: number; + objectType: string; + feeDetails: FeeDetail[]; + vendorId: string; + taxType: string; + taxTotal: number; + taxPercentRate: number; + taxDetails: TaxDetail[]; + discounts: Discount[]; + transactions: Transaction[]; + refunds: Refund[]; + captures: Capture[]; + operationType: string; + parentOperationId: string; + number: string; + isApproved: boolean; + status: string; + comment: string; + currency: string; + sum: number; + outerId: string; + cancelledState: string; + isCancelled: boolean; + cancelledDate: string; + cancelReason: string; + dynamicProperties: DynamicProperty[]; + operationsLog: OperationsLog[]; + createdDate: string; + modifiedDate: string; + createdBy: string; + modifiedBy: string; + id: string; +} + +export interface Capture { + objectType: string; + amount: number; + vendorId: string; + transactionId: string; + customerOrderId: string; + paymentId: string; + items: CaptureItem[]; + closeTransaction: boolean; + operationType: string; + parentOperationId: string; + number: string; + isApproved: boolean; + status: string; + comment: string; + currency: string; + sum: number; + outerId: string; + cancelledState: string; + isCancelled: boolean; + cancelledDate: string; + cancelReason: string; + dynamicProperties: DynamicProperty[]; + operationsLog: OperationsLog[]; + createdDate: string; + modifiedDate: string; + createdBy: string; + modifiedBy: string; + id: string; +} + +export interface CaptureItem { + quantity: number; + lineItemId: string; + lineItem: LineItem; + captureId: string; + outerId: string; + createdDate: string; + modifiedDate: string; + createdBy: string; + modifiedBy: string; + id: string; +} + +export interface Refund { + objectType: string; + amount: number; + reasonCode: string; + refundStatus: string; + reasonMessage: string; + rejectReasonMessage: string; + vendorId: string; + transactionId: string; + customerOrderId: string; + paymentId: string; + items: RefundItem[]; + operationType: string; + parentOperationId: string; + number: string; + isApproved: boolean; + status: string; + comment: string; + currency: string; + sum: number; + outerId: string; + cancelledState: string; + isCancelled: boolean; + cancelledDate: string; + cancelReason: string; + dynamicProperties: DynamicProperty[]; + operationsLog: OperationsLog[]; + createdDate: string; + modifiedDate: string; + createdBy: string; + modifiedBy: string; + id: string; +} + +export interface OperationsLog { + objectType: string; + objectId: string; + operationType: string; + detail: string; + createdDate: string; + modifiedDate: string; + createdBy: string; + modifiedBy: string; + id: string; +} + +export interface RefundItem { + quantity: number; + lineItemId: string; + lineItem: LineItem; + refundId: string; + outerId: string; + createdDate: string; + modifiedDate: string; + createdBy: string; + modifiedBy: string; + id: string; +} + +export interface LineItem { + priceId: string; + currency: string; + price: number; + priceWithTax: number; + listTotal: number; + listTotalWithTax: number; + placedPrice: number; + placedPriceWithTax: number; + extendedPrice: number; + extendedPriceWithTax: number; + discountAmount: number; + isDiscountAmountRounded: boolean; + discountAmountWithTax: number; + discountTotal: number; + discountTotalWithTax: number; + fee: number; + feeWithTax: number; + taxType: string; + taxTotal: number; + taxPercentRate: number; + reserveQuantity: number; + quantity: number; + productId: string; + sku: string; + productType: string; + catalogId: string; + categoryId: string; + name: string; + productOuterId: string; + comment: string; + status: string; + imageUrl: string; + isGift: boolean; + shippingMethodCode: string; + fulfillmentLocationCode: string; + fulfillmentCenterId: string; + fulfillmentCenterName: string; + outerId: string; + feeDetails: FeeDetail[]; + vendorId: string; + isConfigured: boolean; + weightUnit: string; + weight: number; + measureUnit: string; + height: number; + length: number; + width: number; + isCancelled: boolean; + cancelledDate: string; + cancelReason: string; + objectType: string; + dynamicProperties: DynamicProperty[]; + discounts: Discount[]; + taxDetails: TaxDetail[]; + configurationItems: ConfigurationItem[]; + createdDate: string; + modifiedDate: string; + createdBy: string; + modifiedBy: string; + id: string; +} + +export interface ConfigurationItem { + productId: string; + name: string; + sku: string; + quantity: number; + imageUrl: string; + catalogId: string; + categoryId: string; + type: string; + customText: string; + files: File[]; + createdDate: string; + modifiedDate: string; + createdBy: string; + modifiedBy: string; + id: string; +} + +export interface File { + name: string; + url: string; + contentType: string; + size: number; + createdDate: string; + modifiedDate: string; + createdBy: string; + modifiedBy: string; + id: string; +} + +export interface DynamicProperty { + objectId: string; + values: DynamicPropertyValue[]; + name: string; + description: string; + objectType: string; + isArray: boolean; + isDictionary: boolean; + isMultilingual: boolean; + isRequired: boolean; + displayOrder: number; + valueType: string; + displayNames: DisplayName[]; + createdDate: string; + modifiedDate: string; + createdBy: string; + modifiedBy: string; + id: string; +} + +export interface DisplayName { + locale: string; + name: string; +} + +export interface DynamicPropertyValue { + objectType: string; + objectId: string; + locale: string; + value: any; + valueId: string; + valueType: string; + propertyId: string; + propertyName: string; +} + +export interface Transaction { + amount: number; + currencyCode: string; + isProcessed: boolean; + processedDate: string; + processError: string; + processAttemptCount: number; + requestData: string; + responseData: string; + responseCode: string; + gatewayIpAddress: string; + type: string; + status: string; + note: string; + createdDate: string; + modifiedDate: string; + createdBy: string; + modifiedBy: string; + id: string; +} + +export interface Discount { + promotionId: string; + currency: string; + discountAmount: number; + discountAmountWithTax: number; + coupon: string; + description: string; + name: string; + id: string; +} + +export interface FeeDetail { + feeId: string; + currency: string; + amount: number; + description: string; +} + +export interface ProcessPaymentResult { + redirectUrl: string; + htmlForm: string; + outerId: string; + paymentMethod: PaymentMethod; + isSuccess: boolean; + errorMessage: string; + newPaymentStatus: string; + publicParameters: Values; +} + +export interface PaymentMethod { + code: string; + name: string; + logoUrl: string; + isActive: boolean; + priority: number; + isAvailableForPartial: boolean; + allowDeferredPayment: boolean; + currency: string; + price: number; + priceWithTax: number; + total: number; + totalWithTax: number; + discountAmount: number; + discountAmountWithTax: number; + allowCartPayment: boolean; + storeId: string; + description: string; + typeName: string; + settings: Setting[]; + taxType: string; + taxTotal: number; + taxPercentRate: number; + taxDetails: TaxDetail[]; + localizedName: LocalizedName; + paymentMethodType: string; + paymentMethodGroupType: string; + id: string; +} + +export interface LocalizedName { + values: Values; +} + +export interface Values { + additionalProp1: string; + additionalProp2: string; + additionalProp3: string; +} + +export interface TaxDetail { + rate: number; + amount: number; + name: string; +} + +export interface Setting { + itHasValues: boolean; + objectId: string; + objectType: string; + isReadOnly: boolean; + value: any; + id: string; + restartRequired: boolean; + moduleId: string; + groupName: string; + name: string; + displayName: string; + isRequired: boolean; + isHidden: boolean; + isPublic: boolean; + valueType: string; + allowedValues: any[]; + defaultValue: any; + isDictionary: boolean; + isLocalizable: boolean; +} + +export interface Address { + addressType: string; + key: string; + name: string; + organization: string; + countryCode: string; + countryName: string; + city: string; + postalCode: string; + zip: string; + line1: string; + line2: string; + regionId: string; + regionName: string; + firstName: string; + middleName: string; + lastName: string; + phone: string; + email: string; + outerId: string; + isDefault: boolean; + description: string; +} \ No newline at end of file diff --git a/virtocommerce-adapter/src/models/index.ts b/virtocommerce-adapter/src/models/index.ts new file mode 100644 index 0000000..7f2f998 --- /dev/null +++ b/virtocommerce-adapter/src/models/index.ts @@ -0,0 +1 @@ +export * from './customer-order.js'; diff --git a/virtocommerce-adapter/src/services/base.service.ts b/virtocommerce-adapter/src/services/base.service.ts new file mode 100644 index 0000000..1abe110 --- /dev/null +++ b/virtocommerce-adapter/src/services/base.service.ts @@ -0,0 +1,35 @@ +/** + * Base service with shared utilities for all domain services + */ + +import type { FulfillmentToolResult } from '@cof-org/mcp'; +import { AdapterError } from '@cof-org/mcp'; +import { ErrorCode } from '../types.js'; +import { ApiClient } from '../utils/api-client.js'; + +export abstract class BaseService { + protected client: ApiClient; + + constructor(client: ApiClient) { + this.client = client; + } + + protected success(payload: T): FulfillmentToolResult { + return { success: true, ...payload } as FulfillmentToolResult; + } + + protected failure(message: string, error?: unknown): FulfillmentToolResult { + return { + success: false, + error: error ?? new AdapterError(message, ErrorCode.API_ERROR, { message }), + message, + } as FulfillmentToolResult; + } + + protected ensureArray(data: T | T[] | undefined | null): T[] { + if (!data) { + return []; + } + return Array.isArray(data) ? data : [data]; + } +} diff --git a/virtocommerce-adapter/src/services/customer.service.ts b/virtocommerce-adapter/src/services/customer.service.ts new file mode 100644 index 0000000..5117915 --- /dev/null +++ b/virtocommerce-adapter/src/services/customer.service.ts @@ -0,0 +1,48 @@ +/** + * Customer service - handles customer-related operations + */ + +import type { Customer, FulfillmentToolResult, GetCustomersInput } from '@cof-org/mcp'; +import type { YourFulfillmentCustomer } from '../types.js'; +import { BaseService } from './base.service.js'; +import { CustomerTransformer } from '../transformers/customer.transformer.js'; +import { mapCustomerFilters } from '../mappers/filter.mappers.js'; +import { getErrorMessage } from '../utils/type-guards.js'; +import { ApiClient } from '../utils/api-client.js'; + +export class CustomerService extends BaseService { + private transformer: CustomerTransformer; + + constructor(client: ApiClient, tenantId: string = 'default-workspace') { + super(client); + this.transformer = new CustomerTransformer(tenantId); + } + + setTenantId(tenantId: string): void { + this.transformer.setTenantId(tenantId); + } + + async getCustomers(input: GetCustomersInput): Promise> { + try { + const response = await this.client.get( + '/customers', + mapCustomerFilters(input) + ); + + if (!response.success) { + return this.failure<{ customers: Customer[] }>( + 'Failed to fetch customers', + response.error ?? response + ); + } + + const customers = this.transformer.toMcpCustomers(this.ensureArray(response.data)); + return this.success<{ customers: Customer[] }>({ customers }); + } catch (error: unknown) { + return this.failure<{ customers: Customer[] }>( + `Customer lookup failed: ${getErrorMessage(error)}`, + error + ); + } + } +} diff --git a/virtocommerce-adapter/src/services/fulfillment.service.ts b/virtocommerce-adapter/src/services/fulfillment.service.ts new file mode 100644 index 0000000..ab42014 --- /dev/null +++ b/virtocommerce-adapter/src/services/fulfillment.service.ts @@ -0,0 +1,86 @@ +/** + * Fulfillment service - handles fulfillment/shipment-related operations + */ + +import type { + Fulfillment, + FulfillmentToolResult, + FulfillOrderInput, + GetFulfillmentsInput, +} from '@cof-org/mcp'; +import type { YourFulfillmentShipment } from '../types.js'; +import { BaseService } from './base.service.js'; +import { FulfillmentTransformer } from '../transformers/fulfillment.transformer.js'; +import { mapFulfillmentFilters } from '../mappers/filter.mappers.js'; +import { getErrorMessage } from '../utils/type-guards.js'; +import { ApiClient } from '../utils/api-client.js'; + +export class FulfillmentService extends BaseService { + private transformer: FulfillmentTransformer; + + constructor(client: ApiClient, tenantId: string = 'default-workspace') { + super(client); + this.transformer = new FulfillmentTransformer(tenantId); + } + + setTenantId(tenantId: string): void { + this.transformer.setTenantId(tenantId); + } + + async fulfillOrder( + input: FulfillOrderInput + ): Promise> { + if (!input.orderId) { + return this.failure<{ fulfillment: Fulfillment }>('orderId is required to fulfill an order'); + } + + try { + const response = await this.client.post( + `/orders/${input.orderId}/shipments`, + this.transformer.fromFulfillOrderInput(input) + ); + + if (!response.success || !response.data) { + return this.failure<{ fulfillment: Fulfillment }>( + 'Failed to create fulfillment', + response.error ?? response + ); + } + + return this.success<{ fulfillment: Fulfillment }>({ + fulfillment: this.transformer.toMcpFulfillment(response.data), + }); + } catch (error: unknown) { + return this.failure<{ fulfillment: Fulfillment }>( + `Fulfillment failed: ${getErrorMessage(error)}`, + error + ); + } + } + + async getFulfillments( + input: GetFulfillmentsInput + ): Promise> { + try { + const response = await this.client.get( + '/shipments', + mapFulfillmentFilters(input) + ); + + if (!response.success) { + return this.failure<{ fulfillments: Fulfillment[] }>( + 'Failed to fetch fulfillments', + response.error ?? response + ); + } + + const fulfillments = this.transformer.toMcpFulfillments(this.ensureArray(response.data)); + return this.success<{ fulfillments: Fulfillment[] }>({ fulfillments }); + } catch (error: unknown) { + return this.failure<{ fulfillments: Fulfillment[] }>( + `Fulfillment lookup failed: ${getErrorMessage(error)}`, + error + ); + } + } +} diff --git a/virtocommerce-adapter/src/services/index.ts b/virtocommerce-adapter/src/services/index.ts new file mode 100644 index 0000000..b8e1c82 --- /dev/null +++ b/virtocommerce-adapter/src/services/index.ts @@ -0,0 +1,11 @@ +/** + * Services module + * Domain-specific operations for the VirtoCommerce adapter + */ + +export { BaseService } from './base.service.js'; +export { OrderService } from './order.service.js'; +export { CustomerService } from './customer.service.js'; +export { FulfillmentService } from './fulfillment.service.js'; +export { ProductService } from './product.service.js'; +export { ReturnService } from './return.service.js'; diff --git a/virtocommerce-adapter/src/services/order.service.ts b/virtocommerce-adapter/src/services/order.service.ts new file mode 100644 index 0000000..f8af291 --- /dev/null +++ b/virtocommerce-adapter/src/services/order.service.ts @@ -0,0 +1,139 @@ +/** + * Order service - handles order-related operations + */ + +import type { + Order, + OrderResult, + FulfillmentToolResult, + CreateSalesOrderInput, + CancelOrderInput, + UpdateOrderInput, + GetOrdersInput, +} from '@cof-org/mcp'; +import type { YourFulfillmentOrder, YourFulfillmentApiResponse } from '../types.js'; +import type { CustomerOrder, CustomerOrderSearchResult } from '../models/customer-order.js'; +import { BaseService } from './base.service.js'; +import { OrderTransformer } from '../transformers/order.transformer.js'; +import { mapOrderFilters } from '../mappers/filter.mappers.js'; +import { getErrorMessage } from '../utils/type-guards.js'; +import { ApiClient } from '../utils/api-client.js'; + +export class OrderService extends BaseService { + private transformer: OrderTransformer; + + constructor(client: ApiClient, tenantId: string = 'default-workspace', workspace?: string) { + super(client); + this.transformer = new OrderTransformer(tenantId, workspace); + } + + setTenantId(tenantId: string): void { + this.transformer.setTenantId(tenantId); + } + + setWorkspace(workspace: string): void { + this.transformer.setWorkspace(workspace); + } + + async createSalesOrder(input: CreateSalesOrderInput): Promise { + try { + const payload = this.transformer.fromCreateSalesOrderInput(input); + const response = await this.client.post('/orders', payload); + + if (!response.success || !response.data) { + return this.failure<{ order: Order }>('Failed to create order', response.error ?? response); + } + + return this.success<{ order: Order }>({ + order: this.transformer.toMcpOrder(response.data as unknown as CustomerOrder), + }); + } catch (error: unknown) { + return this.failure<{ order: Order }>(`Order creation failed: ${getErrorMessage(error)}`, error); + } + } + + async cancelOrder(input: CancelOrderInput): Promise { + if (!input.orderId) { + return this.failure<{ order: Order }>('orderId is required to cancel an order'); + } + + try { + const response = await this.client.post(`/orders/${input.orderId}/cancel`, { + reason: input.reason ?? 'Customer requested cancellation', + notify_customer: input.notifyCustomer ?? false, + notes: input.notes, + cancelled_at: new Date().toISOString(), + }); + + if (!response.success) { + return this.failure<{ order: Order }>('Failed to cancel order', response.error ?? response); + } + + const orderData = response.data ?? (await this.fetchOrderById(input.orderId)).data; + + if (!orderData) { + return this.failure<{ order: Order }>('Order not found after cancellation', { + orderId: input.orderId, + }); + } + + return this.success<{ order: Order }>({ + order: this.transformer.toMcpOrder(orderData as unknown as CustomerOrder), + }); + } catch (error: unknown) { + return this.failure<{ order: Order }>( + `Order cancellation failed: ${getErrorMessage(error)}`, + error + ); + } + } + + async updateOrder(input: UpdateOrderInput): Promise { + try { + const response = await this.client.patch( + `/orders/${input.id}`, + this.transformer.fromUpdateOrderInput(input.updates) + ); + + if (!response.success) { + return this.failure<{ order: Order }>('Failed to update order', response.error ?? response); + } + + const orderData = response.data ?? (await this.fetchOrderById(input.id)).data; + + if (!orderData) { + return this.failure<{ order: Order }>('Order not found after update', { orderId: input.id }); + } + + return this.success<{ order: Order }>({ + order: this.transformer.toMcpOrder(orderData as unknown as CustomerOrder), + }); + } catch (error: unknown) { + return this.failure<{ order: Order }>(`Order update failed: ${getErrorMessage(error)}`, error); + } + } + + async getOrders(input: GetOrdersInput): Promise> { + try { + const response = await this.client.post( + '/api/order/customerOrders/search', + {}, + mapOrderFilters(input) + ); + + if (!response.success) { + return this.failure<{ orders: Order[] }>('Failed to fetch orders', response.error ?? response); + } + + const results = (response.data as CustomerOrderSearchResult)?.results ?? []; + const orders = this.transformer.toMcpOrders(results); + return this.success<{ orders: Order[] }>({ orders }); + } catch (error: unknown) { + return this.failure<{ orders: Order[] }>(`Order lookup failed: ${getErrorMessage(error)}`, error); + } + } + + private async fetchOrderById(orderId: string): Promise> { + return this.client.get(`/orders/${orderId}`); + } +} diff --git a/virtocommerce-adapter/src/services/product.service.ts b/virtocommerce-adapter/src/services/product.service.ts new file mode 100644 index 0000000..e661189 --- /dev/null +++ b/virtocommerce-adapter/src/services/product.service.ts @@ -0,0 +1,112 @@ +/** + * Product service - handles product, variant, and inventory operations + */ + +import type { + Product, + ProductVariant, + InventoryItem, + FulfillmentToolResult, + GetProductsInput, + GetProductVariantsInput, + GetInventoryInput, +} from '@cof-org/mcp'; +import type { YourFulfillmentProduct, YourFulfillmentInventory } from '../types.js'; +import { BaseService } from './base.service.js'; +import { ProductTransformer } from '../transformers/product.transformer.js'; +import { + mapProductFilters, + mapProductVariantFilters, + mapInventoryFilters, +} from '../mappers/filter.mappers.js'; +import { getErrorMessage } from '../utils/type-guards.js'; +import { ApiClient } from '../utils/api-client.js'; + +export class ProductService extends BaseService { + private transformer: ProductTransformer; + + constructor(client: ApiClient, tenantId: string = 'default-workspace') { + super(client); + this.transformer = new ProductTransformer(tenantId); + } + + setTenantId(tenantId: string): void { + this.transformer.setTenantId(tenantId); + } + + async getProducts(input: GetProductsInput): Promise> { + try { + const response = await this.client.get( + '/products', + mapProductFilters(input) + ); + + if (!response.success) { + return this.failure<{ products: Product[] }>( + 'Failed to fetch products', + response.error ?? response + ); + } + + const products = this.transformer.toMcpProducts(this.ensureArray(response.data)); + return this.success<{ products: Product[] }>({ products }); + } catch (error: unknown) { + return this.failure<{ products: Product[] }>( + `Product lookup failed: ${getErrorMessage(error)}`, + error + ); + } + } + + async getProductVariants( + input: GetProductVariantsInput + ): Promise> { + try { + const response = await this.client.get( + '/products', + mapProductVariantFilters(input) + ); + + if (!response.success) { + return this.failure<{ productVariants: ProductVariant[] }>( + 'Failed to fetch product variants', + response.error ?? response + ); + } + + const productVariants = this.transformer.toMcpProductVariants(this.ensureArray(response.data)); + return this.success<{ productVariants: ProductVariant[] }>({ productVariants }); + } catch (error: unknown) { + return this.failure<{ productVariants: ProductVariant[] }>( + `Product variant lookup failed: ${getErrorMessage(error)}`, + error + ); + } + } + + async getInventory( + input: GetInventoryInput + ): Promise> { + try { + const response = await this.client.get( + '/inventory', + mapInventoryFilters(input) + ); + + if (!response.success) { + return this.failure<{ inventory: InventoryItem[] }>( + 'Failed to fetch inventory', + response.error ?? response + ); + } + + const inventory = this.transformer.toMcpInventory(this.ensureArray(response.data)); + return this.success<{ inventory: InventoryItem[] }>({ inventory }); + } catch (error: unknown) { + return this.failure<{ inventory: InventoryItem[] }>( + `Inventory lookup failed: ${getErrorMessage(error)}`, + error + ); + } + } +} diff --git a/virtocommerce-adapter/src/services/return.service.ts b/virtocommerce-adapter/src/services/return.service.ts new file mode 100644 index 0000000..0a2af30 --- /dev/null +++ b/virtocommerce-adapter/src/services/return.service.ts @@ -0,0 +1,77 @@ +/** + * Return service - handles return-related operations + */ + +import type { + Return, + ReturnResult, + FulfillmentToolResult, + CreateReturnInput, + GetReturnsInput, +} from '@cof-org/mcp'; +import { BaseService } from './base.service.js'; +import { mapReturnFilters } from '../mappers/filter.mappers.js'; +import { getErrorMessage } from '../utils/type-guards.js'; +import { ApiClient } from '../utils/api-client.js'; + +export class ReturnService extends BaseService { + constructor(client: ApiClient) { + super(client); + } + + async createReturn(input: CreateReturnInput): Promise { + try { + const payload = { + order_id: input.return.orderId, + return_number: input.return.returnNumber, + status: input.return.status, + outcome: input.return.outcome, + items: input.return.returnLineItems?.map((item) => ({ + sku: item.sku, + quantity: item.quantityReturned, + reason: item.returnReason, + refund_amount: item.refundAmount, + })), + }; + + const response = await this.client.post('/returns', payload); + + if (!response.success || !response.data) { + return this.failure<{ return: Return }>('Failed to create return', response.error ?? response); + } + + // TODO: Transform the response to Return type when VirtoCommerce return API is defined + return this.failure<{ return: Return }>( + 'createReturn not yet implemented - please implement transformation logic' + ); + } catch (error: unknown) { + return this.failure<{ return: Return }>( + `Return creation failed: ${getErrorMessage(error)}`, + error + ); + } + } + + async getReturns(input: GetReturnsInput): Promise> { + try { + const response = await this.client.get('/returns', mapReturnFilters(input)); + + if (!response.success) { + return this.failure<{ returns: Return[] }>( + 'Failed to fetch returns', + response.error ?? response + ); + } + + // TODO: Transform the response to Return[] type when VirtoCommerce return API is defined + return this.failure<{ returns: Return[] }>( + 'getReturns not yet implemented - please implement transformation logic' + ); + } catch (error: unknown) { + return this.failure<{ returns: Return[] }>( + `Return lookup failed: ${getErrorMessage(error)}`, + error + ); + } + } +} diff --git a/virtocommerce-adapter/src/transformers/address.transformer.ts b/virtocommerce-adapter/src/transformers/address.transformer.ts new file mode 100644 index 0000000..dc8550d --- /dev/null +++ b/virtocommerce-adapter/src/transformers/address.transformer.ts @@ -0,0 +1,81 @@ +/** + * Address transformation utilities + */ + +import type { Address, CustomerAddress } from '@cof-org/mcp'; +import type { YourFulfillmentAddress } from '../types.js'; +import type { Address as VirtoAddress } from '../models/customer-order.js'; +import { BaseTransformer } from './base.js'; + +export class AddressTransformer extends BaseTransformer { + /** + * Transform MCP Address to VirtoCommerce address format + */ + toFulfillmentAddress(address?: Address): YourFulfillmentAddress | undefined { + if (!address) { + return undefined; + } + + return { + street1: address.address1 ?? '', + street2: address.address2, + city: address.city ?? '', + state: address.stateOrProvince ?? '', + postal_code: address.zipCodeOrPostalCode ?? '', + country: address.country ?? '', + phone: address.phone, + email: address.email, + name: this.composeName(address.firstName, address.lastName), + company: address.company, + }; + } + + /** + * Transform VirtoCommerce address to MCP Address format + */ + toMcpAddress(address?: VirtoAddress): Address | undefined { + if (!address) { + return undefined; + } + + const { firstName, lastName } = this.splitName(address.name); + + return { + address1: address.line1 ?? '', + address2: address.line2 ?? '', + city: address.city ?? '', + country: address.countryName ?? '', + email: address.email, + firstName, + lastName, + phone: address.phone, + stateOrProvince: address.regionName ?? '', + zipCodeOrPostalCode: address.postalCode ?? address.zip ?? '', + company: address.organization, + }; + } + + /** + * Transform VirtoCommerce addresses to MCP CustomerAddress array + */ + toCustomerAddresses(addresses?: VirtoAddress[]): CustomerAddress[] | undefined { + if (!addresses?.length) { + return undefined; + } + + const mapped = addresses + .map((addr) => { + const address = this.toMcpAddress(addr); + if (!address) { + return undefined; + } + return { + name: addr.name ?? this.composeName(address.firstName, address.lastName), + address, + }; + }) + .filter(Boolean) as CustomerAddress[]; + + return mapped.length ? mapped : undefined; + } +} diff --git a/virtocommerce-adapter/src/transformers/base.ts b/virtocommerce-adapter/src/transformers/base.ts new file mode 100644 index 0000000..514a3a7 --- /dev/null +++ b/virtocommerce-adapter/src/transformers/base.ts @@ -0,0 +1,58 @@ +/** + * Base transformer utilities shared across all transformers + */ + +export abstract class BaseTransformer { + protected tenantId: string; + + constructor(tenantId: string = 'default-workspace') { + this.tenantId = tenantId; + } + + setTenantId(tenantId: string): void { + this.tenantId = tenantId; + } + + protected now(): string { + return new Date().toISOString(); + } + + protected ensureArray(data: T | T[] | undefined | null): T[] { + if (!data) { + return []; + } + return Array.isArray(data) ? data : [data]; + } + + protected valueOrUndefined(value: T | null | undefined): T | undefined { + return value ?? undefined; + } + + protected composeName(firstName?: string | null, lastName?: string | null): string | undefined { + const parts = [firstName, lastName] + .filter((value): value is string => Boolean(value && value.trim())) + .map((value) => value.trim()); + + return parts.length ? parts.join(' ') : undefined; + } + + protected splitName(name?: string | null): { firstName?: string; lastName?: string } { + if (!name) { + return {}; + } + + const parts = name.trim().split(/\s+/); + if (!parts.length) { + return {}; + } + + if (parts.length === 1) { + return { firstName: parts[0] }; + } + + return { + firstName: parts.shift(), + lastName: parts.join(' '), + }; + } +} diff --git a/virtocommerce-adapter/src/transformers/customer.transformer.ts b/virtocommerce-adapter/src/transformers/customer.transformer.ts new file mode 100644 index 0000000..30daffe --- /dev/null +++ b/virtocommerce-adapter/src/transformers/customer.transformer.ts @@ -0,0 +1,69 @@ +/** + * Customer transformation utilities + */ + +import type { Customer } from '@cof-org/mcp'; +import type { YourFulfillmentCustomer, YourFulfillmentAddress } from '../types.js'; +import type { CustomerOrder } from '../models/customer-order.js'; +import { BaseTransformer } from './base.js'; +import { AddressTransformer } from './address.transformer.js'; + +export class CustomerTransformer extends BaseTransformer { + private addressTransformer: AddressTransformer; + + constructor(tenantId: string = 'default-workspace') { + super(tenantId); + this.addressTransformer = new AddressTransformer(tenantId); + } + + override setTenantId(tenantId: string): void { + super.setTenantId(tenantId); + this.addressTransformer.setTenantId(tenantId); + } + + /** + * Transform YourFulfillment customer format to MCP Customer + */ + toMcpCustomer(customer: YourFulfillmentCustomer): Customer { + return { + id: customer.id, + firstName: customer.first_name, + lastName: customer.last_name, + phone: customer.phone, + addresses: this.addressTransformer.toCustomerAddresses(customer.addresses as any), + tags: customer.tags, + createdAt: customer.created_at ?? this.now(), + updatedAt: customer.updated_at ?? this.now(), + tenantId: this.tenantId, + status: 'active', + type: 'customer', + }; + } + + /** + * Extract and transform customer from VirtoCommerce order + */ + fromOrder(order: CustomerOrder): Customer { + const customerData: YourFulfillmentCustomer = { + id: order.customerId, + email: order.customerName, // Using customerName as email fallback + first_name: '', + last_name: order.customerName, + phone: undefined, + created_at: order.createdDate, + updated_at: order.modifiedDate, + addresses: order.addresses as unknown as YourFulfillmentAddress[], + tags: [], + metadata: {}, + }; + + return this.toMcpCustomer(customerData); + } + + /** + * Transform multiple customers + */ + toMcpCustomers(customers: YourFulfillmentCustomer[]): Customer[] { + return customers.map((customer) => this.toMcpCustomer(customer)); + } +} diff --git a/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts b/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts new file mode 100644 index 0000000..dec3ee0 --- /dev/null +++ b/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts @@ -0,0 +1,77 @@ +/** + * Fulfillment/Shipment transformation utilities + */ + +import type { Fulfillment, FulfillOrderInput, Address } from '@cof-org/mcp'; +import type { YourFulfillmentShipment } from '../types.js'; +import { BaseTransformer } from './base.js'; +import { AddressTransformer } from './address.transformer.js'; + +export class FulfillmentTransformer extends BaseTransformer { + private addressTransformer: AddressTransformer; + + constructor(tenantId: string = 'default-workspace') { + super(tenantId); + this.addressTransformer = new AddressTransformer(tenantId); + } + + override setTenantId(tenantId: string): void { + super.setTenantId(tenantId); + this.addressTransformer.setTenantId(tenantId); + } + + /** + * Transform YourFulfillment shipment to MCP Fulfillment format + */ + toMcpFulfillment(shipment: YourFulfillmentShipment): Fulfillment { + return { + id: shipment.id, + externalId: shipment.tracking_number, + orderId: shipment.order_id, + trackingNumbers: shipment.tracking_number ? [shipment.tracking_number] : [], + shippingCarrier: shipment.carrier, + shippingClass: shipment.service, + status: shipment.status, + shippingAddress: this.addressTransformer.toMcpAddress(shipment.to_address as any), + lineItems: shipment.items.map((item, index) => ({ + id: `${shipment.id}-${item.sku}-${index}`, + sku: item.sku, + quantity: item.quantity, + })), + createdAt: shipment.shipped_at ?? this.now(), + updatedAt: shipment.delivered_at ?? shipment.shipped_at ?? this.now(), + tenantId: this.tenantId, + expectedDeliveryDate: shipment.delivered_at, + expectedShipDate: shipment.shipped_at, + shippingNote: shipment.tracking_url, + }; + } + + /** + * Transform multiple shipments + */ + toMcpFulfillments(shipments: YourFulfillmentShipment[]): Fulfillment[] { + return shipments.map((shipment) => this.toMcpFulfillment(shipment)); + } + + /** + * Transform FulfillOrderInput to API payload + */ + fromFulfillOrderInput(input: FulfillOrderInput): Record { + return { + tracking_number: input.trackingNumbers?.[0] ?? undefined, + carrier: input.shippingCarrier, + service: input.shippingClass, + location_id: input.locationId, + shipped_at: input.shipByDate ?? this.now(), + expected_delivery: input.expectedDeliveryDate, + items: input.lineItems?.map((item) => ({ + sku: item.sku, + quantity: item.quantity ?? 0, + })), + shipping_address: this.addressTransformer.toFulfillmentAddress(input.shippingAddress), + incoterms: input.incoterms, + notes: input.giftNote, + }; + } +} diff --git a/virtocommerce-adapter/src/transformers/index.ts b/virtocommerce-adapter/src/transformers/index.ts new file mode 100644 index 0000000..5e16983 --- /dev/null +++ b/virtocommerce-adapter/src/transformers/index.ts @@ -0,0 +1,11 @@ +/** + * Transformers module + * Handles conversion between VirtoCommerce API formats and MCP standard formats + */ + +export { BaseTransformer } from './base.js'; +export { AddressTransformer } from './address.transformer.js'; +export { CustomerTransformer } from './customer.transformer.js'; +export { OrderTransformer } from './order.transformer.js'; +export { FulfillmentTransformer } from './fulfillment.transformer.js'; +export { ProductTransformer } from './product.transformer.js'; diff --git a/virtocommerce-adapter/src/transformers/order.transformer.ts b/virtocommerce-adapter/src/transformers/order.transformer.ts new file mode 100644 index 0000000..403d615 --- /dev/null +++ b/virtocommerce-adapter/src/transformers/order.transformer.ts @@ -0,0 +1,210 @@ +/** + * Order transformation utilities + */ + +import type { + Order, + OrderLineItem, + CustomField, + Address, + CreateSalesOrderInput, + UpdateOrderInput, +} from '@cof-org/mcp'; +import { STATUS_MAP } from '../types.js'; +import type { CustomerOrder, LineItem } from '../models/customer-order.js'; +import { BaseTransformer } from './base.js'; +import { AddressTransformer } from './address.transformer.js'; +import { CustomerTransformer } from './customer.transformer.js'; + +export class OrderTransformer extends BaseTransformer { + private addressTransformer: AddressTransformer; + private customerTransformer: CustomerTransformer; + private workspace?: string; + + constructor(tenantId: string = 'default-workspace', workspace?: string) { + super(tenantId); + this.workspace = workspace; + this.addressTransformer = new AddressTransformer(tenantId); + this.customerTransformer = new CustomerTransformer(tenantId); + } + + override setTenantId(tenantId: string): void { + super.setTenantId(tenantId); + this.addressTransformer.setTenantId(tenantId); + this.customerTransformer.setTenantId(tenantId); + } + + setWorkspace(workspace: string): void { + this.workspace = workspace; + } + + /** + * Transform VirtoCommerce order to MCP Order format + */ + toMcpOrder(order: CustomerOrder): Order { + const shippingAddress = order.shipments + ?.map((x) => x.deliveryAddress) + .find((x) => x); + + return { + id: order.id, + externalId: order.id, + name: order.number, + status: this.mapOrderStatus(order.status), + totalPrice: order.total, + currency: order.currency, + customer: this.customerTransformer.fromOrder(order), + shippingAddress: this.addressTransformer.toMcpAddress(shippingAddress), + billingAddress: this.addressTransformer.toMcpAddress(order.addresses?.[0]), + lineItems: order.items?.map((item, index) => this.toOrderLineItem(order.id, item, index)), + createdAt: order.createdDate, + updatedAt: order.modifiedDate, + tenantId: this.tenantId, + customFields: this.transformDynamicProperties(order.dynamicProperties), + orderNote: order.comment, + }; + } + + /** + * Transform multiple orders + */ + toMcpOrders(orders: CustomerOrder[]): Order[] { + return orders.map((order) => this.toMcpOrder(order)); + } + + /** + * Transform CreateSalesOrderInput to API payload + */ + fromCreateSalesOrderInput(input: CreateSalesOrderInput): Record { + const order = input.order; + if (!order) { + return {}; + } + + return { + external_id: order.externalId ?? order.name, + status: order.status, + total: order.totalPrice, + currency: order.currency ?? 'USD', + customer: order.customer + ? { + id: order.customer.id ?? order.customer.externalId ?? order.customer.email, + email: order.customer.email, + first_name: order.customer.firstName, + last_name: order.customer.lastName, + phone: order.customer.phone, + } + : undefined, + items: order.lineItems?.map((item) => ({ + sku: item.sku, + name: item.name, + quantity: item.quantity ?? 0, + price: item.unitPrice ?? 0, + subtotal: item.totalPrice ?? item.unitPrice ?? 0, + discount: 0, + tax: 0, + })), + shipping_address: this.addressTransformer.toFulfillmentAddress(order.shippingAddress), + billing_address: this.addressTransformer.toFulfillmentAddress(order.billingAddress), + notes: order.orderNote, + metadata: { + source: order.orderSource, + workspace: this.workspace, + }, + }; + } + + /** + * Transform UpdateOrderInput to API payload + */ + fromUpdateOrderInput(updates: UpdateOrderInput['updates']): Record { + const payload: Record = {}; + + const status = this.valueOrUndefined((updates as { status?: string | null | undefined }).status); + if (status) { + payload.status = this.reverseMapStatus(status); + } + + const shippingAddress = this.valueOrUndefined( + (updates as { shippingAddress?: Address | null }).shippingAddress + ); + if (shippingAddress) { + payload.shipping_address = this.addressTransformer.toFulfillmentAddress(shippingAddress); + } + + const billingAddress = this.valueOrUndefined( + (updates as { billingAddress?: Address | null }).billingAddress + ); + if (billingAddress) { + payload.billing_address = this.addressTransformer.toFulfillmentAddress(billingAddress); + } + + const notes = this.valueOrUndefined((updates as { notes?: string | null }).notes); + if (notes) { + payload.notes = notes; + } + + const tags = this.valueOrUndefined((updates as { tags?: string[] | null }).tags); + if (Array.isArray(tags)) { + payload.tags = tags; + } + + return payload; + } + + /** + * Transform line item from VirtoCommerce format + */ + private toOrderLineItem(orderId: string, item: LineItem, index: number): OrderLineItem { + return { + id: item.id ?? `${orderId}-${item.sku}-${index}`, + sku: item.sku, + quantity: item.quantity, + unitPrice: item.price, + totalPrice: item.extendedPrice ?? item.price * item.quantity, + name: item.name, + }; + } + + /** + * Transform dynamic properties to custom fields + */ + private transformDynamicProperties( + properties?: CustomerOrder['dynamicProperties'] + ): CustomField[] | undefined { + if (!properties?.length) { + return undefined; + } + + const entries = properties + .filter((prop) => prop.values?.length) + .map((prop) => ({ + name: prop.name, + value: String(prop.values[0]?.value ?? ''), + })); + + return entries.length ? entries : undefined; + } + + /** + * Map VirtoCommerce status to normalized status + */ + private mapOrderStatus(status: string): string { + return STATUS_MAP[status] ?? status; + } + + /** + * Reverse map normalized status to VirtoCommerce status + */ + private reverseMapStatus(status: string): string { + const reverse = Object.entries(STATUS_MAP).reduce>( + (acc, [fulfillmentStatus, normalized]) => { + acc[normalized] = fulfillmentStatus; + return acc; + }, + {} + ); + + return reverse[status] ?? status; + } +} diff --git a/virtocommerce-adapter/src/transformers/product.transformer.ts b/virtocommerce-adapter/src/transformers/product.transformer.ts new file mode 100644 index 0000000..a81efb5 --- /dev/null +++ b/virtocommerce-adapter/src/transformers/product.transformer.ts @@ -0,0 +1,93 @@ +/** + * Product, Product Variant, and Inventory transformation utilities + */ + +import type { Product, ProductVariant, InventoryItem } from '@cof-org/mcp'; +import type { YourFulfillmentProduct, YourFulfillmentInventory } from '../types.js'; +import { BaseTransformer } from './base.js'; + +export class ProductTransformer extends BaseTransformer { + /** + * Transform YourFulfillment product to MCP Product format + */ + toMcpProduct(product: YourFulfillmentProduct): Product { + return { + id: product.id, + externalId: product.sku, + name: product.name, + description: product.description, + status: product.status, + options: [], + tags: product.attributes ? Object.keys(product.attributes) : undefined, + createdAt: product.created_at, + updatedAt: product.updated_at, + tenantId: this.tenantId, + }; + } + + /** + * Transform multiple products + */ + toMcpProducts(products: YourFulfillmentProduct[]): Product[] { + return products.map((product) => this.toMcpProduct(product)); + } + + /** + * Transform YourFulfillment product to MCP ProductVariant format + */ + toMcpProductVariant(product: YourFulfillmentProduct): ProductVariant { + return { + id: `${product.id}-default`, + productId: product.id, + sku: product.sku, + title: product.name, + price: product.price, + currency: 'USD', + createdAt: product.created_at, + updatedAt: product.updated_at, + tenantId: this.tenantId, + }; + } + + /** + * Transform multiple products to variants + */ + toMcpProductVariants(products: YourFulfillmentProduct[]): ProductVariant[] { + return products.map((product) => this.toMcpProductVariant(product)); + } + + /** + * Transform YourFulfillment inventory to MCP InventoryItem array + * Handles both single location and multi-location inventory + */ + toMcpInventoryItems(inventory: YourFulfillmentInventory): InventoryItem[] { + if (inventory.warehouse_locations?.length) { + return inventory.warehouse_locations.map((location) => ({ + locationId: location.location_id, + sku: inventory.sku, + available: location.available, + onHand: location.available + location.reserved, + unavailable: location.reserved, + tenantId: this.tenantId, + })); + } + + return [ + { + locationId: '', + sku: inventory.sku, + available: inventory.available, + onHand: inventory.total, + unavailable: inventory.total - inventory.available, + tenantId: this.tenantId, + }, + ]; + } + + /** + * Transform multiple inventory records + */ + toMcpInventory(inventoryItems: YourFulfillmentInventory[]): InventoryItem[] { + return inventoryItems.flatMap((item) => this.toMcpInventoryItems(item)); + } +} diff --git a/virtocommerce-adapter/src/types.ts b/virtocommerce-adapter/src/types.ts new file mode 100644 index 0000000..64dda65 --- /dev/null +++ b/virtocommerce-adapter/src/types.ts @@ -0,0 +1,165 @@ +/** + * Custom types for YourFulfillment adapter + * Define any Fulfillment-specific types that are not part of the standard onX types + */ + +// Configuration options for the adapter +export interface AdapterOptions { + apiUrl: string; + apiKey: string; + workspace?: string; + timeout?: number; + retryAttempts?: number; + debugMode?: boolean; +} + +// Example: Your Fulfillment API response format +export interface YourFulfillmentApiResponse { + success: boolean; + data?: T; + error?: { + code: string; + message: string; + details?: Record; + }; + metadata?: { + timestamp: string; + requestId: string; + version: string; + }; +} + +// Example: Your Fulfillment-specific order format +export interface YourFulfillmentOrder { + id: string; + number: string; + external_id: string; + status: string; + customer: { + id: string; + email: string; + first_name: string; + last_name: string; + phone?: string; + }; + items: YourFulfillmentOrderItem[]; + total: number; + currency: string; + created_at: string; + updated_at: string; + shipping_address?: YourFulfillmentAddress; + billing_address?: YourFulfillmentAddress; + metadata?: Record; +} + +export interface YourFulfillmentOrderItem { + sku: string; + name: string; + quantity: number; + price: number; + discount?: number; + tax?: number; + subtotal: number; +} + +export interface YourFulfillmentAddress { + street1: string; + street2?: string; + city: string; + state: string; + postal_code: string; + country: string; + phone?: string; + email?: string; + name?: string; + company?: string; +} + +// Example: Your Fulfillment-specific product format +export interface YourFulfillmentProduct { + id: string; + sku: string; + name: string; + description?: string; + price: number; + inventory: number; + status: 'active' | 'inactive' | 'archived'; + created_at: string; + updated_at: string; + attributes?: Record; +} + +// Example: Your Fulfillment-specific inventory format +export interface YourFulfillmentInventory { + sku: string; + available: number; + reserved: number; + total: number; + warehouse_locations?: { + location_id: string; + available: number; + reserved: number; + }[]; + updated_at: string; +} + +// Example: Your Fulfillment-specific customer format +export interface YourFulfillmentCustomer { + id: string; + email: string; + first_name: string; + last_name: string; + phone?: string; + created_at: string; + updated_at: string; + addresses?: YourFulfillmentAddress[]; + tags?: string[]; + metadata?: Record; +} + +// Example: Your Fulfillment-specific shipment format +export interface YourFulfillmentShipment { + id: string; + order_id: string; + tracking_number: string; + carrier: string; + service: string; + status: string; + shipped_at?: string; + delivered_at?: string; + tracking_url?: string; + items: { + sku: string; + quantity: number; + }[]; + from_address?: YourFulfillmentAddress; + to_address?: YourFulfillmentAddress; +} + +// Status mapping configuration +export const STATUS_MAP: Record = { + new: 'pending', + processing: 'processing', + shipped: 'shipped', + delivered: 'delivered', + cancelled: 'cancelled', + on_hold: 'on_hold', + refunded: 'refunded', + partially_shipped: 'partially_shipped', + partially_delivered: 'partially_delivered', +}; + +// Error codes for consistent error handling +export enum ErrorCode { + CONNECTION_FAILED = 'CONNECTION_FAILED', + AUTHENTICATION_FAILED = 'AUTHENTICATION_FAILED', + INVALID_REQUEST = 'INVALID_REQUEST', + ORDER_NOT_FOUND = 'ORDER_NOT_FOUND', + PRODUCT_NOT_FOUND = 'PRODUCT_NOT_FOUND', + CUSTOMER_NOT_FOUND = 'CUSTOMER_NOT_FOUND', + INSUFFICIENT_INVENTORY = 'INSUFFICIENT_INVENTORY', + INVALID_ORDER_STATE = 'INVALID_ORDER_STATE', + API_ERROR = 'API_ERROR', + TIMEOUT = 'TIMEOUT', + UNKNOWN_ERROR = 'UNKNOWN_ERROR', +} diff --git a/virtocommerce-adapter/src/utils/api-client.ts b/virtocommerce-adapter/src/utils/api-client.ts new file mode 100644 index 0000000..8b6c1f1 --- /dev/null +++ b/virtocommerce-adapter/src/utils/api-client.ts @@ -0,0 +1,350 @@ +/** + * API Client for YourFulfillment + * + * This is a generic HTTP client wrapper that handles: + * - Authentication + * - Request/response formatting + * - Error handling + * - Retries + * - Logging + */ + +import axios, { AxiosInstance, AxiosError, AxiosRequestConfig } from 'axios'; +import type { YourFulfillmentApiResponse } from '../types.js'; + +export interface ApiClientConfig { + baseUrl: string; + apiKey: string; + timeout?: number; + retryAttempts?: number; + debugMode?: boolean; + headers?: Record; +} + +export class ApiClient { + private client: AxiosInstance; + private config: ApiClientConfig; + private retryAttempts: number; + private debugMode: boolean; + + constructor(config: ApiClientConfig) { + this.config = config; + this.retryAttempts = config.retryAttempts || 3; + this.debugMode = config.debugMode || false; + + // Create axios instance with default configuration + this.client = axios.create({ + baseURL: config.baseUrl, + timeout: config.timeout || 30000, + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + // Add your authentication header + // Different APIs use different auth methods: + 'X-API-Key': config.apiKey, + // 'Authorization': `Bearer ${config.apiKey}`, + // 'API-Key': config.apiKey, + ...config.headers, + }, + }); + + // Request interceptor for debugging + this.client.interceptors.request.use( + (request) => { + if (this.debugMode) { + console.log('[API Request]', { + method: request.method?.toUpperCase(), + url: request.url, + params: request.params, + data: request.data, + }); + } + return request; + }, + (error) => { + if (this.debugMode) { + console.error('[API Request Error]', error); + } + return Promise.reject(error); + } + ); + + // Response interceptor for debugging and error handling + this.client.interceptors.response.use( + (response) => { + if (this.debugMode) { + console.log('[API Response]', { + status: response.status, + statusText: response.statusText, + url: response.config.url, + data: response.data, + }); + } + return response; + }, + (error) => { + if (this.debugMode) { + console.error('[API Response Error]', { + message: error.message, + status: error.response?.status, + statusText: error.response?.statusText, + url: error.config?.url, + data: error.response?.data, + }); + } + return Promise.reject(error); + } + ); + } + + /** + * GET request + */ + async get(path: string, params?: unknown): Promise> { + return this.request({ + method: 'GET', + url: path, + params, + }); + } + + /** + * POST request + */ + async post(path: string, data?: unknown, params?: unknown): Promise> { + return this.request({ + method: 'POST', + url: path, + data, + params, + }); + } + + /** + * PUT request + */ + async put(path: string, data?: unknown, params?: unknown): Promise> { + return this.request({ + method: 'PUT', + url: path, + data, + params, + }); + } + + /** + * PATCH request + */ + async patch(path: string, data?: unknown, params?: unknown): Promise> { + return this.request({ + method: 'PATCH', + url: path, + data, + params, + }); + } + + /** + * DELETE request + */ + async delete(path: string, params?: unknown): Promise> { + return this.request({ + method: 'DELETE', + url: path, + params, + }); + } + + /** + * Generic request method with retry logic + */ + private async request(config: AxiosRequestConfig, attempt = 1): Promise> { + try { + const response = await this.client.request(config); + + // Transform the response to our standard format + // Adjust this based on your Fulfillment API response format + return { + success: true, + data: response.data, + metadata: { + timestamp: new Date().toISOString(), + requestId: response.headers['x-request-id'] || '', + version: response.headers['x-api-version'] || '', + }, + }; + } catch (error) { + // Handle axios errors + if (axios.isAxiosError(error)) { + return this.handleAxiosError(error, config, attempt); + } + + // Handle other errors + throw error; + } + } + + /** + * Handle Axios errors with retry logic + */ + private async handleAxiosError( + error: AxiosError, + config: AxiosRequestConfig, + attempt: number + ): Promise> { + const status = error.response?.status; + + // Determine if we should retry + const shouldRetry = this.shouldRetry(status, attempt); + + if (shouldRetry) { + // Wait before retrying (exponential backoff) + const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000); + if (this.debugMode) { + console.log(`[API Retry] Attempt ${attempt + 1}/${this.retryAttempts} after ${delay}ms`); + } + await this.sleep(delay); + + // Retry the request + return this.request(config, attempt + 1); + } + + // Return error response + return { + success: false, + error: { + code: this.getErrorCode(status), + message: this.getErrorMessage(error), + details: error.response?.data as Record | undefined, + }, + metadata: { + timestamp: new Date().toISOString(), + requestId: error.response?.headers['x-request-id'] || '', + version: error.response?.headers['x-api-version'] || '', + }, + }; + } + + /** + * Determine if request should be retried + */ + private shouldRetry(status: number | undefined, attempt: number): boolean { + // Don't retry if we've exceeded max attempts + if (attempt >= this.retryAttempts) { + return false; + } + + // Retry on network errors (no status) + if (!status) { + return true; + } + + // Retry on 5xx errors (server errors) + if (status >= 500) { + return true; + } + + // Retry on specific 4xx errors + const retryableStatuses = [ + 408, // Request Timeout + 429, // Too Many Requests + 423, // Locked + ]; + + return retryableStatuses.includes(status); + } + + /** + * Get error code from status + */ + private getErrorCode(status: number | undefined): string { + if (!status) { + return 'NETWORK_ERROR'; + } + + const errorCodes: Record = { + 400: 'BAD_REQUEST', + 401: 'UNAUTHORIZED', + 403: 'FORBIDDEN', + 404: 'NOT_FOUND', + 408: 'TIMEOUT', + 409: 'CONFLICT', + 422: 'VALIDATION_ERROR', + 429: 'RATE_LIMITED', + 500: 'INTERNAL_SERVER_ERROR', + 502: 'BAD_GATEWAY', + 503: 'SERVICE_UNAVAILABLE', + 504: 'GATEWAY_TIMEOUT', + }; + + return errorCodes[status] || 'UNKNOWN_ERROR'; + } + + /** + * Extract error message from Axios error + */ + private getErrorMessage(error: AxiosError): string { + // Try to get message from response data + const responseData = error.response?.data as unknown; + + if (responseData && typeof responseData === 'object' && responseData !== null) { + // Common error message fields in APIs + if ('message' in responseData && typeof responseData.message === 'string') { + return responseData.message; + } + if ('error' in responseData) { + const errorField = responseData.error; + if (typeof errorField === 'string') { + return errorField; + } + if (errorField && typeof errorField === 'object' && 'message' in errorField) { + return String(errorField.message); + } + } + if ('errors' in responseData && Array.isArray(responseData.errors)) { + return responseData.errors + .map((e: unknown) => { + if (e && typeof e === 'object' && 'message' in e) { + return e.message; + } + return String(e); + }) + .join(', '); + } + } + + // Fall back to axios error message + return error.message || 'An unknown error occurred'; + } + + /** + * Sleep utility for retry delays + */ + private sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + /** + * Update authentication token (if using token-based auth) + */ + updateApiKey(apiKey: string): void { + this.config.apiKey = apiKey; + this.client.defaults.headers.common['X-API-Key'] = apiKey; + // Or if using Bearer token: + // this.client.defaults.headers.common['Authorization'] = `Bearer ${apiKey}`; + } + + /** + * Get current configuration + */ + getConfig(): ApiClientConfig { + return { ...this.config }; + } + + /** + * Enable/disable debug mode + */ + setDebugMode(enabled: boolean): void { + this.debugMode = enabled; + } +} diff --git a/virtocommerce-adapter/src/utils/type-guards.ts b/virtocommerce-adapter/src/utils/type-guards.ts new file mode 100644 index 0000000..be368fa --- /dev/null +++ b/virtocommerce-adapter/src/utils/type-guards.ts @@ -0,0 +1,37 @@ +/** + * Type guard utilities for safe type checking + */ + +/** + * Check if an unknown error is an Error instance + */ +export function isError(error: unknown): error is Error { + return error instanceof Error; +} + +/** + * Check if error has a response property (Axios-like errors) + */ +export function isAxiosError(error: unknown): error is { response?: { data?: unknown; status?: number } } { + return ( + typeof error === 'object' && + error !== null && + 'response' in error + ); +} + +/** + * Get error message safely from unknown error + */ +export function getErrorMessage(error: unknown): string { + if (isError(error)) { + return error.message; + } + if (typeof error === 'string') { + return error; + } + if (error && typeof error === 'object' && 'message' in error) { + return String(error.message); + } + return 'Unknown error occurred'; +} \ No newline at end of file diff --git a/virtocommerce-adapter/test-integration.js b/virtocommerce-adapter/test-integration.js new file mode 100644 index 0000000..3db4371 --- /dev/null +++ b/virtocommerce-adapter/test-integration.js @@ -0,0 +1,97 @@ +#!/usr/bin/env node +/** + * Test script to verify adapter can be loaded by the MCP server + * Run this after building your adapter: npm run build && node test-integration.js + */ + +import { YourFulfillmentAdapter } from './dist/index.js'; + +async function test() { + console.log('Testing adapter integration...\n'); + + // Test 1: Can instantiate with config + console.log('1. Testing instantiation with config object...'); + const adapter1 = new YourFulfillmentAdapter({ + type: 'local', + path: './dist/index.js', + options: { + apiUrl: 'https://test.api.com', + apiKey: 'test-key', + }, + }); + console.log('✓ Instantiated with AdapterConfig structure\n'); + + // Test 2: Can instantiate with just options (fallback) + console.log('2. Testing instantiation with options only...'); + new YourFulfillmentAdapter({ + apiUrl: 'https://test.api.com', + apiKey: 'test-key', + }); + console.log('✓ Instantiated with options only\n'); + + // Test 3: Verify required methods exist + console.log('3. Checking required methods...'); + const requiredMethods = [ + 'connect', + 'disconnect', + 'healthCheck', + 'createSalesOrder', + 'cancelOrder', + 'updateOrder', + 'fulfillOrder', + 'getOrders', + 'getCustomers', + 'getProducts', + 'getProductVariants', + 'getInventory', + 'getFulfillments', + ]; + + let allMethodsPresent = true; + for (const method of requiredMethods) { + if (typeof adapter1[method] !== 'function') { + console.log(`✗ Missing method: ${method}`); + allMethodsPresent = false; + } + } + + if (allMethodsPresent) { + console.log(`✓ All ${requiredMethods.length} required methods present\n`); + } + + // Test 4: Test default export + console.log('4. Testing default export...'); + const module = await import('./dist/index.js'); + const DefaultAdapter = module.default; + + if (DefaultAdapter === YourFulfillmentAdapter) { + console.log('✓ Default export matches YourFulfillmentAdapter\n'); + } else { + console.log('✗ Default export mismatch\n'); + } + + // Test 5: Can connect (mock) + console.log('5. Testing connect method...'); + try { + await adapter1.connect(); + console.log('✓ Connect method works\n'); + } catch (error) { + console.log(`✗ Connect failed: ${error.message}\n`); + } + + // Test 6: Health check + console.log('6. Testing health check...'); + try { + const health = await adapter1.healthCheck(); + console.log(`✓ Health check returned: ${health.status}\n`); + } catch (error) { + console.log(`✗ Health check failed: ${error.message}\n`); + } + + console.log('Integration test complete!'); + console.log('\nYour adapter is compatible with the MCP server adapter factory.'); + console.log('You can now use it with:'); + console.log(' ADAPTER_TYPE=local ADAPTER_PATH=../adapter-template/dist/index.js node dist/index.js'); +} + +test().catch(console.error); diff --git a/virtocommerce-adapter/tests/adapter.test.ts b/virtocommerce-adapter/tests/adapter.test.ts new file mode 100644 index 0000000..7b7d5a2 --- /dev/null +++ b/virtocommerce-adapter/tests/adapter.test.ts @@ -0,0 +1,524 @@ +/** + * Unit tests for YourFulfillment Adapter + * + * These tests demonstrate how to test your adapter implementation. + * Replace with actual tests for your Fulfillment integration. + */ + +import { jest } from '@jest/globals'; +import { YourFulfillmentAdapter } from '../src/adapter.js'; +import { ApiClient } from '../src/utils/api-client.js'; +import type { + CreateSalesOrderInput, + CancelOrderInput, + UpdateOrderInput, + GetOrdersInput, + GetInventoryInput, +} from '@cof-org/mcp'; + +describe('YourFulfillmentAdapter', () => { + let adapter: YourFulfillmentAdapter; + let mockApiClient: ApiClient; + let getSpy: jest.MockedFunction; + let postSpy: jest.MockedFunction; + let patchSpy: jest.MockedFunction; + + beforeEach(() => { + // Create adapter instance + adapter = new YourFulfillmentAdapter({ + apiUrl: 'https://api.test.yourfulfillment.com', + apiKey: 'test-api-key', + workspace: 'test-workspace', + timeout: 5000, + debugMode: false, + }); + + // Get mocked API client + mockApiClient = (adapter as any).client; + getSpy = jest.spyOn(mockApiClient, 'get') as unknown as jest.MockedFunction; + postSpy = jest.spyOn(mockApiClient, 'post') as unknown as jest.MockedFunction; + patchSpy = jest.spyOn(mockApiClient, 'patch') as unknown as jest.MockedFunction; + }); + + describe('Lifecycle Methods', () => { + describe('connect', () => { + it('should connect successfully when API is healthy', async () => { + getSpy.mockResolvedValue({ + success: true, + data: { status: 'healthy' }, + }); + + await expect(adapter.connect()).resolves.not.toThrow(); + expect(getSpy).toHaveBeenCalledWith('/health'); + }); + + it('should throw error when API is unreachable', async () => { + getSpy.mockResolvedValue({ + success: false, + error: { code: 'CONNECTION_FAILED', message: 'Connection failed' }, + }); + + await expect(adapter.connect()).rejects.toThrow('Connection failed'); + }); + }); + + describe('disconnect', () => { + it('should disconnect successfully', async () => { + await expect(adapter.disconnect()).resolves.not.toThrow(); + }); + }); + + describe('healthCheck', () => { + it('should return healthy status when API is working', async () => { + getSpy.mockResolvedValue({ + success: true, + data: { status: 'operational' }, + }); + + const result = await adapter.healthCheck(); + + expect(result.status).toBe('healthy'); + expect(result.checks).toHaveLength(2); + expect(result.checks?.[0]?.status).toBe('pass'); + }); + + it('should return unhealthy status when API fails', async () => { + getSpy.mockRejectedValue(new Error('Network error')); + + const result = await adapter.healthCheck(); + + expect(result.status).toBe('unhealthy'); + expect(result.checks?.[0]?.status).toBe('fail'); + }); + }); + }); + + describe('Order Actions', () => { + describe('createSalesOrder', () => { + const validOrderInput: CreateSalesOrderInput = { + order: { + lineItems: [ + { + sku: 'PROD-001', + quantity: 2, + unitPrice: 29.99, + name: 'Test Product', + }, + ], + customer: { + id: 'CUST-001', + email: 'test@example.com', + firstName: 'John', + lastName: 'Doe', + phone: '+1234567890', + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + tenantId: 'test-tenant', + }, + shippingAddress: { + firstName: 'John', + lastName: 'Doe', + address1: '123 Main St', + address2: 'Apt 4', + city: 'New York', + stateOrProvince: 'NY', + zipCodeOrPostalCode: '10001', + country: 'US', + phone: '+1234567890', + }, + billingAddress: { + firstName: 'John', + lastName: 'Doe', + address1: '123 Main St', + address2: 'Apt 4', + city: 'New York', + stateOrProvince: 'NY', + zipCodeOrPostalCode: '10001', + country: 'US', + phone: '+1234567890', + }, + totalPrice: 57.48, + currency: 'USD', + orderNote: 'Please handle with care', + orderSource: 'website', + name: 'ORD-2024-001', + status: 'pending', + }, + }; + + it('should create sales order successfully', async () => { + postSpy.mockResolvedValue({ + success: true, + data: { + id: 'ORDER-001', + number: 'ORD-2024-001', + external_id: 'EXT-001', + status: 'new', + customer: { + id: 'CUST-001', + email: 'test@example.com', + first_name: 'John', + last_name: 'Doe', + }, + items: [ + { + sku: 'PROD-001', + name: 'Test Product', + quantity: 2, + price: 29.99, + subtotal: 59.98, + }, + ], + total: 57.48, + currency: 'USD', + created_at: '2024-01-01T00:00:00Z', + updated_at: '2024-01-01T00:00:00Z', + shipping_address: {}, + billing_address: {}, + }, + }); + + const result = await adapter.createSalesOrder(validOrderInput); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.order.id).toBe('ORDER-001'); + expect(result.order.name).toBe('ORD-2024-001'); + expect(result.order.status).toBeDefined(); + } + expect(postSpy).toHaveBeenCalledWith('/orders', expect.any(Object)); + }); + + it('should handle order creation failure', async () => { + postSpy.mockResolvedValue({ + success: false, + error: { + code: 'VALIDATION_ERROR', + message: 'Invalid order data', + }, + }); + + const result = await adapter.createSalesOrder(validOrderInput); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error).toBeDefined(); + } + }); + }); + + describe('cancelOrder', () => { + it('should cancel order successfully', async () => { + postSpy.mockResolvedValue({ + success: true, + data: { + id: 'ORDER-001', + number: 'ORD-2024-001', + external_id: 'EXT-001', + status: 'cancelled', + customer: { + id: 'CUST-001', + email: 'test@example.com', + first_name: 'John', + last_name: 'Doe', + }, + items: [], + total: 100.0, + currency: 'USD', + created_at: '2024-01-01T00:00:00Z', + updated_at: '2024-01-01T00:00:00Z', + shipping_address: {}, + billing_address: {}, + }, + }); + + const input: CancelOrderInput = { + orderId: 'ORDER-001', + reason: 'Customer request', + notifyCustomer: true, + }; + + const result = await adapter.cancelOrder(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.order.id).toBe('ORDER-001'); + expect(result.order.status).toBe('cancelled'); + } + }); + + it('should handle cancellation failure', async () => { + postSpy.mockResolvedValue({ + success: false, + error: { + code: 'ORDER_NOT_FOUND', + message: 'Order not found', + }, + }); + + const input: CancelOrderInput = { + orderId: 'INVALID-ID', + }; + + const result = await adapter.cancelOrder(input); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error).toBeDefined(); + } + }); + }); + + describe('updateOrder', () => { + it('should update order successfully', async () => { + patchSpy.mockResolvedValue({ + success: true, + data: { + id: 'ORDER-001', + number: 'ORD-2024-001', + external_id: 'EXT-001', + status: 'processing', + customer: { + id: 'CUST-001', + email: 'test@example.com', + first_name: 'Jane', + last_name: 'Smith', + }, + items: [], + total: 100.0, + currency: 'USD', + created_at: '2024-01-01T00:00:00Z', + updated_at: '2024-01-02T00:00:00Z', + shipping_address: { + street1: '456 Oak Ave', + city: 'Los Angeles', + state: 'CA', + postal_code: '90001', + country: 'US', + }, + billing_address: {}, + }, + }); + + const input: UpdateOrderInput = { + id: 'ORDER-001', + updates: { + status: 'processing', + shippingAddress: { + firstName: 'Jane', + lastName: 'Smith', + address1: '456 Oak Ave', + city: 'Los Angeles', + stateOrProvince: 'CA', + zipCodeOrPostalCode: '90001', + country: 'US', + }, + }, + }; + + const result = await adapter.updateOrder(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.order.id).toBe('ORDER-001'); + expect(result.order.status).toBe('processing'); + } + }); + }); + }); + + describe('Query Operations', () => { + describe('getOrders', () => { + it('should get orders by IDs', async () => { + getSpy.mockResolvedValue({ + success: true, + data: [ + { + id: 'ORDER-001', + number: 'ORD-2024-001', + external_id: 'EXT-001', + status: 'processing', + customer: { + id: 'CUST-001', + email: 'test@example.com', + first_name: 'John', + last_name: 'Doe', + }, + items: [], + total: 100.0, + currency: 'USD', + created_at: '2024-01-01T00:00:00Z', + updated_at: '2024-01-01T00:00:00Z', + shipping_address: {}, + billing_address: {}, + }, + ], + }); + + const input: GetOrdersInput = { + ids: ['ORDER-001'], + }; + + const result = await adapter.getOrders(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.orders).toHaveLength(1); + expect(result.orders[0]?.id).toBe('ORDER-001'); + expect(result.orders[0]?.status).toBe('processing'); + } + expect(getSpy).toHaveBeenCalledWith('/orders', expect.any(Object)); + }); + + it('should get orders by external IDs', async () => { + getSpy.mockResolvedValue({ + success: true, + data: [ + { + id: 'ORDER-001', + number: 'ORD-2024-001', + external_id: 'EXT-001', + status: 'processing', + customer: { + id: 'CUST-001', + email: 'test@example.com', + first_name: 'John', + last_name: 'Doe', + }, + items: [], + total: 100.0, + currency: 'USD', + created_at: '2024-01-01T00:00:00Z', + updated_at: '2024-01-01T00:00:00Z', + shipping_address: {}, + billing_address: {}, + }, + ], + }); + + const input: GetOrdersInput = { + externalIds: ['EXT-001'], + }; + + const result = await adapter.getOrders(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.orders).toHaveLength(1); + expect(result.orders[0]?.externalId).toBe('EXT-001'); + } + }); + + it('should handle empty results', async () => { + getSpy.mockResolvedValue({ + success: true, + data: [], + }); + + const input: GetOrdersInput = { + ids: ['NON-EXISTENT'], + }; + + const result = await adapter.getOrders(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.orders).toHaveLength(0); + } + }); + }); + + describe('getInventory', () => { + it('should get inventory for SKUs', async () => { + getSpy.mockResolvedValue({ + success: true, + data: [ + { + sku: 'PROD-001', + available: 100, + reserved: 10, + total: 110, + warehouse_locations: [ + { location_id: 'LOC-001', available: 60, reserved: 5 }, + { location_id: 'LOC-002', available: 40, reserved: 5 }, + ], + updated_at: '2024-01-01T00:00:00Z', + }, + ], + }); + + const input: GetInventoryInput = { + skus: ['PROD-001'], + }; + + const result = await adapter.getInventory(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.inventory.length).toBeGreaterThan(0); + expect(result.inventory[0]?.sku).toBe('PROD-001'); + expect(result.inventory[0]?.available).toBe(60); + expect(result.inventory[0]?.locationId).toBe('LOC-001'); + } + }); + + it('should handle inventory lookup failure', async () => { + getSpy.mockResolvedValue({ + success: false, + error: { + code: 'NOT_FOUND', + message: 'SKU not found', + }, + }); + + const input: GetInventoryInput = { + skus: ['INVALID-SKU'], + }; + + const result = await adapter.getInventory(input); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error).toBeDefined(); + } + }); + }); + }); + + describe('Error Handling', () => { + it('should handle network errors gracefully', async () => { + getSpy.mockRejectedValue(new Error('Network timeout')); + + const input: GetOrdersInput = { ids: ['ORDER-001'] }; + const result = await adapter.getOrders(input); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error).toBeDefined(); + } + }); + + it('should handle API errors with proper error codes', async () => { + postSpy.mockResolvedValue({ + success: false, + error: { + code: 'RATE_LIMITED', + message: 'Too many requests', + }, + }); + + const input: CreateSalesOrderInput = { + order: { + lineItems: [{ sku: 'PROD-001', quantity: 1 }], + }, + }; + + const result = await adapter.createSalesOrder(input); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error).toBeDefined(); + } + }); + }); +}); diff --git a/virtocommerce-adapter/tests/setup.ts b/virtocommerce-adapter/tests/setup.ts new file mode 100644 index 0000000..21ed406 --- /dev/null +++ b/virtocommerce-adapter/tests/setup.ts @@ -0,0 +1,64 @@ +/** + * Jest Test Setup + * + * This file runs before all tests to set up the test environment. + */ + +import { jest } from '@jest/globals'; + +// Set test environment variables +process.env.NODE_ENV = 'test'; +process.env.API_URL = 'https://api.test.yourfulfillment.com'; +process.env.API_KEY = 'test-api-key'; +process.env.WORKSPACE = 'test-workspace'; + +// Mock console methods to reduce noise in tests +global.console = { + ...console, + // Uncomment to silence console logs during tests + // log: jest.fn(), + // info: jest.fn(), + // debug: jest.fn(), + // Keep error and warn for debugging + error: console.error, + warn: console.warn, +}; + +// Add custom matchers if needed +expect.extend({ + toBeValidOrder(received: any) { + const pass = + received && + typeof received.orderId === 'string' && + typeof received.status === 'string'; + + if (pass) { + return { + message: () => `expected ${received} not to be a valid order`, + pass: true, + }; + } else { + return { + message: () => `expected ${received} to be a valid order with orderId and status`, + pass: false, + }; + } + }, +}); + +// Extend Jest matchers TypeScript definitions +declare global { + namespace jest { + interface Matchers { + toBeValidOrder(): R; + } + } +} + +// Clean up after each test +afterEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); +}); + +export {}; diff --git a/virtocommerce-adapter/tsconfig.eslint.json b/virtocommerce-adapter/tsconfig.eslint.json new file mode 100644 index 0000000..a14a5f6 --- /dev/null +++ b/virtocommerce-adapter/tsconfig.eslint.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "src/**/*.ts", + "tests/**/*.ts", + "examples/**/*.ts" + ], + "exclude": [ + "node_modules", + "dist" + ] +} + diff --git a/virtocommerce-adapter/tsconfig.json b/virtocommerce-adapter/tsconfig.json new file mode 100644 index 0000000..e2146a6 --- /dev/null +++ b/virtocommerce-adapter/tsconfig.json @@ -0,0 +1,50 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "lib": [ + "ES2022" + ], + "types": [ + "node" + ], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true, + "moduleResolution": "node", + "removeComments": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "useUnknownInCatchVariables": true, + "alwaysStrict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "exactOptionalPropertyTypes": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": false, + "allowUnusedLabels": false, + "allowUnreachableCode": false + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist", + "tests" + ] +} \ No newline at end of file From 4f71c5222e6b64b1396783066c133202055b8fc9 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Mon, 19 Jan 2026 11:47:12 +0000 Subject: [PATCH 02/51] create first code --- .claude/settings.local.json | 8 + virtocommerce-adapter/package-lock.json | 5 +- virtocommerce-adapter/package.json | 4 +- .../src/mappers/filter.mappers.ts | 48 +- virtocommerce-adapter/src/mappers/index.ts | 1 + virtocommerce-adapter/src/models/base.ts | 204 ++++++ .../src/models/customer-order.ts | 623 ------------------ virtocommerce-adapter/src/models/customer.ts | 257 ++++++++ virtocommerce-adapter/src/models/index.ts | 125 +++- virtocommerce-adapter/src/models/order.ts | 261 ++++++++ virtocommerce-adapter/src/models/payment.ts | 280 ++++++++ virtocommerce-adapter/src/models/search.ts | 225 +++++++ virtocommerce-adapter/src/models/shipment.ts | 142 ++++ .../src/services/customer.service.ts | 71 ++ .../src/services/order.service.ts | 30 +- .../src/transformers/address.transformer.ts | 2 +- .../src/transformers/customer.transformer.ts | 88 ++- .../transformers/fulfillment.transformer.ts | 2 +- .../src/transformers/order.transformer.ts | 62 +- virtocommerce-adapter/src/utils/api-client.ts | 2 +- virtocommerce-adapter/tests/adapter.test.ts | 14 +- virtocommerce-adapter/tsconfig.json | 6 +- 22 files changed, 1777 insertions(+), 683 deletions(-) create mode 100644 .claude/settings.local.json create mode 100644 virtocommerce-adapter/src/models/base.ts delete mode 100644 virtocommerce-adapter/src/models/customer-order.ts create mode 100644 virtocommerce-adapter/src/models/customer.ts create mode 100644 virtocommerce-adapter/src/models/order.ts create mode 100644 virtocommerce-adapter/src/models/payment.ts create mode 100644 virtocommerce-adapter/src/models/search.ts create mode 100644 virtocommerce-adapter/src/models/shipment.ts diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..26d392f --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "WebFetch(domain:raw.githubusercontent.com)", + "Bash(npx tsc:*)" + ] + } +} diff --git a/virtocommerce-adapter/package-lock.json b/virtocommerce-adapter/package-lock.json index 3363712..f16d2a7 100644 --- a/virtocommerce-adapter/package-lock.json +++ b/virtocommerce-adapter/package-lock.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@eslint/js": "^9.8.0", - "@types/jest": "^29.5.11", + "@types/jest": "^29.5.14", "@types/node": "^20.19.29", "@typescript-eslint/eslint-plugin": "^8.7.0", "@typescript-eslint/parser": "^8.7.0", @@ -1310,8 +1310,9 @@ }, "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" diff --git a/virtocommerce-adapter/package.json b/virtocommerce-adapter/package.json index 763c9a6..1da7cce 100644 --- a/virtocommerce-adapter/package.json +++ b/virtocommerce-adapter/package.json @@ -33,7 +33,7 @@ "license": "MIT", "devDependencies": { "@eslint/js": "^9.8.0", - "@types/jest": "^29.5.11", + "@types/jest": "^29.5.14", "@types/node": "^20.19.29", "@typescript-eslint/eslint-plugin": "^8.7.0", "@typescript-eslint/parser": "^8.7.0", @@ -48,4 +48,4 @@ "@cof-org/mcp": "file:../server", "axios": "^1.6.0" } -} \ No newline at end of file +} diff --git a/virtocommerce-adapter/src/mappers/filter.mappers.ts b/virtocommerce-adapter/src/mappers/filter.mappers.ts index d72a39a..cdff42c 100644 --- a/virtocommerce-adapter/src/mappers/filter.mappers.ts +++ b/virtocommerce-adapter/src/mappers/filter.mappers.ts @@ -12,9 +12,55 @@ import type { GetFulfillmentsInput, GetReturnsInput, } from '@cof-org/mcp'; +import type { CustomerOrderSearchCriteria } from '../models/index.js'; /** - * Map GetOrdersInput to API query parameters + * Map GetOrdersInput to VirtoCommerce CustomerOrderSearchCriteria + */ +export function mapOrderFiltersToSearchCriteria(input: GetOrdersInput): CustomerOrderSearchCriteria { + const criteria: CustomerOrderSearchCriteria = {}; + + // Map order IDs + if (input.ids?.length) { + criteria.objectIds = input.ids; + } + + // Map external IDs to numbers (VirtoCommerce uses 'numbers' for order numbers) + if (input.externalIds?.length) { + criteria.numbers = input.externalIds; + } + + // Map statuses + if (input.statuses?.length) { + criteria.statuses = input.statuses; + } + + // Map names (order numbers) + if (input.names?.length) { + criteria.numbers = [...(criteria.numbers ?? []), ...input.names]; + } + + // Map date filters + if (input.createdAtMin) { + criteria.startDate = input.createdAtMin; + } + if (input.createdAtMax) { + criteria.endDate = input.createdAtMax; + } + + // Map pagination + criteria.skip = input.skip ?? 0; + criteria.take = input.pageSize ?? 20; + + // Set response group for full data + criteria.responseGroup = 'Full'; + + return criteria; +} + +/** + * @deprecated Use mapOrderFiltersToSearchCriteria instead + * Map GetOrdersInput to generic API query parameters (legacy) */ export function mapOrderFilters(input: GetOrdersInput): Record { return { diff --git a/virtocommerce-adapter/src/mappers/index.ts b/virtocommerce-adapter/src/mappers/index.ts index fd7689f..f633784 100644 --- a/virtocommerce-adapter/src/mappers/index.ts +++ b/virtocommerce-adapter/src/mappers/index.ts @@ -5,6 +5,7 @@ export { mapOrderFilters, + mapOrderFiltersToSearchCriteria, mapInventoryFilters, mapProductFilters, mapProductVariantFilters, diff --git a/virtocommerce-adapter/src/models/base.ts b/virtocommerce-adapter/src/models/base.ts new file mode 100644 index 0000000..92c6ea2 --- /dev/null +++ b/virtocommerce-adapter/src/models/base.ts @@ -0,0 +1,204 @@ +/** + * Base types for VirtoCommerce models + * Based on VirtoCommerce.Platform.Core and module-specific base classes + */ + +/** + * Base entity with ID + */ +export interface Entity { + id?: string; +} + +/** + * Auditable entity with tracking fields + */ +export interface AuditableEntity extends Entity { + createdDate?: string; + modifiedDate?: string; + createdBy?: string; + modifiedBy?: string; +} + +/** + * Address model - shared across orders and customers + */ +export interface Address { + addressType?: AddressType; + key?: string; + name?: string; + organization?: string; + countryCode?: string; + countryName?: string; + city?: string; + postalCode?: string; + zip?: string; + line1?: string; + line2?: string; + regionId?: string; + regionName?: string; + firstName?: string; + lastName?: string; + phone?: string; + email?: string; + outerId?: string; + description?: string; +} + +export type AddressType = 'Billing' | 'Shipping' | 'BillingAndShipping' | 'Pickup'; + +/** + * Dynamic property for extensible metadata + */ +export interface DynamicObjectProperty { + id?: string; + name?: string; + objectId?: string; + objectType?: string; + valueType?: string; + values?: DynamicPropertyObjectValue[]; +} + +export interface DynamicPropertyObjectValue { + valueType?: string; + value?: unknown; + locale?: string; + propertyId?: string; + propertyName?: string; +} + +/** + * Discount information + */ +export interface Discount { + id?: string; + promotionId?: string; + currency?: string; + discountAmount?: number; + discountAmountWithTax?: number; + coupon?: string; + description?: string; +} + +/** + * Tax detail breakdown + */ +export interface TaxDetail { + rate?: number; + amount?: number; + name?: string; +} + +/** + * Fee detail breakdown + */ +export interface FeeDetail { + feeId?: string; + currency?: string; + amount?: number; + description?: string; +} + +/** + * SEO information + */ +export interface SeoInfo { + id?: string; + name?: string; + semanticUrl?: string; + pageTitle?: string; + metaDescription?: string; + metaKeywords?: string; + storeId?: string; + objectId?: string; + objectType?: string; + isActive?: boolean; + languageCode?: string; +} + +/** + * Note/comment attached to an entity + */ +export interface Note { + id?: string; + title?: string; + body?: string; + outerId?: string; + createdDate?: string; + modifiedDate?: string; + createdBy?: string; + modifiedBy?: string; +} + +/** + * Operation log entry for change tracking + */ +export interface OperationLog { + id?: string; + objectType?: string; + objectId?: string; + operationType?: string; + detail?: string; + createdDate?: string; + modifiedDate?: string; + createdBy?: string; + modifiedBy?: string; +} + +/** + * Interface for entities with dimensions + */ +export interface HasDimension { + weightUnit?: string; + weight?: number; + measureUnit?: string; + height?: number; + length?: number; + width?: number; +} + +/** + * Interface for cancellable entities + */ +export interface SupportsCancellation { + isCancelled?: boolean; + cancelledDate?: string; + cancelReason?: string; +} + +/** + * Interface for entities with outer ID (external system reference) + */ +export interface HasOuterId { + outerId?: string; +} + +/** + * Interface for taxable entities + */ +export interface Taxable { + taxType?: string; + taxTotal?: number; + taxPercentRate?: number; +} + +/** + * Interface for entities with discounts + */ +export interface HasDiscounts { + discounts?: Discount[]; +} + +/** + * Interface for entities with tax details + */ +export interface HasTaxDetalization { + taxDetails?: TaxDetail[]; +} + +/** + * Interface for entities with fee details + */ +export interface HasFeesDetalization { + feeDetails?: FeeDetail[]; +} diff --git a/virtocommerce-adapter/src/models/customer-order.ts b/virtocommerce-adapter/src/models/customer-order.ts deleted file mode 100644 index 8c79dc7..0000000 --- a/virtocommerce-adapter/src/models/customer-order.ts +++ /dev/null @@ -1,623 +0,0 @@ -export interface CustomerOrderSearchResult { - totalCount: number; - results: CustomerOrder[]; -} - -export interface CustomerOrder { - rowVersion: string; - customerId: string; - customerName: string; - channelId: string; - storeId: string; - storeName: string; - organizationId: string; - organizationName: string; - employeeId: string; - employeeName: string; - shoppingCartId: string; - isPrototype: boolean; - purchaseOrderNumber: string; - subscriptionNumber: string; - subscriptionId: string; - objectType: string; - addresses: Address[]; - inPayments: PaymentIn[]; - items: LineItem[]; - shipments: Shipment[]; - feeDetails: FeeDetail[]; - relevanceScore: number; - discounts: Discount[]; - discountAmount: number; - taxDetails: TaxDetail[]; - scopes: string[]; - total: number; - subTotal: number; - subTotalWithTax: number; - subTotalDiscount: number; - subTotalDiscountWithTax: number; - subTotalTaxTotal: number; - shippingTotal: number; - shippingTotalWithTax: number; - shippingSubTotal: number; - shippingSubTotalWithTax: number; - shippingDiscountTotal: number; - shippingDiscountTotalWithTax: number; - shippingTaxTotal: number; - paymentTotal: number; - paymentTotalWithTax: number; - paymentSubTotal: number; - paymentSubTotalWithTax: number; - paymentDiscountTotal: number; - paymentDiscountTotalWithTax: number; - paymentTaxTotal: number; - discountTotal: number; - discountTotalWithTax: number; - fee: number; - feeWithTax: number; - feeTotal: number; - feeTotalWithTax: number; - handlingTotal: number; - handlingTotalWithTax: number; - isAnonymous: boolean; - taxType: string; - taxTotal: number; - taxPercentRate: number; - languageCode: string; - operationType: string; - parentOperationId: string; - number: string; - isApproved: boolean; - status: string; - comment: string; - currency: string; - sum: number; - outerId: string; - cancelledState: string; - isCancelled: boolean; - cancelledDate: string; - cancelReason: string; - dynamicProperties: DynamicProperty[]; - operationsLog: OperationsLog[]; - createdDate: string; - modifiedDate: string; - createdBy: string; - modifiedBy: string; - id: string; -} - -export interface Shipment { - organizationId: string; - organizationName: string; - fulfillmentCenterId: string; - fulfillmentCenterName: string; - employeeId: string; - employeeName: string; - shipmentMethodCode: string; - shipmentMethodOption: string; - shippingMethod: ShippingMethod; - customerOrderId: string; - customerOrder: string; - items: ShipmentItem[]; - packages: ShipmentPackage[]; - inPayments: PaymentIn[]; - feeDetails: FeeDetail[]; - weightUnit: string; - weight: number; - measureUnit: string; - height: number; - length: number; - width: number; - discounts: Discount[]; - deliveryAddress: Address; - price: number; - priceWithTax: number; - total: number; - totalWithTax: number; - discountAmount: number; - discountAmountWithTax: number; - pickupLocationId: string; - fee: number; - feeWithTax: number; - trackingNumber: string; - trackingUrl: string; - deliveryDate: string; - objectType: string; - vendorId: string; - taxType: string; - taxTotal: number; - taxPercentRate: number; - taxDetails: TaxDetail[]; - operationType: string; - parentOperationId: string; - number: string; - isApproved: boolean; - status: string; - comment: string; - currency: string; - sum: number; - outerId: string; - cancelledState: string; - isCancelled: boolean; - cancelledDate: string; - cancelReason: string; - dynamicProperties: DynamicProperty[]; - operationsLog: OperationsLog[]; - createdDate: string; - modifiedDate: string; - createdBy: string; - modifiedBy: string; - id: string; -} - -export interface ShipmentPackage { - barCode: string; - packageType: string; - items: ShipmentItem[]; - weightUnit: string; - weight: number; - measureUnit: string; - height: number; - length: number; - width: number; - createdDate: string; - modifiedDate: string; - createdBy: string; - modifiedBy: string; - id: string; -} - -export interface ShipmentItem { - lineItemId: string; - lineItem: LineItem; - barCode: string; - quantity: number; - outerId: string; - status: string; - createdDate: string; - modifiedDate: string; - createdBy: string; - modifiedBy: string; - id: string; -} - -export interface ShippingMethod { - code: string; - name: string; - description: string; - logoUrl: string; - isActive: boolean; - priority: number; - taxType: string; - storeId: string; - typeName: string; - id: string; -} - -export interface PaymentIn { - orderId: string; - purpose: string; - gatewayCode: string; - paymentMethod: PaymentMethod; - organizationId: string; - organizationName: string; - customerId: string; - customerName: string; - incomingDate: string; - billingAddress: Address; - paymentStatus: string; - authorizedDate: string; - capturedDate: string; - voidedDate: string; - processPaymentResult: ProcessPaymentResult; - price: number; - priceWithTax: number; - total: number; - totalWithTax: number; - discountAmount: number; - discountAmountWithTax: number; - objectType: string; - feeDetails: FeeDetail[]; - vendorId: string; - taxType: string; - taxTotal: number; - taxPercentRate: number; - taxDetails: TaxDetail[]; - discounts: Discount[]; - transactions: Transaction[]; - refunds: Refund[]; - captures: Capture[]; - operationType: string; - parentOperationId: string; - number: string; - isApproved: boolean; - status: string; - comment: string; - currency: string; - sum: number; - outerId: string; - cancelledState: string; - isCancelled: boolean; - cancelledDate: string; - cancelReason: string; - dynamicProperties: DynamicProperty[]; - operationsLog: OperationsLog[]; - createdDate: string; - modifiedDate: string; - createdBy: string; - modifiedBy: string; - id: string; -} - -export interface Capture { - objectType: string; - amount: number; - vendorId: string; - transactionId: string; - customerOrderId: string; - paymentId: string; - items: CaptureItem[]; - closeTransaction: boolean; - operationType: string; - parentOperationId: string; - number: string; - isApproved: boolean; - status: string; - comment: string; - currency: string; - sum: number; - outerId: string; - cancelledState: string; - isCancelled: boolean; - cancelledDate: string; - cancelReason: string; - dynamicProperties: DynamicProperty[]; - operationsLog: OperationsLog[]; - createdDate: string; - modifiedDate: string; - createdBy: string; - modifiedBy: string; - id: string; -} - -export interface CaptureItem { - quantity: number; - lineItemId: string; - lineItem: LineItem; - captureId: string; - outerId: string; - createdDate: string; - modifiedDate: string; - createdBy: string; - modifiedBy: string; - id: string; -} - -export interface Refund { - objectType: string; - amount: number; - reasonCode: string; - refundStatus: string; - reasonMessage: string; - rejectReasonMessage: string; - vendorId: string; - transactionId: string; - customerOrderId: string; - paymentId: string; - items: RefundItem[]; - operationType: string; - parentOperationId: string; - number: string; - isApproved: boolean; - status: string; - comment: string; - currency: string; - sum: number; - outerId: string; - cancelledState: string; - isCancelled: boolean; - cancelledDate: string; - cancelReason: string; - dynamicProperties: DynamicProperty[]; - operationsLog: OperationsLog[]; - createdDate: string; - modifiedDate: string; - createdBy: string; - modifiedBy: string; - id: string; -} - -export interface OperationsLog { - objectType: string; - objectId: string; - operationType: string; - detail: string; - createdDate: string; - modifiedDate: string; - createdBy: string; - modifiedBy: string; - id: string; -} - -export interface RefundItem { - quantity: number; - lineItemId: string; - lineItem: LineItem; - refundId: string; - outerId: string; - createdDate: string; - modifiedDate: string; - createdBy: string; - modifiedBy: string; - id: string; -} - -export interface LineItem { - priceId: string; - currency: string; - price: number; - priceWithTax: number; - listTotal: number; - listTotalWithTax: number; - placedPrice: number; - placedPriceWithTax: number; - extendedPrice: number; - extendedPriceWithTax: number; - discountAmount: number; - isDiscountAmountRounded: boolean; - discountAmountWithTax: number; - discountTotal: number; - discountTotalWithTax: number; - fee: number; - feeWithTax: number; - taxType: string; - taxTotal: number; - taxPercentRate: number; - reserveQuantity: number; - quantity: number; - productId: string; - sku: string; - productType: string; - catalogId: string; - categoryId: string; - name: string; - productOuterId: string; - comment: string; - status: string; - imageUrl: string; - isGift: boolean; - shippingMethodCode: string; - fulfillmentLocationCode: string; - fulfillmentCenterId: string; - fulfillmentCenterName: string; - outerId: string; - feeDetails: FeeDetail[]; - vendorId: string; - isConfigured: boolean; - weightUnit: string; - weight: number; - measureUnit: string; - height: number; - length: number; - width: number; - isCancelled: boolean; - cancelledDate: string; - cancelReason: string; - objectType: string; - dynamicProperties: DynamicProperty[]; - discounts: Discount[]; - taxDetails: TaxDetail[]; - configurationItems: ConfigurationItem[]; - createdDate: string; - modifiedDate: string; - createdBy: string; - modifiedBy: string; - id: string; -} - -export interface ConfigurationItem { - productId: string; - name: string; - sku: string; - quantity: number; - imageUrl: string; - catalogId: string; - categoryId: string; - type: string; - customText: string; - files: File[]; - createdDate: string; - modifiedDate: string; - createdBy: string; - modifiedBy: string; - id: string; -} - -export interface File { - name: string; - url: string; - contentType: string; - size: number; - createdDate: string; - modifiedDate: string; - createdBy: string; - modifiedBy: string; - id: string; -} - -export interface DynamicProperty { - objectId: string; - values: DynamicPropertyValue[]; - name: string; - description: string; - objectType: string; - isArray: boolean; - isDictionary: boolean; - isMultilingual: boolean; - isRequired: boolean; - displayOrder: number; - valueType: string; - displayNames: DisplayName[]; - createdDate: string; - modifiedDate: string; - createdBy: string; - modifiedBy: string; - id: string; -} - -export interface DisplayName { - locale: string; - name: string; -} - -export interface DynamicPropertyValue { - objectType: string; - objectId: string; - locale: string; - value: any; - valueId: string; - valueType: string; - propertyId: string; - propertyName: string; -} - -export interface Transaction { - amount: number; - currencyCode: string; - isProcessed: boolean; - processedDate: string; - processError: string; - processAttemptCount: number; - requestData: string; - responseData: string; - responseCode: string; - gatewayIpAddress: string; - type: string; - status: string; - note: string; - createdDate: string; - modifiedDate: string; - createdBy: string; - modifiedBy: string; - id: string; -} - -export interface Discount { - promotionId: string; - currency: string; - discountAmount: number; - discountAmountWithTax: number; - coupon: string; - description: string; - name: string; - id: string; -} - -export interface FeeDetail { - feeId: string; - currency: string; - amount: number; - description: string; -} - -export interface ProcessPaymentResult { - redirectUrl: string; - htmlForm: string; - outerId: string; - paymentMethod: PaymentMethod; - isSuccess: boolean; - errorMessage: string; - newPaymentStatus: string; - publicParameters: Values; -} - -export interface PaymentMethod { - code: string; - name: string; - logoUrl: string; - isActive: boolean; - priority: number; - isAvailableForPartial: boolean; - allowDeferredPayment: boolean; - currency: string; - price: number; - priceWithTax: number; - total: number; - totalWithTax: number; - discountAmount: number; - discountAmountWithTax: number; - allowCartPayment: boolean; - storeId: string; - description: string; - typeName: string; - settings: Setting[]; - taxType: string; - taxTotal: number; - taxPercentRate: number; - taxDetails: TaxDetail[]; - localizedName: LocalizedName; - paymentMethodType: string; - paymentMethodGroupType: string; - id: string; -} - -export interface LocalizedName { - values: Values; -} - -export interface Values { - additionalProp1: string; - additionalProp2: string; - additionalProp3: string; -} - -export interface TaxDetail { - rate: number; - amount: number; - name: string; -} - -export interface Setting { - itHasValues: boolean; - objectId: string; - objectType: string; - isReadOnly: boolean; - value: any; - id: string; - restartRequired: boolean; - moduleId: string; - groupName: string; - name: string; - displayName: string; - isRequired: boolean; - isHidden: boolean; - isPublic: boolean; - valueType: string; - allowedValues: any[]; - defaultValue: any; - isDictionary: boolean; - isLocalizable: boolean; -} - -export interface Address { - addressType: string; - key: string; - name: string; - organization: string; - countryCode: string; - countryName: string; - city: string; - postalCode: string; - zip: string; - line1: string; - line2: string; - regionId: string; - regionName: string; - firstName: string; - middleName: string; - lastName: string; - phone: string; - email: string; - outerId: string; - isDefault: boolean; - description: string; -} \ No newline at end of file diff --git a/virtocommerce-adapter/src/models/customer.ts b/virtocommerce-adapter/src/models/customer.ts new file mode 100644 index 0000000..eda4d4c --- /dev/null +++ b/virtocommerce-adapter/src/models/customer.ts @@ -0,0 +1,257 @@ +/** + * Customer-related models for VirtoCommerce + * Based on VirtoCommerce.CustomerModule.Core.Model + */ + +import type { + AuditableEntity, + Address, + Note, + DynamicObjectProperty, + SeoInfo, + HasOuterId, +} from './base.js'; + +/** + * Member type discriminator + */ +export type MemberType = 'Contact' | 'Organization' | 'Employee' | 'Vendor'; + +/** + * Base member class - abstract base for all customer types + */ +export interface Member extends AuditableEntity, HasOuterId { + name?: string; + memberType?: MemberType; + status?: string; + addresses?: Address[]; + phones?: string[]; + emails?: string[]; + notes?: Note[]; + groups?: string[]; + iconUrl?: string; + relevanceScore?: number; + objectType?: string; + dynamicProperties?: DynamicObjectProperty[]; + seoObjectType?: string; + seoInfos?: SeoInfo[]; +} + +/** + * Application user - security account + */ +export interface ApplicationUser { + id?: string; + userName?: string; + email?: string; + emailConfirmed?: boolean; + phoneNumber?: string; + phoneNumberConfirmed?: boolean; + isAdministrator?: boolean; + lockoutEnabled?: boolean; + lockoutEnd?: string; + accessFailedCount?: number; + twoFactorEnabled?: boolean; + passwordExpired?: boolean; + lastPasswordChangedDate?: string; + memberId?: string; + storeId?: string; + userType?: string; + status?: string; + roles?: Role[]; + permissions?: string[]; +} + +/** + * User role + */ +export interface Role { + id?: string; + name?: string; + description?: string; + permissions?: Permission[]; +} + +/** + * Permission + */ +export interface Permission { + id?: string; + name?: string; + moduleId?: string; + groupName?: string; + assignedScopes?: PermissionScope[]; +} + +/** + * Permission scope + */ +export interface PermissionScope { + type?: string; + label?: string; + scope?: string; +} + +/** + * Contact - represents an individual customer + */ +export interface Contact extends Member { + memberType?: 'Contact'; + + // Name components + salutation?: string; + fullName?: string; + firstName?: string; + middleName?: string; + lastName?: string; + + // Personal info + birthDate?: string; + defaultLanguage?: string; + currencyCode?: string; + timeZone?: string; + about?: string; + photoUrl?: string; + isAnonymized?: boolean; + + // Tax + taxPayerId?: string; + + // Organizations + organizations?: string[]; + associatedOrganizations?: string[]; + defaultOrganizationId?: string; + currentOrganizationId?: string; + + // Preferences + preferredDelivery?: string; + preferredCommunication?: string; + defaultShippingAddressId?: string; + defaultBillingAddressId?: string; + + // Security + securityAccounts?: ApplicationUser[]; +} + +/** + * Organization - represents a business/company customer + */ +export interface Organization extends Member { + memberType?: 'Organization'; + description?: string; + businessCategory?: string; + ownerId?: string; + parentId?: string; +} + +/** + * Employee - represents an employee/staff member + */ +export interface Employee extends Member { + memberType?: 'Employee'; + + // Name components + salutation?: string; + fullName?: string; + firstName?: string; + middleName?: string; + lastName?: string; + + // Personal info + birthDate?: string; + defaultLanguage?: string; + timeZone?: string; + photoUrl?: string; + + // Organizations + organizations?: string[]; + + // Security + securityAccounts?: ApplicationUser[]; +} + +/** + * Vendor - represents a vendor/supplier + */ +export interface Vendor extends Member { + memberType?: 'Vendor'; + description?: string; + siteUrl?: string; + logoUrl?: string; + groupName?: string; +} + +/** + * Customer preference + */ +export interface CustomerPreference extends AuditableEntity { + customerId?: string; + name?: string; + value?: string; +} + +/** + * Customer role + */ +export interface CustomerRole { + id?: string; + name?: string; + description?: string; +} + +/** + * Member response group for controlling data loading + */ +export type MemberResponseGroup = + | 'None' + | 'WithAddresses' + | 'WithNotes' + | 'WithEmails' + | 'WithPhones' + | 'WithGroups' + | 'WithSecurityAccounts' + | 'WithSeo' + | 'WithDynamicProperties' + | 'Full'; + +/** + * Invite customer request + */ +export interface InviteCustomerRequest { + storeId?: string; + organizationId?: string; + email?: string; + urlSuffix?: string; + language?: string; + message?: string; + roles?: string[]; + customerOrderId?: string; +} + +/** + * Invite customer result + */ +export interface InviteCustomerResult { + succeeded?: boolean; + errors?: InviteCustomerError[]; + customerId?: string; +} + +/** + * Invite customer error + */ +export interface InviteCustomerError { + code?: string; + description?: string; + parameter?: string; +} + +/** + * Relation type between members + */ +export interface RelationType { + id?: string; + sourceId?: string; + targetId?: string; + relationTypeName?: string; +} diff --git a/virtocommerce-adapter/src/models/index.ts b/virtocommerce-adapter/src/models/index.ts index 7f2f998..3af1328 100644 --- a/virtocommerce-adapter/src/models/index.ts +++ b/virtocommerce-adapter/src/models/index.ts @@ -1 +1,124 @@ -export * from './customer-order.js'; +/** + * VirtoCommerce Models + * + * TypeScript models based on VirtoCommerce's .NET domain models from: + * - VirtoCommerce.OrdersModule.Core.Model + * - VirtoCommerce.CustomerModule.Core.Model + * - VirtoCommerce.Platform.Core.Common + */ + +// Base types +export type { + Entity, + AuditableEntity, + Address, + AddressType, + DynamicObjectProperty, + DynamicPropertyObjectValue, + Discount, + TaxDetail, + FeeDetail, + SeoInfo, + Note, + OperationLog, + HasDimension, + SupportsCancellation, + HasOuterId, + Taxable, + HasDiscounts, + HasTaxDetalization, + HasFeesDetalization, +} from './base.js'; + +// Order models +export type { + CancelledState, + OrderOperation, + CustomerOrder, + ConfigurationItem, + ConfigurationItemFile, + LineItem, + CustomerOrderResponseGroup, + DashboardStatisticsResult, + QuarterPeriodMoney, + OrderOperationStatusChangedEntry, +} from './order.js'; + +// Shipment models +export type { + ShippingMethod, + Shipment, + CustomerOrderRef, + ShipmentItem, + ShipmentPackage, + FulfillmentCenter, + ShipmentStatus, +} from './shipment.js'; + +// Payment models +export type { + PaymentStatus, + RefundStatus, + CaptureStatus, + RefundReasonCode, + PaymentMethod, + PaymentGatewayTransaction, + ProcessPaymentRequestResult, + PaymentIn, + RefundItem, + RefundLineItemRef, + Refund, + CaptureItem, + CaptureLineItemRef, + Capture, + RefundOrderPaymentRequest, + RefundOrderPaymentResult, + CaptureOrderPaymentRequest, + CaptureOrderPaymentResult, + PaymentCallbackParameters, +} from './payment.js'; + +// Customer models +export type { + MemberType, + Member, + ApplicationUser, + Role, + Permission, + PermissionScope, + Contact, + Organization, + Employee, + Vendor, + CustomerPreference, + CustomerRole, + MemberResponseGroup, + InviteCustomerRequest, + InviteCustomerResult, + InviteCustomerError, + RelationType, +} from './customer.js'; + +// Search models +export type { + SearchCriteriaBase, + SearchResult, + CustomerOrderSearchCriteria, + CustomerOrderSearchResult, + MemberSearchCriteria, + MemberSearchResult, + ContactSearchCriteria, + ContactSearchResult, + OrganizationSearchCriteria, + OrganizationSearchResult, + EmployeeSearchCriteria, + EmployeeSearchResult, + VendorSearchCriteria, + VendorSearchResult, + PaymentSearchCriteria, + ShipmentSearchCriteria, + IndexedSearchCriteria, + AggregationItem, + Aggregation, + IndexedSearchResult, +} from './search.js'; diff --git a/virtocommerce-adapter/src/models/order.ts b/virtocommerce-adapter/src/models/order.ts new file mode 100644 index 0000000..a9298b2 --- /dev/null +++ b/virtocommerce-adapter/src/models/order.ts @@ -0,0 +1,261 @@ +/** + * Order-related models for VirtoCommerce + * Based on VirtoCommerce.OrdersModule.Core.Model + */ + +import type { + AuditableEntity, + Address, + DynamicObjectProperty, + OperationLog, + HasDimension, + SupportsCancellation, + HasOuterId, + Taxable, + HasDiscounts, + HasTaxDetalization, + HasFeesDetalization, +} from './base.js'; +import type { PaymentIn } from './payment.js'; +import type { Shipment } from './shipment.js'; + +/** + * Cancelled state for order operations + */ +export type CancelledState = 'Undefined' | 'Requested' | 'Completed'; + +/** + * Base class for all order operations (orders, shipments, payments) + */ +export interface OrderOperation extends AuditableEntity, HasOuterId, SupportsCancellation { + operationType?: string; + parentOperationId?: string; + number?: string; + isApproved?: boolean; + status?: string; + comment?: string; + currency?: string; + sum?: number; + cancelledState?: CancelledState; + objectType?: string; + dynamicProperties?: DynamicObjectProperty[]; + operationsLog?: OperationLog[]; +} + +/** + * Customer order - main order entity + */ +export interface CustomerOrder extends OrderOperation, HasTaxDetalization, Taxable, HasDiscounts, HasFeesDetalization { + customerId?: string; + customerName?: string; + channelId?: string; + storeId?: string; + storeName?: string; + organizationId?: string; + organizationName?: string; + employeeId?: string; + employeeName?: string; + shoppingCartId?: string; + isPrototype?: boolean; + purchaseOrderNumber?: string; + subscriptionNumber?: string; + subscriptionId?: string; + languageCode?: string; + isAnonymous?: boolean; + + // Collections + addresses?: Address[]; + inPayments?: PaymentIn[]; + items?: LineItem[]; + shipments?: Shipment[]; + + // Totals + total?: number; + subTotal?: number; + subTotalWithTax?: number; + subTotalDiscount?: number; + subTotalDiscountWithTax?: number; + subTotalTaxTotal?: number; + + // Shipping totals + shippingTotal?: number; + shippingTotalWithTax?: number; + shippingSubTotal?: number; + shippingSubTotalWithTax?: number; + shippingDiscountTotal?: number; + shippingDiscountTotalWithTax?: number; + shippingTaxTotal?: number; + + // Payment totals + paymentTotal?: number; + paymentTotalWithTax?: number; + paymentSubTotal?: number; + paymentSubTotalWithTax?: number; + paymentDiscountTotal?: number; + paymentDiscountTotalWithTax?: number; + paymentTaxTotal?: number; + + // Discount totals + discountAmount?: number; + discountTotal?: number; + discountTotalWithTax?: number; + + // Fee totals + fee?: number; + feeWithTax?: number; + feeTotal?: number; + feeTotalWithTax?: number; + + // Handling + handlingTotal?: number; + handlingTotalWithTax?: number; + + // Security + scopes?: string[]; +} + +/** + * Configuration item for configurable products + */ +export interface ConfigurationItem { + id?: string; + productId?: string; + name?: string; + sku?: string; + quantity?: number; + imageUrl?: string; + sectionId?: string; + sectionName?: string; + currency?: string; + listPrice?: number; + listPriceWithTax?: number; + salePrice?: number; + salePriceWithTax?: number; + files?: ConfigurationItemFile[]; +} + +export interface ConfigurationItemFile { + id?: string; + url?: string; + name?: string; + size?: number; + contentType?: string; +} + +/** + * Line item in an order + */ +export interface LineItem + extends AuditableEntity, + HasOuterId, + HasTaxDetalization, + SupportsCancellation, + HasDimension, + Taxable, + HasDiscounts, + HasFeesDetalization { + // Product info + productId?: string; + sku?: string; + productType?: string; + catalogId?: string; + categoryId?: string; + name?: string; + productOuterId?: string; + vendorId?: string; + imageUrl?: string; + comment?: string; + status?: string; + + // Quantity + quantity?: number; + reserveQuantity?: number; + + // Pricing + priceId?: string; + currency?: string; + price?: number; + priceWithTax?: number; + listTotal?: number; + listTotalWithTax?: number; + placedPrice?: number; + placedPriceWithTax?: number; + extendedPrice?: number; + extendedPriceWithTax?: number; + + // Discounts + discountAmount?: number; + isDiscountAmountRounded?: boolean; + discountAmountWithTax?: number; + discountTotal?: number; + discountTotalWithTax?: number; + + // Fees + fee?: number; + feeWithTax?: number; + + // Flags + isGift?: boolean; + isConfigured?: boolean; + + // Fulfillment + shippingMethodCode?: string; + fulfillmentLocationCode?: string; + fulfillmentCenterId?: string; + fulfillmentCenterName?: string; + + // Dynamic properties + dynamicProperties?: DynamicObjectProperty[]; + + // Configuration + configurationItems?: ConfigurationItem[]; + + // Object type + objectType?: string; +} + +/** + * Response group for controlling data loading + */ +export type CustomerOrderResponseGroup = + | 'None' + | 'WithItems' + | 'WithInPayments' + | 'WithShipments' + | 'WithAddresses' + | 'WithDiscounts' + | 'WithDynamicProperties' + | 'Full'; + +/** + * Dashboard statistics result + */ +export interface DashboardStatisticsResult { + startDate?: string; + endDate?: string; + revenue?: number; + orderCount?: number; + customersCount?: number; + avgOrderValue?: number; + avgOrderValuePeriodDetails?: QuarterPeriodMoney[]; + revenuePeriodDetails?: QuarterPeriodMoney[]; + revenuePerCustomerPeriodDetails?: QuarterPeriodMoney[]; + orderCountPeriodDetails?: QuarterPeriodMoney[]; + orderAveragePeriodDetails?: QuarterPeriodMoney[]; +} + +export interface QuarterPeriodMoney { + year?: number; + quarter?: number; + amount?: number; +} + +/** + * Order status changed entry for notifications + */ +export interface OrderOperationStatusChangedEntry { + operationType?: string; + oldStatus?: string; + newStatus?: string; + changedDate?: string; +} diff --git a/virtocommerce-adapter/src/models/payment.ts b/virtocommerce-adapter/src/models/payment.ts new file mode 100644 index 0000000..1c96b20 --- /dev/null +++ b/virtocommerce-adapter/src/models/payment.ts @@ -0,0 +1,280 @@ +/** + * Payment-related models for VirtoCommerce + * Based on VirtoCommerce.OrdersModule.Core.Model + */ + +import type { + AuditableEntity, + Address, + HasOuterId, + Taxable, + HasDiscounts, + HasTaxDetalization, + HasFeesDetalization, +} from './base.js'; +import type { OrderOperation } from './order.js'; + +/** + * Payment status enumeration + */ +export type PaymentStatus = + | 'New' + | 'Pending' + | 'Authorized' + | 'Paid' + | 'PartiallyRefunded' + | 'Refunded' + | 'Voided' + | 'Custom' + | 'Cancelled' + | 'Declined' + | 'Error'; + +/** + * Refund status enumeration + */ +export type RefundStatus = + | 'Pending' + | 'Approved' + | 'Rejected' + | 'Processed'; + +/** + * Capture status enumeration + */ +export type CaptureStatus = + | 'Pending' + | 'Approved' + | 'Rejected' + | 'Processed'; + +/** + * Refund reason codes + */ +export type RefundReasonCode = + | 'CustomerRequest' + | 'Duplicate' + | 'Fraudulent' + | 'RequestedByCustomer' + | 'Other'; + +/** + * Payment method information + */ +export interface PaymentMethod { + code?: string; + name?: string; + description?: string; + logoUrl?: string; + isAvailableForPartial?: boolean; + priority?: number; + isActive?: boolean; + storeId?: string; + taxType?: string; + paymentMethodType?: number; + paymentMethodGroupType?: number; +} + +/** + * Payment gateway transaction + */ +export interface PaymentGatewayTransaction extends AuditableEntity { + amount?: number; + currency?: string; + isProcessed?: boolean; + processedDate?: string; + processError?: string; + processAttemptCount?: number; + requestData?: string; + responseData?: string; + responseCode?: string; + gatewayIpAddress?: string; + type?: string; + status?: string; + note?: string; +} + +/** + * Process payment request result + */ +export interface ProcessPaymentRequestResult { + isSuccess?: boolean; + errorMessage?: string; + redirectUrl?: string; + htmlForm?: string; + outerId?: string; + newPaymentStatus?: PaymentStatus; +} + +/** + * Payment in - incoming payment for an order + */ +export interface PaymentIn + extends OrderOperation, + HasTaxDetalization, + Taxable, + HasDiscounts, + HasFeesDetalization { + orderId?: string; + purpose?: string; + gatewayCode?: string; + paymentMethod?: PaymentMethod; + vendorId?: string; + + // Organization info + organizationId?: string; + organizationName?: string; + + // Customer info + customerId?: string; + customerName?: string; + + // Dates + incomingDate?: string; + authorizedDate?: string; + capturedDate?: string; + voidedDate?: string; + + // Status + paymentStatus?: PaymentStatus; + + // Address + billingAddress?: Address; + + // Process result + processPaymentResult?: ProcessPaymentRequestResult; + + // Pricing + price?: number; + priceWithTax?: number; + total?: number; + totalWithTax?: number; + discountAmount?: number; + discountAmountWithTax?: number; + + // Collections + transactions?: PaymentGatewayTransaction[]; + refunds?: Refund[]; + captures?: Capture[]; + + // Object type + objectType?: string; +} + +/** + * Refund item + */ +export interface RefundItem extends AuditableEntity, HasOuterId { + lineItemId?: string; + lineItem?: RefundLineItemRef; + quantity?: number; +} + +/** + * Reference to line item for refunds + */ +export interface RefundLineItemRef { + id?: string; + sku?: string; + name?: string; +} + +/** + * Refund - represents a refund on a payment + */ +export interface Refund extends OrderOperation { + amount?: number; + reasonCode?: RefundReasonCode; + refundStatus?: RefundStatus; + reasonMessage?: string; + rejectReasonMessage?: string; + vendorId?: string; + transactionId?: string; + customerOrderId?: string; + paymentId?: string; + items?: RefundItem[]; + objectType?: string; +} + +/** + * Capture item + */ +export interface CaptureItem extends AuditableEntity, HasOuterId { + lineItemId?: string; + lineItem?: CaptureLineItemRef; + quantity?: number; +} + +/** + * Reference to line item for captures + */ +export interface CaptureLineItemRef { + id?: string; + sku?: string; + name?: string; +} + +/** + * Capture - represents a capture of authorized funds + */ +export interface Capture extends OrderOperation { + amount?: number; + vendorId?: string; + transactionId?: string; + customerOrderId?: string; + paymentId?: string; + closeTransaction?: boolean; + items?: CaptureItem[]; + objectType?: string; +} + +/** + * Refund order payment request + */ +export interface RefundOrderPaymentRequest { + paymentId?: string; + orderId?: string; + amount?: number; + reasonCode?: RefundReasonCode; + reasonMessage?: string; + outerId?: string; + transactionId?: string; + items?: RefundItem[]; +} + +/** + * Refund order payment result + */ +export interface RefundOrderPaymentResult { + isSuccess?: boolean; + errorMessage?: string; + refund?: Refund; +} + +/** + * Capture order payment request + */ +export interface CaptureOrderPaymentRequest { + paymentId?: string; + orderId?: string; + amount?: number; + outerId?: string; + closeTransaction?: boolean; + items?: CaptureItem[]; +} + +/** + * Capture order payment result + */ +export interface CaptureOrderPaymentResult { + isSuccess?: boolean; + errorMessage?: string; + capture?: Capture; +} + +/** + * Payment callback parameters + */ +export interface PaymentCallbackParameters { + parameters?: Record; +} diff --git a/virtocommerce-adapter/src/models/search.ts b/virtocommerce-adapter/src/models/search.ts new file mode 100644 index 0000000..4ba3aaf --- /dev/null +++ b/virtocommerce-adapter/src/models/search.ts @@ -0,0 +1,225 @@ +/** + * Search-related models for VirtoCommerce + * Based on VirtoCommerce.Platform.Core and module-specific search models + */ + +import type { CustomerOrder } from './order.js'; +import type { Member, Contact, Organization, Employee, Vendor } from './customer.js'; + +/** + * Base search criteria + */ +export interface SearchCriteriaBase { + responseGroup?: string; + objectType?: string; + objectTypes?: string[]; + objectIds?: string[]; + keyword?: string; + searchPhrase?: string; + languageCode?: string; + sort?: string; + skip?: number; + take?: number; +} + +/** + * Generic search result + */ +export interface SearchResult { + totalCount?: number; + results?: T[]; +} + +/** + * Customer order search criteria + */ +export interface CustomerOrderSearchCriteria extends SearchCriteriaBase { + number?: string; + numbers?: string[]; + status?: string; + statuses?: string[]; + storeIds?: string[]; + customerId?: string; + customerIds?: string[]; + employeeId?: string; + employeeIds?: string[]; + organizationId?: string; + organizationIds?: string[]; + startDate?: string; + endDate?: string; + isPrototype?: boolean; + subscriptionIds?: string[]; + withPrototypes?: boolean; + onlyRecurring?: boolean; + operationId?: string; +} + +/** + * Customer order search result + */ +export interface CustomerOrderSearchResult extends SearchResult { +} + +/** + * Member search criteria + */ +export interface MemberSearchCriteria extends SearchCriteriaBase { + memberId?: string; + memberIds?: string[]; + memberType?: string; + memberTypes?: string[]; + group?: string; + groups?: string[]; + deepSearch?: boolean; + outerIds?: string[]; +} + +/** + * Member search result + */ +export interface MemberSearchResult extends SearchResult { +} + +/** + * Contact search criteria + */ +export interface ContactSearchCriteria extends MemberSearchCriteria { + organizationId?: string; + organizationIds?: string[]; +} + +/** + * Contact search result + */ +export interface ContactSearchResult extends SearchResult { +} + +/** + * Organization search criteria + */ +export interface OrganizationSearchCriteria extends MemberSearchCriteria { + parentOrganizationId?: string; +} + +/** + * Organization search result + */ +export interface OrganizationSearchResult extends SearchResult { +} + +/** + * Employee search criteria + */ +export interface EmployeeSearchCriteria extends MemberSearchCriteria { + organizationId?: string; +} + +/** + * Employee search result + */ +export interface EmployeeSearchResult extends SearchResult { +} + +/** + * Vendor search criteria + */ +export interface VendorSearchCriteria extends MemberSearchCriteria { + vendorGroupName?: string; +} + +/** + * Vendor search result + */ +export interface VendorSearchResult extends SearchResult { +} + +/** + * Payment search criteria + */ +export interface PaymentSearchCriteria extends SearchCriteriaBase { + orderNumber?: string; + orderNumbers?: string[]; + orderId?: string; + orderIds?: string[]; + status?: string; + statuses?: string[]; + customerId?: string; + customerIds?: string[]; + storeIds?: string[]; + startDate?: string; + endDate?: string; + capturedStartDate?: string; + capturedEndDate?: string; + authorizedStartDate?: string; + authorizedEndDate?: string; +} + +/** + * Shipment search criteria + */ +export interface ShipmentSearchCriteria extends SearchCriteriaBase { + orderNumber?: string; + orderNumbers?: string[]; + orderId?: string; + orderIds?: string[]; + status?: string; + statuses?: string[]; + customerId?: string; + customerIds?: string[]; + storeIds?: string[]; + fulfillmentCenterId?: string; + fulfillmentCenterIds?: string[]; + employeeId?: string; + employeeIds?: string[]; + startDate?: string; + endDate?: string; + shipmentMethodCode?: string; + shipmentMethodOption?: string; + withShippingMethod?: boolean; +} + +/** + * Indexed search criteria for advanced search + */ +export interface IndexedSearchCriteria extends SearchCriteriaBase { + rawQuery?: string; + isFuzzySearch?: boolean; + fuzzy?: boolean; + fuzziness?: number; + includeAggregations?: boolean; + aggregations?: string[]; + userGroups?: string[]; +} + +/** + * Aggregation item + */ +export interface AggregationItem { + value?: unknown; + count?: number; + isApplied?: boolean; + label?: string; + labels?: Record; + lowerBound?: number; + upperBound?: number; + includesLower?: boolean; + includesUpper?: boolean; +} + +/** + * Aggregation + */ +export interface Aggregation { + aggregationType?: string; + field?: string; + label?: string; + labels?: Record; + items?: AggregationItem[]; +} + +/** + * Indexed search result + */ +export interface IndexedSearchResult extends SearchResult { + aggregations?: Aggregation[]; +} diff --git a/virtocommerce-adapter/src/models/shipment.ts b/virtocommerce-adapter/src/models/shipment.ts new file mode 100644 index 0000000..7ca9483 --- /dev/null +++ b/virtocommerce-adapter/src/models/shipment.ts @@ -0,0 +1,142 @@ +/** + * Shipment-related models for VirtoCommerce + * Based on VirtoCommerce.OrdersModule.Core.Model + */ + +import type { + AuditableEntity, + Address, + HasDimension, + SupportsCancellation, + HasOuterId, + Taxable, + HasDiscounts, + HasTaxDetalization, + HasFeesDetalization, +} from './base.js'; +import type { OrderOperation, LineItem } from './order.js'; +import type { PaymentIn } from './payment.js'; + +/** + * Shipping method information + */ +export interface ShippingMethod { + code?: string; + name?: string; + description?: string; + logoUrl?: string; + taxType?: string; + isActive?: boolean; + priority?: number; + storeId?: string; +} + +/** + * Shipment - represents a fulfillment/delivery + */ +export interface Shipment + extends OrderOperation, + HasTaxDetalization, + SupportsCancellation, + Taxable, + HasDiscounts, + HasFeesDetalization, + HasDimension { + // Organization/Fulfillment info + organizationId?: string; + organizationName?: string; + fulfillmentCenterId?: string; + fulfillmentCenterName?: string; + employeeId?: string; + employeeName?: string; + vendorId?: string; + + // Shipping method + shipmentMethodCode?: string; + shipmentMethodOption?: string; + shippingMethod?: ShippingMethod; + + // Parent order reference + customerOrderId?: string; + customerOrder?: CustomerOrderRef; + + // Collections + items?: ShipmentItem[]; + packages?: ShipmentPackage[]; + inPayments?: PaymentIn[]; + + // Address + deliveryAddress?: Address; + pickupLocationId?: string; + + // Pricing + price?: number; + priceWithTax?: number; + total?: number; + totalWithTax?: number; + discountAmount?: number; + discountAmountWithTax?: number; + fee?: number; + feeWithTax?: number; + + // Tracking + trackingNumber?: string; + trackingUrl?: string; + deliveryDate?: string; + + // Object type + objectType?: string; +} + +/** + * Reference to parent customer order (to avoid circular dependency) + */ +export interface CustomerOrderRef { + id?: string; + number?: string; +} + +/** + * Item within a shipment + */ +export interface ShipmentItem extends AuditableEntity, HasOuterId { + lineItemId?: string; + lineItem?: LineItem; + barCode?: string; + quantity?: number; + status?: string; +} + +/** + * Package within a shipment + */ +export interface ShipmentPackage extends AuditableEntity, HasDimension { + barCode?: string; + packageType?: string; + items?: ShipmentItem[]; +} + +/** + * Fulfillment center information + */ +export interface FulfillmentCenter { + id?: string; + name?: string; + description?: string; + geoLocation?: string; + address?: Address; + outerId?: string; +} + +/** + * Shipment status enumeration + */ +export type ShipmentStatus = + | 'New' + | 'PickPack' + | 'ReadyToShip' + | 'Shipped' + | 'Delivered' + | 'Cancelled' + | 'OnHold' + | 'PartiallyShipped'; diff --git a/virtocommerce-adapter/src/services/customer.service.ts b/virtocommerce-adapter/src/services/customer.service.ts index 5117915..b161109 100644 --- a/virtocommerce-adapter/src/services/customer.service.ts +++ b/virtocommerce-adapter/src/services/customer.service.ts @@ -4,6 +4,7 @@ import type { Customer, FulfillmentToolResult, GetCustomersInput } from '@cof-org/mcp'; import type { YourFulfillmentCustomer } from '../types.js'; +import type { Contact, MemberSearchCriteria } from '../models/index.js'; import { BaseService } from './base.service.js'; import { CustomerTransformer } from '../transformers/customer.transformer.js'; import { mapCustomerFilters } from '../mappers/filter.mappers.js'; @@ -22,6 +23,76 @@ export class CustomerService extends BaseService { this.transformer.setTenantId(tenantId); } + /** + * Get a customer by ID from VirtoCommerce + */ + async getCustomerById(customerId: string): Promise { + if (!customerId) { + return null; + } + + try { + // VirtoCommerce API: GET /api/members/{id} + const response = await this.client.get(`/api/members/${customerId}`); + + if (!response.success || !response.data) { + return null; + } + + return response.data; + } catch { + return null; + } + } + + /** + * Get multiple customers by IDs from VirtoCommerce + */ + async getCustomersByIds(customerIds: string[]): Promise> { + const customerMap = new Map(); + + if (!customerIds.length) { + return customerMap; + } + + // Remove duplicates + const uniqueIds = [...new Set(customerIds.filter(Boolean))]; + + try { + // VirtoCommerce API: POST /api/members/search + const searchCriteria: MemberSearchCriteria = { + objectIds: uniqueIds, + memberTypes: ['Contact'], + take: uniqueIds.length, + responseGroup: 'Full', + }; + + const response = await this.client.post<{ results?: Contact[] }>( + '/api/members/search', + searchCriteria + ); + + if (response.success && response.data?.results) { + for (const contact of response.data.results) { + if (contact.id) { + customerMap.set(contact.id, contact); + } + } + } + } catch { + // Return empty map on error - orders will use fallback data + } + + return customerMap; + } + + /** + * Transform a VirtoCommerce Contact to MCP Customer + */ + contactToMcpCustomer(contact: Contact): Customer { + return this.transformer.fromContact(contact); + } + async getCustomers(input: GetCustomersInput): Promise> { try { const response = await this.client.get( diff --git a/virtocommerce-adapter/src/services/order.service.ts b/virtocommerce-adapter/src/services/order.service.ts index f8af291..0c5df56 100644 --- a/virtocommerce-adapter/src/services/order.service.ts +++ b/virtocommerce-adapter/src/services/order.service.ts @@ -12,23 +12,31 @@ import type { GetOrdersInput, } from '@cof-org/mcp'; import type { YourFulfillmentOrder, YourFulfillmentApiResponse } from '../types.js'; -import type { CustomerOrder, CustomerOrderSearchResult } from '../models/customer-order.js'; +import type { + CustomerOrder, + CustomerOrderSearchResult, + CustomerOrderSearchCriteria, +} from '../models/index.js'; import { BaseService } from './base.service.js'; import { OrderTransformer } from '../transformers/order.transformer.js'; -import { mapOrderFilters } from '../mappers/filter.mappers.js'; +import { CustomerService } from './customer.service.js'; +import { mapOrderFiltersToSearchCriteria } from '../mappers/filter.mappers.js'; import { getErrorMessage } from '../utils/type-guards.js'; import { ApiClient } from '../utils/api-client.js'; export class OrderService extends BaseService { private transformer: OrderTransformer; + private customerService: CustomerService; constructor(client: ApiClient, tenantId: string = 'default-workspace', workspace?: string) { super(client); this.transformer = new OrderTransformer(tenantId, workspace); + this.customerService = new CustomerService(client, tenantId); } setTenantId(tenantId: string): void { this.transformer.setTenantId(tenantId); + this.customerService.setTenantId(tenantId); } setWorkspace(workspace: string): void { @@ -115,10 +123,12 @@ export class OrderService extends BaseService { async getOrders(input: GetOrdersInput): Promise> { try { + // Build VirtoCommerce search criteria from input + const searchCriteria: CustomerOrderSearchCriteria = mapOrderFiltersToSearchCriteria(input); + const response = await this.client.post( '/api/order/customerOrders/search', - {}, - mapOrderFilters(input) + searchCriteria ); if (!response.success) { @@ -126,7 +136,17 @@ export class OrderService extends BaseService { } const results = (response.data as CustomerOrderSearchResult)?.results ?? []; - const orders = this.transformer.toMcpOrders(results); + + // Extract unique customer IDs from orders + const customerIds = results + .map((order) => order.customerId) + .filter((id): id is string => !!id); + + // Load customers by IDs + const customerMap = await this.customerService.getCustomersByIds(customerIds); + + // Transform orders with loaded customer data + const orders = this.transformer.toMcpOrders(results, customerMap); return this.success<{ orders: Order[] }>({ orders }); } catch (error: unknown) { return this.failure<{ orders: Order[] }>(`Order lookup failed: ${getErrorMessage(error)}`, error); diff --git a/virtocommerce-adapter/src/transformers/address.transformer.ts b/virtocommerce-adapter/src/transformers/address.transformer.ts index dc8550d..676228c 100644 --- a/virtocommerce-adapter/src/transformers/address.transformer.ts +++ b/virtocommerce-adapter/src/transformers/address.transformer.ts @@ -4,7 +4,7 @@ import type { Address, CustomerAddress } from '@cof-org/mcp'; import type { YourFulfillmentAddress } from '../types.js'; -import type { Address as VirtoAddress } from '../models/customer-order.js'; +import type { Address as VirtoAddress } from '../models/index.js'; import { BaseTransformer } from './base.js'; export class AddressTransformer extends BaseTransformer { diff --git a/virtocommerce-adapter/src/transformers/customer.transformer.ts b/virtocommerce-adapter/src/transformers/customer.transformer.ts index 30daffe..5d22063 100644 --- a/virtocommerce-adapter/src/transformers/customer.transformer.ts +++ b/virtocommerce-adapter/src/transformers/customer.transformer.ts @@ -3,8 +3,8 @@ */ import type { Customer } from '@cof-org/mcp'; -import type { YourFulfillmentCustomer, YourFulfillmentAddress } from '../types.js'; -import type { CustomerOrder } from '../models/customer-order.js'; +import type { YourFulfillmentCustomer } from '../types.js'; +import type { CustomerOrder, Contact, Address } from '../models/index.js'; import { BaseTransformer } from './base.js'; import { AddressTransformer } from './address.transformer.js'; @@ -21,6 +21,39 @@ export class CustomerTransformer extends BaseTransformer { this.addressTransformer.setTenantId(tenantId); } + /** + * Transform VirtoCommerce Contact to MCP Customer + */ + fromContact(contact: Contact): Customer { + // Get primary email from emails array + const primaryEmail = contact.emails?.[0]; + + // Get primary phone from phones array + const primaryPhone = contact.phones?.[0]; + + // Transform addresses + const addresses = this.addressTransformer.toCustomerAddresses( + contact.addresses as Address[] | undefined + ); + + return { + id: contact.id ?? '', + externalId: contact.outerId, + firstName: contact.firstName ?? '', + lastName: contact.lastName ?? '', + email: primaryEmail, + phone: primaryPhone, + addresses, + tags: contact.groups, + createdAt: contact.createdDate ?? this.now(), + updatedAt: contact.modifiedDate ?? this.now(), + tenantId: this.tenantId, + status: contact.status ?? 'active', + type: 'customer', + customFields: this.extractCustomFields(contact), + }; + } + /** * Transform YourFulfillment customer format to MCP Customer */ @@ -41,23 +74,25 @@ export class CustomerTransformer extends BaseTransformer { } /** - * Extract and transform customer from VirtoCommerce order + * Create a minimal customer from order data (fallback when customer not found) */ fromOrder(order: CustomerOrder): Customer { - const customerData: YourFulfillmentCustomer = { - id: order.customerId, - email: order.customerName, // Using customerName as email fallback - first_name: '', - last_name: order.customerName, + return { + id: order.customerId ?? '', + firstName: '', + lastName: order.customerName ?? '', + email: undefined, phone: undefined, - created_at: order.createdDate, - updated_at: order.modifiedDate, - addresses: order.addresses as unknown as YourFulfillmentAddress[], + addresses: this.addressTransformer.toCustomerAddresses( + order.addresses as Address[] | undefined + ), tags: [], - metadata: {}, + createdAt: order.createdDate ?? this.now(), + updatedAt: order.modifiedDate ?? this.now(), + tenantId: this.tenantId, + status: 'active', + type: 'customer', }; - - return this.toMcpCustomer(customerData); } /** @@ -66,4 +101,29 @@ export class CustomerTransformer extends BaseTransformer { toMcpCustomers(customers: YourFulfillmentCustomer[]): Customer[] { return customers.map((customer) => this.toMcpCustomer(customer)); } + + /** + * Transform multiple VirtoCommerce contacts to MCP Customers + */ + fromContacts(contacts: Contact[]): Customer[] { + return contacts.map((contact) => this.fromContact(contact)); + } + + /** + * Extract custom fields from contact's dynamic properties + */ + private extractCustomFields(contact: Contact): { name: string; value: string }[] | undefined { + if (!contact.dynamicProperties?.length) { + return undefined; + } + + const fields = contact.dynamicProperties + .filter((prop) => prop.values?.length) + .map((prop) => ({ + name: prop.name ?? '', + value: String(prop.values?.[0]?.value ?? ''), + })); + + return fields.length ? fields : undefined; + } } diff --git a/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts b/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts index dec3ee0..54bcea3 100644 --- a/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts +++ b/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts @@ -2,7 +2,7 @@ * Fulfillment/Shipment transformation utilities */ -import type { Fulfillment, FulfillOrderInput, Address } from '@cof-org/mcp'; +import type { Fulfillment, FulfillOrderInput } from '@cof-org/mcp'; import type { YourFulfillmentShipment } from '../types.js'; import { BaseTransformer } from './base.js'; import { AddressTransformer } from './address.transformer.js'; diff --git a/virtocommerce-adapter/src/transformers/order.transformer.ts b/virtocommerce-adapter/src/transformers/order.transformer.ts index 403d615..fc925ea 100644 --- a/virtocommerce-adapter/src/transformers/order.transformer.ts +++ b/virtocommerce-adapter/src/transformers/order.transformer.ts @@ -11,7 +11,7 @@ import type { UpdateOrderInput, } from '@cof-org/mcp'; import { STATUS_MAP } from '../types.js'; -import type { CustomerOrder, LineItem } from '../models/customer-order.js'; +import type { CustomerOrder, LineItem, DynamicObjectProperty, Contact } from '../models/index.js'; import { BaseTransformer } from './base.js'; import { AddressTransformer } from './address.transformer.js'; import { CustomerTransformer } from './customer.transformer.js'; @@ -40,25 +40,34 @@ export class OrderTransformer extends BaseTransformer { /** * Transform VirtoCommerce order to MCP Order format + * @param order - The VirtoCommerce CustomerOrder + * @param contact - Optional loaded Contact for the customer (if available) */ - toMcpOrder(order: CustomerOrder): Order { + toMcpOrder(order: CustomerOrder, contact?: Contact): Order { const shippingAddress = order.shipments ?.map((x) => x.deliveryAddress) .find((x) => x); + const orderId = order.id ?? ''; + + // Use loaded contact if available, otherwise fall back to order data + const customer = contact + ? this.customerTransformer.fromContact(contact) + : this.customerTransformer.fromOrder(order); + return { - id: order.id, - externalId: order.id, - name: order.number, - status: this.mapOrderStatus(order.status), + id: orderId, + externalId: orderId, + name: order.number ?? '', + status: this.mapOrderStatus(order.status ?? ''), totalPrice: order.total, currency: order.currency, - customer: this.customerTransformer.fromOrder(order), + customer, shippingAddress: this.addressTransformer.toMcpAddress(shippingAddress), billingAddress: this.addressTransformer.toMcpAddress(order.addresses?.[0]), - lineItems: order.items?.map((item, index) => this.toOrderLineItem(order.id, item, index)), - createdAt: order.createdDate, - updatedAt: order.modifiedDate, + lineItems: order.items?.map((item, index) => this.toOrderLineItem(orderId, item, index)) ?? [], + createdAt: order.createdDate ?? this.now(), + updatedAt: order.modifiedDate ?? this.now(), tenantId: this.tenantId, customFields: this.transformDynamicProperties(order.dynamicProperties), orderNote: order.comment, @@ -66,10 +75,15 @@ export class OrderTransformer extends BaseTransformer { } /** - * Transform multiple orders + * Transform multiple orders with optional customer data + * @param orders - Array of VirtoCommerce CustomerOrders + * @param customerMap - Optional map of customer IDs to Contact objects */ - toMcpOrders(orders: CustomerOrder[]): Order[] { - return orders.map((order) => this.toMcpOrder(order)); + toMcpOrders(orders: CustomerOrder[], customerMap?: Map): Order[] { + return orders.map((order) => { + const contact = order.customerId ? customerMap?.get(order.customerId) : undefined; + return this.toMcpOrder(order, contact); + }); } /** @@ -156,13 +170,17 @@ export class OrderTransformer extends BaseTransformer { * Transform line item from VirtoCommerce format */ private toOrderLineItem(orderId: string, item: LineItem, index: number): OrderLineItem { + const sku = item.sku ?? ''; + const quantity = item.quantity ?? 0; + const price = item.price ?? 0; + return { - id: item.id ?? `${orderId}-${item.sku}-${index}`, - sku: item.sku, - quantity: item.quantity, - unitPrice: item.price, - totalPrice: item.extendedPrice ?? item.price * item.quantity, - name: item.name, + id: item.id ?? `${orderId}-${sku}-${index}`, + sku, + quantity, + unitPrice: price, + totalPrice: item.extendedPrice ?? price * quantity, + name: item.name ?? '', }; } @@ -170,7 +188,7 @@ export class OrderTransformer extends BaseTransformer { * Transform dynamic properties to custom fields */ private transformDynamicProperties( - properties?: CustomerOrder['dynamicProperties'] + properties?: DynamicObjectProperty[] ): CustomField[] | undefined { if (!properties?.length) { return undefined; @@ -179,8 +197,8 @@ export class OrderTransformer extends BaseTransformer { const entries = properties .filter((prop) => prop.values?.length) .map((prop) => ({ - name: prop.name, - value: String(prop.values[0]?.value ?? ''), + name: prop.name ?? '', + value: String(prop.values?.[0]?.value ?? ''), })); return entries.length ? entries : undefined; diff --git a/virtocommerce-adapter/src/utils/api-client.ts b/virtocommerce-adapter/src/utils/api-client.ts index 8b6c1f1..4ddbb13 100644 --- a/virtocommerce-adapter/src/utils/api-client.ts +++ b/virtocommerce-adapter/src/utils/api-client.ts @@ -41,7 +41,7 @@ export class ApiClient { Accept: 'application/json', // Add your authentication header // Different APIs use different auth methods: - 'X-API-Key': config.apiKey, + 'api_key': config.apiKey, // 'Authorization': `Bearer ${config.apiKey}`, // 'API-Key': config.apiKey, ...config.headers, diff --git a/virtocommerce-adapter/tests/adapter.test.ts b/virtocommerce-adapter/tests/adapter.test.ts index 7b7d5a2..3fee1fd 100644 --- a/virtocommerce-adapter/tests/adapter.test.ts +++ b/virtocommerce-adapter/tests/adapter.test.ts @@ -5,8 +5,8 @@ * Replace with actual tests for your Fulfillment integration. */ -import { jest } from '@jest/globals'; -import { YourFulfillmentAdapter } from '../src/adapter.js'; +import { jest, describe, it, expect, beforeEach } from '@jest/globals'; +import { VirtoCommerceFulfillmentAdapter } from '../src/adapter.js'; import { ApiClient } from '../src/utils/api-client.js'; import type { CreateSalesOrderInput, @@ -16,8 +16,8 @@ import type { GetInventoryInput, } from '@cof-org/mcp'; -describe('YourFulfillmentAdapter', () => { - let adapter: YourFulfillmentAdapter; +describe('VirtoCommerceFulfillmentAdapter', () => { + let adapter: VirtoCommerceFulfillmentAdapter; let mockApiClient: ApiClient; let getSpy: jest.MockedFunction; let postSpy: jest.MockedFunction; @@ -25,9 +25,9 @@ describe('YourFulfillmentAdapter', () => { beforeEach(() => { // Create adapter instance - adapter = new YourFulfillmentAdapter({ - apiUrl: 'https://api.test.yourfulfillment.com', - apiKey: 'test-api-key', + adapter = new VirtoCommerceFulfillmentAdapter({ + apiUrl: 'https://localhost:5001', + apiKey: '76bf85d9-196e-4d4a-a6d7-6765102361c9', workspace: 'test-workspace', timeout: 5000, debugMode: false, diff --git a/virtocommerce-adapter/tsconfig.json b/virtocommerce-adapter/tsconfig.json index e2146a6..318a2ae 100644 --- a/virtocommerce-adapter/tsconfig.json +++ b/virtocommerce-adapter/tsconfig.json @@ -6,7 +6,8 @@ "ES2022" ], "types": [ - "node" + "node", + "jest" ], "outDir": "./dist", "rootDir": "./src", @@ -44,7 +45,6 @@ ], "exclude": [ "node_modules", - "dist", - "tests" + "dist" ] } \ No newline at end of file From a26cfcd8d806d21a42dbb1126337ce369f569ee6 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Mon, 26 Jan 2026 13:07:33 +0000 Subject: [PATCH 03/51] small fixes --- .../src/mappers/filter.mappers.ts | 6 +- virtocommerce-adapter/src/models/search.ts | 2 + .../src/transformers/order.transformer.ts | 2 +- virtocommerce-adapter/src/types.ts | 4 +- virtocommerce-adapter/tests/adapter.test.ts | 61 +-- .../getOrders/getOrdersByIdResponse.json | 366 ++++++++++++++++++ virtocommerce-adapter/todo.md | 1 + 7 files changed, 391 insertions(+), 51 deletions(-) create mode 100644 virtocommerce-adapter/tests/fixtures/getOrders/getOrdersByIdResponse.json create mode 100644 virtocommerce-adapter/todo.md diff --git a/virtocommerce-adapter/src/mappers/filter.mappers.ts b/virtocommerce-adapter/src/mappers/filter.mappers.ts index cdff42c..c7551b8 100644 --- a/virtocommerce-adapter/src/mappers/filter.mappers.ts +++ b/virtocommerce-adapter/src/mappers/filter.mappers.ts @@ -22,7 +22,11 @@ export function mapOrderFiltersToSearchCriteria(input: GetOrdersInput): Customer // Map order IDs if (input.ids?.length) { - criteria.objectIds = input.ids; + criteria.ids = input.ids; + } + + if (input.externalIds?.length) { + criteria.outerIds = input.externalIds; } // Map external IDs to numbers (VirtoCommerce uses 'numbers' for order numbers) diff --git a/virtocommerce-adapter/src/models/search.ts b/virtocommerce-adapter/src/models/search.ts index 4ba3aaf..d885633 100644 --- a/virtocommerce-adapter/src/models/search.ts +++ b/virtocommerce-adapter/src/models/search.ts @@ -34,6 +34,8 @@ export interface SearchResult { * Customer order search criteria */ export interface CustomerOrderSearchCriteria extends SearchCriteriaBase { + ids?: string[]; + outerIds?: string[]; number?: string; numbers?: string[]; status?: string; diff --git a/virtocommerce-adapter/src/transformers/order.transformer.ts b/virtocommerce-adapter/src/transformers/order.transformer.ts index fc925ea..1ec0ba6 100644 --- a/virtocommerce-adapter/src/transformers/order.transformer.ts +++ b/virtocommerce-adapter/src/transformers/order.transformer.ts @@ -57,7 +57,7 @@ export class OrderTransformer extends BaseTransformer { return { id: orderId, - externalId: orderId, + externalId: order.outerId, name: order.number ?? '', status: this.mapOrderStatus(order.status ?? ''), totalPrice: order.total, diff --git a/virtocommerce-adapter/src/types.ts b/virtocommerce-adapter/src/types.ts index 64dda65..b71af70 100644 --- a/virtocommerce-adapter/src/types.ts +++ b/virtocommerce-adapter/src/types.ts @@ -138,8 +138,8 @@ export interface YourFulfillmentShipment { // Status mapping configuration export const STATUS_MAP: Record = { - new: 'pending', - processing: 'processing', + New: 'pending', + Processing: 'processing', shipped: 'shipped', delivered: 'delivered', cancelled: 'cancelled', diff --git a/virtocommerce-adapter/tests/adapter.test.ts b/virtocommerce-adapter/tests/adapter.test.ts index 3fee1fd..f960cd3 100644 --- a/virtocommerce-adapter/tests/adapter.test.ts +++ b/virtocommerce-adapter/tests/adapter.test.ts @@ -6,6 +6,7 @@ */ import { jest, describe, it, expect, beforeEach } from '@jest/globals'; +import { readFileSync } from 'fs'; import { VirtoCommerceFulfillmentAdapter } from '../src/adapter.js'; import { ApiClient } from '../src/utils/api-client.js'; import type { @@ -16,6 +17,10 @@ import type { GetInventoryInput, } from '@cof-org/mcp'; +function readResponse(path: string) { + return readFileSync(new URL(`./fixtures/${path}.json`, import.meta.url), 'utf-8'); +} + describe('VirtoCommerceFulfillmentAdapter', () => { let adapter: VirtoCommerceFulfillmentAdapter; let mockApiClient: ApiClient; @@ -330,29 +335,10 @@ describe('VirtoCommerceFulfillmentAdapter', () => { describe('Query Operations', () => { describe('getOrders', () => { it('should get orders by IDs', async () => { - getSpy.mockResolvedValue({ + const response = readResponse('getOrders/getOrdersByIdResponse'); + postSpy.mockResolvedValue({ success: true, - data: [ - { - id: 'ORDER-001', - number: 'ORD-2024-001', - external_id: 'EXT-001', - status: 'processing', - customer: { - id: 'CUST-001', - email: 'test@example.com', - first_name: 'John', - last_name: 'Doe', - }, - items: [], - total: 100.0, - currency: 'USD', - created_at: '2024-01-01T00:00:00Z', - updated_at: '2024-01-01T00:00:00Z', - shipping_address: {}, - billing_address: {}, - }, - ], + data: JSON.parse(response), }); const input: GetOrdersInput = { @@ -365,35 +351,16 @@ describe('VirtoCommerceFulfillmentAdapter', () => { if (result.success) { expect(result.orders).toHaveLength(1); expect(result.orders[0]?.id).toBe('ORDER-001'); - expect(result.orders[0]?.status).toBe('processing'); + expect(result.orders[0]?.status).toBe('pending'); } - expect(getSpy).toHaveBeenCalledWith('/orders', expect.any(Object)); + expect(postSpy).toHaveBeenCalledWith('/api/order/customerOrders/search', expect.any(Object)); }); it('should get orders by external IDs', async () => { - getSpy.mockResolvedValue({ + const response = readResponse('getOrders/getOrdersByIdResponse'); + postSpy.mockResolvedValue({ success: true, - data: [ - { - id: 'ORDER-001', - number: 'ORD-2024-001', - external_id: 'EXT-001', - status: 'processing', - customer: { - id: 'CUST-001', - email: 'test@example.com', - first_name: 'John', - last_name: 'Doe', - }, - items: [], - total: 100.0, - currency: 'USD', - created_at: '2024-01-01T00:00:00Z', - updated_at: '2024-01-01T00:00:00Z', - shipping_address: {}, - billing_address: {}, - }, - ], + data: JSON.parse(response), }); const input: GetOrdersInput = { @@ -410,7 +377,7 @@ describe('VirtoCommerceFulfillmentAdapter', () => { }); it('should handle empty results', async () => { - getSpy.mockResolvedValue({ + postSpy.mockResolvedValue({ success: true, data: [], }); diff --git a/virtocommerce-adapter/tests/fixtures/getOrders/getOrdersByIdResponse.json b/virtocommerce-adapter/tests/fixtures/getOrders/getOrdersByIdResponse.json new file mode 100644 index 0000000..ba3aba1 --- /dev/null +++ b/virtocommerce-adapter/tests/fixtures/getOrders/getOrdersByIdResponse.json @@ -0,0 +1,366 @@ +{ + "totalCount": 1, + "results": [ + { + "rowVersion": "AAAAAAAACd0=", + "customerId": "1a56b7f8-6712-4999-a5ff-5f2699a33e14", + "customerName": "woland", + "channelId": null, + "storeId": "B2B-store", + "storeName": null, + "organizationId": null, + "organizationName": null, + "employeeId": null, + "employeeName": null, + "shoppingCartId": null, + "isPrototype": false, + "purchaseOrderNumber": null, + "subscriptionNumber": null, + "subscriptionId": null, + "objectType": "VirtoCommerce.OrdersModule.Core.Model.CustomerOrder", + "addresses": [], + "inPayments": [ + { + "orderId": "ORDER-001", + "purpose": null, + "gatewayCode": "DefaultManualPaymentMethod", + "paymentMethod": { + "paymentMethodType": "Unknown", + "paymentMethodGroupType": "Manual", + "code": "DefaultManualPaymentMethod", + "name": "Test payment method", + "logoUrl": null, + "isActive": true, + "priority": 0, + "isAvailableForPartial": false, + "allowDeferredPayment": false, + "currency": null, + "price": 0, + "priceWithTax": 0, + "total": 0, + "totalWithTax": 0, + "discountAmount": 0, + "discountAmountWithTax": 0, + "allowCartPayment": false, + "storeId": "B2B-store", + "description": null, + "typeName": "DefaultManualPaymentMethod", + "settings": [ + { + "itHasValues": false, + "objectId": "99424a90-6624-4abf-a843-f7631a37b546", + "objectType": "DefaultManualPaymentMethod", + "isReadOnly": false, + "value": null, + "id": null, + "restartRequired": false, + "moduleId": "VirtoCommerce.Payment", + "groupName": "Payment|DefaultManualPaymentMethod", + "name": "VirtoCommerce.Payment.DefaultManualPaymentMethod.ExampleSetting", + "displayName": null, + "isRequired": false, + "isHidden": false, + "isPublic": false, + "valueType": "ShortText", + "allowedValues": null, + "defaultValue": null, + "isDictionary": false, + "isLocalizable": false + } + ], + "taxType": null, + "taxTotal": 0, + "taxPercentRate": 0, + "taxDetails": null, + "localizedName": { + "values": {} + }, + "id": "99424a90-6624-4abf-a843-f7631a37b546" + }, + "organizationId": null, + "organizationName": null, + "customerId": "1a56b7f8-6712-4999-a5ff-5f2699a33e14", + "customerName": null, + "incomingDate": null, + "billingAddress": null, + "paymentStatus": "New", + "authorizedDate": null, + "capturedDate": "2018-05-03T18:08:19.247Z", + "voidedDate": null, + "processPaymentResult": null, + "price": 0, + "priceWithTax": 0, + "total": 0, + "totalWithTax": 0, + "discountAmount": 0, + "discountAmountWithTax": 0, + "objectType": "VirtoCommerce.OrdersModule.Core.Model.PaymentIn", + "feeDetails": [], + "vendorId": null, + "taxType": null, + "taxTotal": 0, + "taxPercentRate": 0, + "taxDetails": [], + "discounts": [], + "transactions": [], + "refunds": [], + "captures": [], + "operationType": "PaymentIn", + "parentOperationId": null, + "number": "PI180503-00007", + "isApproved": true, + "status": "New", + "comment": null, + "currency": "USD", + "sum": 0, + "outerId": "PI180503-00007", + "childrenOperations": [], + "cancelledState": "Undefined", + "isCancelled": false, + "cancelledDate": null, + "cancelReason": null, + "dynamicProperties": [], + "operationsLog": null, + "createdDate": "2026-01-06T16:43:27.5230653Z", + "modifiedDate": "2026-01-06T16:43:27.5230653Z", + "createdBy": "admin", + "modifiedBy": "admin", + "id": "115a9f4b6faa426bae02bc1573a4915c" + } + ], + "items": [ + { + "priceId": null, + "currency": "USD", + "price": 42.2, + "priceWithTax": 42.2, + "listTotal": 42.2, + "listTotalWithTax": 42.2, + "placedPrice": 42.2, + "placedPriceWithTax": 42.2, + "extendedPrice": 42.2, + "extendedPriceWithTax": 42.2, + "discountAmount": 0, + "isDiscountAmountRounded": false, + "discountAmountWithTax": 0, + "discountTotal": 0, + "discountTotalWithTax": 0, + "fee": 0, + "feeWithTax": 0, + "taxType": null, + "taxTotal": 0, + "taxPercentRate": 0, + "reserveQuantity": 0, + "quantity": 1, + "productId": "baa4931161214690ad51c50787b1ed94", + "sku": "53MF87", + "productType": "Physical", + "catalogId": "7829d35f417e4dd98851f51322f32c23", + "categoryId": "4fbaca886f014767a52f3f38b9df648f", + "name": "1\" Stainless Steel Carriage Bolt, 18-8, NL-19(SM) Finish, 1/4\"-20 Dia/Thread Size, 50 PK", + "productOuterId": null, + "comment": null, + "status": null, + "imageUrl": "catalog/7829d/53MF87/53MF87.jpg", + "isGift": false, + "shippingMethodCode": null, + "fulfillmentLocationCode": null, + "fulfillmentCenterId": null, + "fulfillmentCenterName": null, + "outerId": null, + "feeDetails": [], + "vendorId": null, + "isConfigured": false, + "weightUnit": null, + "weight": null, + "measureUnit": null, + "height": null, + "length": null, + "width": null, + "isCancelled": false, + "cancelledDate": null, + "cancelReason": null, + "objectType": "VirtoCommerce.OrdersModule.Core.Model.LineItem", + "dynamicProperties": [], + "discounts": [], + "taxDetails": [], + "configurationItems": [], + "createdDate": "2026-01-06T16:43:27.523089Z", + "modifiedDate": "2026-01-06T16:43:27.523089Z", + "createdBy": "admin", + "modifiedBy": "admin", + "id": "9a4bef5243964e26929923cf25c2a866" + } + ], + "shipments": [], + "feeDetails": [], + "relevanceScore": null, + "discounts": [], + "discountAmount": 0, + "taxDetails": [], + "scopes": null, + "total": 42.2, + "subTotal": 42.2, + "subTotalWithTax": 42.2, + "subTotalDiscount": 0, + "subTotalDiscountWithTax": 0, + "subTotalTaxTotal": 0, + "shippingTotal": 0, + "shippingTotalWithTax": 0, + "shippingSubTotal": 0, + "shippingSubTotalWithTax": 0, + "shippingDiscountTotal": 0, + "shippingDiscountTotalWithTax": 0, + "shippingTaxTotal": 0, + "paymentTotal": 0, + "paymentTotalWithTax": 0, + "paymentSubTotal": 0, + "paymentSubTotalWithTax": 0, + "paymentDiscountTotal": 0, + "paymentDiscountTotalWithTax": 0, + "paymentTaxTotal": 0, + "discountTotal": 0, + "discountTotalWithTax": 0, + "fee": 0, + "feeWithTax": 0, + "feeTotal": 0, + "feeTotalWithTax": 0, + "handlingTotal": 0, + "handlingTotalWithTax": 0, + "isAnonymous": false, + "taxType": null, + "taxTotal": 0, + "taxPercentRate": 0, + "languageCode": "en-US", + "operationType": "CustomerOrder", + "parentOperationId": null, + "number": "CO180503-00004", + "isApproved": false, + "status": "New", + "comment": null, + "currency": "USD", + "sum": 42.2, + "outerId": "EXT-001", + "childrenOperations": [ + { + "orderId": "ORDER-001", + "purpose": null, + "gatewayCode": "DefaultManualPaymentMethod", + "paymentMethod": { + "paymentMethodType": "Unknown", + "paymentMethodGroupType": "Manual", + "code": "DefaultManualPaymentMethod", + "name": "Test payment method", + "logoUrl": null, + "isActive": true, + "priority": 0, + "isAvailableForPartial": false, + "allowDeferredPayment": false, + "currency": null, + "price": 0, + "priceWithTax": 0, + "total": 0, + "totalWithTax": 0, + "discountAmount": 0, + "discountAmountWithTax": 0, + "allowCartPayment": false, + "storeId": "B2B-store", + "description": null, + "typeName": "DefaultManualPaymentMethod", + "settings": [ + { + "itHasValues": false, + "objectId": "99424a90-6624-4abf-a843-f7631a37b546", + "objectType": "DefaultManualPaymentMethod", + "isReadOnly": false, + "value": null, + "id": null, + "restartRequired": false, + "moduleId": "VirtoCommerce.Payment", + "groupName": "Payment|DefaultManualPaymentMethod", + "name": "VirtoCommerce.Payment.DefaultManualPaymentMethod.ExampleSetting", + "displayName": null, + "isRequired": false, + "isHidden": false, + "isPublic": false, + "valueType": "ShortText", + "allowedValues": null, + "defaultValue": null, + "isDictionary": false, + "isLocalizable": false + } + ], + "taxType": null, + "taxTotal": 0, + "taxPercentRate": 0, + "taxDetails": null, + "localizedName": { + "values": {} + }, + "id": "99424a90-6624-4abf-a843-f7631a37b546" + }, + "organizationId": null, + "organizationName": null, + "customerId": "1a56b7f8-6712-4999-a5ff-5f2699a33e14", + "customerName": null, + "incomingDate": null, + "billingAddress": null, + "paymentStatus": "New", + "authorizedDate": null, + "capturedDate": "2018-05-03T18:08:19.247Z", + "voidedDate": null, + "processPaymentResult": null, + "price": 0, + "priceWithTax": 0, + "total": 0, + "totalWithTax": 0, + "discountAmount": 0, + "discountAmountWithTax": 0, + "objectType": "VirtoCommerce.OrdersModule.Core.Model.PaymentIn", + "feeDetails": [], + "vendorId": null, + "taxType": null, + "taxTotal": 0, + "taxPercentRate": 0, + "taxDetails": [], + "discounts": [], + "transactions": [], + "refunds": [], + "captures": [], + "operationType": "PaymentIn", + "parentOperationId": null, + "number": "PI180503-00007", + "isApproved": true, + "status": "New", + "comment": null, + "currency": "USD", + "sum": 0, + "outerId": "PI180503-00007", + "childrenOperations": [], + "cancelledState": "Undefined", + "isCancelled": false, + "cancelledDate": null, + "cancelReason": null, + "dynamicProperties": [], + "operationsLog": null, + "createdDate": "2026-01-06T16:43:27.5230653Z", + "modifiedDate": "2026-01-06T16:43:27.5230653Z", + "createdBy": "admin", + "modifiedBy": "admin", + "id": "115a9f4b6faa426bae02bc1573a4915c" + } + ], + "cancelledState": "Undefined", + "isCancelled": false, + "cancelledDate": null, + "cancelReason": null, + "dynamicProperties": [], + "operationsLog": null, + "createdDate": "2026-01-06T16:43:27.5230425Z", + "modifiedDate": "2026-01-06T16:43:27.5230425Z", + "createdBy": "admin", + "modifiedBy": "admin", + "id": "ORDER-001" + } + ] +} \ No newline at end of file diff --git a/virtocommerce-adapter/todo.md b/virtocommerce-adapter/todo.md new file mode 100644 index 0000000..c16566b --- /dev/null +++ b/virtocommerce-adapter/todo.md @@ -0,0 +1 @@ +[ ] order statuses mapping \ No newline at end of file From fe94824af4cbbccbb7407128329258ea47dc217d Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Tue, 27 Jan 2026 06:59:15 +0000 Subject: [PATCH 04/51] add cancel order operation --- .claude/settings.local.json | 4 +- .../src/services/order.service.ts | 54 +++++++++--- virtocommerce-adapter/src/types.ts | 9 ++ virtocommerce-adapter/tests/adapter.test.ts | 85 +++++++++++++++---- 4 files changed, 122 insertions(+), 30 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 26d392f..4eececb 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,7 +2,9 @@ "permissions": { "allow": [ "WebFetch(domain:raw.githubusercontent.com)", - "Bash(npx tsc:*)" + "Bash(npx tsc:*)", + "Bash(npm run build:*)", + "Bash(npm test)" ] } } diff --git a/virtocommerce-adapter/src/services/order.service.ts b/virtocommerce-adapter/src/services/order.service.ts index 0c5df56..6b8df74 100644 --- a/virtocommerce-adapter/src/services/order.service.ts +++ b/virtocommerce-adapter/src/services/order.service.ts @@ -66,27 +66,53 @@ export class OrderService extends BaseService { } try { - const response = await this.client.post(`/orders/${input.orderId}/cancel`, { - reason: input.reason ?? 'Customer requested cancellation', - notify_customer: input.notifyCustomer ?? false, - notes: input.notes, - cancelled_at: new Date().toISOString(), - }); + // Fetch the current order + const fetchResponse = await this.client.get( + `/api/order/customerOrders/${input.orderId}` + ); - if (!response.success) { - return this.failure<{ order: Order }>('Failed to cancel order', response.error ?? response); + if (!fetchResponse.success || !fetchResponse.data) { + return this.failure<{ order: Order }>('Order not found', { + orderId: input.orderId, + error: fetchResponse.error, + }); } - const orderData = response.data ?? (await this.fetchOrderById(input.orderId)).data; + const order = fetchResponse.data; - if (!orderData) { - return this.failure<{ order: Order }>('Order not found after cancellation', { + // Check if already cancelled + if (order.isCancelled) { + return this.failure<{ order: Order }>('Order is already cancelled', { orderId: input.orderId, + cancelledDate: order.cancelledDate, }); } + // Update the order with cancellation fields + const cancelledOrder: CustomerOrder = { + ...order, + isCancelled: true, + cancelledDate: new Date().toISOString(), + cancelReason: input.reason ?? 'Customer requested cancellation', + cancelledState: 'Completed', + status: 'Cancelled', + comment: input.notes ? `${order.comment ?? ''}\n[Cancellation] ${input.notes}`.trim() : order.comment, + }; + + // Save the updated order + const saveResponse = await this.client.put( + '/api/order/customerOrders', + cancelledOrder + ); + + if (!saveResponse.success) { + return this.failure<{ order: Order }>('Failed to cancel order', saveResponse.error ?? saveResponse); + } + + const savedOrder = saveResponse.data ?? cancelledOrder; + return this.success<{ order: Order }>({ - order: this.transformer.toMcpOrder(orderData as unknown as CustomerOrder), + order: this.transformer.toMcpOrder(savedOrder), }); } catch (error: unknown) { return this.failure<{ order: Order }>( @@ -153,7 +179,7 @@ export class OrderService extends BaseService { } } - private async fetchOrderById(orderId: string): Promise> { - return this.client.get(`/orders/${orderId}`); + private async fetchOrderById(orderId: string): Promise> { + return this.client.get(`/api/order/customerOrders/${orderId}`); } } diff --git a/virtocommerce-adapter/src/types.ts b/virtocommerce-adapter/src/types.ts index b71af70..c20ff2d 100644 --- a/virtocommerce-adapter/src/types.ts +++ b/virtocommerce-adapter/src/types.ts @@ -137,9 +137,18 @@ export interface YourFulfillmentShipment { } // Status mapping configuration +// VirtoCommerce uses PascalCase for statuses export const STATUS_MAP: Record = { New: 'pending', Processing: 'processing', + Shipped: 'shipped', + Delivered: 'delivered', + Cancelled: 'cancelled', + OnHold: 'on_hold', + Refunded: 'refunded', + PartiallyShipped: 'partially_shipped', + PartiallyDelivered: 'partially_delivered', + // Legacy lowercase mappings for backwards compatibility shipped: 'shipped', delivered: 'delivered', cancelled: 'cancelled', diff --git a/virtocommerce-adapter/tests/adapter.test.ts b/virtocommerce-adapter/tests/adapter.test.ts index f960cd3..e40195e 100644 --- a/virtocommerce-adapter/tests/adapter.test.ts +++ b/virtocommerce-adapter/tests/adapter.test.ts @@ -26,6 +26,7 @@ describe('VirtoCommerceFulfillmentAdapter', () => { let mockApiClient: ApiClient; let getSpy: jest.MockedFunction; let postSpy: jest.MockedFunction; + let putSpy: jest.MockedFunction; let patchSpy: jest.MockedFunction; beforeEach(() => { @@ -42,6 +43,7 @@ describe('VirtoCommerceFulfillmentAdapter', () => { mockApiClient = (adapter as any).client; getSpy = jest.spyOn(mockApiClient, 'get') as unknown as jest.MockedFunction; postSpy = jest.spyOn(mockApiClient, 'post') as unknown as jest.MockedFunction; + putSpy = jest.spyOn(mockApiClient, 'put') as unknown as jest.MockedFunction; patchSpy = jest.spyOn(mockApiClient, 'patch') as unknown as jest.MockedFunction; }); @@ -214,26 +216,44 @@ describe('VirtoCommerceFulfillmentAdapter', () => { describe('cancelOrder', () => { it('should cancel order successfully', async () => { - postSpy.mockResolvedValue({ + // Mock GET to fetch the existing order + getSpy.mockResolvedValue({ success: true, data: { id: 'ORDER-001', number: 'ORD-2024-001', - external_id: 'EXT-001', - status: 'cancelled', - customer: { - id: 'CUST-001', - email: 'test@example.com', - first_name: 'John', - last_name: 'Doe', - }, + outerId: 'EXT-001', + status: 'New', + isCancelled: false, + customerId: 'CUST-001', + customerName: 'John Doe', items: [], total: 100.0, currency: 'USD', - created_at: '2024-01-01T00:00:00Z', - updated_at: '2024-01-01T00:00:00Z', - shipping_address: {}, - billing_address: {}, + createdDate: '2024-01-01T00:00:00Z', + modifiedDate: '2024-01-01T00:00:00Z', + }, + }); + + // Mock PUT to save the cancelled order + putSpy.mockResolvedValue({ + success: true, + data: { + id: 'ORDER-001', + number: 'ORD-2024-001', + outerId: 'EXT-001', + status: 'Cancelled', + isCancelled: true, + cancelledState: 'Completed', + cancelReason: 'Customer request', + cancelledDate: '2024-01-01T12:00:00Z', + customerId: 'CUST-001', + customerName: 'John Doe', + items: [], + total: 100.0, + currency: 'USD', + createdDate: '2024-01-01T00:00:00Z', + modifiedDate: '2024-01-01T12:00:00Z', }, }); @@ -250,10 +270,20 @@ describe('VirtoCommerceFulfillmentAdapter', () => { expect(result.order.id).toBe('ORDER-001'); expect(result.order.status).toBe('cancelled'); } + expect(getSpy).toHaveBeenCalledWith('/api/order/customerOrders/ORDER-001'); + expect(putSpy).toHaveBeenCalledWith( + '/api/order/customerOrders', + expect.objectContaining({ + isCancelled: true, + cancelReason: 'Customer request', + cancelledState: 'Completed', + status: 'Cancelled', + }) + ); }); - it('should handle cancellation failure', async () => { - postSpy.mockResolvedValue({ + it('should handle cancellation failure when order not found', async () => { + getSpy.mockResolvedValue({ success: false, error: { code: 'ORDER_NOT_FOUND', @@ -272,6 +302,31 @@ describe('VirtoCommerceFulfillmentAdapter', () => { expect(result.error).toBeDefined(); } }); + + it('should fail when order is already cancelled', async () => { + getSpy.mockResolvedValue({ + success: true, + data: { + id: 'ORDER-001', + number: 'ORD-2024-001', + status: 'Cancelled', + isCancelled: true, + cancelledDate: '2024-01-01T00:00:00Z', + }, + }); + + const input: CancelOrderInput = { + orderId: 'ORDER-001', + reason: 'Customer request', + }; + + const result = await adapter.cancelOrder(input); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.message).toContain('already cancelled'); + } + }); }); describe('updateOrder', () => { From 3aadc12bc24d72756732da76993627c44fbe69c7 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Tue, 27 Jan 2026 07:11:41 +0000 Subject: [PATCH 05/51] getCustomers method --- .../src/mappers/filter.mappers.ts | 29 +++- .../src/services/customer.service.ts | 24 +++- virtocommerce-adapter/tests/adapter.test.ts | 134 ++++++++++++++++++ 3 files changed, 179 insertions(+), 8 deletions(-) diff --git a/virtocommerce-adapter/src/mappers/filter.mappers.ts b/virtocommerce-adapter/src/mappers/filter.mappers.ts index c7551b8..863ead3 100644 --- a/virtocommerce-adapter/src/mappers/filter.mappers.ts +++ b/virtocommerce-adapter/src/mappers/filter.mappers.ts @@ -12,7 +12,7 @@ import type { GetFulfillmentsInput, GetReturnsInput, } from '@cof-org/mcp'; -import type { CustomerOrderSearchCriteria } from '../models/index.js'; +import type { CustomerOrderSearchCriteria, MemberSearchCriteria } from '../models/index.js'; /** * Map GetOrdersInput to VirtoCommerce CustomerOrderSearchCriteria @@ -124,7 +124,32 @@ export function mapProductVariantFilters(input: GetProductVariantsInput): Record } /** - * Map GetCustomersInput to API query parameters + * Map GetCustomersInput to VirtoCommerce MemberSearchCriteria + */ +export function mapCustomerFiltersToSearchCriteria(input: GetCustomersInput): MemberSearchCriteria { + const criteria: MemberSearchCriteria = { + memberTypes: ['Contact'], + responseGroup: 'Full', + }; + + if (input.ids?.length) { + criteria.objectIds = input.ids; + } + + // Use emails as keyword search (VirtoCommerce member search supports keyword matching) + if (input.emails?.length) { + criteria.keyword = input.emails.join(' '); + } + + criteria.skip = input.skip ?? 0; + criteria.take = input.pageSize ?? 20; + + return criteria; +} + +/** + * @deprecated Use mapCustomerFiltersToSearchCriteria instead + * Map GetCustomersInput to generic API query parameters (legacy) */ export function mapCustomerFilters(input: GetCustomersInput): Record { return { diff --git a/virtocommerce-adapter/src/services/customer.service.ts b/virtocommerce-adapter/src/services/customer.service.ts index b161109..91553a7 100644 --- a/virtocommerce-adapter/src/services/customer.service.ts +++ b/virtocommerce-adapter/src/services/customer.service.ts @@ -3,11 +3,10 @@ */ import type { Customer, FulfillmentToolResult, GetCustomersInput } from '@cof-org/mcp'; -import type { YourFulfillmentCustomer } from '../types.js'; import type { Contact, MemberSearchCriteria } from '../models/index.js'; import { BaseService } from './base.service.js'; import { CustomerTransformer } from '../transformers/customer.transformer.js'; -import { mapCustomerFilters } from '../mappers/filter.mappers.js'; +import { mapCustomerFiltersToSearchCriteria } from '../mappers/filter.mappers.js'; import { getErrorMessage } from '../utils/type-guards.js'; import { ApiClient } from '../utils/api-client.js'; @@ -95,9 +94,11 @@ export class CustomerService extends BaseService { async getCustomers(input: GetCustomersInput): Promise> { try { - const response = await this.client.get( - '/customers', - mapCustomerFilters(input) + const searchCriteria = mapCustomerFiltersToSearchCriteria(input); + + const response = await this.client.post<{ results?: Contact[]; totalCount?: number }>( + '/api/members/search', + searchCriteria ); if (!response.success) { @@ -107,7 +108,18 @@ export class CustomerService extends BaseService { ); } - const customers = this.transformer.toMcpCustomers(this.ensureArray(response.data)); + const contacts = response.data?.results ?? []; + + // If searching by emails, filter results to match requested emails + const filteredContacts = input.emails?.length + ? contacts.filter((contact) => + contact.emails?.some((email) => + input.emails!.some((e) => e.toLowerCase() === email.toLowerCase()) + ) + ) + : contacts; + + const customers = this.transformer.fromContacts(filteredContacts); return this.success<{ customers: Customer[] }>({ customers }); } catch (error: unknown) { return this.failure<{ customers: Customer[] }>( diff --git a/virtocommerce-adapter/tests/adapter.test.ts b/virtocommerce-adapter/tests/adapter.test.ts index e40195e..97e4c6d 100644 --- a/virtocommerce-adapter/tests/adapter.test.ts +++ b/virtocommerce-adapter/tests/adapter.test.ts @@ -15,6 +15,7 @@ import type { UpdateOrderInput, GetOrdersInput, GetInventoryInput, + GetCustomersInput, } from '@cof-org/mcp'; function readResponse(path: string) { @@ -450,6 +451,139 @@ describe('VirtoCommerceFulfillmentAdapter', () => { }); }); + describe('getCustomers', () => { + it('should get customers by IDs', async () => { + postSpy.mockResolvedValue({ + success: true, + data: { + totalCount: 1, + results: [ + { + id: 'CUST-001', + memberType: 'Contact', + firstName: 'John', + lastName: 'Doe', + emails: ['john@example.com'], + phones: ['+1234567890'], + addresses: [], + groups: ['VIP'], + status: 'active', + outerId: 'EXT-CUST-001', + createdDate: '2024-01-01T00:00:00Z', + modifiedDate: '2024-01-15T00:00:00Z', + dynamicProperties: [], + }, + ], + }, + }); + + const input: GetCustomersInput = { + ids: ['CUST-001'], + }; + + const result = await adapter.getCustomers(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.customers).toHaveLength(1); + expect(result.customers[0]?.id).toBe('CUST-001'); + expect(result.customers[0]?.firstName).toBe('John'); + expect(result.customers[0]?.lastName).toBe('Doe'); + expect(result.customers[0]?.email).toBe('john@example.com'); + expect(result.customers[0]?.phone).toBe('+1234567890'); + expect(result.customers[0]?.externalId).toBe('EXT-CUST-001'); + expect(result.customers[0]?.tags).toEqual(['VIP']); + } + expect(postSpy).toHaveBeenCalledWith( + '/api/members/search', + expect.objectContaining({ + objectIds: ['CUST-001'], + memberTypes: ['Contact'], + responseGroup: 'Full', + }) + ); + }); + + it('should get customers by email', async () => { + postSpy.mockResolvedValue({ + success: true, + data: { + totalCount: 1, + results: [ + { + id: 'CUST-002', + memberType: 'Contact', + firstName: 'Jane', + lastName: 'Smith', + emails: ['jane@example.com'], + phones: [], + addresses: [], + groups: [], + status: 'active', + createdDate: '2024-02-01T00:00:00Z', + modifiedDate: '2024-02-01T00:00:00Z', + }, + ], + }, + }); + + const input: GetCustomersInput = { + emails: ['jane@example.com'], + }; + + const result = await adapter.getCustomers(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.customers).toHaveLength(1); + expect(result.customers[0]?.firstName).toBe('Jane'); + expect(result.customers[0]?.email).toBe('jane@example.com'); + } + }); + + it('should handle empty customer results', async () => { + postSpy.mockResolvedValue({ + success: true, + data: { + totalCount: 0, + results: [], + }, + }); + + const input: GetCustomersInput = { + ids: ['NON-EXISTENT'], + }; + + const result = await adapter.getCustomers(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.customers).toHaveLength(0); + } + }); + + it('should handle customer search failure', async () => { + postSpy.mockResolvedValue({ + success: false, + error: { + code: 'API_ERROR', + message: 'Internal server error', + }, + }); + + const input: GetCustomersInput = { + ids: ['CUST-001'], + }; + + const result = await adapter.getCustomers(input); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error).toBeDefined(); + } + }); + }); + describe('getInventory', () => { it('should get inventory for SKUs', async () => { getSpy.mockResolvedValue({ From dc19ea91230e93182ccd3d9bbf32e200ef3517da Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Tue, 27 Jan 2026 07:24:46 +0000 Subject: [PATCH 06/51] implement getProducts method --- .../src/mappers/filter.mappers.ts | 58 ++++- virtocommerce-adapter/src/models/catalog.ts | 207 +++++++++++++++++ virtocommerce-adapter/src/models/index.ts | 16 ++ .../src/services/product.service.ts | 29 ++- .../src/transformers/product.transformer.ts | 161 ++++++++++--- virtocommerce-adapter/tests/adapter.test.ts | 215 ++++++++++++++++++ 6 files changed, 644 insertions(+), 42 deletions(-) create mode 100644 virtocommerce-adapter/src/models/catalog.ts diff --git a/virtocommerce-adapter/src/mappers/filter.mappers.ts b/virtocommerce-adapter/src/mappers/filter.mappers.ts index 863ead3..9ed9b97 100644 --- a/virtocommerce-adapter/src/mappers/filter.mappers.ts +++ b/virtocommerce-adapter/src/mappers/filter.mappers.ts @@ -12,7 +12,7 @@ import type { GetFulfillmentsInput, GetReturnsInput, } from '@cof-org/mcp'; -import type { CustomerOrderSearchCriteria, MemberSearchCriteria } from '../models/index.js'; +import type { CustomerOrderSearchCriteria, MemberSearchCriteria, ProductSearchCriteria } from '../models/index.js'; /** * Map GetOrdersInput to VirtoCommerce CustomerOrderSearchCriteria @@ -91,7 +91,31 @@ export function mapInventoryFilters(input: GetInventoryInput): Record { return { @@ -107,7 +131,35 @@ export function mapProductFilters(input: GetProductsInput): Record { return { diff --git a/virtocommerce-adapter/src/models/catalog.ts b/virtocommerce-adapter/src/models/catalog.ts new file mode 100644 index 0000000..d20c7a1 --- /dev/null +++ b/virtocommerce-adapter/src/models/catalog.ts @@ -0,0 +1,207 @@ +/** + * Catalog-related models for VirtoCommerce + * Based on VirtoCommerce.CatalogModule.Core.Model + */ + +import type { AuditableEntity, HasOuterId, SeoInfo, DynamicObjectProperty } from './base.js'; + +/** + * Catalog product - main product entity + */ +export interface CatalogProduct extends AuditableEntity, HasOuterId { + code?: string; + manufacturerPartNumber?: string; + gtin?: string; + name?: string; + catalogId?: string; + categoryId?: string; + outline?: string; + path?: string; + titularItemId?: string; + mainProductId?: string; + isBuyable?: boolean; + isActive?: boolean; + trackInventory?: boolean; + indexingDate?: string; + maxQuantity?: number; + minQuantity?: number; + productType?: string; + packageType?: string; + imgSrc?: string; + vendor?: string; + startDate?: string; + endDate?: string; + priority?: number; + enableReview?: boolean; + maxNumberOfDownload?: number; + downloadExpiration?: string; + downloadType?: string; + hasUserAgreement?: boolean; + objectType?: string; + + // Dimensions + weightUnit?: string; + weight?: number; + measureUnit?: string; + height?: number; + length?: number; + width?: number; + + // Collections + images?: ProductImage[]; + assets?: ProductAsset[]; + variations?: CatalogProduct[]; + properties?: ProductProperty[]; + categories?: CategoryRef[]; + seoInfos?: SeoInfo[]; + reviews?: EditorialReview[]; + associations?: ProductAssociation[]; + links?: CategoryLink[]; + dynamicProperties?: DynamicObjectProperty[]; +} + +/** + * Product image + */ +export interface ProductImage { + id?: string; + name?: string; + url?: string; + relativeUrl?: string; + group?: string; + sortOrder?: number; + languageCode?: string; + description?: string; + altText?: string; +} + +/** + * Product asset (downloadable file, document, etc.) + */ +export interface ProductAsset { + id?: string; + name?: string; + url?: string; + relativeUrl?: string; + mimeType?: string; + size?: number; + group?: string; + sortOrder?: number; + languageCode?: string; + description?: string; +} + +/** + * Product property (characteristic) + */ +export interface ProductProperty { + id?: string; + catalogId?: string; + categoryId?: string; + name?: string; + required?: boolean; + dictionary?: boolean; + multivalue?: boolean; + multilanguage?: boolean; + valueType?: string; + type?: string; + values?: ProductPropertyValue[]; + displayNames?: PropertyDisplayName[]; +} + +/** + * Product property value + */ +export interface ProductPropertyValue { + id?: string; + propertyId?: string; + propertyName?: string; + valueId?: string; + value?: unknown; + valueType?: string; + languageCode?: string; + alias?: string; +} + +/** + * Property display name for localization + */ +export interface PropertyDisplayName { + name?: string; + languageCode?: string; +} + +/** + * Category reference + */ +export interface CategoryRef { + id?: string; + code?: string; + name?: string; + path?: string; + outline?: string; + isVirtual?: boolean; +} + +/** + * Editorial review (product description) + */ +export interface EditorialReview { + id?: string; + content?: string; + reviewType?: string; + languageCode?: string; +} + +/** + * Product association + */ +export interface ProductAssociation { + type?: string; + priority?: number; + quantity?: number; + associatedObjectId?: string; + associatedObjectType?: string; + tags?: string[]; +} + +/** + * Category link + */ +export interface CategoryLink { + catalogId?: string; + categoryId?: string; +} + +/** + * Product search criteria + */ +export interface ProductSearchCriteria { + responseGroup?: string; + objectType?: string; + keyword?: string; + searchPhrase?: string; + sort?: string; + skip?: number; + take?: number; + objectIds?: string[]; + catalogIds?: string[]; + categoryIds?: string[]; + codes?: string[]; + skus?: string[]; + productTypes?: string[]; + vendorIds?: string[]; + startDate?: string; + endDate?: string; + startDateRange?: string; + searchInChildren?: boolean; + searchInVariations?: boolean; +} + +/** + * Product search result + */ +export interface ProductSearchResult { + totalCount?: number; + results?: CatalogProduct[]; +} diff --git a/virtocommerce-adapter/src/models/index.ts b/virtocommerce-adapter/src/models/index.ts index 3af1328..a47aaca 100644 --- a/virtocommerce-adapter/src/models/index.ts +++ b/virtocommerce-adapter/src/models/index.ts @@ -99,6 +99,22 @@ export type { RelationType, } from './customer.js'; +// Catalog models +export type { + CatalogProduct, + ProductImage, + ProductAsset, + ProductProperty, + ProductPropertyValue, + PropertyDisplayName, + CategoryRef, + EditorialReview, + ProductAssociation, + CategoryLink, + ProductSearchCriteria, + ProductSearchResult, +} from './catalog.js'; + // Search models export type { SearchCriteriaBase, diff --git a/virtocommerce-adapter/src/services/product.service.ts b/virtocommerce-adapter/src/services/product.service.ts index e661189..a3e83b1 100644 --- a/virtocommerce-adapter/src/services/product.service.ts +++ b/virtocommerce-adapter/src/services/product.service.ts @@ -11,12 +11,13 @@ import type { GetProductVariantsInput, GetInventoryInput, } from '@cof-org/mcp'; -import type { YourFulfillmentProduct, YourFulfillmentInventory } from '../types.js'; +import type { YourFulfillmentInventory } from '../types.js'; +import type { ProductSearchResult } from '../models/index.js'; import { BaseService } from './base.service.js'; import { ProductTransformer } from '../transformers/product.transformer.js'; import { - mapProductFilters, - mapProductVariantFilters, + mapProductFiltersToSearchCriteria, + mapProductVariantFiltersToSearchCriteria, mapInventoryFilters, } from '../mappers/filter.mappers.js'; import { getErrorMessage } from '../utils/type-guards.js'; @@ -36,9 +37,11 @@ export class ProductService extends BaseService { async getProducts(input: GetProductsInput): Promise> { try { - const response = await this.client.get( - '/products', - mapProductFilters(input) + const searchCriteria = mapProductFiltersToSearchCriteria(input); + + const response = await this.client.post( + '/api/catalog/search/products', + searchCriteria ); if (!response.success) { @@ -48,7 +51,8 @@ export class ProductService extends BaseService { ); } - const products = this.transformer.toMcpProducts(this.ensureArray(response.data)); + const results = response.data?.results ?? []; + const products = this.transformer.fromCatalogProducts(results); return this.success<{ products: Product[] }>({ products }); } catch (error: unknown) { return this.failure<{ products: Product[] }>( @@ -62,9 +66,11 @@ export class ProductService extends BaseService { input: GetProductVariantsInput ): Promise> { try { - const response = await this.client.get( - '/products', - mapProductVariantFilters(input) + const searchCriteria = mapProductVariantFiltersToSearchCriteria(input); + + const response = await this.client.post( + '/api/catalog/search/products', + searchCriteria ); if (!response.success) { @@ -74,7 +80,8 @@ export class ProductService extends BaseService { ); } - const productVariants = this.transformer.toMcpProductVariants(this.ensureArray(response.data)); + const results = response.data?.results ?? []; + const productVariants = this.transformer.fromCatalogProductVariants(results); return this.success<{ productVariants: ProductVariant[] }>({ productVariants }); } catch (error: unknown) { return this.failure<{ productVariants: ProductVariant[] }>( diff --git a/virtocommerce-adapter/src/transformers/product.transformer.ts b/virtocommerce-adapter/src/transformers/product.transformer.ts index a81efb5..6a93b68 100644 --- a/virtocommerce-adapter/src/transformers/product.transformer.ts +++ b/virtocommerce-adapter/src/transformers/product.transformer.ts @@ -3,57 +3,82 @@ */ import type { Product, ProductVariant, InventoryItem } from '@cof-org/mcp'; -import type { YourFulfillmentProduct, YourFulfillmentInventory } from '../types.js'; +import type { YourFulfillmentInventory } from '../types.js'; +import type { CatalogProduct, ProductProperty } from '../models/index.js'; import { BaseTransformer } from './base.js'; export class ProductTransformer extends BaseTransformer { /** - * Transform YourFulfillment product to MCP Product format + * Transform VirtoCommerce CatalogProduct to MCP Product format */ - toMcpProduct(product: YourFulfillmentProduct): Product { + fromCatalogProduct(product: CatalogProduct): Product { + const description = + product.reviews?.find((r) => r.reviewType === 'FullReview')?.content ?? + product.reviews?.[0]?.content; + + const imageURLs = product.images + ?.map((img) => img.url ?? img.relativeUrl) + .filter((url): url is string => !!url); + + const categories = product.categories + ?.map((cat) => cat.name) + .filter((name): name is string => !!name); + return { - id: product.id, - externalId: product.sku, - name: product.name, - description: product.description, - status: product.status, - options: [], - tags: product.attributes ? Object.keys(product.attributes) : undefined, - createdAt: product.created_at, - updatedAt: product.updated_at, + id: product.id ?? '', + externalId: product.outerId, + externalProductId: product.code, + name: product.name ?? '', + description, + handle: product.path ?? product.code, + status: this.mapProductStatus(product), + tags: this.extractTags(product.properties), + vendor: product.vendor, + categories, + options: this.extractOptions(product), + imageURLs, + customFields: this.extractCustomFields(product.properties), + createdAt: product.createdDate ?? this.now(), + updatedAt: product.modifiedDate ?? this.now(), tenantId: this.tenantId, }; } /** - * Transform multiple products + * Transform multiple VirtoCommerce products */ - toMcpProducts(products: YourFulfillmentProduct[]): Product[] { - return products.map((product) => this.toMcpProduct(product)); + fromCatalogProducts(products: CatalogProduct[]): Product[] { + return products.map((product) => this.fromCatalogProduct(product)); } /** - * Transform YourFulfillment product to MCP ProductVariant format + * Transform VirtoCommerce CatalogProduct to MCP ProductVariant format */ - toMcpProductVariant(product: YourFulfillmentProduct): ProductVariant { + fromCatalogProductVariant(variation: CatalogProduct, parentProduct?: CatalogProduct): ProductVariant { return { - id: `${product.id}-default`, - productId: product.id, - sku: product.sku, - title: product.name, - price: product.price, - currency: 'USD', - createdAt: product.created_at, - updatedAt: product.updated_at, + id: variation.id ?? '', + productId: variation.mainProductId ?? parentProduct?.id ?? '', + sku: variation.code ?? '', + title: variation.name ?? parentProduct?.name ?? '', + price: undefined, + currency: undefined, + createdAt: variation.createdDate ?? this.now(), + updatedAt: variation.modifiedDate ?? this.now(), tenantId: this.tenantId, }; } /** - * Transform multiple products to variants + * Transform multiple product variations */ - toMcpProductVariants(products: YourFulfillmentProduct[]): ProductVariant[] { - return products.map((product) => this.toMcpProductVariant(product)); + fromCatalogProductVariants(products: CatalogProduct[]): ProductVariant[] { + return products.flatMap((product) => { + if (product.variations?.length) { + return product.variations.map((v) => this.fromCatalogProductVariant(v, product)); + } + // If no variations, treat the product itself as a variant + return [this.fromCatalogProductVariant(product)]; + }); } /** @@ -90,4 +115,84 @@ export class ProductTransformer extends BaseTransformer { toMcpInventory(inventoryItems: YourFulfillmentInventory[]): InventoryItem[] { return inventoryItems.flatMap((item) => this.toMcpInventoryItems(item)); } + + /** + * Map product status from VirtoCommerce fields + */ + private mapProductStatus(product: CatalogProduct): string { + if (!product.isActive) return 'inactive'; + if (!product.isBuyable) return 'draft'; + return 'active'; + } + + /** + * Extract option dimensions from product variations + */ + private extractOptions(product: CatalogProduct): { name: string; values: string[] }[] { + if (!product.variations?.length) { + return []; + } + + // Collect variation properties that differ across variations + const optionMap = new Map>(); + + for (const variation of product.variations) { + if (!variation.properties) continue; + for (const prop of variation.properties) { + if (prop.type === 'Variation' && prop.name && prop.values?.length) { + const values = optionMap.get(prop.name) ?? new Set(); + for (const val of prop.values) { + if (val.value != null) { + values.add(String(val.value)); + } + } + optionMap.set(prop.name, values); + } + } + } + + return Array.from(optionMap.entries()).map(([name, valuesSet]) => ({ + name, + values: Array.from(valuesSet), + })) as { name: string; values: string[] }[]; + } + + /** + * Extract tags from product properties + */ + private extractTags(properties?: ProductProperty[]): string[] | undefined { + if (!properties?.length) return undefined; + + const tagProp = properties.find( + (p) => p.name?.toLowerCase() === 'tags' || p.name?.toLowerCase() === 'tag' + ); + + if (tagProp?.values?.length) { + const tags = tagProp.values + .map((v) => (v.value != null ? String(v.value) : '')) + .filter(Boolean); + return tags.length ? tags : undefined; + } + + return undefined; + } + + /** + * Extract custom fields from product properties + */ + private extractCustomFields( + properties?: ProductProperty[] + ): { name: string; value: string }[] | undefined { + if (!properties?.length) return undefined; + + const fields = properties + .filter((p) => p.type === 'Product' && p.values?.length) + .map((p) => ({ + name: p.name ?? '', + value: String(p.values?.[0]?.value ?? ''), + })) + .filter((f) => f.name && f.value); + + return fields.length ? fields : undefined; + } } diff --git a/virtocommerce-adapter/tests/adapter.test.ts b/virtocommerce-adapter/tests/adapter.test.ts index 97e4c6d..09583d7 100644 --- a/virtocommerce-adapter/tests/adapter.test.ts +++ b/virtocommerce-adapter/tests/adapter.test.ts @@ -16,6 +16,7 @@ import type { GetOrdersInput, GetInventoryInput, GetCustomersInput, + GetProductsInput, } from '@cof-org/mcp'; function readResponse(path: string) { @@ -584,6 +585,220 @@ describe('VirtoCommerceFulfillmentAdapter', () => { }); }); + describe('getProducts', () => { + it('should get products by IDs', async () => { + postSpy.mockResolvedValue({ + success: true, + data: { + totalCount: 1, + results: [ + { + id: 'PROD-001', + code: 'SKU-001', + name: 'Stainless Steel Bolt', + isActive: true, + isBuyable: true, + vendor: 'BoltCo', + catalogId: 'CAT-001', + categoryId: 'CATEG-001', + outerId: 'EXT-PROD-001', + imgSrc: 'https://example.com/bolt.jpg', + images: [ + { url: 'https://example.com/bolt.jpg', name: 'Main' }, + { url: 'https://example.com/bolt-2.jpg', name: 'Side' }, + ], + reviews: [ + { reviewType: 'FullReview', content: 'High-quality stainless steel bolt' }, + ], + categories: [ + { id: 'CATEG-001', name: 'Fasteners' }, + ], + properties: [ + { + name: 'Material', + type: 'Product', + values: [{ value: 'Stainless Steel' }], + }, + ], + variations: [ + { + id: 'VAR-001', + code: 'SKU-001-SM', + name: 'Stainless Steel Bolt - Small', + mainProductId: 'PROD-001', + properties: [ + { name: 'Size', type: 'Variation', values: [{ value: 'Small' }] }, + ], + createdDate: '2024-01-01T00:00:00Z', + modifiedDate: '2024-01-01T00:00:00Z', + }, + { + id: 'VAR-002', + code: 'SKU-001-LG', + name: 'Stainless Steel Bolt - Large', + mainProductId: 'PROD-001', + properties: [ + { name: 'Size', type: 'Variation', values: [{ value: 'Large' }] }, + ], + createdDate: '2024-01-01T00:00:00Z', + modifiedDate: '2024-01-01T00:00:00Z', + }, + ], + createdDate: '2024-01-01T00:00:00Z', + modifiedDate: '2024-06-01T00:00:00Z', + }, + ], + }, + }); + + const input: GetProductsInput = { + ids: ['PROD-001'], + }; + + const result = await adapter.getProducts(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.products).toHaveLength(1); + const product = result.products[0]!; + expect(product.id).toBe('PROD-001'); + expect(product.name).toBe('Stainless Steel Bolt'); + expect(product.externalProductId).toBe('SKU-001'); + expect(product.description).toBe('High-quality stainless steel bolt'); + expect(product.status).toBe('active'); + expect(product.vendor).toBe('BoltCo'); + expect(product.categories).toEqual(['Fasteners']); + expect(product.imageURLs).toEqual([ + 'https://example.com/bolt.jpg', + 'https://example.com/bolt-2.jpg', + ]); + expect(product.options).toEqual([ + { name: 'Size', values: ['Small', 'Large'] }, + ]); + expect(product.customFields).toEqual([ + { name: 'Material', value: 'Stainless Steel' }, + ]); + } + expect(postSpy).toHaveBeenCalledWith( + '/api/catalog/search/products', + expect.objectContaining({ + objectIds: ['PROD-001'], + }) + ); + }); + + it('should get products by SKUs', async () => { + postSpy.mockResolvedValue({ + success: true, + data: { + totalCount: 1, + results: [ + { + id: 'PROD-002', + code: 'BOLT-42', + name: 'Hex Bolt', + isActive: true, + isBuyable: true, + createdDate: '2024-01-01T00:00:00Z', + modifiedDate: '2024-01-01T00:00:00Z', + }, + ], + }, + }); + + const input: GetProductsInput = { + skus: ['BOLT-42'], + }; + + const result = await adapter.getProducts(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.products).toHaveLength(1); + expect(result.products[0]?.externalProductId).toBe('BOLT-42'); + } + expect(postSpy).toHaveBeenCalledWith( + '/api/catalog/search/products', + expect.objectContaining({ + codes: ['BOLT-42'], + }) + ); + }); + + it('should handle empty product results', async () => { + postSpy.mockResolvedValue({ + success: true, + data: { + totalCount: 0, + results: [], + }, + }); + + const input: GetProductsInput = { + ids: ['NON-EXISTENT'], + }; + + const result = await adapter.getProducts(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.products).toHaveLength(0); + } + }); + + it('should handle product search failure', async () => { + postSpy.mockResolvedValue({ + success: false, + error: { + code: 'API_ERROR', + message: 'Search service unavailable', + }, + }); + + const input: GetProductsInput = { + ids: ['PROD-001'], + }; + + const result = await adapter.getProducts(input); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error).toBeDefined(); + } + }); + + it('should map inactive products correctly', async () => { + postSpy.mockResolvedValue({ + success: true, + data: { + totalCount: 1, + results: [ + { + id: 'PROD-003', + code: 'INACTIVE-001', + name: 'Discontinued Bolt', + isActive: false, + isBuyable: false, + createdDate: '2024-01-01T00:00:00Z', + modifiedDate: '2024-01-01T00:00:00Z', + }, + ], + }, + }); + + const input: GetProductsInput = { + ids: ['PROD-003'], + }; + + const result = await adapter.getProducts(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.products[0]?.status).toBe('inactive'); + } + }); + }); + describe('getInventory', () => { it('should get inventory for SKUs', async () => { getSpy.mockResolvedValue({ From 32cf41eaa7f5b65c891237bbd108efbf0afce3e1 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Tue, 27 Jan 2026 07:31:38 +0000 Subject: [PATCH 07/51] implement getProductVariants method --- .../src/mappers/filter.mappers.ts | 38 ++- .../src/transformers/product.transformer.ts | 89 ++++++- virtocommerce-adapter/tests/adapter.test.ts | 249 ++++++++++++++++++ 3 files changed, 362 insertions(+), 14 deletions(-) diff --git a/virtocommerce-adapter/src/mappers/filter.mappers.ts b/virtocommerce-adapter/src/mappers/filter.mappers.ts index 9ed9b97..c71644e 100644 --- a/virtocommerce-adapter/src/mappers/filter.mappers.ts +++ b/virtocommerce-adapter/src/mappers/filter.mappers.ts @@ -131,24 +131,36 @@ export function mapProductFilters(input: GetProductsInput): Record img.url ?? img.relativeUrl) + .filter((url): url is string => !!url); + + const selectedOptions = this.extractSelectedOptions(variation); + + const weight = this.extractWeight(variation); + const dimensions = this.extractDimensions(variation); + return { id: variation.id ?? '', + externalId: variation.outerId, + externalProductId: parentProduct?.code ?? variation.code, productId: variation.mainProductId ?? parentProduct?.id ?? '', sku: variation.code ?? '', + barcode: variation.gtin, title: variation.name ?? parentProduct?.name ?? '', + selectedOptions, price: undefined, currency: undefined, + inventoryNotTracked: variation.trackInventory === false ? true : undefined, + weight, + dimensions, + imageURLs: imageURLs?.length ? imageURLs : undefined, + customFields: this.extractCustomFields(variation.properties), createdAt: variation.createdDate ?? this.now(), updatedAt: variation.modifiedDate ?? this.now(), tenantId: this.tenantId, @@ -69,7 +87,9 @@ export class ProductTransformer extends BaseTransformer { } /** - * Transform multiple product variations + * Transform multiple product variations. + * Products with variations return each variation as a separate ProductVariant. + * Products without variations are treated as single-variant products. */ fromCatalogProductVariants(products: CatalogProduct[]): ProductVariant[] { return products.flatMap((product) => { @@ -81,6 +101,73 @@ export class ProductTransformer extends BaseTransformer { }); } + /** + * Extract selected option values from a variation's properties + */ + private extractSelectedOptions( + variation: CatalogProduct + ): { name: string; value: string }[] | undefined { + if (!variation.properties?.length) return undefined; + + const options = variation.properties + .filter((p) => p.type === 'Variation' && p.name && p.values?.length) + .map((p) => ({ + name: p.name!, + value: String(p.values![0]!.value ?? ''), + })) + .filter((o) => o.value); + + return options.length ? options : undefined; + } + + /** + * Extract weight from VirtoCommerce product dimensions + */ + private extractWeight( + product: CatalogProduct + ): { value: number; unit: 'lb' | 'oz' | 'kg' | 'g' } | undefined { + if (product.weight == null) return undefined; + + const unitMap: Record = { + lb: 'lb', lbs: 'lb', pound: 'lb', pounds: 'lb', + oz: 'oz', ounce: 'oz', ounces: 'oz', + kg: 'kg', kilogram: 'kg', kilograms: 'kg', + g: 'g', gram: 'g', grams: 'g', + }; + + const rawUnit = (product.weightUnit ?? 'kg').toLowerCase(); + const unit = unitMap[rawUnit] ?? 'kg'; + + return { value: product.weight, unit }; + } + + /** + * Extract dimensions from VirtoCommerce product + */ + private extractDimensions( + product: CatalogProduct + ): { length: number; width: number; height: number; unit: 'cm' | 'in' | 'ft' } | undefined { + if (product.length == null && product.width == null && product.height == null) { + return undefined; + } + + const unitMap: Record = { + cm: 'cm', centimeter: 'cm', centimeters: 'cm', + in: 'in', inch: 'in', inches: 'in', + ft: 'ft', foot: 'ft', feet: 'ft', + }; + + const rawUnit = (product.measureUnit ?? 'cm').toLowerCase(); + const unit = unitMap[rawUnit] ?? 'cm'; + + return { + length: product.length ?? 0, + width: product.width ?? 0, + height: product.height ?? 0, + unit, + }; + } + /** * Transform YourFulfillment inventory to MCP InventoryItem array * Handles both single location and multi-location inventory diff --git a/virtocommerce-adapter/tests/adapter.test.ts b/virtocommerce-adapter/tests/adapter.test.ts index 09583d7..1a232ae 100644 --- a/virtocommerce-adapter/tests/adapter.test.ts +++ b/virtocommerce-adapter/tests/adapter.test.ts @@ -17,6 +17,7 @@ import type { GetInventoryInput, GetCustomersInput, GetProductsInput, + GetProductVariantsInput, } from '@cof-org/mcp'; function readResponse(path: string) { @@ -799,6 +800,254 @@ describe('VirtoCommerceFulfillmentAdapter', () => { }); }); + describe('getProductVariants', () => { + it('should get variants by parent product IDs', async () => { + postSpy.mockResolvedValue({ + success: true, + data: { + totalCount: 1, + results: [ + { + id: 'PROD-001', + code: 'BOLT-BASE', + name: 'Stainless Steel Bolt', + isActive: true, + isBuyable: true, + variations: [ + { + id: 'VAR-001', + code: 'BOLT-SM', + name: 'Stainless Steel Bolt - Small', + mainProductId: 'PROD-001', + outerId: 'EXT-VAR-001', + gtin: '0012345678901', + trackInventory: true, + weight: 0.5, + weightUnit: 'kg', + length: 5, + width: 1, + height: 1, + measureUnit: 'cm', + images: [ + { url: 'https://example.com/bolt-sm.jpg' }, + ], + properties: [ + { + name: 'Size', + type: 'Variation', + values: [{ value: 'Small' }], + }, + { + name: 'Color', + type: 'Variation', + values: [{ value: 'Silver' }], + }, + { + name: 'Finish', + type: 'Product', + values: [{ value: 'Polished' }], + }, + ], + createdDate: '2024-01-01T00:00:00Z', + modifiedDate: '2024-03-01T00:00:00Z', + }, + { + id: 'VAR-002', + code: 'BOLT-LG', + name: 'Stainless Steel Bolt - Large', + mainProductId: 'PROD-001', + weight: 1.2, + weightUnit: 'kg', + properties: [ + { + name: 'Size', + type: 'Variation', + values: [{ value: 'Large' }], + }, + { + name: 'Color', + type: 'Variation', + values: [{ value: 'Silver' }], + }, + ], + createdDate: '2024-01-01T00:00:00Z', + modifiedDate: '2024-03-01T00:00:00Z', + }, + ], + createdDate: '2024-01-01T00:00:00Z', + modifiedDate: '2024-06-01T00:00:00Z', + }, + ], + }, + }); + + const input: GetProductVariantsInput = { + productIds: ['PROD-001'], + }; + + const result = await adapter.getProductVariants(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.productVariants).toHaveLength(2); + + const variant1 = result.productVariants[0]!; + expect(variant1.id).toBe('VAR-001'); + expect(variant1.productId).toBe('PROD-001'); + expect(variant1.sku).toBe('BOLT-SM'); + expect(variant1.title).toBe('Stainless Steel Bolt - Small'); + expect(variant1.externalId).toBe('EXT-VAR-001'); + expect(variant1.externalProductId).toBe('BOLT-BASE'); + expect(variant1.barcode).toBe('0012345678901'); + expect(variant1.selectedOptions).toEqual([ + { name: 'Size', value: 'Small' }, + { name: 'Color', value: 'Silver' }, + ]); + expect(variant1.weight).toEqual({ value: 0.5, unit: 'kg' }); + expect(variant1.dimensions).toEqual({ length: 5, width: 1, height: 1, unit: 'cm' }); + expect(variant1.imageURLs).toEqual(['https://example.com/bolt-sm.jpg']); + expect(variant1.customFields).toEqual([{ name: 'Finish', value: 'Polished' }]); + + const variant2 = result.productVariants[1]!; + expect(variant2.id).toBe('VAR-002'); + expect(variant2.sku).toBe('BOLT-LG'); + expect(variant2.weight).toEqual({ value: 1.2, unit: 'kg' }); + } + + expect(postSpy).toHaveBeenCalledWith( + '/api/catalog/search/products', + expect.objectContaining({ + objectIds: ['PROD-001'], + searchInVariations: false, + }) + ); + }); + + it('should get variants by SKUs', async () => { + postSpy.mockResolvedValue({ + success: true, + data: { + totalCount: 1, + results: [ + { + id: 'VAR-001', + code: 'BOLT-SM', + name: 'Stainless Steel Bolt - Small', + mainProductId: 'PROD-001', + isActive: true, + isBuyable: true, + properties: [ + { + name: 'Size', + type: 'Variation', + values: [{ value: 'Small' }], + }, + ], + createdDate: '2024-01-01T00:00:00Z', + modifiedDate: '2024-01-01T00:00:00Z', + }, + ], + }, + }); + + const input: GetProductVariantsInput = { + skus: ['BOLT-SM'], + }; + + const result = await adapter.getProductVariants(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.productVariants).toHaveLength(1); + expect(result.productVariants[0]?.sku).toBe('BOLT-SM'); + expect(result.productVariants[0]?.productId).toBe('PROD-001'); + } + + expect(postSpy).toHaveBeenCalledWith( + '/api/catalog/search/products', + expect.objectContaining({ + codes: ['BOLT-SM'], + searchInVariations: true, + }) + ); + }); + + it('should handle product without variations as single variant', async () => { + postSpy.mockResolvedValue({ + success: true, + data: { + totalCount: 1, + results: [ + { + id: 'PROD-SIMPLE', + code: 'SIMPLE-001', + name: 'Simple Product', + isActive: true, + isBuyable: true, + createdDate: '2024-01-01T00:00:00Z', + modifiedDate: '2024-01-01T00:00:00Z', + }, + ], + }, + }); + + const input: GetProductVariantsInput = { + productIds: ['PROD-SIMPLE'], + }; + + const result = await adapter.getProductVariants(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.productVariants).toHaveLength(1); + expect(result.productVariants[0]?.id).toBe('PROD-SIMPLE'); + expect(result.productVariants[0]?.sku).toBe('SIMPLE-001'); + } + }); + + it('should handle empty variant results', async () => { + postSpy.mockResolvedValue({ + success: true, + data: { + totalCount: 0, + results: [], + }, + }); + + const input: GetProductVariantsInput = { + productIds: ['NON-EXISTENT'], + }; + + const result = await adapter.getProductVariants(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.productVariants).toHaveLength(0); + } + }); + + it('should handle variant search failure', async () => { + postSpy.mockResolvedValue({ + success: false, + error: { + code: 'API_ERROR', + message: 'Catalog service unavailable', + }, + }); + + const input: GetProductVariantsInput = { + skus: ['BOLT-SM'], + }; + + const result = await adapter.getProductVariants(input); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error).toBeDefined(); + } + }); + }); + describe('getInventory', () => { it('should get inventory for SKUs', async () => { getSpy.mockResolvedValue({ From 13ec41f640f6f1cecb81f16f2f21a3d753749990 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Tue, 27 Jan 2026 07:45:20 +0000 Subject: [PATCH 08/51] getFulfillments method --- .../src/mappers/filter.mappers.ts | 34 ++- .../src/services/fulfillment.service.ts | 19 +- .../transformers/fulfillment.transformer.ts | 107 +++++--- virtocommerce-adapter/tests/adapter.test.ts | 239 ++++++++++++++++++ 4 files changed, 355 insertions(+), 44 deletions(-) diff --git a/virtocommerce-adapter/src/mappers/filter.mappers.ts b/virtocommerce-adapter/src/mappers/filter.mappers.ts index c71644e..d1efe36 100644 --- a/virtocommerce-adapter/src/mappers/filter.mappers.ts +++ b/virtocommerce-adapter/src/mappers/filter.mappers.ts @@ -12,7 +12,7 @@ import type { GetFulfillmentsInput, GetReturnsInput, } from '@cof-org/mcp'; -import type { CustomerOrderSearchCriteria, MemberSearchCriteria, ProductSearchCriteria } from '../models/index.js'; +import type { CustomerOrderSearchCriteria, MemberSearchCriteria, ProductSearchCriteria, ShipmentSearchCriteria } from '../models/index.js'; /** * Map GetOrdersInput to VirtoCommerce CustomerOrderSearchCriteria @@ -229,7 +229,37 @@ export function mapCustomerFilters(input: GetCustomersInput): Record { return { diff --git a/virtocommerce-adapter/src/services/fulfillment.service.ts b/virtocommerce-adapter/src/services/fulfillment.service.ts index ab42014..de5f7e6 100644 --- a/virtocommerce-adapter/src/services/fulfillment.service.ts +++ b/virtocommerce-adapter/src/services/fulfillment.service.ts @@ -8,10 +8,10 @@ import type { FulfillOrderInput, GetFulfillmentsInput, } from '@cof-org/mcp'; -import type { YourFulfillmentShipment } from '../types.js'; +import type { Shipment } from '../models/index.js'; import { BaseService } from './base.service.js'; import { FulfillmentTransformer } from '../transformers/fulfillment.transformer.js'; -import { mapFulfillmentFilters } from '../mappers/filter.mappers.js'; +import { mapFulfillmentFiltersToSearchCriteria } from '../mappers/filter.mappers.js'; import { getErrorMessage } from '../utils/type-guards.js'; import { ApiClient } from '../utils/api-client.js'; @@ -35,7 +35,7 @@ export class FulfillmentService extends BaseService { } try { - const response = await this.client.post( + const response = await this.client.post( `/orders/${input.orderId}/shipments`, this.transformer.fromFulfillOrderInput(input) ); @@ -48,7 +48,7 @@ export class FulfillmentService extends BaseService { } return this.success<{ fulfillment: Fulfillment }>({ - fulfillment: this.transformer.toMcpFulfillment(response.data), + fulfillment: this.transformer.fromShipment(response.data), }); } catch (error: unknown) { return this.failure<{ fulfillment: Fulfillment }>( @@ -62,9 +62,11 @@ export class FulfillmentService extends BaseService { input: GetFulfillmentsInput ): Promise> { try { - const response = await this.client.get( - '/shipments', - mapFulfillmentFilters(input) + const searchCriteria = mapFulfillmentFiltersToSearchCriteria(input); + + const response = await this.client.post<{ results?: Shipment[]; totalCount?: number }>( + '/api/order/customerOrders/shipments/search', + searchCriteria ); if (!response.success) { @@ -74,7 +76,8 @@ export class FulfillmentService extends BaseService { ); } - const fulfillments = this.transformer.toMcpFulfillments(this.ensureArray(response.data)); + const shipments = response.data?.results ?? []; + const fulfillments = this.transformer.fromShipments(shipments); return this.success<{ fulfillments: Fulfillment[] }>({ fulfillments }); } catch (error: unknown) { return this.failure<{ fulfillments: Fulfillment[] }>( diff --git a/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts b/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts index 54bcea3..c955f93 100644 --- a/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts +++ b/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts @@ -3,10 +3,21 @@ */ import type { Fulfillment, FulfillOrderInput } from '@cof-org/mcp'; -import type { YourFulfillmentShipment } from '../types.js'; +import type { Shipment } from '../models/index.js'; import { BaseTransformer } from './base.js'; import { AddressTransformer } from './address.transformer.js'; +const SHIPMENT_STATUS_MAP: Record = { + New: 'pending', + PickPack: 'processing', + ReadyToShip: 'ready_to_ship', + Shipped: 'shipped', + Delivered: 'delivered', + Cancelled: 'cancelled', + OnHold: 'on_hold', + PartiallyShipped: 'partially_shipped', +}; + export class FulfillmentTransformer extends BaseTransformer { private addressTransformer: AddressTransformer; @@ -21,57 +32,85 @@ export class FulfillmentTransformer extends BaseTransformer { } /** - * Transform YourFulfillment shipment to MCP Fulfillment format + * Transform VirtoCommerce Shipment to MCP Fulfillment format */ - toMcpFulfillment(shipment: YourFulfillmentShipment): Fulfillment { + fromShipment(shipment: Shipment): Fulfillment { + const trackingNumbers = shipment.trackingNumber ? [shipment.trackingNumber] : []; + + const lineItems = (shipment.items ?? []).map((item, index) => ({ + id: item.id ?? item.lineItemId ?? `${shipment.id}-item-${index}`, + sku: item.lineItem?.sku ?? '', + quantity: item.quantity ?? 0, + name: item.lineItem?.name, + })); + return { - id: shipment.id, - externalId: shipment.tracking_number, - orderId: shipment.order_id, - trackingNumbers: shipment.tracking_number ? [shipment.tracking_number] : [], - shippingCarrier: shipment.carrier, - shippingClass: shipment.service, - status: shipment.status, - shippingAddress: this.addressTransformer.toMcpAddress(shipment.to_address as any), - lineItems: shipment.items.map((item, index) => ({ - id: `${shipment.id}-${item.sku}-${index}`, - sku: item.sku, - quantity: item.quantity, - })), - createdAt: shipment.shipped_at ?? this.now(), - updatedAt: shipment.delivered_at ?? shipment.shipped_at ?? this.now(), + id: shipment.id ?? '', + externalId: shipment.outerId, + orderId: shipment.customerOrderId ?? shipment.customerOrder?.id ?? '', + status: this.mapShipmentStatus(shipment.status), + trackingNumbers, + lineItems, + locationId: shipment.fulfillmentCenterId, + shippingAddress: this.addressTransformer.toMcpAddress(shipment.deliveryAddress), + shippingCarrier: shipment.shippingMethod?.name ?? shipment.shipmentMethodCode, + shippingClass: shipment.shipmentMethodOption, + shippingCode: shipment.shipmentMethodCode, + shippingPrice: shipment.price, + shippingNote: shipment.trackingUrl ?? shipment.comment, + expectedDeliveryDate: shipment.deliveryDate, + createdAt: shipment.createdDate ?? this.now(), + updatedAt: shipment.modifiedDate ?? this.now(), tenantId: this.tenantId, - expectedDeliveryDate: shipment.delivered_at, - expectedShipDate: shipment.shipped_at, - shippingNote: shipment.tracking_url, }; } /** - * Transform multiple shipments + * Transform multiple VirtoCommerce shipments */ - toMcpFulfillments(shipments: YourFulfillmentShipment[]): Fulfillment[] { - return shipments.map((shipment) => this.toMcpFulfillment(shipment)); + fromShipments(shipments: Shipment[]): Fulfillment[] { + return shipments.map((shipment) => this.fromShipment(shipment)); } /** - * Transform FulfillOrderInput to API payload + * Transform FulfillOrderInput to VirtoCommerce Shipment payload */ fromFulfillOrderInput(input: FulfillOrderInput): Record { return { - tracking_number: input.trackingNumbers?.[0] ?? undefined, - carrier: input.shippingCarrier, - service: input.shippingClass, - location_id: input.locationId, - shipped_at: input.shipByDate ?? this.now(), - expected_delivery: input.expectedDeliveryDate, + trackingNumber: input.trackingNumbers?.[0], + shipmentMethodCode: input.shippingCarrier, + shipmentMethodOption: input.shippingClass, + fulfillmentCenterId: input.locationId, + deliveryDate: input.expectedDeliveryDate, + deliveryAddress: input.shippingAddress + ? { + line1: input.shippingAddress.address1, + line2: input.shippingAddress.address2, + city: input.shippingAddress.city, + regionName: input.shippingAddress.stateOrProvince, + postalCode: input.shippingAddress.zipCodeOrPostalCode, + countryName: input.shippingAddress.country, + phone: input.shippingAddress.phone, + email: input.shippingAddress.email, + name: [input.shippingAddress.firstName, input.shippingAddress.lastName] + .filter(Boolean) + .join(' '), + organization: input.shippingAddress.company, + } + : undefined, items: input.lineItems?.map((item) => ({ sku: item.sku, quantity: item.quantity ?? 0, })), - shipping_address: this.addressTransformer.toFulfillmentAddress(input.shippingAddress), - incoterms: input.incoterms, - notes: input.giftNote, + comment: input.giftNote ?? input.shippingNote, }; } + + /** + * Map VirtoCommerce shipment status to normalized status + */ + private mapShipmentStatus(status?: string): string { + if (!status) return 'pending'; + return SHIPMENT_STATUS_MAP[status] ?? status; + } } diff --git a/virtocommerce-adapter/tests/adapter.test.ts b/virtocommerce-adapter/tests/adapter.test.ts index 1a232ae..42c0ba5 100644 --- a/virtocommerce-adapter/tests/adapter.test.ts +++ b/virtocommerce-adapter/tests/adapter.test.ts @@ -18,6 +18,7 @@ import type { GetCustomersInput, GetProductsInput, GetProductVariantsInput, + GetFulfillmentsInput, } from '@cof-org/mcp'; function readResponse(path: string) { @@ -1048,6 +1049,244 @@ describe('VirtoCommerceFulfillmentAdapter', () => { }); }); + describe('getFulfillments', () => { + it('should get fulfillments by order IDs', async () => { + postSpy.mockResolvedValue({ + success: true, + data: { + totalCount: 1, + results: [ + { + id: 'SHIP-001', + outerId: 'EXT-SHIP-001', + number: 'SHP-2024-001', + status: 'Shipped', + customerOrderId: 'ORDER-001', + fulfillmentCenterId: 'FC-001', + fulfillmentCenterName: 'Main Warehouse', + trackingNumber: '1Z999AA10123456784', + trackingUrl: 'https://tracking.example.com/1Z999AA10123456784', + shipmentMethodCode: 'UPS', + shipmentMethodOption: 'Ground', + shippingMethod: { + code: 'UPS', + name: 'UPS', + }, + price: 12.99, + deliveryAddress: { + line1: '123 Main St', + city: 'Springfield', + regionName: 'IL', + postalCode: '62701', + countryName: 'US', + name: 'John Doe', + phone: '+1234567890', + }, + deliveryDate: '2024-01-15T00:00:00Z', + items: [ + { + id: 'SITEM-001', + lineItemId: 'LI-001', + lineItem: { + sku: 'BOLT-SM', + name: 'Stainless Steel Bolt - Small', + }, + quantity: 5, + status: 'Shipped', + }, + { + id: 'SITEM-002', + lineItemId: 'LI-002', + lineItem: { + sku: 'NUT-SM', + name: 'Hex Nut - Small', + }, + quantity: 10, + status: 'Shipped', + }, + ], + comment: 'Handle with care', + createdDate: '2024-01-10T00:00:00Z', + modifiedDate: '2024-01-12T00:00:00Z', + }, + ], + }, + }); + + const input: GetFulfillmentsInput = { + orderIds: ['ORDER-001'], + }; + + const result = await adapter.getFulfillments(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.fulfillments).toHaveLength(1); + + const fulfillment = result.fulfillments[0]!; + expect(fulfillment.id).toBe('SHIP-001'); + expect(fulfillment.externalId).toBe('EXT-SHIP-001'); + expect(fulfillment.orderId).toBe('ORDER-001'); + expect(fulfillment.status).toBe('shipped'); + expect(fulfillment.trackingNumbers).toEqual(['1Z999AA10123456784']); + expect(fulfillment.locationId).toBe('FC-001'); + expect(fulfillment.shippingCarrier).toBe('UPS'); + expect(fulfillment.shippingClass).toBe('Ground'); + expect(fulfillment.shippingCode).toBe('UPS'); + expect(fulfillment.shippingPrice).toBe(12.99); + expect(fulfillment.shippingNote).toBe('https://tracking.example.com/1Z999AA10123456784'); + expect(fulfillment.expectedDeliveryDate).toBe('2024-01-15T00:00:00Z'); + + // Verify address mapping + expect(fulfillment.shippingAddress).toBeDefined(); + expect(fulfillment.shippingAddress?.address1).toBe('123 Main St'); + expect(fulfillment.shippingAddress?.city).toBe('Springfield'); + + // Verify line items + expect(fulfillment.lineItems).toHaveLength(2); + expect(fulfillment.lineItems[0]?.sku).toBe('BOLT-SM'); + expect(fulfillment.lineItems[0]?.quantity).toBe(5); + expect(fulfillment.lineItems[1]?.sku).toBe('NUT-SM'); + expect(fulfillment.lineItems[1]?.quantity).toBe(10); + } + + expect(postSpy).toHaveBeenCalledWith( + '/api/order/customerOrders/shipments/search', + expect.objectContaining({ + orderIds: ['ORDER-001'], + responseGroup: 'Full', + }) + ); + }); + + it('should get fulfillments by IDs', async () => { + postSpy.mockResolvedValue({ + success: true, + data: { + totalCount: 1, + results: [ + { + id: 'SHIP-002', + status: 'New', + customerOrderId: 'ORDER-002', + items: [], + createdDate: '2024-01-01T00:00:00Z', + modifiedDate: '2024-01-01T00:00:00Z', + }, + ], + }, + }); + + const input: GetFulfillmentsInput = { + ids: ['SHIP-002'], + }; + + const result = await adapter.getFulfillments(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.fulfillments).toHaveLength(1); + expect(result.fulfillments[0]?.id).toBe('SHIP-002'); + expect(result.fulfillments[0]?.status).toBe('pending'); + } + + expect(postSpy).toHaveBeenCalledWith( + '/api/order/customerOrders/shipments/search', + expect.objectContaining({ + objectIds: ['SHIP-002'], + }) + ); + }); + + it('should handle empty fulfillment results', async () => { + postSpy.mockResolvedValue({ + success: true, + data: { + totalCount: 0, + results: [], + }, + }); + + const input: GetFulfillmentsInput = { + orderIds: ['NON-EXISTENT'], + }; + + const result = await adapter.getFulfillments(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.fulfillments).toHaveLength(0); + } + }); + + it('should handle fulfillment search failure', async () => { + postSpy.mockResolvedValue({ + success: false, + error: { + code: 'API_ERROR', + message: 'Shipment service unavailable', + }, + }); + + const input: GetFulfillmentsInput = { + orderIds: ['ORDER-001'], + }; + + const result = await adapter.getFulfillments(input); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error).toBeDefined(); + } + }); + + it('should map all shipment statuses correctly', async () => { + postSpy.mockResolvedValue({ + success: true, + data: { + totalCount: 3, + results: [ + { + id: 'SHIP-A', + status: 'ReadyToShip', + customerOrderId: 'ORD-1', + items: [], + createdDate: '2024-01-01T00:00:00Z', + modifiedDate: '2024-01-01T00:00:00Z', + }, + { + id: 'SHIP-B', + status: 'Delivered', + customerOrderId: 'ORD-2', + items: [], + createdDate: '2024-01-01T00:00:00Z', + modifiedDate: '2024-01-01T00:00:00Z', + }, + { + id: 'SHIP-C', + status: 'PickPack', + customerOrderId: 'ORD-3', + items: [], + createdDate: '2024-01-01T00:00:00Z', + modifiedDate: '2024-01-01T00:00:00Z', + }, + ], + }, + }); + + const input: GetFulfillmentsInput = {}; + + const result = await adapter.getFulfillments(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.fulfillments[0]?.status).toBe('ready_to_ship'); + expect(result.fulfillments[1]?.status).toBe('delivered'); + expect(result.fulfillments[2]?.status).toBe('processing'); + } + }); + }); + describe('getInventory', () => { it('should get inventory for SKUs', async () => { getSpy.mockResolvedValue({ From a88e3cda188eb7b23e14ac056a554bc4f19be7a5 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Tue, 27 Jan 2026 07:58:38 +0000 Subject: [PATCH 09/51] implement getInventory method --- virtocommerce-adapter/src/models/index.ts | 9 + virtocommerce-adapter/src/models/inventory.ts | 58 +++++ .../src/services/product.service.ts | 68 +++++- .../src/transformers/product.transformer.ts | 57 ++--- virtocommerce-adapter/tests/adapter.test.ts | 219 +++++++++++++++--- 5 files changed, 348 insertions(+), 63 deletions(-) create mode 100644 virtocommerce-adapter/src/models/inventory.ts diff --git a/virtocommerce-adapter/src/models/index.ts b/virtocommerce-adapter/src/models/index.ts index a47aaca..5049c57 100644 --- a/virtocommerce-adapter/src/models/index.ts +++ b/virtocommerce-adapter/src/models/index.ts @@ -115,6 +115,15 @@ export type { ProductSearchResult, } from './catalog.js'; +// Inventory models +export type { + InventoryInfo, + InventoryStatus, + InventorySearchCriteria, + InventorySearchResult, + ProductInventoryInfo, +} from './inventory.js'; + // Search models export type { SearchCriteriaBase, diff --git a/virtocommerce-adapter/src/models/inventory.ts b/virtocommerce-adapter/src/models/inventory.ts new file mode 100644 index 0000000..c04c865 --- /dev/null +++ b/virtocommerce-adapter/src/models/inventory.ts @@ -0,0 +1,58 @@ +/** + * Inventory-related models for VirtoCommerce + * Based on VirtoCommerce.InventoryModule.Core.Model + */ + +import type { AuditableEntity, HasOuterId } from './base.js'; + +/** + * Inventory info - stock record for a product at a fulfillment center + */ +export interface InventoryInfo extends AuditableEntity, HasOuterId { + productId?: string; + fulfillmentCenterId?: string; + fulfillmentCenterName?: string; + inStockQuantity?: number; + reservedQuantity?: number; + reorderMinQuantity?: number; + preorderQuantity?: number; + backorderQuantity?: number; + allowBackorder?: boolean; + allowPreorder?: boolean; + status?: InventoryStatus; + preorderAvailabilityDate?: string; + backorderAvailabilityDate?: string; +} + +/** + * Inventory status + */ +export type InventoryStatus = 'Enabled' | 'Disabled' | 'Tracked' | 'Ignored'; + +/** + * Inventory search criteria + */ +export interface InventorySearchCriteria { + productIds?: string[]; + fulfillmentCenterIds?: string[]; + keyword?: string; + skip?: number; + take?: number; + responseGroup?: string; +} + +/** + * Inventory search result + */ +export interface InventorySearchResult { + totalCount?: number; + results?: InventoryInfo[]; +} + +/** + * Product inventory info - inventory grouped by product + */ +export interface ProductInventoryInfo { + productId?: string; + inventoryInfos?: InventoryInfo[]; +} diff --git a/virtocommerce-adapter/src/services/product.service.ts b/virtocommerce-adapter/src/services/product.service.ts index a3e83b1..c206f90 100644 --- a/virtocommerce-adapter/src/services/product.service.ts +++ b/virtocommerce-adapter/src/services/product.service.ts @@ -11,14 +11,17 @@ import type { GetProductVariantsInput, GetInventoryInput, } from '@cof-org/mcp'; -import type { YourFulfillmentInventory } from '../types.js'; -import type { ProductSearchResult } from '../models/index.js'; +import type { + ProductSearchResult, + CatalogProduct, + InventorySearchResult, + InventorySearchCriteria, +} from '../models/index.js'; import { BaseService } from './base.service.js'; import { ProductTransformer } from '../transformers/product.transformer.js'; import { mapProductFiltersToSearchCriteria, mapProductVariantFiltersToSearchCriteria, - mapInventoryFilters, } from '../mappers/filter.mappers.js'; import { getErrorMessage } from '../utils/type-guards.js'; import { ApiClient } from '../utils/api-client.js'; @@ -95,19 +98,66 @@ export class ProductService extends BaseService { input: GetInventoryInput ): Promise> { try { - const response = await this.client.get( - '/inventory', - mapInventoryFilters(input) + // Step 1: Resolve SKUs to product IDs via catalog search + const catalogResponse = await this.client.post( + '/api/catalog/search/products', + { + codes: input.skus, + responseGroup: 'ItemInfo', + searchInVariations: true, + take: input.skus.length, + } ); - if (!response.success) { + if (!catalogResponse.success) { + return this.failure<{ inventory: InventoryItem[] }>( + 'Failed to resolve product SKUs', + catalogResponse.error ?? catalogResponse + ); + } + + const products = catalogResponse.data?.results ?? []; + if (!products.length) { + return this.success<{ inventory: InventoryItem[] }>({ inventory: [] }); + } + + // Build SKU map: productId → SKU code + const skuMap = new Map(); + for (const product of products) { + if (product.id && product.code) { + skuMap.set(product.id, product.code); + } + } + + const productIds = products + .map((p: CatalogProduct) => p.id) + .filter((id): id is string => !!id); + + // Step 2: Query inventory for resolved product IDs + const inventoryCriteria: InventorySearchCriteria = { + productIds, + take: productIds.length * 10, // Allow multiple locations per product + }; + + if (input.locationIds?.length) { + inventoryCriteria.fulfillmentCenterIds = input.locationIds; + } + + const inventoryResponse = await this.client.post( + '/api/inventory/search', + inventoryCriteria + ); + + if (!inventoryResponse.success) { return this.failure<{ inventory: InventoryItem[] }>( 'Failed to fetch inventory', - response.error ?? response + inventoryResponse.error ?? inventoryResponse ); } - const inventory = this.transformer.toMcpInventory(this.ensureArray(response.data)); + const inventoryRecords = inventoryResponse.data?.results ?? []; + const inventory = this.transformer.fromInventoryInfos(inventoryRecords, skuMap); + return this.success<{ inventory: InventoryItem[] }>({ inventory }); } catch (error: unknown) { return this.failure<{ inventory: InventoryItem[] }>( diff --git a/virtocommerce-adapter/src/transformers/product.transformer.ts b/virtocommerce-adapter/src/transformers/product.transformer.ts index 5081595..79d8977 100644 --- a/virtocommerce-adapter/src/transformers/product.transformer.ts +++ b/virtocommerce-adapter/src/transformers/product.transformer.ts @@ -3,8 +3,7 @@ */ import type { Product, ProductVariant, InventoryItem } from '@cof-org/mcp'; -import type { YourFulfillmentInventory } from '../types.js'; -import type { CatalogProduct, ProductProperty } from '../models/index.js'; +import type { CatalogProduct, ProductProperty, InventoryInfo } from '../models/index.js'; import { BaseTransformer } from './base.js'; export class ProductTransformer extends BaseTransformer { @@ -169,38 +168,40 @@ export class ProductTransformer extends BaseTransformer { } /** - * Transform YourFulfillment inventory to MCP InventoryItem array - * Handles both single location and multi-location inventory + * Transform a VirtoCommerce InventoryInfo to an MCP InventoryItem. + * Requires the product SKU to be passed in because InventoryInfo + * only stores productId, not the SKU code. */ - toMcpInventoryItems(inventory: YourFulfillmentInventory): InventoryItem[] { - if (inventory.warehouse_locations?.length) { - return inventory.warehouse_locations.map((location) => ({ - locationId: location.location_id, - sku: inventory.sku, - available: location.available, - onHand: location.available + location.reserved, - unavailable: location.reserved, - tenantId: this.tenantId, - })); - } + fromInventoryInfo(info: InventoryInfo, sku: string): InventoryItem { + const inStock = info.inStockQuantity ?? 0; + const reserved = info.reservedQuantity ?? 0; + const available = inStock - reserved; - return [ - { - locationId: '', - sku: inventory.sku, - available: inventory.available, - onHand: inventory.total, - unavailable: inventory.total - inventory.available, - tenantId: this.tenantId, - }, - ]; + return { + sku, + locationId: info.fulfillmentCenterId ?? '', + available: Math.max(available, 0), + onHand: inStock, + unavailable: reserved, + tenantId: this.tenantId, + }; } /** - * Transform multiple inventory records + * Transform multiple VirtoCommerce InventoryInfo records. + * Each record represents stock for one product at one fulfillment center. + * + * @param records - InventoryInfo records from VirtoCommerce + * @param skuMap - Map of productId → SKU for resolving codes */ - toMcpInventory(inventoryItems: YourFulfillmentInventory[]): InventoryItem[] { - return inventoryItems.flatMap((item) => this.toMcpInventoryItems(item)); + fromInventoryInfos(records: InventoryInfo[], skuMap: Map): InventoryItem[] { + return records + .map((info) => { + const sku = info.productId ? skuMap.get(info.productId) : undefined; + if (!sku) return undefined; + return this.fromInventoryInfo(info, sku); + }) + .filter((item): item is InventoryItem => item != null); } /** diff --git a/virtocommerce-adapter/tests/adapter.test.ts b/virtocommerce-adapter/tests/adapter.test.ts index 42c0ba5..278b85a 100644 --- a/virtocommerce-adapter/tests/adapter.test.ts +++ b/virtocommerce-adapter/tests/adapter.test.ts @@ -1288,57 +1288,224 @@ describe('VirtoCommerceFulfillmentAdapter', () => { }); describe('getInventory', () => { - it('should get inventory for SKUs', async () => { - getSpy.mockResolvedValue({ + it('should get inventory for SKUs across multiple fulfillment centers', async () => { + // Mock step 1: catalog search to resolve SKUs → product IDs + postSpy.mockResolvedValueOnce({ success: true, - data: [ - { - sku: 'PROD-001', - available: 100, - reserved: 10, - total: 110, - warehouse_locations: [ - { location_id: 'LOC-001', available: 60, reserved: 5 }, - { location_id: 'LOC-002', available: 40, reserved: 5 }, - ], - updated_at: '2024-01-01T00:00:00Z', - }, - ], + data: { + totalCount: 2, + results: [ + { id: 'PROD-001', code: 'BOLT-SM' }, + { id: 'PROD-002', code: 'NUT-SM' }, + ], + }, + }); + + // Mock step 2: inventory search for those product IDs + postSpy.mockResolvedValueOnce({ + success: true, + data: { + totalCount: 3, + results: [ + { + productId: 'PROD-001', + fulfillmentCenterId: 'FC-001', + fulfillmentCenterName: 'Main Warehouse', + inStockQuantity: 100, + reservedQuantity: 15, + status: 'Enabled', + }, + { + productId: 'PROD-001', + fulfillmentCenterId: 'FC-002', + fulfillmentCenterName: 'East Warehouse', + inStockQuantity: 50, + reservedQuantity: 5, + status: 'Enabled', + }, + { + productId: 'PROD-002', + fulfillmentCenterId: 'FC-001', + fulfillmentCenterName: 'Main Warehouse', + inStockQuantity: 200, + reservedQuantity: 20, + status: 'Enabled', + }, + ], + }, }); const input: GetInventoryInput = { - skus: ['PROD-001'], + skus: ['BOLT-SM', 'NUT-SM'], }; const result = await adapter.getInventory(input); expect(result.success).toBe(true); if (result.success) { - expect(result.inventory.length).toBeGreaterThan(0); - expect(result.inventory[0]?.sku).toBe('PROD-001'); - expect(result.inventory[0]?.available).toBe(60); - expect(result.inventory[0]?.locationId).toBe('LOC-001'); + expect(result.inventory).toHaveLength(3); + + // BOLT-SM at Main Warehouse + const boltMain = result.inventory.find( + (i) => i.sku === 'BOLT-SM' && i.locationId === 'FC-001' + ); + expect(boltMain).toBeDefined(); + expect(boltMain?.available).toBe(85); // 100 - 15 + expect(boltMain?.onHand).toBe(100); + expect(boltMain?.unavailable).toBe(15); + + // BOLT-SM at East Warehouse + const boltEast = result.inventory.find( + (i) => i.sku === 'BOLT-SM' && i.locationId === 'FC-002' + ); + expect(boltEast).toBeDefined(); + expect(boltEast?.available).toBe(45); // 50 - 5 + + // NUT-SM at Main Warehouse + const nutMain = result.inventory.find( + (i) => i.sku === 'NUT-SM' && i.locationId === 'FC-001' + ); + expect(nutMain).toBeDefined(); + expect(nutMain?.available).toBe(180); // 200 - 20 } + + // Verify catalog search was called first + expect(postSpy).toHaveBeenNthCalledWith( + 1, + '/api/catalog/search/products', + expect.objectContaining({ + codes: ['BOLT-SM', 'NUT-SM'], + searchInVariations: true, + }) + ); + + // Verify inventory search was called second + expect(postSpy).toHaveBeenNthCalledWith( + 2, + '/api/inventory/search', + expect.objectContaining({ + productIds: ['PROD-001', 'PROD-002'], + }) + ); }); - it('should handle inventory lookup failure', async () => { - getSpy.mockResolvedValue({ + it('should filter inventory by location IDs', async () => { + postSpy.mockResolvedValueOnce({ + success: true, + data: { + totalCount: 1, + results: [{ id: 'PROD-001', code: 'BOLT-SM' }], + }, + }); + + postSpy.mockResolvedValueOnce({ + success: true, + data: { + totalCount: 1, + results: [ + { + productId: 'PROD-001', + fulfillmentCenterId: 'FC-002', + inStockQuantity: 50, + reservedQuantity: 5, + }, + ], + }, + }); + + const input: GetInventoryInput = { + skus: ['BOLT-SM'], + locationIds: ['FC-002'], + }; + + const result = await adapter.getInventory(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.inventory).toHaveLength(1); + expect(result.inventory[0]?.locationId).toBe('FC-002'); + } + + expect(postSpy).toHaveBeenNthCalledWith( + 2, + '/api/inventory/search', + expect.objectContaining({ + fulfillmentCenterIds: ['FC-002'], + }) + ); + }); + + it('should return empty inventory when SKUs not found in catalog', async () => { + postSpy.mockResolvedValueOnce({ + success: true, + data: { + totalCount: 0, + results: [], + }, + }); + + const input: GetInventoryInput = { + skus: ['NON-EXISTENT'], + }; + + const result = await adapter.getInventory(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.inventory).toHaveLength(0); + } + + // Should NOT call inventory search if no products found + expect(postSpy).toHaveBeenCalledTimes(1); + }); + + it('should handle catalog search failure', async () => { + postSpy.mockResolvedValueOnce({ success: false, error: { - code: 'NOT_FOUND', - message: 'SKU not found', + code: 'API_ERROR', + message: 'Catalog service unavailable', }, }); const input: GetInventoryInput = { - skus: ['INVALID-SKU'], + skus: ['BOLT-SM'], }; const result = await adapter.getInventory(input); expect(result.success).toBe(false); if (!result.success) { - expect(result.error).toBeDefined(); + expect(result.message).toContain('resolve product SKUs'); + } + }); + + it('should handle inventory search failure', async () => { + postSpy.mockResolvedValueOnce({ + success: true, + data: { + totalCount: 1, + results: [{ id: 'PROD-001', code: 'BOLT-SM' }], + }, + }); + + postSpy.mockResolvedValueOnce({ + success: false, + error: { + code: 'API_ERROR', + message: 'Inventory service unavailable', + }, + }); + + const input: GetInventoryInput = { + skus: ['BOLT-SM'], + }; + + const result = await adapter.getInventory(input); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.message).toContain('fetch inventory'); } }); }); From 280700123cc7cb74204c166ec55056f64e23be5c Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Fri, 30 Jan 2026 15:39:12 +0000 Subject: [PATCH 10/51] updateOrder operation --- .../src/services/order.service.ts | 43 ++-- .../src/transformers/address.transformer.ts | 21 ++ .../transformers/fulfillment.transformer.ts | 4 +- .../src/transformers/order.transformer.ts | 55 +++-- virtocommerce-adapter/tests/adapter.test.ts | 221 ++++++++++++++++-- 5 files changed, 291 insertions(+), 53 deletions(-) diff --git a/virtocommerce-adapter/src/services/order.service.ts b/virtocommerce-adapter/src/services/order.service.ts index 6b8df74..87271c8 100644 --- a/virtocommerce-adapter/src/services/order.service.ts +++ b/virtocommerce-adapter/src/services/order.service.ts @@ -11,7 +11,7 @@ import type { UpdateOrderInput, GetOrdersInput, } from '@cof-org/mcp'; -import type { YourFulfillmentOrder, YourFulfillmentApiResponse } from '../types.js'; +import type { YourFulfillmentOrder } from '../types.js'; import type { CustomerOrder, CustomerOrderSearchResult, @@ -123,24 +123,43 @@ export class OrderService extends BaseService { } async updateOrder(input: UpdateOrderInput): Promise { + if (!input.id) { + return this.failure<{ order: Order }>('id is required to update an order'); + } + try { - const response = await this.client.patch( - `/orders/${input.id}`, - this.transformer.fromUpdateOrderInput(input.updates) + // Step 1: Fetch the current order + const fetchResponse = await this.client.get( + `/api/order/customerOrders/${input.id}` ); - if (!response.success) { - return this.failure<{ order: Order }>('Failed to update order', response.error ?? response); + if (!fetchResponse.success || !fetchResponse.data) { + return this.failure<{ order: Order }>('Order not found', { + orderId: input.id, + error: fetchResponse.error, + }); } - const orderData = response.data ?? (await this.fetchOrderById(input.id)).data; + // Step 2: Apply updates to the VirtoCommerce order object + const updatedOrder = this.transformer.applyUpdatesToOrder( + fetchResponse.data, + input.updates + ); + + // Step 3: Save the updated order + const saveResponse = await this.client.put( + '/api/order/customerOrders', + updatedOrder + ); - if (!orderData) { - return this.failure<{ order: Order }>('Order not found after update', { orderId: input.id }); + if (!saveResponse.success) { + return this.failure<{ order: Order }>('Failed to update order', saveResponse.error ?? saveResponse); } + const savedOrder = saveResponse.data ?? updatedOrder; + return this.success<{ order: Order }>({ - order: this.transformer.toMcpOrder(orderData as unknown as CustomerOrder), + order: this.transformer.toMcpOrder(savedOrder), }); } catch (error: unknown) { return this.failure<{ order: Order }>(`Order update failed: ${getErrorMessage(error)}`, error); @@ -178,8 +197,4 @@ export class OrderService extends BaseService { return this.failure<{ orders: Order[] }>(`Order lookup failed: ${getErrorMessage(error)}`, error); } } - - private async fetchOrderById(orderId: string): Promise> { - return this.client.get(`/api/order/customerOrders/${orderId}`); - } } diff --git a/virtocommerce-adapter/src/transformers/address.transformer.ts b/virtocommerce-adapter/src/transformers/address.transformer.ts index 676228c..6240869 100644 --- a/virtocommerce-adapter/src/transformers/address.transformer.ts +++ b/virtocommerce-adapter/src/transformers/address.transformer.ts @@ -30,6 +30,27 @@ export class AddressTransformer extends BaseTransformer { }; } + /** + * Transform MCP Address to VirtoCommerce native Address format + */ + toVirtoAddress(address: Address, addressType?: 'Billing' | 'Shipping'): VirtoAddress { + return { + addressType: addressType, + firstName: address.firstName, + lastName: address.lastName, + name: this.composeName(address.firstName, address.lastName), + organization: address.company, + line1: address.address1, + line2: address.address2, + city: address.city, + regionName: address.stateOrProvince, + postalCode: address.zipCodeOrPostalCode, + countryName: address.country, + phone: address.phone, + email: address.email, + }; + } + /** * Transform VirtoCommerce address to MCP Address format */ diff --git a/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts b/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts index c955f93..a28c9db 100644 --- a/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts +++ b/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts @@ -110,7 +110,9 @@ export class FulfillmentTransformer extends BaseTransformer { * Map VirtoCommerce shipment status to normalized status */ private mapShipmentStatus(status?: string): string { - if (!status) return 'pending'; + if (!status) { + return 'pending'; + } return SHIPMENT_STATUS_MAP[status] ?? status; } } diff --git a/virtocommerce-adapter/src/transformers/order.transformer.ts b/virtocommerce-adapter/src/transformers/order.transformer.ts index 1ec0ba6..a384ca1 100644 --- a/virtocommerce-adapter/src/transformers/order.transformer.ts +++ b/virtocommerce-adapter/src/transformers/order.transformer.ts @@ -129,41 +129,66 @@ export class OrderTransformer extends BaseTransformer { } /** - * Transform UpdateOrderInput to API payload + * Apply MCP update fields to a VirtoCommerce CustomerOrder object. + * Returns the modified order ready for PUT save. */ - fromUpdateOrderInput(updates: UpdateOrderInput['updates']): Record { - const payload: Record = {}; + applyUpdatesToOrder(order: CustomerOrder, updates: UpdateOrderInput['updates']): CustomerOrder { + const updated = { ...order }; const status = this.valueOrUndefined((updates as { status?: string | null | undefined }).status); if (status) { - payload.status = this.reverseMapStatus(status); + updated.status = this.reverseMapStatus(status); + } + + const orderNote = this.valueOrUndefined((updates as { orderNote?: string | null }).orderNote); + if (orderNote !== undefined) { + updated.comment = orderNote; } const shippingAddress = this.valueOrUndefined( (updates as { shippingAddress?: Address | null }).shippingAddress ); if (shippingAddress) { - payload.shipping_address = this.addressTransformer.toFulfillmentAddress(shippingAddress); + const virtoShipping = this.addressTransformer.toVirtoAddress(shippingAddress, 'Shipping'); + // Update shipping address on the first shipment + if (updated.shipments?.length) { + updated.shipments = updated.shipments.map((s, i) => + i === 0 ? { ...s, deliveryAddress: virtoShipping } : s + ); + } + // Also update in the order-level addresses array + this.upsertAddress(updated, virtoShipping, 'Shipping'); } const billingAddress = this.valueOrUndefined( (updates as { billingAddress?: Address | null }).billingAddress ); if (billingAddress) { - payload.billing_address = this.addressTransformer.toFulfillmentAddress(billingAddress); + const virtoBilling = this.addressTransformer.toVirtoAddress(billingAddress, 'Billing'); + this.upsertAddress(updated, virtoBilling, 'Billing'); } - const notes = this.valueOrUndefined((updates as { notes?: string | null }).notes); - if (notes) { - payload.notes = notes; - } + return updated; + } - const tags = this.valueOrUndefined((updates as { tags?: string[] | null }).tags); - if (Array.isArray(tags)) { - payload.tags = tags; + /** + * Upsert an address in the order's addresses array by type. + * Replaces the first address of the given type, or appends if none found. + */ + private upsertAddress( + order: CustomerOrder, + address: import('../models/index.js').Address, + type: 'Billing' | 'Shipping' + ): void { + if (!order.addresses) { + order.addresses = []; + } + const idx = order.addresses.findIndex((a) => a.addressType === type); + if (idx >= 0) { + order.addresses[idx] = address; + } else { + order.addresses.push(address); } - - return payload; } /** diff --git a/virtocommerce-adapter/tests/adapter.test.ts b/virtocommerce-adapter/tests/adapter.test.ts index 278b85a..e39f3fe 100644 --- a/virtocommerce-adapter/tests/adapter.test.ts +++ b/virtocommerce-adapter/tests/adapter.test.ts @@ -31,7 +31,6 @@ describe('VirtoCommerceFulfillmentAdapter', () => { let getSpy: jest.MockedFunction; let postSpy: jest.MockedFunction; let putSpy: jest.MockedFunction; - let patchSpy: jest.MockedFunction; beforeEach(() => { // Create adapter instance @@ -48,7 +47,6 @@ describe('VirtoCommerceFulfillmentAdapter', () => { getSpy = jest.spyOn(mockApiClient, 'get') as unknown as jest.MockedFunction; postSpy = jest.spyOn(mockApiClient, 'post') as unknown as jest.MockedFunction; putSpy = jest.spyOn(mockApiClient, 'put') as unknown as jest.MockedFunction; - patchSpy = jest.spyOn(mockApiClient, 'patch') as unknown as jest.MockedFunction; }); describe('Lifecycle Methods', () => { @@ -334,33 +332,90 @@ describe('VirtoCommerceFulfillmentAdapter', () => { }); describe('updateOrder', () => { - it('should update order successfully', async () => { - patchSpy.mockResolvedValue({ + it('should update order status and shipping address', async () => { + // Mock GET to fetch the existing order + getSpy.mockResolvedValue({ success: true, data: { id: 'ORDER-001', number: 'ORD-2024-001', - external_id: 'EXT-001', - status: 'processing', - customer: { - id: 'CUST-001', - email: 'test@example.com', - first_name: 'Jane', - last_name: 'Smith', - }, - items: [], + outerId: 'EXT-001', + status: 'New', + customerId: 'CUST-001', + customerName: 'John Doe', + items: [ + { id: 'LI-001', sku: 'PROD-001', name: 'Test Product', quantity: 2, price: 29.99 }, + ], + shipments: [ + { + id: 'SHIP-001', + deliveryAddress: { + line1: '123 Main St', + city: 'New York', + regionName: 'NY', + postalCode: '10001', + countryName: 'US', + name: 'John Doe', + }, + }, + ], + addresses: [ + { + addressType: 'Shipping', + line1: '123 Main St', + city: 'New York', + regionName: 'NY', + postalCode: '10001', + countryName: 'US', + }, + ], total: 100.0, currency: 'USD', - created_at: '2024-01-01T00:00:00Z', - updated_at: '2024-01-02T00:00:00Z', - shipping_address: { - street1: '456 Oak Ave', - city: 'Los Angeles', - state: 'CA', - postal_code: '90001', - country: 'US', - }, - billing_address: {}, + createdDate: '2024-01-01T00:00:00Z', + modifiedDate: '2024-01-01T00:00:00Z', + }, + }); + + // Mock PUT to save the updated order + putSpy.mockResolvedValue({ + success: true, + data: { + id: 'ORDER-001', + number: 'ORD-2024-001', + outerId: 'EXT-001', + status: 'Processing', + customerId: 'CUST-001', + customerName: 'John Doe', + items: [ + { id: 'LI-001', sku: 'PROD-001', name: 'Test Product', quantity: 2, price: 29.99 }, + ], + shipments: [ + { + id: 'SHIP-001', + deliveryAddress: { + line1: '456 Oak Ave', + city: 'Los Angeles', + regionName: 'CA', + postalCode: '90001', + countryName: 'US', + name: 'Jane Smith', + }, + }, + ], + addresses: [ + { + addressType: 'Shipping', + line1: '456 Oak Ave', + city: 'Los Angeles', + regionName: 'CA', + postalCode: '90001', + countryName: 'US', + }, + ], + total: 100.0, + currency: 'USD', + createdDate: '2024-01-01T00:00:00Z', + modifiedDate: '2024-01-02T00:00:00Z', }, }); @@ -386,6 +441,126 @@ describe('VirtoCommerceFulfillmentAdapter', () => { if (result.success) { expect(result.order.id).toBe('ORDER-001'); expect(result.order.status).toBe('processing'); + expect(result.order.shippingAddress?.address1).toBe('456 Oak Ave'); + expect(result.order.shippingAddress?.city).toBe('Los Angeles'); + } + + expect(getSpy).toHaveBeenCalledWith('/api/order/customerOrders/ORDER-001'); + expect(putSpy).toHaveBeenCalledWith( + '/api/order/customerOrders', + expect.objectContaining({ + id: 'ORDER-001', + status: 'Processing', + }) + ); + }); + + it('should update order note', async () => { + getSpy.mockResolvedValue({ + success: true, + data: { + id: 'ORDER-002', + number: 'ORD-2024-002', + status: 'New', + comment: 'Original note', + items: [], + total: 50.0, + currency: 'USD', + createdDate: '2024-01-01T00:00:00Z', + modifiedDate: '2024-01-01T00:00:00Z', + }, + }); + + putSpy.mockResolvedValue({ + success: true, + data: { + id: 'ORDER-002', + number: 'ORD-2024-002', + status: 'New', + comment: 'Updated shipping instructions', + items: [], + total: 50.0, + currency: 'USD', + createdDate: '2024-01-01T00:00:00Z', + modifiedDate: '2024-01-02T00:00:00Z', + }, + }); + + const input: UpdateOrderInput = { + id: 'ORDER-002', + updates: { + orderNote: 'Updated shipping instructions', + }, + }; + + const result = await adapter.updateOrder(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.order.orderNote).toBe('Updated shipping instructions'); + } + + expect(putSpy).toHaveBeenCalledWith( + '/api/order/customerOrders', + expect.objectContaining({ + comment: 'Updated shipping instructions', + }) + ); + }); + + it('should handle update failure when order not found', async () => { + getSpy.mockResolvedValue({ + success: false, + error: { + code: 'ORDER_NOT_FOUND', + message: 'Order not found', + }, + }); + + const input: UpdateOrderInput = { + id: 'INVALID-ID', + updates: { status: 'processing' }, + }; + + const result = await adapter.updateOrder(input); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.message).toContain('Order not found'); + } + }); + + it('should handle save failure', async () => { + getSpy.mockResolvedValue({ + success: true, + data: { + id: 'ORDER-003', + number: 'ORD-2024-003', + status: 'New', + items: [], + createdDate: '2024-01-01T00:00:00Z', + modifiedDate: '2024-01-01T00:00:00Z', + }, + }); + + putSpy.mockResolvedValue({ + success: false, + error: { + code: 'VALIDATION_ERROR', + message: 'Invalid status transition', + }, + }); + + const input: UpdateOrderInput = { + id: 'ORDER-003', + updates: { status: 'shipped' }, + }; + + const result = await adapter.updateOrder(input); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.message).toContain('Failed to update order'); } }); }); From f620521f3c05662fa4ab2377bb9fd51426695ff6 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Sat, 31 Jan 2026 09:52:49 +0000 Subject: [PATCH 11/51] change order statuses --- virtocommerce-adapter/jest.config.js | 1 + .../src/transformers/order.transformer.ts | 12 ++---------- virtocommerce-adapter/src/types.ts | 13 +++++++++++++ virtocommerce-adapter/todo.md | 2 +- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/virtocommerce-adapter/jest.config.js b/virtocommerce-adapter/jest.config.js index b6969ad..be360e4 100644 --- a/virtocommerce-adapter/jest.config.js +++ b/virtocommerce-adapter/jest.config.js @@ -11,6 +11,7 @@ export default { '^.+\\.(ts|tsx)$': ['ts-jest', { useESM: true, tsconfig: { + module: 'ES2022', allowSyntheticDefaultImports: true } }] diff --git a/virtocommerce-adapter/src/transformers/order.transformer.ts b/virtocommerce-adapter/src/transformers/order.transformer.ts index a384ca1..cbd70c4 100644 --- a/virtocommerce-adapter/src/transformers/order.transformer.ts +++ b/virtocommerce-adapter/src/transformers/order.transformer.ts @@ -10,7 +10,7 @@ import type { CreateSalesOrderInput, UpdateOrderInput, } from '@cof-org/mcp'; -import { STATUS_MAP } from '../types.js'; +import { STATUS_MAP, REVERSE_STATUS_MAP } from '../types.js'; import type { CustomerOrder, LineItem, DynamicObjectProperty, Contact } from '../models/index.js'; import { BaseTransformer } from './base.js'; import { AddressTransformer } from './address.transformer.js'; @@ -240,14 +240,6 @@ export class OrderTransformer extends BaseTransformer { * Reverse map normalized status to VirtoCommerce status */ private reverseMapStatus(status: string): string { - const reverse = Object.entries(STATUS_MAP).reduce>( - (acc, [fulfillmentStatus, normalized]) => { - acc[normalized] = fulfillmentStatus; - return acc; - }, - {} - ); - - return reverse[status] ?? status; + return REVERSE_STATUS_MAP[status] ?? status; } } diff --git a/virtocommerce-adapter/src/types.ts b/virtocommerce-adapter/src/types.ts index c20ff2d..552a6b7 100644 --- a/virtocommerce-adapter/src/types.ts +++ b/virtocommerce-adapter/src/types.ts @@ -158,6 +158,19 @@ export const STATUS_MAP: Record = { partially_delivered: 'partially_delivered', }; +// Reverse mapping: normalized MCP status → VirtoCommerce PascalCase status +export const REVERSE_STATUS_MAP: Record = { + pending: 'New', + processing: 'Processing', + shipped: 'Shipped', + delivered: 'Delivered', + cancelled: 'Cancelled', + on_hold: 'OnHold', + refunded: 'Refunded', + partially_shipped: 'PartiallyShipped', + partially_delivered: 'PartiallyDelivered', +}; + // Error codes for consistent error handling export enum ErrorCode { CONNECTION_FAILED = 'CONNECTION_FAILED', diff --git a/virtocommerce-adapter/todo.md b/virtocommerce-adapter/todo.md index c16566b..a3bced7 100644 --- a/virtocommerce-adapter/todo.md +++ b/virtocommerce-adapter/todo.md @@ -1 +1 @@ -[ ] order statuses mapping \ No newline at end of file +[x] order statuses mapping \ No newline at end of file From feb54277eed8799618b477a2783f35b17cca0de1 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Mon, 9 Feb 2026 11:25:45 +0000 Subject: [PATCH 12/51] prepare to npm --- virtocommerce-adapter/examples.md | 2 ++ virtocommerce-adapter/package.json | 15 ++++++++++++--- virtocommerce-adapter/src/adapter.ts | 4 ++-- virtocommerce-adapter/src/utils/api-client.ts | 6 +++--- 4 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 virtocommerce-adapter/examples.md diff --git a/virtocommerce-adapter/examples.md b/virtocommerce-adapter/examples.md new file mode 100644 index 0000000..89bebb9 --- /dev/null +++ b/virtocommerce-adapter/examples.md @@ -0,0 +1,2 @@ +'{"jsonrpc":"2.0","method":"tools/call","id":2,"params":{"name":"get-orders","arguments":{"ids":["abbf43ada4124ed7bcbf5a2c37663ed9"],"includeLineItems":true,"pageSize":20,"skip":0}}}' | node server\dist\index.js + diff --git a/virtocommerce-adapter/package.json b/virtocommerce-adapter/package.json index 1da7cce..76b2297 100644 --- a/virtocommerce-adapter/package.json +++ b/virtocommerce-adapter/package.json @@ -2,6 +2,14 @@ "name": "@virtocommerce/vc-fulfillment-mcp-adapter", "version": "1.0.0", "description": "Commerce Operations Foundation adapter for VirtoCommerce Fulfillment", + "repository": { + "type": "git", + "url": "https://github.com/VirtoCommerce/mcp-onx" + }, + "bugs": { + "url": "https://github.com/VirtoCommerce/mcp-onx/issues" + }, + "homepage": "https://docs.virtocommerce.com/adapter", "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -27,9 +35,10 @@ "cof", "fulfillment", "adapter", - "mcp" + "mcp", + "virtocommerce" ], - "author": "Your Company", + "author": "VirtoCommerce ", "license": "MIT", "devDependencies": { "@eslint/js": "^9.8.0", @@ -48,4 +57,4 @@ "@cof-org/mcp": "file:../server", "axios": "^1.6.0" } -} +} \ No newline at end of file diff --git a/virtocommerce-adapter/src/adapter.ts b/virtocommerce-adapter/src/adapter.ts index 7f1790b..fd7dbbe 100644 --- a/virtocommerce-adapter/src/adapter.ts +++ b/virtocommerce-adapter/src/adapter.ts @@ -111,7 +111,7 @@ export class VirtoCommerceFulfillmentAdapter implements IFulfillmentAdapter { } this.connected = true; - console.info('Successfully connected to VirtoCommerce'); + console.error('Successfully connected to VirtoCommerce'); } catch (error: unknown) { this.connected = false; throw new AdapterError( @@ -124,7 +124,7 @@ export class VirtoCommerceFulfillmentAdapter implements IFulfillmentAdapter { async disconnect(): Promise { this.connected = false; - console.info('Disconnected from VirtoCommerce'); + console.error('Disconnected from VirtoCommerce'); } async healthCheck(): Promise { diff --git a/virtocommerce-adapter/src/utils/api-client.ts b/virtocommerce-adapter/src/utils/api-client.ts index 4ddbb13..540e4ce 100644 --- a/virtocommerce-adapter/src/utils/api-client.ts +++ b/virtocommerce-adapter/src/utils/api-client.ts @@ -52,7 +52,7 @@ export class ApiClient { this.client.interceptors.request.use( (request) => { if (this.debugMode) { - console.log('[API Request]', { + console.error('[API Request]', { method: request.method?.toUpperCase(), url: request.url, params: request.params, @@ -73,7 +73,7 @@ export class ApiClient { this.client.interceptors.response.use( (response) => { if (this.debugMode) { - console.log('[API Response]', { + console.error('[API Response]', { status: response.status, statusText: response.statusText, url: response.config.url, @@ -201,7 +201,7 @@ export class ApiClient { // Wait before retrying (exponential backoff) const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000); if (this.debugMode) { - console.log(`[API Retry] Attempt ${attempt + 1}/${this.retryAttempts} after ${delay}ms`); + console.error(`[API Retry] Attempt ${attempt + 1}/${this.retryAttempts} after ${delay}ms`); } await this.sleep(delay); From 5bf2e98b01ff9cfe225e398ad57367de9e5eabf9 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Wed, 11 Feb 2026 19:25:24 +0000 Subject: [PATCH 13/51] create dockerfile which works over http --- .claude/settings.local.json | 22 ++- .dockerignore | 35 ++++ CLAUDE.md | 208 ++++++++++++++++++------ Dockerfile | 71 ++++++++ docker-compose.yml | 15 ++ server/src/adapters/adapter-factory.ts | 3 +- server/src/index.ts | 28 +++- server/src/logging/structured-logger.ts | 3 +- server/src/server.ts | 80 +++++++++ virtocommerce-adapter/package.json | 2 +- virtocommerce-adapter/tsconfig.json | 2 +- 11 files changed, 410 insertions(+), 59 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 4eececb..6ed1a6b 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -4,7 +4,27 @@ "WebFetch(domain:raw.githubusercontent.com)", "Bash(npx tsc:*)", "Bash(npm run build:*)", - "Bash(npm test)" + "Bash(npm test)", + "Bash(tree:*)", + "Bash(npx jest:*)", + "Bash(dir /s \"e:\\\\Develops\\\\VirtoWay\\\\projects\\\\tasks\\\\4464\\\\mcp-reference-server\\\\virtocommerce-adapter\")", + "Bash(dir /B /S \"e:\\\\Develops\\\\VirtoWay\\\\projects\\\\tasks\\\\4464\\\\mcp-reference-server\\\\virtocommerce-adapter\")", + "Bash(node --experimental-vm-modules node_modules/jest/bin/jest.js:*)", + "Bash(test:*)", + "Bash(npm ls:*)", + "Bash(docker build:*)", + "Bash(docker run:*)", + "Bash(powershell -Command \"Write-Output ''{\"\"jsonrpc\"\":\"\"2.0\"\",\"\"method\"\":\"\"tools/list\"\",\"\"id\"\":1}'' | docker run --rm -i -e ADAPTER_CONFIG=''{}'' cof-mcp-virto\")", + "Bash(powershell -Command \"Write-Output ''{\"\"jsonrpc\"\":\"\"2.0\"\",\"\"method\"\":\"\"tools/list\"\",\"\"id\"\":1}'' | docker run --rm -i -e ADAPTER_CONFIG=''{\"\"apiUrl\"\":\"\"https://host.docker.internal:5001\"\",\"\"apiKey\"\":\"\"76bf85d9-196e-4d4a-a6d7-6765102361c9\"\"}'' -e LOG_LEVEL=debug cof-mcp-virto\")", + "Bash(powershell -Command \"Write-Output ''{\"\"jsonrpc\"\":\"\"2.0\"\",\"\"method\"\":\"\"tools/list\"\",\"\"id\"\":1}'' | docker run --rm -i --env-file ''e:\\\\Develops\\\\VirtoWay\\\\projects\\\\tasks\\\\4464\\\\mcp-reference-server\\\\.env.docker'' cof-mcp-virto\")", + "Bash(npm view:*)", + "Bash(powershell -Command:*)", + "Bash(timeout:*)", + "Bash(curl:*)", + "Bash(docker stop:*)", + "Bash(docker rm:*)", + "Bash(node -e:*)", + "Bash(docker compose up:*)" ] } } diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b4d6574 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,35 @@ +node_modules +dist +coverage +*.tsbuildinfo + +.env* +!.env.example + +.git +.gitignore +.gitattributes + +.vscode/ +.idea/ +.claude/ + +docs/ +prompts/ +schemas/ +adapter-template/ + +**/tests/ +**/*.test.ts +**/*.spec.ts + +*.md +!package.json + +logs/ +*.log +tmp/ +temp/ + +.DS_Store +Thumbs.db diff --git a/CLAUDE.md b/CLAUDE.md index 19cd432..1eae09b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,94 +4,179 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Repository Overview -This is the Commerce Operations Foundation (COF) MCP Reference Server - a monorepo implementing the Order Network eXchange (onX) standard for AI-powered fulfillment operations via the Model Context Protocol. +This is the Commerce Operations Foundation (COF) MCP Reference Server - a monorepo implementing the Order Network eXchange (onX) standard for AI-powered fulfillment operations via the Model Context Protocol. Active development branch: `virto` (VirtoCommerce adapter integration). ## Repository Structure ``` -/server - Core MCP server implementation (main development target) -/adapter-template - Boilerplate for creating custom fulfillment adapters -/schemas - JSON Schema definitions for domain models -/docs - Specification and architectural documentation +/server - Core MCP server (main package: @cof-org/mcp) +/virtocommerce-adapter - VirtoCommerce fulfillment adapter (@virtocommerce/vc-fulfillment-mcp-adapter) +/adapter-template - Boilerplate for creating new adapters +/schemas - JSON Schema definitions for domain models +/docs - Specification and architectural documentation +/prompts - Developer notes/context for the integration work ``` ## Development Commands -All primary development happens in `/server`. Run commands from that directory: +### Server (`/server`) — Vitest ```bash cd server npm install -npm run build # Compile TypeScript -npm run dev # Development with hot reload -npm test # Build + run all tests -npm run test:unit # Unit tests only +npm run build # Compile TypeScript to dist/ +npm run dev # Hot reload via vite-node +npm test # Build + run all tests +npm run test:unit # Unit tests only npm run test:integration -npx vitest tests/unit/file.test.ts # Run single test file -npm run lint # ESLint -npm run format # Prettier +npx vitest tests/unit/adapters/adapter-factory.test.ts # Single test file +npx vitest tests/unit --grep "AdapterFactory" # Tests matching pattern +npm run lint # ESLint +npm run format # Prettier +npm run generate:json-schemas # Regenerate /schemas from TypeScript types ``` -For adapter development in `/adapter-template`: +### VirtoCommerce Adapter (`/virtocommerce-adapter`) — Jest + ```bash -cd adapter-template +cd virtocommerce-adapter npm install -npm run build -npm test -npm run dev # Watch mode compilation +npm run build # Compile TypeScript +npm run dev # Watch mode (tsc --watch) +npm test # Jest with experimental VM modules +npm run test:integration # Build + test-integration.js ``` +**Important**: The VirtoCommerce adapter uses **Jest** (not Vitest like the server). It depends on the server via `file:../server` — build the server first. + ## Architecture ### Three-Layer Design 1. **Protocol Layer** (`server/src/server.ts`, `server/src/index.ts`) - - MCP SDK integration with stdio transport - - Request routing (tools/list, tools/call) + - Dual transport: **stdio** (default, for Claude Desktop) and **SSE** (HTTP, for cloud/remote) + - All `console.*` redirected to `stderr` to keep stdout clean for MCP JSON-RPC + - Loads `.env` then `.env.local` (overrides) via dotenv + - Transport selected via `MCP_TRANSPORT` env var (`stdio` | `sse`) 2. **Service Layer** (`server/src/services/`) - - `ServiceOrchestrator` - main facade for all operations + - `ServiceOrchestrator` — main facade; every operation wraps adapter calls with timeout, error handling, and metrics - `AdapterManager`, `HealthMonitor`, `ErrorHandler`, `Transformer`, `Validator` 3. **Adapter Layer** (`server/src/adapters/`) - - `IFulfillmentAdapter` interface that all adapters implement - - Pluggable: supports built-in, NPM packages, and local file adapters - - `AdapterFactory` handles creation and caching + - `IFulfillmentAdapter` interface (14 methods: 4 lifecycle + 5 actions + 5 queries + optional) + - `AdapterFactory` — creates, validates, and caches adapter instances + - Dynamic loading via `import()` for npm/local adapters ### Tool System -Tools in `server/src/tools/` extend `BaseTool`: -- **Actions**: `create-sales-order`, `update-order`, `cancel-order`, `fulfill-order`, `create-return` -- **Queries**: `get-orders`, `get-customers`, `get-products`, `get-product-variants`, `get-inventory`, `get-fulfillments`, `get-returns` +Tools in `server/src/tools/` extend `BaseTool` and are **manually registered** in `registerTools()` (not auto-discovered): +- **Actions** (`tools/actions/`): `create-sales-order`, `update-order`, `cancel-order`, `fulfill-order`, `create-return` +- **Queries** (`tools/queries/`): `get-orders`, `get-customers`, `get-products`, `get-product-variants`, `get-inventory`, `get-fulfillments`, `get-returns` -New tools: create in `src/tools/[category]/`, extend `BaseTool`, add to `registerTools` function. +Tools are thin wrappers — all business logic lives in `ServiceOrchestrator`. ### Adapter Loading Configured via environment variables: -- `ADAPTER_TYPE=built-in` + `ADAPTER_NAME=mock` - Use built-in adapter -- `ADAPTER_TYPE=npm` + `ADAPTER_PACKAGE=@company/adapter` - Load from NPM -- `ADAPTER_TYPE=local` + `ADAPTER_PATH=/path/to/adapter.js` - Load local file +- `ADAPTER_TYPE=built-in` + `ADAPTER_NAME=mock` — built-in MockAdapter +- `ADAPTER_TYPE=npm` + `ADAPTER_PACKAGE=@company/adapter` — dynamic import from node_modules +- `ADAPTER_TYPE=local` + `ADAPTER_PATH=/path/to/adapter.js` — dynamic import from file path +- `ADAPTER_CONFIG={"apiUrl":"...","apiKey":"..."}` — JSON config passed to adapter +- `ADAPTER_EXPORT=ClassName` — optional, defaults to `default` export + +### Logging + +Winston-based structured logging (`server/src/logging/structured-logger.ts`): +- Console transport to stderr (all levels, colorized) +- File rotation: `error.log` (10MB/5 files), `combined.log` (10MB/10 files) +- Daily rotation in production with gzip, 14-day retention +- Built-in correlation IDs, PII sanitization, and structured methods (`logRequest`, `logResponse`, `logToolExecution`, `logAdapterCall`) + +## VirtoCommerce Adapter + +### Architecture + +Domain-driven design with service delegation: + +``` +adapter.ts (VirtoCommerceFulfillmentAdapter) +├── services/ +│ ├── order.service.ts — order CRUD + search +│ ├── customer.service.ts — customer queries +│ ├── fulfillment.service.ts — shipment tracking +│ ├── product.service.ts — products, variants, inventory +│ └── return.service.ts — returns handling +├── transformers/ — VirtoCommerce ↔ MCP bidirectional mapping +├── mappers/filter.mappers.ts — MCP filters → VC search criteria +├── models/ — TypeScript defs matching VC API +└── utils/api-client.ts — axios with retry, timeout, auth +``` -### JSON Schemas +### Key Patterns -Domain model schemas in `/schemas/`: -- `order.json`, `customer.json`, `product.json`, `product-variant.json` -- `inventory.json`, `fulfillment.json`, `return.json` -- `tool-inputs/` - Input schemas for each tool +- **Status mapping**: VirtoCommerce uses PascalCase (`New`, `Processing`, `Shipped`), MCP uses lowercase (`pending`, `processing`, `shipped`). See `STATUS_MAP` / `REVERSE_STATUS_MAP` in `types.ts`. +- **Order updates**: Three-step (GET current → apply changes → PUT full order), not PATCH. +- **Customer enrichment**: Orders fetched separately from customers, then merged via `customerMap`. +- **API authentication**: `api_key` header (configurable), with exponential backoff retry (default 3 attempts, 30s timeout). -Generate schemas: `npm run generate:json-schemas` (in server/) +### Testing via JSON-RPC (stdio) + +```bash +echo '{"jsonrpc":"2.0","method":"tools/call","id":2,"params":{"name":"get-orders","arguments":{"ids":["order-id"],"includeLineItems":true}}}' | node server\dist\index.js +``` + +## SSE Transport & HTTP Deployment + +### SSE Endpoints + +When running with `MCP_TRANSPORT=sse`: +- `GET /sse` — Establishes SSE connection (returns `sessionId`) +- `POST /messages?sessionId=` — Sends JSON-RPC messages to a session +- `GET /health` — Health check (`{"status":"ok","transport":"sse","sessions":N}`) +- CORS enabled for all origins + +### Running via Docker + +```bash +# Local testing +docker compose up --build + +# Or directly +docker build -t cof-mcp . +docker run -p 3000:3000 --env-file .env.docker -e MCP_TRANSPORT=sse -e MCP_PORT=3000 cof-mcp +``` + +The Dockerfile is a multi-stage build (builder → prod-deps → runtime) on `node:22-alpine`. Runs as non-root user `cof`. HEALTHCHECK hits `/health` every 30s. + +`docker-compose.yml` uses `.env.docker` for adapter config and adds `host.docker.internal` for accessing local VirtoCommerce API from inside the container. + +### Cloud Deployment + +For cloud, override these environment variables: +- `ADAPTER_CONFIG` — set real VirtoCommerce API URL and key (not `host.docker.internal`) +- `NODE_TLS_REJECT_UNAUTHORIZED` — remove or set to `1` in production +- `LOG_LEVEL` — use `info` or `warn` (debug is force-disabled in production anyway) + +### Error Handling (Two-Tier) + +1. **Protocol errors** — thrown as `McpError` (invalid request, unknown tool) → MCP SDK handles, client sees JSON-RPC error +2. **Tool execution errors** — returned as `{ content: [...], isError: true }` (adapter failures, business logic errors) → client can inspect and retry + +`ErrorAdapter.processError()` in `server/src/errors/error-adapter.ts` decides which tier an error belongs to. ## Key Implementation Details ### ES Module Requirements - All imports use `.js` extension (e.g., `from './file.js'`) -- `type: "module"` in package.json +- `type: "module"` in both packages - Use `import`/`export`, not `require` -### Test Isolation -- Vitest with `singleThread: true` to prevent race conditions +### Test Isolation (Server) +- Vitest with `singleThread: true` to prevent AdapterFactory cache race conditions - Always call `AdapterFactory.clearInstances()` in test cleanup +- Setup file: `tests/setup.ts` +- Path alias: `@` → `./src` ### MCP Response Format ```typescript @@ -104,18 +189,19 @@ Generate schemas: `npm run generate:json-schemas` (in server/) ### ServiceOrchestrator Lifecycle ```typescript await serviceOrchestrator.initialize(adapterConfig); // Required before use +await serviceOrchestrator.cleanup(); // Disconnect + stop monitors ``` -## Creating a Custom Adapter +### IFulfillmentAdapter Required Methods +Lifecycle: `connect()`, `disconnect()`, `healthCheck()`, optional `initialize(config)` +Actions: `createSalesOrder`, `cancelOrder`, `updateOrder`, `fulfillOrder`, `createReturn` +Queries: `getOrders`, `getCustomers`, `getProducts`, `getProductVariants`, `getInventory`, `getFulfillments`, `getReturns` -1. Copy `/adapter-template` to your project -2. Implement `IFulfillmentAdapter` interface (all 12 operations) -3. For built-in: add to `AdapterFactory.builtInAdapters` map -4. For external: publish as NPM package or load via local path +`AdapterFactory.validateAdapter()` checks all required methods at creation time. -The interface requires: `initialize()`, `shutdown()`, plus operations like `createSalesOrder`, `getOrders`, `getCustomers`, etc. +## Client Integration -## Claude Desktop Integration +### Claude Desktop (stdio) ```json { @@ -124,8 +210,10 @@ The interface requires: `initialize()`, `shutdown()`, plus operations like `crea "command": "node", "args": ["/absolute/path/to/server/dist/index.js"], "env": { - "ADAPTER_TYPE": "built-in", - "ADAPTER_NAME": "mock" + "ADAPTER_TYPE": "local", + "ADAPTER_PATH": "/absolute/path/to/virtocommerce-adapter/dist/index.js", + "ADAPTER_CONFIG": "{\"apiUrl\":\"https://vc.example.com\",\"apiKey\":\"key\",\"workspace\":\"default\"}", + "LOG_LEVEL": "info" } } } @@ -134,11 +222,25 @@ The interface requires: `initialize()`, `shutdown()`, plus operations like `crea Config location: `%APPDATA%\Claude\claude_desktop_config.json` (Windows), `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) +### Remote SSE (Docker / Cloud) + +```json +{ + "mcpServers": { + "cof-mcp": { + "url": "http://localhost:3000/sse" + } + } +} +``` + +For cloud deployments, replace `localhost:3000` with the actual host URL. + ## Detailed Server Documentation See `/server/CLAUDE.md` for comprehensive server-specific guidance including: -- Configuration system details -- Error handling patterns +- Configuration system details (ConfigManager, multi-source loading, environment priority) +- Error handling patterns (McpError vs tool errors, ErrorHandler pipeline) - Health monitoring -- Security considerations -- Common development patterns +- Security considerations (dynamic adapter loading, production hardening) +- Common development patterns (adding tools, adding service operations) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0a7753c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,71 @@ +# Multi-stage build: MCP Server + VirtoCommerce Adapter +# Build context: repository root + +# --- Stage 1: Build both packages --- +FROM node:22-alpine AS builder +WORKDIR /app + +# Server: install deps & build +COPY server/package*.json server/ +RUN cd server && npm ci + +COPY server/tsconfig.json server/ +COPY server/src/ server/src/ +RUN cd server && npm run build + +# VirtoCommerce adapter: install deps & build +# (depends on server via file:../server, so server must be present) +COPY virtocommerce-adapter/package*.json virtocommerce-adapter/ +RUN cd virtocommerce-adapter && npm ci + +COPY virtocommerce-adapter/tsconfig.json virtocommerce-adapter/ +COPY virtocommerce-adapter/src/ virtocommerce-adapter/src/ +RUN cd virtocommerce-adapter && npm run build + +# --- Stage 2: Production deps only --- +FROM node:22-alpine AS prod-deps +WORKDIR /app + +COPY server/package*.json server/ +RUN cd server && npm ci --omit=dev + +COPY virtocommerce-adapter/package*.json virtocommerce-adapter/ +COPY --from=builder /app/server/ server/ +RUN cd virtocommerce-adapter && npm ci --omit=dev + +# --- Stage 3: Runtime --- +FROM node:22-alpine + +RUN addgroup -g 1001 -S nodejs && \ + adduser -S cof -u 1001 + +WORKDIR /app + +# Server +COPY --from=builder --chown=cof:nodejs /app/server/dist server/dist +COPY --from=prod-deps --chown=cof:nodejs /app/server/node_modules server/node_modules +COPY --chown=cof:nodejs server/package.json server/ + +# VirtoCommerce adapter +COPY --from=builder --chown=cof:nodejs /app/virtocommerce-adapter/dist virtocommerce-adapter/dist +COPY --from=prod-deps --chown=cof:nodejs /app/virtocommerce-adapter/node_modules virtocommerce-adapter/node_modules +COPY --chown=cof:nodejs virtocommerce-adapter/package.json virtocommerce-adapter/ + +RUN mkdir -p /app/logs && chown cof:nodejs /app/logs + +USER cof + +ENV NODE_ENV=production +ENV LOG_LEVEL=info +ENV ADAPTER_TYPE=local +ENV ADAPTER_PATH=/app/virtocommerce-adapter/dist/index.js +ENV MCP_TRANSPORT=sse +ENV MCP_PORT=3000 + +EXPOSE 3000 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD wget -qO- http://localhost:3000/health || exit 1 + +WORKDIR /app/server +CMD ["node", "dist/index.js"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a7205c9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +services: + mcp-server: + build: + context: . + dockerfile: Dockerfile + ports: + - "3000:3000" + env_file: + - .env.docker + environment: + - MCP_TRANSPORT=sse + - MCP_PORT=3000 + extra_hosts: + - "host.docker.internal:host-gateway" + restart: unless-stopped diff --git a/server/src/adapters/adapter-factory.ts b/server/src/adapters/adapter-factory.ts index d0f8317..ad3a476 100644 --- a/server/src/adapters/adapter-factory.ts +++ b/server/src/adapters/adapter-factory.ts @@ -4,6 +4,7 @@ import * as path from 'path'; import * as fs from 'fs'; +import { pathToFileURL } from 'url'; import { IFulfillmentAdapter, AdapterConfig, AdapterConstructor, AdapterError } from '../types/index.js'; import { Logger } from '../utils/logger.js'; import { MockAdapter } from './mock/mock-adapter.js'; @@ -178,7 +179,7 @@ export class AdapterFactory { try { // Dynamically import local file - const module = await import(adapterPath); + const module = await import(pathToFileURL(adapterPath).href); const exportName = config.exportName || 'default'; const AdapterClass = module[exportName]; diff --git a/server/src/index.ts b/server/src/index.ts index 32165b2..81f6e45 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -5,6 +5,9 @@ * Entry point using MCP SDK components */ +import * as fs from 'fs'; +import * as path from 'path'; +import dotenv from 'dotenv'; import { fileURLToPath } from 'url'; import { ConfigManager } from './config/config-manager.js'; @@ -16,6 +19,22 @@ import { TimeoutHandler } from './utils/timeout.js'; async function main() { try { + // Ensure stdout stays чистым для MCP JSON-RPC. + console.log = console.error; + console.info = console.error; + console.warn = console.error; + console.debug = console.error; + + // Load .env files before config initialization (local overrides default). + const envPath = path.join(process.cwd(), '.env'); + const envLocalPath = path.join(process.cwd(), '.env.local'); + if (fs.existsSync(envPath)) { + dotenv.config({ path: envPath }); + } + if (fs.existsSync(envLocalPath)) { + dotenv.config({ path: envLocalPath, override: true }); + } + // Initialize logger with safe defaults before config loading Logger.init('info'); @@ -38,7 +57,14 @@ async function main() { // Create and start server const server = new MCPServerSDK(config); - await server.start(); + + const transport = process.env.MCP_TRANSPORT || 'stdio'; + if (transport === 'sse') { + const port = parseInt(process.env.MCP_PORT || '3000', 10); + await server.startSSE(port); + } else { + await server.start(); + } // Handle graceful shutdown const shutdown = async () => { diff --git a/server/src/logging/structured-logger.ts b/server/src/logging/structured-logger.ts index 7f44d1b..b65b72e 100644 --- a/server/src/logging/structured-logger.ts +++ b/server/src/logging/structured-logger.ts @@ -6,6 +6,7 @@ import * as winston from 'winston'; import * as path from 'path'; import * as fs from 'fs'; +import { fileURLToPath } from 'url'; import DailyRotateFile from 'winston-daily-rotate-file'; import { LogSanitizer } from '../security/log-sanitizer.js'; @@ -42,7 +43,7 @@ export class StructuredLogger { // Make log directory absolute if it's relative if (!path.isAbsolute(logDir)) { // Find the server directory (parent of dist when running compiled code) - const currentDir = path.dirname(new URL(import.meta.url).pathname); + const currentDir = path.dirname(fileURLToPath(import.meta.url)); const serverDir = currentDir.includes('/dist/') ? path.resolve(currentDir.split('/dist/')[0]) : path.resolve(process.cwd(), 'server'); diff --git a/server/src/server.ts b/server/src/server.ts index 6a789ba..35249f2 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -3,8 +3,10 @@ * This is the recommended approach for MCP servers */ +import * as http from 'http'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import { CallToolRequestSchema, ListToolsRequestSchema, @@ -25,6 +27,8 @@ export class MCPServerSDK { private serviceOrchestrator: ServiceOrchestrator; private toolRegistry: ToolRegistry; private config: ServerConfig; + private httpServer?: http.Server; + private sseTransports: Map = new Map(); constructor(config: ServerConfig) { this.config = config; @@ -131,6 +135,71 @@ export class MCPServerSDK { Logger.info('MCP server running on stdio transport'); } + async startSSE(port: number): Promise { + Logger.info('Starting MCP server with SSE transport...'); + + await this.serviceOrchestrator.initialize(this.config.adapter); + await this.registerTools(); + + this.httpServer = http.createServer(async (req, res) => { + // CORS headers + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + + if (req.method === 'OPTIONS') { + res.writeHead(204); + res.end(); + return; + } + + const url = new URL(req.url || '/', `http://localhost:${port}`); + + if (url.pathname === '/sse' && req.method === 'GET') { + // New SSE connection + const transport = new SSEServerTransport('/messages', res); + this.sseTransports.set(transport.sessionId, transport); + Logger.info(`SSE client connected: ${transport.sessionId}`); + + transport.onclose = () => { + this.sseTransports.delete(transport.sessionId); + Logger.info(`SSE client disconnected: ${transport.sessionId}`); + }; + + await this.server.connect(transport); + return; + } + + if (url.pathname === '/messages' && req.method === 'POST') { + // Route POST to the correct session + const sessionId = url.searchParams.get('sessionId'); + const transport = sessionId ? this.sseTransports.get(sessionId) : undefined; + + if (!transport) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'Invalid or missing sessionId' })); + return; + } + + await transport.handlePostMessage(req, res); + return; + } + + if (url.pathname === '/health' && req.method === 'GET') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ status: 'ok', transport: 'sse', sessions: this.sseTransports.size })); + return; + } + + res.writeHead(404); + res.end(); + }); + + this.httpServer.listen(port, () => { + Logger.info(`MCP server running on SSE transport at http://0.0.0.0:${port}/sse`); + }); + } + private async registerTools(): Promise { Logger.debug('Registering Fulfillment tools...'); @@ -147,6 +216,17 @@ export class MCPServerSDK { // Cleanup service orchestrator first await this.serviceOrchestrator.cleanup(); + // Close all SSE transports + for (const transport of this.sseTransports.values()) { + await transport.close(); + } + this.sseTransports.clear(); + + // Close HTTP server if running + if (this.httpServer) { + await new Promise((resolve) => this.httpServer!.close(() => resolve())); + } + // Disconnect the MCP server properly if (this.server) { await this.server.close(); diff --git a/virtocommerce-adapter/package.json b/virtocommerce-adapter/package.json index 76b2297..bd19906 100644 --- a/virtocommerce-adapter/package.json +++ b/virtocommerce-adapter/package.json @@ -28,7 +28,7 @@ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", "test:integration": "npm run build && node test-integration.js", "lint": "eslint .", - "prepublishOnly": "npm run build && npm test", + "prepublishOnly": "npm run build", "dev": "tsc --watch" }, "keywords": [ diff --git a/virtocommerce-adapter/tsconfig.json b/virtocommerce-adapter/tsconfig.json index 318a2ae..de6708d 100644 --- a/virtocommerce-adapter/tsconfig.json +++ b/virtocommerce-adapter/tsconfig.json @@ -21,7 +21,7 @@ "resolveJsonModule": true, "moduleResolution": "node", "removeComments": true, - "noImplicitAny": true, + "noImplicitAny": false, "strictNullChecks": true, "strictFunctionTypes": true, "strictBindCallApply": true, From 6fbadbc7add7bab687e9c056ca46fa8a79cdf0c2 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Thu, 12 Feb 2026 21:17:24 +0000 Subject: [PATCH 14/51] add ci --- .github/workflows/mcp-ci.yml | 150 +++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 .github/workflows/mcp-ci.yml diff --git a/.github/workflows/mcp-ci.yml b/.github/workflows/mcp-ci.yml new file mode 100644 index 0000000..5719300 --- /dev/null +++ b/.github/workflows/mcp-ci.yml @@ -0,0 +1,150 @@ +# v1.0.1 +name: MCP Server CI + +on: + workflow_dispatch: + +jobs: + ci: + runs-on: ubuntu-latest + env: + FORCE_COLOR: true + CLOUD_INSTANCE_BASE_URL: ${{secrets.CLOUD_INSTANCE_BASE_URL}} + CLIENT_ID: ${{secrets.CLIENT_ID}} + CLIENT_SECRET: ${{secrets.CLIENT_SECRET}} + GITHUB_TOKEN: ${{ secrets.REPO_TOKEN }} + BLOB_SAS: ${{ secrets.BLOB_TOKEN }} + VERSION: '' + BUILD_STATE: 'failed' + RELEASE_STATUS: 'false' + DOCKER_REPOSITORY: 'ghcr.io' + DOCKER_IMAGE: 'vc-fulfillment-mcp-server' + DOCKER_TAG: '' + outputs: + jira-keys: ${{ steps.jira_keys.outputs.jira-keys }} + version: ${{ env.VERSION }} + tag: ${{ env.DOCKER_TAG }} + + steps: + + - name: Set up Node 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Set RELEASE_STATUS + if: ${{ github.ref == 'refs/heads/master' }} + run: echo "RELEASE_STATUS=true" >> $GITHUB_ENV + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get version from package.json + id: version + run: | + BASE_VERSION=$(jq -r .version server/package.json) + SHORT_SHA=$(git rev-parse --short HEAD) + echo "base=$BASE_VERSION" >> $GITHUB_OUTPUT + echo "sha=$SHORT_SHA" >> $GITHUB_OUTPUT + + - name: Set VERSION + run: | + if [ '${{ github.ref }}' = 'refs/heads/master' ]; then + echo "VERSION=${{ steps.version.outputs.base }}" >> $GITHUB_ENV + else + echo "VERSION=${{ steps.version.outputs.base }}-alpha.${{ github.run_number }}" >> $GITHUB_ENV + fi + + - name: Update package.json Version + run: | + npm version ${{ env.VERSION }} --no-git-tag-version --prefix server + npm version ${{ env.VERSION }} --no-git-tag-version --prefix virtocommerce-adapter + + - name: Set DOCKER_TAG + run: echo "DOCKER_TAG=${{ env.VERSION }}" >> $GITHUB_ENV + + - name: Install and build server + run: cd server && npm ci && npm run build + + - name: Install and build adapter + run: cd virtocommerce-adapter && npm ci && npm run build + + - name: BUILD_STATE::successful + if: success() + run: echo "BUILD_STATE=successful" >> $GITHUB_ENV + + - name: Add files to zip + run: | + mkdir ./artifacts + zip -r ./artifacts/vc-fulfillment-mcp-server-${{ env.VERSION }}.zip \ + server/dist/ server/package.json \ + virtocommerce-adapter/dist/ virtocommerce-adapter/package.json \ + Dockerfile docker-compose.yml + + - name: Publish to Blob + id: blobRelease + uses: VirtoCommerce/vc-github-actions/publish-blob-release@master + with: + blobSAS: ${{ secrets.BLOB_TOKEN }} + blobUrl: ${{ vars.BLOB_URL }} + + - name: Set URLs + id: urls + run: | + echo "DOCKER_URL=${{ env.DOCKER_REPOSITORY }}/${GITHUB_REPOSITORY_OWNER,,}/${{ env.DOCKER_IMAGE }}:" >> $GITHUB_OUTPUT + echo "BLOB_URL=${{ steps.blobRelease.outputs.packageUrl }}" >> $GITHUB_OUTPUT + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKER_REPOSITORY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + push: true + context: . + file: ./Dockerfile + tags: | + ${{ steps.urls.outputs.DOCKER_URL }}${{ env.DOCKER_TAG }} + ${{ steps.urls.outputs.DOCKER_URL }}${{ github.ref == 'refs/heads/master' && 'latest' || 'dev-latest' }} + + - name: Parse Jira Keys from All Commits + uses: VirtoCommerce/vc-github-actions/get-jira-keys@master + if: always() + id: jira_keys + with: + release: ${{ env.RELEASE_STATUS }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Push Build Info to Jira + if: ${{ env.CLOUD_INSTANCE_BASE_URL != 0 && env.CLIENT_ID != 0 && env.CLIENT_SECRET != 0 && steps.jira_keys.outputs.jira-keys != '' && always() }} + id: push_build_info_to_jira + uses: VirtoCommerce/jira-upload-build-info@master + with: + cloud-instance-base-url: '${{ secrets.CLOUD_INSTANCE_BASE_URL }}' + client-id: '${{ secrets.CLIENT_ID }}' + client-secret: '${{ secrets.CLIENT_SECRET }}' + pipeline-id: '${{ github.repository }} ${{ github.workflow }}' + build-number: ${{ github.run_number }} + build-display-name: 'Workflow: ${{ github.workflow }} (#${{ github.run_number }})' + build-state: '${{ env.BUILD_STATE }}' + build-url: '${{github.event.repository.url}}/actions/runs/${{github.run_id}}' + update-sequence-number: '${{ github.run_id }}' + last-updated: '${{github.event.head_commit.timestamp}}' + issue-keys: '${{ steps.jira_keys.outputs.jira-keys }}' + commit-id: '${{ github.sha }}' + repo-url: '${{ github.event.repository.url }}' + build-ref-url: '${{ github.event.repository.url }}/actions/runs/${{ github.run_id }}' + + - name: Confirm Jira Build Output + if: success() + run: | + echo "Jira Upload Build Info response: ${{ steps.push_build_info_to_jira.outputs.response }}" From 55faef1926dc3428f2cef7d16654d1f2c19c8685 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Thu, 12 Feb 2026 21:19:41 +0000 Subject: [PATCH 15/51] add ci --- .github/workflows/mcp-ci.yml | 150 +++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 .github/workflows/mcp-ci.yml diff --git a/.github/workflows/mcp-ci.yml b/.github/workflows/mcp-ci.yml new file mode 100644 index 0000000..5719300 --- /dev/null +++ b/.github/workflows/mcp-ci.yml @@ -0,0 +1,150 @@ +# v1.0.1 +name: MCP Server CI + +on: + workflow_dispatch: + +jobs: + ci: + runs-on: ubuntu-latest + env: + FORCE_COLOR: true + CLOUD_INSTANCE_BASE_URL: ${{secrets.CLOUD_INSTANCE_BASE_URL}} + CLIENT_ID: ${{secrets.CLIENT_ID}} + CLIENT_SECRET: ${{secrets.CLIENT_SECRET}} + GITHUB_TOKEN: ${{ secrets.REPO_TOKEN }} + BLOB_SAS: ${{ secrets.BLOB_TOKEN }} + VERSION: '' + BUILD_STATE: 'failed' + RELEASE_STATUS: 'false' + DOCKER_REPOSITORY: 'ghcr.io' + DOCKER_IMAGE: 'vc-fulfillment-mcp-server' + DOCKER_TAG: '' + outputs: + jira-keys: ${{ steps.jira_keys.outputs.jira-keys }} + version: ${{ env.VERSION }} + tag: ${{ env.DOCKER_TAG }} + + steps: + + - name: Set up Node 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Set RELEASE_STATUS + if: ${{ github.ref == 'refs/heads/master' }} + run: echo "RELEASE_STATUS=true" >> $GITHUB_ENV + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get version from package.json + id: version + run: | + BASE_VERSION=$(jq -r .version server/package.json) + SHORT_SHA=$(git rev-parse --short HEAD) + echo "base=$BASE_VERSION" >> $GITHUB_OUTPUT + echo "sha=$SHORT_SHA" >> $GITHUB_OUTPUT + + - name: Set VERSION + run: | + if [ '${{ github.ref }}' = 'refs/heads/master' ]; then + echo "VERSION=${{ steps.version.outputs.base }}" >> $GITHUB_ENV + else + echo "VERSION=${{ steps.version.outputs.base }}-alpha.${{ github.run_number }}" >> $GITHUB_ENV + fi + + - name: Update package.json Version + run: | + npm version ${{ env.VERSION }} --no-git-tag-version --prefix server + npm version ${{ env.VERSION }} --no-git-tag-version --prefix virtocommerce-adapter + + - name: Set DOCKER_TAG + run: echo "DOCKER_TAG=${{ env.VERSION }}" >> $GITHUB_ENV + + - name: Install and build server + run: cd server && npm ci && npm run build + + - name: Install and build adapter + run: cd virtocommerce-adapter && npm ci && npm run build + + - name: BUILD_STATE::successful + if: success() + run: echo "BUILD_STATE=successful" >> $GITHUB_ENV + + - name: Add files to zip + run: | + mkdir ./artifacts + zip -r ./artifacts/vc-fulfillment-mcp-server-${{ env.VERSION }}.zip \ + server/dist/ server/package.json \ + virtocommerce-adapter/dist/ virtocommerce-adapter/package.json \ + Dockerfile docker-compose.yml + + - name: Publish to Blob + id: blobRelease + uses: VirtoCommerce/vc-github-actions/publish-blob-release@master + with: + blobSAS: ${{ secrets.BLOB_TOKEN }} + blobUrl: ${{ vars.BLOB_URL }} + + - name: Set URLs + id: urls + run: | + echo "DOCKER_URL=${{ env.DOCKER_REPOSITORY }}/${GITHUB_REPOSITORY_OWNER,,}/${{ env.DOCKER_IMAGE }}:" >> $GITHUB_OUTPUT + echo "BLOB_URL=${{ steps.blobRelease.outputs.packageUrl }}" >> $GITHUB_OUTPUT + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKER_REPOSITORY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + push: true + context: . + file: ./Dockerfile + tags: | + ${{ steps.urls.outputs.DOCKER_URL }}${{ env.DOCKER_TAG }} + ${{ steps.urls.outputs.DOCKER_URL }}${{ github.ref == 'refs/heads/master' && 'latest' || 'dev-latest' }} + + - name: Parse Jira Keys from All Commits + uses: VirtoCommerce/vc-github-actions/get-jira-keys@master + if: always() + id: jira_keys + with: + release: ${{ env.RELEASE_STATUS }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Push Build Info to Jira + if: ${{ env.CLOUD_INSTANCE_BASE_URL != 0 && env.CLIENT_ID != 0 && env.CLIENT_SECRET != 0 && steps.jira_keys.outputs.jira-keys != '' && always() }} + id: push_build_info_to_jira + uses: VirtoCommerce/jira-upload-build-info@master + with: + cloud-instance-base-url: '${{ secrets.CLOUD_INSTANCE_BASE_URL }}' + client-id: '${{ secrets.CLIENT_ID }}' + client-secret: '${{ secrets.CLIENT_SECRET }}' + pipeline-id: '${{ github.repository }} ${{ github.workflow }}' + build-number: ${{ github.run_number }} + build-display-name: 'Workflow: ${{ github.workflow }} (#${{ github.run_number }})' + build-state: '${{ env.BUILD_STATE }}' + build-url: '${{github.event.repository.url}}/actions/runs/${{github.run_id}}' + update-sequence-number: '${{ github.run_id }}' + last-updated: '${{github.event.head_commit.timestamp}}' + issue-keys: '${{ steps.jira_keys.outputs.jira-keys }}' + commit-id: '${{ github.sha }}' + repo-url: '${{ github.event.repository.url }}' + build-ref-url: '${{ github.event.repository.url }}/actions/runs/${{ github.run_id }}' + + - name: Confirm Jira Build Output + if: success() + run: | + echo "Jira Upload Build Info response: ${{ steps.push_build_info_to_jira.outputs.response }}" From c6e5262ffda685fa691d24930ae3a9f8498840a0 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Thu, 12 Feb 2026 21:25:23 +0000 Subject: [PATCH 16/51] rename artifact --- .github/workflows/mcp-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/mcp-ci.yml b/.github/workflows/mcp-ci.yml index 5719300..f2bf7ca 100644 --- a/.github/workflows/mcp-ci.yml +++ b/.github/workflows/mcp-ci.yml @@ -18,7 +18,7 @@ jobs: BUILD_STATE: 'failed' RELEASE_STATUS: 'false' DOCKER_REPOSITORY: 'ghcr.io' - DOCKER_IMAGE: 'vc-fulfillment-mcp-server' + DOCKER_IMAGE: 'vc-mcp-onx' DOCKER_TAG: '' outputs: jira-keys: ${{ steps.jira_keys.outputs.jira-keys }} @@ -77,7 +77,7 @@ jobs: - name: Add files to zip run: | mkdir ./artifacts - zip -r ./artifacts/vc-fulfillment-mcp-server-${{ env.VERSION }}.zip \ + zip -r ./artifacts/vc-mcp-onx-${{ env.VERSION }}.zip \ server/dist/ server/package.json \ virtocommerce-adapter/dist/ virtocommerce-adapter/package.json \ Dockerfile docker-compose.yml From 0e23bb518bd95f58f04c8c0ed37d4c099b9a3761 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Thu, 12 Feb 2026 21:27:43 +0000 Subject: [PATCH 17/51] rename image in ci --- .github/workflows/ci.yml | 6 +----- .github/workflows/mcp-ci.yml | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c272651..19f0960 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,11 +1,7 @@ name: CI on: - push: - branches: [main, develop, feature/*] - pull_request: - branches: [main, develop] - + workflow_dispatch: permissions: contents: read diff --git a/.github/workflows/mcp-ci.yml b/.github/workflows/mcp-ci.yml index 5719300..f2bf7ca 100644 --- a/.github/workflows/mcp-ci.yml +++ b/.github/workflows/mcp-ci.yml @@ -18,7 +18,7 @@ jobs: BUILD_STATE: 'failed' RELEASE_STATUS: 'false' DOCKER_REPOSITORY: 'ghcr.io' - DOCKER_IMAGE: 'vc-fulfillment-mcp-server' + DOCKER_IMAGE: 'vc-mcp-onx' DOCKER_TAG: '' outputs: jira-keys: ${{ steps.jira_keys.outputs.jira-keys }} @@ -77,7 +77,7 @@ jobs: - name: Add files to zip run: | mkdir ./artifacts - zip -r ./artifacts/vc-fulfillment-mcp-server-${{ env.VERSION }}.zip \ + zip -r ./artifacts/vc-mcp-onx-${{ env.VERSION }}.zip \ server/dist/ server/package.json \ virtocommerce-adapter/dist/ virtocommerce-adapter/package.json \ Dockerfile docker-compose.yml From 00ec4d6e5f6bae4657aa07dc28d242fed6636d18 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Fri, 13 Feb 2026 07:19:29 +0000 Subject: [PATCH 18/51] change exposed port in docker --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index a7205c9..ed07fe3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: context: . dockerfile: Dockerfile ports: - - "3000:3000" + - "80:3000" env_file: - .env.docker environment: From cc8369e689fc15a53e91ef6f180749c19cff8f69 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Mon, 16 Feb 2026 10:10:51 +0000 Subject: [PATCH 19/51] add support for overriding apiKey from environment variable --- server/src/config/environment.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/src/config/environment.ts b/server/src/config/environment.ts index c320a77..971c0ac 100644 --- a/server/src/config/environment.ts +++ b/server/src/config/environment.ts @@ -31,6 +31,11 @@ export class EnvironmentConfig { } } + // Allow overriding apiKey from a separate env var (for Kubernetes secrets) + if (process.env.ADAPTER_API_KEY) { + (options as Record).apiKey = process.env.ADAPTER_API_KEY; + } + const adapterType = process.env.ADAPTER_TYPE as 'built-in' | 'npm' | 'local'; // Build adapter config based on type to match discriminated union From e0acc46af4cd820145f79144735e6f489e247669 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Mon, 16 Feb 2026 11:10:10 +0000 Subject: [PATCH 20/51] refactor adapter configuration to support overriding options from environment variables --- server/src/config/environment.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/server/src/config/environment.ts b/server/src/config/environment.ts index 971c0ac..70d0d08 100644 --- a/server/src/config/environment.ts +++ b/server/src/config/environment.ts @@ -19,7 +19,9 @@ export class EnvironmentConfig { // Adapter configuration if (process.env.ADAPTER_TYPE) { - let options = {}; + let options: Record = {}; + + // Option 1: ADAPTER_CONFIG as JSON string if (process.env.ADAPTER_CONFIG) { try { options = JSON.parse(process.env.ADAPTER_CONFIG); @@ -31,10 +33,10 @@ export class EnvironmentConfig { } } - // Allow overriding apiKey from a separate env var (for Kubernetes secrets) - if (process.env.ADAPTER_API_KEY) { - (options as Record).apiKey = process.env.ADAPTER_API_KEY; - } + // Option 2: Individual ADAPTER_* env vars (override ADAPTER_CONFIG values) + if (process.env.ADAPTER_API_URL) options.apiUrl = process.env.ADAPTER_API_URL; + if (process.env.ADAPTER_API_KEY) options.apiKey = process.env.ADAPTER_API_KEY; + if (process.env.ADAPTER_WORKSPACE) options.workspace = process.env.ADAPTER_WORKSPACE ?? 'default'; const adapterType = process.env.ADAPTER_TYPE as 'built-in' | 'npm' | 'local'; From d8767f73f468928ff4e9a165c6f750f2558a3fc9 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Mon, 16 Feb 2026 12:05:05 +0000 Subject: [PATCH 21/51] change exposed port from 3000 to 80 in Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0a7753c..36a4388 100644 --- a/Dockerfile +++ b/Dockerfile @@ -62,7 +62,7 @@ ENV ADAPTER_PATH=/app/virtocommerce-adapter/dist/index.js ENV MCP_TRANSPORT=sse ENV MCP_PORT=3000 -EXPOSE 3000 +EXPOSE 80 HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD wget -qO- http://localhost:3000/health || exit 1 From 38f0ef609322f3dfa8eb8a947b6dd6d4a1e08d10 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Mon, 16 Feb 2026 12:10:59 +0000 Subject: [PATCH 22/51] remove cof user from docker file --- Dockerfile | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/Dockerfile b/Dockerfile index 36a4388..65b313d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,36 +36,31 @@ RUN cd virtocommerce-adapter && npm ci --omit=dev # --- Stage 3: Runtime --- FROM node:22-alpine -RUN addgroup -g 1001 -S nodejs && \ - adduser -S cof -u 1001 - WORKDIR /app # Server -COPY --from=builder --chown=cof:nodejs /app/server/dist server/dist -COPY --from=prod-deps --chown=cof:nodejs /app/server/node_modules server/node_modules -COPY --chown=cof:nodejs server/package.json server/ +COPY --from=builder /app/server/dist server/dist +COPY --from=prod-deps /app/server/node_modules server/node_modules +COPY server/package.json server/ # VirtoCommerce adapter -COPY --from=builder --chown=cof:nodejs /app/virtocommerce-adapter/dist virtocommerce-adapter/dist -COPY --from=prod-deps --chown=cof:nodejs /app/virtocommerce-adapter/node_modules virtocommerce-adapter/node_modules -COPY --chown=cof:nodejs virtocommerce-adapter/package.json virtocommerce-adapter/ - -RUN mkdir -p /app/logs && chown cof:nodejs /app/logs +COPY --from=builder /app/virtocommerce-adapter/dist virtocommerce-adapter/dist +COPY --from=prod-deps /app/virtocommerce-adapter/node_modules virtocommerce-adapter/node_modules +COPY virtocommerce-adapter/package.json virtocommerce-adapter/ -USER cof +RUN mkdir -p /app/logs ENV NODE_ENV=production ENV LOG_LEVEL=info ENV ADAPTER_TYPE=local ENV ADAPTER_PATH=/app/virtocommerce-adapter/dist/index.js ENV MCP_TRANSPORT=sse -ENV MCP_PORT=3000 +ENV MCP_PORT=80 EXPOSE 80 HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ - CMD wget -qO- http://localhost:3000/health || exit 1 + CMD wget -qO- http://localhost:80/health || exit 1 WORKDIR /app/server CMD ["node", "dist/index.js"] From c6ff0491481b6bd2aeb12fd8efcc4a4d2d3e657b Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Mon, 16 Feb 2026 13:19:17 +0000 Subject: [PATCH 23/51] add SSE headers and keep-alive mechanism to improve connection stability --- server/src/server.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/server/src/server.ts b/server/src/server.ts index 35249f2..e95005e 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -156,12 +156,27 @@ export class MCPServerSDK { const url = new URL(req.url || '/', `http://localhost:${port}`); if (url.pathname === '/sse' && req.method === 'GET') { + // Disable nginx buffering so SSE events are forwarded immediately + res.setHeader('X-Accel-Buffering', 'no'); + res.setHeader('Cache-Control', 'no-cache, no-transform'); + res.setHeader('Connection', 'keep-alive'); + // New SSE connection const transport = new SSEServerTransport('/messages', res); this.sseTransports.set(transport.sessionId, transport); Logger.info(`SSE client connected: ${transport.sessionId}`); + // Periodic keep-alive to prevent proxy/nginx from dropping idle connections + const pingInterval = setInterval(() => { + try { + res.write(':ping\n\n'); + } catch { + clearInterval(pingInterval); + } + }, 15000); + transport.onclose = () => { + clearInterval(pingInterval); this.sseTransports.delete(transport.sessionId); Logger.info(`SSE client disconnected: ${transport.sessionId}`); }; From bb0419e2680c02103ac91eb99f5bc4acb79c931c Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Mon, 16 Feb 2026 13:41:37 +0000 Subject: [PATCH 24/51] feat: upgrade @modelcontextprotocol/sdk to v1.26.0 and add Streamable HTTP transport - Updated the @modelcontextprotocol/sdk dependency from v0.5.0 to v1.26.0. - Introduced a new Streamable HTTP transport in the MCPServerSDK. - Modified the main function to support starting the server with Streamable HTTP. - Implemented request handling for health checks, POST, GET, and DELETE methods in the Streamable HTTP transport. - Ensured proper session management for Streamable HTTP connections. --- Dockerfile | 2 +- server/package-lock.json | 601 ++++++++++++++++++++++++++++++++++++--- server/package.json | 2 +- server/src/index.ts | 6 +- server/src/server.ts | 118 ++++++++ 5 files changed, 685 insertions(+), 44 deletions(-) diff --git a/Dockerfile b/Dockerfile index 65b313d..d965fac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -54,7 +54,7 @@ ENV NODE_ENV=production ENV LOG_LEVEL=info ENV ADAPTER_TYPE=local ENV ADAPTER_PATH=/app/virtocommerce-adapter/dist/index.js -ENV MCP_TRANSPORT=sse +ENV MCP_TRANSPORT=http ENV MCP_PORT=80 EXPOSE 80 diff --git a/server/package-lock.json b/server/package-lock.json index 51691e3..82a7033 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^0.5.0", + "@modelcontextprotocol/sdk": "^1.26.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "dotenv": "^16.3.1", @@ -792,6 +792,17 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@hono/node-server": { + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -912,23 +923,42 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.5.0.tgz", - "integrity": "sha512-RXgulUX6ewvxjAG0kOpLMEdXXWkzWgaoCGaA2CwNW7cQCIphjpJhjpHSiaPdVCnisjRF/0Cm9KWHUuIoeiAblQ==", - "license": "MIT", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", + "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", - "zod": "^3.23.8" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } } }, "node_modules/@nodelib/fs.scandir": { @@ -1780,6 +1810,18 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -2055,6 +2097,29 @@ "dev": true, "license": "MIT" }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -2120,7 +2185,6 @@ "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==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2134,7 +2198,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -2283,6 +2346,18 @@ "dev": true, "license": "MIT" }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", @@ -2292,11 +2367,42 @@ "node": ">= 0.6" } }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "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", @@ -2365,7 +2471,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2470,7 +2575,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -2488,6 +2592,11 @@ "dev": true, "license": "MIT" }, + "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==" + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -2501,6 +2610,14 @@ "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", "license": "MIT" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/es-abstract": { "version": "1.24.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", @@ -2574,7 +2691,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2584,7 +2700,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2601,7 +2716,6 @@ "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==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -2699,6 +2813,11 @@ "@esbuild/win32-x64": "0.25.11" } }, + "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==" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3104,6 +3223,33 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/expect-type": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", @@ -3114,6 +3260,65 @@ "node": ">=12.0.0" } }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "dependencies": { + "ip-address": "10.0.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3238,6 +3443,26 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3315,6 +3540,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3334,7 +3575,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3385,7 +3625,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -3410,7 +3649,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -3506,7 +3744,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3578,7 +3815,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3607,7 +3843,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -3616,6 +3851,14 @@ "node": ">= 0.4" } }, + "node_modules/hono": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz", + "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==", + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -3713,6 +3956,22 @@ "node": ">= 0.4" } }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "engines": { + "node": ">= 12" + } + }, + "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==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -3970,6 +4229,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -4138,7 +4402,6 @@ "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": { @@ -4211,6 +4474,14 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", @@ -4244,6 +4515,11 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==" + }, "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", @@ -4390,12 +4666,30 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" } }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -4420,6 +4714,29 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -4507,6 +4824,22 @@ "dev": true, "license": "MIT" }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-hash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", @@ -4520,7 +4853,6 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4613,6 +4945,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/one-time": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", @@ -4710,6 +5061,14 @@ "node": ">=6" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4724,7 +5083,6 @@ "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" @@ -4754,6 +5112,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -4791,6 +5158,14 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -4856,6 +5231,18 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "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==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4866,6 +5253,20 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4887,6 +5288,14 @@ ], "license": "MIT" }, + "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==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/raw-body": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", @@ -5053,6 +5462,21 @@ "fsevents": "~2.3.2" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5180,6 +5604,76 @@ "node": ">=10" } }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/send/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -5239,7 +5733,6 @@ "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" @@ -5252,7 +5745,6 @@ "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" @@ -5262,7 +5754,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5282,7 +5773,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5299,7 +5789,6 @@ "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==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -5318,7 +5807,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -5852,6 +6340,19 @@ "node": ">= 0.8.0" } }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -6032,6 +6533,14 @@ "uuid": "dist/bin/uuid" } }, + "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==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vite": { "version": "7.1.12", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz", @@ -6251,7 +6760,6 @@ "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" @@ -6528,6 +7036,11 @@ "url": "https://github.com/chalk/ansi-styles?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==" + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -6549,6 +7062,14 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "peerDependencies": { + "zod": "^3.25 || ^4" + } } } } diff --git a/server/package.json b/server/package.json index 2300974..b4e2196 100644 --- a/server/package.json +++ b/server/package.json @@ -43,7 +43,7 @@ "author": "Commerce Operations Foundation", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^0.5.0", + "@modelcontextprotocol/sdk": "^1.26.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "dotenv": "^16.3.1", diff --git a/server/src/index.ts b/server/src/index.ts index 81f6e45..23411e3 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -59,8 +59,10 @@ async function main() { const server = new MCPServerSDK(config); const transport = process.env.MCP_TRANSPORT || 'stdio'; - if (transport === 'sse') { - const port = parseInt(process.env.MCP_PORT || '3000', 10); + const port = parseInt(process.env.MCP_PORT || '3000', 10); + if (transport === 'http') { + await server.startStreamableHTTP(port); + } else if (transport === 'sse') { await server.startSSE(port); } else { await server.start(); diff --git a/server/src/server.ts b/server/src/server.ts index e95005e..8089b11 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -3,10 +3,12 @@ * This is the recommended approach for MCP servers */ +import { randomUUID } from 'node:crypto'; import * as http from 'http'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { CallToolRequestSchema, ListToolsRequestSchema, @@ -15,6 +17,7 @@ import { PingRequestSchema, McpError, ErrorCode, + isInitializeRequest, } from '@modelcontextprotocol/sdk/types.js'; import { ToolRegistry } from './tools/registry.js'; import { ServiceOrchestrator } from './services/service-orchestrator.js'; @@ -29,6 +32,7 @@ export class MCPServerSDK { private config: ServerConfig; private httpServer?: http.Server; private sseTransports: Map = new Map(); + private streamableTransports: Map = new Map(); constructor(config: ServerConfig) { this.config = config; @@ -215,6 +219,114 @@ export class MCPServerSDK { }); } + async startStreamableHTTP(port: number): Promise { + Logger.info('Starting MCP server with Streamable HTTP transport...'); + + await this.serviceOrchestrator.initialize(this.config.adapter); + await this.registerTools(); + + this.httpServer = http.createServer(async (req, res) => { + // CORS headers + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type, mcp-session-id, last-event-id'); + res.setHeader('Access-Control-Expose-Headers', 'mcp-session-id'); + + if (req.method === 'OPTIONS') { + res.writeHead(204); + res.end(); + return; + } + + const url = new URL(req.url || '/', `http://localhost:${port}`); + + if (url.pathname === '/health' && req.method === 'GET') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ status: 'ok', transport: 'streamable-http', sessions: this.streamableTransports.size })); + return; + } + + if (url.pathname !== '/mcp') { + res.writeHead(404); + res.end(); + return; + } + + const sessionId = req.headers['mcp-session-id'] as string | undefined; + + if (req.method === 'POST') { + // Read request body + const chunks: Buffer[] = []; + for await (const chunk of req) { + chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk); + } + const body = JSON.parse(Buffer.concat(chunks).toString()); + + let transport: StreamableHTTPServerTransport; + + if (sessionId && this.streamableTransports.has(sessionId)) { + transport = this.streamableTransports.get(sessionId)!; + } else if (!sessionId && isInitializeRequest(body)) { + transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: () => randomUUID(), + onsessioninitialized: (id) => { + this.streamableTransports.set(id, transport); + Logger.info(`Streamable HTTP session initialized: ${id}`); + }, + }); + + transport.onclose = () => { + const sid = transport.sessionId; + if (sid) { + this.streamableTransports.delete(sid); + Logger.info(`Streamable HTTP session closed: ${sid}`); + } + }; + + await this.server.connect(transport); + await transport.handleRequest(req, res, body); + return; + } else { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ jsonrpc: '2.0', error: { code: -32000, message: 'Bad Request: No valid session ID provided' }, id: null })); + return; + } + + await transport.handleRequest(req, res, body); + return; + } + + if (req.method === 'GET') { + if (!sessionId || !this.streamableTransports.has(sessionId)) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'Invalid or missing session ID' })); + return; + } + const transport = this.streamableTransports.get(sessionId)!; + await transport.handleRequest(req, res); + return; + } + + if (req.method === 'DELETE') { + if (!sessionId || !this.streamableTransports.has(sessionId)) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'Invalid or missing session ID' })); + return; + } + const transport = this.streamableTransports.get(sessionId)!; + await transport.handleRequest(req, res); + return; + } + + res.writeHead(405); + res.end(); + }); + + this.httpServer.listen(port, () => { + Logger.info(`MCP server running on Streamable HTTP transport at http://0.0.0.0:${port}/mcp`); + }); + } + private async registerTools(): Promise { Logger.debug('Registering Fulfillment tools...'); @@ -237,6 +349,12 @@ export class MCPServerSDK { } this.sseTransports.clear(); + // Close all Streamable HTTP transports + for (const transport of this.streamableTransports.values()) { + await transport.close(); + } + this.streamableTransports.clear(); + // Close HTTP server if running if (this.httpServer) { await new Promise((resolve) => this.httpServer!.close(() => resolve())); From db351c65a36f33b4ca195453412763eb7e3a1e7f Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Mon, 16 Feb 2026 14:08:12 +0000 Subject: [PATCH 25/51] refactor: streamline server initialization and handler setup in MCPServerSDK --- server/src/server.ts | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/server/src/server.ts b/server/src/server.ts index 8089b11..be2230a 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -37,12 +37,18 @@ export class MCPServerSDK { constructor(config: ServerConfig) { this.config = config; - // Create MCP SDK server - this.server = new Server( + this.server = this.createServer(); + this.serviceOrchestrator = new ServiceOrchestrator(); + this.toolRegistry = new ToolRegistry(this.serviceOrchestrator); + } + + /** Create a new MCP Server instance with all handlers wired up. */ + private createServer(): Server { + const server = new Server( { - name: config.server.name, - version: config.server.version, - description: config.server.description, + name: this.config.server.name, + version: this.config.server.version, + description: this.config.server.description, }, { capabilities: { @@ -53,22 +59,20 @@ export class MCPServerSDK { } ); - this.serviceOrchestrator = new ServiceOrchestrator(); - this.toolRegistry = new ToolRegistry(this.serviceOrchestrator); - - this.setupHandlers(); + this.setupHandlers(server); + return server; } - private setupHandlers(): void { + private setupHandlers(server: Server): void { // Handle tools/list requests - this.server.setRequestHandler(ListToolsRequestSchema, async () => { + server.setRequestHandler(ListToolsRequestSchema, async () => { Logger.debug('Handling tools/list request'); const tools = this.toolRegistry.list(); return { tools }; }); // Handle tools/call requests with improved response wrapping - this.server.setRequestHandler(CallToolRequestSchema, async (request) => { + server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; Logger.debug(`Handling tools/call request for: ${name}`); @@ -104,25 +108,25 @@ export class MCPServerSDK { }); // Handle ping requests - this.server.setRequestHandler(PingRequestSchema, async () => { + server.setRequestHandler(PingRequestSchema, async () => { Logger.debug('Handling ping request'); return {}; }); // Handle prompts/list requests - return empty list since we don't support prompts - this.server.setRequestHandler(ListPromptsRequestSchema, async () => { + server.setRequestHandler(ListPromptsRequestSchema, async () => { Logger.debug('Handling prompts/list request'); return { prompts: [] }; }); // Handle resources/list requests - return empty list since we don't support resources - this.server.setRequestHandler(ListResourcesRequestSchema, async () => { + server.setRequestHandler(ListResourcesRequestSchema, async () => { Logger.debug('Handling resources/list request'); return { resources: [] }; }); // Handle any other custom requests if needed - this.server.onerror = (error) => { + server.onerror = (error) => { Logger.error('Server error:', error); }; } @@ -283,7 +287,8 @@ export class MCPServerSDK { } }; - await this.server.connect(transport); + const server = this.createServer(); + await server.connect(transport); await transport.handleRequest(req, res, body); return; } else { From 24588507e96a1d3609c77e79ad46cc213d5a48bf Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Mon, 16 Feb 2026 14:50:17 +0000 Subject: [PATCH 26/51] fix: update adapter workspace assignment and add debug mode handling --- server/src/config/environment.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/config/environment.ts b/server/src/config/environment.ts index 70d0d08..4725288 100644 --- a/server/src/config/environment.ts +++ b/server/src/config/environment.ts @@ -36,7 +36,8 @@ export class EnvironmentConfig { // Option 2: Individual ADAPTER_* env vars (override ADAPTER_CONFIG values) if (process.env.ADAPTER_API_URL) options.apiUrl = process.env.ADAPTER_API_URL; if (process.env.ADAPTER_API_KEY) options.apiKey = process.env.ADAPTER_API_KEY; - if (process.env.ADAPTER_WORKSPACE) options.workspace = process.env.ADAPTER_WORKSPACE ?? 'default'; + if (process.env.ADAPTER_WORKSPACE) options.workspace = process.env.ADAPTER_WORKSPACE; + if (process.env.ADAPTER_DEBUG_MODE) options.debugMode = process.env.ADAPTER_DEBUG_MODE === 'true'; const adapterType = process.env.ADAPTER_TYPE as 'built-in' | 'npm' | 'local'; From 5d45d195e412c389340d027e50d6c19b86cc0da4 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Mon, 16 Feb 2026 15:08:04 +0000 Subject: [PATCH 27/51] fix: enable type coercion in AJV validation configuration --- server/src/services/validator.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/services/validator.ts b/server/src/services/validator.ts index 227c230..2774e9a 100644 --- a/server/src/services/validator.ts +++ b/server/src/services/validator.ts @@ -17,6 +17,7 @@ export class Validator { verbose: true, strict: false, validateFormats: true, + coerceTypes: true, }); } From b0305c1f3eef26385d58f99c11dc829efe1ba254 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Tue, 17 Feb 2026 05:08:30 +0000 Subject: [PATCH 28/51] fix: mask sensitive values in API request logging --- virtocommerce-adapter/src/utils/api-client.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/virtocommerce-adapter/src/utils/api-client.ts b/virtocommerce-adapter/src/utils/api-client.ts index 540e4ce..e1dc951 100644 --- a/virtocommerce-adapter/src/utils/api-client.ts +++ b/virtocommerce-adapter/src/utils/api-client.ts @@ -52,9 +52,17 @@ export class ApiClient { this.client.interceptors.request.use( (request) => { if (this.debugMode) { + const headers = { ...request.headers } as Record; + // Mask sensitive values + if (headers['api_key']) headers['api_key'] = '***'; + if (headers['Authorization']) headers['Authorization'] = '***'; + console.error('[API Request]', { method: request.method?.toUpperCase(), + baseURL: request.baseURL, url: request.url, + fullUrl: `${request.baseURL}${request.url}`, + headers, params: request.params, data: request.data, }); From c9f75ce182f3520924fa245662976c46f3053523 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Tue, 17 Feb 2026 05:18:55 +0000 Subject: [PATCH 29/51] test changes --- virtocommerce-adapter/src/utils/api-client.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/virtocommerce-adapter/src/utils/api-client.ts b/virtocommerce-adapter/src/utils/api-client.ts index e1dc951..cc231f2 100644 --- a/virtocommerce-adapter/src/utils/api-client.ts +++ b/virtocommerce-adapter/src/utils/api-client.ts @@ -53,10 +53,6 @@ export class ApiClient { (request) => { if (this.debugMode) { const headers = { ...request.headers } as Record; - // Mask sensitive values - if (headers['api_key']) headers['api_key'] = '***'; - if (headers['Authorization']) headers['Authorization'] = '***'; - console.error('[API Request]', { method: request.method?.toUpperCase(), baseURL: request.baseURL, From ad14093aba906d6e7aa8a6e4cccdfa24f9ddb50d Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Tue, 17 Feb 2026 05:46:36 +0000 Subject: [PATCH 30/51] update correct api key --- virtocommerce-adapter/src/utils/api-client.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/virtocommerce-adapter/src/utils/api-client.ts b/virtocommerce-adapter/src/utils/api-client.ts index cc231f2..521ba52 100644 --- a/virtocommerce-adapter/src/utils/api-client.ts +++ b/virtocommerce-adapter/src/utils/api-client.ts @@ -333,7 +333,8 @@ export class ApiClient { */ updateApiKey(apiKey: string): void { this.config.apiKey = apiKey; - this.client.defaults.headers.common['X-API-Key'] = apiKey; + this.client.defaults.headers.common['api_key'] = apiKey; + // this.client.defaults.headers.common['X-API-Key'] = apiKey; // Or if using Bearer token: // this.client.defaults.headers.common['Authorization'] = `Bearer ${apiKey}`; } From 1d897f8f50d253c9b47777049644d9219202c0a5 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Tue, 17 Feb 2026 06:09:00 +0000 Subject: [PATCH 31/51] add log info --- server/src/server.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/server.ts b/server/src/server.ts index be2230a..97000f5 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -214,6 +214,8 @@ export class MCPServerSDK { return; } + Logger.info('MCP server received unknown request', { method: req.method, url: req.url }); + res.writeHead(404); res.end(); }); From 82f07171fd3467703859e623f576c36a62509ccd Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Thu, 19 Feb 2026 10:22:18 +0000 Subject: [PATCH 32/51] revert changes in server folder --- .claude/settings.local.json | 22 +- .github/workflows/ci.yml | 6 +- .gitignore | 4 + server/package-lock.json | 601 ++---------------- server/package.json | 4 +- server/src/adapters/adapter-factory.ts | 3 +- server/src/config/environment.ts | 10 +- server/src/index.ts | 30 +- server/src/logging/structured-logger.ts | 3 +- server/src/server.ts | 252 +------- server/src/services/validator.ts | 1 - virtocommerce-adapter/package-lock.json | 2 +- virtocommerce-adapter/src/adapter.ts | 4 + virtocommerce-adapter/src/models/base.ts | 3 + virtocommerce-adapter/src/models/catalog.ts | 2 +- virtocommerce-adapter/src/models/index.ts | 2 +- virtocommerce-adapter/src/models/order.ts | 6 - .../src/services/fulfillment.service.ts | 61 +- .../src/services/order.service.ts | 37 +- .../src/services/product.service.ts | 6 +- .../transformers/fulfillment.transformer.ts | 56 +- .../src/transformers/order.transformer.ts | 103 ++- .../src/transformers/product.transformer.ts | 32 +- virtocommerce-adapter/src/types.ts | 31 +- virtocommerce-adapter/src/utils/api-client.ts | 4 +- virtocommerce-adapter/tests/adapter.test.ts | 35 +- .../fixtures/getCustomers/getCustomers.json | 78 +++ 27 files changed, 445 insertions(+), 953 deletions(-) create mode 100644 virtocommerce-adapter/tests/fixtures/getCustomers/getCustomers.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 6ed1a6b..6305934 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -24,7 +24,27 @@ "Bash(docker stop:*)", "Bash(docker rm:*)", "Bash(node -e:*)", - "Bash(docker compose up:*)" + "Bash(docker compose up:*)", + "mcp__plugin_serena_serena__find_symbol", + "Bash(npm info:*)", + "Bash(wc:*)", + "mcp__plugin_serena_serena__list_dir", + "mcp__plugin_serena_serena__activate_project", + "mcp__plugin_serena_serena__check_onboarding_performed", + "mcp__plugin_serena_serena__get_symbols_overview", + "mcp__plugin_serena_serena__read_file", + "mcp__plugin_serena_serena__search_for_pattern", + "mcp__plugin_serena_serena__replace_content", + "mcp__plugin_serena_serena__replace_symbol_body", + "Bash(dir:*)", + "Bash(cmd /c:*)", + "Bash(powershell.exe \"Get-ChildItem ''E:\\\\Develops\\\\VirtoWay\\\\projects\\\\tasks\\\\4464\\\\vc-module-order\\\\src'' -Recurse | Select-Object FullName\")", + "Bash(powershell.exe \"Get-ChildItem ''E:\\\\Develops\\\\VirtoWay\\\\projects\\\\tasks\\\\4464\\\\vc-module-order\\\\src'' -Recurse -Filter ''*.cs'' | Where-Object { $_.Name -match ''Order|Controller|Cancel'' } | Select-Object FullName\")", + "Bash(powershell.exe -Command \"Get-ChildItem ''E:\\\\Develops\\\\VirtoWay\\\\projects\\\\tasks\\\\4464\\\\vc-module-order\\\\src'' -Recurse -Filter ''*.cs'' | Select-Object -ExpandProperty FullName\")", + "Bash(powershell.exe -Command \"Get-ChildItem ''E:\\\\Develops\\\\VirtoWay\\\\projects\\\\tasks\\\\4464\\\\vc-module-order\\\\src'' -Recurse -Filter ''*.cs'' | Select-Object -ExpandProperty FullName | Out-String\")", + "Bash(powershell.exe -Command \"Get-ChildItem ''E:\\\\Develops\\\\VirtoWay\\\\projects\\\\tasks\\\\4464\\\\vc-module-order\\\\src'' -Recurse -Filter ''*.cs'' | Select-Object -First 50 -ExpandProperty FullName\")", + "Bash(powershell.exe -Command:*)", + "mcp__plugin_serena_serena__insert_after_symbol" ] } } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 19f0960..c272651 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,11 @@ name: CI on: - workflow_dispatch: + push: + branches: [main, develop, feature/*] + pull_request: + branches: [main, develop] + permissions: contents: read diff --git a/.gitignore b/.gitignore index 72a21d8..e5aa235 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,7 @@ working-docs/ .idea/ *.sublime-workspace *.sublime-project + +# AI tools +.claude/ +.serena/ \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json index 82a7033..51691e3 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.26.0", + "@modelcontextprotocol/sdk": "^0.5.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "dotenv": "^16.3.1", @@ -792,17 +792,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@hono/node-server": { - "version": "1.19.9", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", - "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", - "engines": { - "node": ">=18.14.1" - }, - "peerDependencies": { - "hono": "^4" - } - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -923,42 +912,23 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", - "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.5.0.tgz", + "integrity": "sha512-RXgulUX6ewvxjAG0kOpLMEdXXWkzWgaoCGaA2CwNW7cQCIphjpJhjpHSiaPdVCnisjRF/0Cm9KWHUuIoeiAblQ==", + "license": "MIT", "dependencies": { - "@hono/node-server": "^1.19.9", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "hono": "^4.11.4", - "jose": "^6.1.3", - "json-schema-typed": "^8.0.2", - "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", - "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@cfworker/json-schema": "^4.1.1", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - }, - "zod": { - "optional": false - } + "zod": "^3.23.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/@nodelib/fs.scandir": { @@ -1810,18 +1780,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -2097,29 +2055,6 @@ "dev": true, "license": "MIT" }, - "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", - "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -2185,6 +2120,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2198,6 +2134,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -2346,18 +2283,6 @@ "dev": true, "license": "MIT" }, - "node_modules/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", @@ -2367,42 +2292,11 @@ "node": ">= 0.6" } }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "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", @@ -2471,6 +2365,7 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2575,6 +2470,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -2592,11 +2488,6 @@ "dev": true, "license": "MIT" }, - "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==" - }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -2610,14 +2501,6 @@ "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", "license": "MIT" }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/es-abstract": { "version": "1.24.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", @@ -2691,6 +2574,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2700,6 +2584,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2716,6 +2601,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -2813,11 +2699,6 @@ "@esbuild/win32-x64": "0.25.11" } }, - "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==" - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3223,33 +3104,6 @@ "node": ">=0.10.0" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventsource": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/expect-type": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", @@ -3260,65 +3114,6 @@ "node": ">=12.0.0" } }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", - "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", - "dependencies": { - "ip-address": "10.0.1" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3443,26 +3238,6 @@ "node": ">=8" } }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3540,22 +3315,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3575,6 +3334,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3625,6 +3385,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -3649,6 +3410,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -3744,6 +3506,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3815,6 +3578,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3843,6 +3607,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -3851,14 +3616,6 @@ "node": ">= 0.4" } }, - "node_modules/hono": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz", - "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==", - "engines": { - "node": ">=16.9.0" - } - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -3956,22 +3713,6 @@ "node": ">= 0.4" } }, - "node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", - "engines": { - "node": ">= 12" - } - }, - "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==", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -4229,11 +3970,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" - }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -4402,6 +4138,7 @@ "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": { @@ -4474,14 +4211,6 @@ "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/jose": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", - "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, "node_modules/js-tokens": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", @@ -4515,11 +4244,6 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, - "node_modules/json-schema-typed": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==" - }, "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", @@ -4666,30 +4390,12 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" } }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -4714,29 +4420,6 @@ "node": ">=8.6" } }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -4824,22 +4507,6 @@ "dev": true, "license": "MIT" }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "engines": { - "node": ">= 0.6" - } - }, - "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==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object-hash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", @@ -4853,6 +4520,7 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4945,25 +4613,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "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==", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/one-time": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", @@ -5061,14 +4710,6 @@ "node": ">=6" } }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5083,6 +4724,7 @@ "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" @@ -5112,15 +4754,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -5158,14 +4791,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pkce-challenge": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", - "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", - "engines": { - "node": ">=16.20.0" - } - }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -5231,18 +4856,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "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==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5253,20 +4866,6 @@ "node": ">=6" } }, - "node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -5288,14 +4887,6 @@ ], "license": "MIT" }, - "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==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/raw-body": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", @@ -5462,21 +5053,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5604,76 +5180,6 @@ "node": ">=10" } }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/send/node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/send/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -5733,6 +5239,7 @@ "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" @@ -5745,6 +5252,7 @@ "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" @@ -5754,6 +5262,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5773,6 +5282,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5789,6 +5299,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -5807,6 +5318,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -6340,19 +5852,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -6533,14 +6032,6 @@ "uuid": "dist/bin/uuid" } }, - "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==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/vite": { "version": "7.1.12", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz", @@ -6760,6 +6251,7 @@ "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" @@ -7036,11 +6528,6 @@ "url": "https://github.com/chalk/ansi-styles?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==" - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -7062,14 +6549,6 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } - }, - "node_modules/zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", - "peerDependencies": { - "zod": "^3.25 || ^4" - } } } } diff --git a/server/package.json b/server/package.json index b4e2196..f04e808 100644 --- a/server/package.json +++ b/server/package.json @@ -43,7 +43,7 @@ "author": "Commerce Operations Foundation", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.26.0", + "@modelcontextprotocol/sdk": "^0.5.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "dotenv": "^16.3.1", @@ -81,6 +81,6 @@ ], "repository": { "type": "git", - "url": "https://github.com/commerce-operations-foundation/mcp.git" + "url": "https://github.com/cof-org/mcp.git" } } diff --git a/server/src/adapters/adapter-factory.ts b/server/src/adapters/adapter-factory.ts index ad3a476..d0f8317 100644 --- a/server/src/adapters/adapter-factory.ts +++ b/server/src/adapters/adapter-factory.ts @@ -4,7 +4,6 @@ import * as path from 'path'; import * as fs from 'fs'; -import { pathToFileURL } from 'url'; import { IFulfillmentAdapter, AdapterConfig, AdapterConstructor, AdapterError } from '../types/index.js'; import { Logger } from '../utils/logger.js'; import { MockAdapter } from './mock/mock-adapter.js'; @@ -179,7 +178,7 @@ export class AdapterFactory { try { // Dynamically import local file - const module = await import(pathToFileURL(adapterPath).href); + const module = await import(adapterPath); const exportName = config.exportName || 'default'; const AdapterClass = module[exportName]; diff --git a/server/src/config/environment.ts b/server/src/config/environment.ts index 4725288..c320a77 100644 --- a/server/src/config/environment.ts +++ b/server/src/config/environment.ts @@ -19,9 +19,7 @@ export class EnvironmentConfig { // Adapter configuration if (process.env.ADAPTER_TYPE) { - let options: Record = {}; - - // Option 1: ADAPTER_CONFIG as JSON string + let options = {}; if (process.env.ADAPTER_CONFIG) { try { options = JSON.parse(process.env.ADAPTER_CONFIG); @@ -33,12 +31,6 @@ export class EnvironmentConfig { } } - // Option 2: Individual ADAPTER_* env vars (override ADAPTER_CONFIG values) - if (process.env.ADAPTER_API_URL) options.apiUrl = process.env.ADAPTER_API_URL; - if (process.env.ADAPTER_API_KEY) options.apiKey = process.env.ADAPTER_API_KEY; - if (process.env.ADAPTER_WORKSPACE) options.workspace = process.env.ADAPTER_WORKSPACE; - if (process.env.ADAPTER_DEBUG_MODE) options.debugMode = process.env.ADAPTER_DEBUG_MODE === 'true'; - const adapterType = process.env.ADAPTER_TYPE as 'built-in' | 'npm' | 'local'; // Build adapter config based on type to match discriminated union diff --git a/server/src/index.ts b/server/src/index.ts index 23411e3..32165b2 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -5,9 +5,6 @@ * Entry point using MCP SDK components */ -import * as fs from 'fs'; -import * as path from 'path'; -import dotenv from 'dotenv'; import { fileURLToPath } from 'url'; import { ConfigManager } from './config/config-manager.js'; @@ -19,22 +16,6 @@ import { TimeoutHandler } from './utils/timeout.js'; async function main() { try { - // Ensure stdout stays чистым для MCP JSON-RPC. - console.log = console.error; - console.info = console.error; - console.warn = console.error; - console.debug = console.error; - - // Load .env files before config initialization (local overrides default). - const envPath = path.join(process.cwd(), '.env'); - const envLocalPath = path.join(process.cwd(), '.env.local'); - if (fs.existsSync(envPath)) { - dotenv.config({ path: envPath }); - } - if (fs.existsSync(envLocalPath)) { - dotenv.config({ path: envLocalPath, override: true }); - } - // Initialize logger with safe defaults before config loading Logger.init('info'); @@ -57,16 +38,7 @@ async function main() { // Create and start server const server = new MCPServerSDK(config); - - const transport = process.env.MCP_TRANSPORT || 'stdio'; - const port = parseInt(process.env.MCP_PORT || '3000', 10); - if (transport === 'http') { - await server.startStreamableHTTP(port); - } else if (transport === 'sse') { - await server.startSSE(port); - } else { - await server.start(); - } + await server.start(); // Handle graceful shutdown const shutdown = async () => { diff --git a/server/src/logging/structured-logger.ts b/server/src/logging/structured-logger.ts index b65b72e..7f44d1b 100644 --- a/server/src/logging/structured-logger.ts +++ b/server/src/logging/structured-logger.ts @@ -6,7 +6,6 @@ import * as winston from 'winston'; import * as path from 'path'; import * as fs from 'fs'; -import { fileURLToPath } from 'url'; import DailyRotateFile from 'winston-daily-rotate-file'; import { LogSanitizer } from '../security/log-sanitizer.js'; @@ -43,7 +42,7 @@ export class StructuredLogger { // Make log directory absolute if it's relative if (!path.isAbsolute(logDir)) { // Find the server directory (parent of dist when running compiled code) - const currentDir = path.dirname(fileURLToPath(import.meta.url)); + const currentDir = path.dirname(new URL(import.meta.url).pathname); const serverDir = currentDir.includes('/dist/') ? path.resolve(currentDir.split('/dist/')[0]) : path.resolve(process.cwd(), 'server'); diff --git a/server/src/server.ts b/server/src/server.ts index 97000f5..6a789ba 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -3,12 +3,8 @@ * This is the recommended approach for MCP servers */ -import { randomUUID } from 'node:crypto'; -import * as http from 'http'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; -import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { CallToolRequestSchema, ListToolsRequestSchema, @@ -17,7 +13,6 @@ import { PingRequestSchema, McpError, ErrorCode, - isInitializeRequest, } from '@modelcontextprotocol/sdk/types.js'; import { ToolRegistry } from './tools/registry.js'; import { ServiceOrchestrator } from './services/service-orchestrator.js'; @@ -30,25 +25,16 @@ export class MCPServerSDK { private serviceOrchestrator: ServiceOrchestrator; private toolRegistry: ToolRegistry; private config: ServerConfig; - private httpServer?: http.Server; - private sseTransports: Map = new Map(); - private streamableTransports: Map = new Map(); constructor(config: ServerConfig) { this.config = config; - this.server = this.createServer(); - this.serviceOrchestrator = new ServiceOrchestrator(); - this.toolRegistry = new ToolRegistry(this.serviceOrchestrator); - } - - /** Create a new MCP Server instance with all handlers wired up. */ - private createServer(): Server { - const server = new Server( + // Create MCP SDK server + this.server = new Server( { - name: this.config.server.name, - version: this.config.server.version, - description: this.config.server.description, + name: config.server.name, + version: config.server.version, + description: config.server.description, }, { capabilities: { @@ -59,20 +45,22 @@ export class MCPServerSDK { } ); - this.setupHandlers(server); - return server; + this.serviceOrchestrator = new ServiceOrchestrator(); + this.toolRegistry = new ToolRegistry(this.serviceOrchestrator); + + this.setupHandlers(); } - private setupHandlers(server: Server): void { + private setupHandlers(): void { // Handle tools/list requests - server.setRequestHandler(ListToolsRequestSchema, async () => { + this.server.setRequestHandler(ListToolsRequestSchema, async () => { Logger.debug('Handling tools/list request'); const tools = this.toolRegistry.list(); return { tools }; }); // Handle tools/call requests with improved response wrapping - server.setRequestHandler(CallToolRequestSchema, async (request) => { + this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; Logger.debug(`Handling tools/call request for: ${name}`); @@ -108,25 +96,25 @@ export class MCPServerSDK { }); // Handle ping requests - server.setRequestHandler(PingRequestSchema, async () => { + this.server.setRequestHandler(PingRequestSchema, async () => { Logger.debug('Handling ping request'); return {}; }); // Handle prompts/list requests - return empty list since we don't support prompts - server.setRequestHandler(ListPromptsRequestSchema, async () => { + this.server.setRequestHandler(ListPromptsRequestSchema, async () => { Logger.debug('Handling prompts/list request'); return { prompts: [] }; }); // Handle resources/list requests - return empty list since we don't support resources - server.setRequestHandler(ListResourcesRequestSchema, async () => { + this.server.setRequestHandler(ListResourcesRequestSchema, async () => { Logger.debug('Handling resources/list request'); return { resources: [] }; }); // Handle any other custom requests if needed - server.onerror = (error) => { + this.server.onerror = (error) => { Logger.error('Server error:', error); }; } @@ -143,197 +131,6 @@ export class MCPServerSDK { Logger.info('MCP server running on stdio transport'); } - async startSSE(port: number): Promise { - Logger.info('Starting MCP server with SSE transport...'); - - await this.serviceOrchestrator.initialize(this.config.adapter); - await this.registerTools(); - - this.httpServer = http.createServer(async (req, res) => { - // CORS headers - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); - - if (req.method === 'OPTIONS') { - res.writeHead(204); - res.end(); - return; - } - - const url = new URL(req.url || '/', `http://localhost:${port}`); - - if (url.pathname === '/sse' && req.method === 'GET') { - // Disable nginx buffering so SSE events are forwarded immediately - res.setHeader('X-Accel-Buffering', 'no'); - res.setHeader('Cache-Control', 'no-cache, no-transform'); - res.setHeader('Connection', 'keep-alive'); - - // New SSE connection - const transport = new SSEServerTransport('/messages', res); - this.sseTransports.set(transport.sessionId, transport); - Logger.info(`SSE client connected: ${transport.sessionId}`); - - // Periodic keep-alive to prevent proxy/nginx from dropping idle connections - const pingInterval = setInterval(() => { - try { - res.write(':ping\n\n'); - } catch { - clearInterval(pingInterval); - } - }, 15000); - - transport.onclose = () => { - clearInterval(pingInterval); - this.sseTransports.delete(transport.sessionId); - Logger.info(`SSE client disconnected: ${transport.sessionId}`); - }; - - await this.server.connect(transport); - return; - } - - if (url.pathname === '/messages' && req.method === 'POST') { - // Route POST to the correct session - const sessionId = url.searchParams.get('sessionId'); - const transport = sessionId ? this.sseTransports.get(sessionId) : undefined; - - if (!transport) { - res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: 'Invalid or missing sessionId' })); - return; - } - - await transport.handlePostMessage(req, res); - return; - } - - if (url.pathname === '/health' && req.method === 'GET') { - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ status: 'ok', transport: 'sse', sessions: this.sseTransports.size })); - return; - } - - Logger.info('MCP server received unknown request', { method: req.method, url: req.url }); - - res.writeHead(404); - res.end(); - }); - - this.httpServer.listen(port, () => { - Logger.info(`MCP server running on SSE transport at http://0.0.0.0:${port}/sse`); - }); - } - - async startStreamableHTTP(port: number): Promise { - Logger.info('Starting MCP server with Streamable HTTP transport...'); - - await this.serviceOrchestrator.initialize(this.config.adapter); - await this.registerTools(); - - this.httpServer = http.createServer(async (req, res) => { - // CORS headers - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS'); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type, mcp-session-id, last-event-id'); - res.setHeader('Access-Control-Expose-Headers', 'mcp-session-id'); - - if (req.method === 'OPTIONS') { - res.writeHead(204); - res.end(); - return; - } - - const url = new URL(req.url || '/', `http://localhost:${port}`); - - if (url.pathname === '/health' && req.method === 'GET') { - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ status: 'ok', transport: 'streamable-http', sessions: this.streamableTransports.size })); - return; - } - - if (url.pathname !== '/mcp') { - res.writeHead(404); - res.end(); - return; - } - - const sessionId = req.headers['mcp-session-id'] as string | undefined; - - if (req.method === 'POST') { - // Read request body - const chunks: Buffer[] = []; - for await (const chunk of req) { - chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk); - } - const body = JSON.parse(Buffer.concat(chunks).toString()); - - let transport: StreamableHTTPServerTransport; - - if (sessionId && this.streamableTransports.has(sessionId)) { - transport = this.streamableTransports.get(sessionId)!; - } else if (!sessionId && isInitializeRequest(body)) { - transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: () => randomUUID(), - onsessioninitialized: (id) => { - this.streamableTransports.set(id, transport); - Logger.info(`Streamable HTTP session initialized: ${id}`); - }, - }); - - transport.onclose = () => { - const sid = transport.sessionId; - if (sid) { - this.streamableTransports.delete(sid); - Logger.info(`Streamable HTTP session closed: ${sid}`); - } - }; - - const server = this.createServer(); - await server.connect(transport); - await transport.handleRequest(req, res, body); - return; - } else { - res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ jsonrpc: '2.0', error: { code: -32000, message: 'Bad Request: No valid session ID provided' }, id: null })); - return; - } - - await transport.handleRequest(req, res, body); - return; - } - - if (req.method === 'GET') { - if (!sessionId || !this.streamableTransports.has(sessionId)) { - res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: 'Invalid or missing session ID' })); - return; - } - const transport = this.streamableTransports.get(sessionId)!; - await transport.handleRequest(req, res); - return; - } - - if (req.method === 'DELETE') { - if (!sessionId || !this.streamableTransports.has(sessionId)) { - res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: 'Invalid or missing session ID' })); - return; - } - const transport = this.streamableTransports.get(sessionId)!; - await transport.handleRequest(req, res); - return; - } - - res.writeHead(405); - res.end(); - }); - - this.httpServer.listen(port, () => { - Logger.info(`MCP server running on Streamable HTTP transport at http://0.0.0.0:${port}/mcp`); - }); - } - private async registerTools(): Promise { Logger.debug('Registering Fulfillment tools...'); @@ -350,23 +147,6 @@ export class MCPServerSDK { // Cleanup service orchestrator first await this.serviceOrchestrator.cleanup(); - // Close all SSE transports - for (const transport of this.sseTransports.values()) { - await transport.close(); - } - this.sseTransports.clear(); - - // Close all Streamable HTTP transports - for (const transport of this.streamableTransports.values()) { - await transport.close(); - } - this.streamableTransports.clear(); - - // Close HTTP server if running - if (this.httpServer) { - await new Promise((resolve) => this.httpServer!.close(() => resolve())); - } - // Disconnect the MCP server properly if (this.server) { await this.server.close(); diff --git a/server/src/services/validator.ts b/server/src/services/validator.ts index 2774e9a..227c230 100644 --- a/server/src/services/validator.ts +++ b/server/src/services/validator.ts @@ -17,7 +17,6 @@ export class Validator { verbose: true, strict: false, validateFormats: true, - coerceTypes: true, }); } diff --git a/virtocommerce-adapter/package-lock.json b/virtocommerce-adapter/package-lock.json index f16d2a7..7ca39c5 100644 --- a/virtocommerce-adapter/package-lock.json +++ b/virtocommerce-adapter/package-lock.json @@ -31,7 +31,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^0.5.0", + "@modelcontextprotocol/sdk": "^1.26.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "dotenv": "^16.3.1", diff --git a/virtocommerce-adapter/src/adapter.ts b/virtocommerce-adapter/src/adapter.ts index fd7dbbe..ba3e63e 100644 --- a/virtocommerce-adapter/src/adapter.ts +++ b/virtocommerce-adapter/src/adapter.ts @@ -272,6 +272,10 @@ export class VirtoCommerceFulfillmentAdapter implements IFulfillmentAdapter { if (options.workspace) { this.orderService.setWorkspace(options.workspace); } + + if (options.catalogId) { + this.orderService.setCatalogId(options.catalogId); + } } private getTenantId(): string { diff --git a/virtocommerce-adapter/src/models/base.ts b/virtocommerce-adapter/src/models/base.ts index 92c6ea2..3d07966 100644 --- a/virtocommerce-adapter/src/models/base.ts +++ b/virtocommerce-adapter/src/models/base.ts @@ -157,6 +157,8 @@ export interface HasDimension { width?: number; } +export type CancelledState = 'Undefined' | 'Requested' | 'Completed'; + /** * Interface for cancellable entities */ @@ -164,6 +166,7 @@ export interface SupportsCancellation { isCancelled?: boolean; cancelledDate?: string; cancelReason?: string; + cancelledState?: CancelledState; } /** diff --git a/virtocommerce-adapter/src/models/catalog.ts b/virtocommerce-adapter/src/models/catalog.ts index d20c7a1..3255375 100644 --- a/virtocommerce-adapter/src/models/catalog.ts +++ b/virtocommerce-adapter/src/models/catalog.ts @@ -203,5 +203,5 @@ export interface ProductSearchCriteria { */ export interface ProductSearchResult { totalCount?: number; - results?: CatalogProduct[]; + items?: CatalogProduct[]; } diff --git a/virtocommerce-adapter/src/models/index.ts b/virtocommerce-adapter/src/models/index.ts index 5049c57..f84806b 100644 --- a/virtocommerce-adapter/src/models/index.ts +++ b/virtocommerce-adapter/src/models/index.ts @@ -23,6 +23,7 @@ export type { OperationLog, HasDimension, SupportsCancellation, + CancelledState, HasOuterId, Taxable, HasDiscounts, @@ -32,7 +33,6 @@ export type { // Order models export type { - CancelledState, OrderOperation, CustomerOrder, ConfigurationItem, diff --git a/virtocommerce-adapter/src/models/order.ts b/virtocommerce-adapter/src/models/order.ts index a9298b2..5f1740c 100644 --- a/virtocommerce-adapter/src/models/order.ts +++ b/virtocommerce-adapter/src/models/order.ts @@ -19,11 +19,6 @@ import type { import type { PaymentIn } from './payment.js'; import type { Shipment } from './shipment.js'; -/** - * Cancelled state for order operations - */ -export type CancelledState = 'Undefined' | 'Requested' | 'Completed'; - /** * Base class for all order operations (orders, shipments, payments) */ @@ -36,7 +31,6 @@ export interface OrderOperation extends AuditableEntity, HasOuterId, SupportsCan comment?: string; currency?: string; sum?: number; - cancelledState?: CancelledState; objectType?: string; dynamicProperties?: DynamicObjectProperty[]; operationsLog?: OperationLog[]; diff --git a/virtocommerce-adapter/src/services/fulfillment.service.ts b/virtocommerce-adapter/src/services/fulfillment.service.ts index de5f7e6..a8bfd4b 100644 --- a/virtocommerce-adapter/src/services/fulfillment.service.ts +++ b/virtocommerce-adapter/src/services/fulfillment.service.ts @@ -8,7 +8,7 @@ import type { FulfillOrderInput, GetFulfillmentsInput, } from '@cof-org/mcp'; -import type { Shipment } from '../models/index.js'; +import type { Shipment, CustomerOrder } from '../models/index.js'; import { BaseService } from './base.service.js'; import { FulfillmentTransformer } from '../transformers/fulfillment.transformer.js'; import { mapFulfillmentFiltersToSearchCriteria } from '../mappers/filter.mappers.js'; @@ -35,20 +35,65 @@ export class FulfillmentService extends BaseService { } try { - const response = await this.client.post( - `/orders/${input.orderId}/shipments`, - this.transformer.fromFulfillOrderInput(input) + // Step 1: Fetch the current order + const fetchResponse = await this.client.get( + `/api/order/customerOrders/${input.orderId}` ); - if (!response.success || !response.data) { + if (!fetchResponse.success || !fetchResponse.data) { + return this.failure<{ fulfillment: Fulfillment }>('Order not found', { + orderId: input.orderId, + error: fetchResponse.error, + }); + } + + const order = fetchResponse.data; + const existingShipmentIds = new Set((order.shipments ?? []).map((s) => s.id)); + + // Step 2: Build a Shipment from input, resolving SKUs to lineItemIds + const newShipment = this.transformer.fromFulfillOrderInput(input, order.items, order.currency); + + // Step 3: Add the shipment to the order + const updatedOrder: CustomerOrder = { + ...order, + shipments: [...(order.shipments ?? []), newShipment], + }; + + // Step 4: Save the updated order (PUT returns 204 No Content) + const saveResponse = await this.client.put( + '/api/order/customerOrders', + updatedOrder + ); + + if (!saveResponse.success) { return this.failure<{ fulfillment: Fulfillment }>( 'Failed to create fulfillment', - response.error ?? response + saveResponse.error ?? saveResponse + ); + } + + // Step 5: Fetch the saved order to get server-assigned id/number for the new shipment + const refetchResponse = await this.client.get( + `/api/order/customerOrders/${input.orderId}` + ); + + const savedShipments = refetchResponse.success && refetchResponse.data + ? refetchResponse.data.shipments ?? [] + : updatedOrder.shipments ?? []; + + // Find the newly added shipment — the one not present in the original order + const addedShipment = + savedShipments.find((s) => s.id && !existingShipmentIds.has(s.id)) ?? + savedShipments[savedShipments.length - 1]; + + if (!addedShipment) { + return this.failure<{ fulfillment: Fulfillment }>( + 'Shipment was saved but could not be retrieved from order' ); } return this.success<{ fulfillment: Fulfillment }>({ - fulfillment: this.transformer.fromShipment(response.data), + fulfillment: this.transformer.fromShipment(addedShipment), }); } catch (error: unknown) { return this.failure<{ fulfillment: Fulfillment }>( @@ -65,7 +110,7 @@ export class FulfillmentService extends BaseService { const searchCriteria = mapFulfillmentFiltersToSearchCriteria(input); const response = await this.client.post<{ results?: Shipment[]; totalCount?: number }>( - '/api/order/customerOrders/shipments/search', + '/api/order/shipments/search', searchCriteria ); diff --git a/virtocommerce-adapter/src/services/order.service.ts b/virtocommerce-adapter/src/services/order.service.ts index 87271c8..aa655fc 100644 --- a/virtocommerce-adapter/src/services/order.service.ts +++ b/virtocommerce-adapter/src/services/order.service.ts @@ -11,7 +11,6 @@ import type { UpdateOrderInput, GetOrdersInput, } from '@cof-org/mcp'; -import type { YourFulfillmentOrder } from '../types.js'; import type { CustomerOrder, CustomerOrderSearchResult, @@ -43,17 +42,21 @@ export class OrderService extends BaseService { this.transformer.setWorkspace(workspace); } + setCatalogId(catalogId: string): void { + this.transformer.setCatalogId(catalogId); + } + async createSalesOrder(input: CreateSalesOrderInput): Promise { try { const payload = this.transformer.fromCreateSalesOrderInput(input); - const response = await this.client.post('/orders', payload); + const response = await this.client.post('/api/order/customerOrders', payload); if (!response.success || !response.data) { return this.failure<{ order: Order }>('Failed to create order', response.error ?? response); } return this.success<{ order: Order }>({ - order: this.transformer.toMcpOrder(response.data as unknown as CustomerOrder), + order: this.transformer.toMcpOrder(response.data), }); } catch (error: unknown) { return this.failure<{ order: Order }>(`Order creation failed: ${getErrorMessage(error)}`, error); @@ -94,12 +97,14 @@ export class OrderService extends BaseService { isCancelled: true, cancelledDate: new Date().toISOString(), cancelReason: input.reason ?? 'Customer requested cancellation', - cancelledState: 'Completed', + cancelledState: 'Requested', status: 'Cancelled', - comment: input.notes ? `${order.comment ?? ''}\n[Cancellation] ${input.notes}`.trim() : order.comment, + comment: input.notes + ? `${order.comment ?? ''}\n[Cancellation] ${input.notes}`.trim().slice(0, 2048) + : order.comment, }; - // Save the updated order + // Save the updated order (PUT returns 204 No Content) const saveResponse = await this.client.put( '/api/order/customerOrders', cancelledOrder @@ -109,7 +114,14 @@ export class OrderService extends BaseService { return this.failure<{ order: Order }>('Failed to cancel order', saveResponse.error ?? saveResponse); } - const savedOrder = saveResponse.data ?? cancelledOrder; + // Fetch the saved order to get server-recalculated totals + const refetchResponse = await this.client.get( + `/api/order/customerOrders/${input.orderId}` + ); + + const savedOrder = refetchResponse.success && refetchResponse.data + ? refetchResponse.data + : cancelledOrder; return this.success<{ order: Order }>({ order: this.transformer.toMcpOrder(savedOrder), @@ -146,7 +158,7 @@ export class OrderService extends BaseService { input.updates ); - // Step 3: Save the updated order + // Step 3: Save the updated order (PUT returns 204 No Content) const saveResponse = await this.client.put( '/api/order/customerOrders', updatedOrder @@ -156,7 +168,14 @@ export class OrderService extends BaseService { return this.failure<{ order: Order }>('Failed to update order', saveResponse.error ?? saveResponse); } - const savedOrder = saveResponse.data ?? updatedOrder; + // Fetch the saved order to get server-recalculated totals + const refetchResponse = await this.client.get( + `/api/order/customerOrders/${input.id}` + ); + + const savedOrder = refetchResponse.success && refetchResponse.data + ? refetchResponse.data + : updatedOrder; return this.success<{ order: Order }>({ order: this.transformer.toMcpOrder(savedOrder), diff --git a/virtocommerce-adapter/src/services/product.service.ts b/virtocommerce-adapter/src/services/product.service.ts index c206f90..e29581f 100644 --- a/virtocommerce-adapter/src/services/product.service.ts +++ b/virtocommerce-adapter/src/services/product.service.ts @@ -54,7 +54,7 @@ export class ProductService extends BaseService { ); } - const results = response.data?.results ?? []; + const results = response.data?.items ?? []; const products = this.transformer.fromCatalogProducts(results); return this.success<{ products: Product[] }>({ products }); } catch (error: unknown) { @@ -83,7 +83,7 @@ export class ProductService extends BaseService { ); } - const results = response.data?.results ?? []; + const results = response.data?.items ?? []; const productVariants = this.transformer.fromCatalogProductVariants(results); return this.success<{ productVariants: ProductVariant[] }>({ productVariants }); } catch (error: unknown) { @@ -116,7 +116,7 @@ export class ProductService extends BaseService { ); } - const products = catalogResponse.data?.results ?? []; + const products = catalogResponse.data?.items ?? []; if (!products.length) { return this.success<{ inventory: InventoryItem[] }>({ inventory: [] }); } diff --git a/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts b/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts index a28c9db..e6ac185 100644 --- a/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts +++ b/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts @@ -3,20 +3,23 @@ */ import type { Fulfillment, FulfillOrderInput } from '@cof-org/mcp'; -import type { Shipment } from '../models/index.js'; +import type { Shipment, ShipmentItem, LineItem } from '../models/index.js'; import { BaseTransformer } from './base.js'; import { AddressTransformer } from './address.transformer.js'; const SHIPMENT_STATUS_MAP: Record = { + // Standard VC shipment statuses (from ModuleConstants) New: 'pending', PickPack: 'processing', - ReadyToShip: 'ready_to_ship', + ReadyToSend: 'ready_to_send', + Sent: 'shipped', + Cancelled: 'cancelled', + // Non-standard but kept for backwards compatibility Shipped: 'shipped', Delivered: 'delivered', - Cancelled: 'cancelled', OnHold: 'on_hold', PartiallyShipped: 'partially_shipped', -}; +};; export class FulfillmentTransformer extends BaseTransformer { private addressTransformer: AddressTransformer; @@ -57,7 +60,7 @@ export class FulfillmentTransformer extends BaseTransformer { shippingClass: shipment.shipmentMethodOption, shippingCode: shipment.shipmentMethodCode, shippingPrice: shipment.price, - shippingNote: shipment.trackingUrl ?? shipment.comment, + shippingNote: shipment.comment, expectedDeliveryDate: shipment.deliveryDate, createdAt: shipment.createdDate ?? this.now(), updatedAt: shipment.modifiedDate ?? this.now(), @@ -75,7 +78,24 @@ export class FulfillmentTransformer extends BaseTransformer { /** * Transform FulfillOrderInput to VirtoCommerce Shipment payload */ - fromFulfillOrderInput(input: FulfillOrderInput): Record { + fromFulfillOrderInput(input: FulfillOrderInput, orderItems?: LineItem[], orderCurrency?: string): Shipment { + // Resolve input line items (by SKU) to VirtoCommerce ShipmentItems (by lineItemId) + const shipmentItems: ShipmentItem[] = []; + for (const inputItem of input.lineItems ?? []) { + const matchedLineItem = orderItems?.find((li) => li.sku === inputItem.sku); + if (matchedLineItem?.id) { + shipmentItems.push({ + lineItemId: matchedLineItem.id, + quantity: inputItem.quantity ?? matchedLineItem.quantity ?? 0, + }); + } + } + + const commentParts = [input.giftNote, input.shippingNote].filter(Boolean); + const comment = commentParts.length > 0 + ? commentParts.join('\n').slice(0, 2048) + : undefined; + return { trackingNumber: input.trackingNumbers?.[0], shipmentMethodCode: input.shippingCarrier, @@ -83,26 +103,12 @@ export class FulfillmentTransformer extends BaseTransformer { fulfillmentCenterId: input.locationId, deliveryDate: input.expectedDeliveryDate, deliveryAddress: input.shippingAddress - ? { - line1: input.shippingAddress.address1, - line2: input.shippingAddress.address2, - city: input.shippingAddress.city, - regionName: input.shippingAddress.stateOrProvince, - postalCode: input.shippingAddress.zipCodeOrPostalCode, - countryName: input.shippingAddress.country, - phone: input.shippingAddress.phone, - email: input.shippingAddress.email, - name: [input.shippingAddress.firstName, input.shippingAddress.lastName] - .filter(Boolean) - .join(' '), - organization: input.shippingAddress.company, - } + ? this.addressTransformer.toVirtoAddress(input.shippingAddress, 'Shipping') : undefined, - items: input.lineItems?.map((item) => ({ - sku: item.sku, - quantity: item.quantity ?? 0, - })), - comment: input.giftNote ?? input.shippingNote, + items: shipmentItems, + comment, + currency: orderCurrency, + price: input.shippingPrice, }; } diff --git a/virtocommerce-adapter/src/transformers/order.transformer.ts b/virtocommerce-adapter/src/transformers/order.transformer.ts index cbd70c4..e9fa275 100644 --- a/virtocommerce-adapter/src/transformers/order.transformer.ts +++ b/virtocommerce-adapter/src/transformers/order.transformer.ts @@ -11,7 +11,7 @@ import type { UpdateOrderInput, } from '@cof-org/mcp'; import { STATUS_MAP, REVERSE_STATUS_MAP } from '../types.js'; -import type { CustomerOrder, LineItem, DynamicObjectProperty, Contact } from '../models/index.js'; +import type { CustomerOrder, LineItem, Shipment, DynamicObjectProperty, Contact } from '../models/index.js'; import { BaseTransformer } from './base.js'; import { AddressTransformer } from './address.transformer.js'; import { CustomerTransformer } from './customer.transformer.js'; @@ -20,6 +20,7 @@ export class OrderTransformer extends BaseTransformer { private addressTransformer: AddressTransformer; private customerTransformer: CustomerTransformer; private workspace?: string; + private catalogId?: string; constructor(tenantId: string = 'default-workspace', workspace?: string) { super(tenantId); @@ -38,6 +39,10 @@ export class OrderTransformer extends BaseTransformer { this.workspace = workspace; } + setCatalogId(catalogId: string): void { + this.catalogId = catalogId; + } + /** * Transform VirtoCommerce order to MCP Order format * @param order - The VirtoCommerce CustomerOrder @@ -89,42 +94,67 @@ export class OrderTransformer extends BaseTransformer { /** * Transform CreateSalesOrderInput to API payload */ - fromCreateSalesOrderInput(input: CreateSalesOrderInput): Record { + fromCreateSalesOrderInput(input: CreateSalesOrderInput): CustomerOrder { const order = input.order; if (!order) { return {}; } - return { - external_id: order.externalId ?? order.name, - status: order.status, - total: order.totalPrice, - currency: order.currency ?? 'USD', - customer: order.customer - ? { - id: order.customer.id ?? order.customer.externalId ?? order.customer.email, - email: order.customer.email, - first_name: order.customer.firstName, - last_name: order.customer.lastName, - phone: order.customer.phone, - } + const currency = order.currency ?? 'USD'; + + const addresses = [ + order.shippingAddress + ? this.addressTransformer.toVirtoAddress(order.shippingAddress, 'Shipping') + : undefined, + order.billingAddress + ? this.addressTransformer.toVirtoAddress(order.billingAddress, 'Billing') : undefined, - items: order.lineItems?.map((item) => ({ + ].filter((a): a is NonNullable => a !== undefined); + + const items: LineItem[] = + order.lineItems?.map((item) => ({ sku: item.sku, name: item.name, quantity: item.quantity ?? 0, price: item.unitPrice ?? 0, - subtotal: item.totalPrice ?? item.unitPrice ?? 0, - discount: 0, - tax: 0, - })), - shipping_address: this.addressTransformer.toFulfillmentAddress(order.shippingAddress), - billing_address: this.addressTransformer.toFulfillmentAddress(order.billingAddress), - notes: order.orderNote, - metadata: { - source: order.orderSource, - workspace: this.workspace, - }, + placedPrice: item.unitPrice ?? 0, + currency, + catalogId: this.catalogId, + })) ?? []; + + const shipments: Shipment[] = order.shippingAddress + ? [ + { + deliveryAddress: this.addressTransformer.toVirtoAddress( + order.shippingAddress, + 'Shipping' + ), + currency, + }, + ] + : []; + + const customerName = order.customer + ? [order.customer.firstName, order.customer.lastName].filter(Boolean).join(' ') + || order.customer.email + || order.customer.id + || order.customer.externalId + : undefined; + + return { + outerId: order.externalId, + number: order.name ?? order.externalId ?? `ORD-${Date.now()}`, + status: order.status ? this.reverseMapStatus(order.status) : 'New', + currency, + total: order.totalPrice, + subTotal: order.subTotalPrice, + customerId: order.customer?.id ?? order.customer?.externalId, + customerName, + storeId: this.workspace, + comment: order.orderNote, + items, + addresses: addresses.length ? addresses : undefined, + shipments: shipments.length ? shipments : undefined, }; } @@ -142,7 +172,7 @@ export class OrderTransformer extends BaseTransformer { const orderNote = this.valueOrUndefined((updates as { orderNote?: string | null }).orderNote); if (orderNote !== undefined) { - updated.comment = orderNote; + updated.comment = orderNote.slice(0, 2048); } const shippingAddress = this.valueOrUndefined( @@ -168,6 +198,23 @@ export class OrderTransformer extends BaseTransformer { this.upsertAddress(updated, virtoBilling, 'Billing'); } + const lineItems = this.valueOrUndefined( + (updates as { lineItems?: UpdateOrderInput['updates']['lineItems'] }).lineItems + ); + if (lineItems?.length) { + updated.items = (updated.items ?? []).map((item) => { + const patch = lineItems.find((li) => li.sku === item.sku); + if (!patch) return item; + return { + ...item, + quantity: patch.quantity ?? item.quantity, + price: patch.unitPrice ?? item.price, + placedPrice: patch.unitPrice ?? item.placedPrice, + name: patch.name ?? item.name, + }; + }); + } + return updated; } diff --git a/virtocommerce-adapter/src/transformers/product.transformer.ts b/virtocommerce-adapter/src/transformers/product.transformer.ts index 79d8977..3da7c34 100644 --- a/virtocommerce-adapter/src/transformers/product.transformer.ts +++ b/virtocommerce-adapter/src/transformers/product.transformer.ts @@ -106,7 +106,9 @@ export class ProductTransformer extends BaseTransformer { private extractSelectedOptions( variation: CatalogProduct ): { name: string; value: string }[] | undefined { - if (!variation.properties?.length) return undefined; + if (!variation.properties?.length) { + return undefined; + } const options = variation.properties .filter((p) => p.type === 'Variation' && p.name && p.values?.length) @@ -125,7 +127,9 @@ export class ProductTransformer extends BaseTransformer { private extractWeight( product: CatalogProduct ): { value: number; unit: 'lb' | 'oz' | 'kg' | 'g' } | undefined { - if (product.weight == null) return undefined; + if (product.weight == null) { + return undefined; + } const unitMap: Record = { lb: 'lb', lbs: 'lb', pound: 'lb', pounds: 'lb', @@ -198,7 +202,9 @@ export class ProductTransformer extends BaseTransformer { return records .map((info) => { const sku = info.productId ? skuMap.get(info.productId) : undefined; - if (!sku) return undefined; + if (!sku) { + return undefined; + } return this.fromInventoryInfo(info, sku); }) .filter((item): item is InventoryItem => item != null); @@ -208,8 +214,12 @@ export class ProductTransformer extends BaseTransformer { * Map product status from VirtoCommerce fields */ private mapProductStatus(product: CatalogProduct): string { - if (!product.isActive) return 'inactive'; - if (!product.isBuyable) return 'draft'; + if (!product.isActive) { + return 'inactive'; + } + if (!product.isBuyable) { + return 'draft'; + } return 'active'; } @@ -225,7 +235,9 @@ export class ProductTransformer extends BaseTransformer { const optionMap = new Map>(); for (const variation of product.variations) { - if (!variation.properties) continue; + if (!variation.properties) { + continue; + } for (const prop of variation.properties) { if (prop.type === 'Variation' && prop.name && prop.values?.length) { const values = optionMap.get(prop.name) ?? new Set(); @@ -249,7 +261,9 @@ export class ProductTransformer extends BaseTransformer { * Extract tags from product properties */ private extractTags(properties?: ProductProperty[]): string[] | undefined { - if (!properties?.length) return undefined; + if (!properties?.length) { + return undefined; + } const tagProp = properties.find( (p) => p.name?.toLowerCase() === 'tags' || p.name?.toLowerCase() === 'tag' @@ -271,7 +285,9 @@ export class ProductTransformer extends BaseTransformer { private extractCustomFields( properties?: ProductProperty[] ): { name: string; value: string }[] | undefined { - if (!properties?.length) return undefined; + if (!properties?.length) { + return undefined; + } const fields = properties .filter((p) => p.type === 'Product' && p.values?.length) diff --git a/virtocommerce-adapter/src/types.ts b/virtocommerce-adapter/src/types.ts index 552a6b7..6714f58 100644 --- a/virtocommerce-adapter/src/types.ts +++ b/virtocommerce-adapter/src/types.ts @@ -8,6 +8,7 @@ export interface AdapterOptions { apiUrl: string; apiKey: string; workspace?: string; + catalogId?: string; timeout?: number; retryAttempts?: number; debugMode?: boolean; @@ -137,37 +138,41 @@ export interface YourFulfillmentShipment { } // Status mapping configuration -// VirtoCommerce uses PascalCase for statuses +// Keys are the actual VirtoCommerce status strings (from ModuleConstants.CustomerOrderStatus) export const STATUS_MAP: Record = { + // Standard VC order statuses New: 'pending', + 'Not payed': 'not_payed', + Pending: 'pending_approval', Processing: 'processing', + 'Ready to send': 'ready_to_send', + Cancelled: 'cancelled', + 'Partially sent': 'partially_shipped', + Completed: 'completed', + // Legacy/custom VC statuses kept for backwards compatibility Shipped: 'shipped', Delivered: 'delivered', - Cancelled: 'cancelled', OnHold: 'on_hold', Refunded: 'refunded', PartiallyShipped: 'partially_shipped', PartiallyDelivered: 'partially_delivered', - // Legacy lowercase mappings for backwards compatibility - shipped: 'shipped', - delivered: 'delivered', - cancelled: 'cancelled', - on_hold: 'on_hold', - refunded: 'refunded', - partially_shipped: 'partially_shipped', - partially_delivered: 'partially_delivered', }; -// Reverse mapping: normalized MCP status → VirtoCommerce PascalCase status +// Reverse mapping: normalized MCP status → actual VirtoCommerce status string export const REVERSE_STATUS_MAP: Record = { pending: 'New', + not_payed: 'Not payed', + pending_approval: 'Pending', processing: 'Processing', + ready_to_send: 'Ready to send', + cancelled: 'Cancelled', + partially_shipped: 'Partially sent', + completed: 'Completed', + // Non-standard VC statuses (VC accepts any string for status) shipped: 'Shipped', delivered: 'Delivered', - cancelled: 'Cancelled', on_hold: 'OnHold', refunded: 'Refunded', - partially_shipped: 'PartiallyShipped', partially_delivered: 'PartiallyDelivered', }; diff --git a/virtocommerce-adapter/src/utils/api-client.ts b/virtocommerce-adapter/src/utils/api-client.ts index 521ba52..a7c0ac4 100644 --- a/virtocommerce-adapter/src/utils/api-client.ts +++ b/virtocommerce-adapter/src/utils/api-client.ts @@ -51,7 +51,7 @@ export class ApiClient { // Request interceptor for debugging this.client.interceptors.request.use( (request) => { - if (this.debugMode) { + if (this.debugMode && request.url !== '/health') { const headers = { ...request.headers } as Record; console.error('[API Request]', { method: request.method?.toUpperCase(), @@ -76,7 +76,7 @@ export class ApiClient { // Response interceptor for debugging and error handling this.client.interceptors.response.use( (response) => { - if (this.debugMode) { + if (this.debugMode && response.config.url !== '/health') { console.error('[API Response]', { status: response.status, statusText: response.statusText, diff --git a/virtocommerce-adapter/tests/adapter.test.ts b/virtocommerce-adapter/tests/adapter.test.ts index e39f3fe..6803832 100644 --- a/virtocommerce-adapter/tests/adapter.test.ts +++ b/virtocommerce-adapter/tests/adapter.test.ts @@ -631,12 +631,39 @@ describe('VirtoCommerceFulfillmentAdapter', () => { describe('getCustomers', () => { it('should get customers by IDs', async () => { + const response = readResponse('getCustomers/getCustomers'); postSpy.mockResolvedValue({ success: true, - data: { - totalCount: 1, - results: [ - { + data: JSON.parse(response), + }); + + const input: GetCustomersInput = { + ids: ['CUST-001'], + }; + + const result = await adapter.getCustomers(input); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.customers).toHaveLength(1); + expect(result.customers[0]?.id).toBe('CUST-001'); + expect(result.customers[0]?.firstName).toBe('John'); + expect(result.customers[0]?.lastName).toBe('Doe'); + expect(result.customers[0]?.email).toBe('john@example.com'); + expect(result.customers[0]?.phone).toBe('+1234567890'); + expect(result.customers[0]?.externalId).toBe('EXT-CUST-001'); + expect(result.customers[0]?.tags).toEqual(['VIP']); + } + expect(postSpy).toHaveBeenCalledWith( + '/api/members/search', + expect.objectContaining({ + objectIds: ['CUST-001'], + memberTypes: ['Contact'], + responseGroup: 'Full', + }) + ); + }); + id: 'CUST-001', memberType: 'Contact', firstName: 'John', diff --git a/virtocommerce-adapter/tests/fixtures/getCustomers/getCustomers.json b/virtocommerce-adapter/tests/fixtures/getCustomers/getCustomers.json new file mode 100644 index 0000000..6a03ba3 --- /dev/null +++ b/virtocommerce-adapter/tests/fixtures/getCustomers/getCustomers.json @@ -0,0 +1,78 @@ +{ + "totalCount": 18, + "results": [ + { + "salutation": null, + "fullName": "Alexander Siniougin", + "firstName": "Alexander", + "middleName": null, + "lastName": "Siniougin", + "birthDate": null, + "defaultLanguage": null, + "currencyCode": null, + "timeZone": null, + "organizations": [], + "associatedOrganizations": [], + "defaultOrganizationId": null, + "currentOrganizationId": null, + "selectedAddressId": null, + "taxPayerId": null, + "preferredDelivery": null, + "preferredCommunication": null, + "defaultShippingAddressId": null, + "defaultBillingAddressId": null, + "photoUrl": null, + "isAnonymized": false, + "about": null, + "objectType": "VirtoCommerce.CustomerModule.Core.Model.Contact", + "securityAccounts": [], + "name": "Alexander Siniougin", + "memberType": "Contact", + "outerId": null, + "status": null, + "addresses": [ + { + "addressType": "Undefined", + "key": "419690fbf9e7429890fd7567fcb02a2b", + "name": "Alexander Siniougin 20945 Devonshire St Suite 102 Los Angeles California 34535 United States", + "organization": null, + "countryCode": "USA", + "countryName": "United States", + "city": "Los Angeles", + "postalCode": "34535", + "zip": null, + "line1": "20945 Devonshire St Suite 102", + "line2": null, + "regionId": "CA", + "regionName": "California", + "firstName": "Alexander", + "middleName": null, + "lastName": "Siniougin", + "phone": "23123", + "email": "sasha@virtoway.com", + "outerId": null, + "isDefault": false, + "description": null + } + ], + "phones": [], + "emails": [ + "sasha@virtoway.com" + ], + "notes": [], + "groups": [], + "iconUrl": null, + "relevanceScore": null, + "dynamicProperties": [], + "useDynamicPropertyAccessor": false, + "dynamicPropertyAccessor": {}, + "seoObjectType": "Contact", + "seoInfos": [], + "createdDate": "2026-01-06T16:43:15.4024086Z", + "modifiedDate": "2026-01-06T16:43:15.4024086Z", + "createdBy": "admin", + "modifiedBy": "admin", + "id": "CUST-001" + } + ] +} \ No newline at end of file From eac8365440d11a80f5a97f55aafc89dfe19996fe Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Mon, 23 Feb 2026 11:28:13 +0000 Subject: [PATCH 33/51] fix customer search --- virtocommerce-adapter/src/mappers/filter.mappers.ts | 2 +- virtocommerce-adapter/src/services/customer.service.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/virtocommerce-adapter/src/mappers/filter.mappers.ts b/virtocommerce-adapter/src/mappers/filter.mappers.ts index d1efe36..54c972f 100644 --- a/virtocommerce-adapter/src/mappers/filter.mappers.ts +++ b/virtocommerce-adapter/src/mappers/filter.mappers.ts @@ -192,7 +192,6 @@ export function mapProductVariantFilters(input: GetProductVariantsInput): Record */ export function mapCustomerFiltersToSearchCriteria(input: GetCustomersInput): MemberSearchCriteria { const criteria: MemberSearchCriteria = { - memberTypes: ['Contact'], responseGroup: 'Full', }; @@ -207,6 +206,7 @@ export function mapCustomerFiltersToSearchCriteria(input: GetCustomersInput): Me criteria.skip = input.skip ?? 0; criteria.take = input.pageSize ?? 20; + criteria.deepSearch = true; return criteria; } diff --git a/virtocommerce-adapter/src/services/customer.service.ts b/virtocommerce-adapter/src/services/customer.service.ts index 91553a7..57acd1b 100644 --- a/virtocommerce-adapter/src/services/customer.service.ts +++ b/virtocommerce-adapter/src/services/customer.service.ts @@ -61,6 +61,7 @@ export class CustomerService extends BaseService { // VirtoCommerce API: POST /api/members/search const searchCriteria: MemberSearchCriteria = { objectIds: uniqueIds, + deepSearch: true, memberTypes: ['Contact'], take: uniqueIds.length, responseGroup: 'Full', From fca985a36faea9949d37707dc3942b5e5f544e8a Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Mon, 23 Feb 2026 11:28:45 +0000 Subject: [PATCH 34/51] fetch catalogId on start --- virtocommerce-adapter/src/adapter.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/virtocommerce-adapter/src/adapter.ts b/virtocommerce-adapter/src/adapter.ts index ba3e63e..1280468 100644 --- a/virtocommerce-adapter/src/adapter.ts +++ b/virtocommerce-adapter/src/adapter.ts @@ -112,6 +112,11 @@ export class VirtoCommerceFulfillmentAdapter implements IFulfillmentAdapter { this.connected = true; console.error('Successfully connected to VirtoCommerce'); + + // Fetch catalogId from store configuration if workspace is set and catalogId is not already provided + if (this.options.workspace && !this.options.catalogId) { + await this.fetchCatalogId(); + } } catch (error: unknown) { this.connected = false; throw new AdapterError( @@ -278,6 +283,22 @@ export class VirtoCommerceFulfillmentAdapter implements IFulfillmentAdapter { } } + private async fetchCatalogId(): Promise { + try { + const storeResponse = await this.client.get<{ catalog?: string }>(`/api/stores/${this.options.workspace}`); + + if (storeResponse.success && storeResponse.data?.catalog) { + this.options.catalogId = storeResponse.data.catalog; + this.orderService.setCatalogId(storeResponse.data.catalog); + console.error(`Resolved catalogId "${storeResponse.data.catalog}" from store "${this.options.workspace}"`); + } else { + console.error(`Warning: Could not resolve catalogId from store "${this.options.workspace}"`); + } + } catch (error: unknown) { + console.error(`Warning: Failed to fetch store info for catalogId resolution: ${getErrorMessage(error)}`); + } + } + private getTenantId(): string { return this.options.workspace ?? 'default-workspace'; } From 2c20d63537613b3a03012f758a9dd0eb9cad1831 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Mon, 23 Feb 2026 11:28:55 +0000 Subject: [PATCH 35/51] small changes --- CLAUDE.md | 181 +- MCP_TEST_PROMPTS_RESULTS.md | 307 +++ server/CLAUDE.md | 2 +- server/src/adapters/adapter-factory.ts | 5 +- server/src/logging/structured-logger.ts | 7 +- .../VIRTO_MCP_TEST_PROMPTS.md | 314 +++ virtocommerce-adapter/package-lock.json | 1950 +++++++++-------- 7 files changed, 1681 insertions(+), 1085 deletions(-) create mode 100644 MCP_TEST_PROMPTS_RESULTS.md create mode 100644 virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md diff --git a/CLAUDE.md b/CLAUDE.md index 1eae09b..b1c91d5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,113 +4,115 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Repository Overview -This is the Commerce Operations Foundation (COF) MCP Reference Server - a monorepo implementing the Order Network eXchange (onX) standard for AI-powered fulfillment operations via the Model Context Protocol. Active development branch: `virto` (VirtoCommerce adapter integration). - -## Repository Structure +Commerce Operations Foundation (COF) MCP Reference Server — a monorepo implementing the Order Network eXchange (onX) standard for AI-powered fulfillment operations via the Model Context Protocol. ``` -/server - Core MCP server (main package: @cof-org/mcp) -/virtocommerce-adapter - VirtoCommerce fulfillment adapter (@virtocommerce/vc-fulfillment-mcp-adapter) +/server - Core MCP server (@cof-org/mcp) — Vitest +/virtocommerce-adapter - VirtoCommerce fulfillment adapter (@virtocommerce/vc-fulfillment-mcp-adapter) — Jest /adapter-template - Boilerplate for creating new adapters /schemas - JSON Schema definitions for domain models /docs - Specification and architectural documentation -/prompts - Developer notes/context for the integration work ``` ## Development Commands -### Server (`/server`) — Vitest +### Server (`/server`) ```bash cd server npm install -npm run build # Compile TypeScript to dist/ +npm run build # tsc → dist/ npm run dev # Hot reload via vite-node -npm test # Build + run all tests +npm test # Build + run all tests (vitest) npm run test:unit # Unit tests only npm run test:integration npx vitest tests/unit/adapters/adapter-factory.test.ts # Single test file npx vitest tests/unit --grep "AdapterFactory" # Tests matching pattern -npm run lint # ESLint +npm run lint # ESLint (flat config) npm run format # Prettier +npm run typecheck # tsc --noEmit npm run generate:json-schemas # Regenerate /schemas from TypeScript types ``` -### VirtoCommerce Adapter (`/virtocommerce-adapter`) — Jest +### VirtoCommerce Adapter (`/virtocommerce-adapter`) ```bash cd virtocommerce-adapter npm install -npm run build # Compile TypeScript -npm run dev # Watch mode (tsc --watch) -npm test # Jest with experimental VM modules +npm run build # tsc → dist/ +npm run dev # tsc --watch +npm test # Jest with --experimental-vm-modules npm run test:integration # Build + test-integration.js ``` -**Important**: The VirtoCommerce adapter uses **Jest** (not Vitest like the server). It depends on the server via `file:../server` — build the server first. +**Build order matters**: The adapter depends on the server via `"@cof-org/mcp": "file:../server"` — always build the server first. + +### CI (GitHub Actions) + +Node 20. Runs typecheck → lint → test (unit + integration matrix) on the server. Codecov for unit coverage. Trivy security scan on push. ## Architecture ### Three-Layer Design 1. **Protocol Layer** (`server/src/server.ts`, `server/src/index.ts`) - - Dual transport: **stdio** (default, for Claude Desktop) and **SSE** (HTTP, for cloud/remote) + - `MCPServerSDK` wraps `@modelcontextprotocol/sdk` Server with `StdioServerTransport` - All `console.*` redirected to `stderr` to keep stdout clean for MCP JSON-RPC - Loads `.env` then `.env.local` (overrides) via dotenv - - Transport selected via `MCP_TRANSPORT` env var (`stdio` | `sse`) + - Startup: `ConfigManager.getInstance()` → `Logger.init()` → `RetryHandler/Sanitizer/TimeoutHandler.setConfig()` → `server.start()` 2. **Service Layer** (`server/src/services/`) - `ServiceOrchestrator` — main facade; every operation wraps adapter calls with timeout, error handling, and metrics - - `AdapterManager`, `HealthMonitor`, `ErrorHandler`, `Transformer`, `Validator` + - `AdapterManager`, `HealthMonitor`, `ErrorHandler`, `Transformer`, `Validator` (AJV-based JSON Schema) 3. **Adapter Layer** (`server/src/adapters/`) - - `IFulfillmentAdapter` interface (14 methods: 4 lifecycle + 5 actions + 5 queries + optional) - - `AdapterFactory` — creates, validates, and caches adapter instances + - `IFulfillmentAdapter` interface — 13 required methods validated at creation time by `AdapterFactory.validateAdapter()` + - `AdapterFactory` — creates, validates, and caches adapter instances (singleton per config key) - Dynamic loading via `import()` for npm/local adapters ### Tool System -Tools in `server/src/tools/` extend `BaseTool` and are **manually registered** in `registerTools()` (not auto-discovered): -- **Actions** (`tools/actions/`): `create-sales-order`, `update-order`, `cancel-order`, `fulfill-order`, `create-return` -- **Queries** (`tools/queries/`): `get-orders`, `get-customers`, `get-products`, `get-product-variants`, `get-inventory`, `get-fulfillments`, `get-returns` +Tools in `server/src/tools/` extend `BaseTool` and are **manually registered** in `registerTools()` (`tools/index.ts`) — not auto-discovered: +- **Actions**: `create-sales-order`, `update-order`, `cancel-order`, `fulfill-order`, `create-return` +- **Queries**: `get-orders`, `get-customers`, `get-products`, `get-product-variants`, `get-inventory`, `get-fulfillments`, `get-returns` -Tools are thin wrappers — all business logic lives in `ServiceOrchestrator`. +Tools are thin wrappers — all business logic lives in `ServiceOrchestrator`. New tools must be added to `registerTools()`. ### Adapter Loading -Configured via environment variables: +Configured via environment variables (see `server/.env.example`): - `ADAPTER_TYPE=built-in` + `ADAPTER_NAME=mock` — built-in MockAdapter - `ADAPTER_TYPE=npm` + `ADAPTER_PACKAGE=@company/adapter` — dynamic import from node_modules - `ADAPTER_TYPE=local` + `ADAPTER_PATH=/path/to/adapter.js` — dynamic import from file path - `ADAPTER_CONFIG={"apiUrl":"...","apiKey":"..."}` — JSON config passed to adapter - `ADAPTER_EXPORT=ClassName` — optional, defaults to `default` export +- Feature flags: `FEATURE_*` env vars converted to boolean + +### Error Handling (Two-Tier) + +1. **Protocol errors** — thrown as `McpError` (invalid request, unknown tool) → MCP SDK handles, client sees JSON-RPC error +2. **Tool execution errors** — returned as `{ content: [...], isError: true }` (adapter failures, business logic) → client can inspect and retry + +`ErrorAdapter.processError()` in `server/src/errors/error-adapter.ts` decides the tier. Error classes in `server/src/utils/errors.ts` carry `isProtocolError` and `retryable` properties. ### Logging -Winston-based structured logging (`server/src/logging/structured-logger.ts`): -- Console transport to stderr (all levels, colorized) +Winston-based structured logging (`server/src/logging/structured-logger.ts`) + simple `Logger` (`server/src/utils/logger.ts`): +- All output to stderr (preserves stdout for MCP JSON-RPC) - File rotation: `error.log` (10MB/5 files), `combined.log` (10MB/10 files) -- Daily rotation in production with gzip, 14-day retention -- Built-in correlation IDs, PII sanitization, and structured methods (`logRequest`, `logResponse`, `logToolExecution`, `logAdapterCall`) +- Built-in correlation IDs, PII sanitization, structured methods ## VirtoCommerce Adapter ### Architecture -Domain-driven design with service delegation: - ``` adapter.ts (VirtoCommerceFulfillmentAdapter) -├── services/ -│ ├── order.service.ts — order CRUD + search -│ ├── customer.service.ts — customer queries -│ ├── fulfillment.service.ts — shipment tracking -│ ├── product.service.ts — products, variants, inventory -│ └── return.service.ts — returns handling -├── transformers/ — VirtoCommerce ↔ MCP bidirectional mapping -├── mappers/filter.mappers.ts — MCP filters → VC search criteria -├── models/ — TypeScript defs matching VC API -└── utils/api-client.ts — axios with retry, timeout, auth +├── services/ — domain services (order, customer, fulfillment, product, return) +├── transformers/ — VirtoCommerce ↔ MCP bidirectional mapping +├── mappers/ — MCP filters → VC search criteria +├── models/ — TypeScript defs matching VC API +└── utils/api-client.ts — axios with retry, timeout, auth ``` ### Key Patterns @@ -120,50 +122,11 @@ adapter.ts (VirtoCommerceFulfillmentAdapter) - **Customer enrichment**: Orders fetched separately from customers, then merged via `customerMap`. - **API authentication**: `api_key` header (configurable), with exponential backoff retry (default 3 attempts, 30s timeout). -### Testing via JSON-RPC (stdio) - -```bash -echo '{"jsonrpc":"2.0","method":"tools/call","id":2,"params":{"name":"get-orders","arguments":{"ids":["order-id"],"includeLineItems":true}}}' | node server\dist\index.js -``` +## Code Style -## SSE Transport & HTTP Deployment - -### SSE Endpoints - -When running with `MCP_TRANSPORT=sse`: -- `GET /sse` — Establishes SSE connection (returns `sessionId`) -- `POST /messages?sessionId=` — Sends JSON-RPC messages to a session -- `GET /health` — Health check (`{"status":"ok","transport":"sse","sessions":N}`) -- CORS enabled for all origins - -### Running via Docker - -```bash -# Local testing -docker compose up --build - -# Or directly -docker build -t cof-mcp . -docker run -p 3000:3000 --env-file .env.docker -e MCP_TRANSPORT=sse -e MCP_PORT=3000 cof-mcp -``` - -The Dockerfile is a multi-stage build (builder → prod-deps → runtime) on `node:22-alpine`. Runs as non-root user `cof`. HEALTHCHECK hits `/health` every 30s. - -`docker-compose.yml` uses `.env.docker` for adapter config and adds `host.docker.internal` for accessing local VirtoCommerce API from inside the container. - -### Cloud Deployment - -For cloud, override these environment variables: -- `ADAPTER_CONFIG` — set real VirtoCommerce API URL and key (not `host.docker.internal`) -- `NODE_TLS_REJECT_UNAUTHORIZED` — remove or set to `1` in production -- `LOG_LEVEL` — use `info` or `warn` (debug is force-disabled in production anyway) - -### Error Handling (Two-Tier) - -1. **Protocol errors** — thrown as `McpError` (invalid request, unknown tool) → MCP SDK handles, client sees JSON-RPC error -2. **Tool execution errors** — returned as `{ content: [...], isError: true }` (adapter failures, business logic errors) → client can inspect and retry - -`ErrorAdapter.processError()` in `server/src/errors/error-adapter.ts` decides which tier an error belongs to. +- **Prettier**: `printWidth: 120`, `singleQuote: true`, `trailingComma: 'es5'`, `semi: true` (see `prettier.config.cjs`) +- **ESLint**: Flat config (`server/eslint.config.js`). `no-console` allows only `warn`/`error`. Unused vars prefixed with `_` (`argsIgnorePattern: '^_'`). `no-var: error`. +- **Naming**: Classes PascalCase, functions camelCase, constants UPPER_SNAKE_CASE, files kebab-case ## Key Implementation Details @@ -172,11 +135,19 @@ For cloud, override these environment variables: - `type: "module"` in both packages - Use `import`/`export`, not `require` -### Test Isolation (Server) -- Vitest with `singleThread: true` to prevent AdapterFactory cache race conditions -- Always call `AdapterFactory.clearInstances()` in test cleanup -- Setup file: `tests/setup.ts` -- Path alias: `@` → `./src` +### Test Isolation + +**Server (Vitest)**: `singleThread: true` to prevent `AdapterFactory` cache race conditions. Always call `AdapterFactory.clearInstances()` in test cleanup. Setup file: `tests/setup.ts`. Path alias: `@` → `./src`. + +**VirtoCommerce Adapter (Jest)**: Uses `ts-jest/presets/default-esm` with `extensionsToTreatAsEsm: ['.ts']`. Coverage threshold: 80% (branches, functions, lines, statements). Custom `moduleNameMapper` strips `.js` extensions. Requires `--experimental-vm-modules`. + +### IFulfillmentAdapter Required Methods + +Lifecycle: `connect()`, `disconnect()`, `healthCheck()` +Actions: `createSalesOrder`, `cancelOrder`, `updateOrder`, `fulfillOrder` +Queries: `getOrders`, `getCustomers`, `getProducts`, `getProductVariants`, `getInventory`, `getFulfillments` + +Optional (registered as tools but not in `validateAdapter`): `createReturn`, `getReturns` ### MCP Response Format ```typescript @@ -192,16 +163,7 @@ await serviceOrchestrator.initialize(adapterConfig); // Required before use await serviceOrchestrator.cleanup(); // Disconnect + stop monitors ``` -### IFulfillmentAdapter Required Methods -Lifecycle: `connect()`, `disconnect()`, `healthCheck()`, optional `initialize(config)` -Actions: `createSalesOrder`, `cancelOrder`, `updateOrder`, `fulfillOrder`, `createReturn` -Queries: `getOrders`, `getCustomers`, `getProducts`, `getProductVariants`, `getInventory`, `getFulfillments`, `getReturns` - -`AdapterFactory.validateAdapter()` checks all required methods at creation time. - -## Client Integration - -### Claude Desktop (stdio) +## Client Integration (Claude Desktop — stdio) ```json { @@ -222,25 +184,12 @@ Queries: `getOrders`, `getCustomers`, `getProducts`, `getProductVariants`, `getI Config location: `%APPDATA%\Claude\claude_desktop_config.json` (Windows), `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) -### Remote SSE (Docker / Cloud) +### Testing via JSON-RPC (stdio) -```json -{ - "mcpServers": { - "cof-mcp": { - "url": "http://localhost:3000/sse" - } - } -} +```bash +echo '{"jsonrpc":"2.0","method":"tools/call","id":2,"params":{"name":"get-orders","arguments":{"ids":["order-id"],"includeLineItems":true}}}' | node server/dist/index.js ``` -For cloud deployments, replace `localhost:3000` with the actual host URL. - ## Detailed Server Documentation -See `/server/CLAUDE.md` for comprehensive server-specific guidance including: -- Configuration system details (ConfigManager, multi-source loading, environment priority) -- Error handling patterns (McpError vs tool errors, ErrorHandler pipeline) -- Health monitoring -- Security considerations (dynamic adapter loading, production hardening) -- Common development patterns (adding tools, adding service operations) +See `/server/CLAUDE.md` for server-specific guidance including configuration system details (`ConfigManager`, multi-source loading), error handling pipeline, health monitoring, and common development patterns. diff --git a/MCP_TEST_PROMPTS_RESULTS.md b/MCP_TEST_PROMPTS_RESULTS.md new file mode 100644 index 0000000..83da377 --- /dev/null +++ b/MCP_TEST_PROMPTS_RESULTS.md @@ -0,0 +1,307 @@ +# MCP Test Prompts — Verification Results + +The file `MCP_TEST_PROMPTS.md` was written for an early version of the server ("Universal OMS MCP Server") with the mock adapter. Below is an analysis of each block of prompts for compatibility with the current state of the MCP server. + +--- + +## Tool Mapping: Document vs Actual Server + +| # | Document | Actual Tool | Status | +| --- | ---------------------- | -------------------- | ------------------ | +| 1 | Get Order Tool | `get-orders` | :white_check_mark: | +| 2 | Get Customer Tool | `get-customers` | :white_check_mark: | +| 3 | Get Product Tool | `get-products` | :white_check_mark: | +| 4 | Get Inventory Tool | `get-inventory` | :white_check_mark: | +| 5 | Get Shipment Tool | `get-fulfillments` | :white_check_mark: | +| 6 | Get Buyer Tool | — | :white_check_mark: | +| 7 | Capture Order Tool | `create-sales-order` | | +| 8 | Cancel Order Tool | `cancel-order` | | +| 9 | Update Order Tool | `update-order` | | +| 10 | Return Order Tool | `create-return` | | +| 11 | Exchange Order Tool | — | | +| 12 | Ship Order Tool | `fulfill-order` | | +| 13 | Hold Order Tool | — | | +| 14 | Split Order Tool | — | | +| 15 | Reserve Inventory Tool | — | | + +**5 out of 15 tools from the document do not exist in the current server.** Another 3 have different names. + +--- + +## Test Data + +The test data in the document **correctly matches** the mock adapter (`server/src/adapters/mock/mock-data.ts`): + +| Entity | Document | Mock Data | Match | +| ------------------------- | ------------------------- | -------------------------- | ----- | +| order_001 / EXT-001 | confirmed, John Smith | :white_check_mark: Present | Yes | +| order_002 / ORD-1001 | processing, Sarah Johnson | :white_check_mark: Present | Yes | +| order_003 / WEB-2024-1002 | shipped, John Smith | :white_check_mark: Present | Yes | +| cust_001 John Smith | john.smith@example.com | :white_check_mark: Present | Yes | +| cust_002 Sarah Johnson | sarah.johnson@example.com | :white_check_mark: Present | Yes | +| prod_001 / WID-001 | Headphones $199.99 | :white_check_mark: Present | Yes | +| prod_002 / TSH-002 | T-Shirt $29.99 | :white_check_mark: Present | Yes | +| prod_003 / COF-003 | Coffee $24.99 | :white_check_mark: Present | Yes | +| WH001, WH002, WH003 | Warehouses | :white_check_mark: Present | Yes | + +**Important:** Test data only works with the mock adapter (`ADAPTER_TYPE=built-in`, `ADAPTER_NAME=mock`). With the VirtoCommerce adapter, data will come from the actual VC instance. + +--- + +## Query Tools Test Prompts + +### 1. Get Order Tool — :white_check_mark: WORKS (with caveats) + +| Prompt | How the agent should call | Result | +| ----------------------------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------- | +| "Get me the details for order EXT-001" | `get-orders` with `externalIds: ["EXT-001"]` | :white_check_mark: Works — `externalIds` maps to VC `outerIds` + `numbers` | +| "Show me order order_002" | `get-orders` with `ids: ["order_002"]` | :white_check_mark: Works — direct lookup by ID | +| "What's the status of order ORD-1001?" | `get-orders` with `externalIds: ["ORD-1001"]` or `names: ["ORD-1001"]` | :white_check_mark: Works — `names` maps to `numbers` | +| "Can you retrieve order WEB-2024-1002..." | `get-orders` with `externalIds: ["WEB-2024-1002"]` | :white_check_mark: Works | + +**Note:** The agent must determine whether the identifier is an internal ID (`order_002`), external ID (`EXT-001`), or order number (`ORD-1001`), and use the corresponding parameter. For natural language prompts, this is the AI agent's responsibility. + +--- + +### 2. Get Customer Tool — :warning: PARTIALLY WORKS + +| Prompt | How the agent should call | Result | +| --------------------------------------------------------------------- | ------------------------------------------------------------ | ------------------------ | +| "Get customer information for cust_001" | `get-customers` with `ids: ["cust_001"]` | :white_check_mark: Works | +| "Show me the details for customer john.smith@example.com" | `get-customers` with `emails: ["john.smith@example.com"]` | :white_check_mark: Works | +| "Find customer cust_002" | `get-customers` with `ids: ["cust_002"]` | :white_check_mark: Works | +| "What information do you have on customer sarah.johnson@example.com?" | `get-customers` with `emails: ["sarah.johnson@example.com"]` | :white_check_mark: Works | + +**All prompts work** because they use ID or email. Prompts with name search ("Find John Smith") are not included — and that's correct, since `get-customers` does not support name search. + +--- + +### 3. Get Product Tool — :white_check_mark: WORKS + +| Prompt | How the agent should call | Result | +| ------------------------------------------------------ | --------------------------------------- | ------------------------ | +| "Show me product details for SKU WID-001" | `get-products` with `skus: ["WID-001"]` | :white_check_mark: Works | +| "Get information about product prod_002" | `get-products` with `ids: ["prod_002"]` | :white_check_mark: Works | +| "What are the details for the coffee product COF-003?" | `get-products` with `skus: ["COF-003"]` | :white_check_mark: Works | +| "Find product TSH-002 and show me all its attributes" | `get-products` with `skus: ["TSH-002"]` | :white_check_mark: Works | + +**All prompts work** — they use direct lookup by SKU or product ID. + +--- + +### 4. Get Inventory Tool — :white_check_mark: WORKS + +| Prompt | How the agent should call | Result | +| ------------------------------------------------------------ | ---------------------------------------------------------------- | ------------------------------------------------ | +| "Check inventory for SKU WID-001 at warehouse WH001" | `get-inventory` with `skus: ["WID-001"], locationIds: ["WH001"]` | :white_check_mark: Works | +| "What's the available stock for TSH-002 in location WH002?" | `get-inventory` with `skus: ["TSH-002"], locationIds: ["WH002"]` | :white_check_mark: Works | +| "Show me inventory levels for COF-003 across all warehouses" | `get-inventory` with `skus: ["COF-003"]` (without locationIds) | :white_check_mark: Works — returns all locations | +| "Get inventory status for WID-001 at WH003" | `get-inventory` with `skus: ["WID-001"], locationIds: ["WH003"]` | :white_check_mark: Works | + +--- + +### 5. Get Shipment Tool — :white_check_mark: WORKS + +Actual tool name: **`get-fulfillments`** (not "get-shipment"). + +| Prompt | How the agent should call | Result | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------------- | +| "Get shipment details for order order_003" | `get-fulfillments` with `orderIds: ["order_003"]` | :white_check_mark: Works | +| "Show me the shipment information for order WEB-2024-1002" | First `get-orders` with `externalIds: ["WEB-2024-1002"]` to get ID, then `get-fulfillments` | :white_check_mark: Works (two-step) | +| "Check if order_001 has been shipped" | `get-fulfillments` with `orderIds: ["order_001"]` | :white_check_mark: Works — empty result = not shipped | +| "Find shipment tracking for order EXT-001" | Similarly — resolve external ID then get-fulfillments | :white_check_mark: Works (two-step) | + +**Note:** Prompts use external IDs, but `get-fulfillments` only accepts `orderIds` (internal). The agent must first resolve the external ID via `get-orders`. + +--- + +### 6. Get Buyer Tool — :x: DOES NOT EXIST + +| Prompt | Result | +| -------------------------------------------------- | --------------------------------------- | +| "Get buyer information for order order_001" | :x: The `get-buyer` tool does not exist | +| "Who is the buyer for order ORD-1001?" | :x: No such tool | +| "Show me the buyer details for order_002" | :x: No such tool | +| "Find the customer who placed order WEB-2024-1002" | :x: No such tool | + +**Workaround:** `get-orders` returns a `customer` object as part of the order (with `includeLineItems: true`, default). The agent can get customer info from the `get-orders` response. A separate "Get Buyer" tool is not needed. + +--- + +## Action Tools Test Prompts + +### 7. Capture Order Tool — :white_check_mark: WORKS + +Actual name: **`create-sales-order`** (not "capture-order"). + +| Prompt | How the agent should call | Result | +| --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| "Create a new order for customer cust_001 with 2 units of WID-001 shipping to 123 Main St..." | `create-sales-order` with `order: { customer: { id: "cust_001" }, lineItems: [{ sku: "WID-001", quantity: 2 }], shippingAddress: { ... } }` | :white_check_mark: Works | +| "Capture an order for sarah.johnson@example.com with 1 TSH-002 and 2 COF-003" | `create-sales-order` with `customer: { email: "..." }, lineItems: [...]` | :white_check_mark: Works | +| "Place an order for customer cust_002 with product WID-001, quantity 1..." | Similarly | :white_check_mark: Works | + +**Note:** `lineItems` must contain `sku` and `quantity`. `unitPrice` is optional in the schema, but without it VirtoCommerce may create an order with zero prices. + +--- + +### 8. Cancel Order Tool — :white_check_mark: WORKS + +| Prompt | How the agent should call | Result | +| --------------------------------------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------- | +| "Cancel order order_001 due to customer request" | `cancel-order` with `orderId: "order_001", reason: "customer request"` | :white_check_mark: Works | +| "Please cancel order EXT-001 - the customer changed their mind" | First resolve EXT-001 to internal ID, then `cancel-order` | :warning: Two-step — the agent must resolve the external ID | +| "Cancel order ORD-1001 because of inventory issues" | Similarly — resolve then cancel | :warning: Two-step | + +**Note:** `cancel-order` accepts `orderId` (internal ID), not external ID or order number. The agent must first call `get-orders` to resolve. + +--- + +### 9. Update Order Tool — :warning: PARTIALLY WORKS + +| Prompt | How the agent should call | Result | +| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| "Update order order_002 to change the quantity of TSH-002 to 3 units" | `update-order` with `id: "order_002", updates: { lineItems: [{ sku: "TSH-002", quantity: 3, unitPrice: 29.99 }] }` | :white_check_mark: Works — `unitPrice` is required in update lineItems | +| "Modify order EXT-001 to ship to 789 Broadway..." | Resolve EXT-001 then `update-order` with `updates: { shippingAddress: {...} }` | :white_check_mark: Works (two-step) | +| "Update order ORD-1001 with express shipping" | `update-order` with `updates: { shippingClass: "Express" }` | :warning: Partial — `shippingClass` is part of `ShippingInfoSchema`, but the adapter's `applyUpdatesToOrder` does not handle shipping method changes (only address, status, lineItems, notes) | + +--- + +### 10. Return Order Tool — :x: DOES NOT WORK (stub) + +Actual name: **`create-return`**. + +| Prompt | Result | +| ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | +| "Process a return for order order_003 - customer says the coffee tastes bad" | :x: `create-return` exists, but the VC adapter returns a hardcoded failure: "createReturn not yet implemented" | +| "Create a return for order WEB-2024-1002 with reason damaged during shipping" | :x: Same — stub | +| "Return order order_001 because the headphones don't work" | :x: Same — stub | + +**With mock adapter:** Returns work (mock data contains `return_001`, `return_002`, `return_003`). With VirtoCommerce adapter — stub. + +--- + +### 11. Exchange Order Tool — :x: DOES NOT EXIST + +| Prompt | Result | +| ---------------------------------------------------------------------------- | -------------------- | +| "Exchange order order_001 - customer wants TSH-002 instead of WID-001" | :x: No exchange tool | +| "Process an exchange for order ORD-1001 - swap the t-shirt for coffee beans" | :x: No such tool | +| "Exchange the items in order_002 for different products" | :x: No such tool | + +**Note:** An exchange can be modeled as `create-return` (outcome: "exchange") + `create-sales-order`. But `create-return` is a stub in the VC adapter. + +--- + +### 12. Ship Order Tool — :white_check_mark: WORKS + +Actual name: **`fulfill-order`** (not "ship-order"). + +| Prompt | How the agent should call | Result | +| ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | +| "Mark order order_001 as shipped with tracking number TRK123456789" | `fulfill-order` with `orderId: "order_001", trackingNumbers: ["TRK123456789"], lineItems: [{ sku: "WID-001", quantity: 1 }]` | :white_check_mark: Works | +| "Ship order EXT-001 via FedEx with tracking FDX987654321" | Resolve then `fulfill-order` with `shippingCarrier: "FedEx", trackingNumbers: ["FDX987654321"]` | :white_check_mark: Works (two-step) | +| "Process shipment for order_002 using UPS tracking 1Z999AA10123456784" | `fulfill-order` with corresponding parameters | :white_check_mark: Works | + +**Note:** `fulfill-order` requires `orderId`, `lineItems` (with SKU + quantity), and `trackingNumbers` — all mandatory. The adapter adds a shipment to the order via the GET -> add shipment -> PUT flow. + +--- + +## Management Tools Test Prompts + +### 13. Hold Order Tool — :x: DOES NOT EXIST + +| Prompt | Result | +| ------------------------------------------------ | ----------------------- | +| "Put order order_001 on hold" | :x: No hold/unhold tool | +| "Hold order EXT-001 due to address verification" | :x: | +| "Place a hold on order_002" | :x: | +| "Release the hold on order ORD-1001" | :x: | + +**Workaround:** You can use `update-order` with `updates: { status: "on_hold" }`, since `applyUpdatesToOrder` supports status changes. However, it depends on the VirtoCommerce state machine — the transition to "OnHold" may be allowed or disallowed. + +--- + +### 14. Split Order Tool — :x: DOES NOT EXIST + +| Prompt | Result | +| -------------------------------------------------------------- | ----------------- | +| "Split order order_002 so that 1 TSH-002 ships immediately..." | :x: No split tool | +| "I need to split order_001 into two separate shipments" | :x: | +| "Divide order WEB-2024-1002..." | :x: | + +**Workaround:** You can create a fulfillment with a subset of items via `fulfill-order` (specifying a subset of line items), leaving the rest unfulfilled. But this is not a split at the order level. + +--- + +### 15. Reserve Inventory Tool — :x: DOES NOT EXIST + +| Prompt | Result | +| -------------------------------------------------------- | ------------------------------------- | +| "Reserve 5 units of WID-001 from warehouse WH001" | :x: No reserve/release inventory tool | +| "Hold 10 units of TSH-002 inventory at location WH002" | :x: | +| "Reserve 3 COF-003 from WH003 for upcoming order" | :x: | +| "Release the reservation on 2 units of WID-001 at WH001" | :x: | + +--- + +## Complex Workflow Test Prompts + +### Multi-Tool Operations — :warning: PARTIALLY WORK + +| Prompt | Result | +| ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| "Get order order_001, then check inventory for all its items, and finally ship it with tracking ABC123" | :white_check_mark: Works — `get-orders` -> extract SKUs -> `get-inventory` -> `fulfill-order`. All three tools exist. | +| "Find customer cust_002, show me all their orders, and then check product availability for TSH-002" | :warning: Partial — `get-customers` works, but "all their orders" requires a `customerIds` filter in `get-orders` (does not exist). `get-inventory` for TSH-002 works. | +| "Create a new order for john.smith@example.com with 2 WID-001, then put it on hold for payment verification" | :warning: Partial — `create-sales-order` works. "Put on hold" can be done via `update-order` with `status: "on_hold"`, but there's no guarantee the VC state machine will allow the transition. | + +--- + +### Error Handling Tests — :white_check_mark: WORK + +| Prompt | Result | +| ------------------------------------------------------ | --------------------------------------------------------------------------------------- | +| "Get order INVALID_ORDER_ID" | :white_check_mark: Will return an empty result or error response | +| "Cancel an order that doesn't exist: order_999" | :white_check_mark: `cancel-order` -> adapter will return "Order not found" | +| "Check inventory for a non-existent SKU: FAKE-SKU-001" | :white_check_mark: `get-inventory` -> empty array (SKU doesn't resolve to a product ID) | +| "Get customer information for unknown@email.com" | :white_check_mark: `get-customers` -> empty customers array | + +--- + +### Business Logic Tests — :warning: PARTIALLY WORK + +| Prompt | Result | +| -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| "Cancel order order_003 (already shipped - should fail)" | :warning: The adapter doesn't check the "shipped" status — it only checks `isCancelled`. It may attempt to cancel and get an error from the VC state machine, or successfully cancel (depends on VC settings). | +| "Ship order order_001 twice (should handle duplicate shipment)" | :white_check_mark: `fulfill-order` adds a new shipment to the order — two calls will create two shipments. Not an error, but may be undesirable behavior. | +| "Reserve 1000 units of WID-001 (likely exceeds available inventory)" | :x: `reserve-inventory` does not exist | + +--- + +## Final Summary + +### By Tool Groups + +| Group | Total Prompts | Works | Partial | Does Not Work | +| ------------------------ | ------------- | ------ | ------- | ------------- | +| Query Tools (1-5) | 20 | 18 | 2 | 0 | +| Get Buyer (6) | 4 | 0 | 0 | 4 | +| Action Tools (7-12) | 15 | 6 | 3 | 6 | +| Management Tools (13-15) | 10 | 0 | 0 | 10 | +| Complex Workflows | 3 | 1 | 2 | 0 | +| Error Handling | 4 | 4 | 0 | 0 | +| Business Logic | 3 | 1 | 1 | 1 | +| **TOTAL** | **59** | **30** | **8** | **21** | + +### Key Document Issues + +1. **5 non-existent tools** (Get Buyer, Exchange Order, Hold Order, Split Order, Reserve Inventory) — 18 prompts test things that don't exist. +2. **3 tools with incorrect names** — "Get Shipment" is `get-fulfillments`, "Capture Order" is `create-sales-order`, "Ship Order" is `fulfill-order`. The AI agent can figure it out from the description, but the documentation is misleading. +3. **External ID to Internal ID resolution** is not accounted for — many prompts use external IDs (EXT-001, ORD-1001), but write operations (`cancel-order`, `update-order`, `fulfill-order`) require the internal order ID. +4. **`create-return` is a stub** in the VirtoCommerce adapter — return prompts don't work with VC. + +### Recommendations + +1. **Remove or mark as "planned"** prompts for non-existent tools (6, 11, 13, 14, 15). +2. **Update tool names** in the document: capture -> create-sales-order, ship -> fulfill-order, get-shipment -> get-fulfillments. +3. **Add a note** about the need to resolve external ID to internal ID for write operations. +4. **Separate** prompts for mock adapter and VirtoCommerce adapter (returns only work with mock). diff --git a/server/CLAUDE.md b/server/CLAUDE.md index 0e13826..66c5ee5 100644 --- a/server/CLAUDE.md +++ b/server/CLAUDE.md @@ -227,7 +227,7 @@ Location: 2. Extend `BaseTool` 3. Define `name`, `description`, `inputSchema` 4. Implement `execute(input: TInput): Promise` -5. Registry auto-discovers and registers it +5. Add `registry.register(new YourTool(serviceLayer))` to `registerTools()` in `tools/index.ts` ### Adding a Service Operation 1. Add method to appropriate service class diff --git a/server/src/adapters/adapter-factory.ts b/server/src/adapters/adapter-factory.ts index d0f8317..a170f1d 100644 --- a/server/src/adapters/adapter-factory.ts +++ b/server/src/adapters/adapter-factory.ts @@ -4,6 +4,7 @@ import * as path from 'path'; import * as fs from 'fs'; +import { pathToFileURL } from 'url'; import { IFulfillmentAdapter, AdapterConfig, AdapterConstructor, AdapterError } from '../types/index.js'; import { Logger } from '../utils/logger.js'; import { MockAdapter } from './mock/mock-adapter.js'; @@ -177,8 +178,8 @@ export class AdapterFactory { Logger.info(`Loading local adapter from: ${adapterPath}`); try { - // Dynamically import local file - const module = await import(adapterPath); + // Dynamically import local file (use file:// URL for Windows compatibility) + const module = await import(pathToFileURL(adapterPath).href); const exportName = config.exportName || 'default'; const AdapterClass = module[exportName]; diff --git a/server/src/logging/structured-logger.ts b/server/src/logging/structured-logger.ts index 7f44d1b..510b7fc 100644 --- a/server/src/logging/structured-logger.ts +++ b/server/src/logging/structured-logger.ts @@ -6,6 +6,7 @@ import * as winston from 'winston'; import * as path from 'path'; import * as fs from 'fs'; +import { fileURLToPath } from 'url'; import DailyRotateFile from 'winston-daily-rotate-file'; import { LogSanitizer } from '../security/log-sanitizer.js'; @@ -42,9 +43,9 @@ export class StructuredLogger { // Make log directory absolute if it's relative if (!path.isAbsolute(logDir)) { // Find the server directory (parent of dist when running compiled code) - const currentDir = path.dirname(new URL(import.meta.url).pathname); - const serverDir = currentDir.includes('/dist/') - ? path.resolve(currentDir.split('/dist/')[0]) + const currentDir = path.dirname(fileURLToPath(import.meta.url)); + const serverDir = currentDir.includes(`${path.sep}dist${path.sep}`) + ? currentDir.split(`${path.sep}dist${path.sep}`)[0] : path.resolve(process.cwd(), 'server'); logDir = path.join(serverDir, logDir); } diff --git a/virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md b/virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md new file mode 100644 index 0000000..03aa14b --- /dev/null +++ b/virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md @@ -0,0 +1,314 @@ +# Universal OMS MCP Server - Test Prompts + +Copy and paste these prompts into Claude Desktop to test each MCP tool. The mock adapter has pre-populated test data you can reference. + +## Test Data Reference + +### Available Orders +- **95bee1c2-f6b6-4eef-b9fd-df260b980d71** / CO220518-00001: + - Status=new + - Customer=b2b admin, + - Product=Brother MFC-L6700DW Wireless Monochrome All-in-One Laser Printer, Copy/Fax/Print/Scan +- **9eebb423-619b-4fcb-a52e-82e367ae37cc** / CO220715-00001: + - Status=processing + - Customer=b2b admin, + - Products + - Epson Expression Premium XP-820 Wireless Color Photo Printer/Copier/Scanner/Fax Machine + - Epson Expression Premium XP-830 All-In-One Wireless Printer +- todo: add more orders with different statuses, customers, and products for comprehensive testing + +### Available Customers +- **cb0a5340-f9fb-4f49-bd62-9d03518868ff**: b2b admin (b2badmin@test.com, VIP, Wholesaler) +- **fa90d0b3-4bf5-4fc8-8c7c-787cafc4c678**: Alla Volkova (allagrvolkova@mail.ru) + +### Available Products/SKUs +- **prod_001** / WID-001: Wireless Bluetooth Headphones ($199.99) +- **prod_002** / TSH-002: Organic Cotton T-Shirt ($29.99) +- **prod_003** / COF-003: Premium Coffee Beans ($24.99) + +### Warehouse Locations +- WH001, WH002, WH003 (all have inventory for each product) + +--- + +## Query Tools Test Prompts + +### 1. Get Order Tool +``` +Get me the details for order CO220518-00001 +``` +:white_check_mark: Passed - returns order details for CO220518-00001 +``` +Show me order 95bee1c2-f6b6-4eef-b9fd-df260b980d71 +``` +:white_check_mark: Passed - returns order details +``` +What's the status of order CO220715-00001? +``` +:white_check_mark: Passed - returns "processing" +``` +Can you retrieve order CO220715-00001 and show me all details? +``` +:white_check_mark: Passed - returns full order details including products and customer info + +### 2. Get Customer Tool +``` +Get customer information for cb0a5340-f9fb-4f49-bd62-9d03518868ff +``` +:white_check_mark: Passed - returns customer details for b2b admin +``` +Show me the details for customer allagrvolkova@mail.ru +``` +:white_check_mark: Passed - returns customer details for Alla Volkova +``` +Find customer cb0a5340-f9fb-4f49-bd62-9d03518868ff +``` +:white_check_mark: Passed - returns customer details for b2b admin +``` +What information do you have on customer b2badmin@test.com +``` +:white_check_mark: Passed - returns no customer found (since it doesn't exist in test data) + +### 3. Get Product Tool +``` +Show me product details for SKU 566903892 +``` +:x: No endpoint for search product by SKU +``` +Get information about product 08c33cfc9f664426a52fac8882da2df0 +``` +:white_check_mark: Passed - returns product details +``` +What are the details for the printer product 4b729fae613046448aaba7c265bb4f2d? +``` +:white_check_mark: Passed - returns product details for the printer +``` +Find product 47e4aaef9c9e4326924d4a4080f461a5 and show me all its attributes +``` +:white_check_mark: Passed - returns product details + + +### 4. Get Inventory Tool +``` +Check inventory for SKU 566903892 at warehouse e5aea833-dfce-4347-bf38-a479d33dce28 +``` +:x: No endpoint for inventory by SKU +``` +What's the available stock for TSH-002 in location WH002? +``` +:x: +``` +Show me inventory levels for COF-003 across all warehouses +``` +``` +Get inventory status for WID-001 at WH003 +``` + +### 5. Get Shipment Tool +``` +Get shipment details for order CO220518-00001 +``` +:x: there is a search only for the particular order, not for a collection + +``` +Show me the shipment information for order WEB-2024-1002 +``` +``` +Check if order_001 has been shipped +``` +``` +Find shipment tracking for order EXT-001 +``` +:x: didn't test it + +### 6. Get Buyer Tool +``` +Get buyer information for order order_001 +``` +``` +Who is the buyer for order ORD-1001? +``` +``` +Show me the buyer details for order_002 +``` +``` +Find the customer who placed order WEB-2024-1002 +``` + +--- + +## Action Tools Test Prompts + +### 7. Capture Order Tool +``` +Create a new order for customer cust_001 with 2 units of WID-001 shipping to 123 Main St, New York, NY 10001 +``` +``` +Capture an order for sarah.johnson@example.com with 1 TSH-002 and 2 COF-003 items +``` +``` +Place an order for customer cust_002 with product WID-001, quantity 1, shipping to 456 Oak Ave, Los Angeles, CA 90210 +``` + +### 8. Cancel Order Tool +``` +Cancel order order_001 due to customer request +``` +``` +Please cancel order EXT-001 - the customer changed their mind +``` +``` +Cancel order ORD-1001 because of inventory issues +``` + +### 9. Update Order Tool +``` +Update order order_002 to change the quantity of TSH-002 to 3 units +``` +``` +Modify order EXT-001 to ship to 789 Broadway, New York, NY 10002 instead +``` +``` +Update order ORD-1001 with express shipping +``` + +### 10. Return Order Tool +``` +Process a return for order order_003 - customer says the coffee tastes bad +``` +``` +Create a return for order WEB-2024-1002 with reason "damaged during shipping" +``` +``` +Return order order_001 because the headphones don't work +``` + +### 11. Exchange Order Tool +``` +Exchange order order_001 - customer wants TSH-002 instead of WID-001 +``` +``` +Process an exchange for order ORD-1001 - swap the t-shirt for coffee beans +``` +``` +Exchange the items in order_002 for different products +``` + +### 12. Ship Order Tool +``` +Mark order order_001 as shipped with tracking number TRK123456789 +``` +``` +Ship order EXT-001 via FedEx with tracking FDX987654321 +``` +``` +Process shipment for order_002 using UPS tracking 1Z999AA10123456784 +``` + +--- + +## Management Tools Test Prompts + +### 13. Hold Order Tool +``` +Put order order_001 on hold - waiting for payment verification +``` +``` +Hold order EXT-001 due to address verification needed +``` +``` +Place a hold on order_002 for customer service review +``` +``` +Release the hold on order ORD-1001 +``` + +### 14. Split Order Tool +``` +Split order order_002 so that 1 TSH-002 ships immediately and the other ships later +``` +``` +I need to split order_001 into two separate shipments +``` +``` +Divide order WEB-2024-1002 - send 2 coffee bags now and 1 later +``` + +### 15. Reserve Inventory Tool +``` +Reserve 5 units of WID-001 from warehouse WH001 +``` +``` +Hold 10 units of TSH-002 inventory at location WH002 for a special customer +``` +``` +Reserve 3 COF-003 from WH003 for upcoming order +``` +``` +Release the reservation on 2 units of WID-001 at WH001 +``` + +--- + +## Complex Workflow Test Prompts + +### Multi-Tool Operations +``` +Get order order_001, then check inventory for all its items, and finally ship it with tracking ABC123 +``` +``` +Find customer cust_002, show me all their orders, and then check product availability for TSH-002 +``` +``` +Create a new order for john.smith@example.com with 2 WID-001, then put it on hold for payment verification +``` + +### Error Handling Tests +``` +Get order INVALID_ORDER_ID +``` +``` +Cancel an order that doesn't exist: order_999 +``` +``` +Check inventory for a non-existent SKU: FAKE-SKU-001 +``` +``` +Get customer information for unknown@email.com +``` + +### Business Logic Tests +``` +Cancel order order_003 (note: it's already shipped - should fail) +``` +``` +Ship order order_001 twice (should handle duplicate shipment) +``` +``` +Reserve 1000 units of WID-001 (likely exceeds available inventory) +``` + +--- + +## Notes for Testing + +1. The mock adapter includes realistic delays (50-200ms) to simulate network latency +2. There's a 1% error rate configured to occasionally test error handling +3. All timestamps are automatically generated relative to current date +4. Custom fields are preserved and returned in responses +5. The mock data persists during the session but resets on server restart + +## Expected Responses + +- Successful queries should return detailed JSON objects with all fields +- Failed operations should return clear error messages with reasons +- The server logs to stderr, so you'll see debug information in the server console +- All tools validate required fields and will reject invalid requests + +## Troubleshooting + +If a tool doesn't work as expected: +1. Check that the order/customer/product ID exists in the test data +2. Verify the status allows the operation (e.g., can't ship an already shipped order) +3. Look for validation errors in the response +4. Check server logs for detailed error information \ No newline at end of file diff --git a/virtocommerce-adapter/package-lock.json b/virtocommerce-adapter/package-lock.json index 7ca39c5..5e7f2d2 100644 --- a/virtocommerce-adapter/package-lock.json +++ b/virtocommerce-adapter/package-lock.json @@ -27,11 +27,10 @@ } }, "../server": { - "name": "@cof-org/mcp", "version": "1.0.0", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.26.0", + "@modelcontextprotocol/sdk": "^0.5.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "dotenv": "^16.3.1", @@ -65,24 +64,13 @@ "node": ">=18.0.0" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "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/@babel/code-frame": { - "version": "7.27.1", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -91,28 +79,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.0", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.28.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.3", - "@babel/parser": "^7.28.3", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.3", - "@babel/types": "^7.28.2", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -129,19 +119,21 @@ }, "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.28.3", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -151,11 +143,12 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", + "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -167,40 +160,44 @@ }, "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-globals": { "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -210,55 +207,61 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "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", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "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.28.3", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.2" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.3", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.28.2" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -269,8 +272,9 @@ }, "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" }, @@ -280,8 +284,9 @@ }, "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" }, @@ -291,8 +296,9 @@ }, "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" }, @@ -302,8 +308,9 @@ }, "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" }, @@ -315,11 +322,12 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -330,8 +338,9 @@ }, "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" }, @@ -341,8 +350,9 @@ }, "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" }, @@ -351,11 +361,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -366,8 +377,9 @@ }, "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" }, @@ -377,8 +389,9 @@ }, "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" }, @@ -388,8 +401,9 @@ }, "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" }, @@ -399,8 +413,9 @@ }, "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" }, @@ -410,8 +425,9 @@ }, "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" }, @@ -421,8 +437,9 @@ }, "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" }, @@ -432,8 +449,9 @@ }, "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" }, @@ -446,8 +464,9 @@ }, "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" }, @@ -459,11 +478,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -473,29 +493,31 @@ } }, "node_modules/@babel/template": { - "version": "7.27.2", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.28.3", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.3", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.2", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -503,12 +525,13 @@ } }, "node_modules/@babel/types": { - "version": "7.28.2", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -516,19 +539,19 @@ }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true }, "node_modules/@cof-org/mcp": { "resolved": "../server", "link": true }, "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==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, - "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" }, @@ -543,23 +566,21 @@ } }, "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==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, - "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/config-array": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.6", + "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -572,7 +593,6 @@ "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" @@ -583,7 +603,6 @@ "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" }, @@ -592,21 +611,22 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", - "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, - "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, @@ -615,11 +635,10 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", "dev": true, - "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -627,7 +646,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", + "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, @@ -643,7 +662,6 @@ "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" @@ -654,7 +672,6 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, - "license": "MIT", "engines": { "node": ">= 4" } @@ -664,7 +681,6 @@ "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" }, @@ -673,11 +689,10 @@ } }, "node_modules/@eslint/js": { - "version": "9.34.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.34.0.tgz", - "integrity": "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -686,23 +701,21 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.2", + "@eslint/core": "^0.17.0", "levn": "^0.4.1" }, "engines": { @@ -714,45 +727,28 @@ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, - "license": "Apache-2.0", "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" + "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, "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" }, @@ -766,7 +762,6 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, - "license": "Apache-2.0", "engines": { "node": ">=18.18" }, @@ -777,8 +772,9 @@ }, "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", @@ -792,16 +788,18 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, - "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" } }, "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" @@ -811,9 +809,10 @@ } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, - "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -824,8 +823,9 @@ }, "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" }, @@ -835,8 +835,9 @@ }, "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" }, @@ -849,8 +850,9 @@ }, "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" }, @@ -860,24 +862,27 @@ }, "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": "*", @@ -892,8 +897,9 @@ }, "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", @@ -938,8 +944,9 @@ }, "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", @@ -952,8 +959,9 @@ }, "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" @@ -964,8 +972,9 @@ }, "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" }, @@ -975,8 +984,9 @@ }, "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", @@ -991,8 +1001,9 @@ }, "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", @@ -1005,8 +1016,9 @@ }, "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", @@ -1047,8 +1059,9 @@ }, "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" }, @@ -1058,8 +1071,9 @@ }, "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", @@ -1071,8 +1085,9 @@ }, "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", @@ -1085,8 +1100,9 @@ }, "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", @@ -1099,8 +1115,9 @@ }, "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", @@ -1124,8 +1141,9 @@ }, "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", @@ -1140,105 +1158,84 @@ }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, - "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "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.5", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.30", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "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/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "dev": true, - "license": "MIT" + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "dev": true }, "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/@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", @@ -1249,16 +1246,18 @@ }, "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" @@ -1266,8 +1265,9 @@ }, "node_modules/@types/babel__traverse": { "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, - "license": "MIT", "dependencies": { "@babel/types": "^7.28.2" } @@ -1276,34 +1276,37 @@ "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" + "dev": true }, "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", - "dev": true, - "license": "MIT" + "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 }, "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": "*" } @@ -1322,20 +1325,18 @@ "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" + "dev": true }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@types/node": { - "version": "20.19.29", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.29.tgz", - "integrity": "sha512-YrT9ArrGaHForBaCNwFjoqJWmn8G1Pr7+BH/vwyLHciA9qT/wSiuOhxGCT50JA5xLvFBd6PIiGkE3afxcPE1nw==", + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", "dev": true, "dependencies": { "undici-types": "~6.21.0" @@ -1343,38 +1344,39 @@ }, "node_modules/@types/stack-utils": { "version": "2.0.3", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true }, "node_modules/@types/yargs": { - "version": "17.0.33", + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", "dev": true, - "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, "node_modules/@types/yargs-parser": { "version": "21.0.3", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.41.0.tgz", - "integrity": "sha512-8fz6oa6wEKZrhXWro/S3n2eRJqlRcIa6SlDh59FXJ5Wp5XRZ8B9ixpJDcjadHq47hMx0u+HW6SNa6LjJQ6NLtw==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.0.tgz", + "integrity": "sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==", "dev": true, - "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.41.0", - "@typescript-eslint/type-utils": "8.41.0", - "@typescript-eslint/utils": "8.41.0", - "@typescript-eslint/visitor-keys": "8.41.0", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.56.0", + "@typescript-eslint/type-utils": "8.56.0", + "@typescript-eslint/utils": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1384,23 +1386,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.41.0", - "eslint": "^8.57.0 || ^9.0.0", + "@typescript-eslint/parser": "^8.56.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.41.0.tgz", - "integrity": "sha512-gTtSdWX9xiMPA/7MV9STjJOOYtWwIJIYxkQxnSV1U3xcE+mnJSH3f6zI0RYP+ew66WSlZ5ed+h0VCxsvdC1jJg==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.0.tgz", + "integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.41.0", - "@typescript-eslint/types": "8.41.0", - "@typescript-eslint/typescript-estree": "8.41.0", - "@typescript-eslint/visitor-keys": "8.41.0", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.56.0", + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1410,20 +1411,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.41.0.tgz", - "integrity": "sha512-b8V9SdGBQzQdjJ/IO3eDifGpDBJfvrNTp2QD9P2BeqWTGrRibgfgIlBSw6z3b6R7dPzg752tOs4u/7yCLxksSQ==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.0.tgz", + "integrity": "sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.41.0", - "@typescript-eslint/types": "^8.41.0", - "debug": "^4.3.4" + "@typescript-eslint/tsconfig-utils": "^8.56.0", + "@typescript-eslint/types": "^8.56.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1437,14 +1437,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.41.0.tgz", - "integrity": "sha512-n6m05bXn/Cd6DZDGyrpXrELCPVaTnLdPToyhBoFkLIMznRUQUEQdSp96s/pcWSQdqOhrgR1mzJ+yItK7T+WPMQ==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.0.tgz", + "integrity": "sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.41.0", - "@typescript-eslint/visitor-keys": "8.41.0" + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1455,11 +1454,10 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.41.0.tgz", - "integrity": "sha512-TDhxYFPUYRFxFhuU5hTIJk+auzM/wKvWgoNYOPcOf6i4ReYlOoYN8q1dV5kOTjNQNJgzWN3TUUQMtlLOcUgdUw==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.0.tgz", + "integrity": "sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -1472,17 +1470,16 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.41.0.tgz", - "integrity": "sha512-63qt1h91vg3KsjVVonFJWjgSK7pZHSQFKH6uwqxAH9bBrsyRhO6ONoKyXxyVBzG1lJnFAJcKAcxLS54N1ee1OQ==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.0.tgz", + "integrity": "sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.41.0", - "@typescript-eslint/typescript-estree": "8.41.0", - "@typescript-eslint/utils": "8.41.0", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0", + "@typescript-eslint/utils": "8.56.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1492,16 +1489,15 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.41.0.tgz", - "integrity": "sha512-9EwxsWdVqh42afLbHP90n2VdHaWU/oWgbH2P0CfcNfdKL7CuKpwMQGjwev56vWu9cSKU7FWSu6r9zck6CVfnag==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.0.tgz", + "integrity": "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -1511,22 +1507,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.41.0.tgz", - "integrity": "sha512-D43UwUYJmGhuwHfY7MtNKRZMmfd8+p/eNSfFe6tH5mbVDto+VQCayeAt35rOx3Cs6wxD16DQtIKw/YXxt5E0UQ==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.0.tgz", + "integrity": "sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.41.0", - "@typescript-eslint/tsconfig-utils": "8.41.0", - "@typescript-eslint/types": "8.41.0", - "@typescript-eslint/visitor-keys": "8.41.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/project-service": "8.56.0", + "@typescript-eslint/tsconfig-utils": "8.56.0", + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1540,16 +1534,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.41.0.tgz", - "integrity": "sha512-udbCVstxZ5jiPIXrdH+BZWnPatjlYwJuJkDA4Tbo3WyYLh8NvB+h/bKeSZHDOFKfphsZYJQqaFtLeXEqurQn1A==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.0.tgz", + "integrity": "sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==", "dev": true, - "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.41.0", - "@typescript-eslint/types": "8.41.0", - "@typescript-eslint/typescript-estree": "8.41.0" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.0", + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1559,19 +1552,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.41.0.tgz", - "integrity": "sha512-+GeGMebMCy0elMNg67LRNoVnUFPIm37iu5CmHESVx56/9Jsfdpsvbv605DQ81Pi/x11IdKUsS5nzgTYbCQU9fg==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.0.tgz", + "integrity": "sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.41.0", - "eslint-visitor-keys": "^4.2.1" + "@typescript-eslint/types": "8.56.0", + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1582,13 +1574,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.0.tgz", + "integrity": "sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==", "dev": true, - "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -1599,7 +1590,6 @@ "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" }, @@ -1612,7 +1602,6 @@ "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" } @@ -1622,7 +1611,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", @@ -1636,8 +1624,9 @@ }, "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" }, @@ -1648,29 +1637,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "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==", "dev": true, - "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==", "dev": true, - "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -1683,8 +1663,9 @@ }, "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" @@ -1697,15 +1678,13 @@ "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" + "dev": true }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" @@ -1722,7 +1701,6 @@ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", @@ -1745,7 +1723,6 @@ "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", @@ -1767,7 +1744,6 @@ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", @@ -1786,7 +1762,6 @@ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", @@ -1805,7 +1780,6 @@ "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "dev": true, - "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", @@ -1827,21 +1801,20 @@ "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/asynckit": { "version": "0.4.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, - "license": "MIT", "dependencies": { "possible-typed-array-names": "^1.0.0" }, @@ -1853,18 +1826,20 @@ } }, "node_modules/axios": { - "version": "1.11.0", - "license": "MIT", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "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", @@ -1883,8 +1858,9 @@ }, "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", @@ -1898,8 +1874,9 @@ }, "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", @@ -1913,16 +1890,18 @@ }, "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", @@ -1935,8 +1914,9 @@ }, "node_modules/babel-preset-current-node-syntax": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", "dev": true, - "license": "MIT", "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", @@ -1960,8 +1940,9 @@ }, "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" @@ -1975,23 +1956,33 @@ }, "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 + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", "dev": true, - "license": "MIT" + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "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" }, @@ -2000,7 +1991,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.2", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -2016,12 +2009,12 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001733", - "electron-to-chromium": "^1.5.199", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -2032,8 +2025,9 @@ }, "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" }, @@ -2043,23 +2037,24 @@ }, "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-from": { "version": "1.1.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dev": true, - "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", @@ -2075,7 +2070,8 @@ }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -2089,7 +2085,6 @@ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, - "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -2103,22 +2098,26 @@ }, "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.30001735", + "version": "1.0.30001770", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", + "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==", "dev": true, "funding": [ { @@ -2133,13 +2132,13 @@ "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==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2153,14 +2152,17 @@ }, "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": [ { @@ -2168,20 +2170,21 @@ "url": "https://github.com/sponsors/sibiraj-s" } ], - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/cjs-module-lexer": { "version": "1.4.3", - "dev": true, - "license": "MIT" + "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 }, "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", @@ -2193,22 +2196,25 @@ }, "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", - "dev": true, - "license": "MIT" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true }, "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==", "dev": true, - "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -2218,12 +2224,14 @@ }, "node_modules/color-name": { "version": "1.1.4", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/combined-stream": { "version": "1.0.8", - "license": "MIT", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -2233,18 +2241,21 @@ }, "node_modules/concat-map": { "version": "0.0.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "node_modules/convert-source-map": { "version": "2.0.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true }, "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", @@ -2263,8 +2274,9 @@ }, "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", @@ -2279,7 +2291,6 @@ "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", @@ -2297,7 +2308,6 @@ "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", @@ -2315,7 +2325,6 @@ "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -2329,9 +2338,10 @@ } }, "node_modules/debug": { - "version": "4.4.1", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, - "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -2345,9 +2355,10 @@ } }, "node_modules/dedent": { - "version": "1.6.0", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", + "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", "dev": true, - "license": "MIT", "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, @@ -2361,13 +2372,13 @@ "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" + "dev": true }, "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" } @@ -2377,7 +2388,6 @@ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, - "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -2395,7 +2405,6 @@ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, - "license": "MIT", "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", @@ -2410,23 +2419,26 @@ }, "node_modules/delayed-stream": { "version": "1.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "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" } @@ -2436,7 +2448,6 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, @@ -2446,7 +2457,8 @@ }, "node_modules/dunder-proto": { "version": "1.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -2457,14 +2469,16 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.203", - "dev": true, - "license": "ISC" + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "dev": true }, "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" }, @@ -2474,23 +2488,24 @@ }, "node_modules/emoji-regex": { "version": "8.0.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "node_modules/error-ex": { - "version": "1.3.2", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, - "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } }, "node_modules/es-abstract": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", "dev": true, - "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", @@ -2556,21 +2571,24 @@ }, "node_modules/es-define-property": { "version": "1.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "engines": { "node": ">= 0.4" } }, "node_modules/es-errors": { "version": "1.3.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "engines": { "node": ">= 0.4" } }, "node_modules/es-object-atoms": { "version": "1.1.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dependencies": { "es-errors": "^1.3.0" }, @@ -2580,7 +2598,8 @@ }, "node_modules/es-set-tostringtag": { "version": "2.1.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", @@ -2596,7 +2615,6 @@ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", "dev": true, - "license": "MIT", "dependencies": { "hasown": "^2.0.2" }, @@ -2609,7 +2627,6 @@ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "dev": true, - "license": "MIT", "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", @@ -2624,8 +2641,9 @@ }, "node_modules/escalade": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -2635,7 +2653,6 @@ "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" }, @@ -2644,25 +2661,23 @@ } }, "node_modules/eslint": { - "version": "9.34.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.34.0.tgz", - "integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, - "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.1", - "@eslint/core": "^0.15.2", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.34.0", - "@eslint/plugin-kit": "^0.3.5", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", @@ -2709,7 +2724,6 @@ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, - "license": "MIT", "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", @@ -2721,7 +2735,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "license": "MIT", "dependencies": { "ms": "^2.1.1" } @@ -2731,7 +2744,6 @@ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", "dev": true, - "license": "MIT", "dependencies": { "debug": "^3.2.7" }, @@ -2749,7 +2761,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "license": "MIT", "dependencies": { "ms": "^2.1.1" } @@ -2759,7 +2770,6 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, - "license": "MIT", "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -2793,7 +2803,6 @@ "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" @@ -2804,7 +2813,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "license": "MIT", "dependencies": { "ms": "^2.1.1" } @@ -2814,7 +2822,6 @@ "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" }, @@ -2827,7 +2834,6 @@ "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" } @@ -2837,7 +2843,6 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -2854,7 +2859,6 @@ "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" }, @@ -2867,7 +2871,6 @@ "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" @@ -2878,7 +2881,6 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -2891,7 +2893,6 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, - "license": "MIT", "engines": { "node": ">= 4" } @@ -2901,7 +2902,6 @@ "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" }, @@ -2914,7 +2914,6 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", @@ -2932,7 +2931,6 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -2942,8 +2940,9 @@ }, "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" @@ -2953,11 +2952,10 @@ } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -2970,7 +2968,6 @@ "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" }, @@ -2983,7 +2980,6 @@ "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" } @@ -2993,15 +2989,15 @@ "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", @@ -3022,6 +3018,8 @@ }, "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" @@ -3029,8 +3027,9 @@ }, "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", @@ -3046,65 +3045,25 @@ "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" - } + "dev": true }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true }, "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/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" - } + "dev": true }, "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" } @@ -3114,7 +3073,6 @@ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, - "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" }, @@ -3124,8 +3082,9 @@ }, "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" }, @@ -3138,7 +3097,6 @@ "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" @@ -3155,7 +3113,6 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, - "license": "MIT", "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" @@ -3168,18 +3125,18 @@ "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" + "dev": true }, "node_modules/follow-redirects": { "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], - "license": "MIT", "engines": { "node": ">=4.0" }, @@ -3194,7 +3151,6 @@ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, - "license": "MIT", "dependencies": { "is-callable": "^1.2.7" }, @@ -3206,8 +3162,9 @@ } }, "node_modules/form-data": { - "version": "4.0.4", - "license": "MIT", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -3221,13 +3178,16 @@ }, "node_modules/fs.realpath": { "version": "1.0.0", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "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, - "license": "MIT", + "hasInstallScript": true, "optional": true, "os": [ "darwin" @@ -3238,7 +3198,8 @@ }, "node_modules/function-bind": { "version": "1.1.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3248,7 +3209,6 @@ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -3269,30 +3229,41 @@ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, - "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "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==", "dev": true, - "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } }, "node_modules/get-intrinsic": { "version": "1.3.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -3314,15 +3285,17 @@ }, "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", - "license": "MIT", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -3333,8 +3306,9 @@ }, "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" }, @@ -3347,7 +3321,6 @@ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", @@ -3362,8 +3335,10 @@ }, "node_modules/glob": { "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3384,7 +3359,6 @@ "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" }, @@ -3394,8 +3368,9 @@ }, "node_modules/glob/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" @@ -3403,8 +3378,9 @@ }, "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" }, @@ -3417,7 +3393,6 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" }, @@ -3430,7 +3405,6 @@ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dev": true, - "license": "MIT", "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" @@ -3444,7 +3418,8 @@ }, "node_modules/gopd": { "version": "1.2.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "engines": { "node": ">= 0.4" }, @@ -3454,20 +3429,15 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", - "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" + "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 }, "node_modules/handlebars": { "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, - "license": "MIT", "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", @@ -3489,7 +3459,6 @@ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -3499,8 +3468,9 @@ }, "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==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -3510,7 +3480,6 @@ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, - "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" }, @@ -3523,7 +3492,6 @@ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", "dev": true, - "license": "MIT", "dependencies": { "dunder-proto": "^1.0.0" }, @@ -3536,7 +3504,8 @@ }, "node_modules/has-symbols": { "version": "1.1.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "engines": { "node": ">= 0.4" }, @@ -3546,7 +3515,8 @@ }, "node_modules/has-tostringtag": { "version": "1.0.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dependencies": { "has-symbols": "^1.0.3" }, @@ -3559,7 +3529,8 @@ }, "node_modules/hasown": { "version": "2.0.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dependencies": { "function-bind": "^1.1.2" }, @@ -3569,13 +3540,15 @@ }, "node_modules/html-escaper": { "version": "2.0.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true }, "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" } @@ -3585,7 +3558,6 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 4" } @@ -3595,7 +3567,6 @@ "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" @@ -3609,8 +3580,9 @@ }, "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" @@ -3627,16 +3599,19 @@ }, "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" @@ -3644,15 +3619,15 @@ }, "node_modules/inherits": { "version": "2.0.4", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, - "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", @@ -3667,7 +3642,6 @@ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -3682,15 +3656,15 @@ }, "node_modules/is-arrayish": { "version": "0.2.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true }, "node_modules/is-async-function": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", "dev": true, - "license": "MIT", "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", @@ -3710,7 +3684,6 @@ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, - "license": "MIT", "dependencies": { "has-bigints": "^1.0.2" }, @@ -3726,7 +3699,6 @@ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" @@ -3743,7 +3715,6 @@ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -3753,8 +3724,9 @@ }, "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" }, @@ -3770,7 +3742,6 @@ "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", @@ -3788,7 +3759,6 @@ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" @@ -3805,7 +3775,6 @@ "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" } @@ -3815,7 +3784,6 @@ "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.3" }, @@ -3828,29 +3796,31 @@ }, "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==", "dev": true, - "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-generator-function": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", - "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", "dev": true, - "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "get-proto": "^1.0.0", + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" }, @@ -3866,7 +3836,6 @@ "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" }, @@ -3879,7 +3848,6 @@ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -3892,7 +3860,6 @@ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -3902,8 +3869,9 @@ }, "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" } @@ -3913,7 +3881,6 @@ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" @@ -3930,7 +3897,6 @@ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", @@ -3949,7 +3915,6 @@ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -3962,7 +3927,6 @@ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.3" }, @@ -3975,8 +3939,9 @@ }, "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" }, @@ -3989,7 +3954,6 @@ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" @@ -4006,7 +3970,6 @@ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", @@ -4024,7 +3987,6 @@ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, - "license": "MIT", "dependencies": { "which-typed-array": "^1.1.16" }, @@ -4040,7 +4002,6 @@ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -4053,7 +4014,6 @@ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.3" }, @@ -4069,7 +4029,6 @@ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" @@ -4085,26 +4044,28 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/isexe": { "version": "2.0.0", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true }, "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", @@ -4118,8 +4079,9 @@ }, "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", @@ -4131,8 +4093,9 @@ }, "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", @@ -4144,8 +4107,9 @@ }, "node_modules/istanbul-reports": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -4156,8 +4120,9 @@ }, "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", @@ -4181,8 +4146,9 @@ }, "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", @@ -4194,8 +4160,9 @@ }, "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", @@ -4224,8 +4191,9 @@ }, "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", @@ -4256,8 +4224,9 @@ }, "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", @@ -4300,8 +4269,9 @@ }, "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", @@ -4314,8 +4284,9 @@ }, "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" }, @@ -4325,8 +4296,9 @@ }, "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", @@ -4340,8 +4312,9 @@ }, "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", @@ -4356,16 +4329,18 @@ }, "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", @@ -4388,8 +4363,9 @@ }, "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" @@ -4400,8 +4376,9 @@ }, "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", @@ -4414,8 +4391,9 @@ }, "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", @@ -4433,8 +4411,9 @@ }, "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": "*", @@ -4446,8 +4425,9 @@ }, "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" }, @@ -4462,16 +4442,18 @@ }, "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", @@ -4489,8 +4471,9 @@ }, "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" @@ -4501,8 +4484,9 @@ }, "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", @@ -4532,8 +4516,9 @@ }, "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", @@ -4564,8 +4549,9 @@ }, "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", @@ -4594,8 +4580,9 @@ }, "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": "*", @@ -4610,8 +4597,9 @@ }, "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", @@ -4626,8 +4614,9 @@ }, "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" }, @@ -4637,8 +4626,9 @@ }, "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", @@ -4655,8 +4645,9 @@ }, "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", @@ -4669,8 +4660,9 @@ }, "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" }, @@ -4683,15 +4675,15 @@ }, "node_modules/js-tokens": { "version": "4.0.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "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==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, - "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -4701,8 +4693,9 @@ }, "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" }, @@ -4714,32 +4707,31 @@ "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" + "dev": true }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", - "dev": true, - "license": "MIT" + "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 }, "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" + "dev": true }, "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" + "dev": true }, "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" }, @@ -4752,23 +4744,24 @@ "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" } @@ -4778,7 +4771,6 @@ "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" @@ -4789,15 +4781,15 @@ }, "node_modules/lines-and-columns": { "version": "1.2.4", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true }, "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" }, @@ -4810,28 +4802,30 @@ }, "node_modules/lodash.memoize": { "version": "4.1.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true }, "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" + "dev": true }, "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/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" }, @@ -4844,43 +4838,38 @@ }, "node_modules/make-error": { "version": "1.3.6", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true }, "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", - "license": "MIT", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "engines": { "node": ">= 0.4" } }, "node_modules/merge-stream": { "version": "2.0.0", - "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" - } + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true }, "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" @@ -4891,14 +4880,16 @@ }, "node_modules/mime-db": { "version": "1.52.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { "version": "2.1.35", - "license": "MIT", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { "mime-db": "1.52.0" }, @@ -4908,8 +4899,9 @@ }, "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" } @@ -4919,7 +4911,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -4932,49 +4923,57 @@ }, "node_modules/minimist": { "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, - "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/ms": { "version": "2.1.3", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true }, "node_modules/natural-compare": { "version": "1.4.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true }, "node_modules/neo-async": { "version": "2.6.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true }, "node_modules/node-int64": { "version": "0.4.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true }, "node_modules/node-releases": { - "version": "2.0.19", - "dev": true, - "license": "MIT" + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true }, "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" }, @@ -4987,7 +4986,6 @@ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -5000,7 +4998,6 @@ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" } @@ -5010,7 +5007,6 @@ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -5031,7 +5027,6 @@ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -5050,7 +5045,6 @@ "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -5065,7 +5059,6 @@ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -5081,16 +5074,18 @@ }, "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" }, @@ -5106,7 +5101,6 @@ "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", @@ -5124,7 +5118,6 @@ "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", "dev": true, - "license": "MIT", "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", @@ -5139,8 +5132,9 @@ }, "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" }, @@ -5156,7 +5150,6 @@ "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" }, @@ -5169,8 +5162,9 @@ }, "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" } @@ -5180,7 +5174,6 @@ "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" }, @@ -5190,8 +5183,9 @@ }, "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", @@ -5207,42 +5201,48 @@ }, "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", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true }, "node_modules/picocolors": { "version": "1.1.1", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true }, "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" }, @@ -5252,16 +5252,18 @@ }, "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" }, @@ -5271,8 +5273,9 @@ }, "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" @@ -5283,8 +5286,9 @@ }, "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" }, @@ -5294,8 +5298,9 @@ }, "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" }, @@ -5308,8 +5313,9 @@ }, "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" }, @@ -5322,7 +5328,6 @@ "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" } @@ -5332,15 +5337,15 @@ "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", @@ -5352,8 +5357,9 @@ }, "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" }, @@ -5363,8 +5369,9 @@ }, "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" @@ -5375,20 +5382,22 @@ }, "node_modules/proxy-from-env": { "version": "1.1.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "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": [ { @@ -5399,41 +5408,19 @@ "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/react-is": { "version": "18.3.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", @@ -5456,7 +5443,6 @@ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", @@ -5474,18 +5460,20 @@ }, "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==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/resolve": { - "version": "1.22.10", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, - "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -5501,8 +5489,9 @@ }, "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" }, @@ -5512,8 +5501,9 @@ }, "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" } @@ -5523,60 +5513,24 @@ "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/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-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", @@ -5596,7 +5550,6 @@ "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", "dev": true, - "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" @@ -5613,7 +5566,6 @@ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -5627,9 +5579,10 @@ } }, "node_modules/semver": { - "version": "7.7.2", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -5642,7 +5595,6 @@ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, - "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -5660,7 +5612,6 @@ "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, - "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -5676,7 +5627,6 @@ "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", "dev": true, - "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", @@ -5688,8 +5638,9 @@ }, "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" }, @@ -5699,8 +5650,9 @@ }, "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" } @@ -5710,7 +5662,6 @@ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, - "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -5730,7 +5681,6 @@ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "dev": true, - "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" @@ -5747,7 +5697,6 @@ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -5766,7 +5715,6 @@ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -5783,34 +5731,39 @@ }, "node_modules/signal-exit": { "version": "3.0.7", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true }, "node_modules/sisteransi": { "version": "1.0.5", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true }, "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" @@ -5818,13 +5771,15 @@ }, "node_modules/sprintf-js": { "version": "1.0.3", - "dev": true, - "license": "BSD-3-Clause" + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true }, "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" }, @@ -5834,8 +5789,9 @@ }, "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" } @@ -5845,7 +5801,6 @@ "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", "dev": true, - "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" @@ -5856,8 +5811,9 @@ }, "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" @@ -5868,8 +5824,9 @@ }, "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==", "dev": true, - "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -5884,7 +5841,6 @@ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", @@ -5906,7 +5862,6 @@ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", @@ -5925,7 +5880,6 @@ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -5940,8 +5894,9 @@ }, "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==", "dev": true, - "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -5951,24 +5906,27 @@ }, "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" }, @@ -5978,8 +5936,9 @@ }, "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==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -5989,8 +5948,9 @@ }, "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" }, @@ -6000,8 +5960,9 @@ }, "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", @@ -6013,8 +5974,9 @@ }, "node_modules/test-exclude/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" @@ -6022,8 +5984,9 @@ }, "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" }, @@ -6031,15 +5994,62 @@ "node": "*" } }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tmpl": { "version": "1.0.5", - "dev": true, - "license": "BSD-3-Clause" + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true }, "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" }, @@ -6048,11 +6058,10 @@ } }, "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, - "license": "MIT", "engines": { "node": ">=18.12" }, @@ -6061,9 +6070,10 @@ } }, "node_modules/ts-jest": { - "version": "29.4.1", + "version": "29.4.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", + "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", "dev": true, - "license": "MIT", "dependencies": { "bs-logger": "^0.2.6", "fast-json-stable-stringify": "^2.1.0", @@ -6071,7 +6081,7 @@ "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", - "semver": "^7.7.2", + "semver": "^7.7.3", "type-fest": "^4.41.0", "yargs-parser": "^21.1.1" }, @@ -6113,8 +6123,9 @@ }, "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" }, @@ -6127,7 +6138,6 @@ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, - "license": "MIT", "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -6140,7 +6150,6 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, - "license": "MIT", "dependencies": { "minimist": "^1.2.0" }, @@ -6153,7 +6162,6 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } @@ -6163,7 +6171,6 @@ "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" }, @@ -6173,18 +6180,30 @@ }, "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.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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", @@ -6199,7 +6218,6 @@ "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", @@ -6219,7 +6237,6 @@ "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", "dev": true, - "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", @@ -6241,7 +6258,6 @@ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", @@ -6258,9 +6274,10 @@ } }, "node_modules/typescript": { - "version": "5.9.2", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, - "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6270,16 +6287,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.41.0.tgz", - "integrity": "sha512-n66rzs5OBXW3SFSnZHr2T685q1i4ODm2nulFJhMZBotaTavsS8TrI3d7bDlRSs9yWo7HmyWrN9qDu14Qv7Y0Dw==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.0.tgz", + "integrity": "sha512-c7toRLrotJ9oixgdW7liukZpsnq5CZ7PuKztubGYlNppuTqhIoWfhgHo/7EU0v06gS2l/x0i2NEFK1qMIf0rIg==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.41.0", - "@typescript-eslint/parser": "8.41.0", - "@typescript-eslint/typescript-estree": "8.41.0", - "@typescript-eslint/utils": "8.41.0" + "@typescript-eslint/eslint-plugin": "8.56.0", + "@typescript-eslint/parser": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0", + "@typescript-eslint/utils": "8.56.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6289,14 +6305,15 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/uglify-js": { "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, - "license": "BSD-2-Clause", "optional": true, "bin": { "uglifyjs": "bin/uglifyjs" @@ -6310,7 +6327,6 @@ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", @@ -6326,11 +6342,14 @@ }, "node_modules/undici-types": { "version": "6.21.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true }, "node_modules/update-browserslist-db": { - "version": "1.1.3", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -6346,7 +6365,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" @@ -6363,15 +6381,15 @@ "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/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", @@ -6383,16 +6401,18 @@ }, "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/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" }, @@ -6408,7 +6428,6 @@ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", "dev": true, - "license": "MIT", "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", @@ -6428,7 +6447,6 @@ "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", @@ -6456,7 +6474,6 @@ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, - "license": "MIT", "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", @@ -6471,11 +6488,10 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", "dev": true, - "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", @@ -6497,20 +6513,21 @@ "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/wordwrap": { "version": "1.0.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true }, "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==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -6525,13 +6542,15 @@ }, "node_modules/wrappy": { "version": "1.0.2", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true }, "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" @@ -6542,21 +6561,24 @@ }, "node_modules/y18n": { "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, - "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/yallist": { "version": "3.1.1", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true }, "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", @@ -6572,16 +6594,18 @@ }, "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/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" }, From 9001012860d6f9fc66555371c3446f47389ae79f Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Mon, 23 Feb 2026 20:50:14 +0000 Subject: [PATCH 36/51] Add catalogId handling to product service and enhance API client logging --- virtocommerce-adapter/src/adapter.ts | 2 + virtocommerce-adapter/src/models/catalog.ts | 40 +++++++ virtocommerce-adapter/src/models/index.ts | 3 + .../src/services/product.service.ts | 105 ++++++++++++++---- virtocommerce-adapter/src/utils/api-client.ts | 12 +- 5 files changed, 134 insertions(+), 28 deletions(-) diff --git a/virtocommerce-adapter/src/adapter.ts b/virtocommerce-adapter/src/adapter.ts index 1280468..5131c81 100644 --- a/virtocommerce-adapter/src/adapter.ts +++ b/virtocommerce-adapter/src/adapter.ts @@ -280,6 +280,7 @@ export class VirtoCommerceFulfillmentAdapter implements IFulfillmentAdapter { if (options.catalogId) { this.orderService.setCatalogId(options.catalogId); + this.productService.setCatalogId(options.catalogId); } } @@ -290,6 +291,7 @@ export class VirtoCommerceFulfillmentAdapter implements IFulfillmentAdapter { if (storeResponse.success && storeResponse.data?.catalog) { this.options.catalogId = storeResponse.data.catalog; this.orderService.setCatalogId(storeResponse.data.catalog); + this.productService.setCatalogId(storeResponse.data.catalog); console.error(`Resolved catalogId "${storeResponse.data.catalog}" from store "${this.options.workspace}"`); } else { console.error(`Warning: Could not resolve catalogId from store "${this.options.workspace}"`); diff --git a/virtocommerce-adapter/src/models/catalog.ts b/virtocommerce-adapter/src/models/catalog.ts index 3255375..b78bb34 100644 --- a/virtocommerce-adapter/src/models/catalog.ts +++ b/virtocommerce-adapter/src/models/catalog.ts @@ -205,3 +205,43 @@ export interface ProductSearchResult { totalCount?: number; items?: CatalogProduct[]; } + +/** + * List entry search criteria for /api/catalog/listentries + */ +export interface ListEntrySearchCriteria { + keyword?: string; + catalogId?: string; + catalogIds?: string[]; + categoryId?: string; + categoryIds?: string[]; + searchInChildren?: boolean; + searchInVariations?: boolean; + withHidden?: boolean; + responseGroup?: string; + objectIds?: string[]; + skip?: number; + take?: number; +} + +/** + * List entry base - returned by /api/catalog/listentries + */ +export interface ListEntryBase { + id?: string; + type?: string; + code?: string; + name?: string; + imageUrl?: string; + isActive?: boolean; + catalogId?: string; +} + +/** + * List entry search result + */ +export interface ListEntrySearchResult { + totalCount?: number; + results?: ListEntryBase[]; + listEntries?: ListEntryBase[]; +} diff --git a/virtocommerce-adapter/src/models/index.ts b/virtocommerce-adapter/src/models/index.ts index f84806b..f40bf9e 100644 --- a/virtocommerce-adapter/src/models/index.ts +++ b/virtocommerce-adapter/src/models/index.ts @@ -113,6 +113,9 @@ export type { CategoryLink, ProductSearchCriteria, ProductSearchResult, + ListEntrySearchCriteria, + ListEntryBase, + ListEntrySearchResult, } from './catalog.js'; // Inventory models diff --git a/virtocommerce-adapter/src/services/product.service.ts b/virtocommerce-adapter/src/services/product.service.ts index e29581f..34dfffe 100644 --- a/virtocommerce-adapter/src/services/product.service.ts +++ b/virtocommerce-adapter/src/services/product.service.ts @@ -13,9 +13,10 @@ import type { } from '@cof-org/mcp'; import type { ProductSearchResult, - CatalogProduct, InventorySearchResult, InventorySearchCriteria, + ListEntrySearchResult, + ListEntrySearchCriteria, } from '../models/index.js'; import { BaseService } from './base.service.js'; import { ProductTransformer } from '../transformers/product.transformer.js'; @@ -28,6 +29,7 @@ import { ApiClient } from '../utils/api-client.js'; export class ProductService extends BaseService { private transformer: ProductTransformer; + private catalogId?: string; constructor(client: ApiClient, tenantId: string = 'default-workspace') { super(client); @@ -38,8 +40,17 @@ export class ProductService extends BaseService { this.transformer.setTenantId(tenantId); } + setCatalogId(catalogId: string): void { + this.catalogId = catalogId; + } + async getProducts(input: GetProductsInput): Promise> { try { + // Use products-by-codes endpoint for SKU search when catalogId is available + if (input.skus?.length && this.catalogId) { + return this.getProductsByCodes(input.skus); + } + const searchCriteria = mapProductFiltersToSearchCriteria(input); const response = await this.client.post( @@ -65,6 +76,62 @@ export class ProductService extends BaseService { } } + private async getProductsByCodes(skus: string[]): Promise> { + // Step 1: Resolve SKUs to product IDs via listentries + const productIds = await this.resolveSkusToIds(skus); + + if (!productIds.length) { + return this.success<{ products: Product[] }>({ products: [] }); + } + + // Step 2: Fetch full products by IDs + const response = await this.client.post( + '/api/catalog/search/products', + { + objectIds: productIds, + responseGroup: 'ItemInfo,ItemAssets,ItemProperties,Links,Variations,Seo', + take: productIds.length, + } + ); + + if (!response.success) { + return this.failure<{ products: Product[] }>( + 'Failed to fetch products by codes', + response.error ?? response + ); + } + + const results = response.data?.items ?? []; + const products = this.transformer.fromCatalogProducts(results); + return this.success<{ products: Product[] }>({ products }); + } + + /** + * Resolve SKU codes to product IDs via /api/catalog/listentries + */ + private async resolveSkusToIds(skus: string[]): Promise { + const criteria: ListEntrySearchCriteria = { + keyword: `code:${skus.join(',')}`, + catalogId: this.catalogId, + searchInVariations: true, + take: skus.length, + }; + + const response = await this.client.post( + '/api/catalog/listentries', + criteria + ); + + if (!response.success || !response.data) { + return []; + } + + const entries = response.data.results ?? response.data.listEntries ?? []; + return entries + .filter((entry) => entry.id && entry.type?.toLowerCase() === 'product') + .map((entry) => entry.id!); + } + async getProductVariants( input: GetProductVariantsInput ): Promise> { @@ -98,28 +165,26 @@ export class ProductService extends BaseService { input: GetInventoryInput ): Promise> { try { - // Step 1: Resolve SKUs to product IDs via catalog search + // Step 1: Resolve SKUs to product IDs via listentries, then fetch product details + const resolvedIds = this.catalogId + ? await this.resolveSkusToIds(input.skus) + : []; + + if (!resolvedIds.length) { + return this.success<{ inventory: InventoryItem[] }>({ inventory: [] }); + } + + // Fetch product details to build SKU map const catalogResponse = await this.client.post( '/api/catalog/search/products', { - codes: input.skus, + objectIds: resolvedIds, responseGroup: 'ItemInfo', - searchInVariations: true, - take: input.skus.length, + take: resolvedIds.length, } ); - if (!catalogResponse.success) { - return this.failure<{ inventory: InventoryItem[] }>( - 'Failed to resolve product SKUs', - catalogResponse.error ?? catalogResponse - ); - } - - const products = catalogResponse.data?.items ?? []; - if (!products.length) { - return this.success<{ inventory: InventoryItem[] }>({ inventory: [] }); - } + const products = catalogResponse.success ? (catalogResponse.data?.items ?? []) : []; // Build SKU map: productId → SKU code const skuMap = new Map(); @@ -129,14 +194,10 @@ export class ProductService extends BaseService { } } - const productIds = products - .map((p: CatalogProduct) => p.id) - .filter((id): id is string => !!id); - // Step 2: Query inventory for resolved product IDs const inventoryCriteria: InventorySearchCriteria = { - productIds, - take: productIds.length * 10, // Allow multiple locations per product + productIds: resolvedIds, + take: resolvedIds.length * 10, // Allow multiple locations per product }; if (input.locationIds?.length) { diff --git a/virtocommerce-adapter/src/utils/api-client.ts b/virtocommerce-adapter/src/utils/api-client.ts index a7c0ac4..527a53d 100644 --- a/virtocommerce-adapter/src/utils/api-client.ts +++ b/virtocommerce-adapter/src/utils/api-client.ts @@ -53,7 +53,7 @@ export class ApiClient { (request) => { if (this.debugMode && request.url !== '/health') { const headers = { ...request.headers } as Record; - console.error('[API Request]', { + console.error('[API Request]', JSON.stringify({ method: request.method?.toUpperCase(), baseURL: request.baseURL, url: request.url, @@ -61,7 +61,7 @@ export class ApiClient { headers, params: request.params, data: request.data, - }); + }, null, 2)); } return request; }, @@ -77,24 +77,24 @@ export class ApiClient { this.client.interceptors.response.use( (response) => { if (this.debugMode && response.config.url !== '/health') { - console.error('[API Response]', { + console.error('[API Response]', JSON.stringify({ status: response.status, statusText: response.statusText, url: response.config.url, data: response.data, - }); + }, null, 2)); } return response; }, (error) => { if (this.debugMode) { - console.error('[API Response Error]', { + console.error('[API Response Error]', JSON.stringify({ message: error.message, status: error.response?.status, statusText: error.response?.statusText, url: error.config?.url, data: error.response?.data, - }); + }, null, 2)); } return Promise.reject(error); } From c19463920decc87f454a61397f8f7443c1e41ee0 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Tue, 24 Feb 2026 10:26:19 +0000 Subject: [PATCH 37/51] get shipments (fulfillments) --- .../VIRTO_MCP_TEST_PROMPTS.md | 42 +++++++++++-------- .../src/mappers/filter.mappers.ts | 5 ++- virtocommerce-adapter/src/models/search.ts | 16 +++---- .../src/services/fulfillment.service.ts | 23 ++++++++++ 4 files changed, 60 insertions(+), 26 deletions(-) diff --git a/virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md b/virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md index 03aa14b..b4c3683 100644 --- a/virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md +++ b/virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md @@ -73,7 +73,7 @@ What information do you have on customer b2badmin@test.com ``` Show me product details for SKU 566903892 ``` -:x: No endpoint for search product by SKU +:white_check_mark: Passed - returns product details for 08c33cfc9f664426a52fac8882da2df0 ``` Get information about product 08c33cfc9f664426a52fac8882da2df0 ``` @@ -90,50 +90,58 @@ Find product 47e4aaef9c9e4326924d4a4080f461a5 and show me all its attributes ### 4. Get Inventory Tool ``` -Check inventory for SKU 566903892 at warehouse e5aea833-dfce-4347-bf38-a479d33dce28 +Check inventory for SKU 566903892 at warehouse vendor-fulfillment ``` -:x: No endpoint for inventory by SKU +:white_check_mark: Passed - returns inventory levels for SKU 566903892 at vendor-fulfillment ``` -What's the available stock for TSH-002 in location WH002? +What's the available stock for 566903892 in location vendor-fulfillment? ``` -:x: +:white_check_mark: Passed - returns available stock for SKU 566903892 in location vendor-fulfillment ``` -Show me inventory levels for COF-003 across all warehouses +Show me inventory levels for 566903892 across all warehouses ``` +:white_check_mark: Passed - returns inventory levels for SKU 566903892 across all warehouses ``` -Get inventory status for WID-001 at WH003 +Get inventory status for 566903892 at vendor-fulfillment ``` +:white_check_mark: Passed - returns inventory status for SKU 566903892 at vendor-fulfillment + ### 5. Get Shipment Tool ``` Get shipment details for order CO220518-00001 ``` -:x: there is a search only for the particular order, not for a collection +:white_check_mark: Passed - returns shipment details for order CO220518-00001 ``` -Show me the shipment information for order WEB-2024-1002 +Show me the shipment information for order CO220518-00001 ``` +:white_check_mark: Passed - returns shipment information for order CO220518-00001 ``` -Check if order_001 has been shipped +Check if CO220518-00001 has been shipped ``` +:white_check_mark: Passed - returns shipment status ("not shipped") ``` -Find shipment tracking for order EXT-001 +Find shipment tracking for order CO220518-00001 ``` -:x: didn't test it +:white_check_mark: Passwed ### 6. Get Buyer Tool ``` -Get buyer information for order order_001 -``` +Get buyer information for order CO220518-00001 ``` -Who is the buyer for order ORD-1001? +:white_check_mark: Passed - returns buyer information for order CO220518-00001 ``` +Who is the buyer for order CO220518-00001? ``` -Show me the buyer details for order_002 +:white_check_mark: Passed - returns buyer name and contact info for order CO220518-00001 +Show me the buyer details for order CO220518-00001 ``` +:white_check_mark: Passed - returns buyer details for order CO220518-00001 ``` -Find the customer who placed order WEB-2024-1002 +Find the customer who placed order CO220518-00001 ``` +:white_check_mark: Passed - returns customer details for b2b admin --- diff --git a/virtocommerce-adapter/src/mappers/filter.mappers.ts b/virtocommerce-adapter/src/mappers/filter.mappers.ts index 54c972f..19a4d67 100644 --- a/virtocommerce-adapter/src/mappers/filter.mappers.ts +++ b/virtocommerce-adapter/src/mappers/filter.mappers.ts @@ -237,11 +237,12 @@ export function mapFulfillmentFiltersToSearchCriteria(input: GetFulfillmentsInpu }; if (input.ids?.length) { - criteria.objectIds = input.ids; + criteria.ids = input.ids; } + // VC ShipmentSearchCriteria supports single orderId, not an array if (input.orderIds?.length) { - criteria.orderIds = input.orderIds; + criteria.orderId = input.orderIds[0]; } if (input.createdAtMin) { diff --git a/virtocommerce-adapter/src/models/search.ts b/virtocommerce-adapter/src/models/search.ts index d885633..0100c91 100644 --- a/virtocommerce-adapter/src/models/search.ts +++ b/virtocommerce-adapter/src/models/search.ts @@ -160,19 +160,21 @@ export interface PaymentSearchCriteria extends SearchCriteriaBase { * Shipment search criteria */ export interface ShipmentSearchCriteria extends SearchCriteriaBase { - orderNumber?: string; - orderNumbers?: string[]; + // From OrderOperationSearchCriteriaBase + ids?: string[]; + outerIds?: string[]; + hasParentOperation?: boolean; + parentOperationId?: string; + numbers?: string[]; + number?: string; + // ShipmentSearchCriteria own fields orderId?: string; - orderIds?: string[]; + orderNumber?: string; status?: string; statuses?: string[]; - customerId?: string; - customerIds?: string[]; storeIds?: string[]; fulfillmentCenterId?: string; - fulfillmentCenterIds?: string[]; employeeId?: string; - employeeIds?: string[]; startDate?: string; endDate?: string; shipmentMethodCode?: string; diff --git a/virtocommerce-adapter/src/services/fulfillment.service.ts b/virtocommerce-adapter/src/services/fulfillment.service.ts index a8bfd4b..4af7fa3 100644 --- a/virtocommerce-adapter/src/services/fulfillment.service.ts +++ b/virtocommerce-adapter/src/services/fulfillment.service.ts @@ -107,6 +107,29 @@ export class FulfillmentService extends BaseService { input: GetFulfillmentsInput ): Promise> { try { + // VC ShipmentSearchCriteria supports single orderId only, + // so we run a separate request per orderId and merge results + if (input.orderIds && input.orderIds.length > 1) { + const allShipments: Shipment[] = []; + + for (const orderId of input.orderIds) { + const perOrderInput = { ...input, orderIds: [orderId] }; + const searchCriteria = mapFulfillmentFiltersToSearchCriteria(perOrderInput); + + const response = await this.client.post<{ results?: Shipment[]; totalCount?: number }>( + '/api/order/shipments/search', + searchCriteria + ); + + if (response.success) { + allShipments.push(...(response.data?.results ?? [])); + } + } + + const fulfillments = this.transformer.fromShipments(allShipments); + return this.success<{ fulfillments: Fulfillment[] }>({ fulfillments }); + } + const searchCriteria = mapFulfillmentFiltersToSearchCriteria(input); const response = await this.client.post<{ results?: Shipment[]; totalCount?: number }>( From b99846e0f17a89179e38da5165c3e75b0c985230 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Tue, 24 Feb 2026 10:58:16 +0000 Subject: [PATCH 38/51] use workspace as storeId in requests --- virtocommerce-adapter/src/adapter.ts | 3 ++- .../src/services/fulfillment.service.ts | 18 +++++++++++++++++- .../src/services/order.service.ts | 8 ++++++++ .../src/services/product.service.ts | 10 ++++++++++ 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/virtocommerce-adapter/src/adapter.ts b/virtocommerce-adapter/src/adapter.ts index 5131c81..f503cd9 100644 --- a/virtocommerce-adapter/src/adapter.ts +++ b/virtocommerce-adapter/src/adapter.ts @@ -83,7 +83,7 @@ export class VirtoCommerceFulfillmentAdapter implements IFulfillmentAdapter { // Initialize services this.orderService = new OrderService(this.client, tenantId, this.options.workspace); this.customerService = new CustomerService(this.client, tenantId); - this.fulfillmentService = new FulfillmentService(this.client, tenantId); + this.fulfillmentService = new FulfillmentService(this.client, tenantId, this.options.workspace); this.productService = new ProductService(this.client, tenantId); this.returnService = new ReturnService(this.client); } @@ -276,6 +276,7 @@ export class VirtoCommerceFulfillmentAdapter implements IFulfillmentAdapter { if (options.workspace) { this.orderService.setWorkspace(options.workspace); + this.fulfillmentService.setWorkspace(options.workspace); } if (options.catalogId) { diff --git a/virtocommerce-adapter/src/services/fulfillment.service.ts b/virtocommerce-adapter/src/services/fulfillment.service.ts index 4af7fa3..bd19fc3 100644 --- a/virtocommerce-adapter/src/services/fulfillment.service.ts +++ b/virtocommerce-adapter/src/services/fulfillment.service.ts @@ -17,9 +17,11 @@ import { ApiClient } from '../utils/api-client.js'; export class FulfillmentService extends BaseService { private transformer: FulfillmentTransformer; + private workspace?: string; - constructor(client: ApiClient, tenantId: string = 'default-workspace') { + constructor(client: ApiClient, tenantId: string = 'default-workspace', workspace?: string) { super(client); + this.workspace = workspace; this.transformer = new FulfillmentTransformer(tenantId); } @@ -27,6 +29,10 @@ export class FulfillmentService extends BaseService { this.transformer.setTenantId(tenantId); } + setWorkspace(workspace: string): void { + this.workspace = workspace; + } + async fulfillOrder( input: FulfillOrderInput ): Promise> { @@ -116,6 +122,11 @@ export class FulfillmentService extends BaseService { const perOrderInput = { ...input, orderIds: [orderId] }; const searchCriteria = mapFulfillmentFiltersToSearchCriteria(perOrderInput); + // Filter by store when workspace is configured + if (this.workspace) { + searchCriteria.storeIds = [this.workspace]; + } + const response = await this.client.post<{ results?: Shipment[]; totalCount?: number }>( '/api/order/shipments/search', searchCriteria @@ -132,6 +143,11 @@ export class FulfillmentService extends BaseService { const searchCriteria = mapFulfillmentFiltersToSearchCriteria(input); + // Filter by store when workspace is configured + if (this.workspace) { + searchCriteria.storeIds = [this.workspace]; + } + const response = await this.client.post<{ results?: Shipment[]; totalCount?: number }>( '/api/order/shipments/search', searchCriteria diff --git a/virtocommerce-adapter/src/services/order.service.ts b/virtocommerce-adapter/src/services/order.service.ts index aa655fc..8874f55 100644 --- a/virtocommerce-adapter/src/services/order.service.ts +++ b/virtocommerce-adapter/src/services/order.service.ts @@ -26,9 +26,11 @@ import { ApiClient } from '../utils/api-client.js'; export class OrderService extends BaseService { private transformer: OrderTransformer; private customerService: CustomerService; + private workspace?: string; constructor(client: ApiClient, tenantId: string = 'default-workspace', workspace?: string) { super(client); + this.workspace = workspace; this.transformer = new OrderTransformer(tenantId, workspace); this.customerService = new CustomerService(client, tenantId); } @@ -39,6 +41,7 @@ export class OrderService extends BaseService { } setWorkspace(workspace: string): void { + this.workspace = workspace; this.transformer.setWorkspace(workspace); } @@ -190,6 +193,11 @@ export class OrderService extends BaseService { // Build VirtoCommerce search criteria from input const searchCriteria: CustomerOrderSearchCriteria = mapOrderFiltersToSearchCriteria(input); + // Filter by store when workspace is configured + if (this.workspace) { + searchCriteria.storeIds = [this.workspace]; + } + const response = await this.client.post( '/api/order/customerOrders/search', searchCriteria diff --git a/virtocommerce-adapter/src/services/product.service.ts b/virtocommerce-adapter/src/services/product.service.ts index 34dfffe..09bce3b 100644 --- a/virtocommerce-adapter/src/services/product.service.ts +++ b/virtocommerce-adapter/src/services/product.service.ts @@ -53,6 +53,11 @@ export class ProductService extends BaseService { const searchCriteria = mapProductFiltersToSearchCriteria(input); + // Filter by catalog when catalogId is configured + if (this.catalogId) { + searchCriteria.catalogIds = [this.catalogId]; + } + const response = await this.client.post( '/api/catalog/search/products', searchCriteria @@ -138,6 +143,11 @@ export class ProductService extends BaseService { try { const searchCriteria = mapProductVariantFiltersToSearchCriteria(input); + // Filter by catalog when catalogId is configured + if (this.catalogId) { + searchCriteria.catalogIds = [this.catalogId]; + } + const response = await this.client.post( '/api/catalog/search/products', searchCriteria From d4fee741f8478ce09d7927de475451e40a0d8972 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Tue, 24 Feb 2026 11:40:33 +0000 Subject: [PATCH 39/51] Update test prompts with accurate product SKUs and customer references --- virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md b/virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md index b4c3683..850d7a7 100644 --- a/virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md +++ b/virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md @@ -22,12 +22,12 @@ Copy and paste these prompts into Claude Desktop to test each MCP tool. The mock - **fa90d0b3-4bf5-4fc8-8c7c-787cafc4c678**: Alla Volkova (allagrvolkova@mail.ru) ### Available Products/SKUs -- **prod_001** / WID-001: Wireless Bluetooth Headphones ($199.99) -- **prod_002** / TSH-002: Organic Cotton T-Shirt ($29.99) -- **prod_003** / COF-003: Premium Coffee Beans ($24.99) +- **47e4aaef9c9e4326924d4a4080f461a5** / 564698896: Wireless Bluetooth Headphones ($199.99) +- **08c33cfc9f664426a52fac8882da2df0** / 566903892: Organic Cotton T-Shirt ($29.99) +- **6ee23bd045a549d785d9abc7e2a61b02** / 552223579: Premium Coffee Beans ($24.99) ### Warehouse Locations -- WH001, WH002, WH003 (all have inventory for each product) +- vendor-fulfillment --- @@ -149,7 +149,7 @@ Find the customer who placed order CO220518-00001 ### 7. Capture Order Tool ``` -Create a new order for customer cust_001 with 2 units of WID-001 shipping to 123 Main St, New York, NY 10001 +Create a new order for customer cb0a5340-f9fb-4f49-bd62-9d03518868ff with 2 units of 47e4aaef9c9e4326924d4a4080f461a5 shipping to 123 Main St, New York, NY 10001 ``` ``` Capture an order for sarah.johnson@example.com with 1 TSH-002 and 2 COF-003 items From 3ddd608fed6ad17a83e6c67129b8220855992208 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Tue, 24 Feb 2026 12:29:06 +0000 Subject: [PATCH 40/51] Create order by request through mcp server --- .claude/settings.local.json | 6 +++- virtocommerce-adapter/src/adapter.ts | 3 ++ .../src/services/order.service.ts | 17 ++++++++++- .../src/services/product.service.ts | 28 +++++++++++++++---- .../src/transformers/order.transformer.ts | 8 ++++-- 5 files changed, 52 insertions(+), 10 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 6305934..7d6ed52 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -44,7 +44,11 @@ "Bash(powershell.exe -Command \"Get-ChildItem ''E:\\\\Develops\\\\VirtoWay\\\\projects\\\\tasks\\\\4464\\\\vc-module-order\\\\src'' -Recurse -Filter ''*.cs'' | Select-Object -ExpandProperty FullName | Out-String\")", "Bash(powershell.exe -Command \"Get-ChildItem ''E:\\\\Develops\\\\VirtoWay\\\\projects\\\\tasks\\\\4464\\\\vc-module-order\\\\src'' -Recurse -Filter ''*.cs'' | Select-Object -First 50 -ExpandProperty FullName\")", "Bash(powershell.exe -Command:*)", - "mcp__plugin_serena_serena__insert_after_symbol" + "mcp__plugin_serena_serena__insert_after_symbol", + "mcp__plugin_context7_context7__resolve-library-id", + "mcp__plugin_context7_context7__query-docs", + "Bash(grep:*)", + "Bash(find:*)" ] } } diff --git a/virtocommerce-adapter/src/adapter.ts b/virtocommerce-adapter/src/adapter.ts index f503cd9..87511e7 100644 --- a/virtocommerce-adapter/src/adapter.ts +++ b/virtocommerce-adapter/src/adapter.ts @@ -86,6 +86,9 @@ export class VirtoCommerceFulfillmentAdapter implements IFulfillmentAdapter { this.fulfillmentService = new FulfillmentService(this.client, tenantId, this.options.workspace); this.productService = new ProductService(this.client, tenantId); this.returnService = new ReturnService(this.client); + + // Wire cross-service dependencies + this.orderService.setProductService(this.productService); } // Lifecycle methods diff --git a/virtocommerce-adapter/src/services/order.service.ts b/virtocommerce-adapter/src/services/order.service.ts index 8874f55..f8ad61e 100644 --- a/virtocommerce-adapter/src/services/order.service.ts +++ b/virtocommerce-adapter/src/services/order.service.ts @@ -22,10 +22,12 @@ import { CustomerService } from './customer.service.js'; import { mapOrderFiltersToSearchCriteria } from '../mappers/filter.mappers.js'; import { getErrorMessage } from '../utils/type-guards.js'; import { ApiClient } from '../utils/api-client.js'; +import { ProductService } from './product.service.js'; export class OrderService extends BaseService { private transformer: OrderTransformer; private customerService: CustomerService; + private productService?: ProductService; private workspace?: string; constructor(client: ApiClient, tenantId: string = 'default-workspace', workspace?: string) { @@ -35,6 +37,10 @@ export class OrderService extends BaseService { this.customerService = new CustomerService(client, tenantId); } + setProductService(productService: ProductService): void { + this.productService = productService; + } + setTenantId(tenantId: string): void { this.transformer.setTenantId(tenantId); this.customerService.setTenantId(tenantId); @@ -51,7 +57,15 @@ export class OrderService extends BaseService { async createSalesOrder(input: CreateSalesOrderInput): Promise { try { - const payload = this.transformer.fromCreateSalesOrderInput(input); + // Resolve SKUs to product IDs before building the payload + const skus = input.order?.lineItems?.map((li) => li.sku).filter(Boolean) as string[] ?? []; + const skuProductIdMap = skus.length && this.productService + ? await this.productService.resolveSkuProductIdMap(skus) + : new Map(); + + console.error(`[OrderService] Resolved SKUs to productIds: ${JSON.stringify(Object.fromEntries(skuProductIdMap))}`); + + const payload = this.transformer.fromCreateSalesOrderInput(input, skuProductIdMap); const response = await this.client.post('/api/order/customerOrders', payload); if (!response.success || !response.data) { @@ -224,4 +238,5 @@ export class OrderService extends BaseService { return this.failure<{ orders: Order[] }>(`Order lookup failed: ${getErrorMessage(error)}`, error); } } + } diff --git a/virtocommerce-adapter/src/services/product.service.ts b/virtocommerce-adapter/src/services/product.service.ts index 09bce3b..5bd7b7a 100644 --- a/virtocommerce-adapter/src/services/product.service.ts +++ b/virtocommerce-adapter/src/services/product.service.ts @@ -112,9 +112,13 @@ export class ProductService extends BaseService { } /** - * Resolve SKU codes to product IDs via /api/catalog/listentries + * Resolve SKU codes to a Map of code → catalog product/variation ID + * via /api/catalog/listentries. Public so other services (e.g. OrderService) + * can resolve SKUs to productIds when creating orders. */ - private async resolveSkusToIds(skus: string[]): Promise { + async resolveSkuProductIdMap(skus: string[]): Promise> { + const map = new Map(); + const criteria: ListEntrySearchCriteria = { keyword: `code:${skus.join(',')}`, catalogId: this.catalogId, @@ -128,13 +132,25 @@ export class ProductService extends BaseService { ); if (!response.success || !response.data) { - return []; + return map; } const entries = response.data.results ?? response.data.listEntries ?? []; - return entries - .filter((entry) => entry.id && entry.type?.toLowerCase() === 'product') - .map((entry) => entry.id!); + for (const entry of entries) { + if (entry.id && entry.code && entry.type?.toLowerCase() === 'product') { + map.set(entry.code, entry.id); + } + } + + return map; + } + + /** + * Resolve SKU codes to product IDs via /api/catalog/listentries + */ + private async resolveSkusToIds(skus: string[]): Promise { + const map = await this.resolveSkuProductIdMap(skus); + return Array.from(map.values()); } async getProductVariants( diff --git a/virtocommerce-adapter/src/transformers/order.transformer.ts b/virtocommerce-adapter/src/transformers/order.transformer.ts index e9fa275..4b6bc0d 100644 --- a/virtocommerce-adapter/src/transformers/order.transformer.ts +++ b/virtocommerce-adapter/src/transformers/order.transformer.ts @@ -94,7 +94,10 @@ export class OrderTransformer extends BaseTransformer { /** * Transform CreateSalesOrderInput to API payload */ - fromCreateSalesOrderInput(input: CreateSalesOrderInput): CustomerOrder { + fromCreateSalesOrderInput( + input: CreateSalesOrderInput, + skuProductIdMap?: Map + ): CustomerOrder { const order = input.order; if (!order) { return {}; @@ -113,6 +116,7 @@ export class OrderTransformer extends BaseTransformer { const items: LineItem[] = order.lineItems?.map((item) => ({ + productId: skuProductIdMap?.get(item.sku), sku: item.sku, name: item.name, quantity: item.quantity ?? 0, @@ -204,7 +208,7 @@ export class OrderTransformer extends BaseTransformer { if (lineItems?.length) { updated.items = (updated.items ?? []).map((item) => { const patch = lineItems.find((li) => li.sku === item.sku); - if (!patch) return item; + if (!patch) { return item; } return { ...item, quantity: patch.quantity ?? item.quantity, From 0137335da70c8df4b1d692bde8f75e1cacaea435 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Wed, 25 Feb 2026 12:22:38 +0000 Subject: [PATCH 41/51] Enhance product pricing handling in transformers and models, adding tax support and updating test prompts --- .../VIRTO_MCP_TEST_PROMPTS.md | 9 ++-- .../src/mappers/filter.mappers.ts | 2 +- virtocommerce-adapter/src/models/catalog.ts | 19 +++++++ virtocommerce-adapter/src/models/index.ts | 1 + .../src/transformers/product.transformer.ts | 49 +++++++++++++++++-- 5 files changed, 72 insertions(+), 8 deletions(-) diff --git a/virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md b/virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md index 850d7a7..0fafe2f 100644 --- a/virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md +++ b/virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md @@ -22,9 +22,9 @@ Copy and paste these prompts into Claude Desktop to test each MCP tool. The mock - **fa90d0b3-4bf5-4fc8-8c7c-787cafc4c678**: Alla Volkova (allagrvolkova@mail.ru) ### Available Products/SKUs -- **47e4aaef9c9e4326924d4a4080f461a5** / 564698896: Wireless Bluetooth Headphones ($199.99) -- **08c33cfc9f664426a52fac8882da2df0** / 566903892: Organic Cotton T-Shirt ($29.99) -- **6ee23bd045a549d785d9abc7e2a61b02** / 552223579: Premium Coffee Beans ($24.99) +- **47e4aaef9c9e4326924d4a4080f461a5** / 564698896: Brother MFC-L6700DW Wireless Monochrome All-in-One Laser Printer, Copy/Fax/Print/Scan, $539 +- **08c33cfc9f664426a52fac8882da2df0** / 566903892: Canon Imageclass WiFi MF232W Monochrome Laser Printer/Scanner/Copier, $189 +- **6ee23bd045a549d785d9abc7e2a61b02** / 552223579: HP LaserJet Pro MFP M127fn Multifunction Laser Printer, Copy/Fax/Print/Scan, $315 ### Warehouse Locations - vendor-fulfillment @@ -151,8 +151,9 @@ Find the customer who placed order CO220518-00001 ``` Create a new order for customer cb0a5340-f9fb-4f49-bd62-9d03518868ff with 2 units of 47e4aaef9c9e4326924d4a4080f461a5 shipping to 123 Main St, New York, NY 10001 ``` +:white_check_mark: Passed - creates new order for b2b admin with specified products and shipping address ``` -Capture an order for sarah.johnson@example.com with 1 TSH-002 and 2 COF-003 items +Capture an order for allagrvolkova@mail.ru with 1 566903892 and 2 552223579 items ``` ``` Place an order for customer cust_002 with product WID-001, quantity 1, shipping to 456 Oak Ave, Los Angeles, CA 90210 diff --git a/virtocommerce-adapter/src/mappers/filter.mappers.ts b/virtocommerce-adapter/src/mappers/filter.mappers.ts index 19a4d67..866c162 100644 --- a/virtocommerce-adapter/src/mappers/filter.mappers.ts +++ b/virtocommerce-adapter/src/mappers/filter.mappers.ts @@ -143,7 +143,7 @@ export function mapProductVariantFiltersToSearchCriteria(input: GetProductVarian const hasSkus = !!input.skus?.length; const criteria: ProductSearchCriteria = { - responseGroup: 'ItemInfo,ItemAssets,ItemProperties,Variations', + responseGroup: 'ItemInfo,ItemAssets,ItemProperties,Variations,WithPrices', }; if (hasProductIds && !hasVariantIds && !hasSkus) { diff --git a/virtocommerce-adapter/src/models/catalog.ts b/virtocommerce-adapter/src/models/catalog.ts index b78bb34..14755a9 100644 --- a/virtocommerce-adapter/src/models/catalog.ts +++ b/virtocommerce-adapter/src/models/catalog.ts @@ -58,6 +58,25 @@ export interface CatalogProduct extends AuditableEntity, HasOuterId { associations?: ProductAssociation[]; links?: CategoryLink[]; dynamicProperties?: DynamicObjectProperty[]; + + // Pricing (available when responseGroup includes 'WithPrices') + prices?: ProductPrice[]; + + // Tax + taxType?: string; +} + +/** + * Product price entry from VirtoCommerce Pricing module. + * Returned when the `WithPrices` response group is requested. + */ +export interface ProductPrice { + productId?: string; + pricelistId?: string; + currency?: string; + list?: number; + sale?: number; + minQuantity?: number; } /** diff --git a/virtocommerce-adapter/src/models/index.ts b/virtocommerce-adapter/src/models/index.ts index f40bf9e..837851d 100644 --- a/virtocommerce-adapter/src/models/index.ts +++ b/virtocommerce-adapter/src/models/index.ts @@ -111,6 +111,7 @@ export type { EditorialReview, ProductAssociation, CategoryLink, + ProductPrice, ProductSearchCriteria, ProductSearchResult, ListEntrySearchCriteria, diff --git a/virtocommerce-adapter/src/transformers/product.transformer.ts b/virtocommerce-adapter/src/transformers/product.transformer.ts index 3da7c34..00f2795 100644 --- a/virtocommerce-adapter/src/transformers/product.transformer.ts +++ b/virtocommerce-adapter/src/transformers/product.transformer.ts @@ -3,7 +3,7 @@ */ import type { Product, ProductVariant, InventoryItem } from '@cof-org/mcp'; -import type { CatalogProduct, ProductProperty, InventoryInfo } from '../models/index.js'; +import type { CatalogProduct, ProductPrice, ProductProperty, InventoryInfo } from '../models/index.js'; import { BaseTransformer } from './base.js'; export class ProductTransformer extends BaseTransformer { @@ -63,6 +63,13 @@ export class ProductTransformer extends BaseTransformer { const weight = this.extractWeight(variation); const dimensions = this.extractDimensions(variation); + // Resolve prices: prefer variation's own prices, fall back to parent + const pricing = this.extractPricing(variation.prices ?? parentProduct?.prices); + + // Resolve taxable: a non-empty taxType indicates the variant is taxable + const taxType = variation.taxType ?? parentProduct?.taxType; + const taxable = taxType ? true : undefined; + return { id: variation.id ?? '', externalId: variation.outerId, @@ -72,8 +79,10 @@ export class ProductTransformer extends BaseTransformer { barcode: variation.gtin, title: variation.name ?? parentProduct?.name ?? '', selectedOptions, - price: undefined, - currency: undefined, + price: pricing?.price, + currency: pricing?.currency, + compareAtPrice: pricing?.compareAtPrice, + taxable, inventoryNotTracked: variation.trackInventory === false ? true : undefined, weight, dimensions, @@ -171,6 +180,40 @@ export class ProductTransformer extends BaseTransformer { }; } + /** + * Extract pricing from VirtoCommerce product prices array. + * Uses the first price entry with minQuantity <= 1 (or the very first entry). + * Maps: sale → price, list → compareAtPrice (only when sale is present). + */ + private extractPricing( + prices?: ProductPrice[] + ): { price: number; currency: string; compareAtPrice?: number } | undefined { + if (!prices?.length) { + return undefined; + } + + // Prefer the price entry applicable to single-unit purchases + const entry = prices.find((p) => (p.minQuantity ?? 0) <= 1) ?? prices[0]; + if (!entry) { + return undefined; + } + + const list = entry.list; + const sale = entry.sale; + const currency = entry.currency; + + if (list == null || !currency) { + return undefined; + } + + // When a sale price exists, it becomes the selling price and list is compare-at + if (sale != null && sale < list) { + return { price: sale, currency, compareAtPrice: list }; + } + + return { price: list, currency }; + } + /** * Transform a VirtoCommerce InventoryInfo to an MCP InventoryItem. * Requires the product SKU to be passed in because InventoryInfo From f3a9d84f2fcc41f1d5785c47201cfd7cbf1a6496 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Wed, 25 Feb 2026 13:51:57 +0000 Subject: [PATCH 42/51] Refactor product variant handling to align with VirtoCommerce API, removing unsupported fields and adding SKU resolution logic --- .claude/settings.local.json | 3 +- docs/plans/starry-orbiting-tower.md | 64 +++++++++++++++++++ .../src/mappers/filter.mappers.ts | 29 ++++----- virtocommerce-adapter/src/models/catalog.ts | 48 +++++++------- virtocommerce-adapter/src/models/index.ts | 1 - .../src/services/product.service.ts | 45 +++++++++++++ .../src/transformers/product.transformer.ts | 42 +----------- virtocommerce-adapter/tests/adapter.test.ts | 44 +++++++++++-- 8 files changed, 185 insertions(+), 91 deletions(-) create mode 100644 docs/plans/starry-orbiting-tower.md diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 7d6ed52..d1dd74a 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -48,7 +48,8 @@ "mcp__plugin_context7_context7__resolve-library-id", "mcp__plugin_context7_context7__query-docs", "Bash(grep:*)", - "Bash(find:*)" + "Bash(find:*)", + "Bash(head:*)" ] } } diff --git a/docs/plans/starry-orbiting-tower.md b/docs/plans/starry-orbiting-tower.md new file mode 100644 index 0000000..a53a46b --- /dev/null +++ b/docs/plans/starry-orbiting-tower.md @@ -0,0 +1,64 @@ +# Fix `getProductVariants` mapping — align with VirtoCommerce API + +## Context + +Audit of `getProductVariants` against the real VirtoCommerce C# source (`vc-module-catalog`) revealed that the adapter's TypeScript models and API calls are misaligned with what the VC indexed search endpoint actually accepts and returns: + +1. **SKU search is broken** — adapter sends `codes` field, but `ProductIndexedSearchCriteria` has no such property; it's silently ignored +2. **`WithPrices` responseGroup doesn't exist** — `ItemResponseGroup` enum has no `WithPrices` flag; silently ignored +3. **`CatalogProduct` has no `prices` field** — prices live in a separate Pricing module; the `ProductPrice` interface and `extractPricing()` added earlier are dead code +4. **TypeScript `ProductSearchCriteria`** has fields from the DB-based search criteria, not the indexed search criteria that the actual endpoint uses + +## Changes + +### 1. `virtocommerce-adapter/src/models/catalog.ts` + +- Remove `ProductPrice` interface (added incorrectly in previous session) +- Remove `prices?: ProductPrice[]` from `CatalogProduct` +- Keep `taxType?: string` (exists on real C# model) +- Remove `codes` from `ProductSearchCriteria` (not on indexed search criteria) + +### 2. `virtocommerce-adapter/src/models/index.ts` + +- Remove `ProductPrice` from exports + +### 3. `virtocommerce-adapter/src/mappers/filter.mappers.ts` + +- **`mapProductVariantFiltersToSearchCriteria`**: remove `WithPrices` from responseGroup, remove `codes` usage entirely (SKU resolution moves to service layer) +- **`mapProductFiltersToSearchCriteria`**: remove dead `codes` branch (SKU path already early-returns in service) + +### 4. `virtocommerce-adapter/src/services/product.service.ts` + +- Add `getProductVariantsBySkus()` private method — two-step pattern (same as existing `getProductsByCodes`): + 1. Resolve SKUs to IDs via `resolveSkuProductIdMap()` (`POST /api/catalog/listentries`) + 2. Fetch products by resolved IDs via `POST /api/catalog/search/products` with `searchInVariations: true` +- Update `getProductVariants()` to early-return to `getProductVariantsBySkus()` when `input.skus` provided + +### 5. `virtocommerce-adapter/src/transformers/product.transformer.ts` + +- Remove `ProductPrice` import +- Remove `extractPricing()` method +- In `fromCatalogProductVariant()`: remove pricing extraction, keep `taxable` mapping from `taxType` + +### 6. `virtocommerce-adapter/tests/adapter.test.ts` + +- **"should get variants by SKUs"** (line 1129): change single mock to two sequential mocks (listentries + search/products), update assertion to verify two-step flow +- **"should handle variant search failure"** (line 1232): add listentries mock before the failing search mock (test uses `skus` input which now triggers two-step) + +## Implementation Order + +1. `models/catalog.ts` — remove `ProductPrice`, `prices`, `codes` +2. `models/index.ts` — remove `ProductPrice` export +3. `transformers/product.transformer.ts` — remove pricing code +4. `mappers/filter.mappers.ts` — remove `codes` and `WithPrices` +5. `services/product.service.ts` — add `getProductVariantsBySkus()` +6. `tests/adapter.test.ts` — update SKU test mocks + +## Verification + +```bash +cd server && npm run build +cd ../virtocommerce-adapter && npm run build +``` + +Note: `adapter.test.ts` has pre-existing syntax errors (customer section ~line 667) that prevent tests from running. Our changes should not introduce new failures. diff --git a/virtocommerce-adapter/src/mappers/filter.mappers.ts b/virtocommerce-adapter/src/mappers/filter.mappers.ts index 866c162..99da260 100644 --- a/virtocommerce-adapter/src/mappers/filter.mappers.ts +++ b/virtocommerce-adapter/src/mappers/filter.mappers.ts @@ -103,9 +103,8 @@ export function mapProductFiltersToSearchCriteria(input: GetProductsInput): Prod criteria.objectIds = input.ids; } - if (input.skus?.length) { - criteria.codes = input.skus; - } + // NOTE: SKU search is handled separately via getProductsByCodes() in ProductService. + // The indexed search endpoint does not support a `codes` / `skus` field. criteria.skip = input.skip ?? 0; criteria.take = input.pageSize ?? 20; @@ -135,32 +134,28 @@ export function mapProductFilters(input: GetProductsInput): Record> { try { + // When SKUs are provided, resolve them to IDs via listentries first + // (the indexed search endpoint does not support searching by SKU code) + if (input.skus?.length) { + return this.getProductVariantsBySkus(input.skus); + } + const searchCriteria = mapProductVariantFiltersToSearchCriteria(input); // Filter by catalog when catalogId is configured @@ -187,6 +193,45 @@ export class ProductService extends BaseService { } } + /** + * Resolve variant SKUs to IDs via /api/catalog/listentries, then fetch + * the full product data so the transformer can extract variant details. + */ + private async getProductVariantsBySkus( + skus: string[] + ): Promise> { + // Step 1: Resolve SKUs to product/variation IDs via listentries + const resolvedIds = await this.resolveSkusToIds(skus); + + if (!resolvedIds.length) { + return this.success<{ productVariants: ProductVariant[] }>({ productVariants: [] }); + } + + // Step 2: Fetch products by resolved IDs with searchInVariations=true + // so that variation-level IDs are also matched + const response = await this.client.post( + '/api/catalog/search/products', + { + objectIds: resolvedIds, + responseGroup: 'ItemInfo,ItemAssets,ItemProperties,Variations', + searchInVariations: true, + catalogIds: this.catalogId ? [this.catalogId] : undefined, + take: resolvedIds.length, + } + ); + + if (!response.success) { + return this.failure<{ productVariants: ProductVariant[] }>( + 'Failed to fetch product variants by SKUs', + response.error ?? response + ); + } + + const results = response.data?.items ?? []; + const productVariants = this.transformer.fromCatalogProductVariants(results); + return this.success<{ productVariants: ProductVariant[] }>({ productVariants }); + } + async getInventory( input: GetInventoryInput ): Promise> { diff --git a/virtocommerce-adapter/src/transformers/product.transformer.ts b/virtocommerce-adapter/src/transformers/product.transformer.ts index 00f2795..58852cf 100644 --- a/virtocommerce-adapter/src/transformers/product.transformer.ts +++ b/virtocommerce-adapter/src/transformers/product.transformer.ts @@ -3,7 +3,7 @@ */ import type { Product, ProductVariant, InventoryItem } from '@cof-org/mcp'; -import type { CatalogProduct, ProductPrice, ProductProperty, InventoryInfo } from '../models/index.js'; +import type { CatalogProduct, ProductProperty, InventoryInfo } from '../models/index.js'; import { BaseTransformer } from './base.js'; export class ProductTransformer extends BaseTransformer { @@ -63,9 +63,6 @@ export class ProductTransformer extends BaseTransformer { const weight = this.extractWeight(variation); const dimensions = this.extractDimensions(variation); - // Resolve prices: prefer variation's own prices, fall back to parent - const pricing = this.extractPricing(variation.prices ?? parentProduct?.prices); - // Resolve taxable: a non-empty taxType indicates the variant is taxable const taxType = variation.taxType ?? parentProduct?.taxType; const taxable = taxType ? true : undefined; @@ -79,9 +76,6 @@ export class ProductTransformer extends BaseTransformer { barcode: variation.gtin, title: variation.name ?? parentProduct?.name ?? '', selectedOptions, - price: pricing?.price, - currency: pricing?.currency, - compareAtPrice: pricing?.compareAtPrice, taxable, inventoryNotTracked: variation.trackInventory === false ? true : undefined, weight, @@ -180,40 +174,6 @@ export class ProductTransformer extends BaseTransformer { }; } - /** - * Extract pricing from VirtoCommerce product prices array. - * Uses the first price entry with minQuantity <= 1 (or the very first entry). - * Maps: sale → price, list → compareAtPrice (only when sale is present). - */ - private extractPricing( - prices?: ProductPrice[] - ): { price: number; currency: string; compareAtPrice?: number } | undefined { - if (!prices?.length) { - return undefined; - } - - // Prefer the price entry applicable to single-unit purchases - const entry = prices.find((p) => (p.minQuantity ?? 0) <= 1) ?? prices[0]; - if (!entry) { - return undefined; - } - - const list = entry.list; - const sale = entry.sale; - const currency = entry.currency; - - if (list == null || !currency) { - return undefined; - } - - // When a sale price exists, it becomes the selling price and list is compare-at - if (sale != null && sale < list) { - return { price: sale, currency, compareAtPrice: list }; - } - - return { price: list, currency }; - } - /** * Transform a VirtoCommerce InventoryInfo to an MCP InventoryItem. * Requires the product SKU to be passed in because InventoryInfo diff --git a/virtocommerce-adapter/tests/adapter.test.ts b/virtocommerce-adapter/tests/adapter.test.ts index 6803832..d160488 100644 --- a/virtocommerce-adapter/tests/adapter.test.ts +++ b/virtocommerce-adapter/tests/adapter.test.ts @@ -1127,11 +1127,22 @@ describe('VirtoCommerceFulfillmentAdapter', () => { }); it('should get variants by SKUs', async () => { - postSpy.mockResolvedValue({ + // Step 1: resolveSkuProductIdMap via /api/catalog/listentries + postSpy.mockResolvedValueOnce({ success: true, data: { - totalCount: 1, results: [ + { id: 'VAR-001', code: 'BOLT-SM', type: 'Product' }, + ], + }, + }); + + // Step 2: fetch products via /api/catalog/search/products + postSpy.mockResolvedValueOnce({ + success: true, + data: { + totalCount: 1, + items: [ { id: 'VAR-001', code: 'BOLT-SM', @@ -1166,10 +1177,22 @@ describe('VirtoCommerceFulfillmentAdapter', () => { expect(result.productVariants[0]?.productId).toBe('PROD-001'); } - expect(postSpy).toHaveBeenCalledWith( + // First call should be to listentries for SKU resolution + expect(postSpy).toHaveBeenNthCalledWith( + 1, + '/api/catalog/listentries', + expect.objectContaining({ + keyword: 'code:BOLT-SM', + searchInVariations: true, + }) + ); + + // Second call should be to search/products with resolved IDs + expect(postSpy).toHaveBeenNthCalledWith( + 2, '/api/catalog/search/products', expect.objectContaining({ - codes: ['BOLT-SM'], + objectIds: ['VAR-001'], searchInVariations: true, }) ); @@ -1230,7 +1253,18 @@ describe('VirtoCommerceFulfillmentAdapter', () => { }); it('should handle variant search failure', async () => { - postSpy.mockResolvedValue({ + // Step 1: listentries resolves successfully + postSpy.mockResolvedValueOnce({ + success: true, + data: { + results: [ + { id: 'VAR-001', code: 'BOLT-SM', type: 'Product' }, + ], + }, + }); + + // Step 2: search/products fails + postSpy.mockResolvedValueOnce({ success: false, error: { code: 'API_ERROR', From a12fe838973ad7817bb5c6703a6d494282110d5a Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Wed, 25 Feb 2026 16:15:05 +0000 Subject: [PATCH 43/51] Fix customer schema validation and enhance SKU resolution for order creation --- .claude/settings.local.json | 3 +- server/docs/plans/starry-orbiting-tower.md | 78 +++++++++++ server/src/adapters/mock/mock-adapter.ts | 3 - server/src/schemas/entities/order.ts | 3 +- .../src/mappers/filter.mappers.ts | 18 ++- .../src/services/order.service.ts | 20 ++- .../src/services/product.service.ts | 127 +++--------------- .../src/transformers/order.transformer.ts | 25 ++-- virtocommerce-adapter/tests/adapter.test.ts | 91 +------------ 9 files changed, 147 insertions(+), 221 deletions(-) create mode 100644 server/docs/plans/starry-orbiting-tower.md diff --git a/.claude/settings.local.json b/.claude/settings.local.json index d1dd74a..c2649f4 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -49,7 +49,8 @@ "mcp__plugin_context7_context7__query-docs", "Bash(grep:*)", "Bash(find:*)", - "Bash(head:*)" + "Bash(head:*)", + "Bash(cd \"e:/Develops/VirtoWay/projects/tasks/4464/mcp-reference-server/server\" && npm run test:unit 2>&1)" ] } } diff --git a/server/docs/plans/starry-orbiting-tower.md b/server/docs/plans/starry-orbiting-tower.md new file mode 100644 index 0000000..6e67d01 --- /dev/null +++ b/server/docs/plans/starry-orbiting-tower.md @@ -0,0 +1,78 @@ +# Fix Order Customer Schema + Resolve Product Names by SKU + +## Context + +Two problems found during end-to-end testing of `create-sales-order`: + +1. **Customer schema validation blocks order creation** — `CustomerSchema` inherits `ObjectProps` which makes `createdAt`, `updatedAt`, `tenantId` required. When the LLM passes `order.customer: { id: "...", firstName: "..." }`, the MCP server rejects it with `"must have required property 'createdAt'"`. These timestamp fields make no sense for a customer reference within an order. + +2. **Line items missing product name** — VirtoCommerce requires `name` on line items. Currently `resolveSkuProductIdMap` returns only `Map`. If the LLM doesn't provide `item.name`, the line item arrives at VC without a name. The product name is already available in the search response and should be captured. + +## Changes + +### 1. `server/src/schemas/entities/order.ts` — Customer reference schema + +Replace `CustomerSchema.partial()` with a proper omit that removes only timestamp/system fields but keeps `id` required: + +```typescript +const OrderCustomerRefSchema = CustomerSchema.omit( + makeZodFieldMap(['createdAt', 'updatedAt', 'tenantId'] as const) +).describe('Customer reference for the order'); +``` + +Result: +- `id`: string (**required**) → maps to VC `customerId` +- `externalId`: string (optional) → fallback customerId +- `firstName`, `lastName`, `email`, `phone`: optional → used to derive `customerName` +- No `createdAt`, `updatedAt`, `tenantId` + +Use `OrderCustomerRefSchema` in `OrderCoreSchema.customer`. + +Type compatibility: `Customer` (returned by adapter's `toMcpOrder`) is structurally assignable to the ref type — extra properties are allowed by TypeScript structural typing. + +### 2. `virtocommerce-adapter/src/services/product.service.ts` — Resolve names with SKUs + +Change `resolveSkuProductIdMap` signature: +- **Before:** `resolveSkuProductIdMap(skus): Promise>` (code → id) +- **After:** `resolveSkuProductMap(skus): Promise>` (code → {id, name}) + +Inside the method: capture `item.name` alongside `item.id` from the search response. The `name` field is a base entity property available with `responseGroup: 'None'`. + +Update private `resolveSkusToIds` and `getInventory` to work with the new map shape (extract `.id` from values). + +### 3. `virtocommerce-adapter/src/services/order.service.ts` — Pass new map + +Update `createSalesOrder` to pass the new `Map` to the transformer. + +### 4. `virtocommerce-adapter/src/transformers/order.transformer.ts` — Use resolved names + +Update `fromCreateSalesOrderInput`: +- `skuProductIdMap` parameter type → `Map` +- Line item mapping: `productId: resolved?.id`, `name: item.name ?? resolved?.name ?? item.sku` + +This ensures every line item has a name even if the LLM only passes SKU + quantity. + +### 5. `virtocommerce-adapter/tests/adapter.test.ts` — Update mocks + +Update test at ~line 1738 (order creation with minimal input) to include customer data since the adapter now validates it. + +Update any mock data that uses the old `skuProductIdMap` shape if directly asserted. + +## Files + +| File | Package | Change | +|------|---------|--------| +| `server/src/schemas/entities/order.ts` | server | Replace `.partial()` with `.omit()` on customer | +| `virtocommerce-adapter/src/services/product.service.ts` | adapter | Return `{id, name}` from SKU resolution | +| `virtocommerce-adapter/src/services/order.service.ts` | adapter | Pass new map shape to transformer | +| `virtocommerce-adapter/src/transformers/order.transformer.ts` | adapter | Use resolved name as fallback | +| `virtocommerce-adapter/tests/adapter.test.ts` | adapter | Adjust mocks | + +## Verification + +```bash +cd server && npm run build +cd ../virtocommerce-adapter && npm run build +``` + +End-to-end: restart MCP server, test `create-sales-order` with customer `{ id, firstName, lastName }` — no more `createdAt` validation error. Line items should have product names resolved from catalog. diff --git a/server/src/adapters/mock/mock-adapter.ts b/server/src/adapters/mock/mock-adapter.ts index ab6951d..51c77d6 100644 --- a/server/src/adapters/mock/mock-adapter.ts +++ b/server/src/adapters/mock/mock-adapter.ts @@ -1148,9 +1148,6 @@ export class MockAdapter implements IFulfillmentAdapter { firstName: customer.firstName ?? 'Mock', lastName: customer.lastName ?? 'Customer', type: customer.type ?? 'individual', - createdAt: DateUtils.now(), - updatedAt: DateUtils.now(), - tenantId: (customer as Partial).tenantId ?? 'mock-tenant', addresses: customer.addresses ?? [], customFields: customer.customFields ?? [], tags: customer.tags ?? [], diff --git a/server/src/schemas/entities/order.ts b/server/src/schemas/entities/order.ts index 164dc86..ca2a1d2 100644 --- a/server/src/schemas/entities/order.ts +++ b/server/src/schemas/entities/order.ts @@ -21,7 +21,8 @@ const OrderCoreSchema = z billingAddress: AddressSchema.describe('Billing address'), currency: z.string().describe('Order currency code'), customFields: CustomFieldsSchema, - customer: CustomerSchema.describe('Order customer information'), + customer: CustomerSchema.omit(makeZodFieldMap(['createdAt', 'updatedAt', 'tenantId'] as const)) + .describe('Customer reference for the order'), discounts: z.array(z.looseObject({})).describe('Discounts'), lineItems: z.array(OrderLineItemSchema), orderDiscount: z.number().describe('Order Discount'), diff --git a/virtocommerce-adapter/src/mappers/filter.mappers.ts b/virtocommerce-adapter/src/mappers/filter.mappers.ts index 99da260..1bd9516 100644 --- a/virtocommerce-adapter/src/mappers/filter.mappers.ts +++ b/virtocommerce-adapter/src/mappers/filter.mappers.ts @@ -103,8 +103,10 @@ export function mapProductFiltersToSearchCriteria(input: GetProductsInput): Prod criteria.objectIds = input.ids; } - // NOTE: SKU search is handled separately via getProductsByCodes() in ProductService. - // The indexed search endpoint does not support a `codes` / `skus` field. + if (input.skus?.length) { + criteria.searchPhrase = `code:${input.skus!.join(',')}`; + criteria.searchInVariations = true; + } criteria.skip = input.skip ?? 0; criteria.take = input.pageSize ?? 20; @@ -135,20 +137,18 @@ export function mapProductFilters(input: GetProductsInput): Record,` format. */ export function mapProductVariantFiltersToSearchCriteria(input: GetProductVariantsInput): ProductSearchCriteria { const hasProductIds = !!input.productIds?.length; const hasVariantIds = !!input.ids?.length; + const hasSkus = !!input.skus?.length; const criteria: ProductSearchCriteria = { responseGroup: 'ItemInfo,ItemAssets,ItemProperties,Variations', }; - if (hasProductIds && !hasVariantIds) { + if (hasProductIds && !hasVariantIds && !hasSkus) { // Fetch parent products to extract their variations criteria.objectIds = input.productIds; criteria.searchInVariations = false; @@ -156,6 +156,10 @@ export function mapProductVariantFiltersToSearchCriteria(input: GetProductVarian // Search for specific variations by ID criteria.objectIds = input.ids; criteria.searchInVariations = true; + } else if (hasSkus) { + // Search variations by SKU code via searchPhrase + criteria.searchPhrase = `code:${input.skus!.join(',')}`; + criteria.searchInVariations = true; } criteria.skip = input.skip ?? 0; diff --git a/virtocommerce-adapter/src/services/order.service.ts b/virtocommerce-adapter/src/services/order.service.ts index f8ad61e..8e2e49a 100644 --- a/virtocommerce-adapter/src/services/order.service.ts +++ b/virtocommerce-adapter/src/services/order.service.ts @@ -57,15 +57,23 @@ export class OrderService extends BaseService { async createSalesOrder(input: CreateSalesOrderInput): Promise { try { - // Resolve SKUs to product IDs before building the payload + // VirtoCommerce requires customer identification on orders + const customer = input.order?.customer; + if (!customer?.id && !customer?.externalId) { + return this.failure<{ order: Order }>( + 'Customer identification is required to create an order. Provide customer.id or customer.externalId in order.customer.' + ); + } + + // Resolve SKUs to product info (id + name) before building the payload const skus = input.order?.lineItems?.map((li) => li.sku).filter(Boolean) as string[] ?? []; - const skuProductIdMap = skus.length && this.productService - ? await this.productService.resolveSkuProductIdMap(skus) - : new Map(); + const skuProductMap = skus.length && this.productService + ? await this.productService.resolveSkuProductMap(skus) + : new Map(); - console.error(`[OrderService] Resolved SKUs to productIds: ${JSON.stringify(Object.fromEntries(skuProductIdMap))}`); + console.error(`[OrderService] Resolved SKUs to products: ${JSON.stringify(Object.fromEntries(skuProductMap))}`); - const payload = this.transformer.fromCreateSalesOrderInput(input, skuProductIdMap); + const payload = this.transformer.fromCreateSalesOrderInput(input, skuProductMap); const response = await this.client.post('/api/order/customerOrders', payload); if (!response.success || !response.data) { diff --git a/virtocommerce-adapter/src/services/product.service.ts b/virtocommerce-adapter/src/services/product.service.ts index aebb5dd..9dc50d7 100644 --- a/virtocommerce-adapter/src/services/product.service.ts +++ b/virtocommerce-adapter/src/services/product.service.ts @@ -15,8 +15,6 @@ import type { ProductSearchResult, InventorySearchResult, InventorySearchCriteria, - ListEntrySearchResult, - ListEntrySearchCriteria, } from '../models/index.js'; import { BaseService } from './base.service.js'; import { ProductTransformer } from '../transformers/product.transformer.js'; @@ -46,11 +44,6 @@ export class ProductService extends BaseService { async getProducts(input: GetProductsInput): Promise> { try { - // Use products-by-codes endpoint for SKU search when catalogId is available - if (input.skus?.length && this.catalogId) { - return this.getProductsByCodes(input.skus); - } - const searchCriteria = mapProductFiltersToSearchCriteria(input); // Filter by catalog when catalogId is configured @@ -81,64 +74,33 @@ export class ProductService extends BaseService { } } - private async getProductsByCodes(skus: string[]): Promise> { - // Step 1: Resolve SKUs to product IDs via listentries - const productIds = await this.resolveSkusToIds(skus); - - if (!productIds.length) { - return this.success<{ products: Product[] }>({ products: [] }); - } + /** + * Resolve SKU codes to a Map of code → { id, name } from the catalog + * via /api/catalog/search/products with searchPhrase and responseGroup=None. + * Public so other services (e.g. OrderService) can resolve SKUs to product info. + */ + async resolveSkuProductMap(skus: string[]): Promise> { + const map = new Map(); - // Step 2: Fetch full products by IDs const response = await this.client.post( '/api/catalog/search/products', { - objectIds: productIds, - responseGroup: 'ItemInfo,ItemAssets,ItemProperties,Links,Variations,Seo', - take: productIds.length, + searchPhrase: `code:${skus.join(',')}`, + responseGroup: 'None', + searchInVariations: true, + catalogIds: this.catalogId ? [this.catalogId] : undefined, + take: skus.length, } ); - if (!response.success) { - return this.failure<{ products: Product[] }>( - 'Failed to fetch products by codes', - response.error ?? response - ); - } - - const results = response.data?.items ?? []; - const products = this.transformer.fromCatalogProducts(results); - return this.success<{ products: Product[] }>({ products }); - } - - /** - * Resolve SKU codes to a Map of code → catalog product/variation ID - * via /api/catalog/listentries. Public so other services (e.g. OrderService) - * can resolve SKUs to productIds when creating orders. - */ - async resolveSkuProductIdMap(skus: string[]): Promise> { - const map = new Map(); - - const criteria: ListEntrySearchCriteria = { - keyword: `code:${skus.join(',')}`, - catalogId: this.catalogId, - searchInVariations: true, - take: skus.length, - }; - - const response = await this.client.post( - '/api/catalog/listentries', - criteria - ); - if (!response.success || !response.data) { return map; } - const entries = response.data.results ?? response.data.listEntries ?? []; - for (const entry of entries) { - if (entry.id && entry.code && entry.type?.toLowerCase() === 'product') { - map.set(entry.code, entry.id); + const items = response.data.items ?? []; + for (const item of items) { + if (item.id && item.code) { + map.set(item.code, { id: item.id, name: item.name ?? '' }); } } @@ -146,23 +108,17 @@ export class ProductService extends BaseService { } /** - * Resolve SKU codes to product IDs via /api/catalog/listentries + * Resolve SKU codes to product IDs via /api/catalog/search/products */ private async resolveSkusToIds(skus: string[]): Promise { - const map = await this.resolveSkuProductIdMap(skus); - return Array.from(map.values()); + const map = await this.resolveSkuProductMap(skus); + return Array.from(map.values()).map((v) => v.id); } async getProductVariants( input: GetProductVariantsInput ): Promise> { try { - // When SKUs are provided, resolve them to IDs via listentries first - // (the indexed search endpoint does not support searching by SKU code) - if (input.skus?.length) { - return this.getProductVariantsBySkus(input.skus); - } - const searchCriteria = mapProductVariantFiltersToSearchCriteria(input); // Filter by catalog when catalogId is configured @@ -193,53 +149,12 @@ export class ProductService extends BaseService { } } - /** - * Resolve variant SKUs to IDs via /api/catalog/listentries, then fetch - * the full product data so the transformer can extract variant details. - */ - private async getProductVariantsBySkus( - skus: string[] - ): Promise> { - // Step 1: Resolve SKUs to product/variation IDs via listentries - const resolvedIds = await this.resolveSkusToIds(skus); - - if (!resolvedIds.length) { - return this.success<{ productVariants: ProductVariant[] }>({ productVariants: [] }); - } - - // Step 2: Fetch products by resolved IDs with searchInVariations=true - // so that variation-level IDs are also matched - const response = await this.client.post( - '/api/catalog/search/products', - { - objectIds: resolvedIds, - responseGroup: 'ItemInfo,ItemAssets,ItemProperties,Variations', - searchInVariations: true, - catalogIds: this.catalogId ? [this.catalogId] : undefined, - take: resolvedIds.length, - } - ); - - if (!response.success) { - return this.failure<{ productVariants: ProductVariant[] }>( - 'Failed to fetch product variants by SKUs', - response.error ?? response - ); - } - - const results = response.data?.items ?? []; - const productVariants = this.transformer.fromCatalogProductVariants(results); - return this.success<{ productVariants: ProductVariant[] }>({ productVariants }); - } - async getInventory( input: GetInventoryInput ): Promise> { try { - // Step 1: Resolve SKUs to product IDs via listentries, then fetch product details - const resolvedIds = this.catalogId - ? await this.resolveSkusToIds(input.skus) - : []; + // Step 1: Resolve SKUs to product IDs via search/products + const resolvedIds = await this.resolveSkusToIds(input.skus); if (!resolvedIds.length) { return this.success<{ inventory: InventoryItem[] }>({ inventory: [] }); diff --git a/virtocommerce-adapter/src/transformers/order.transformer.ts b/virtocommerce-adapter/src/transformers/order.transformer.ts index 4b6bc0d..0178ca1 100644 --- a/virtocommerce-adapter/src/transformers/order.transformer.ts +++ b/virtocommerce-adapter/src/transformers/order.transformer.ts @@ -96,7 +96,7 @@ export class OrderTransformer extends BaseTransformer { */ fromCreateSalesOrderInput( input: CreateSalesOrderInput, - skuProductIdMap?: Map + skuProductMap?: Map ): CustomerOrder { const order = input.order; if (!order) { @@ -115,16 +115,19 @@ export class OrderTransformer extends BaseTransformer { ].filter((a): a is NonNullable => a !== undefined); const items: LineItem[] = - order.lineItems?.map((item) => ({ - productId: skuProductIdMap?.get(item.sku), - sku: item.sku, - name: item.name, - quantity: item.quantity ?? 0, - price: item.unitPrice ?? 0, - placedPrice: item.unitPrice ?? 0, - currency, - catalogId: this.catalogId, - })) ?? []; + order.lineItems?.map((item) => { + const resolved = skuProductMap?.get(item.sku); + return { + productId: resolved?.id, + sku: item.sku, + name: item.name ?? resolved?.name ?? item.sku, + quantity: item.quantity ?? 0, + price: item.unitPrice ?? 0, + placedPrice: item.unitPrice ?? 0, + currency, + catalogId: this.catalogId, + }; + }) ?? []; const shipments: Shipment[] = order.shippingAddress ? [ diff --git a/virtocommerce-adapter/tests/adapter.test.ts b/virtocommerce-adapter/tests/adapter.test.ts index d160488..0a3bce4 100644 --- a/virtocommerce-adapter/tests/adapter.test.ts +++ b/virtocommerce-adapter/tests/adapter.test.ts @@ -120,9 +120,6 @@ describe('VirtoCommerceFulfillmentAdapter', () => { firstName: 'John', lastName: 'Doe', phone: '+1234567890', - createdAt: '2024-01-01T00:00:00Z', - updatedAt: '2024-01-01T00:00:00Z', - tenantId: 'test-tenant', }, shippingAddress: { firstName: 'John', @@ -643,51 +640,6 @@ describe('VirtoCommerceFulfillmentAdapter', () => { const result = await adapter.getCustomers(input); - expect(result.success).toBe(true); - if (result.success) { - expect(result.customers).toHaveLength(1); - expect(result.customers[0]?.id).toBe('CUST-001'); - expect(result.customers[0]?.firstName).toBe('John'); - expect(result.customers[0]?.lastName).toBe('Doe'); - expect(result.customers[0]?.email).toBe('john@example.com'); - expect(result.customers[0]?.phone).toBe('+1234567890'); - expect(result.customers[0]?.externalId).toBe('EXT-CUST-001'); - expect(result.customers[0]?.tags).toEqual(['VIP']); - } - expect(postSpy).toHaveBeenCalledWith( - '/api/members/search', - expect.objectContaining({ - objectIds: ['CUST-001'], - memberTypes: ['Contact'], - responseGroup: 'Full', - }) - ); - }); - - id: 'CUST-001', - memberType: 'Contact', - firstName: 'John', - lastName: 'Doe', - emails: ['john@example.com'], - phones: ['+1234567890'], - addresses: [], - groups: ['VIP'], - status: 'active', - outerId: 'EXT-CUST-001', - createdDate: '2024-01-01T00:00:00Z', - modifiedDate: '2024-01-15T00:00:00Z', - dynamicProperties: [], - }, - ], - }, - }); - - const input: GetCustomersInput = { - ids: ['CUST-001'], - }; - - const result = await adapter.getCustomers(input); - expect(result.success).toBe(true); if (result.success) { expect(result.customers).toHaveLength(1); @@ -1127,18 +1079,7 @@ describe('VirtoCommerceFulfillmentAdapter', () => { }); it('should get variants by SKUs', async () => { - // Step 1: resolveSkuProductIdMap via /api/catalog/listentries - postSpy.mockResolvedValueOnce({ - success: true, - data: { - results: [ - { id: 'VAR-001', code: 'BOLT-SM', type: 'Product' }, - ], - }, - }); - - // Step 2: fetch products via /api/catalog/search/products - postSpy.mockResolvedValueOnce({ + postSpy.mockResolvedValue({ success: true, data: { totalCount: 1, @@ -1177,22 +1118,10 @@ describe('VirtoCommerceFulfillmentAdapter', () => { expect(result.productVariants[0]?.productId).toBe('PROD-001'); } - // First call should be to listentries for SKU resolution - expect(postSpy).toHaveBeenNthCalledWith( - 1, - '/api/catalog/listentries', - expect.objectContaining({ - keyword: 'code:BOLT-SM', - searchInVariations: true, - }) - ); - - // Second call should be to search/products with resolved IDs - expect(postSpy).toHaveBeenNthCalledWith( - 2, + expect(postSpy).toHaveBeenCalledWith( '/api/catalog/search/products', expect.objectContaining({ - objectIds: ['VAR-001'], + searchPhrase: 'code:BOLT-SM', searchInVariations: true, }) ); @@ -1253,18 +1182,7 @@ describe('VirtoCommerceFulfillmentAdapter', () => { }); it('should handle variant search failure', async () => { - // Step 1: listentries resolves successfully - postSpy.mockResolvedValueOnce({ - success: true, - data: { - results: [ - { id: 'VAR-001', code: 'BOLT-SM', type: 'Product' }, - ], - }, - }); - - // Step 2: search/products fails - postSpy.mockResolvedValueOnce({ + postSpy.mockResolvedValue({ success: false, error: { code: 'API_ERROR', @@ -1772,6 +1690,7 @@ describe('VirtoCommerceFulfillmentAdapter', () => { const input: CreateSalesOrderInput = { order: { lineItems: [{ sku: 'PROD-001', quantity: 1 }], + customer: { id: 'CUST-001' }, }, }; From acd74eece0367f3b76faeaca5a64c48da9875ec2 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Thu, 26 Feb 2026 06:48:28 +0000 Subject: [PATCH 44/51] Update test prompts for order creation and cancellation, refining customer and product references --- virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md b/virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md index 0fafe2f..99a4b8c 100644 --- a/virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md +++ b/virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md @@ -155,20 +155,28 @@ Create a new order for customer cb0a5340-f9fb-4f49-bd62-9d03518868ff with 2 unit ``` Capture an order for allagrvolkova@mail.ru with 1 566903892 and 2 552223579 items ``` +:white_check_mark: Passed - creates new order for Alla Volkova with specified products ``` -Place an order for customer cust_002 with product WID-001, quantity 1, shipping to 456 Oak Ave, Los Angeles, CA 90210 +Place an order for customer b2b admin with product 08c33cfc9f664426a52fac8882da2df0, quantity 1, shipping to 456 Oak Ave, Los Angeles, CA 90210 ``` +:white_check_mark: Passed - creates new order for b2b admin with specified product and shipping address + ### 8. Cancel Order Tool ``` -Cancel order order_001 due to customer request +Cancel order ORDER-NY-001 due to customer request ``` +:white_check_mark: Passed - cancels order ORDER-NY-001 + ``` -Please cancel order EXT-001 - the customer changed their mind +Please cancel order TEST-ORDER-007 - the customer changed their mind ``` +:white_check_mark: Passed - cancels order TEST-ORDER-007 + ``` -Cancel order ORD-1001 because of inventory issues +Cancel order 8676953f-8728-4719-9c0f-d243422da361 because of inventory issues ``` +:white_check_mark: Passed - cancels order 8676953f-8728-4719-9c0f-d243422da361 ### 9. Update Order Tool ``` From a42bc79ebfceb90578f951b2c79b14a47b2c3eb0 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Thu, 26 Feb 2026 09:29:00 +0000 Subject: [PATCH 45/51] create return generated code --- .../VIRTO_MCP_TEST_PROMPTS.md | 82 +++++- .../docs/plans/starry-orbiting-tower.md | 213 ++++++++++++++++ virtocommerce-adapter/src/adapter.ts | 3 +- .../src/mappers/filter.mappers.ts | 48 +++- virtocommerce-adapter/src/mappers/index.ts | 1 + virtocommerce-adapter/src/models/index.ts | 8 + virtocommerce-adapter/src/models/return.ts | 51 ++++ .../src/services/return.service.ts | 233 +++++++++++++++--- .../src/transformers/index.ts | 1 + .../src/transformers/return.transformer.ts | 127 ++++++++++ virtocommerce-adapter/src/types.ts | 1 + 11 files changed, 729 insertions(+), 39 deletions(-) create mode 100644 virtocommerce-adapter/docs/plans/starry-orbiting-tower.md create mode 100644 virtocommerce-adapter/src/models/return.ts create mode 100644 virtocommerce-adapter/src/transformers/return.transformer.ts diff --git a/virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md b/virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md index 99a4b8c..61a3345 100644 --- a/virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md +++ b/virtocommerce-adapter/VIRTO_MCP_TEST_PROMPTS.md @@ -34,168 +34,236 @@ Copy and paste these prompts into Claude Desktop to test each MCP tool. The mock ## Query Tools Test Prompts ### 1. Get Order Tool + ``` Get me the details for order CO220518-00001 ``` + :white_check_mark: Passed - returns order details for CO220518-00001 + ``` Show me order 95bee1c2-f6b6-4eef-b9fd-df260b980d71 ``` + :white_check_mark: Passed - returns order details + ``` What's the status of order CO220715-00001? ``` + :white_check_mark: Passed - returns "processing" + ``` Can you retrieve order CO220715-00001 and show me all details? ``` + :white_check_mark: Passed - returns full order details including products and customer info ### 2. Get Customer Tool + ``` Get customer information for cb0a5340-f9fb-4f49-bd62-9d03518868ff ``` + :white_check_mark: Passed - returns customer details for b2b admin + ``` Show me the details for customer allagrvolkova@mail.ru ``` + :white_check_mark: Passed - returns customer details for Alla Volkova + ``` Find customer cb0a5340-f9fb-4f49-bd62-9d03518868ff ``` + :white_check_mark: Passed - returns customer details for b2b admin + ``` What information do you have on customer b2badmin@test.com ``` + :white_check_mark: Passed - returns no customer found (since it doesn't exist in test data) ### 3. Get Product Tool + ``` Show me product details for SKU 566903892 ``` + :white_check_mark: Passed - returns product details for 08c33cfc9f664426a52fac8882da2df0 + ``` Get information about product 08c33cfc9f664426a52fac8882da2df0 ``` + :white_check_mark: Passed - returns product details + ``` What are the details for the printer product 4b729fae613046448aaba7c265bb4f2d? ``` + :white_check_mark: Passed - returns product details for the printer + ``` Find product 47e4aaef9c9e4326924d4a4080f461a5 and show me all its attributes ``` + :white_check_mark: Passed - returns product details ### 4. Get Inventory Tool + ``` Check inventory for SKU 566903892 at warehouse vendor-fulfillment ``` + :white_check_mark: Passed - returns inventory levels for SKU 566903892 at vendor-fulfillment + ``` What's the available stock for 566903892 in location vendor-fulfillment? ``` + :white_check_mark: Passed - returns available stock for SKU 566903892 in location vendor-fulfillment + ``` Show me inventory levels for 566903892 across all warehouses ``` + :white_check_mark: Passed - returns inventory levels for SKU 566903892 across all warehouses + ``` Get inventory status for 566903892 at vendor-fulfillment ``` + :white_check_mark: Passed - returns inventory status for SKU 566903892 at vendor-fulfillment ### 5. Get Shipment Tool + ``` Get shipment details for order CO220518-00001 ``` + :white_check_mark: Passed - returns shipment details for order CO220518-00001 ``` Show me the shipment information for order CO220518-00001 ``` + :white_check_mark: Passed - returns shipment information for order CO220518-00001 + ``` Check if CO220518-00001 has been shipped ``` + :white_check_mark: Passed - returns shipment status ("not shipped") + ``` Find shipment tracking for order CO220518-00001 ``` -:white_check_mark: Passwed + +:white_check_mark: Passed - returns shipment tracking for order CO220518-00001 ### 6. Get Buyer Tool + ``` Get buyer information for order CO220518-00001 ``` + :white_check_mark: Passed - returns buyer information for order CO220518-00001 + ``` Who is the buyer for order CO220518-00001? ``` + :white_check_mark: Passed - returns buyer name and contact info for order CO220518-00001 + +``` Show me the buyer details for order CO220518-00001 ``` + :white_check_mark: Passed - returns buyer details for order CO220518-00001 + ``` Find the customer who placed order CO220518-00001 ``` -:white_check_mark: Passed - returns customer details for b2b admin ---- +:white_check_mark: Passed - returns customer details for b2b admin ## Action Tools Test Prompts ### 7. Capture Order Tool + ``` Create a new order for customer cb0a5340-f9fb-4f49-bd62-9d03518868ff with 2 units of 47e4aaef9c9e4326924d4a4080f461a5 shipping to 123 Main St, New York, NY 10001 ``` + :white_check_mark: Passed - creates new order for b2b admin with specified products and shipping address + ``` Capture an order for allagrvolkova@mail.ru with 1 566903892 and 2 552223579 items ``` + :white_check_mark: Passed - creates new order for Alla Volkova with specified products + ``` Place an order for customer b2b admin with product 08c33cfc9f664426a52fac8882da2df0, quantity 1, shipping to 456 Oak Ave, Los Angeles, CA 90210 ``` + :white_check_mark: Passed - creates new order for b2b admin with specified product and shipping address ### 8. Cancel Order Tool + ``` Cancel order ORDER-NY-001 due to customer request ``` + :white_check_mark: Passed - cancels order ORDER-NY-001 ``` Please cancel order TEST-ORDER-007 - the customer changed their mind ``` + :white_check_mark: Passed - cancels order TEST-ORDER-007 ``` Cancel order 8676953f-8728-4719-9c0f-d243422da361 because of inventory issues ``` + :white_check_mark: Passed - cancels order 8676953f-8728-4719-9c0f-d243422da361 ### 9. Update Order Tool + ``` -Update order order_002 to change the quantity of TSH-002 to 3 units +Update order ORDER-LA-001 to change the quantity of 566903892 to 3 units ``` + +:white_check_mark: Passed - updates order ORDER-LA-001 to change quantity of 566903892 to 3 units + ``` -Modify order EXT-001 to ship to 789 Broadway, New York, NY 10002 instead +Modify order ORDER-LA-001 to ship to 789 Broadway, New York, NY 10002 instead ``` + +:white_check_mark: Passed - updates order ORDER-LA-001 to change shipping address to 789 Broadway, New York, NY 10002 + ``` -Update order ORD-1001 with express shipping +Update order ORDER-LA-001 with express shipping ``` +:note: + ### 10. Return Order Tool + ``` -Process a return for order order_003 - customer says the coffee tastes bad +Process a return for order TEST-ORDER-007 - customer says the coffee tastes bad ``` + ``` Create a return for order WEB-2024-1002 with reason "damaged during shipping" ``` + ``` Return order order_001 because the headphones don't work ``` diff --git a/virtocommerce-adapter/docs/plans/starry-orbiting-tower.md b/virtocommerce-adapter/docs/plans/starry-orbiting-tower.md new file mode 100644 index 0000000..575e05c --- /dev/null +++ b/virtocommerce-adapter/docs/plans/starry-orbiting-tower.md @@ -0,0 +1,213 @@ +# Implement VirtoCommerce Return Operations + +## Context + +The MCP server layer for returns is complete (tools, schemas, ServiceOrchestrator), but the VirtoCommerce adapter has only stub implementations that immediately return `failure("not yet implemented")`. The adapter uses wrong endpoints (`/returns` instead of `/api/return/...`), has no VC Return models, no transformer, and incorrect filter mapping. + +The VirtoCommerce Return Module (`vc-module-return`) exposes: +- `PUT /api/return/` — Create/Update (SaveChanges pattern) +- `POST /api/return/search` — Search with `ReturnSearchCriteria` +- `GET /api/return/{id}` — Get by ID (includes calculated `availableQuantity`) +- `GET /api/return/available-quantities/{orderId}` — Available quantities per line item + +Goal: make `create-return` and `get-returns` MCP tools work end-to-end against VirtoCommerce. + +## Files + +| File | Action | Description | +|------|--------|-------------| +| `src/models/return.ts` | **CREATE** | VC Return domain models | +| `src/models/index.ts` | MODIFY | Export return models | +| `src/transformers/return.transformer.ts` | **CREATE** | VC ↔ MCP bidirectional mapping | +| `src/transformers/index.ts` | MODIFY | Export ReturnTransformer | +| `src/mappers/filter.mappers.ts` | MODIFY | Replace `mapReturnFilters` with `mapReturnFiltersToSearchCriteria` | +| `src/mappers/index.ts` | MODIFY | Export new mapper | +| `src/services/return.service.ts` | **REWRITE** | Full implementation against VC API | +| `src/adapter.ts` | MODIFY | Pass `tenantId` to ReturnService | +| `src/types.ts` | MODIFY | Add `RETURN_NOT_FOUND` error code | + +All files under `virtocommerce-adapter/`. + +--- + +## 1. `src/models/return.ts` — VC Domain Models + +Interfaces matching the VC Return Module C# models (JSON camelCase): + +```typescript +import type { AuditableEntity } from './base.js'; +import type { CustomerOrder } from './order.js'; + +export interface VcReturn extends AuditableEntity { + number?: string; // Auto-generated "RET220314-00001" + orderId?: string; + status?: string; // PascalCase: New, Approved, Processing, Completed, Canceled + resolution?: string; // Free-text → maps to MCP "outcome" + order?: CustomerOrder; // Only with WithOrders response group + lineItems?: VcReturnLineItem[]; +} + +export interface VcReturnLineItem extends AuditableEntity { + returnId?: string; + orderLineItemId?: string; + quantity?: number; // int + availableQuantity?: number; // Calculated server-side + price?: number; // decimal + reason?: string; +} + +export interface ReturnSearchCriteria { + orderId?: string; // Single string (NOT array) + objectIds?: string[]; // Return IDs + keyword?: string; // Searches Number and Status + skip?: number; + take?: number; + sort?: string; +} + +export interface ReturnSearchResult { + totalCount?: number; + results?: VcReturn[]; +} + +export interface ReturnSaveResponse { + id: string; +} +``` + +## 2. `src/transformers/return.transformer.ts` — Bidirectional Mapping + +Extends `BaseTransformer`. Key methods: + +### Status Maps + +``` +VC PascalCase → MCP lowercase +New → requested +Approved → approved +Processing → processing +Completed → completed +Canceled → cancelled +``` + +Reverse: `requested`/`pending` → `New`, `cancelled`/`canceled`/`declined` → `Canceled` + +### `toMcpReturn(vcReturn, order?)` — VC → MCP + +| VcReturn | MCP Return | +|----------|------------| +| `id` | `id` | +| `number` | `returnNumber` | +| `orderId` | `orderId` | +| `status` | `status` (via status map) | +| `resolution` | `outcome` | +| `lineItems[]` | `returnLineItems[]` (via `toMcpReturnLineItem`) | +| `createdDate` | `createdAt`, `requestedAt` | +| `modifiedDate` | `updatedAt` | +| — | `tenantId` (from transformer) | + +### `toMcpReturnLineItem(vcItem, orderLineItems?)` — VC → MCP + +| VcReturnLineItem | MCP ReturnLineItem | +|------------------|-------------------| +| `id` | `id` | +| `orderLineItemId` | `orderLineItemId` | +| `quantity` | `quantityReturned` | +| `price` | `unitPrice` | +| `reason` | `returnReason` | +| — | `sku` (lookup from `order.items` by `orderLineItemId`) | +| — | `name` (lookup from `order.items` by `orderLineItemId`) | + +**Key:** VC `ReturnLineItem` has no `sku`/`name`. Resolved from the order's line items by matching `orderLineItemId` → `item.id`. + +### `fromCreateReturnInput(input, order)` — MCP → VC + +``` +{ + orderId: input.return.orderId, + status: reverseMapStatus(input.return.status) ?? 'New', + resolution: input.return.outcome, + lineItems: input.return.returnLineItems.map(item => ({ + orderLineItemId: item.orderLineItemId, + quantity: item.quantityReturned, + reason: item.returnReason, + price: item.unitPrice ?? orderLineItem.price ?? 0 // fallback to order + })) +} +``` + +## 3. `src/mappers/filter.mappers.ts` — Search Criteria Mapping + +Replace `mapReturnFilters` with `mapReturnFiltersToSearchCriteria`: + +| MCP GetReturnsInput | VC ReturnSearchCriteria | Strategy | +|---------------------|------------------------|----------| +| `ids` | `objectIds` | Direct | +| `orderIds[0]` | `orderId` | First only (VC: single string) | +| `returnNumbers[0]` | `keyword` | Partial; post-filter for exact | +| `statuses` | — | Client-side post-filter | +| `outcomes` | — | Client-side post-filter | +| `createdAtMin/Max` | — | Client-side post-filter | +| `pageSize` | `take` | Inflate when post-filtering needed | +| `skip` | `skip` | Direct | + +When post-filtering is needed, `take` is inflated to `max(pageSize, 100)` to fetch enough results. + +## 4. `src/services/return.service.ts` — Full Implementation + +### `createReturn(input)` Flow + +``` +1. Validate input.return.orderId present +2. Fetch order: GET /api/order/customerOrders/{orderId} + → failure if not found +3. Transform: transformer.fromCreateReturnInput(input, order) + → price fallback from order line items +4. Save: PUT /api/return/ (body = VcReturn without id) + → response: { id: string } +5. Fetch created: GET /api/return/{id} +6. Transform: transformer.toMcpReturn(vcReturn, order) +7. Return success +``` + +### `getReturns(input)` Flow + +``` +1. If multiple orderIds → loop per orderId (same as FulfillmentService pattern) +2. Build criteria: mapReturnFiltersToSearchCriteria(input) +3. Search: POST /api/return/search +4. Collect unique orderIds from results +5. Fetch orders for SKU resolution: GET /api/order/customerOrders/{orderId} per ID + → build Map +6. Post-filter by statuses, outcomes, returnNumbers, temporal filters +7. Apply pagination to filtered results +8. Transform: transformer.toMcpReturns(vcReturns, orderMap) +9. Return success +``` + +### Private helpers + +- `fetchOrder(orderId)` — `GET /api/order/customerOrders/{orderId}`, returns `CustomerOrder | null` +- `fetchReturnById(returnId)` — `GET /api/return/{returnId}`, returns `VcReturn | null` + +## 5. `src/adapter.ts` — Wiring + +Change constructor: `new ReturnService(this.client)` → `new ReturnService(this.client, tenantId)` + +Add in `updateOptions()`: `this.returnService.setTenantId(tenantId)` + +## 6. `src/types.ts` — Error Code + +Add `RETURN_NOT_FOUND = 'RETURN_NOT_FOUND'` to `ErrorCode` enum. + +## Verification + +```bash +cd virtocommerce-adapter && npm run build # TypeScript compilation +cd ../server && npm run build # Server (dependency) compilation +``` + +End-to-end: restart MCP server, test: +1. `get-orders` for an order ID → get line item IDs and SKUs +2. `create-return` with `{ orderId, outcome: "refund", returnLineItems: [{ orderLineItemId, sku, quantityReturned: 1, returnReason: "quality_issue" }] }` → should create return in VC with status "New" +3. `get-returns` with `{ orderIds: ["..."] }` → should return the created return with SKUs resolved diff --git a/virtocommerce-adapter/src/adapter.ts b/virtocommerce-adapter/src/adapter.ts index 87511e7..39c34c1 100644 --- a/virtocommerce-adapter/src/adapter.ts +++ b/virtocommerce-adapter/src/adapter.ts @@ -85,7 +85,7 @@ export class VirtoCommerceFulfillmentAdapter implements IFulfillmentAdapter { this.customerService = new CustomerService(this.client, tenantId); this.fulfillmentService = new FulfillmentService(this.client, tenantId, this.options.workspace); this.productService = new ProductService(this.client, tenantId); - this.returnService = new ReturnService(this.client); + this.returnService = new ReturnService(this.client, tenantId); // Wire cross-service dependencies this.orderService.setProductService(this.productService); @@ -276,6 +276,7 @@ export class VirtoCommerceFulfillmentAdapter implements IFulfillmentAdapter { this.customerService.setTenantId(tenantId); this.fulfillmentService.setTenantId(tenantId); this.productService.setTenantId(tenantId); + this.returnService.setTenantId(tenantId); if (options.workspace) { this.orderService.setWorkspace(options.workspace); diff --git a/virtocommerce-adapter/src/mappers/filter.mappers.ts b/virtocommerce-adapter/src/mappers/filter.mappers.ts index 1bd9516..87c23e5 100644 --- a/virtocommerce-adapter/src/mappers/filter.mappers.ts +++ b/virtocommerce-adapter/src/mappers/filter.mappers.ts @@ -12,7 +12,7 @@ import type { GetFulfillmentsInput, GetReturnsInput, } from '@cof-org/mcp'; -import type { CustomerOrderSearchCriteria, MemberSearchCriteria, ProductSearchCriteria, ShipmentSearchCriteria } from '../models/index.js'; +import type { CustomerOrderSearchCriteria, MemberSearchCriteria, ProductSearchCriteria, ShipmentSearchCriteria, ReturnSearchCriteria } from '../models/index.js'; /** * Map GetOrdersInput to VirtoCommerce CustomerOrderSearchCriteria @@ -275,7 +275,51 @@ export function mapFulfillmentFilters(input: GetFulfillmentsInput): Record 1) || + input.createdAtMin || + input.createdAtMax || + input.updatedAtMin || + input.updatedAtMax + ); + + const pageSize = input.pageSize ?? 20; + criteria.skip = input.skip ?? 0; + // Inflate take when post-filtering is needed to ensure enough results + criteria.take = needsPostFilter ? Math.max(pageSize, 100) : pageSize; + + return criteria; +} + +/** + * @deprecated Use mapReturnFiltersToSearchCriteria instead + * Map GetReturnsInput to generic API query parameters (legacy) */ export function mapReturnFilters(input: GetReturnsInput): Record { return { diff --git a/virtocommerce-adapter/src/mappers/index.ts b/virtocommerce-adapter/src/mappers/index.ts index f633784..9baa59d 100644 --- a/virtocommerce-adapter/src/mappers/index.ts +++ b/virtocommerce-adapter/src/mappers/index.ts @@ -12,4 +12,5 @@ export { mapCustomerFilters, mapFulfillmentFilters, mapReturnFilters, + mapReturnFiltersToSearchCriteria, } from './filter.mappers.js'; diff --git a/virtocommerce-adapter/src/models/index.ts b/virtocommerce-adapter/src/models/index.ts index f40bf9e..0ecbaf8 100644 --- a/virtocommerce-adapter/src/models/index.ts +++ b/virtocommerce-adapter/src/models/index.ts @@ -127,6 +127,14 @@ export type { ProductInventoryInfo, } from './inventory.js'; +// Return models +export type { + VcReturn, + VcReturnLineItem, + ReturnSearchCriteria, + ReturnSearchResult, +} from './return.js'; + // Search models export type { SearchCriteriaBase, diff --git a/virtocommerce-adapter/src/models/return.ts b/virtocommerce-adapter/src/models/return.ts new file mode 100644 index 0000000..7561d11 --- /dev/null +++ b/virtocommerce-adapter/src/models/return.ts @@ -0,0 +1,51 @@ +/** + * VirtoCommerce Return Module models + * Based on vc-module-return C# domain models (JSON camelCase) + */ + +import type { AuditableEntity } from './base.js'; +import type { CustomerOrder } from './order.js'; + +/** + * VirtoCommerce Return entity + */ +export interface VcReturn extends AuditableEntity { + number?: string; + orderId?: string; + status?: string; + resolution?: string; + order?: CustomerOrder; + lineItems?: VcReturnLineItem[]; +} + +/** + * VirtoCommerce Return line item + */ +export interface VcReturnLineItem extends AuditableEntity { + returnId?: string; + orderLineItemId?: string; + quantity?: number; + availableQuantity?: number; + price?: number; + reason?: string; +} + +/** + * Search criteria for VirtoCommerce Return search endpoint + */ +export interface ReturnSearchCriteria { + orderId?: string; + objectIds?: string[]; + keyword?: string; + skip?: number; + take?: number; + sort?: string; +} + +/** + * Search result from VirtoCommerce Return search endpoint + */ +export interface ReturnSearchResult { + totalCount?: number; + results?: VcReturn[]; +} diff --git a/virtocommerce-adapter/src/services/return.service.ts b/virtocommerce-adapter/src/services/return.service.ts index 0a2af30..6e70b0f 100644 --- a/virtocommerce-adapter/src/services/return.service.ts +++ b/virtocommerce-adapter/src/services/return.service.ts @@ -1,5 +1,11 @@ /** - * Return service - handles return-related operations + * Return service - handles return-related operations against VirtoCommerce Return Module API + * + * Endpoints used: + * - PUT /api/return/ — Create/Update (SaveChanges pattern) + * - POST /api/return/search — Search with ReturnSearchCriteria + * - GET /api/return/{id} — Get by ID + * - GET /api/order/customerOrders/{id} — Fetch order for SKU resolution */ import type { @@ -9,41 +15,72 @@ import type { CreateReturnInput, GetReturnsInput, } from '@cof-org/mcp'; +import type { VcReturn, CustomerOrder, ReturnSearchResult } from '../models/index.js'; import { BaseService } from './base.service.js'; -import { mapReturnFilters } from '../mappers/filter.mappers.js'; +import { ReturnTransformer } from '../transformers/return.transformer.js'; +import { mapReturnFiltersToSearchCriteria } from '../mappers/filter.mappers.js'; import { getErrorMessage } from '../utils/type-guards.js'; import { ApiClient } from '../utils/api-client.js'; export class ReturnService extends BaseService { - constructor(client: ApiClient) { + private transformer: ReturnTransformer; + + constructor(client: ApiClient, tenantId: string = 'default-workspace') { super(client); + this.transformer = new ReturnTransformer(tenantId); + } + + setTenantId(tenantId: string): void { + this.transformer.setTenantId(tenantId); } async createReturn(input: CreateReturnInput): Promise { try { - const payload = { - order_id: input.return.orderId, - return_number: input.return.returnNumber, - status: input.return.status, - outcome: input.return.outcome, - items: input.return.returnLineItems?.map((item) => ({ - sku: item.sku, - quantity: item.quantityReturned, - reason: item.returnReason, - refund_amount: item.refundAmount, - })), - }; - - const response = await this.client.post('/returns', payload); - - if (!response.success || !response.data) { - return this.failure<{ return: Return }>('Failed to create return', response.error ?? response); + // Step 1: Validate orderId is present + if (!input.return.orderId) { + return this.failure<{ return: Return }>('orderId is required to create a return'); } - // TODO: Transform the response to Return type when VirtoCommerce return API is defined - return this.failure<{ return: Return }>( - 'createReturn not yet implemented - please implement transformation logic' - ); + // Step 2: Fetch the order for line item resolution and price fallback + const order = await this.fetchOrder(input.return.orderId); + if (!order) { + return this.failure<{ return: Return }>( + `Order not found: ${input.return.orderId}` + ); + } + + // Step 3: Transform MCP input to VC Return payload + const vcReturn = this.transformer.fromCreateReturnInput(input, order); + + // Step 4: Save via PUT /api/return/ (returns the saved entity with id) + const saveResponse = await this.client.put('/api/return/', vcReturn); + + if (!saveResponse.success || !saveResponse.data) { + return this.failure<{ return: Return }>( + 'Failed to create return', + saveResponse.error ?? saveResponse + ); + } + + // Step 5: Extract the ID from the response + const savedId = saveResponse.data.id; + if (!savedId) { + return this.failure<{ return: Return }>( + 'Return was saved but no ID was returned' + ); + } + + // Step 6: Fetch the created return to get the full server-generated data + const created = await this.fetchReturnById(savedId); + if (!created) { + // Fallback: use the save response directly + const mcpReturn = this.transformer.toMcpReturn(saveResponse.data, order); + return this.success<{ return: Return }>({ return: mcpReturn }); + } + + // Step 7: Transform and return + const mcpReturn = this.transformer.toMcpReturn(created, order); + return this.success<{ return: Return }>({ return: mcpReturn }); } catch (error: unknown) { return this.failure<{ return: Return }>( `Return creation failed: ${getErrorMessage(error)}`, @@ -54,7 +91,34 @@ export class ReturnService extends BaseService { async getReturns(input: GetReturnsInput): Promise> { try { - const response = await this.client.get('/returns', mapReturnFilters(input)); + // When multiple orderIds are provided, loop per orderId (VC supports single orderId) + if (input.orderIds && input.orderIds.length > 1) { + const allReturns: VcReturn[] = []; + + for (const orderId of input.orderIds) { + const perOrderInput = { ...input, orderIds: [orderId] }; + const searchCriteria = mapReturnFiltersToSearchCriteria(perOrderInput); + + const response = await this.client.post( + '/api/return/search', + searchCriteria + ); + + if (response.success && response.data?.results) { + allReturns.push(...response.data.results); + } + } + + return this.transformAndReturn(allReturns, input); + } + + // Standard single-request path + const searchCriteria = mapReturnFiltersToSearchCriteria(input); + + const response = await this.client.post( + '/api/return/search', + searchCriteria + ); if (!response.success) { return this.failure<{ returns: Return[] }>( @@ -63,10 +127,8 @@ export class ReturnService extends BaseService { ); } - // TODO: Transform the response to Return[] type when VirtoCommerce return API is defined - return this.failure<{ returns: Return[] }>( - 'getReturns not yet implemented - please implement transformation logic' - ); + const vcReturns = response.data?.results ?? []; + return this.transformAndReturn(vcReturns, input); } catch (error: unknown) { return this.failure<{ returns: Return[] }>( `Return lookup failed: ${getErrorMessage(error)}`, @@ -74,4 +136,117 @@ export class ReturnService extends BaseService { ); } } + + /** + * Post-filter, resolve orders for SKU enrichment, transform, and return + */ + private async transformAndReturn( + vcReturns: VcReturn[], + input: GetReturnsInput + ): Promise> { + // Apply client-side post-filters + let filtered = this.applyPostFilters(vcReturns, input); + + // Apply pagination to filtered results + const pageSize = input.pageSize ?? 20; + const skip = input.skip ?? 0; + filtered = filtered.slice(skip, skip + pageSize); + + // Collect unique orderIds for SKU resolution + const orderIds = new Set(); + for (const vcReturn of filtered) { + if (vcReturn.orderId) { + orderIds.add(vcReturn.orderId); + } + } + + // Fetch orders for SKU/name enrichment + const orderMap = new Map(); + for (const orderId of orderIds) { + const order = await this.fetchOrder(orderId); + if (order) { + orderMap.set(orderId, order); + } + } + + const returns = this.transformer.toMcpReturns(filtered, orderMap); + return this.success<{ returns: Return[] }>({ returns }); + } + + /** + * Apply client-side filters that VC Return search doesn't support + */ + private applyPostFilters(vcReturns: VcReturn[], input: GetReturnsInput): VcReturn[] { + let results = vcReturns; + + // Filter by statuses (VC uses PascalCase, input may use MCP lowercase) + if (input.statuses?.length) { + const statusSet = new Set(input.statuses.map((s) => s.toLowerCase())); + results = results.filter((r) => { + const normalized = (r.status ?? '').toLowerCase(); + return statusSet.has(normalized); + }); + } + + // Filter by outcomes/resolution + if (input.outcomes?.length) { + const outcomeSet = new Set(input.outcomes.map((o) => o.toLowerCase())); + results = results.filter((r) => { + const normalized = (r.resolution ?? '').toLowerCase(); + return outcomeSet.has(normalized); + }); + } + + // Exact match on return numbers (keyword search is partial) + if (input.returnNumbers?.length) { + const numberSet = new Set(input.returnNumbers); + results = results.filter((r) => r.number && numberSet.has(r.number)); + } + + // Temporal filters + if (input.createdAtMin) { + const min = new Date(input.createdAtMin).getTime(); + results = results.filter((r) => r.createdDate && new Date(r.createdDate).getTime() >= min); + } + if (input.createdAtMax) { + const max = new Date(input.createdAtMax).getTime(); + results = results.filter((r) => r.createdDate && new Date(r.createdDate).getTime() <= max); + } + if (input.updatedAtMin) { + const min = new Date(input.updatedAtMin).getTime(); + results = results.filter((r) => r.modifiedDate && new Date(r.modifiedDate).getTime() >= min); + } + if (input.updatedAtMax) { + const max = new Date(input.updatedAtMax).getTime(); + results = results.filter((r) => r.modifiedDate && new Date(r.modifiedDate).getTime() <= max); + } + + return results; + } + + /** + * Fetch a single order by ID for SKU/name resolution + */ + private async fetchOrder(orderId: string): Promise { + try { + const response = await this.client.get( + `/api/order/customerOrders/${orderId}` + ); + return response.success && response.data ? response.data : null; + } catch { + return null; + } + } + + /** + * Fetch a single return by ID + */ + private async fetchReturnById(returnId: string): Promise { + try { + const response = await this.client.get(`/api/return/${returnId}`); + return response.success && response.data ? response.data : null; + } catch { + return null; + } + } } diff --git a/virtocommerce-adapter/src/transformers/index.ts b/virtocommerce-adapter/src/transformers/index.ts index 5e16983..2eff1f8 100644 --- a/virtocommerce-adapter/src/transformers/index.ts +++ b/virtocommerce-adapter/src/transformers/index.ts @@ -9,3 +9,4 @@ export { CustomerTransformer } from './customer.transformer.js'; export { OrderTransformer } from './order.transformer.js'; export { FulfillmentTransformer } from './fulfillment.transformer.js'; export { ProductTransformer } from './product.transformer.js'; +export { ReturnTransformer } from './return.transformer.js'; diff --git a/virtocommerce-adapter/src/transformers/return.transformer.ts b/virtocommerce-adapter/src/transformers/return.transformer.ts new file mode 100644 index 0000000..a159446 --- /dev/null +++ b/virtocommerce-adapter/src/transformers/return.transformer.ts @@ -0,0 +1,127 @@ +/** + * Return transformation utilities + * Bidirectional mapping between VirtoCommerce Return models and MCP Return format + */ + +import type { Return, ReturnLineItem, CreateReturnInput } from '@cof-org/mcp'; +import type { VcReturn, VcReturnLineItem, CustomerOrder, LineItem } from '../models/index.js'; +import { BaseTransformer } from './base.js'; + +/** + * VC Return status → MCP normalized status + */ +const RETURN_STATUS_MAP: Record = { + New: 'requested', + Approved: 'approved', + Processing: 'processing', + Completed: 'completed', + Canceled: 'cancelled', +}; + +/** + * MCP normalized status → VC Return status + */ +const REVERSE_RETURN_STATUS_MAP: Record = { + requested: 'New', + pending: 'New', + approved: 'Approved', + processing: 'Processing', + completed: 'Completed', + cancelled: 'Canceled', + canceled: 'Canceled', + declined: 'Canceled', +}; + +export class ReturnTransformer extends BaseTransformer { + /** + * Transform a VirtoCommerce Return to MCP Return format + */ + toMcpReturn(vcReturn: VcReturn, order?: CustomerOrder): Return { + const orderItems = order?.items ?? vcReturn.order?.items ?? []; + + return { + id: vcReturn.id ?? '', + returnNumber: vcReturn.number, + orderId: vcReturn.orderId ?? '', + status: this.mapReturnStatus(vcReturn.status), + outcome: vcReturn.resolution ?? '', + returnLineItems: (vcReturn.lineItems ?? []).map((item) => this.toMcpReturnLineItem(item, orderItems)), + createdAt: vcReturn.createdDate ?? this.now(), + updatedAt: vcReturn.modifiedDate ?? this.now(), + requestedAt: vcReturn.createdDate, + tenantId: this.tenantId, + }; + } + + /** + * Transform multiple VirtoCommerce Returns to MCP format + */ + toMcpReturns(vcReturns: VcReturn[], orderMap: Map): Return[] { + return vcReturns.map((vcReturn) => { + const order = vcReturn.orderId ? orderMap.get(vcReturn.orderId) : undefined; + return this.toMcpReturn(vcReturn, order); + }); + } + + /** + * Transform a VirtoCommerce ReturnLineItem to MCP ReturnLineItem format + */ + private toMcpReturnLineItem(vcItem: VcReturnLineItem, orderItems: LineItem[]): ReturnLineItem { + const orderLineItem = orderItems.find((li) => li.id === vcItem.orderLineItemId); + + return { + id: vcItem.id, + orderLineItemId: vcItem.orderLineItemId ?? '', + sku: orderLineItem?.sku ?? '', + quantityReturned: vcItem.quantity ?? 0, + returnReason: vcItem.reason ?? 'unknown', + unitPrice: vcItem.price, + name: orderLineItem?.name, + }; + } + + /** + * Transform MCP CreateReturnInput to VirtoCommerce Return for the PUT /api/return/ endpoint + */ + fromCreateReturnInput(input: CreateReturnInput, order: CustomerOrder): VcReturn { + const orderItems = order.items ?? []; + + return { + orderId: input.return.orderId, + status: this.reverseMapReturnStatus(input.return.status) ?? 'New', + resolution: input.return.outcome, + lineItems: (input.return.returnLineItems ?? []).map((item) => { + const orderLineItem = orderItems.find( + (li) => li.id === item.orderLineItemId || li.sku === item.sku + ); + + return { + orderLineItemId: item.orderLineItemId ?? orderLineItem?.id, + quantity: item.quantityReturned, + reason: item.returnReason, + price: item.unitPrice ?? orderLineItem?.price ?? 0, + }; + }), + }; + } + + /** + * Map VC Return status to MCP normalized status + */ + private mapReturnStatus(status?: string): string { + if (!status) { + return 'requested'; + } + return RETURN_STATUS_MAP[status] ?? status.toLowerCase(); + } + + /** + * Map MCP normalized status to VC Return status + */ + private reverseMapReturnStatus(status?: string): string | undefined { + if (!status) { + return undefined; + } + return REVERSE_RETURN_STATUS_MAP[status] ?? undefined; + } +} diff --git a/virtocommerce-adapter/src/types.ts b/virtocommerce-adapter/src/types.ts index 6714f58..0bcdcb4 100644 --- a/virtocommerce-adapter/src/types.ts +++ b/virtocommerce-adapter/src/types.ts @@ -186,6 +186,7 @@ export enum ErrorCode { CUSTOMER_NOT_FOUND = 'CUSTOMER_NOT_FOUND', INSUFFICIENT_INVENTORY = 'INSUFFICIENT_INVENTORY', INVALID_ORDER_STATE = 'INVALID_ORDER_STATE', + RETURN_NOT_FOUND = 'RETURN_NOT_FOUND', API_ERROR = 'API_ERROR', TIMEOUT = 'TIMEOUT', UNKNOWN_ERROR = 'UNKNOWN_ERROR', From 1a311fc2d6ca576456292c5f09dd16fed1f1166a Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Thu, 26 Feb 2026 14:21:29 +0000 Subject: [PATCH 46/51] prepare npm publishing --- .claude/settings.local.json | 4 +++- CLAUDE.md | 6 +++--- adapter-template/docs/API.md | 6 +++--- adapter-template/jest.config.js | 2 +- adapter-template/package.json | 2 +- adapter-template/src/adapter.ts | 4 ++-- adapter-template/tests/adapter.test.ts | 2 +- docs/getting-started/for-fulfillment-vendors.md | 2 +- docs/guides/configuration.md | 2 +- docs/specification/mcp-server-spec.md | 6 +++--- server/package.json | 2 +- virtocommerce-adapter/docs/API.md | 6 +++--- virtocommerce-adapter/examples/advanced-config.ts | 4 ++-- virtocommerce-adapter/jest.config.js | 2 +- virtocommerce-adapter/package-lock.json | 15 ++++++++------- virtocommerce-adapter/package.json | 4 ++-- virtocommerce-adapter/src/adapter.ts | 4 ++-- .../src/mappers/filter.mappers.ts | 2 +- .../src/services/base.service.ts | 4 ++-- .../src/services/customer.service.ts | 2 +- .../src/services/fulfillment.service.ts | 2 +- .../src/services/order.service.ts | 2 +- .../src/services/product.service.ts | 2 +- .../src/services/return.service.ts | 2 +- .../src/transformers/address.transformer.ts | 2 +- .../src/transformers/customer.transformer.ts | 2 +- .../src/transformers/fulfillment.transformer.ts | 2 +- .../src/transformers/order.transformer.ts | 2 +- .../src/transformers/product.transformer.ts | 2 +- .../src/transformers/return.transformer.ts | 2 +- virtocommerce-adapter/tests/adapter.test.ts | 2 +- 31 files changed, 53 insertions(+), 50 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index c2649f4..cde6279 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -50,7 +50,9 @@ "Bash(grep:*)", "Bash(find:*)", "Bash(head:*)", - "Bash(cd \"e:/Develops/VirtoWay/projects/tasks/4464/mcp-reference-server/server\" && npm run test:unit 2>&1)" + "Bash(cd \"e:/Develops/VirtoWay/projects/tasks/4464/mcp-reference-server/server\" && npm run test:unit 2>&1)", + "Bash(sed:*)", + "Bash(npm:*)" ] } } diff --git a/CLAUDE.md b/CLAUDE.md index b1c91d5..92d5bc5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,8 +7,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Commerce Operations Foundation (COF) MCP Reference Server — a monorepo implementing the Order Network eXchange (onX) standard for AI-powered fulfillment operations via the Model Context Protocol. ``` -/server - Core MCP server (@cof-org/mcp) — Vitest -/virtocommerce-adapter - VirtoCommerce fulfillment adapter (@virtocommerce/vc-fulfillment-mcp-adapter) — Jest +/server - Core MCP server (@virtocommerce/cof-mcp) — Vitest +/virtocommerce-adapter - VirtoCommerce fulfillment adapter (@virtocommerce/mcp-onx) — Jest /adapter-template - Boilerplate for creating new adapters /schemas - JSON Schema definitions for domain models /docs - Specification and architectural documentation @@ -45,7 +45,7 @@ npm test # Jest with --experimental-vm-modules npm run test:integration # Build + test-integration.js ``` -**Build order matters**: The adapter depends on the server via `"@cof-org/mcp": "file:../server"` — always build the server first. +**Build order matters**: The adapter depends on the server via `"@virtocommerce/cof-mcp": "file:../server"` — always build the server first. ### CI (GitHub Actions) diff --git a/adapter-template/docs/API.md b/adapter-template/docs/API.md index d0ea6d1..7545a68 100644 --- a/adapter-template/docs/API.md +++ b/adapter-template/docs/API.md @@ -2,7 +2,7 @@ ## YourFulfillment Adapter Reference -This document describes how the template adapter maps to the interfaces exposed by `@cof-org/mcp`. +This document describes how the template adapter maps to the interfaces exposed by `@virtocommerce/cof-mcp`. ## Initialization @@ -51,7 +51,7 @@ Each method should translate between platform types and your API’s payloads, t | `getInventory(input: GetInventoryInput)` | Reports stock levels. | | `getFulfillments(input: GetFulfillmentsInput)` | Lists fulfillment records. | -All responses use the `FulfillmentToolResult` union declared in `@cof-org/mcp`. On failure, include adapter specific error metadata so downstream tools can diagnose issues. +All responses use the `FulfillmentToolResult` union declared in `@virtocommerce/cof-mcp`. On failure, include adapter specific error metadata so downstream tools can diagnose issues. ## Error Handling @@ -67,7 +67,7 @@ if (!response.success) { ## Type Definitions -Reference `@cof-org/mcp` for shared fulfillment models and `src/types.ts` for YourFulfillment-specific shapes. Maintaining strict typing in adapters ensures the server’s JSON Schema validation continues to succeed. +Reference `@virtocommerce/cof-mcp` for shared fulfillment models and `src/types.ts` for YourFulfillment-specific shapes. Maintaining strict typing in adapters ensures the server’s JSON Schema validation continues to succeed. ## Best Practices diff --git a/adapter-template/jest.config.js b/adapter-template/jest.config.js index b6969ad..9c5c49d 100644 --- a/adapter-template/jest.config.js +++ b/adapter-template/jest.config.js @@ -34,7 +34,7 @@ export default { }, moduleNameMapper: { '^@/(.*)$': '/src/$1', - '^@cof-org/mcp$': '/node_modules/@cof-org/mcp/dist/index.js', + '^@virtocommerce/cof-mcp$': '/node_modules/@virtocommerce/cof-mcp/dist/index.js', '^(\\.{1,2}/.*)\\.js$': '$1' }, moduleDirectories: ['node_modules'], diff --git a/adapter-template/package.json b/adapter-template/package.json index d53677a..d1a3b4f 100644 --- a/adapter-template/package.json +++ b/adapter-template/package.json @@ -47,6 +47,6 @@ }, "dependencies": { "axios": "^1.6.0", - "@cof-org/mcp": "file:../server" + "@virtocommerce/cof-mcp": "file:../server" } } diff --git a/adapter-template/src/adapter.ts b/adapter-template/src/adapter.ts index 742cabd..c90f8c5 100644 --- a/adapter-template/src/adapter.ts +++ b/adapter-template/src/adapter.ts @@ -36,8 +36,8 @@ import type { CustomerAddress, OrderLineItem, CustomField, -} from '@cof-org/mcp'; -import { AdapterError } from '@cof-org/mcp'; +} from '@virtocommerce/cof-mcp'; +import { AdapterError } from '@virtocommerce/cof-mcp'; import { ApiClient } from './utils/api-client.js'; import type { AdapterOptions as TemplateAdapterOptions, diff --git a/adapter-template/tests/adapter.test.ts b/adapter-template/tests/adapter.test.ts index 7b7d5a2..8656336 100644 --- a/adapter-template/tests/adapter.test.ts +++ b/adapter-template/tests/adapter.test.ts @@ -14,7 +14,7 @@ import type { UpdateOrderInput, GetOrdersInput, GetInventoryInput, -} from '@cof-org/mcp'; +} from '@virtocommerce/cof-mcp'; describe('YourFulfillmentAdapter', () => { let adapter: YourFulfillmentAdapter; diff --git a/docs/getting-started/for-fulfillment-vendors.md b/docs/getting-started/for-fulfillment-vendors.md index 8888b93..5686bd9 100644 --- a/docs/getting-started/for-fulfillment-vendors.md +++ b/docs/getting-started/for-fulfillment-vendors.md @@ -16,7 +16,7 @@ Update `package.json`, rename the adapter class, and configure the connection op ## 2. Implement Required Methods -The `IFulfillmentAdapter` interface (exported from `@cof-org/mcp`) defines: +The `IFulfillmentAdapter` interface (exported from `@virtocommerce/cof-mcp`) defines: - Lifecycle: `connect`, `disconnect`, `healthCheck` - Action operations: `createSalesOrder`, `cancelOrder`, `updateOrder`, `fulfillOrder` diff --git a/docs/guides/configuration.md b/docs/guides/configuration.md index fc97d2d..7660acd 100644 --- a/docs/guides/configuration.md +++ b/docs/guides/configuration.md @@ -40,7 +40,7 @@ These files merge with defaults before environment variables are applied. The sc Use the `ConfigManager` singleton to inspect or change settings programmatically: ```typescript -import { ConfigManager } from '@cof-org/mcp'; +import { ConfigManager } from '@virtocommerce/cof-mcp'; const manager = ConfigManager.getInstance(); const loggingLevel = manager.get('logging.level'); diff --git a/docs/specification/mcp-server-spec.md b/docs/specification/mcp-server-spec.md index f03cb13..5312c91 100644 --- a/docs/specification/mcp-server-spec.md +++ b/docs/specification/mcp-server-spec.md @@ -632,7 +632,7 @@ interface FulfillmentAdapter { "version": "1.0.0", "main": "dist/index.js", "peerDependencies": { - "@cof-org/mcp": "^1.0.0" + "@virtocommerce/cof-mcp": "^1.0.0" } } ``` @@ -640,7 +640,7 @@ interface FulfillmentAdapter { 2. **Implement adapter:** ```typescript -import { IFulfillmentAdapter } from '@cof-org/mcp'; +import { IFulfillmentAdapter } from '@virtocommerce/cof-mcp'; export default class VendorAdapter implements IFulfillmentAdapter { constructor(options: any) { @@ -677,7 +677,7 @@ my-fulfillment-adapter/ 2. **Implement adapter:** ```typescript -import { IFulfillmentAdapter } from '@cof-org/mcp/types'; +import { IFulfillmentAdapter } from '@virtocommerce/cof-mcp/types'; export default class RetailerAdapter implements IFulfillmentAdapter { // Implementation diff --git a/server/package.json b/server/package.json index f04e808..d3baa84 100644 --- a/server/package.json +++ b/server/package.json @@ -1,5 +1,5 @@ { - "name": "@cof-org/mcp", + "name": "@virtocommerce/cof-mcp", "version": "1.0.0", "description": "Commerce Operations Foundation MCP Server", "type": "module", diff --git a/virtocommerce-adapter/docs/API.md b/virtocommerce-adapter/docs/API.md index d0ea6d1..7545a68 100644 --- a/virtocommerce-adapter/docs/API.md +++ b/virtocommerce-adapter/docs/API.md @@ -2,7 +2,7 @@ ## YourFulfillment Adapter Reference -This document describes how the template adapter maps to the interfaces exposed by `@cof-org/mcp`. +This document describes how the template adapter maps to the interfaces exposed by `@virtocommerce/cof-mcp`. ## Initialization @@ -51,7 +51,7 @@ Each method should translate between platform types and your API’s payloads, t | `getInventory(input: GetInventoryInput)` | Reports stock levels. | | `getFulfillments(input: GetFulfillmentsInput)` | Lists fulfillment records. | -All responses use the `FulfillmentToolResult` union declared in `@cof-org/mcp`. On failure, include adapter specific error metadata so downstream tools can diagnose issues. +All responses use the `FulfillmentToolResult` union declared in `@virtocommerce/cof-mcp`. On failure, include adapter specific error metadata so downstream tools can diagnose issues. ## Error Handling @@ -67,7 +67,7 @@ if (!response.success) { ## Type Definitions -Reference `@cof-org/mcp` for shared fulfillment models and `src/types.ts` for YourFulfillment-specific shapes. Maintaining strict typing in adapters ensures the server’s JSON Schema validation continues to succeed. +Reference `@virtocommerce/cof-mcp` for shared fulfillment models and `src/types.ts` for YourFulfillment-specific shapes. Maintaining strict typing in adapters ensures the server’s JSON Schema validation continues to succeed. ## Best Practices diff --git a/virtocommerce-adapter/examples/advanced-config.ts b/virtocommerce-adapter/examples/advanced-config.ts index 8588be1..82ac1cb 100644 --- a/virtocommerce-adapter/examples/advanced-config.ts +++ b/virtocommerce-adapter/examples/advanced-config.ts @@ -1,9 +1,9 @@ -import VirtoCommerceFulfillmentAdapter from '@virtocommerce/vc-fulfillment-mcp-adapter'; +import VirtoCommerceFulfillmentAdapter from '@virtocommerce/mcp-onx'; async function main() { const adapter = new VirtoCommerceFulfillmentAdapter({ type: 'npm', - package: '@virtocommerce/vc-fulfillment-mcp-adapter', + package: '@virtocommerce/mcp-onx', options: { apiUrl: 'https://api.yourfulfillment.com', apiKey: process.env.API_KEY || '', diff --git a/virtocommerce-adapter/jest.config.js b/virtocommerce-adapter/jest.config.js index be360e4..882f604 100644 --- a/virtocommerce-adapter/jest.config.js +++ b/virtocommerce-adapter/jest.config.js @@ -35,7 +35,7 @@ export default { }, moduleNameMapper: { '^@/(.*)$': '/src/$1', - '^@cof-org/mcp$': '/node_modules/@cof-org/mcp/dist/index.js', + '^@virtocommerce/cof-mcp$': '/node_modules/@virtocommerce/cof-mcp/dist/index.js', '^(\\.{1,2}/.*)\\.js$': '$1' }, moduleDirectories: ['node_modules'], diff --git a/virtocommerce-adapter/package-lock.json b/virtocommerce-adapter/package-lock.json index 5e7f2d2..ba947f2 100644 --- a/virtocommerce-adapter/package-lock.json +++ b/virtocommerce-adapter/package-lock.json @@ -1,15 +1,15 @@ { - "name": "@virtocommerce/vc-fulfillment-mcp-adapter", + "name": "@virtocommerce/mcp-onx", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@virtocommerce/vc-fulfillment-mcp-adapter", + "name": "@virtocommerce/mcp-onx", "version": "1.0.0", "license": "MIT", "dependencies": { - "@cof-org/mcp": "file:../server", + "@virtocommerce/cof-mcp": "^1.0.0", "axios": "^1.6.0" }, "devDependencies": { @@ -27,6 +27,7 @@ } }, "../server": { + "name": "@virtocommerce/cof-mcp", "version": "1.0.0", "license": "MIT", "dependencies": { @@ -543,10 +544,6 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "node_modules/@cof-org/mcp": { - "resolved": "../server", - "link": true - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -1585,6 +1582,10 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@virtocommerce/cof-mcp": { + "resolved": "../server", + "link": true + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", diff --git a/virtocommerce-adapter/package.json b/virtocommerce-adapter/package.json index bd19906..34680cf 100644 --- a/virtocommerce-adapter/package.json +++ b/virtocommerce-adapter/package.json @@ -1,5 +1,5 @@ { - "name": "@virtocommerce/vc-fulfillment-mcp-adapter", + "name": "@virtocommerce/mcp-onx", "version": "1.0.0", "description": "Commerce Operations Foundation adapter for VirtoCommerce Fulfillment", "repository": { @@ -54,7 +54,7 @@ "typescript-eslint": "^8.7.0" }, "dependencies": { - "@cof-org/mcp": "file:../server", + "@virtocommerce/cof-mcp": "^1.0.0", "axios": "^1.6.0" } } \ No newline at end of file diff --git a/virtocommerce-adapter/src/adapter.ts b/virtocommerce-adapter/src/adapter.ts index 39c34c1..33e3ae7 100644 --- a/virtocommerce-adapter/src/adapter.ts +++ b/virtocommerce-adapter/src/adapter.ts @@ -32,8 +32,8 @@ import type { GetCustomersInput, GetFulfillmentsInput, GetReturnsInput, -} from '@cof-org/mcp'; -import { AdapterError } from '@cof-org/mcp'; +} from '@virtocommerce/cof-mcp'; +import { AdapterError } from '@virtocommerce/cof-mcp'; import { ApiClient } from './utils/api-client.js'; import type { AdapterOptions } from './types.js'; import { ErrorCode } from './types.js'; diff --git a/virtocommerce-adapter/src/mappers/filter.mappers.ts b/virtocommerce-adapter/src/mappers/filter.mappers.ts index 87c23e5..859b0ed 100644 --- a/virtocommerce-adapter/src/mappers/filter.mappers.ts +++ b/virtocommerce-adapter/src/mappers/filter.mappers.ts @@ -11,7 +11,7 @@ import type { GetCustomersInput, GetFulfillmentsInput, GetReturnsInput, -} from '@cof-org/mcp'; +} from '@virtocommerce/cof-mcp'; import type { CustomerOrderSearchCriteria, MemberSearchCriteria, ProductSearchCriteria, ShipmentSearchCriteria, ReturnSearchCriteria } from '../models/index.js'; /** diff --git a/virtocommerce-adapter/src/services/base.service.ts b/virtocommerce-adapter/src/services/base.service.ts index 1abe110..963212e 100644 --- a/virtocommerce-adapter/src/services/base.service.ts +++ b/virtocommerce-adapter/src/services/base.service.ts @@ -2,8 +2,8 @@ * Base service with shared utilities for all domain services */ -import type { FulfillmentToolResult } from '@cof-org/mcp'; -import { AdapterError } from '@cof-org/mcp'; +import type { FulfillmentToolResult } from '@virtocommerce/cof-mcp'; +import { AdapterError } from '@virtocommerce/cof-mcp'; import { ErrorCode } from '../types.js'; import { ApiClient } from '../utils/api-client.js'; diff --git a/virtocommerce-adapter/src/services/customer.service.ts b/virtocommerce-adapter/src/services/customer.service.ts index 57acd1b..30cc4dd 100644 --- a/virtocommerce-adapter/src/services/customer.service.ts +++ b/virtocommerce-adapter/src/services/customer.service.ts @@ -2,7 +2,7 @@ * Customer service - handles customer-related operations */ -import type { Customer, FulfillmentToolResult, GetCustomersInput } from '@cof-org/mcp'; +import type { Customer, FulfillmentToolResult, GetCustomersInput } from '@virtocommerce/cof-mcp'; import type { Contact, MemberSearchCriteria } from '../models/index.js'; import { BaseService } from './base.service.js'; import { CustomerTransformer } from '../transformers/customer.transformer.js'; diff --git a/virtocommerce-adapter/src/services/fulfillment.service.ts b/virtocommerce-adapter/src/services/fulfillment.service.ts index bd19fc3..4f292b5 100644 --- a/virtocommerce-adapter/src/services/fulfillment.service.ts +++ b/virtocommerce-adapter/src/services/fulfillment.service.ts @@ -7,7 +7,7 @@ import type { FulfillmentToolResult, FulfillOrderInput, GetFulfillmentsInput, -} from '@cof-org/mcp'; +} from '@virtocommerce/cof-mcp'; import type { Shipment, CustomerOrder } from '../models/index.js'; import { BaseService } from './base.service.js'; import { FulfillmentTransformer } from '../transformers/fulfillment.transformer.js'; diff --git a/virtocommerce-adapter/src/services/order.service.ts b/virtocommerce-adapter/src/services/order.service.ts index 8e2e49a..49df766 100644 --- a/virtocommerce-adapter/src/services/order.service.ts +++ b/virtocommerce-adapter/src/services/order.service.ts @@ -10,7 +10,7 @@ import type { CancelOrderInput, UpdateOrderInput, GetOrdersInput, -} from '@cof-org/mcp'; +} from '@virtocommerce/cof-mcp'; import type { CustomerOrder, CustomerOrderSearchResult, diff --git a/virtocommerce-adapter/src/services/product.service.ts b/virtocommerce-adapter/src/services/product.service.ts index 9dc50d7..62a804e 100644 --- a/virtocommerce-adapter/src/services/product.service.ts +++ b/virtocommerce-adapter/src/services/product.service.ts @@ -10,7 +10,7 @@ import type { GetProductsInput, GetProductVariantsInput, GetInventoryInput, -} from '@cof-org/mcp'; +} from '@virtocommerce/cof-mcp'; import type { ProductSearchResult, InventorySearchResult, diff --git a/virtocommerce-adapter/src/services/return.service.ts b/virtocommerce-adapter/src/services/return.service.ts index 6e70b0f..33cc8e1 100644 --- a/virtocommerce-adapter/src/services/return.service.ts +++ b/virtocommerce-adapter/src/services/return.service.ts @@ -14,7 +14,7 @@ import type { FulfillmentToolResult, CreateReturnInput, GetReturnsInput, -} from '@cof-org/mcp'; +} from '@virtocommerce/cof-mcp'; import type { VcReturn, CustomerOrder, ReturnSearchResult } from '../models/index.js'; import { BaseService } from './base.service.js'; import { ReturnTransformer } from '../transformers/return.transformer.js'; diff --git a/virtocommerce-adapter/src/transformers/address.transformer.ts b/virtocommerce-adapter/src/transformers/address.transformer.ts index 6240869..aa969a1 100644 --- a/virtocommerce-adapter/src/transformers/address.transformer.ts +++ b/virtocommerce-adapter/src/transformers/address.transformer.ts @@ -2,7 +2,7 @@ * Address transformation utilities */ -import type { Address, CustomerAddress } from '@cof-org/mcp'; +import type { Address, CustomerAddress } from '@virtocommerce/cof-mcp'; import type { YourFulfillmentAddress } from '../types.js'; import type { Address as VirtoAddress } from '../models/index.js'; import { BaseTransformer } from './base.js'; diff --git a/virtocommerce-adapter/src/transformers/customer.transformer.ts b/virtocommerce-adapter/src/transformers/customer.transformer.ts index 5d22063..5fdd6ac 100644 --- a/virtocommerce-adapter/src/transformers/customer.transformer.ts +++ b/virtocommerce-adapter/src/transformers/customer.transformer.ts @@ -2,7 +2,7 @@ * Customer transformation utilities */ -import type { Customer } from '@cof-org/mcp'; +import type { Customer } from '@virtocommerce/cof-mcp'; import type { YourFulfillmentCustomer } from '../types.js'; import type { CustomerOrder, Contact, Address } from '../models/index.js'; import { BaseTransformer } from './base.js'; diff --git a/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts b/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts index e6ac185..bd6ee9a 100644 --- a/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts +++ b/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts @@ -2,7 +2,7 @@ * Fulfillment/Shipment transformation utilities */ -import type { Fulfillment, FulfillOrderInput } from '@cof-org/mcp'; +import type { Fulfillment, FulfillOrderInput } from '@virtocommerce/cof-mcp'; import type { Shipment, ShipmentItem, LineItem } from '../models/index.js'; import { BaseTransformer } from './base.js'; import { AddressTransformer } from './address.transformer.js'; diff --git a/virtocommerce-adapter/src/transformers/order.transformer.ts b/virtocommerce-adapter/src/transformers/order.transformer.ts index 0178ca1..13ef5e6 100644 --- a/virtocommerce-adapter/src/transformers/order.transformer.ts +++ b/virtocommerce-adapter/src/transformers/order.transformer.ts @@ -9,7 +9,7 @@ import type { Address, CreateSalesOrderInput, UpdateOrderInput, -} from '@cof-org/mcp'; +} from '@virtocommerce/cof-mcp'; import { STATUS_MAP, REVERSE_STATUS_MAP } from '../types.js'; import type { CustomerOrder, LineItem, Shipment, DynamicObjectProperty, Contact } from '../models/index.js'; import { BaseTransformer } from './base.js'; diff --git a/virtocommerce-adapter/src/transformers/product.transformer.ts b/virtocommerce-adapter/src/transformers/product.transformer.ts index 58852cf..aee1a22 100644 --- a/virtocommerce-adapter/src/transformers/product.transformer.ts +++ b/virtocommerce-adapter/src/transformers/product.transformer.ts @@ -2,7 +2,7 @@ * Product, Product Variant, and Inventory transformation utilities */ -import type { Product, ProductVariant, InventoryItem } from '@cof-org/mcp'; +import type { Product, ProductVariant, InventoryItem } from '@virtocommerce/cof-mcp'; import type { CatalogProduct, ProductProperty, InventoryInfo } from '../models/index.js'; import { BaseTransformer } from './base.js'; diff --git a/virtocommerce-adapter/src/transformers/return.transformer.ts b/virtocommerce-adapter/src/transformers/return.transformer.ts index a159446..6b7a115 100644 --- a/virtocommerce-adapter/src/transformers/return.transformer.ts +++ b/virtocommerce-adapter/src/transformers/return.transformer.ts @@ -3,7 +3,7 @@ * Bidirectional mapping between VirtoCommerce Return models and MCP Return format */ -import type { Return, ReturnLineItem, CreateReturnInput } from '@cof-org/mcp'; +import type { Return, ReturnLineItem, CreateReturnInput } from '@virtocommerce/cof-mcp'; import type { VcReturn, VcReturnLineItem, CustomerOrder, LineItem } from '../models/index.js'; import { BaseTransformer } from './base.js'; diff --git a/virtocommerce-adapter/tests/adapter.test.ts b/virtocommerce-adapter/tests/adapter.test.ts index 0a3bce4..9c0fdef 100644 --- a/virtocommerce-adapter/tests/adapter.test.ts +++ b/virtocommerce-adapter/tests/adapter.test.ts @@ -19,7 +19,7 @@ import type { GetProductsInput, GetProductVariantsInput, GetFulfillmentsInput, -} from '@cof-org/mcp'; +} from '@virtocommerce/cof-mcp'; function readResponse(path: string) { return readFileSync(new URL(`./fixtures/${path}.json`, import.meta.url), 'utf-8'); From 6958ecb6794f150736c09317e6946a971575275e Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Thu, 26 Feb 2026 20:02:00 +0000 Subject: [PATCH 47/51] revert virtocommerce/cof-mcp package --- .claude/settings.local.json | 6 +- .dockerignore | 35 -- .gitignore | 4 - .serena/.gitignore | 1 + .serena/project.yml | 124 ++++ CLAUDE.md | 4 +- Dockerfile | 66 --- MCP_TEST_PROMPTS_RESULTS.md | 307 ---------- adapter-template/docs/API.md | 6 +- adapter-template/jest.config.js | 2 +- adapter-template/package.json | 2 +- adapter-template/src/adapter.ts | 4 +- adapter-template/tests/adapter.test.ts | 2 +- docker-compose.yml | 15 - .../for-fulfillment-vendors.md | 2 +- docs/guides/configuration.md | 2 +- docs/specification/mcp-server-spec.md | 6 +- server/CLAUDE.md | 2 +- server/package.json | 2 +- server/src/adapters/adapter-factory.ts | 5 +- server/src/adapters/mock/mock-adapter.ts | 3 + server/src/logging/structured-logger.ts | 7 +- server/src/schemas/entities/order.ts | 3 +- virtocommerce-adapter/docs/API.md | 6 +- virtocommerce-adapter/jest.config.js | 2 +- virtocommerce-adapter/package-lock.json | 554 +++++++++++++++++- virtocommerce-adapter/package.json | 2 +- virtocommerce-adapter/src/adapter.ts | 4 +- .../src/mappers/filter.mappers.ts | 2 +- .../src/services/base.service.ts | 4 +- .../src/services/customer.service.ts | 2 +- .../src/services/fulfillment.service.ts | 2 +- .../src/services/order.service.ts | 2 +- .../src/services/product.service.ts | 2 +- .../src/services/return.service.ts | 2 +- .../src/transformers/address.transformer.ts | 2 +- .../src/transformers/customer.transformer.ts | 2 +- .../transformers/fulfillment.transformer.ts | 2 +- .../src/transformers/order.transformer.ts | 2 +- .../src/transformers/product.transformer.ts | 2 +- .../src/transformers/return.transformer.ts | 2 +- virtocommerce-adapter/tests/adapter.test.ts | 2 +- 42 files changed, 719 insertions(+), 489 deletions(-) delete mode 100644 .dockerignore create mode 100644 .serena/.gitignore create mode 100644 .serena/project.yml delete mode 100644 Dockerfile delete mode 100644 MCP_TEST_PROMPTS_RESULTS.md delete mode 100644 docker-compose.yml diff --git a/.claude/settings.local.json b/.claude/settings.local.json index cde6279..134a6b6 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -52,7 +52,11 @@ "Bash(head:*)", "Bash(cd \"e:/Develops/VirtoWay/projects/tasks/4464/mcp-reference-server/server\" && npm run test:unit 2>&1)", "Bash(sed:*)", - "Bash(npm:*)" + "Bash(npm:*)", + "WebSearch", + "WebFetch(domain:www.npmjs.com)", + "WebFetch(domain:github.com)", + "Bash(mkdir:*)" ] } } diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index b4d6574..0000000 --- a/.dockerignore +++ /dev/null @@ -1,35 +0,0 @@ -node_modules -dist -coverage -*.tsbuildinfo - -.env* -!.env.example - -.git -.gitignore -.gitattributes - -.vscode/ -.idea/ -.claude/ - -docs/ -prompts/ -schemas/ -adapter-template/ - -**/tests/ -**/*.test.ts -**/*.spec.ts - -*.md -!package.json - -logs/ -*.log -tmp/ -temp/ - -.DS_Store -Thumbs.db diff --git a/.gitignore b/.gitignore index e5aa235..72a21d8 100644 --- a/.gitignore +++ b/.gitignore @@ -38,7 +38,3 @@ working-docs/ .idea/ *.sublime-workspace *.sublime-project - -# AI tools -.claude/ -.serena/ \ No newline at end of file diff --git a/.serena/.gitignore b/.serena/.gitignore new file mode 100644 index 0000000..14d86ad --- /dev/null +++ b/.serena/.gitignore @@ -0,0 +1 @@ +/cache diff --git a/.serena/project.yml b/.serena/project.yml new file mode 100644 index 0000000..c8ccf67 --- /dev/null +++ b/.serena/project.yml @@ -0,0 +1,124 @@ +# the name by which the project can be referenced within Serena +project_name: "mcp-reference-server" + + +# list of languages for which language servers are started; choose from: +# al bash clojure cpp csharp +# csharp_omnisharp dart elixir elm erlang +# fortran fsharp go groovy haskell +# java julia kotlin lua markdown +# matlab nix pascal perl php +# php_phpactor powershell python python_jedi r +# rego ruby ruby_solargraph rust scala +# swift terraform toml typescript typescript_vts +# vue yaml zig +# (This list may be outdated. For the current list, see values of Language enum here: +# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py +# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.) +# Note: +# - For C, use cpp +# - For JavaScript, use typescript +# - For Free Pascal/Lazarus, use pascal +# Special requirements: +# Some languages require additional setup/installations. +# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers +# When using multiple languages, the first language server that supports a given file will be used for that file. +# The first language is the default language and the respective language server will be used as a fallback. +# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. +languages: +- typescript + +# the encoding used by text files in the project +# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings +encoding: "utf-8" + +# whether to use project's .gitignore files to ignore files +ignore_all_files_in_gitignore: true + +# list of additional paths to ignore in this project. +# Same syntax as gitignore, so you can use * and **. +# Note: global ignored_paths from serena_config.yml are also applied additively. +ignored_paths: [] + +# whether the project is in read-only mode +# If set to true, all editing tools will be disabled and attempts to use them will result in an error +# Added on 2025-04-18 +read_only: false + +# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details. +# Below is the complete list of tools for convenience. +# To make sure you have the latest list of tools, and to view their descriptions, +# execute `uv run scripts/print_tool_overview.py`. +# +# * `activate_project`: Activates a project by name. +# * `check_onboarding_performed`: Checks whether project onboarding was already performed. +# * `create_text_file`: Creates/overwrites a file in the project directory. +# * `delete_lines`: Deletes a range of lines within a file. +# * `delete_memory`: Deletes a memory from Serena's project-specific memory store. +# * `execute_shell_command`: Executes a shell command. +# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. +# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). +# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). +# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. +# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. +# * `initial_instructions`: Gets the initial instructions for the current project. +# Should only be used in settings where the system prompt cannot be set, +# e.g. in clients you have no control over, like Claude Desktop. +# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. +# * `insert_at_line`: Inserts content at a given line in a file. +# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. +# * `list_dir`: Lists files and directories in the given directory (optionally with recursion). +# * `list_memories`: Lists memories in Serena's project-specific memory store. +# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). +# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). +# * `read_file`: Reads a file within the project directory. +# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. +# * `remove_project`: Removes a project from the Serena configuration. +# * `replace_lines`: Replaces a range of lines within a file with new content. +# * `replace_symbol_body`: Replaces the full definition of a symbol. +# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. +# * `search_for_pattern`: Performs a search for a pattern in the project. +# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. +# * `switch_modes`: Activates modes by providing a list of their names +# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. +# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. +# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. +# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. +excluded_tools: [] + +# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default) +included_optional_tools: [] + +# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools. +# This cannot be combined with non-empty excluded_tools or included_optional_tools. +fixed_tools: [] + +# list of mode names to that are always to be included in the set of active modes +# The full set of modes to be activated is base_modes + default_modes. +# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply. +# Otherwise, this setting overrides the global configuration. +# Set this to [] to disable base modes for this project. +# Set this to a list of mode names to always include the respective modes for this project. +base_modes: + +# list of mode names that are to be activated by default. +# The full set of modes to be activated is base_modes + default_modes. +# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply. +# Otherwise, this overrides the setting from the global configuration (serena_config.yml). +# This setting can, in turn, be overridden by CLI parameters (--mode). +default_modes: + +# initial prompt for the project. It will always be given to the LLM upon activating the project +# (contrary to the memories, which are loaded on demand). +initial_prompt: "" + +# override of the corresponding setting in serena_config.yml, see the documentation there. +# If null or missing, the value from the global config is used. +symbol_info_budget: + +# The language backend to use for this project. +# If not set, the global setting from serena_config.yml is used. +# Valid values: LSP, JetBrains +# Note: the backend is fixed at startup. If a project with a different backend +# is activated post-init, an error will be returned. +language_backend: diff --git a/CLAUDE.md b/CLAUDE.md index 92d5bc5..fce8785 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,7 +7,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Commerce Operations Foundation (COF) MCP Reference Server — a monorepo implementing the Order Network eXchange (onX) standard for AI-powered fulfillment operations via the Model Context Protocol. ``` -/server - Core MCP server (@virtocommerce/cof-mcp) — Vitest +/server - Core MCP server (@cof-org/mcp) — Vitest /virtocommerce-adapter - VirtoCommerce fulfillment adapter (@virtocommerce/mcp-onx) — Jest /adapter-template - Boilerplate for creating new adapters /schemas - JSON Schema definitions for domain models @@ -45,7 +45,7 @@ npm test # Jest with --experimental-vm-modules npm run test:integration # Build + test-integration.js ``` -**Build order matters**: The adapter depends on the server via `"@virtocommerce/cof-mcp": "file:../server"` — always build the server first. +**Build order matters**: The adapter depends on the server via `"@cof-org/mcp": "file:../server"` — always build the server first. ### CI (GitHub Actions) diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index d965fac..0000000 --- a/Dockerfile +++ /dev/null @@ -1,66 +0,0 @@ -# Multi-stage build: MCP Server + VirtoCommerce Adapter -# Build context: repository root - -# --- Stage 1: Build both packages --- -FROM node:22-alpine AS builder -WORKDIR /app - -# Server: install deps & build -COPY server/package*.json server/ -RUN cd server && npm ci - -COPY server/tsconfig.json server/ -COPY server/src/ server/src/ -RUN cd server && npm run build - -# VirtoCommerce adapter: install deps & build -# (depends on server via file:../server, so server must be present) -COPY virtocommerce-adapter/package*.json virtocommerce-adapter/ -RUN cd virtocommerce-adapter && npm ci - -COPY virtocommerce-adapter/tsconfig.json virtocommerce-adapter/ -COPY virtocommerce-adapter/src/ virtocommerce-adapter/src/ -RUN cd virtocommerce-adapter && npm run build - -# --- Stage 2: Production deps only --- -FROM node:22-alpine AS prod-deps -WORKDIR /app - -COPY server/package*.json server/ -RUN cd server && npm ci --omit=dev - -COPY virtocommerce-adapter/package*.json virtocommerce-adapter/ -COPY --from=builder /app/server/ server/ -RUN cd virtocommerce-adapter && npm ci --omit=dev - -# --- Stage 3: Runtime --- -FROM node:22-alpine - -WORKDIR /app - -# Server -COPY --from=builder /app/server/dist server/dist -COPY --from=prod-deps /app/server/node_modules server/node_modules -COPY server/package.json server/ - -# VirtoCommerce adapter -COPY --from=builder /app/virtocommerce-adapter/dist virtocommerce-adapter/dist -COPY --from=prod-deps /app/virtocommerce-adapter/node_modules virtocommerce-adapter/node_modules -COPY virtocommerce-adapter/package.json virtocommerce-adapter/ - -RUN mkdir -p /app/logs - -ENV NODE_ENV=production -ENV LOG_LEVEL=info -ENV ADAPTER_TYPE=local -ENV ADAPTER_PATH=/app/virtocommerce-adapter/dist/index.js -ENV MCP_TRANSPORT=http -ENV MCP_PORT=80 - -EXPOSE 80 - -HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ - CMD wget -qO- http://localhost:80/health || exit 1 - -WORKDIR /app/server -CMD ["node", "dist/index.js"] diff --git a/MCP_TEST_PROMPTS_RESULTS.md b/MCP_TEST_PROMPTS_RESULTS.md deleted file mode 100644 index 83da377..0000000 --- a/MCP_TEST_PROMPTS_RESULTS.md +++ /dev/null @@ -1,307 +0,0 @@ -# MCP Test Prompts — Verification Results - -The file `MCP_TEST_PROMPTS.md` was written for an early version of the server ("Universal OMS MCP Server") with the mock adapter. Below is an analysis of each block of prompts for compatibility with the current state of the MCP server. - ---- - -## Tool Mapping: Document vs Actual Server - -| # | Document | Actual Tool | Status | -| --- | ---------------------- | -------------------- | ------------------ | -| 1 | Get Order Tool | `get-orders` | :white_check_mark: | -| 2 | Get Customer Tool | `get-customers` | :white_check_mark: | -| 3 | Get Product Tool | `get-products` | :white_check_mark: | -| 4 | Get Inventory Tool | `get-inventory` | :white_check_mark: | -| 5 | Get Shipment Tool | `get-fulfillments` | :white_check_mark: | -| 6 | Get Buyer Tool | — | :white_check_mark: | -| 7 | Capture Order Tool | `create-sales-order` | | -| 8 | Cancel Order Tool | `cancel-order` | | -| 9 | Update Order Tool | `update-order` | | -| 10 | Return Order Tool | `create-return` | | -| 11 | Exchange Order Tool | — | | -| 12 | Ship Order Tool | `fulfill-order` | | -| 13 | Hold Order Tool | — | | -| 14 | Split Order Tool | — | | -| 15 | Reserve Inventory Tool | — | | - -**5 out of 15 tools from the document do not exist in the current server.** Another 3 have different names. - ---- - -## Test Data - -The test data in the document **correctly matches** the mock adapter (`server/src/adapters/mock/mock-data.ts`): - -| Entity | Document | Mock Data | Match | -| ------------------------- | ------------------------- | -------------------------- | ----- | -| order_001 / EXT-001 | confirmed, John Smith | :white_check_mark: Present | Yes | -| order_002 / ORD-1001 | processing, Sarah Johnson | :white_check_mark: Present | Yes | -| order_003 / WEB-2024-1002 | shipped, John Smith | :white_check_mark: Present | Yes | -| cust_001 John Smith | john.smith@example.com | :white_check_mark: Present | Yes | -| cust_002 Sarah Johnson | sarah.johnson@example.com | :white_check_mark: Present | Yes | -| prod_001 / WID-001 | Headphones $199.99 | :white_check_mark: Present | Yes | -| prod_002 / TSH-002 | T-Shirt $29.99 | :white_check_mark: Present | Yes | -| prod_003 / COF-003 | Coffee $24.99 | :white_check_mark: Present | Yes | -| WH001, WH002, WH003 | Warehouses | :white_check_mark: Present | Yes | - -**Important:** Test data only works with the mock adapter (`ADAPTER_TYPE=built-in`, `ADAPTER_NAME=mock`). With the VirtoCommerce adapter, data will come from the actual VC instance. - ---- - -## Query Tools Test Prompts - -### 1. Get Order Tool — :white_check_mark: WORKS (with caveats) - -| Prompt | How the agent should call | Result | -| ----------------------------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------- | -| "Get me the details for order EXT-001" | `get-orders` with `externalIds: ["EXT-001"]` | :white_check_mark: Works — `externalIds` maps to VC `outerIds` + `numbers` | -| "Show me order order_002" | `get-orders` with `ids: ["order_002"]` | :white_check_mark: Works — direct lookup by ID | -| "What's the status of order ORD-1001?" | `get-orders` with `externalIds: ["ORD-1001"]` or `names: ["ORD-1001"]` | :white_check_mark: Works — `names` maps to `numbers` | -| "Can you retrieve order WEB-2024-1002..." | `get-orders` with `externalIds: ["WEB-2024-1002"]` | :white_check_mark: Works | - -**Note:** The agent must determine whether the identifier is an internal ID (`order_002`), external ID (`EXT-001`), or order number (`ORD-1001`), and use the corresponding parameter. For natural language prompts, this is the AI agent's responsibility. - ---- - -### 2. Get Customer Tool — :warning: PARTIALLY WORKS - -| Prompt | How the agent should call | Result | -| --------------------------------------------------------------------- | ------------------------------------------------------------ | ------------------------ | -| "Get customer information for cust_001" | `get-customers` with `ids: ["cust_001"]` | :white_check_mark: Works | -| "Show me the details for customer john.smith@example.com" | `get-customers` with `emails: ["john.smith@example.com"]` | :white_check_mark: Works | -| "Find customer cust_002" | `get-customers` with `ids: ["cust_002"]` | :white_check_mark: Works | -| "What information do you have on customer sarah.johnson@example.com?" | `get-customers` with `emails: ["sarah.johnson@example.com"]` | :white_check_mark: Works | - -**All prompts work** because they use ID or email. Prompts with name search ("Find John Smith") are not included — and that's correct, since `get-customers` does not support name search. - ---- - -### 3. Get Product Tool — :white_check_mark: WORKS - -| Prompt | How the agent should call | Result | -| ------------------------------------------------------ | --------------------------------------- | ------------------------ | -| "Show me product details for SKU WID-001" | `get-products` with `skus: ["WID-001"]` | :white_check_mark: Works | -| "Get information about product prod_002" | `get-products` with `ids: ["prod_002"]` | :white_check_mark: Works | -| "What are the details for the coffee product COF-003?" | `get-products` with `skus: ["COF-003"]` | :white_check_mark: Works | -| "Find product TSH-002 and show me all its attributes" | `get-products` with `skus: ["TSH-002"]` | :white_check_mark: Works | - -**All prompts work** — they use direct lookup by SKU or product ID. - ---- - -### 4. Get Inventory Tool — :white_check_mark: WORKS - -| Prompt | How the agent should call | Result | -| ------------------------------------------------------------ | ---------------------------------------------------------------- | ------------------------------------------------ | -| "Check inventory for SKU WID-001 at warehouse WH001" | `get-inventory` with `skus: ["WID-001"], locationIds: ["WH001"]` | :white_check_mark: Works | -| "What's the available stock for TSH-002 in location WH002?" | `get-inventory` with `skus: ["TSH-002"], locationIds: ["WH002"]` | :white_check_mark: Works | -| "Show me inventory levels for COF-003 across all warehouses" | `get-inventory` with `skus: ["COF-003"]` (without locationIds) | :white_check_mark: Works — returns all locations | -| "Get inventory status for WID-001 at WH003" | `get-inventory` with `skus: ["WID-001"], locationIds: ["WH003"]` | :white_check_mark: Works | - ---- - -### 5. Get Shipment Tool — :white_check_mark: WORKS - -Actual tool name: **`get-fulfillments`** (not "get-shipment"). - -| Prompt | How the agent should call | Result | -| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------------- | -| "Get shipment details for order order_003" | `get-fulfillments` with `orderIds: ["order_003"]` | :white_check_mark: Works | -| "Show me the shipment information for order WEB-2024-1002" | First `get-orders` with `externalIds: ["WEB-2024-1002"]` to get ID, then `get-fulfillments` | :white_check_mark: Works (two-step) | -| "Check if order_001 has been shipped" | `get-fulfillments` with `orderIds: ["order_001"]` | :white_check_mark: Works — empty result = not shipped | -| "Find shipment tracking for order EXT-001" | Similarly — resolve external ID then get-fulfillments | :white_check_mark: Works (two-step) | - -**Note:** Prompts use external IDs, but `get-fulfillments` only accepts `orderIds` (internal). The agent must first resolve the external ID via `get-orders`. - ---- - -### 6. Get Buyer Tool — :x: DOES NOT EXIST - -| Prompt | Result | -| -------------------------------------------------- | --------------------------------------- | -| "Get buyer information for order order_001" | :x: The `get-buyer` tool does not exist | -| "Who is the buyer for order ORD-1001?" | :x: No such tool | -| "Show me the buyer details for order_002" | :x: No such tool | -| "Find the customer who placed order WEB-2024-1002" | :x: No such tool | - -**Workaround:** `get-orders` returns a `customer` object as part of the order (with `includeLineItems: true`, default). The agent can get customer info from the `get-orders` response. A separate "Get Buyer" tool is not needed. - ---- - -## Action Tools Test Prompts - -### 7. Capture Order Tool — :white_check_mark: WORKS - -Actual name: **`create-sales-order`** (not "capture-order"). - -| Prompt | How the agent should call | Result | -| --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| "Create a new order for customer cust_001 with 2 units of WID-001 shipping to 123 Main St..." | `create-sales-order` with `order: { customer: { id: "cust_001" }, lineItems: [{ sku: "WID-001", quantity: 2 }], shippingAddress: { ... } }` | :white_check_mark: Works | -| "Capture an order for sarah.johnson@example.com with 1 TSH-002 and 2 COF-003" | `create-sales-order` with `customer: { email: "..." }, lineItems: [...]` | :white_check_mark: Works | -| "Place an order for customer cust_002 with product WID-001, quantity 1..." | Similarly | :white_check_mark: Works | - -**Note:** `lineItems` must contain `sku` and `quantity`. `unitPrice` is optional in the schema, but without it VirtoCommerce may create an order with zero prices. - ---- - -### 8. Cancel Order Tool — :white_check_mark: WORKS - -| Prompt | How the agent should call | Result | -| --------------------------------------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------- | -| "Cancel order order_001 due to customer request" | `cancel-order` with `orderId: "order_001", reason: "customer request"` | :white_check_mark: Works | -| "Please cancel order EXT-001 - the customer changed their mind" | First resolve EXT-001 to internal ID, then `cancel-order` | :warning: Two-step — the agent must resolve the external ID | -| "Cancel order ORD-1001 because of inventory issues" | Similarly — resolve then cancel | :warning: Two-step | - -**Note:** `cancel-order` accepts `orderId` (internal ID), not external ID or order number. The agent must first call `get-orders` to resolve. - ---- - -### 9. Update Order Tool — :warning: PARTIALLY WORKS - -| Prompt | How the agent should call | Result | -| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| "Update order order_002 to change the quantity of TSH-002 to 3 units" | `update-order` with `id: "order_002", updates: { lineItems: [{ sku: "TSH-002", quantity: 3, unitPrice: 29.99 }] }` | :white_check_mark: Works — `unitPrice` is required in update lineItems | -| "Modify order EXT-001 to ship to 789 Broadway..." | Resolve EXT-001 then `update-order` with `updates: { shippingAddress: {...} }` | :white_check_mark: Works (two-step) | -| "Update order ORD-1001 with express shipping" | `update-order` with `updates: { shippingClass: "Express" }` | :warning: Partial — `shippingClass` is part of `ShippingInfoSchema`, but the adapter's `applyUpdatesToOrder` does not handle shipping method changes (only address, status, lineItems, notes) | - ---- - -### 10. Return Order Tool — :x: DOES NOT WORK (stub) - -Actual name: **`create-return`**. - -| Prompt | Result | -| ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -| "Process a return for order order_003 - customer says the coffee tastes bad" | :x: `create-return` exists, but the VC adapter returns a hardcoded failure: "createReturn not yet implemented" | -| "Create a return for order WEB-2024-1002 with reason damaged during shipping" | :x: Same — stub | -| "Return order order_001 because the headphones don't work" | :x: Same — stub | - -**With mock adapter:** Returns work (mock data contains `return_001`, `return_002`, `return_003`). With VirtoCommerce adapter — stub. - ---- - -### 11. Exchange Order Tool — :x: DOES NOT EXIST - -| Prompt | Result | -| ---------------------------------------------------------------------------- | -------------------- | -| "Exchange order order_001 - customer wants TSH-002 instead of WID-001" | :x: No exchange tool | -| "Process an exchange for order ORD-1001 - swap the t-shirt for coffee beans" | :x: No such tool | -| "Exchange the items in order_002 for different products" | :x: No such tool | - -**Note:** An exchange can be modeled as `create-return` (outcome: "exchange") + `create-sales-order`. But `create-return` is a stub in the VC adapter. - ---- - -### 12. Ship Order Tool — :white_check_mark: WORKS - -Actual name: **`fulfill-order`** (not "ship-order"). - -| Prompt | How the agent should call | Result | -| ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | -| "Mark order order_001 as shipped with tracking number TRK123456789" | `fulfill-order` with `orderId: "order_001", trackingNumbers: ["TRK123456789"], lineItems: [{ sku: "WID-001", quantity: 1 }]` | :white_check_mark: Works | -| "Ship order EXT-001 via FedEx with tracking FDX987654321" | Resolve then `fulfill-order` with `shippingCarrier: "FedEx", trackingNumbers: ["FDX987654321"]` | :white_check_mark: Works (two-step) | -| "Process shipment for order_002 using UPS tracking 1Z999AA10123456784" | `fulfill-order` with corresponding parameters | :white_check_mark: Works | - -**Note:** `fulfill-order` requires `orderId`, `lineItems` (with SKU + quantity), and `trackingNumbers` — all mandatory. The adapter adds a shipment to the order via the GET -> add shipment -> PUT flow. - ---- - -## Management Tools Test Prompts - -### 13. Hold Order Tool — :x: DOES NOT EXIST - -| Prompt | Result | -| ------------------------------------------------ | ----------------------- | -| "Put order order_001 on hold" | :x: No hold/unhold tool | -| "Hold order EXT-001 due to address verification" | :x: | -| "Place a hold on order_002" | :x: | -| "Release the hold on order ORD-1001" | :x: | - -**Workaround:** You can use `update-order` with `updates: { status: "on_hold" }`, since `applyUpdatesToOrder` supports status changes. However, it depends on the VirtoCommerce state machine — the transition to "OnHold" may be allowed or disallowed. - ---- - -### 14. Split Order Tool — :x: DOES NOT EXIST - -| Prompt | Result | -| -------------------------------------------------------------- | ----------------- | -| "Split order order_002 so that 1 TSH-002 ships immediately..." | :x: No split tool | -| "I need to split order_001 into two separate shipments" | :x: | -| "Divide order WEB-2024-1002..." | :x: | - -**Workaround:** You can create a fulfillment with a subset of items via `fulfill-order` (specifying a subset of line items), leaving the rest unfulfilled. But this is not a split at the order level. - ---- - -### 15. Reserve Inventory Tool — :x: DOES NOT EXIST - -| Prompt | Result | -| -------------------------------------------------------- | ------------------------------------- | -| "Reserve 5 units of WID-001 from warehouse WH001" | :x: No reserve/release inventory tool | -| "Hold 10 units of TSH-002 inventory at location WH002" | :x: | -| "Reserve 3 COF-003 from WH003 for upcoming order" | :x: | -| "Release the reservation on 2 units of WID-001 at WH001" | :x: | - ---- - -## Complex Workflow Test Prompts - -### Multi-Tool Operations — :warning: PARTIALLY WORK - -| Prompt | Result | -| ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| "Get order order_001, then check inventory for all its items, and finally ship it with tracking ABC123" | :white_check_mark: Works — `get-orders` -> extract SKUs -> `get-inventory` -> `fulfill-order`. All three tools exist. | -| "Find customer cust_002, show me all their orders, and then check product availability for TSH-002" | :warning: Partial — `get-customers` works, but "all their orders" requires a `customerIds` filter in `get-orders` (does not exist). `get-inventory` for TSH-002 works. | -| "Create a new order for john.smith@example.com with 2 WID-001, then put it on hold for payment verification" | :warning: Partial — `create-sales-order` works. "Put on hold" can be done via `update-order` with `status: "on_hold"`, but there's no guarantee the VC state machine will allow the transition. | - ---- - -### Error Handling Tests — :white_check_mark: WORK - -| Prompt | Result | -| ------------------------------------------------------ | --------------------------------------------------------------------------------------- | -| "Get order INVALID_ORDER_ID" | :white_check_mark: Will return an empty result or error response | -| "Cancel an order that doesn't exist: order_999" | :white_check_mark: `cancel-order` -> adapter will return "Order not found" | -| "Check inventory for a non-existent SKU: FAKE-SKU-001" | :white_check_mark: `get-inventory` -> empty array (SKU doesn't resolve to a product ID) | -| "Get customer information for unknown@email.com" | :white_check_mark: `get-customers` -> empty customers array | - ---- - -### Business Logic Tests — :warning: PARTIALLY WORK - -| Prompt | Result | -| -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| "Cancel order order_003 (already shipped - should fail)" | :warning: The adapter doesn't check the "shipped" status — it only checks `isCancelled`. It may attempt to cancel and get an error from the VC state machine, or successfully cancel (depends on VC settings). | -| "Ship order order_001 twice (should handle duplicate shipment)" | :white_check_mark: `fulfill-order` adds a new shipment to the order — two calls will create two shipments. Not an error, but may be undesirable behavior. | -| "Reserve 1000 units of WID-001 (likely exceeds available inventory)" | :x: `reserve-inventory` does not exist | - ---- - -## Final Summary - -### By Tool Groups - -| Group | Total Prompts | Works | Partial | Does Not Work | -| ------------------------ | ------------- | ------ | ------- | ------------- | -| Query Tools (1-5) | 20 | 18 | 2 | 0 | -| Get Buyer (6) | 4 | 0 | 0 | 4 | -| Action Tools (7-12) | 15 | 6 | 3 | 6 | -| Management Tools (13-15) | 10 | 0 | 0 | 10 | -| Complex Workflows | 3 | 1 | 2 | 0 | -| Error Handling | 4 | 4 | 0 | 0 | -| Business Logic | 3 | 1 | 1 | 1 | -| **TOTAL** | **59** | **30** | **8** | **21** | - -### Key Document Issues - -1. **5 non-existent tools** (Get Buyer, Exchange Order, Hold Order, Split Order, Reserve Inventory) — 18 prompts test things that don't exist. -2. **3 tools with incorrect names** — "Get Shipment" is `get-fulfillments`, "Capture Order" is `create-sales-order`, "Ship Order" is `fulfill-order`. The AI agent can figure it out from the description, but the documentation is misleading. -3. **External ID to Internal ID resolution** is not accounted for — many prompts use external IDs (EXT-001, ORD-1001), but write operations (`cancel-order`, `update-order`, `fulfill-order`) require the internal order ID. -4. **`create-return` is a stub** in the VirtoCommerce adapter — return prompts don't work with VC. - -### Recommendations - -1. **Remove or mark as "planned"** prompts for non-existent tools (6, 11, 13, 14, 15). -2. **Update tool names** in the document: capture -> create-sales-order, ship -> fulfill-order, get-shipment -> get-fulfillments. -3. **Add a note** about the need to resolve external ID to internal ID for write operations. -4. **Separate** prompts for mock adapter and VirtoCommerce adapter (returns only work with mock). diff --git a/adapter-template/docs/API.md b/adapter-template/docs/API.md index 7545a68..d0ea6d1 100644 --- a/adapter-template/docs/API.md +++ b/adapter-template/docs/API.md @@ -2,7 +2,7 @@ ## YourFulfillment Adapter Reference -This document describes how the template adapter maps to the interfaces exposed by `@virtocommerce/cof-mcp`. +This document describes how the template adapter maps to the interfaces exposed by `@cof-org/mcp`. ## Initialization @@ -51,7 +51,7 @@ Each method should translate between platform types and your API’s payloads, t | `getInventory(input: GetInventoryInput)` | Reports stock levels. | | `getFulfillments(input: GetFulfillmentsInput)` | Lists fulfillment records. | -All responses use the `FulfillmentToolResult` union declared in `@virtocommerce/cof-mcp`. On failure, include adapter specific error metadata so downstream tools can diagnose issues. +All responses use the `FulfillmentToolResult` union declared in `@cof-org/mcp`. On failure, include adapter specific error metadata so downstream tools can diagnose issues. ## Error Handling @@ -67,7 +67,7 @@ if (!response.success) { ## Type Definitions -Reference `@virtocommerce/cof-mcp` for shared fulfillment models and `src/types.ts` for YourFulfillment-specific shapes. Maintaining strict typing in adapters ensures the server’s JSON Schema validation continues to succeed. +Reference `@cof-org/mcp` for shared fulfillment models and `src/types.ts` for YourFulfillment-specific shapes. Maintaining strict typing in adapters ensures the server’s JSON Schema validation continues to succeed. ## Best Practices diff --git a/adapter-template/jest.config.js b/adapter-template/jest.config.js index 9c5c49d..b6969ad 100644 --- a/adapter-template/jest.config.js +++ b/adapter-template/jest.config.js @@ -34,7 +34,7 @@ export default { }, moduleNameMapper: { '^@/(.*)$': '/src/$1', - '^@virtocommerce/cof-mcp$': '/node_modules/@virtocommerce/cof-mcp/dist/index.js', + '^@cof-org/mcp$': '/node_modules/@cof-org/mcp/dist/index.js', '^(\\.{1,2}/.*)\\.js$': '$1' }, moduleDirectories: ['node_modules'], diff --git a/adapter-template/package.json b/adapter-template/package.json index d1a3b4f..d53677a 100644 --- a/adapter-template/package.json +++ b/adapter-template/package.json @@ -47,6 +47,6 @@ }, "dependencies": { "axios": "^1.6.0", - "@virtocommerce/cof-mcp": "file:../server" + "@cof-org/mcp": "file:../server" } } diff --git a/adapter-template/src/adapter.ts b/adapter-template/src/adapter.ts index c90f8c5..742cabd 100644 --- a/adapter-template/src/adapter.ts +++ b/adapter-template/src/adapter.ts @@ -36,8 +36,8 @@ import type { CustomerAddress, OrderLineItem, CustomField, -} from '@virtocommerce/cof-mcp'; -import { AdapterError } from '@virtocommerce/cof-mcp'; +} from '@cof-org/mcp'; +import { AdapterError } from '@cof-org/mcp'; import { ApiClient } from './utils/api-client.js'; import type { AdapterOptions as TemplateAdapterOptions, diff --git a/adapter-template/tests/adapter.test.ts b/adapter-template/tests/adapter.test.ts index 8656336..7b7d5a2 100644 --- a/adapter-template/tests/adapter.test.ts +++ b/adapter-template/tests/adapter.test.ts @@ -14,7 +14,7 @@ import type { UpdateOrderInput, GetOrdersInput, GetInventoryInput, -} from '@virtocommerce/cof-mcp'; +} from '@cof-org/mcp'; describe('YourFulfillmentAdapter', () => { let adapter: YourFulfillmentAdapter; diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index ed07fe3..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,15 +0,0 @@ -services: - mcp-server: - build: - context: . - dockerfile: Dockerfile - ports: - - "80:3000" - env_file: - - .env.docker - environment: - - MCP_TRANSPORT=sse - - MCP_PORT=3000 - extra_hosts: - - "host.docker.internal:host-gateway" - restart: unless-stopped diff --git a/docs/getting-started/for-fulfillment-vendors.md b/docs/getting-started/for-fulfillment-vendors.md index 5686bd9..8888b93 100644 --- a/docs/getting-started/for-fulfillment-vendors.md +++ b/docs/getting-started/for-fulfillment-vendors.md @@ -16,7 +16,7 @@ Update `package.json`, rename the adapter class, and configure the connection op ## 2. Implement Required Methods -The `IFulfillmentAdapter` interface (exported from `@virtocommerce/cof-mcp`) defines: +The `IFulfillmentAdapter` interface (exported from `@cof-org/mcp`) defines: - Lifecycle: `connect`, `disconnect`, `healthCheck` - Action operations: `createSalesOrder`, `cancelOrder`, `updateOrder`, `fulfillOrder` diff --git a/docs/guides/configuration.md b/docs/guides/configuration.md index 7660acd..fc97d2d 100644 --- a/docs/guides/configuration.md +++ b/docs/guides/configuration.md @@ -40,7 +40,7 @@ These files merge with defaults before environment variables are applied. The sc Use the `ConfigManager` singleton to inspect or change settings programmatically: ```typescript -import { ConfigManager } from '@virtocommerce/cof-mcp'; +import { ConfigManager } from '@cof-org/mcp'; const manager = ConfigManager.getInstance(); const loggingLevel = manager.get('logging.level'); diff --git a/docs/specification/mcp-server-spec.md b/docs/specification/mcp-server-spec.md index 5312c91..f03cb13 100644 --- a/docs/specification/mcp-server-spec.md +++ b/docs/specification/mcp-server-spec.md @@ -632,7 +632,7 @@ interface FulfillmentAdapter { "version": "1.0.0", "main": "dist/index.js", "peerDependencies": { - "@virtocommerce/cof-mcp": "^1.0.0" + "@cof-org/mcp": "^1.0.0" } } ``` @@ -640,7 +640,7 @@ interface FulfillmentAdapter { 2. **Implement adapter:** ```typescript -import { IFulfillmentAdapter } from '@virtocommerce/cof-mcp'; +import { IFulfillmentAdapter } from '@cof-org/mcp'; export default class VendorAdapter implements IFulfillmentAdapter { constructor(options: any) { @@ -677,7 +677,7 @@ my-fulfillment-adapter/ 2. **Implement adapter:** ```typescript -import { IFulfillmentAdapter } from '@virtocommerce/cof-mcp/types'; +import { IFulfillmentAdapter } from '@cof-org/mcp/types'; export default class RetailerAdapter implements IFulfillmentAdapter { // Implementation diff --git a/server/CLAUDE.md b/server/CLAUDE.md index 66c5ee5..0e13826 100644 --- a/server/CLAUDE.md +++ b/server/CLAUDE.md @@ -227,7 +227,7 @@ Location: 2. Extend `BaseTool` 3. Define `name`, `description`, `inputSchema` 4. Implement `execute(input: TInput): Promise` -5. Add `registry.register(new YourTool(serviceLayer))` to `registerTools()` in `tools/index.ts` +5. Registry auto-discovers and registers it ### Adding a Service Operation 1. Add method to appropriate service class diff --git a/server/package.json b/server/package.json index d3baa84..f04e808 100644 --- a/server/package.json +++ b/server/package.json @@ -1,5 +1,5 @@ { - "name": "@virtocommerce/cof-mcp", + "name": "@cof-org/mcp", "version": "1.0.0", "description": "Commerce Operations Foundation MCP Server", "type": "module", diff --git a/server/src/adapters/adapter-factory.ts b/server/src/adapters/adapter-factory.ts index a170f1d..d0f8317 100644 --- a/server/src/adapters/adapter-factory.ts +++ b/server/src/adapters/adapter-factory.ts @@ -4,7 +4,6 @@ import * as path from 'path'; import * as fs from 'fs'; -import { pathToFileURL } from 'url'; import { IFulfillmentAdapter, AdapterConfig, AdapterConstructor, AdapterError } from '../types/index.js'; import { Logger } from '../utils/logger.js'; import { MockAdapter } from './mock/mock-adapter.js'; @@ -178,8 +177,8 @@ export class AdapterFactory { Logger.info(`Loading local adapter from: ${adapterPath}`); try { - // Dynamically import local file (use file:// URL for Windows compatibility) - const module = await import(pathToFileURL(adapterPath).href); + // Dynamically import local file + const module = await import(adapterPath); const exportName = config.exportName || 'default'; const AdapterClass = module[exportName]; diff --git a/server/src/adapters/mock/mock-adapter.ts b/server/src/adapters/mock/mock-adapter.ts index 51c77d6..ab6951d 100644 --- a/server/src/adapters/mock/mock-adapter.ts +++ b/server/src/adapters/mock/mock-adapter.ts @@ -1148,6 +1148,9 @@ export class MockAdapter implements IFulfillmentAdapter { firstName: customer.firstName ?? 'Mock', lastName: customer.lastName ?? 'Customer', type: customer.type ?? 'individual', + createdAt: DateUtils.now(), + updatedAt: DateUtils.now(), + tenantId: (customer as Partial).tenantId ?? 'mock-tenant', addresses: customer.addresses ?? [], customFields: customer.customFields ?? [], tags: customer.tags ?? [], diff --git a/server/src/logging/structured-logger.ts b/server/src/logging/structured-logger.ts index 510b7fc..7f44d1b 100644 --- a/server/src/logging/structured-logger.ts +++ b/server/src/logging/structured-logger.ts @@ -6,7 +6,6 @@ import * as winston from 'winston'; import * as path from 'path'; import * as fs from 'fs'; -import { fileURLToPath } from 'url'; import DailyRotateFile from 'winston-daily-rotate-file'; import { LogSanitizer } from '../security/log-sanitizer.js'; @@ -43,9 +42,9 @@ export class StructuredLogger { // Make log directory absolute if it's relative if (!path.isAbsolute(logDir)) { // Find the server directory (parent of dist when running compiled code) - const currentDir = path.dirname(fileURLToPath(import.meta.url)); - const serverDir = currentDir.includes(`${path.sep}dist${path.sep}`) - ? currentDir.split(`${path.sep}dist${path.sep}`)[0] + const currentDir = path.dirname(new URL(import.meta.url).pathname); + const serverDir = currentDir.includes('/dist/') + ? path.resolve(currentDir.split('/dist/')[0]) : path.resolve(process.cwd(), 'server'); logDir = path.join(serverDir, logDir); } diff --git a/server/src/schemas/entities/order.ts b/server/src/schemas/entities/order.ts index ca2a1d2..164dc86 100644 --- a/server/src/schemas/entities/order.ts +++ b/server/src/schemas/entities/order.ts @@ -21,8 +21,7 @@ const OrderCoreSchema = z billingAddress: AddressSchema.describe('Billing address'), currency: z.string().describe('Order currency code'), customFields: CustomFieldsSchema, - customer: CustomerSchema.omit(makeZodFieldMap(['createdAt', 'updatedAt', 'tenantId'] as const)) - .describe('Customer reference for the order'), + customer: CustomerSchema.describe('Order customer information'), discounts: z.array(z.looseObject({})).describe('Discounts'), lineItems: z.array(OrderLineItemSchema), orderDiscount: z.number().describe('Order Discount'), diff --git a/virtocommerce-adapter/docs/API.md b/virtocommerce-adapter/docs/API.md index 7545a68..d0ea6d1 100644 --- a/virtocommerce-adapter/docs/API.md +++ b/virtocommerce-adapter/docs/API.md @@ -2,7 +2,7 @@ ## YourFulfillment Adapter Reference -This document describes how the template adapter maps to the interfaces exposed by `@virtocommerce/cof-mcp`. +This document describes how the template adapter maps to the interfaces exposed by `@cof-org/mcp`. ## Initialization @@ -51,7 +51,7 @@ Each method should translate between platform types and your API’s payloads, t | `getInventory(input: GetInventoryInput)` | Reports stock levels. | | `getFulfillments(input: GetFulfillmentsInput)` | Lists fulfillment records. | -All responses use the `FulfillmentToolResult` union declared in `@virtocommerce/cof-mcp`. On failure, include adapter specific error metadata so downstream tools can diagnose issues. +All responses use the `FulfillmentToolResult` union declared in `@cof-org/mcp`. On failure, include adapter specific error metadata so downstream tools can diagnose issues. ## Error Handling @@ -67,7 +67,7 @@ if (!response.success) { ## Type Definitions -Reference `@virtocommerce/cof-mcp` for shared fulfillment models and `src/types.ts` for YourFulfillment-specific shapes. Maintaining strict typing in adapters ensures the server’s JSON Schema validation continues to succeed. +Reference `@cof-org/mcp` for shared fulfillment models and `src/types.ts` for YourFulfillment-specific shapes. Maintaining strict typing in adapters ensures the server’s JSON Schema validation continues to succeed. ## Best Practices diff --git a/virtocommerce-adapter/jest.config.js b/virtocommerce-adapter/jest.config.js index 882f604..be360e4 100644 --- a/virtocommerce-adapter/jest.config.js +++ b/virtocommerce-adapter/jest.config.js @@ -35,7 +35,7 @@ export default { }, moduleNameMapper: { '^@/(.*)$': '/src/$1', - '^@virtocommerce/cof-mcp$': '/node_modules/@virtocommerce/cof-mcp/dist/index.js', + '^@cof-org/mcp$': '/node_modules/@cof-org/mcp/dist/index.js', '^(\\.{1,2}/.*)\\.js$': '$1' }, moduleDirectories: ['node_modules'], diff --git a/virtocommerce-adapter/package-lock.json b/virtocommerce-adapter/package-lock.json index ba947f2..821b89b 100644 --- a/virtocommerce-adapter/package-lock.json +++ b/virtocommerce-adapter/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@virtocommerce/cof-mcp": "^1.0.0", + "@cof-org/mcp": "^0.5.1", "axios": "^1.6.0" }, "devDependencies": { @@ -27,8 +27,9 @@ } }, "../server": { - "name": "@virtocommerce/cof-mcp", + "name": "@cof-org/mcp", "version": "1.0.0", + "extraneous": true, "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^0.5.0", @@ -544,6 +545,65 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@cof-org/mcp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@cof-org/mcp/-/mcp-0.5.1.tgz", + "integrity": "sha512-W+bZUWV+/LdCKr/u2z0uiMpYLfB7BTViGydCThLS9LfeIR9Vis8h3bgbHalqG8LOdxAgNOW1TFbl63C/bCoilA==", + "dependencies": { + "@modelcontextprotocol/sdk": "^0.5.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "dotenv": "^16.3.1", + "uuid": "^9.0.1", + "winston": "^3.17.0", + "winston-daily-rotate-file": "^5.0.0", + "zod": "^4.1.12" + }, + "bin": { + "cof-mcp": "dist/index.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@cof-org/mcp/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@cof-org/mcp/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", + "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", + "dependencies": { + "@so-ric/colorspace": "^1.1.6", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -1198,6 +1258,24 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.5.0.tgz", + "integrity": "sha512-RXgulUX6ewvxjAG0kOpLMEdXXWkzWgaoCGaA2CwNW7cQCIphjpJhjpHSiaPdVCnisjRF/0Cm9KWHUuIoeiAblQ==", + "dependencies": { + "content-type": "^1.0.5", + "raw-body": "^3.0.0", + "zod": "^3.23.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1228,6 +1306,15 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "dependencies": { + "color": "^5.0.2", + "text-hex": "1.0.x" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1345,6 +1432,11 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" + }, "node_modules/@types/yargs": { "version": "17.0.35", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", @@ -1582,10 +1674,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@virtocommerce/cof-mcp": { - "resolved": "../server", - "link": true - }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -1623,6 +1711,42 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -1797,6 +1921,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", @@ -2051,6 +2180,14 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -2211,6 +2348,18 @@ "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", "dev": true }, + "node_modules/color": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", + "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", + "dependencies": { + "color-convert": "^3.1.3", + "color-string": "^2.1.3" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2229,6 +2378,44 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/color-string": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/color-string/node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", + "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=14.6" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "engines": { + "node": ">=12.20" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2246,6 +2433,14 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "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==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -2426,6 +2621,14 @@ "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==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2456,6 +2659,17 @@ "node": ">=0.10.0" } }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "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", @@ -2493,6 +2707,11 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, "node_modules/error-ex": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", @@ -3045,8 +3264,7 @@ "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 + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", @@ -3060,6 +3278,21 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -3069,6 +3302,11 @@ "bser": "2.1.1" } }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -3081,6 +3319,14 @@ "node": ">=16.0.0" } }, + "node_modules/file-stream-rotator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz", + "integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==", + "dependencies": { + "moment": "^2.29.1" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -3128,6 +3374,11 @@ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, "node_modules/follow-redirects": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", @@ -3545,6 +3796,25 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -3554,6 +3824,21 @@ "node": ">=10.17.0" } }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/ignore": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", @@ -3621,8 +3906,7 @@ "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 + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/internal-slot": { "version": "1.1.0", @@ -3942,7 +4226,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, "engines": { "node": ">=8" }, @@ -4758,6 +5041,11 @@ "node": ">=6" } }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -4813,6 +5101,22 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -4931,11 +5235,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "engines": { + "node": "*" + } + }, "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 + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/natural-compare": { "version": "1.4.0", @@ -4982,6 +5293,14 @@ "node": ">=8" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -5082,6 +5401,14 @@ "wrappy": "1" } }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dependencies": { + "fn.name": "1.x.x" + } + }, "node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -5411,12 +5738,39 @@ } ] }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, "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 }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -5468,6 +5822,14 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -5546,6 +5908,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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" + } + ] + }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", @@ -5579,6 +5960,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", @@ -5637,6 +6031,11 @@ "node": ">= 0.4" } }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5776,6 +6175,14 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "engines": { + "node": "*" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -5797,6 +6204,14 @@ "node": ">=8" } }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -5810,6 +6225,14 @@ "node": ">= 0.4" } }, + "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==", + "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", @@ -5995,6 +6418,11 @@ "node": "*" } }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -6058,6 +6486,22 @@ "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==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/ts-api-utils": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", @@ -6347,6 +6791,14 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -6386,6 +6838,23 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "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" + ], + "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", @@ -6509,6 +6978,57 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/winston": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", + "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.8", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-daily-rotate-file": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-5.0.0.tgz", + "integrity": "sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==", + "dependencies": { + "file-stream-rotator": "^0.6.1", + "object-hash": "^3.0.0", + "triple-beam": "^1.4.1", + "winston-transport": "^4.7.0" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "winston": "^3" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -6613,6 +7133,14 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/virtocommerce-adapter/package.json b/virtocommerce-adapter/package.json index 34680cf..a97c2b3 100644 --- a/virtocommerce-adapter/package.json +++ b/virtocommerce-adapter/package.json @@ -54,7 +54,7 @@ "typescript-eslint": "^8.7.0" }, "dependencies": { - "@virtocommerce/cof-mcp": "^1.0.0", + "@cof-org/mcp": "^0.5.1", "axios": "^1.6.0" } } \ No newline at end of file diff --git a/virtocommerce-adapter/src/adapter.ts b/virtocommerce-adapter/src/adapter.ts index 33e3ae7..39c34c1 100644 --- a/virtocommerce-adapter/src/adapter.ts +++ b/virtocommerce-adapter/src/adapter.ts @@ -32,8 +32,8 @@ import type { GetCustomersInput, GetFulfillmentsInput, GetReturnsInput, -} from '@virtocommerce/cof-mcp'; -import { AdapterError } from '@virtocommerce/cof-mcp'; +} from '@cof-org/mcp'; +import { AdapterError } from '@cof-org/mcp'; import { ApiClient } from './utils/api-client.js'; import type { AdapterOptions } from './types.js'; import { ErrorCode } from './types.js'; diff --git a/virtocommerce-adapter/src/mappers/filter.mappers.ts b/virtocommerce-adapter/src/mappers/filter.mappers.ts index 859b0ed..87c23e5 100644 --- a/virtocommerce-adapter/src/mappers/filter.mappers.ts +++ b/virtocommerce-adapter/src/mappers/filter.mappers.ts @@ -11,7 +11,7 @@ import type { GetCustomersInput, GetFulfillmentsInput, GetReturnsInput, -} from '@virtocommerce/cof-mcp'; +} from '@cof-org/mcp'; import type { CustomerOrderSearchCriteria, MemberSearchCriteria, ProductSearchCriteria, ShipmentSearchCriteria, ReturnSearchCriteria } from '../models/index.js'; /** diff --git a/virtocommerce-adapter/src/services/base.service.ts b/virtocommerce-adapter/src/services/base.service.ts index 963212e..1abe110 100644 --- a/virtocommerce-adapter/src/services/base.service.ts +++ b/virtocommerce-adapter/src/services/base.service.ts @@ -2,8 +2,8 @@ * Base service with shared utilities for all domain services */ -import type { FulfillmentToolResult } from '@virtocommerce/cof-mcp'; -import { AdapterError } from '@virtocommerce/cof-mcp'; +import type { FulfillmentToolResult } from '@cof-org/mcp'; +import { AdapterError } from '@cof-org/mcp'; import { ErrorCode } from '../types.js'; import { ApiClient } from '../utils/api-client.js'; diff --git a/virtocommerce-adapter/src/services/customer.service.ts b/virtocommerce-adapter/src/services/customer.service.ts index 30cc4dd..57acd1b 100644 --- a/virtocommerce-adapter/src/services/customer.service.ts +++ b/virtocommerce-adapter/src/services/customer.service.ts @@ -2,7 +2,7 @@ * Customer service - handles customer-related operations */ -import type { Customer, FulfillmentToolResult, GetCustomersInput } from '@virtocommerce/cof-mcp'; +import type { Customer, FulfillmentToolResult, GetCustomersInput } from '@cof-org/mcp'; import type { Contact, MemberSearchCriteria } from '../models/index.js'; import { BaseService } from './base.service.js'; import { CustomerTransformer } from '../transformers/customer.transformer.js'; diff --git a/virtocommerce-adapter/src/services/fulfillment.service.ts b/virtocommerce-adapter/src/services/fulfillment.service.ts index 4f292b5..bd19fc3 100644 --- a/virtocommerce-adapter/src/services/fulfillment.service.ts +++ b/virtocommerce-adapter/src/services/fulfillment.service.ts @@ -7,7 +7,7 @@ import type { FulfillmentToolResult, FulfillOrderInput, GetFulfillmentsInput, -} from '@virtocommerce/cof-mcp'; +} from '@cof-org/mcp'; import type { Shipment, CustomerOrder } from '../models/index.js'; import { BaseService } from './base.service.js'; import { FulfillmentTransformer } from '../transformers/fulfillment.transformer.js'; diff --git a/virtocommerce-adapter/src/services/order.service.ts b/virtocommerce-adapter/src/services/order.service.ts index 49df766..8e2e49a 100644 --- a/virtocommerce-adapter/src/services/order.service.ts +++ b/virtocommerce-adapter/src/services/order.service.ts @@ -10,7 +10,7 @@ import type { CancelOrderInput, UpdateOrderInput, GetOrdersInput, -} from '@virtocommerce/cof-mcp'; +} from '@cof-org/mcp'; import type { CustomerOrder, CustomerOrderSearchResult, diff --git a/virtocommerce-adapter/src/services/product.service.ts b/virtocommerce-adapter/src/services/product.service.ts index 62a804e..9dc50d7 100644 --- a/virtocommerce-adapter/src/services/product.service.ts +++ b/virtocommerce-adapter/src/services/product.service.ts @@ -10,7 +10,7 @@ import type { GetProductsInput, GetProductVariantsInput, GetInventoryInput, -} from '@virtocommerce/cof-mcp'; +} from '@cof-org/mcp'; import type { ProductSearchResult, InventorySearchResult, diff --git a/virtocommerce-adapter/src/services/return.service.ts b/virtocommerce-adapter/src/services/return.service.ts index 33cc8e1..6e70b0f 100644 --- a/virtocommerce-adapter/src/services/return.service.ts +++ b/virtocommerce-adapter/src/services/return.service.ts @@ -14,7 +14,7 @@ import type { FulfillmentToolResult, CreateReturnInput, GetReturnsInput, -} from '@virtocommerce/cof-mcp'; +} from '@cof-org/mcp'; import type { VcReturn, CustomerOrder, ReturnSearchResult } from '../models/index.js'; import { BaseService } from './base.service.js'; import { ReturnTransformer } from '../transformers/return.transformer.js'; diff --git a/virtocommerce-adapter/src/transformers/address.transformer.ts b/virtocommerce-adapter/src/transformers/address.transformer.ts index aa969a1..6240869 100644 --- a/virtocommerce-adapter/src/transformers/address.transformer.ts +++ b/virtocommerce-adapter/src/transformers/address.transformer.ts @@ -2,7 +2,7 @@ * Address transformation utilities */ -import type { Address, CustomerAddress } from '@virtocommerce/cof-mcp'; +import type { Address, CustomerAddress } from '@cof-org/mcp'; import type { YourFulfillmentAddress } from '../types.js'; import type { Address as VirtoAddress } from '../models/index.js'; import { BaseTransformer } from './base.js'; diff --git a/virtocommerce-adapter/src/transformers/customer.transformer.ts b/virtocommerce-adapter/src/transformers/customer.transformer.ts index 5fdd6ac..5d22063 100644 --- a/virtocommerce-adapter/src/transformers/customer.transformer.ts +++ b/virtocommerce-adapter/src/transformers/customer.transformer.ts @@ -2,7 +2,7 @@ * Customer transformation utilities */ -import type { Customer } from '@virtocommerce/cof-mcp'; +import type { Customer } from '@cof-org/mcp'; import type { YourFulfillmentCustomer } from '../types.js'; import type { CustomerOrder, Contact, Address } from '../models/index.js'; import { BaseTransformer } from './base.js'; diff --git a/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts b/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts index bd6ee9a..e6ac185 100644 --- a/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts +++ b/virtocommerce-adapter/src/transformers/fulfillment.transformer.ts @@ -2,7 +2,7 @@ * Fulfillment/Shipment transformation utilities */ -import type { Fulfillment, FulfillOrderInput } from '@virtocommerce/cof-mcp'; +import type { Fulfillment, FulfillOrderInput } from '@cof-org/mcp'; import type { Shipment, ShipmentItem, LineItem } from '../models/index.js'; import { BaseTransformer } from './base.js'; import { AddressTransformer } from './address.transformer.js'; diff --git a/virtocommerce-adapter/src/transformers/order.transformer.ts b/virtocommerce-adapter/src/transformers/order.transformer.ts index 13ef5e6..0178ca1 100644 --- a/virtocommerce-adapter/src/transformers/order.transformer.ts +++ b/virtocommerce-adapter/src/transformers/order.transformer.ts @@ -9,7 +9,7 @@ import type { Address, CreateSalesOrderInput, UpdateOrderInput, -} from '@virtocommerce/cof-mcp'; +} from '@cof-org/mcp'; import { STATUS_MAP, REVERSE_STATUS_MAP } from '../types.js'; import type { CustomerOrder, LineItem, Shipment, DynamicObjectProperty, Contact } from '../models/index.js'; import { BaseTransformer } from './base.js'; diff --git a/virtocommerce-adapter/src/transformers/product.transformer.ts b/virtocommerce-adapter/src/transformers/product.transformer.ts index aee1a22..58852cf 100644 --- a/virtocommerce-adapter/src/transformers/product.transformer.ts +++ b/virtocommerce-adapter/src/transformers/product.transformer.ts @@ -2,7 +2,7 @@ * Product, Product Variant, and Inventory transformation utilities */ -import type { Product, ProductVariant, InventoryItem } from '@virtocommerce/cof-mcp'; +import type { Product, ProductVariant, InventoryItem } from '@cof-org/mcp'; import type { CatalogProduct, ProductProperty, InventoryInfo } from '../models/index.js'; import { BaseTransformer } from './base.js'; diff --git a/virtocommerce-adapter/src/transformers/return.transformer.ts b/virtocommerce-adapter/src/transformers/return.transformer.ts index 6b7a115..a159446 100644 --- a/virtocommerce-adapter/src/transformers/return.transformer.ts +++ b/virtocommerce-adapter/src/transformers/return.transformer.ts @@ -3,7 +3,7 @@ * Bidirectional mapping between VirtoCommerce Return models and MCP Return format */ -import type { Return, ReturnLineItem, CreateReturnInput } from '@virtocommerce/cof-mcp'; +import type { Return, ReturnLineItem, CreateReturnInput } from '@cof-org/mcp'; import type { VcReturn, VcReturnLineItem, CustomerOrder, LineItem } from '../models/index.js'; import { BaseTransformer } from './base.js'; diff --git a/virtocommerce-adapter/tests/adapter.test.ts b/virtocommerce-adapter/tests/adapter.test.ts index 9c0fdef..0a3bce4 100644 --- a/virtocommerce-adapter/tests/adapter.test.ts +++ b/virtocommerce-adapter/tests/adapter.test.ts @@ -19,7 +19,7 @@ import type { GetProductsInput, GetProductVariantsInput, GetFulfillmentsInput, -} from '@virtocommerce/cof-mcp'; +} from '@cof-org/mcp'; function readResponse(path: string) { return readFileSync(new URL(`./fixtures/${path}.json`, import.meta.url), 'utf-8'); From fc7ae35e7931eae189f6a442ed205cf1f3af531c Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Thu, 26 Feb 2026 20:04:39 +0000 Subject: [PATCH 48/51] remove agent plans --- docs/plans/starry-orbiting-tower.md | 64 ------------------ server/docs/plans/starry-orbiting-tower.md | 78 ---------------------- virtocommerce-adapter/package.json | 2 +- 3 files changed, 1 insertion(+), 143 deletions(-) delete mode 100644 docs/plans/starry-orbiting-tower.md delete mode 100644 server/docs/plans/starry-orbiting-tower.md diff --git a/docs/plans/starry-orbiting-tower.md b/docs/plans/starry-orbiting-tower.md deleted file mode 100644 index a53a46b..0000000 --- a/docs/plans/starry-orbiting-tower.md +++ /dev/null @@ -1,64 +0,0 @@ -# Fix `getProductVariants` mapping — align with VirtoCommerce API - -## Context - -Audit of `getProductVariants` against the real VirtoCommerce C# source (`vc-module-catalog`) revealed that the adapter's TypeScript models and API calls are misaligned with what the VC indexed search endpoint actually accepts and returns: - -1. **SKU search is broken** — adapter sends `codes` field, but `ProductIndexedSearchCriteria` has no such property; it's silently ignored -2. **`WithPrices` responseGroup doesn't exist** — `ItemResponseGroup` enum has no `WithPrices` flag; silently ignored -3. **`CatalogProduct` has no `prices` field** — prices live in a separate Pricing module; the `ProductPrice` interface and `extractPricing()` added earlier are dead code -4. **TypeScript `ProductSearchCriteria`** has fields from the DB-based search criteria, not the indexed search criteria that the actual endpoint uses - -## Changes - -### 1. `virtocommerce-adapter/src/models/catalog.ts` - -- Remove `ProductPrice` interface (added incorrectly in previous session) -- Remove `prices?: ProductPrice[]` from `CatalogProduct` -- Keep `taxType?: string` (exists on real C# model) -- Remove `codes` from `ProductSearchCriteria` (not on indexed search criteria) - -### 2. `virtocommerce-adapter/src/models/index.ts` - -- Remove `ProductPrice` from exports - -### 3. `virtocommerce-adapter/src/mappers/filter.mappers.ts` - -- **`mapProductVariantFiltersToSearchCriteria`**: remove `WithPrices` from responseGroup, remove `codes` usage entirely (SKU resolution moves to service layer) -- **`mapProductFiltersToSearchCriteria`**: remove dead `codes` branch (SKU path already early-returns in service) - -### 4. `virtocommerce-adapter/src/services/product.service.ts` - -- Add `getProductVariantsBySkus()` private method — two-step pattern (same as existing `getProductsByCodes`): - 1. Resolve SKUs to IDs via `resolveSkuProductIdMap()` (`POST /api/catalog/listentries`) - 2. Fetch products by resolved IDs via `POST /api/catalog/search/products` with `searchInVariations: true` -- Update `getProductVariants()` to early-return to `getProductVariantsBySkus()` when `input.skus` provided - -### 5. `virtocommerce-adapter/src/transformers/product.transformer.ts` - -- Remove `ProductPrice` import -- Remove `extractPricing()` method -- In `fromCatalogProductVariant()`: remove pricing extraction, keep `taxable` mapping from `taxType` - -### 6. `virtocommerce-adapter/tests/adapter.test.ts` - -- **"should get variants by SKUs"** (line 1129): change single mock to two sequential mocks (listentries + search/products), update assertion to verify two-step flow -- **"should handle variant search failure"** (line 1232): add listentries mock before the failing search mock (test uses `skus` input which now triggers two-step) - -## Implementation Order - -1. `models/catalog.ts` — remove `ProductPrice`, `prices`, `codes` -2. `models/index.ts` — remove `ProductPrice` export -3. `transformers/product.transformer.ts` — remove pricing code -4. `mappers/filter.mappers.ts` — remove `codes` and `WithPrices` -5. `services/product.service.ts` — add `getProductVariantsBySkus()` -6. `tests/adapter.test.ts` — update SKU test mocks - -## Verification - -```bash -cd server && npm run build -cd ../virtocommerce-adapter && npm run build -``` - -Note: `adapter.test.ts` has pre-existing syntax errors (customer section ~line 667) that prevent tests from running. Our changes should not introduce new failures. diff --git a/server/docs/plans/starry-orbiting-tower.md b/server/docs/plans/starry-orbiting-tower.md deleted file mode 100644 index 6e67d01..0000000 --- a/server/docs/plans/starry-orbiting-tower.md +++ /dev/null @@ -1,78 +0,0 @@ -# Fix Order Customer Schema + Resolve Product Names by SKU - -## Context - -Two problems found during end-to-end testing of `create-sales-order`: - -1. **Customer schema validation blocks order creation** — `CustomerSchema` inherits `ObjectProps` which makes `createdAt`, `updatedAt`, `tenantId` required. When the LLM passes `order.customer: { id: "...", firstName: "..." }`, the MCP server rejects it with `"must have required property 'createdAt'"`. These timestamp fields make no sense for a customer reference within an order. - -2. **Line items missing product name** — VirtoCommerce requires `name` on line items. Currently `resolveSkuProductIdMap` returns only `Map`. If the LLM doesn't provide `item.name`, the line item arrives at VC without a name. The product name is already available in the search response and should be captured. - -## Changes - -### 1. `server/src/schemas/entities/order.ts` — Customer reference schema - -Replace `CustomerSchema.partial()` with a proper omit that removes only timestamp/system fields but keeps `id` required: - -```typescript -const OrderCustomerRefSchema = CustomerSchema.omit( - makeZodFieldMap(['createdAt', 'updatedAt', 'tenantId'] as const) -).describe('Customer reference for the order'); -``` - -Result: -- `id`: string (**required**) → maps to VC `customerId` -- `externalId`: string (optional) → fallback customerId -- `firstName`, `lastName`, `email`, `phone`: optional → used to derive `customerName` -- No `createdAt`, `updatedAt`, `tenantId` - -Use `OrderCustomerRefSchema` in `OrderCoreSchema.customer`. - -Type compatibility: `Customer` (returned by adapter's `toMcpOrder`) is structurally assignable to the ref type — extra properties are allowed by TypeScript structural typing. - -### 2. `virtocommerce-adapter/src/services/product.service.ts` — Resolve names with SKUs - -Change `resolveSkuProductIdMap` signature: -- **Before:** `resolveSkuProductIdMap(skus): Promise>` (code → id) -- **After:** `resolveSkuProductMap(skus): Promise>` (code → {id, name}) - -Inside the method: capture `item.name` alongside `item.id` from the search response. The `name` field is a base entity property available with `responseGroup: 'None'`. - -Update private `resolveSkusToIds` and `getInventory` to work with the new map shape (extract `.id` from values). - -### 3. `virtocommerce-adapter/src/services/order.service.ts` — Pass new map - -Update `createSalesOrder` to pass the new `Map` to the transformer. - -### 4. `virtocommerce-adapter/src/transformers/order.transformer.ts` — Use resolved names - -Update `fromCreateSalesOrderInput`: -- `skuProductIdMap` parameter type → `Map` -- Line item mapping: `productId: resolved?.id`, `name: item.name ?? resolved?.name ?? item.sku` - -This ensures every line item has a name even if the LLM only passes SKU + quantity. - -### 5. `virtocommerce-adapter/tests/adapter.test.ts` — Update mocks - -Update test at ~line 1738 (order creation with minimal input) to include customer data since the adapter now validates it. - -Update any mock data that uses the old `skuProductIdMap` shape if directly asserted. - -## Files - -| File | Package | Change | -|------|---------|--------| -| `server/src/schemas/entities/order.ts` | server | Replace `.partial()` with `.omit()` on customer | -| `virtocommerce-adapter/src/services/product.service.ts` | adapter | Return `{id, name}` from SKU resolution | -| `virtocommerce-adapter/src/services/order.service.ts` | adapter | Pass new map shape to transformer | -| `virtocommerce-adapter/src/transformers/order.transformer.ts` | adapter | Use resolved name as fallback | -| `virtocommerce-adapter/tests/adapter.test.ts` | adapter | Adjust mocks | - -## Verification - -```bash -cd server && npm run build -cd ../virtocommerce-adapter && npm run build -``` - -End-to-end: restart MCP server, test `create-sales-order` with customer `{ id, firstName, lastName }` — no more `createdAt` validation error. Line items should have product names resolved from catalog. diff --git a/virtocommerce-adapter/package.json b/virtocommerce-adapter/package.json index a97c2b3..93a086e 100644 --- a/virtocommerce-adapter/package.json +++ b/virtocommerce-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@virtocommerce/mcp-onx", - "version": "1.0.0", + "version": "0.0.1", "description": "Commerce Operations Foundation adapter for VirtoCommerce Fulfillment", "repository": { "type": "git", From 395a29bf149fb7688483ac81cd6c68f67a2ba581 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Thu, 26 Feb 2026 21:36:06 +0000 Subject: [PATCH 49/51] fix: update log directory resolution to use fileURLToPath for compatibility --- server/src/logging/structured-logger.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/src/logging/structured-logger.ts b/server/src/logging/structured-logger.ts index 7f44d1b..510b7fc 100644 --- a/server/src/logging/structured-logger.ts +++ b/server/src/logging/structured-logger.ts @@ -6,6 +6,7 @@ import * as winston from 'winston'; import * as path from 'path'; import * as fs from 'fs'; +import { fileURLToPath } from 'url'; import DailyRotateFile from 'winston-daily-rotate-file'; import { LogSanitizer } from '../security/log-sanitizer.js'; @@ -42,9 +43,9 @@ export class StructuredLogger { // Make log directory absolute if it's relative if (!path.isAbsolute(logDir)) { // Find the server directory (parent of dist when running compiled code) - const currentDir = path.dirname(new URL(import.meta.url).pathname); - const serverDir = currentDir.includes('/dist/') - ? path.resolve(currentDir.split('/dist/')[0]) + const currentDir = path.dirname(fileURLToPath(import.meta.url)); + const serverDir = currentDir.includes(`${path.sep}dist${path.sep}`) + ? currentDir.split(`${path.sep}dist${path.sep}`)[0] : path.resolve(process.cwd(), 'server'); logDir = path.join(serverDir, logDir); } From f1fb4e68f64ac923534aa360a79d610170cd5b8d Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Fri, 27 Feb 2026 09:41:45 +0000 Subject: [PATCH 50/51] fix: use pathToFileURL for dynamic adapter imports on Windows `import(absolutePath)` fails on Windows because Node.js ESM loader interprets the drive letter (e.g. `E:`) as a URL scheme. Convert the path to a file:// URL before importing. Co-Authored-By: Claude Opus 4.6 --- server/src/adapters/adapter-factory.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/src/adapters/adapter-factory.ts b/server/src/adapters/adapter-factory.ts index d0f8317..a170f1d 100644 --- a/server/src/adapters/adapter-factory.ts +++ b/server/src/adapters/adapter-factory.ts @@ -4,6 +4,7 @@ import * as path from 'path'; import * as fs from 'fs'; +import { pathToFileURL } from 'url'; import { IFulfillmentAdapter, AdapterConfig, AdapterConstructor, AdapterError } from '../types/index.js'; import { Logger } from '../utils/logger.js'; import { MockAdapter } from './mock/mock-adapter.js'; @@ -177,8 +178,8 @@ export class AdapterFactory { Logger.info(`Loading local adapter from: ${adapterPath}`); try { - // Dynamically import local file - const module = await import(adapterPath); + // Dynamically import local file (use file:// URL for Windows compatibility) + const module = await import(pathToFileURL(adapterPath).href); const exportName = config.exportName || 'default'; const AdapterClass = module[exportName]; From 56f9f058804a60f54a421fd34df611a498076159 Mon Sep 17 00:00:00 2001 From: Basil Kotov Date: Wed, 13 May 2026 09:40:11 +0100 Subject: [PATCH 51/51] fix: resolve symlink handling for entry point detection on POSIX systems --- server/src/index.ts | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/server/src/index.ts b/server/src/index.ts index 32165b2..48d0fff 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -5,7 +5,8 @@ * Entry point using MCP SDK components */ -import { fileURLToPath } from 'url'; +import { realpathSync } from 'fs'; +import { pathToFileURL } from 'url'; import { ConfigManager } from './config/config-manager.js'; import { MCPServerSDK } from './server.js'; @@ -57,12 +58,31 @@ async function main() { } } -// Run if this is the main module -// ES modules use import.meta.url -const __filename = fileURLToPath(import.meta.url); +/** + * Detect whether this module is the process entry point. + * + * `process.argv[1]` holds the path Node was invoked with. When the package is + * installed via npm/npx, the `bin` entry is exposed as a symlink (for example + * `node_modules/.bin/cof-mcp -> ../@virtocommerce/cof-mcp/dist/index.js`), so a + * direct string comparison against `import.meta.url` fails on POSIX systems + * and `main()` is silently skipped — the process exits with code 0 and no + * output, which is what causes the server to "start and immediately disconnect" + * under Claude Desktop on macOS/Linux. + * + * Resolving symlinks with `fs.realpathSync` and comparing canonical file URLs + * makes the check correct on macOS, Linux, and Windows. + */ +function isMainModule(): boolean { + const invokedPath = process.argv[1]; + if (!invokedPath) return false; + try { + return pathToFileURL(realpathSync(invokedPath)).href === import.meta.url; + } catch { + return false; + } +} -// Check if this file was run directly -if (process.argv[1] === __filename) { +if (isMainModule()) { main(); }