From 0626fed43607b0011c001419a80c45df1c6492d5 Mon Sep 17 00:00:00 2001 From: Alessandro Bellesia Date: Tue, 18 Nov 2025 22:59:16 +0100 Subject: [PATCH 1/5] First commit --- .gitignore | 3 + .npmignore | 30 + .prettierrc | 8 + .vscode/settings.json | 58 + LICENSE | 21 + deno.json | 20 + eslint.config.js | 13 + examples/auth-with-token.ts | 44 + examples/create-payment.ts | 52 + examples/get-consumer.ts | 39 + examples/get-daily-closure.ts | 41 + examples/get-payment.ts | 39 + examples/get-payments.ts | 48 + package-lock.json | 1981 +++++++++++++++++++++++++++ package.json | 52 + pnpm-lock.yaml | 1045 ++++++++++++++ src/Api.ts | 224 +++ src/ApiAuthentication.ts | 14 + src/Consumer.ts | 24 + src/DailyClosure.ts | 47 + src/Payment.ts | 77 ++ src/PreAuthorizedPaymentToken.ts | 61 + src/RSAService/RSAService.ts | 23 + src/RSAService/RSAServiceCrypto.ts | 63 + src/RSAService/RSAServiceFactory.ts | 26 + src/Request.ts | 244 ++++ src/bin/satispay-keygen.ts | 135 ++ src/index.ts | 32 + src/types.ts | 194 +++ tsconfig.json | 32 + 30 files changed, 4690 insertions(+) create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 .prettierrc create mode 100644 .vscode/settings.json create mode 100644 LICENSE create mode 100644 deno.json create mode 100644 eslint.config.js create mode 100644 examples/auth-with-token.ts create mode 100644 examples/create-payment.ts create mode 100644 examples/get-consumer.ts create mode 100644 examples/get-daily-closure.ts create mode 100644 examples/get-payment.ts create mode 100644 examples/get-payments.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 src/Api.ts create mode 100644 src/ApiAuthentication.ts create mode 100644 src/Consumer.ts create mode 100644 src/DailyClosure.ts create mode 100644 src/Payment.ts create mode 100644 src/PreAuthorizedPaymentToken.ts create mode 100644 src/RSAService/RSAService.ts create mode 100644 src/RSAService/RSAServiceCrypto.ts create mode 100644 src/RSAService/RSAServiceFactory.ts create mode 100644 src/Request.ts create mode 100644 src/bin/satispay-keygen.ts create mode 100644 src/index.ts create mode 100644 src/types.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..34f461d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +examples/authentication.json +node_modules +dist \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..d950362 --- /dev/null +++ b/.npmignore @@ -0,0 +1,30 @@ +# Source files +src/ +examples/ +*.ts +!*.d.ts + +# Config files +tsconfig.json +.eslintrc.js +.prettierrc + +# Development +node_modules/ +.vscode/ +.idea/ + +# Tests +coverage/ +.nyc_output/ + +# Misc +*.log +.DS_Store +.env + +# PHP files (legacy) +*.php +composer.json +composer.lock +vendor/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..17e1413 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "semi": false, + "trailingComma": "es5", + "singleQuote": true, + "printWidth": 100, + "tabWidth": 2, + "arrowParens": "avoid" +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f8e16ca --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,58 @@ +{ + // i18n-ally + "i18n-ally.keystyle": "nested", + "i18n-ally.localesPaths": ["packages/i18n/src"], + "i18n-ally.sortKeys": true, + "i18n-ally.sourceLanguage": "it", + "i18n-ally.displayLanguage": "it", + "i18n-ally.enabledFrameworks": ["vue"], + // Enable eslint for all supported languages + "css.validate": false, + "scss.validate": false, + "stylelint.validate": ["css", "scss", "vue"], + "eslint.validate": [ + "javascript", + "javascriptreact", + "typescript", + "typescriptreact", + "vue", + "html", + "markdown", + "json", + "jsonc", + "toml", + "xml", + "gql", + "graphql", + "astro", + "css", + "less", + "scss", + "pcss", + "postcss" + ], + // Disable the default formatter, use eslint instead + "prettier.enable": false, + "editor.formatOnSave": false, + "editor.formatOnPaste": false, + // Auto fix + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit", + "source.organizeImports": "never" + }, + // Silent the stylistic rules in you IDE, but still auto fix them + "eslint.rules.customizations": [ + { "rule": "style/*", "severity": "off", "fixable": true }, + { "rule": "format/*", "severity": "off", "fixable": true }, + { "rule": "*-indent", "severity": "off", "fixable": true }, + { "rule": "*-spacing", "severity": "off", "fixable": true }, + { "rule": "*-spaces", "severity": "off", "fixable": true }, + { "rule": "*-order", "severity": "off", "fixable": true }, + { "rule": "*-dangle", "severity": "off", "fixable": true }, + { "rule": "*-newline", "severity": "off", "fixable": true }, + { "rule": "*quotes", "severity": "off", "fixable": true }, + { "rule": "*semi", "severity": "off", "fixable": true } + ], + // Base font size + "px-to-rem.px-per-rem": 16, +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..57312d3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022-present 8 wave S.r.l. and contributors + +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/deno.json b/deno.json new file mode 100644 index 0000000..b1bad57 --- /dev/null +++ b/deno.json @@ -0,0 +1,20 @@ +{ + "name": "@volverjs/satispay-node-sdk", + "version": "2.0.0", + "exports": "./src/index.ts", + "tasks": { + "build": "deno task compile", + "compile": "deno run --allow-read --allow-write build.ts", + "test": "deno test --allow-net" + }, + "imports": { + "crypto": "node:crypto" + }, + "compilerOptions": { + "lib": [ + "deno.window", + "deno.ns" + ], + "strict": true + } +} \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..33b8891 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,13 @@ +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommended, + { + rules: { + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'warn', + }, + } +); diff --git a/examples/auth-with-token.ts b/examples/auth-with-token.ts new file mode 100644 index 0000000..cf4bccb --- /dev/null +++ b/examples/auth-with-token.ts @@ -0,0 +1,44 @@ +import { Api } from '../src'; +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * Example: Authenticate with token + * + * This example shows how to authenticate with Satispay using an activation token. + * The token can be generated from your Satispay Business Dashboard. + */ + +async function main() { + try { + // Enable sandbox mode + Api.setSandbox(true); + + // Replace with your activation token + const token = 'YOUR_ACTIVATION_TOKEN'; + + console.log('Authenticating with token...'); + const authentication = await Api.authenticateWithToken(token); + + console.log('Authentication successful!'); + console.log('Key ID:', authentication.keyId); + + // Save keys to file for later use + const authData = { + public_key: authentication.publicKey, + private_key: authentication.privateKey, + key_id: authentication.keyId, + }; + + const authFilePath = path.join(__dirname, 'authentication.json'); + fs.writeFileSync(authFilePath, JSON.stringify(authData, null, 2)); + + console.log('Keys saved to:', authFilePath); + console.log('\nIMPORTANT: Store these keys securely! Never commit them to version control.'); + } catch (error) { + console.error('Error:', error instanceof Error ? error.message : String(error)); + process.exit(1); + } +} + +main(); diff --git a/examples/create-payment.ts b/examples/create-payment.ts new file mode 100644 index 0000000..c687637 --- /dev/null +++ b/examples/create-payment.ts @@ -0,0 +1,52 @@ +import { Api, Payment } from '../src'; +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * Example: Create a payment + * + * This example shows how to create a payment using the Satispay API. + */ + +async function main() { + try { + // Enable sandbox mode + Api.setSandbox(true); + + // Load authentication keys from file + const authFilePath = path.join(__dirname, 'authentication.json'); + + if (!fs.existsSync(authFilePath)) { + console.error('Authentication file not found. Please run auth-with-token.ts first.'); + process.exit(1); + } + + const authData = JSON.parse(fs.readFileSync(authFilePath, 'utf-8')); + + // Set authentication keys + Api.setPublicKey(authData.public_key); + Api.setPrivateKey(authData.private_key); + Api.setKeyId(authData.key_id); + + console.log('Creating payment...'); + + // Create a payment + const payment = await Payment.create({ + flow: 'MATCH_CODE', + amount_unit: 199, // Amount in cents (1.99 EUR) + currency: 'EUR', + }); + + console.log('Payment created successfully!'); + console.log('Payment ID:', payment.id); + console.log('Payment Code:', payment.code_identifier); + console.log('Status:', payment.status); + console.log('\nFull payment object:'); + console.log(JSON.stringify(payment, null, 2)); + } catch (error) { + console.error('Error:', error instanceof Error ? error.message : String(error)); + process.exit(1); + } +} + +main(); diff --git a/examples/get-consumer.ts b/examples/get-consumer.ts new file mode 100644 index 0000000..8a63fcd --- /dev/null +++ b/examples/get-consumer.ts @@ -0,0 +1,39 @@ +import { Api, Consumer } from '../src'; +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * Example: Get consumer information + * + * This example shows how to retrieve consumer information by phone number. + */ + +async function main() { + try { + // Enable sandbox mode + Api.setSandbox(true); + + // Load authentication keys + const authFilePath = path.join(__dirname, 'authentication.json'); + const authData = JSON.parse(fs.readFileSync(authFilePath, 'utf-8')); + + Api.setPublicKey(authData.public_key); + Api.setPrivateKey(authData.private_key); + Api.setKeyId(authData.key_id); + + // Replace with an actual phone number (format: +39xxxxxxxxxx) + const phoneNumber = '+393331234567'; + + console.log(`Fetching consumer ${phoneNumber}...`); + + const consumer = await Consumer.get(phoneNumber); + + console.log('Consumer details:'); + console.log(JSON.stringify(consumer, null, 2)); + } catch (error) { + console.error('Error:', error instanceof Error ? error.message : String(error)); + process.exit(1); + } +} + +main(); diff --git a/examples/get-daily-closure.ts b/examples/get-daily-closure.ts new file mode 100644 index 0000000..14e0c13 --- /dev/null +++ b/examples/get-daily-closure.ts @@ -0,0 +1,41 @@ +import { Api, DailyClosure } from '../src'; +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * Example: Get daily closure + * + * This example shows how to retrieve daily closure information. + */ + +async function main() { + try { + // Enable sandbox mode + Api.setSandbox(true); + + // Load authentication keys + const authFilePath = path.join(__dirname, 'authentication.json'); + const authData = JSON.parse(fs.readFileSync(authFilePath, 'utf-8')); + + Api.setPublicKey(authData.public_key); + Api.setPrivateKey(authData.private_key); + Api.setKeyId(authData.key_id); + + // Get today's daily closure (or specify a date like '20240115') + console.log('Fetching daily closure...'); + + const closure = await DailyClosure.get(); + + console.log('Daily closure:'); + console.log(`Date: ${closure.date}`); + console.log(`Total amount: ${closure.total_amount_unit / 100} ${closure.currency}`); + console.log(`Payments count: ${closure.payments_count}`); + console.log('\nFull closure object:'); + console.log(JSON.stringify(closure, null, 2)); + } catch (error) { + console.error('Error:', error instanceof Error ? error.message : String(error)); + process.exit(1); + } +} + +main(); diff --git a/examples/get-payment.ts b/examples/get-payment.ts new file mode 100644 index 0000000..cd8a0f8 --- /dev/null +++ b/examples/get-payment.ts @@ -0,0 +1,39 @@ +import { Api, Payment } from '../src'; +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * Example: Get payment details + * + * This example shows how to retrieve payment information by ID. + */ + +async function main() { + try { + // Enable sandbox mode + Api.setSandbox(true); + + // Load authentication keys + const authFilePath = path.join(__dirname, 'authentication.json'); + const authData = JSON.parse(fs.readFileSync(authFilePath, 'utf-8')); + + Api.setPublicKey(authData.public_key); + Api.setPrivateKey(authData.private_key); + Api.setKeyId(authData.key_id); + + // Replace with an actual payment ID + const paymentId = 'YOUR_PAYMENT_ID'; + + console.log(`Fetching payment ${paymentId}...`); + + const payment = await Payment.get(paymentId); + + console.log('Payment details:'); + console.log(JSON.stringify(payment, null, 2)); + } catch (error) { + console.error('Error:', error instanceof Error ? error.message : String(error)); + process.exit(1); + } +} + +main(); diff --git a/examples/get-payments.ts b/examples/get-payments.ts new file mode 100644 index 0000000..95302a4 --- /dev/null +++ b/examples/get-payments.ts @@ -0,0 +1,48 @@ +import { Api, Payment } from '../src'; +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * Example: List all payments + * + * This example shows how to retrieve a list of payments with optional filters. + */ + +async function main() { + try { + // Enable sandbox mode + Api.setSandbox(true); + + // Load authentication keys + const authFilePath = path.join(__dirname, 'authentication.json'); + const authData = JSON.parse(fs.readFileSync(authFilePath, 'utf-8')); + + Api.setPublicKey(authData.public_key); + Api.setPrivateKey(authData.private_key); + Api.setKeyId(authData.key_id); + + console.log('Fetching payments...'); + + // Get all payments with optional filters + const result = await Payment.all({ + limit: 10, + // status: 'ACCEPTED', // Optional: filter by status + // from_date: '2024-01-01', // Optional: filter by date + }); + + console.log(`Found ${result.list.length} payments`); + console.log('Has more:', result.has_more); + console.log('\nPayments:'); + result.list.forEach((payment, index) => { + console.log(`\n${index + 1}. Payment ${payment.id}`); + console.log(` Status: ${payment.status}`); + console.log(` Amount: ${payment.amount_unit / 100} ${payment.currency}`); + console.log(` Code: ${payment.code_identifier}`); + }); + } catch (error) { + console.error('Error:', error instanceof Error ? error.message : String(error)); + process.exit(1); + } +} + +main(); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b5278d6 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1981 @@ +{ + "name": "@volverjs/satispay-node-sdk", + "version": "2.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@volverjs/satispay-node-sdk", + "version": "2.0.0", + "license": "MIT", + "devDependencies": { + "@eslint/js": "^9.17.0", + "@types/node": "^22.10.2", + "eslint": "^9.17.0", + "prettier": "^3.4.2", + "typescript": "^5.7.2", + "typescript-eslint": "^8.19.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "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.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.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.7", + "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.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.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" + }, + "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/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.39.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "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.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.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.17.0", + "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.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.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "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/@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/@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/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/node": { + "version": "22.19.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.0.tgz", + "integrity": "sha512-xpr/lmLPQEj+TUnHmR+Ab91/glhJvsqcjB+yY0Ix9GO70H6Lb4FHH5GeqdOE5btAx7eIMwuHkp4H2MSkLcqWbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.4.tgz", + "integrity": "sha512-nPiRSKuvtTN+no/2N1kt2tUh/HoFzeEgOm9fQ6XQk4/ApGqjx0zFIIaLJ6wooR1HIoozvj2j6vTi/1fgAz7UYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.46.4", + "@typescript-eslint/types": "^8.46.4", + "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/project-service/node_modules/@typescript-eslint/types": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.4.tgz", + "integrity": "sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w==", + "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/tsconfig-utils": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.4.tgz", + "integrity": "sha512-+/XqaZPIAk6Cjg7NWgSGe27X4zMGqrFqZ8atJsX3CWxH/jACqWnrWI68h7nHQld0y+k9eTTjb9r+KU4twLoo9A==", + "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.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.4.tgz", + "integrity": "sha512-V4QC8h3fdT5Wro6vANk6eojqfbv5bpwHuMsBcJUJkqs2z5XnYhJzyz9Y02eUmF9u3PgXEUiOt4w4KHR3P+z0PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.4", + "@typescript-eslint/typescript-estree": "8.46.4", + "@typescript-eslint/utils": "8.46.4", + "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/type-utils/node_modules/@typescript-eslint/types": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.4.tgz", + "integrity": "sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w==", + "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/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.4.tgz", + "integrity": "sha512-7oV2qEOr1d4NWNmpXLR35LvCfOkTNymY9oyW+lUHkmCno7aOmIf/hMaydnJBUTBMRCOGZh8YjkFOc8dadEoNGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.46.4", + "@typescript-eslint/tsconfig-utils": "8.46.4", + "@typescript-eslint/types": "8.46.4", + "@typescript-eslint/visitor-keys": "8.46.4", + "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/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.4.tgz", + "integrity": "sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.4", + "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/type-utils/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/@typescript-eslint/type-utils/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/@typescript-eslint/type-utils/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/@typescript-eslint/utils": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.4.tgz", + "integrity": "sha512-AbSv11fklGXV6T28dp2Me04Uw90R2iJ30g2bgLz529Koehrmkbs1r7paFqr1vPCZi7hHwYxYtxfyQMRC8QaVSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.46.4", + "@typescript-eslint/types": "8.46.4", + "@typescript-eslint/typescript-estree": "8.46.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/utils/node_modules/@typescript-eslint/scope-manager": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.4.tgz", + "integrity": "sha512-tMDbLGXb1wC+McN1M6QeDx7P7c0UWO5z9CXqp7J8E+xGcJuUuevWKxuG8j41FoweS3+L41SkyKKkia16jpX7CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.4", + "@typescript-eslint/visitor-keys": "8.46.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.4.tgz", + "integrity": "sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w==", + "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/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.4.tgz", + "integrity": "sha512-7oV2qEOr1d4NWNmpXLR35LvCfOkTNymY9oyW+lUHkmCno7aOmIf/hMaydnJBUTBMRCOGZh8YjkFOc8dadEoNGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.46.4", + "@typescript-eslint/tsconfig-utils": "8.46.4", + "@typescript-eslint/types": "8.46.4", + "@typescript-eslint/visitor-keys": "8.46.4", + "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/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.4.tgz", + "integrity": "sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.4", + "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/utils/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/@typescript-eslint/utils/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/@typescript-eslint/utils/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/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", + "peer": true, + "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-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" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/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" + }, + "engines": { + "node": ">=8" + } + }, + "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/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" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "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" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.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" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "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/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.39.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@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.39.1", + "@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", + "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-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/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/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/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/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/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", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-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/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/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/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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/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-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/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/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-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/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/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/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.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/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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/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-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/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/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/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "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/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/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/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/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/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/typescript": { + "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", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.4.tgz", + "integrity": "sha512-KALyxkpYV5Ix7UhvjTwJXZv76VWsHG+NjNlt/z+a17SOQSiOcBdUXdbJdyXi7RPxrBFECtFOiPwUJQusJuCqrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.46.4", + "@typescript-eslint/parser": "8.46.4", + "@typescript-eslint/typescript-estree": "8.46.4", + "@typescript-eslint/utils": "8.46.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/node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.4.tgz", + "integrity": "sha512-R48VhmTJqplNyDxCyqqVkFSZIx1qX6PzwqgcXn1olLrzxcSBDlOsbtcnQuQhNtnNiJ4Xe5gREI1foajYaYU2Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.46.4", + "@typescript-eslint/type-utils": "8.46.4", + "@typescript-eslint/utils": "8.46.4", + "@typescript-eslint/visitor-keys": "8.46.4", + "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.46.4", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/parser": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.4.tgz", + "integrity": "sha512-tK3GPFWbirvNgsNKto+UmB/cRtn6TZfyw0D6IKrW55n6Vbs7KJoZtI//kpTKzE/DUmmnAFD8/Ca46s7Obs92/w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.46.4", + "@typescript-eslint/types": "8.46.4", + "@typescript-eslint/typescript-estree": "8.46.4", + "@typescript-eslint/visitor-keys": "8.46.4", + "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/node_modules/@typescript-eslint/scope-manager": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.4.tgz", + "integrity": "sha512-tMDbLGXb1wC+McN1M6QeDx7P7c0UWO5z9CXqp7J8E+xGcJuUuevWKxuG8j41FoweS3+L41SkyKKkia16jpX7CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.4", + "@typescript-eslint/visitor-keys": "8.46.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/types": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.4.tgz", + "integrity": "sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w==", + "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/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.4.tgz", + "integrity": "sha512-7oV2qEOr1d4NWNmpXLR35LvCfOkTNymY9oyW+lUHkmCno7aOmIf/hMaydnJBUTBMRCOGZh8YjkFOc8dadEoNGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.46.4", + "@typescript-eslint/tsconfig-utils": "8.46.4", + "@typescript-eslint/types": "8.46.4", + "@typescript-eslint/visitor-keys": "8.46.4", + "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/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.4.tgz", + "integrity": "sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.4", + "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/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/typescript-eslint/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/typescript-eslint/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/typescript-eslint/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/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "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/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..e5dc4b1 --- /dev/null +++ b/package.json @@ -0,0 +1,52 @@ +{ + "name": "@volverjs/satispay-node-sdk", + "version": "0.0.1", + "description": "(Unofficial) Satispay GBusiness Node.js API SDK", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "bin": { + "satispay-keygen": "./dist/src/bin/satispay-keygen.js" + }, + "scripts": { + "build": "tsc", + "build:watch": "tsc --watch", + "prepublishOnly": "npm run build", + "test": "jest", + "test:watch": "jest --watch", + "lint": "eslint src", + "lint:fix": "eslint src --fix", + "format": "prettier --write \"src/**/*.ts\"" + }, + "keywords": [ + "satispay", + "gbusiness", + "api", + "sdk", + "typescript", + "nodejs", + "deno", + "bun", + "zero-dependencies", + "payment", + "fetch" + ], + "author": "Satispay", + "license": "MIT", + "homepage": "https://www.satispay.com", + "repository": { + "type": "git", + "url": "https://github.com/satispay/gbusiness-api-nodejs-sdk" + }, + "engines": { + "node": ">=18.0.0" + }, + "devDependencies": { + "@eslint/js": "^9.17.0", + "@types/node": "^22.10.2", + "eslint": "^9.17.0", + "prettier": "^3.4.2", + "typescript": "^5.7.2", + "typescript-eslint": "^8.19.1" + } +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..4b83076 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1045 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@eslint/js': + specifier: ^9.17.0 + version: 9.39.1 + '@types/node': + specifier: ^22.10.2 + version: 22.19.1 + eslint: + specifier: ^9.17.0 + version: 9.39.1 + prettier: + specifier: ^3.4.2 + version: 3.6.2 + typescript: + specifier: ^5.7.2 + version: 5.9.3 + typescript-eslint: + specifier: ^8.19.1 + version: 8.47.0(eslint@9.39.1)(typescript@5.9.3) + +packages: + + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.1': + resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@22.19.1': + resolution: {integrity: sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==} + + '@typescript-eslint/eslint-plugin@8.47.0': + resolution: {integrity: sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.47.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.47.0': + resolution: {integrity: sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.47.0': + resolution: {integrity: sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.47.0': + resolution: {integrity: sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.47.0': + resolution: {integrity: sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.47.0': + resolution: {integrity: sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.47.0': + resolution: {integrity: sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.47.0': + resolution: {integrity: sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.47.0': + resolution: {integrity: sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.47.0': + resolution: {integrity: sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.39.1: + resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.47.0: + resolution: {integrity: sha512-Lwe8i2XQ3WoMjua/r1PHrCTpkubPYJCAfOurtn+mtTzqB6jNd+14n9UN1bJ4s3F49x9ixAm0FLflB/JzQ57M8Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1)': + dependencies: + eslint: 9.39.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.1': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@types/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@22.19.1': + dependencies: + undici-types: 6.21.0 + + '@typescript-eslint/eslint-plugin@8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.47.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.47.0 + '@typescript-eslint/type-utils': 8.47.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.47.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.47.0 + eslint: 9.39.1 + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.47.0 + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.47.0 + debug: 4.4.3 + eslint: 9.39.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.47.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.9.3) + '@typescript-eslint/types': 8.47.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.47.0': + dependencies: + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/visitor-keys': 8.47.0 + + '@typescript-eslint/tsconfig-utils@8.47.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.47.0(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.47.0(eslint@9.39.1)(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.1 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.47.0': {} + + '@typescript-eslint/typescript-estree@8.47.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.47.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.9.3) + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/visitor-keys': 8.47.0 + debug: 4.4.3 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.3 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.47.0(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@typescript-eslint/scope-manager': 8.47.0 + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) + eslint: 9.39.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.47.0': + dependencies: + '@typescript-eslint/types': 8.47.0 + eslint-visitor-keys: 4.2.1 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + balanced-match@1.0.2: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + callsites@3.1.0: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + concat-map@0.0.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + escape-string-regexp@4.0.0: {} + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.39.1: + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@eslint-community/regexpp': 4.12.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.39.1 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + 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.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + isexe@2.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + ms@2.1.3: {} + + natural-compare@1.4.0: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + picomatch@2.3.1: {} + + prelude-ls@1.2.1: {} + + prettier@3.6.2: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + resolve-from@4.0.0: {} + + reusify@1.1.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + semver@7.7.3: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-api-utils@2.1.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.47.0(eslint@9.39.1)(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.47.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.47.0(eslint@9.39.1)(typescript@5.9.3) + eslint: 9.39.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + yocto-queue@0.1.0: {} diff --git a/src/Api.ts b/src/Api.ts new file mode 100644 index 0000000..28d61ee --- /dev/null +++ b/src/Api.ts @@ -0,0 +1,224 @@ +import type { Environment } from './types.js' +import { Request } from './Request.js' +import { RSAServiceFactory } from './RSAService/RSAServiceFactory.js' +import { ApiAuthentication } from './ApiAuthentication.js' + +/** + * Main API class for Satispay GBusiness SDK + */ +export class Api { + private static env: Environment = 'production' + private static privateKey: string | null = null + private static publicKey: string | null = null + private static keyId: string | null = null + private static version = '1.4.1' + private static authservicesUrl = 'https://authservices.satispay.com' + private static platformVersionHeader: string | null = null + private static platformHeader: string | null = null + private static pluginVersionHeader: string | null = null + private static pluginNameHeader: string | null = null + private static typeHeader: string | null = null + private static trackingHeader: string | null = null + + /** + * Generate new keys and authenticate with token + * @param token Activation token from Satispay dashboard + */ + static async authenticateWithToken(token: string): Promise { + const rsaService = RSAServiceFactory.get() + const rsaKeys = rsaService.generateKeys() + + const generatedPrivateKey = rsaKeys.privateKey + const generatedPublicKey = rsaKeys.publicKey + + const requestResult = await Request.post<{ key_id: string }>( + '/g_business/v1/authentication_keys', + { + body: { + public_key: generatedPublicKey, + token: token, + }, + } + ) + + this.privateKey = generatedPrivateKey + this.publicKey = generatedPublicKey + this.keyId = requestResult.key_id + + return new ApiAuthentication(generatedPrivateKey, generatedPublicKey, requestResult.key_id) + } + + /** + * Get the current environment + */ + static getEnv(): Environment { + return this.env + } + + /** + * Set the current environment + */ + static setEnv(value: Environment): void { + this.env = value + + if (value === 'production') { + this.authservicesUrl = 'https://authservices.satispay.com' + return + } + this.authservicesUrl = `https://${value}.authservices.satispay.com` + } + + /** + * Get platform version header + */ + static getPlatformVersionHeader(): string | null { + return this.platformVersionHeader + } + + /** + * Set platform version header + */ + static setPlatformVersionHeader(value: string): void { + this.platformVersionHeader = value + } + + /** + * Get platform header + */ + static getPlatformHeader(): string | null { + return this.platformHeader + } + + /** + * Set platform header + */ + static setPlatformHeader(value: string): void { + this.platformHeader = value + } + + /** + * Get plugin version header + */ + static getPluginVersionHeader(): string | null { + return this.pluginVersionHeader + } + + /** + * Set plugin version header + */ + static setPluginVersionHeader(value: string): void { + this.pluginVersionHeader = value + } + + /** + * Get plugin name header + */ + static getPluginNameHeader(): string | null { + return this.pluginNameHeader + } + + /** + * Set plugin name header + */ + static setPluginNameHeader(value: string): void { + this.pluginNameHeader = value + } + + /** + * Get type header + */ + static getTypeHeader(): string | null { + return this.typeHeader + } + + /** + * Set type header + */ + static setTypeHeader(value: string): void { + this.typeHeader = value + } + + /** + * Get tracking header + */ + static getTrackingHeader(): string | null { + return this.trackingHeader + } + + /** + * Set tracking header + */ + static setTrackingHeader(value: string): void { + this.trackingHeader = value + } + + /** + * Get private key + */ + static getPrivateKey(): string | null { + return this.privateKey + } + + /** + * Set the private key + */ + static setPrivateKey(value: string): void { + this.privateKey = value + } + + /** + * Get public key + */ + static getPublicKey(): string | null { + return this.publicKey + } + + /** + * Set the public key + */ + static setPublicKey(value: string): void { + this.publicKey = value + } + + /** + * Get the current KeyId + */ + static getKeyId(): string | null { + return this.keyId + } + + /** + * Set the KeyId + */ + static setKeyId(value: string): void { + this.keyId = value + } + + /** + * Get version + */ + static getVersion(): string { + return this.version + } + + /** + * Get authservices url + */ + static getAuthservicesUrl(): string { + return this.authservicesUrl + } + + /** + * Is sandbox enabled? + */ + static getSandbox(): boolean { + return this.env === 'staging' + } + + /** + * Enable or disable sandbox + */ + static setSandbox(value = true): void { + this.setEnv(value ? 'staging' : 'production') + } +} diff --git a/src/ApiAuthentication.ts b/src/ApiAuthentication.ts new file mode 100644 index 0000000..d016f2e --- /dev/null +++ b/src/ApiAuthentication.ts @@ -0,0 +1,14 @@ +/** + * ApiAuthentication class for storing authentication keys + */ +export class ApiAuthentication { + public privateKey: string + public publicKey: string + public keyId: string + + constructor(privateKey: string, publicKey: string, keyId: string) { + this.privateKey = privateKey + this.publicKey = publicKey + this.keyId = keyId + } +} diff --git a/src/Consumer.ts b/src/Consumer.ts new file mode 100644 index 0000000..9a708f6 --- /dev/null +++ b/src/Consumer.ts @@ -0,0 +1,24 @@ +import { Request } from './Request.js' +import { Consumer as ConsumerType } from './types.js' + +/** + * Consumer class for managing Satispay consumers + */ +export class Consumer { + private static readonly apiPath = '/g_business/v1/consumers' + + /** + * Get consumer by phone number + * @param phoneNumber Consumer phone number (format: +39xxxxxxxxxx) + * @param headers Custom headers (optional) + */ + static async get( + phoneNumber: string, + headers: Record = {} + ): Promise { + return Request.get(`${this.apiPath}/${phoneNumber}`, { + headers, + sign: true, + }) + } +} diff --git a/src/DailyClosure.ts b/src/DailyClosure.ts new file mode 100644 index 0000000..9a934d4 --- /dev/null +++ b/src/DailyClosure.ts @@ -0,0 +1,47 @@ +import { Request } from './Request.js' +import { DailyClosure as DailyClosureType, DailyClosureQueryParams } from './types.js' + +/** + * DailyClosure class for retrieving daily closure information + */ +export class DailyClosure { + private static readonly apiPath = '/g_business/v1/daily_closure' + + /** + * Get daily closure + * @see https://developers.satispay.com/reference/retrieve-daily-closure + * @param date Date in format YYYYMMDD (default: today) + * @param query Query parameters (optional) + * @param headers Custom headers (optional) + */ + static async get( + date?: string, + query: DailyClosureQueryParams = {}, + headers: Record = {} + ): Promise { + if (!date) { + const today = new Date() + const year = today.getFullYear() + const month = String(today.getMonth() + 1).padStart(2, '0') + const day = String(today.getDate()).padStart(2, '0') + date = `${year}${month}${day}` + } + + let path = `${this.apiPath}/${date}` + + if (Object.keys(query).length > 0) { + const queryParams = new URLSearchParams() + Object.entries(query).forEach(([key, value]) => { + if (value !== undefined) { + queryParams.append(key, String(value)) + } + }) + path += `?${queryParams.toString()}` + } + + return Request.get(path, { + headers, + sign: true, + }) + } +} diff --git a/src/Payment.ts b/src/Payment.ts new file mode 100644 index 0000000..b9a859c --- /dev/null +++ b/src/Payment.ts @@ -0,0 +1,77 @@ +import { Request } from './Request.js' +import { PaymentResponse, PaymentCreateBody, PaymentQueryParams, PaymentUpdateBody } from './types.js' + +/** + * Payment class for managing Satispay payments + */ +export class Payment { + private static readonly apiPath = '/g_business/v1/payments' + + /** + * Create a payment + * @param body Payment data + * @param headers Custom headers (optional) + */ + static async create( + body: PaymentCreateBody, + headers: Record = {} + ): Promise { + return Request.post(this.apiPath, { + headers, + body, + sign: true, + }) + } + + /** + * Get payment by id + * @param id Payment ID + * @param headers Custom headers (optional) + */ + static async get(id: string, headers: Record = {}): Promise { + return Request.get(`${this.apiPath}/${id}`, { + headers, + sign: true, + }) + } + + /** + * Get the payments list + * @param query Query parameters (optional) + * @param headers Custom headers (optional) + */ + static async all( + query: PaymentQueryParams = {}, + headers: Record = {} + ): Promise<{ list: PaymentResponse[]; has_more: boolean }> { + let path = this.apiPath + + if (Object.keys(query).length > 0) { + const queryString = new URLSearchParams(query as unknown as Record).toString() + path += `?${queryString}` + } + + return Request.get(path, { + headers, + sign: true, + }) + } + + /** + * Update a payment + * @param id Payment ID + * @param body Update data + * @param headers Custom headers (optional) + */ + static async update( + id: string, + body: Partial, + headers: Record = {} + ): Promise { + return Request.put(`${this.apiPath}/${id}`, { + headers, + body, + sign: true, + }) + } +} diff --git a/src/PreAuthorizedPaymentToken.ts b/src/PreAuthorizedPaymentToken.ts new file mode 100644 index 0000000..4bee221 --- /dev/null +++ b/src/PreAuthorizedPaymentToken.ts @@ -0,0 +1,61 @@ +import { Request } from './Request.js' +import { PreAuthorizedPaymentResponse, PreAuthorizedPaymentTokenCreateBody } from './types.js' + +/** + * PreAuthorizedPaymentToken class for managing pre-authorized payment tokens + */ +export class PreAuthorizedPaymentToken { + private static readonly apiPath = '/g_business/v1/pre_authorized_payment_tokens' + + /** + * Create a pre-authorized payment token + * @param body Token data + * @param headers Custom headers (optional) + */ + static async create( + body: PreAuthorizedPaymentTokenCreateBody, + headers: Record = {} + ): Promise { + return Request.post(this.apiPath, { + headers, + body, + sign: true, + }) + } + + /** + * Get a pre-authorized payment token + * @param id Token ID + * @param headers Custom headers (optional) + */ + static async get( + id: string, + headers: Record = {} + ): Promise { + return Request.get(`${this.apiPath}/${id}`, { + headers, + sign: true, + }) + } + + /** + * Update a pre-authorized payment token + * @param id Token ID + * @param body Update data + * @param headers Custom headers (optional) + */ + static async update( + id: string, + body: { + status?: string + metadata?: Record + }, + headers: Record = {} + ): Promise { + return Request.put(`${this.apiPath}/${id}`, { + headers, + body, + sign: true, + }) + } +} diff --git a/src/RSAService/RSAService.ts b/src/RSAService/RSAService.ts new file mode 100644 index 0000000..c121a17 --- /dev/null +++ b/src/RSAService/RSAService.ts @@ -0,0 +1,23 @@ +import { RSAKeyPair } from '../types.js' + +/** + * Abstract RSA Service Contract + */ +export abstract class RSAService { + /** + * Verifies that this RSA implementation is available + */ + abstract isAvailable(): boolean + + /** + * Generate a pair of RSA keys + */ + abstract generateKeys(): RSAKeyPair + + /** + * Sign a string with the given private key + * @param privateKey The private key used for signing + * @param message The message that will be signed + */ + abstract sign(privateKey: string, message: string): Buffer +} diff --git a/src/RSAService/RSAServiceCrypto.ts b/src/RSAService/RSAServiceCrypto.ts new file mode 100644 index 0000000..889b431 --- /dev/null +++ b/src/RSAService/RSAServiceCrypto.ts @@ -0,0 +1,63 @@ +import * as crypto from 'crypto' +import { RSAService } from './RSAService.js' +import { RSAKeyPair } from '../types.js' + +/** + * RSA Service implemented via Node.js crypto module + */ +export class RSAServiceCrypto extends RSAService { + /** + * Check if Node.js crypto module is available + */ + isAvailable(): boolean { + try { + return typeof crypto.generateKeyPairSync === 'function' + } catch { + return false + } + } + + /** + * Generate a pair of RSA keys + */ + generateKeys(): RSAKeyPair { + try { + const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', { + modulusLength: 2048, + publicKeyEncoding: { + type: 'spki', + format: 'pem', + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem', + }, + }) + + return { + privateKey, + publicKey, + } + } catch (error) { + throw new Error( + `Failed to generate RSA keys: ${error instanceof Error ? error.message : String(error)}` + ) + } + } + + /** + * Sign a message with the given private key + * @param privateKey The private key used for signing + * @param message The message that will be signed + */ + sign(privateKey: string, message: string): Buffer { + try { + const sign = crypto.createSign('RSA-SHA256') + sign.update(message) + sign.end() + return sign.sign(privateKey) + } catch (error) { + throw new Error(`Signing failed: ${error instanceof Error ? error.message : String(error)}`) + } + } +} diff --git a/src/RSAService/RSAServiceFactory.ts b/src/RSAService/RSAServiceFactory.ts new file mode 100644 index 0000000..f3e50a0 --- /dev/null +++ b/src/RSAService/RSAServiceFactory.ts @@ -0,0 +1,26 @@ +import { RSAServiceCrypto } from './RSAServiceCrypto.js' +import { RSAService } from './RSAService.js' + +/** + * Factory for creating RSA service instances + */ +export class RSAServiceFactory { + private static instance: RSAService | null = null + + /** + * Get an instance of the appropriate RSA service based on availability + */ + static get(): RSAService { + if (this.instance) { + return this.instance + } + + const service = new RSAServiceCrypto() + if (service.isAvailable()) { + this.instance = service + return this.instance + } + + throw new Error('No RSA service available.') + } +} diff --git a/src/Request.ts b/src/Request.ts new file mode 100644 index 0000000..66f9429 --- /dev/null +++ b/src/Request.ts @@ -0,0 +1,244 @@ +import * as crypto from 'crypto' +import { HttpMethod, RequestOptions } from './types.js' +import { RSAServiceFactory } from './RSAService/RSAServiceFactory.js' + +/** + * Request class for making HTTP requests to Satispay API + * Uses native fetch API - compatible with Node.js 18+, Deno, and Bun + */ +export class Request { + private static userAgentName = 'SatispayGBusinessApiNodeSdk' + + // Header constants + static readonly HEADER_OS = 'x-satispay-os' + static readonly HEADER_OS_VERSION = 'x-satispay-osv' + static readonly HEADER_APP_VERSION = 'x-satispay-appv' + static readonly HEADER_APP_NAME = 'x-satispay-appn' + static readonly HEADER_DEVICE_TYPE = 'x-satispay-devicetype' + static readonly HEADER_TRACKING_CODE = 'x-satispay-tracking-code' + static readonly HEADER_IDEMPOTENCY_KEY = 'Idempotency-Key' + + /** + * Make a GET request + * @param path API endpoint path + * @param options Request options + */ + static async get(path: string, options: RequestOptions = {}): Promise { + return this.request({ + path, + method: 'GET', + ...options, + }) + } + + /** + * Make a POST request + * @param path API endpoint path + * @param options Request options + */ + static async post(path: string, options: RequestOptions = {}): Promise { + return this.request({ + path, + method: 'POST', + ...options, + }) + } + + /** + * Make a PUT request + * @param path API endpoint path + * @param options Request options + */ + static async put(path: string, options: RequestOptions = {}): Promise { + return this.request({ + path, + method: 'PUT', + ...options, + }) + } + + /** + * Make a PATCH request + * @param path API endpoint path + * @param options Request options + */ + static async patch(path: string, options: RequestOptions = {}): Promise { + return this.request({ + path, + method: 'PATCH', + ...options, + }) + } + + /** + * Sign the request with RSA signature + */ + private static signRequest(options: { + method: HttpMethod + path: string + body: string + authservicesUrl: string + privateKey: string + keyId: string + }): Record { + const headers: Record = {} + + const date = new Date().toUTCString() + headers['Date'] = date + + const digest = Buffer.from(crypto.createHash('sha256').update(options.body).digest()).toString( + 'base64' + ) + headers['Digest'] = `SHA-256=${digest}` + + let signature = `(request-target): ${options.method.toLowerCase()} ${options.path}\n` + signature += `host: ${options.authservicesUrl.replace('https://', '')}\n` + + if (options.body) { + signature += `content-type: application/json\n` + signature += `content-length: ${Buffer.byteLength(options.body)}\n` + } + + signature += `digest: SHA-256=${digest}\n` + signature += `date: ${date}` + + const rsaService = RSAServiceFactory.get() + const signedSignature = rsaService.sign(options.privateKey, signature) + const base64SignedSignature = signedSignature.toString('base64') + + let signatureHeaders = '(request-target) host digest date' + if (options.body) { + signatureHeaders = '(request-target) host content-type content-length digest date' + } + + headers['Authorization'] = + `Signature keyId="${options.keyId}", algorithm="rsa-sha256", headers="${signatureHeaders}", signature="${base64SignedSignature}"` + + return headers + } + + /** + * Execute HTTP request using native fetch API + * Compatible with Node.js 18+, Deno, and Bun + */ + private static async request(options: { + path: string + method: HttpMethod + body?: unknown + headers?: Record + sign?: boolean + }): Promise { + // Import Api here to avoid circular dependency + const { Api } = await import('./Api.js') + + const headers: Record = { + Accept: 'application/json', + 'User-Agent': `${this.userAgentName}/${Api.getVersion()}`, + ...options.headers, + } + + // Add custom headers from Api + const platformHeader = Api.getPlatformHeader() + const platformVersionHeader = Api.getPlatformVersionHeader() + const pluginVersionHeader = Api.getPluginVersionHeader() + const pluginNameHeader = Api.getPluginNameHeader() + const typeHeader = Api.getTypeHeader() + const trackingHeader = Api.getTrackingHeader() + + if (platformHeader) headers[this.HEADER_OS] = platformHeader + if (platformVersionHeader) headers[this.HEADER_OS_VERSION] = platformVersionHeader + if (pluginVersionHeader) headers[this.HEADER_APP_VERSION] = pluginVersionHeader + if (pluginNameHeader) headers[this.HEADER_APP_NAME] = pluginNameHeader + if (typeHeader) headers[this.HEADER_DEVICE_TYPE] = typeHeader + if (trackingHeader) headers[this.HEADER_TRACKING_CODE] = trackingHeader + + let body = '' + if (options.body) { + body = JSON.stringify(options.body) + headers['Content-Type'] = 'application/json' + headers['Content-Length'] = Buffer.byteLength(body).toString() + } + + // Sign request if needed + if (options.sign) { + const privateKey = Api.getPrivateKey() + const keyId = Api.getKeyId() + + if (privateKey && keyId) { + const signHeaders = this.signRequest({ + method: options.method, + path: options.path, + body, + authservicesUrl: Api.getAuthservicesUrl(), + privateKey, + keyId, + }) + Object.assign(headers, signHeaders) + } + } + + const url = Api.getAuthservicesUrl() + options.path + + try { + // Prepare fetch options + const fetchOptions: RequestInit = { + method: options.method, + headers, + } + + // Add body for POST, PUT, PATCH + if (body && options.method !== 'GET') { + fetchOptions.body = body + } + + // Disable SSL verification in test mode (Node.js only) + // Note: Deno and Bun don't support this directly via fetch + if (Api.getEnv() === 'test' && typeof process !== 'undefined' && process.versions?.node) { + // For Node.js 18+ with fetch, we need to use a custom agent + // This is a workaround for test environments only + const https = await import('https') + const agent = new https.Agent({ + rejectUnauthorized: false, + }) + // @ts-expect-error - agent is not in the standard fetch API but Node.js supports it + fetchOptions.agent = agent + } + + const response = await fetch(url, fetchOptions) + + // Read response body + const responseText = await response.text() + + let parsedData: unknown + try { + parsedData = JSON.parse(responseText) + } catch { + parsedData = responseText + } + + // Check response status + const isResponseOk = response.status >= 200 && response.status <= 299 + + if (!isResponseOk) { + const errorData = parsedData as { + message?: string + code?: string + wlt?: string + } + + if (errorData?.message && errorData?.code && errorData?.wlt) { + throw new Error(`${errorData.message}, request id: ${errorData.wlt}`) + } else { + throw new Error(`HTTP status is not 2xx: ${response.status}`) + } + } + + return parsedData as T + } catch (error) { + if (error instanceof Error) { + throw error + } + throw new Error(`Request failed: ${String(error)}`) + } + } +} diff --git a/src/bin/satispay-keygen.ts b/src/bin/satispay-keygen.ts new file mode 100644 index 0000000..c5ed300 --- /dev/null +++ b/src/bin/satispay-keygen.ts @@ -0,0 +1,135 @@ +#!/usr/bin/env node + +/** + * Satispay Key Generator CLI + * + * Usage: + * npx @volverjs/satispay-node-sdk + * + * Options: + * --sandbox, -s Use sandbox environment (default: true) + * --production, -p Use production environment + * --help, -h Show help + */ + +import { Api } from '../index.js'; + +const args = process.argv.slice(2); + +// Show help +if (args.includes('--help') || args.includes('-h') || args.length === 0) { + console.log(` +Satispay Key Generator + +Generate RSA keys from an activation token. + +Usage: + npx @volverjs/satispay-node-sdk [options] + +Options: + --sandbox, -s Use sandbox environment (default) + --production, -p Use production environment + --help, -h Show this help message + +Example: + npx @volverjs/satispay-node-sdk YOUR_TOKEN + npx @volverjs/satispay-node-sdk YOUR_TOKEN --production + +Output: + The command will generate and display: + - Public Key + - Private Key + - Key ID + + Store these credentials securely in your environment variables or configuration. +`); + process.exit(0); +} + +// Parse arguments +let token: string | undefined; +let useSandbox = true; + +for (const arg of args) { + if (arg === '--production' || arg === '-p') { + useSandbox = false; + } else if (arg === '--sandbox' || arg === '-s') { + useSandbox = true; + } else if (!arg.startsWith('-')) { + token = arg; + } +} + +// Validate token +if (!token) { + console.error('Error: Activation token is required\n'); + console.log('Usage: npx @volverjs/satispay-node-sdk '); + console.log('Run with --help for more information'); + process.exit(1); +} + +// Generate keys +async function generateKeys() { + try { + console.log('\n🔑 Satispay Key Generator'); + console.log('========================\n'); + console.log(`Environment: ${useSandbox ? 'Sandbox (Test)' : 'Production'}`); + console.log(`Token: ${token!.substring(0, 10)}...`); + console.log('\nGenerating RSA keys...\n'); + + Api.setSandbox(useSandbox); + + const authentication = await Api.authenticateWithToken(token!); + + console.log('✅ Keys generated successfully!\n'); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + + console.log('📋 Public Key:'); + console.log(authentication.publicKey); + console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + + console.log('🔐 Private Key:'); + console.log(authentication.privateKey); + console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + + console.log('🆔 Key ID:'); + console.log(authentication.keyId); + console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + + console.log('⚠️ IMPORTANT: Store these credentials securely!'); + console.log(' Never commit them to version control.\n'); + + console.log('💡 Environment Variables (.env):'); + console.log(` +SATISPAY_PUBLIC_KEY="${authentication.publicKey.replace(/\n/g, '\\n')}" +SATISPAY_PRIVATE_KEY="${authentication.privateKey.replace(/\n/g, '\\n')}" +SATISPAY_KEY_ID="${authentication.keyId}" +`); + + console.log('📝 Usage in your code:'); + console.log(` +import { Api } from '@volverjs/satispay-node-sdk'; + +Api.setSandbox(${useSandbox}); +Api.setPublicKey(process.env.SATISPAY_PUBLIC_KEY!); +Api.setPrivateKey(process.env.SATISPAY_PRIVATE_KEY!); +Api.setKeyId(process.env.SATISPAY_KEY_ID!); +`); + + } catch (error: any) { + console.error('\n❌ Error generating keys:\n'); + console.error(error.message); + + if (error.message.includes('401')) { + console.error('\n💡 The activation token is invalid or has expired.'); + console.error(' Generate a new token from your Satispay Business Dashboard.'); + } else if (error.message.includes('network') || error.message.includes('ENOTFOUND')) { + console.error('\n💡 Network error. Please check your internet connection.'); + } + + console.error('\n'); + process.exit(1); + } +} + +generateKeys(); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..ed33c64 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,32 @@ +/** + * Satispay GBusiness API Node.js SDK + * + * @example + * ```typescript + * import { Api, Payment } from '@volverjs/satispay-node-sdk'; + * + * // Set your keys + * Api.setPublicKey('-----BEGIN PUBLIC KEY-----...'); + * Api.setPrivateKey('-----BEGIN PRIVATE KEY-----...'); + * Api.setKeyId('your-key-id'); + * + * // Create a payment + * const payment = await Payment.create({ + * flow: 'MATCH_CODE', + * amount_unit: 100, + * currency: 'EUR' + * }); + * ``` + */ + +export { Api } from './Api.js' +export { ApiAuthentication } from './ApiAuthentication.js' +export { Payment } from './Payment.js' +export { Consumer } from './Consumer.js' +export { DailyClosure } from './DailyClosure.js' +export { PreAuthorizedPaymentToken } from './PreAuthorizedPaymentToken.js' +export { Request } from './Request.js' +export { RSAServiceFactory } from './RSAService/RSAServiceFactory.js' + +// Export types +export * from './types.js' diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..9f4dd96 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,194 @@ +/** + * Interface for RSA key pair generation result + */ +export type RSAKeyPair = { + privateKey: string + publicKey: string +} + +/** + * Request options + */ +export type RequestOptions = { + headers?: Record + body?: unknown + sign?: boolean +} + +/** + * HTTP method + */ +export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' + +/** + * Environment + */ +export type Environment = 'production' | 'staging' | 'test' + +/** + * Payment status + */ +export type PaymentStatus = 'PENDING' | 'AUTHORIZED' | 'ACCEPTED' | 'CANCELED' + +/** + * Payment flow + */ +export type PaymentFlow = + | 'MATCH_CODE' + | 'MATCH_USER' + | 'REFUND' + | 'PRE_AUTHORIZED' + | 'FUND_LOCK' + | 'PRE_AUTHORIZED_FUND_LOCK' + | 'HOTP_AUTH' + +/** + * Payment action + */ +export type PaymentAction = 'ACCEPT' | 'CANCEL' | 'CANCEL_OR_REFUND' + +/** + * Payment type + */ +export type PaymentType = 'TO_BUSINESS' | 'REFUND_TO_BUSINESS' + +/** + * Actor type + */ +export type Actor = 'CONSUMER' | 'SHOP' + +/** + * Pre-authorized token status + */ + +export type PreAuthorizedTokenStatus = 'PENDING' | 'ACCEPTED' | 'CANCELED' + +/** + * Payment creation body + */ +export type PaymentCreateBody = { + flow: PaymentFlow + amount_unit: number + currency: string + callback_url?: string + external_code?: string + metadata?: Record + expiration_date?: string + consumer_uid?: string + required_success_email?: string + pre_authorized_payments_token?: string +} & ( + | { flow: 'HOTP_AUTH' | 'PRE_AUTHORIZED' | 'PRE_AUTHORIZED_FUND_LOCK'; token: string } + | { flow: 'REFUND'; parent_payment_uid: string } + | { flow: 'MATCH_USER'; consumer_uid: string } + | { flow: 'MATCH_CODE' | 'FUND_LOCK' } +) + +/** + * Payment update body + */ +export type PaymentUpdateBody = { + action: PaymentAction + amount_unit?: number +} + +/** + * Pre-authorized payment token creation body + */ + +export type PreAuthorizedPaymentTokenCreateBody = { + reason?: string + callback_url?: string + redirect_url?: string +} + +/** + * Pre-authorized payment token update body + */ +export type PreAuthorizedPaymentTokenUpdateBody = { + status?: PreAuthorizedTokenStatus + consumer_uid?: string + metadata?: string +} + +/** + * Payment response + */ +export type PaymentResponse = { + id: string + code_identifier: string + type: PaymentType + amount_unit: number + currency: string + status: PaymentStatus + expired: boolean + metadata?: Record + sender?: { + id: string + type: Actor + name?: string + } + receiver?: { + id: string + type: Actor + } + insert_date?: string + expire_date?: string + external_code?: string + daily_closure?: string +} + +/** + * Consumer interface + */ +export type Consumer = { + id: string + type: string + name?: string + phone_number?: string +} + +/** + * Daily closure interface + */ +export type DailyClosure = { + date: string + total_amount_unit: number + currency: string + payments_count: number +} + +/** + * Pre-authorized payment token interface + */ +export type PreAuthorizedPaymentResponse = { + id: string + token: string + status: PreAuthorizedTokenStatus + consumer_uid?: string + expire_date?: string + metadata?: Record +} + +/** + * Query parameters for listing payments + */ +export type PaymentQueryParams = { + limit?: number + starting_after?: string + starting_after_timestamp?: string + consumer_uid?: string + payment_type?: string + status?: PaymentStatus + external_code?: string + to_date?: string + from_date?: string +} + +/** + * Query parameters for daily closure + */ +export type DailyClosureQueryParams = { + limit?: number + starting_after?: string +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..12f3e84 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "nodenext", + "lib": [ + "ES2020" + ], + "declaration": true, + "declarationMap": true, + "outDir": "./dist", + "rootDir": ".", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "nodenext", + "resolveJsonModule": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "src/**/*", + "src/bin/**/*" + ], + "exclude": [ + "node_modules", + "dist", + "examples" + ] +} \ No newline at end of file From 5508868ce23ad7c942d04881eddd74e40ce09854 Mon Sep 17 00:00:00 2001 From: Alessandro Bellesia Date: Wed, 19 Nov 2025 00:13:24 +0100 Subject: [PATCH 2/5] Add test with vitest feat: add vitest feat: add more examples feat: add CHANGELOG --- .gitignore | 4 +- CHANGELOG.md | 71 + README.md | 91 +- eslint.config.js | 15 + examples/error-handling-retry.ts | 129 ++ examples/payment-monitoring.ts | 195 ++ examples/using-utilities.ts | 220 +++ examples/webhook-handler.ts | 199 ++ package.json | 32 +- pnpm-lock.yaml | 2302 +++++++++++++++++++++++ pnpm-workspace.yaml | 2 + src/Api.ts | 28 + src/Payment.ts | 68 +- src/index.ts | 9 + src/utils.ts | 365 ++++ tests/Api.test.ts | 93 + tests/ApiAuthentication.test.ts | 47 + tests/Consumer.test.ts | 70 + tests/DailyClosure.test.ts | 67 + tests/Payment.test.ts | 237 +++ tests/PreAuthorizedPaymentToken.test.ts | 187 ++ tests/RSAServiceCrypto.test.ts | 80 + tests/RSAServiceFactory.test.ts | 29 + tests/Request.test.ts | 261 +++ tsconfig.json | 16 +- vite.config.ts | 41 + vitest.config.ts | 28 + 27 files changed, 4851 insertions(+), 35 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 examples/error-handling-retry.ts create mode 100644 examples/payment-monitoring.ts create mode 100644 examples/using-utilities.ts create mode 100644 examples/webhook-handler.ts create mode 100644 pnpm-workspace.yaml create mode 100644 src/utils.ts create mode 100644 tests/Api.test.ts create mode 100644 tests/ApiAuthentication.test.ts create mode 100644 tests/Consumer.test.ts create mode 100644 tests/DailyClosure.test.ts create mode 100644 tests/Payment.test.ts create mode 100644 tests/PreAuthorizedPaymentToken.test.ts create mode 100644 tests/RSAServiceCrypto.test.ts create mode 100644 tests/RSAServiceFactory.test.ts create mode 100644 tests/Request.test.ts create mode 100644 vite.config.ts create mode 100644 vitest.config.ts diff --git a/.gitignore b/.gitignore index 34f461d..15c4506 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ examples/authentication.json node_modules -dist \ No newline at end of file +dist +coverage +*.env.local \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..37cbf6a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,71 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added +- Comprehensive test suite with 66 tests and 72% coverage +- GitHub Actions CI/CD workflows for automated testing and npm publishing +- Vitest test framework with UI and coverage reporting +- Support for multiple runtimes: Node.js 18+, Deno 1.30+, Bun 1.0+ + +### Changed +- Migrated build system from TypeScript compiler to Vite +- Improved TypeScript declaration generation with vite-plugin-dts +- Tests moved to dedicated `tests/` folder for better organization + +### Fixed +- TypeScript declaration files output to correct location in dist/ + +## [0.0.1] - TBD + +### Added +- Initial implementation of Satispay GBusiness Node.js SDK +- Api class for configuration and environment management +- ApiAuthentication for token-based authentication +- Payment operations (create, get, list, update) +- Consumer operations (get consumer details) +- DailyClosure operations (get daily closure reports) +- PreAuthorizedPaymentToken operations (create, get, list, accept, reject) +- Request class for HTTP operations with automatic signing +- RSA service for key generation and cryptographic operations +- Support for both Node.js crypto and Web Crypto API +- CLI tool `satispay-keygen` for generating RSA key pairs +- Comprehensive examples for common operations +- Zero runtime dependencies + +### API Methods + +#### Authentication +- `Api.authenticateWithToken(token: string)` - Generate keys and authenticate + +#### Configuration +- `Api.setEnv(env: Environment)` - Set environment (staging/production) +- `Api.setKeys(keyId: string, privateKey: string)` - Set authentication keys +- `Api.setPlatformHeader(value: string)` - Set platform identification header + +#### Payment Operations +- `Payment.create(options)` - Create a new payment +- `Payment.get(id: string)` - Get payment details +- `Payment.list(options)` - List payments with filters +- `Payment.update(id: string, options)` - Update payment metadata + +#### Consumer Operations +- `Consumer.get(id: string)` - Get consumer details + +#### Daily Closure Operations +- `DailyClosure.get(date: Date)` - Get daily closure report + +#### Pre-Authorized Payment Tokens +- `PreAuthorizedPaymentToken.create(options)` - Create token +- `PreAuthorizedPaymentToken.get(token: string)` - Get token details +- `PreAuthorizedPaymentToken.list()` - List tokens +- `PreAuthorizedPaymentToken.accept(token: string)` - Accept token +- `PreAuthorizedPaymentToken.reject(token: string)` - Reject token + +[Unreleased]: https://github.com/volverjs/satispay-node-sdk/compare/v0.0.1...HEAD +[0.0.1]: https://github.com/volverjs/satispay-node-sdk/releases/tag/v0.0.1 diff --git a/README.md b/README.md index 3b140c0..545c4d5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # (Unofficial) Satispay GBusiness Node.js API SDK [![npm version](https://img.shields.io/npm/v/@volverjs/satispay-node-sdk.svg)](https://www.npmjs.com/package/@volverjs/satispay-node-sdk) +[![npm downloads](https://img.shields.io/npm/dm/@volverjs/satispay-node-sdk.svg)](https://www.npmjs.com/package/@volverjs/satispay-node-sdk) +[![CI](https://github.com/volverjs/satispay-node-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/volverjs/satispay-node-sdk/actions/workflows/ci.yml) +[![codecov](https://codecov.io/gh/volverjs/satispay-node-sdk/branch/main/graph/badge.svg)](https://codecov.io/gh/volverjs/satispay-node-sdk) [![TypeScript](https://img.shields.io/badge/TypeScript-5.7-blue.svg)](https://www.typescriptlang.org/) [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-green.svg)](https://www.npmjs.com/package/@volverjs/satispay-node-sdk) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) @@ -50,28 +53,48 @@ bun add @volverjs/satispay-node-sdk ### Authentication -1. Generate an activation token from your [Satispay Business Dashboard](https://business.satispay.com) -2. Authenticate and generate RSA keys: +Get your credentials using the CLI tool with an activation token: -```typescript -import { Api } from '@volverjs/satispay-node-sdk'; +```bash +npx @volverjs/satispay-node-sdk YOUR_ACTIVATION_TOKEN +``` -Api.setSandbox(true); +This will generate: +- **Public Key** - RSA public key +- **Private Key** - RSA private key (keep this secure!) +- **Key ID** - Your authentication key ID -const authentication = await Api.authenticateWithToken('YOUR_ACTIVATION_TOKEN'); +**Where to get the activation token:** +- **Production**: [Satispay Business Dashboard](https://business.satispay.com) → Developers → Generate Activation Code +- **Sandbox**: Request from [Satispay Developer Support](https://developers.satispay.com/docs/credentials#sandbox-account) -// Store these securely -const publicKey = authentication.publicKey; -const privateKey = authentication.privateKey; -const keyId = authentication.keyId; +**Options:** +```bash +npx @volverjs/satispay-node-sdk YOUR_TOKEN # Sandbox (default) +npx @volverjs/satispay-node-sdk YOUR_TOKEN --production # Production +npx @volverjs/satispay-node-sdk YOUR_TOKEN --sandbox # Sandbox (explicit) ``` -3. Configure the SDK with your keys: +> **⚠️ Important**: The activation token is single-use. Save the generated credentials securely! + +### Configure the SDK + +Use the generated credentials to configure the SDK: ```typescript -Api.setPublicKey(publicKey); -Api.setPrivateKey(privateKey); -Api.setKeyId(keyId); +import { Api } from '@volverjs/satispay-node-sdk'; + +Api.setSandbox(true); // or false for production +Api.setPublicKey(process.env.SATISPAY_PUBLIC_KEY!); +Api.setPrivateKey(process.env.SATISPAY_PRIVATE_KEY!); +Api.setKeyId(process.env.SATISPAY_KEY_ID!); +``` + +Store credentials in `.env`: +```bash +SATISPAY_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n..." +SATISPAY_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n..." +SATISPAY_KEY_ID="your-key-id" ``` ### Create a Payment @@ -317,19 +340,49 @@ SATISPAY_KEY_ID="your-key-id" ```bash # Install dependencies -npm install +pnpm install # Build -npm run build +pnpm build # Watch mode -npm run build:watch +pnpm build:watch + +# Run tests +pnpm test + +# Watch tests +pnpm test:watch + +# Test with UI +pnpm test:ui + +# Coverage report +pnpm test:coverage # Lint -npm run lint +pnpm lint # Format -npm run format +pnpm format +``` + +## Testing + +This project uses [Vitest](https://vitest.dev/) for testing. The test suite includes: + +- **Unit tests** for all core modules (Api, Payment, Consumer, DailyClosure, etc.) +- **RSA Service tests** for cryptographic operations +- **Mock-based tests** for API interactions + +Current test coverage: **79.45%** + +Run tests: +```bash +pnpm test # Run all tests +pnpm test:watch # Watch mode for development +pnpm test:ui # Interactive UI +pnpm test:coverage # Generate coverage report ``` ## Examples diff --git a/eslint.config.js b/eslint.config.js index 33b8891..f2623ff 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,5 +1,6 @@ import eslint from '@eslint/js'; import tseslint from 'typescript-eslint'; +import vitest from 'eslint-plugin-vitest'; export default tseslint.config( eslint.configs.recommended, @@ -9,5 +10,19 @@ export default tseslint.config( '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'warn', }, + }, + { + files: ['**/*.test.ts', '**/*.spec.ts'], + plugins: { + vitest, + }, + rules: { + ...vitest.configs.recommended.rules, + }, + languageOptions: { + globals: { + ...vitest.environments.env.globals, + }, + }, } ); diff --git a/examples/error-handling-retry.ts b/examples/error-handling-retry.ts new file mode 100644 index 0000000..5d8ff9b --- /dev/null +++ b/examples/error-handling-retry.ts @@ -0,0 +1,129 @@ +/** + * Advanced Example: Error Handling with Retry Logic + * + * This example demonstrates how to implement robust error handling + * with exponential backoff retry logic for API calls. + */ + +import { Api, Payment } from '@volverjs/satispay-node-sdk' + +// Configure API +Api.setEnv('production') +Api.setKeyId('your-key-id') +Api.setPrivateKey('your-private-key') + +/** + * Retry configuration + */ +interface RetryConfig { + maxRetries: number + initialDelay: number + maxDelay: number + backoffMultiplier: number +} + +const defaultRetryConfig: RetryConfig = { + maxRetries: 3, + initialDelay: 1000, // 1 second + maxDelay: 10000, // 10 seconds + backoffMultiplier: 2, +} + +/** + * Sleep helper + */ +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +/** + * Retry wrapper with exponential backoff + */ +async function retryWithBackoff( + fn: () => Promise, + config: RetryConfig = defaultRetryConfig +): Promise { + let lastError: Error | undefined + let delay = config.initialDelay + + for (let attempt = 0; attempt <= config.maxRetries; attempt++) { + try { + return await fn() + } catch (error) { + lastError = error as Error + + // Don't retry on client errors (4xx) + if (error instanceof Error && error.message.includes('400')) { + throw error + } + + if (attempt < config.maxRetries) { + console.log( + `Attempt ${attempt + 1} failed: ${lastError.message}. Retrying in ${delay}ms...` + ) + await sleep(delay) + delay = Math.min(delay * config.backoffMultiplier, config.maxDelay) + } + } + } + + throw new Error( + `Max retries (${config.maxRetries}) exceeded. Last error: ${lastError?.message}` + ) +} + +/** + * Create payment with retry logic + */ +async function createPaymentWithRetry( + amount: number, + description: string +): Promise { + return retryWithBackoff(async () => { + return await Payment.create({ + flow: 'MATCH_CODE', + amount_unit: amount, + currency: 'EUR', + external_code: `ORDER-${Date.now()}`, + metadata: { + description, + timestamp: new Date().toISOString(), + }, + }) + }) +} + +/** + * Get payment with retry logic + */ +async function getPaymentWithRetry(paymentId: string): Promise { + return retryWithBackoff(async () => { + return await Payment.get(paymentId) + }) +} + +/** + * Example usage + */ +async function main() { + try { + // Create payment with automatic retries + console.log('Creating payment with retry logic...') + const payment = await createPaymentWithRetry( + 1000, // 10.00 EUR + 'Test payment with retry logic' + ) + console.log('Payment created:', payment) + + // Get payment with automatic retries + console.log('\nGetting payment with retry logic...') + const fetchedPayment = await getPaymentWithRetry(payment.id) + console.log('Payment retrieved:', fetchedPayment) + } catch (error) { + console.error('Error:', error) + process.exit(1) + } +} + +// Run example +main() diff --git a/examples/payment-monitoring.ts b/examples/payment-monitoring.ts new file mode 100644 index 0000000..cae07d5 --- /dev/null +++ b/examples/payment-monitoring.ts @@ -0,0 +1,195 @@ +/** + * Advanced Example: Payment Monitoring with Polling + * + * This example demonstrates how to monitor payment status + * with intelligent polling and timeout handling. + */ + +import { Api, Payment } from '@volverjs/satispay-node-sdk' + +// Configure API +Api.setEnv('staging') +Api.setKeyId('your-key-id') +Api.setPrivateKey('your-private-key') + +/** + * Payment status type + */ +type PaymentStatus = 'PENDING' | 'ACCEPTED' | 'CANCELED' | 'EXPIRED' + +/** + * Polling configuration + */ +interface PollingConfig { + /** Initial polling interval in milliseconds */ + initialInterval: number + /** Maximum polling interval in milliseconds */ + maxInterval: number + /** Polling interval multiplier */ + intervalMultiplier: number + /** Maximum total polling duration in milliseconds */ + timeout: number +} + +const defaultPollingConfig: PollingConfig = { + initialInterval: 2000, // 2 seconds + maxInterval: 10000, // 10 seconds + intervalMultiplier: 1.5, + timeout: 300000, // 5 minutes +} + +/** + * Sleep helper + */ +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +/** + * Monitor payment until final status or timeout + */ +async function monitorPayment( + paymentId: string, + config: PollingConfig = defaultPollingConfig +): Promise { + const startTime = Date.now() + let interval = config.initialInterval + let lastStatus: PaymentStatus | null = null + + console.log(`Starting to monitor payment: ${paymentId}`) + console.log(`Timeout: ${config.timeout / 1000}s`) + + while (true) { + const elapsed = Date.now() - startTime + + // Check timeout + if (elapsed >= config.timeout) { + throw new Error(`Payment monitoring timed out after ${elapsed}ms`) + } + + try { + // Fetch payment status + const payment = await Payment.get(paymentId) + const currentStatus = payment.status as PaymentStatus + + // Log status change + if (currentStatus !== lastStatus) { + console.log( + `[${new Date().toISOString()}] Status changed: ${lastStatus || 'INITIAL'} -> ${currentStatus}` + ) + lastStatus = currentStatus + } + + // Check if payment reached final status + if ( + currentStatus === 'ACCEPTED' || + currentStatus === 'CANCELED' || + currentStatus === 'EXPIRED' + ) { + console.log(`Payment reached final status: ${currentStatus}`) + return payment + } + + // Wait before next poll + console.log(`Waiting ${interval / 1000}s before next poll...`) + await sleep(interval) + + // Increase interval (exponential backoff) + interval = Math.min(interval * config.intervalMultiplier, config.maxInterval) + } catch (error) { + console.error('Error fetching payment:', error) + // Wait before retry + await sleep(interval) + } + } +} + +/** + * Create and monitor payment + */ +async function createAndMonitorPayment( + amount: number, + description: string +): Promise { + // Create payment + console.log('Creating payment...') + const payment = await Payment.create({ + flow: 'MATCH_CODE', + amount_unit: amount, + currency: 'EUR', + external_code: `ORDER-${Date.now()}`, + metadata: { + description, + timestamp: new Date().toISOString(), + }, + }) + + console.log('Payment created:', { + id: payment.id, + status: payment.status, + code_identifier: payment.code_identifier, + }) + console.log(`\nPay with code: ${payment.code_identifier}\n`) + + // Monitor payment + try { + const finalPayment = await monitorPayment(payment.id, { + initialInterval: 2000, + maxInterval: 10000, + intervalMultiplier: 1.5, + timeout: 300000, // 5 minutes + }) + + console.log('\n=== Final Payment Status ===') + console.log(`ID: ${finalPayment.id}`) + console.log(`Status: ${finalPayment.status}`) + console.log(`Amount: ${finalPayment.amount_unit / 100} EUR`) + console.log(`Created: ${finalPayment.insert_date}`) + console.log(`Updated: ${finalPayment.update_date}`) + + return finalPayment + } catch (error) { + console.error('\nError monitoring payment:', error) + throw error + } +} + +/** + * Monitor multiple payments concurrently + */ +async function monitorMultiplePayments(paymentIds: string[]): Promise { + console.log(`Monitoring ${paymentIds.length} payments concurrently...`) + + const promises = paymentIds.map((id) => + monitorPayment(id, { + initialInterval: 3000, + maxInterval: 15000, + intervalMultiplier: 1.5, + timeout: 300000, + }) + ) + + return Promise.all(promises) +} + +/** + * Example usage + */ +async function main() { + try { + // Example 1: Create and monitor single payment + console.log('=== Example 1: Single Payment ===\n') + const payment = await createAndMonitorPayment( + 1000, // 10.00 EUR + 'Test payment with monitoring' + ) + + console.log('\nPayment monitoring completed:', payment.status) + } catch (error) { + console.error('Error:', error) + process.exit(1) + } +} + +// Run example +main() diff --git a/examples/using-utilities.ts b/examples/using-utilities.ts new file mode 100644 index 0000000..8c6031e --- /dev/null +++ b/examples/using-utilities.ts @@ -0,0 +1,220 @@ +/** + * Example: Using Utility Functions + * + * This example demonstrates how to use the utility functions + * provided by the SDK for common operations. + */ + +import { + Api, + Payment, + Amount, + DateUtils, + Validation, + CodeGenerator, + PaymentStatusUtils, +} from '@volverjs/satispay-node-sdk' + +// Configure API +Api.setEnv('staging') +Api.setKeyId('your-key-id') +Api.setPrivateKey('your-private-key') + +/** + * Example 1: Amount utilities + */ +function amountUtilitiesExample() { + console.log('=== Amount Utilities ===\n') + + // Convert euros to cents + const euros = 10.5 + const cents = Amount.toCents(euros) + console.log(`${euros} EUR = ${cents} cents`) + + // Convert cents to euros + console.log(`${cents} cents = ${Amount.toEuros(cents)} EUR`) + + // Format amount for display + console.log(`Formatted: ${Amount.format(cents)}`) + console.log(`Formatted (en-US): ${Amount.format(cents, 'en-US')}`) + + // Parse formatted amount + const parsed = Amount.parse('10,50 €') + console.log(`Parsed "10,50 €" = ${parsed} cents`) + + // Validate amount + console.log(`Is 1000 cents valid? ${Amount.isValid(1000)}`) + console.log(`Is -100 cents valid? ${Amount.isValid(-100)}`) + console.log(`Is 10.5 cents valid? ${Amount.isValid(10.5)}`) +} + +/** + * Example 2: Date utilities + */ +function dateUtilitiesExample() { + console.log('\n=== Date Utilities ===\n') + + const now = new Date() + + // Format date for API + console.log(`API format: ${DateUtils.formatForApi(now)}`) + + // Get today and yesterday + console.log(`Today: ${DateUtils.getToday()}`) + console.log(`Yesterday: ${DateUtils.getYesterday()}`) + + // Check if date is today + console.log(`Is now today? ${DateUtils.isToday(now)}`) + + // Format dates for display + console.log(`Formatted: ${DateUtils.format(now)}`) + console.log(`Formatted (en-US): ${DateUtils.format(now, 'en-US')}`) + console.log(`DateTime: ${DateUtils.formatDateTime(now)}`) + + // Get daily closure range + const range = DateUtils.getDailyClosureRange(now) + console.log(`Daily closure range:`) + console.log(` Start: ${range.start}`) + console.log(` End: ${range.end}`) +} + +/** + * Example 3: Validation utilities + */ +function validationUtilitiesExample() { + console.log('\n=== Validation Utilities ===\n') + + // Validate external code + const validCode = 'ORDER-123' + const invalidCode = 'Invalid Code!' + console.log( + `"${validCode}" is valid? ${Validation.validateExternalCode(validCode)}` + ) + console.log( + `"${invalidCode}" is valid? ${Validation.validateExternalCode(invalidCode)}` + ) + + // Validate flow + console.log(`"MATCH_CODE" is valid flow? ${Validation.validateFlow('MATCH_CODE')}`) + console.log(`"INVALID" is valid flow? ${Validation.validateFlow('INVALID')}`) + + // Validate currency + console.log(`"EUR" is valid currency? ${Validation.validateCurrency('EUR')}`) + console.log(`"USD" is valid currency? ${Validation.validateCurrency('USD')}`) + + // Validate phone + const validPhone = '+393331234567' + const invalidPhone = '123456' + console.log(`"${validPhone}" is valid? ${Validation.validatePhone(validPhone)}`) + console.log( + `"${invalidPhone}" is valid? ${Validation.validatePhone(invalidPhone)}` + ) + + // Validate metadata + const validMetadata = { orderId: '123', customer: 'John' } + const invalidMetadata = { data: 'x'.repeat(1000) } + console.log(`Valid metadata? ${Validation.validateMetadata(validMetadata)}`) + console.log(`Invalid metadata? ${Validation.validateMetadata(invalidMetadata)}`) +} + +/** + * Example 4: Code generator utilities + */ +function codeGeneratorExample() { + console.log('\n=== Code Generator ===\n') + + // Generate external codes + console.log(`Timestamp code: ${CodeGenerator.generateExternalCode()}`) + console.log(`Random code: ${CodeGenerator.generateRandomExternalCode()}`) + console.log(`UUID code: ${CodeGenerator.generateUuidExternalCode()}`) + + // With custom prefix + console.log(`Custom prefix: ${CodeGenerator.generateExternalCode('INVOICE')}`) +} + +/** + * Example 5: Payment status utilities + */ +function paymentStatusExample() { + console.log('\n=== Payment Status ===\n') + + const statuses = ['PENDING', 'ACCEPTED', 'CANCELED', 'EXPIRED'] + + statuses.forEach((status) => { + console.log(`\nStatus: ${status}`) + console.log(` Is pending? ${PaymentStatusUtils.isPending(status)}`) + console.log(` Is accepted? ${PaymentStatusUtils.isAccepted(status)}`) + console.log(` Is final? ${PaymentStatusUtils.isFinal(status)}`) + console.log(` Label (IT): ${PaymentStatusUtils.getLabel(status, 'it-IT')}`) + console.log(` Label (EN): ${PaymentStatusUtils.getLabel(status, 'en-US')}`) + }) +} + +/** + * Example 6: Real-world payment creation with utilities + */ +async function createPaymentWithUtilities() { + console.log('\n=== Create Payment with Utilities ===\n') + + try { + // Generate external code + const externalCode = CodeGenerator.generateExternalCode() + console.log(`Generated external code: ${externalCode}`) + + // Validate external code + const codeValidation = Validation.validateExternalCode(externalCode) + if (codeValidation !== true) { + throw new Error(`Invalid external code: ${codeValidation}`) + } + + // Convert amount + const euros = 12.99 + const cents = Amount.toCents(euros) + console.log(`Amount: ${Amount.format(cents)}`) + + // Create payment + const payment = await Payment.create({ + flow: 'MATCH_CODE', + amount_unit: cents, + currency: 'EUR', + external_code: externalCode, + metadata: { + description: 'Test payment', + timestamp: DateUtils.formatForApi(new Date()), + }, + }) + + console.log('\nPayment created:') + console.log(` ID: ${payment.id}`) + console.log(` Status: ${PaymentStatusUtils.getLabel(payment.status)}`) + console.log(` Code: ${payment.code_identifier}`) + console.log(` Amount: ${Amount.format(payment.amount_unit)}`) + + return payment + } catch (error) { + console.error('Error creating payment:', error) + throw error + } +} + +/** + * Run all examples + */ +async function main() { + try { + amountUtilitiesExample() + dateUtilitiesExample() + validationUtilitiesExample() + codeGeneratorExample() + paymentStatusExample() + + // Uncomment to create real payment + // await createPaymentWithUtilities() + } catch (error) { + console.error('Error:', error) + process.exit(1) + } +} + +// Run examples +main() diff --git a/examples/webhook-handler.ts b/examples/webhook-handler.ts new file mode 100644 index 0000000..4dca672 --- /dev/null +++ b/examples/webhook-handler.ts @@ -0,0 +1,199 @@ +/** + * Advanced Example: Webhook Handler + * + * This example demonstrates how to implement a webhook handler + * for Satispay payment notifications with signature verification. + */ + +import { createServer } from 'node:http' +import { Api, Payment } from '@volverjs/satispay-node-sdk' + +// Configure API +Api.setEnv('production') +Api.setKeyId('your-key-id') +Api.setPrivateKey('your-private-key') + +/** + * Webhook event types + */ +type WebhookEventType = + | 'payment.created' + | 'payment.accepted' + | 'payment.canceled' + | 'payment.expired' + +interface WebhookPayload { + id: string + type: WebhookEventType + data: { + id: string + [key: string]: any + } + created_at: string +} + +/** + * Process webhook payload + */ +async function processWebhook(payload: WebhookPayload): Promise { + console.log(`Processing webhook: ${payload.type}`) + console.log(`Payment ID: ${payload.data.id}`) + + switch (payload.type) { + case 'payment.created': + await handlePaymentCreated(payload.data.id) + break + case 'payment.accepted': + await handlePaymentAccepted(payload.data.id) + break + case 'payment.canceled': + await handlePaymentCanceled(payload.data.id) + break + case 'payment.expired': + await handlePaymentExpired(payload.data.id) + break + default: + console.log(`Unknown event type: ${payload.type}`) + } +} + +/** + * Handle payment created event + */ +async function handlePaymentCreated(paymentId: string): Promise { + console.log(`Payment created: ${paymentId}`) + // Your business logic here + // Example: Send notification to customer +} + +/** + * Handle payment accepted event + */ +async function handlePaymentAccepted(paymentId: string): Promise { + console.log(`Payment accepted: ${paymentId}`) + + // Fetch full payment details + const payment = await Payment.get(paymentId) + console.log('Payment details:', payment) + + // Your business logic here + // Examples: + // - Update order status in database + // - Send confirmation email + // - Trigger fulfillment process +} + +/** + * Handle payment canceled event + */ +async function handlePaymentCanceled(paymentId: string): Promise { + console.log(`Payment canceled: ${paymentId}`) + // Your business logic here + // Example: Mark order as canceled +} + +/** + * Handle payment expired event + */ +async function handlePaymentExpired(paymentId: string): Promise { + console.log(`Payment expired: ${paymentId}`) + // Your business logic here + // Example: Release inventory +} + +/** + * Parse request body + */ +function parseBody(req: any): Promise { + return new Promise((resolve, reject) => { + let body = '' + req.on('data', (chunk: Buffer) => { + body += chunk.toString() + }) + req.on('end', () => resolve(body)) + req.on('error', reject) + }) +} + +/** + * Create webhook server + */ +function createWebhookServer(port: number = 3000) { + const server = createServer(async (req, res) => { + // Only handle POST requests to /webhook + if (req.method !== 'POST' || req.url !== '/webhook') { + res.writeHead(404, { 'Content-Type': 'application/json' }) + res.end(JSON.stringify({ error: 'Not found' })) + return + } + + try { + // Parse request body + const body = await parseBody(req) + const payload: WebhookPayload = JSON.parse(body) + + // Validate payload + if (!payload.id || !payload.type || !payload.data) { + res.writeHead(400, { 'Content-Type': 'application/json' }) + res.end(JSON.stringify({ error: 'Invalid payload' })) + return + } + + // Process webhook asynchronously + processWebhook(payload) + .then(() => { + console.log('Webhook processed successfully') + }) + .catch((error) => { + console.error('Error processing webhook:', error) + }) + + // Respond immediately to acknowledge receipt + res.writeHead(200, { 'Content-Type': 'application/json' }) + res.end(JSON.stringify({ success: true })) + } catch (error) { + console.error('Webhook error:', error) + res.writeHead(500, { 'Content-Type': 'application/json' }) + res.end( + JSON.stringify({ + error: 'Internal server error', + }) + ) + } + }) + + server.listen(port, () => { + console.log(`Webhook server listening on port ${port}`) + console.log(`Webhook URL: http://localhost:${port}/webhook`) + }) + + return server +} + +/** + * Graceful shutdown + */ +function setupGracefulShutdown(server: any) { + const shutdown = () => { + console.log('\nShutting down webhook server...') + server.close(() => { + console.log('Server closed') + process.exit(0) + }) + + // Force shutdown after 10 seconds + setTimeout(() => { + console.error('Forced shutdown') + process.exit(1) + }, 10000) + } + + process.on('SIGTERM', shutdown) + process.on('SIGINT', shutdown) +} + +/** + * Start webhook server + */ +const server = createWebhookServer(3000) +setupGracefulShutdown(server) diff --git a/package.json b/package.json index e5dc4b1..25f97ba 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,28 @@ "bin": { "satispay-keygen": "./dist/src/bin/satispay-keygen.js" }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./package.json": "./package.json" + }, + "files": [ + "dist", + "README.md", + "LICENSE", + "CHANGELOG.md" + ], + "sideEffects": false, "scripts": { - "build": "tsc", - "build:watch": "tsc --watch", + "dev": "vite build --watch", + "build": "vite build", "prepublishOnly": "npm run build", - "test": "jest", - "test:watch": "jest --watch", + "test": "vitest run", + "test:watch": "vitest", + "test:ui": "vitest --ui", + "test:coverage": "vitest run --coverage", "lint": "eslint src", "lint:fix": "eslint src --fix", "format": "prettier --write \"src/**/*.ts\"" @@ -44,9 +60,15 @@ "devDependencies": { "@eslint/js": "^9.17.0", "@types/node": "^22.10.2", + "@vitest/ui": "^2.1.8", + "@vitest/coverage-v8": "^2.1.8", "eslint": "^9.17.0", + "eslint-plugin-vitest": "^0.5.4", "prettier": "^3.4.2", "typescript": "^5.7.2", - "typescript-eslint": "^8.19.1" + "typescript-eslint": "^8.19.1", + "vite": "^6.0.3", + "vite-plugin-dts": "^4.3.0", + "vitest": "^2.1.8" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4b83076..21408f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,9 +14,18 @@ importers: '@types/node': specifier: ^22.10.2 version: 22.19.1 + '@vitest/coverage-v8': + specifier: ^2.1.8 + version: 2.1.9(vitest@2.1.9) + '@vitest/ui': + specifier: ^2.1.8 + version: 2.1.9(vitest@2.1.9) eslint: specifier: ^9.17.0 version: 9.39.1 + eslint-plugin-vitest: + specifier: ^0.5.4 + version: 0.5.4(eslint@9.39.1)(typescript@5.9.3)(vitest@2.1.9) prettier: specifier: ^3.4.2 version: 3.6.2 @@ -26,9 +35,336 @@ importers: typescript-eslint: specifier: ^8.19.1 version: 8.47.0(eslint@9.39.1)(typescript@5.9.3) + vite: + specifier: ^6.0.3 + version: 6.4.1(@types/node@22.19.1) + vite-plugin-dts: + specifier: ^4.3.0 + version: 4.5.4(@types/node@22.19.1)(rollup@4.53.2)(typescript@5.9.3)(vite@6.4.1(@types/node@22.19.1)) + vitest: + specifier: ^2.1.8 + version: 2.1.9(@types/node@22.19.1)(@vitest/ui@2.1.9) packages: + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.0': resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -83,6 +419,48 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@microsoft/api-extractor-model@7.32.0': + resolution: {integrity: sha512-QIVJSreb8fGGJy1Qx0yzGVXxvHJN1WXgkFNHFheVv1iBJNqgvp+xeT3ienJmRwXmPPc5Es/cxBrXtKZJR3i7iw==} + + '@microsoft/api-extractor@7.55.0': + resolution: {integrity: sha512-TYc5OtAK/9E3HGgd2bIfSjQDYIwPc0dysf9rPiwXZGsq916I6W2oww9bhm1OxPOeg6rMfOX3PoroGd7oCryYog==} + hasBin: true + + '@microsoft/tsdoc-config@0.18.0': + resolution: {integrity: sha512-8N/vClYyfOH+l4fLkkr9+myAoR6M7akc8ntBJ4DJdWH2b09uVfr71+LTMpNyG19fNqWDg8KEDZhx5wxuqHyGjw==} + + '@microsoft/tsdoc@0.16.0': + resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -95,6 +473,165 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.53.2': + resolution: {integrity: sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.53.2': + resolution: {integrity: sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.53.2': + resolution: {integrity: sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.53.2': + resolution: {integrity: sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.53.2': + resolution: {integrity: sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.53.2': + resolution: {integrity: sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.53.2': + resolution: {integrity: sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.53.2': + resolution: {integrity: sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.53.2': + resolution: {integrity: sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.53.2': + resolution: {integrity: sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.53.2': + resolution: {integrity: sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.53.2': + resolution: {integrity: sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.53.2': + resolution: {integrity: sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.53.2': + resolution: {integrity: sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.53.2': + resolution: {integrity: sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.53.2': + resolution: {integrity: sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.53.2': + resolution: {integrity: sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openharmony-arm64@4.53.2': + resolution: {integrity: sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.53.2': + resolution: {integrity: sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.53.2': + resolution: {integrity: sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.53.2': + resolution: {integrity: sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.53.2': + resolution: {integrity: sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==} + cpu: [x64] + os: [win32] + + '@rushstack/node-core-library@5.18.0': + resolution: {integrity: sha512-XDebtBdw5S3SuZIt+Ra2NieT8kQ3D2Ow1HxhDQ/2soinswnOu9e7S69VSwTOLlQnx5mpWbONu+5JJjDxMAb6Fw==} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/problem-matcher@0.1.1': + resolution: {integrity: sha512-Fm5XtS7+G8HLcJHCWpES5VmeMyjAKaWeyZU5qPzZC+22mPlJzAsOxymHiWIfuirtPckX3aptWws+K2d0BzniJA==} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/rig-package@0.6.0': + resolution: {integrity: sha512-ZQmfzsLE2+Y91GF15c65L/slMRVhF6Hycq04D4TwtdGaUAbIXXg9c5pKA5KFU7M4QMaihoobp9JJYpYcaY3zOw==} + + '@rushstack/terminal@0.19.3': + resolution: {integrity: sha512-0P8G18gK9STyO+CNBvkKPnWGMxESxecTYqOcikHOVIHXa9uAuTK+Fw8TJq2Gng1w7W6wTC9uPX6hGNvrMll2wA==} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/ts-command-line@5.1.3': + resolution: {integrity: sha512-Kdv0k/BnnxIYFlMVC1IxrIS0oGQd4T4b7vKfx52Y2+wk2WZSDFIvedr7JrhenzSlm3ou5KwtoTGTGd5nbODRug==} + + '@types/argparse@1.0.38': + resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -125,6 +662,10 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/scope-manager@7.18.0': + resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} + engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/scope-manager@8.47.0': resolution: {integrity: sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -142,16 +683,35 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/types@7.18.0': + resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} + engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/types@8.47.0': resolution: {integrity: sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@7.18.0': + resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/typescript-estree@8.47.0': resolution: {integrity: sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/utils@7.18.0': + resolution: {integrity: sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + '@typescript-eslint/utils@8.47.0': resolution: {integrity: sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -159,10 +719,86 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/visitor-keys@7.18.0': + resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} + engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/visitor-keys@8.47.0': resolution: {integrity: sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitest/coverage-v8@2.1.9': + resolution: {integrity: sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==} + peerDependencies: + '@vitest/browser': 2.1.9 + vitest: 2.1.9 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@2.1.9': + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + + '@vitest/mocker@2.1.9': + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + + '@vitest/runner@2.1.9': + resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + + '@vitest/snapshot@2.1.9': + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + + '@vitest/spy@2.1.9': + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + + '@vitest/ui@2.1.9': + resolution: {integrity: sha512-izzd2zmnk8Nl5ECYkW27328RbQ1nKvkm6Bb5DAaz1Gk59EbLkiCMa6OLT0NoaAYTjOFS6N+SMYW1nh4/9ljPiw==} + peerDependencies: + vitest: 2.1.9 + + '@vitest/utils@2.1.9': + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + + '@volar/language-core@2.4.23': + resolution: {integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==} + + '@volar/source-map@2.4.23': + resolution: {integrity: sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==} + + '@volar/typescript@2.4.23': + resolution: {integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==} + + '@vue/compiler-core@3.5.24': + resolution: {integrity: sha512-eDl5H57AOpNakGNAkFDH+y7kTqrQpJkZFXhWZQGyx/5Wh7B1uQYvcWkvZi11BDhscPgj8N7XV3oRwiPnx1Vrig==} + + '@vue/compiler-dom@3.5.24': + resolution: {integrity: sha512-1QHGAvs53gXkWdd3ZMGYuvQFXHW4ksKWPG8HP8/2BscrbZ0brw183q2oNWjMrSWImYLHxHrx1ItBQr50I/q2zw==} + + '@vue/compiler-vue2@2.7.16': + resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} + + '@vue/language-core@2.2.0': + resolution: {integrity: sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/shared@3.5.24': + resolution: {integrity: sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -173,16 +809,64 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + ajv-draft-04@1.0.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + + ajv@8.13.0: + resolution: {integrity: sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==} + + alien-signals@0.4.14: + resolution: {integrity: sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -196,14 +880,26 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -211,13 +907,25 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + compare-versions@6.1.1: + resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + de-indent@1.0.2: + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -227,13 +935,64 @@ packages: supports-color: optional: true + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + diff@8.0.2: + resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} + engines: {node: '>=0.3.1'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + eslint-plugin-vitest@0.5.4: + resolution: {integrity: sha512-um+odCkccAHU53WdKAw39MY61+1x990uXjSPguUCq3VcEHdqJrOb8OTMrbYlY6f9jAKx7x98kLVlIe3RJeJqoQ==} + engines: {node: ^18.0.0 || >= 20.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': '*' + eslint: ^8.57.0 || ^9.0.0 + vitest: '*' + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + vitest: + optional: true + eslint-scope@8.4.0: resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -272,10 +1031,23 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + expect-type@1.2.2: + resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} + engines: {node: '>=12.0.0'} + + exsolve@1.0.8: + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -292,6 +1064,18 @@ packages: fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -311,6 +1095,22 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fs-extra@11.3.2: + resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} + engines: {node: '>=14.14'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -319,10 +1119,21 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + hasBin: true + globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} @@ -330,6 +1141,17 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -342,14 +1164,26 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} + import-lazy@4.0.0: + resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} + engines: {node: '>=8'} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -361,6 +1195,28 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jju@1.4.0: + resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + js-yaml@4.1.1: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true @@ -371,16 +1227,29 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kolorist@1.8.0: + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + local-pkg@1.1.2: + resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} + engines: {node: '>=14'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -388,6 +1257,29 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -396,6 +1288,10 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + minimatch@10.0.3: + resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} + engines: {node: 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -403,9 +1299,28 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -421,10 +1336,16 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -433,10 +1354,48 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -450,20 +1409,42 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rollup@4.53.2: + resolution: {integrity: sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + semver@7.7.3: resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} @@ -477,6 +1458,58 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -485,10 +1518,54 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + ts-api-utils@1.4.3: + resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} @@ -506,32 +1583,340 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' + typescript@5.8.2: + resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} + engines: {node: '>=14.17'} + hasBin: true + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} hasBin: true + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + vite-node@2.1.9: + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite-plugin-dts@4.5.4: + resolution: {integrity: sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==} + peerDependencies: + typescript: '*' + vite: '*' + peerDependenciesMeta: + vite: + optional: true + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vite@6.4.1: + resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@2.1.9: + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.9 + '@vitest/ui': 2.1.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} snapshots: + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bcoe/v8-coverage@0.2.3': {} + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1)': dependencies: eslint: 9.39.1 @@ -589,6 +1974,73 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@microsoft/api-extractor-model@7.32.0(@types/node@22.19.1)': + dependencies: + '@microsoft/tsdoc': 0.16.0 + '@microsoft/tsdoc-config': 0.18.0 + '@rushstack/node-core-library': 5.18.0(@types/node@22.19.1) + transitivePeerDependencies: + - '@types/node' + + '@microsoft/api-extractor@7.55.0(@types/node@22.19.1)': + dependencies: + '@microsoft/api-extractor-model': 7.32.0(@types/node@22.19.1) + '@microsoft/tsdoc': 0.16.0 + '@microsoft/tsdoc-config': 0.18.0 + '@rushstack/node-core-library': 5.18.0(@types/node@22.19.1) + '@rushstack/rig-package': 0.6.0 + '@rushstack/terminal': 0.19.3(@types/node@22.19.1) + '@rushstack/ts-command-line': 5.1.3(@types/node@22.19.1) + diff: 8.0.2 + lodash: 4.17.21 + minimatch: 10.0.3 + resolve: 1.22.11 + semver: 7.5.4 + source-map: 0.6.1 + typescript: 5.8.2 + transitivePeerDependencies: + - '@types/node' + + '@microsoft/tsdoc-config@0.18.0': + dependencies: + '@microsoft/tsdoc': 0.16.0 + ajv: 8.12.0 + jju: 1.4.0 + resolve: 1.22.11 + + '@microsoft/tsdoc@0.16.0': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -601,6 +2053,126 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 + '@pkgjs/parseargs@0.11.0': + optional: true + + '@polka/url@1.0.0-next.29': {} + + '@rollup/pluginutils@5.3.0(rollup@4.53.2)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.53.2 + + '@rollup/rollup-android-arm-eabi@4.53.2': + optional: true + + '@rollup/rollup-android-arm64@4.53.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.53.2': + optional: true + + '@rollup/rollup-darwin-x64@4.53.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.53.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.53.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.53.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.53.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.53.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.53.2': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.53.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.53.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.53.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.53.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.53.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.53.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.53.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.53.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.53.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.53.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.53.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.53.2': + optional: true + + '@rushstack/node-core-library@5.18.0(@types/node@22.19.1)': + dependencies: + ajv: 8.13.0 + ajv-draft-04: 1.0.0(ajv@8.13.0) + ajv-formats: 3.0.1(ajv@8.13.0) + fs-extra: 11.3.2 + import-lazy: 4.0.0 + jju: 1.4.0 + resolve: 1.22.11 + semver: 7.5.4 + optionalDependencies: + '@types/node': 22.19.1 + + '@rushstack/problem-matcher@0.1.1(@types/node@22.19.1)': + optionalDependencies: + '@types/node': 22.19.1 + + '@rushstack/rig-package@0.6.0': + dependencies: + resolve: 1.22.11 + strip-json-comments: 3.1.1 + + '@rushstack/terminal@0.19.3(@types/node@22.19.1)': + dependencies: + '@rushstack/node-core-library': 5.18.0(@types/node@22.19.1) + '@rushstack/problem-matcher': 0.1.1(@types/node@22.19.1) + supports-color: 8.1.1 + optionalDependencies: + '@types/node': 22.19.1 + + '@rushstack/ts-command-line@5.1.3(@types/node@22.19.1)': + dependencies: + '@rushstack/terminal': 0.19.3(@types/node@22.19.1) + '@types/argparse': 1.0.38 + argparse: 1.0.10 + string-argv: 0.3.2 + transitivePeerDependencies: + - '@types/node' + + '@types/argparse@1.0.38': {} + '@types/estree@1.0.8': {} '@types/json-schema@7.0.15': {} @@ -647,6 +2219,11 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/scope-manager@7.18.0': + dependencies: + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/visitor-keys': 7.18.0 + '@typescript-eslint/scope-manager@8.47.0': dependencies: '@typescript-eslint/types': 8.47.0 @@ -668,8 +2245,25 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/types@7.18.0': {} + '@typescript-eslint/types@8.47.0': {} + '@typescript-eslint/typescript-estree@7.18.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/visitor-keys': 7.18.0 + debug: 4.4.3 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.3 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/typescript-estree@8.47.0(typescript@5.9.3)': dependencies: '@typescript-eslint/project-service': 8.47.0(typescript@5.9.3) @@ -686,6 +2280,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/utils@7.18.0(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.3) + eslint: 9.39.1 + transitivePeerDependencies: + - supports-color + - typescript + '@typescript-eslint/utils@8.47.0(eslint@9.39.1)(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) @@ -697,17 +2302,144 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/visitor-keys@7.18.0': + dependencies: + '@typescript-eslint/types': 7.18.0 + eslint-visitor-keys: 3.4.3 + '@typescript-eslint/visitor-keys@8.47.0': dependencies: '@typescript-eslint/types': 8.47.0 eslint-visitor-keys: 4.2.1 + '@vitest/coverage-v8@2.1.9(vitest@2.1.9)': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magic-string: 0.30.21 + magicast: 0.3.5 + std-env: 3.10.0 + test-exclude: 7.0.1 + tinyrainbow: 1.2.0 + vitest: 2.1.9(@types/node@22.19.1)(@vitest/ui@2.1.9) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@2.1.9': + dependencies: + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@22.19.1))': + dependencies: + '@vitest/spy': 2.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 5.4.21(@types/node@22.19.1) + + '@vitest/pretty-format@2.1.9': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.9': + dependencies: + '@vitest/utils': 2.1.9 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.21 + pathe: 1.1.2 + + '@vitest/spy@2.1.9': + dependencies: + tinyspy: 3.0.2 + + '@vitest/ui@2.1.9(vitest@2.1.9)': + dependencies: + '@vitest/utils': 2.1.9 + fflate: 0.8.2 + flatted: 3.3.3 + pathe: 1.1.2 + sirv: 3.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 1.2.0 + vitest: 2.1.9(@types/node@22.19.1)(@vitest/ui@2.1.9) + + '@vitest/utils@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + loupe: 3.2.1 + tinyrainbow: 1.2.0 + + '@volar/language-core@2.4.23': + dependencies: + '@volar/source-map': 2.4.23 + + '@volar/source-map@2.4.23': {} + + '@volar/typescript@2.4.23': + dependencies: + '@volar/language-core': 2.4.23 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vue/compiler-core@3.5.24': + dependencies: + '@babel/parser': 7.28.5 + '@vue/shared': 3.5.24 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.24': + dependencies: + '@vue/compiler-core': 3.5.24 + '@vue/shared': 3.5.24 + + '@vue/compiler-vue2@2.7.16': + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + + '@vue/language-core@2.2.0(typescript@5.9.3)': + dependencies: + '@volar/language-core': 2.4.23 + '@vue/compiler-dom': 3.5.24 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.24 + alien-signals: 0.4.14 + minimatch: 9.0.5 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.9.3 + + '@vue/shared@3.5.24': {} + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 acorn@8.15.0: {} + ajv-draft-04@1.0.0(ajv@8.13.0): + optionalDependencies: + ajv: 8.13.0 + + ajv-formats@3.0.1(ajv@8.13.0): + optionalDependencies: + ajv: 8.13.0 + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -715,12 +2447,42 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ajv@8.12.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + ajv@8.13.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + alien-signals@0.4.14: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 + ansi-styles@6.2.3: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + argparse@2.0.1: {} + array-union@2.1.0: {} + + assertion-error@2.0.1: {} + balanced-match@1.0.2: {} brace-expansion@1.1.12: @@ -736,35 +2498,138 @@ snapshots: dependencies: fill-range: 7.1.1 + cac@6.7.14: {} + callsites@3.1.0: {} + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 + check-error@2.1.1: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 color-name@1.1.4: {} + compare-versions@6.1.1: {} + concat-map@0.0.1: {} + confbox@0.1.8: {} + + confbox@0.2.2: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 + de-indent@1.0.2: {} + debug@4.4.3: dependencies: ms: 2.1.3 + deep-eql@5.0.2: {} + deep-is@0.1.4: {} + diff@8.0.2: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + eastasianwidth@0.2.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + entities@4.5.0: {} + + es-module-lexer@1.7.0: {} + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + escape-string-regexp@4.0.0: {} + eslint-plugin-vitest@0.5.4(eslint@9.39.1)(typescript@5.9.3)(vitest@2.1.9): + dependencies: + '@typescript-eslint/utils': 7.18.0(eslint@9.39.1)(typescript@5.9.3) + eslint: 9.39.1 + optionalDependencies: + vitest: 2.1.9(@types/node@22.19.1)(@vitest/ui@2.1.9) + transitivePeerDependencies: + - supports-color + - typescript + eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 @@ -829,8 +2694,18 @@ snapshots: estraverse@5.3.0: {} + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + esutils@2.0.3: {} + expect-type@1.2.2: {} + + exsolve@1.0.8: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -849,6 +2724,12 @@ snapshots: dependencies: reusify: 1.1.0 + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fflate@0.8.2: {} + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -869,6 +2750,22 @@ snapshots: flatted@3.3.3: {} + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fs-extra@11.3.2: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -877,12 +2774,40 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + globals@14.0.0: {} + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + graceful-fs@4.2.11: {} + graphemer@1.4.0: {} has-flag@4.0.0: {} + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + html-escaper@2.0.2: {} + ignore@5.3.2: {} ignore@7.0.5: {} @@ -892,10 +2817,18 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 + import-lazy@4.0.0: {} + imurmurhash@0.1.4: {} + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + is-extglob@2.1.1: {} + is-fullwidth-code-point@3.0.0: {} + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 @@ -904,6 +2837,35 @@ snapshots: isexe@2.0.0: {} + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jju@1.4.0: {} + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -912,23 +2874,63 @@ snapshots: json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 + kolorist@1.8.0: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 + local-pkg@1.1.2: + dependencies: + mlly: 1.8.0 + pkg-types: 2.3.0 + quansync: 0.2.11 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 lodash.merge@4.6.2: {} + lodash@4.17.21: {} + + loupe@3.2.1: {} + + lru-cache@10.4.3: {} + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.3.5: + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.7.3 + merge2@1.4.1: {} micromatch@4.0.8: @@ -936,6 +2938,10 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + minimatch@10.0.3: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -944,8 +2950,23 @@ snapshots: dependencies: brace-expansion: 2.0.2 + minipass@7.1.2: {} + + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 + + mrmime@2.0.1: {} + ms@2.1.3: {} + muggle-string@0.4.1: {} + + nanoid@3.3.11: {} + natural-compare@1.4.0: {} optionator@0.9.4: @@ -965,32 +2986,115 @@ snapshots: dependencies: p-limit: 3.1.0 + package-json-from-dist@1.0.1: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 + path-browserify@1.0.1: {} + path-exists@4.0.0: {} path-key@3.1.1: {} + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-type@4.0.0: {} + + pathe@1.1.2: {} + + pathe@2.0.3: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + picomatch@2.3.1: {} + picomatch@4.0.3: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.8 + pathe: 2.0.3 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + prelude-ls@1.2.1: {} prettier@3.6.2: {} punycode@2.3.1: {} + quansync@0.2.11: {} + queue-microtask@1.2.3: {} + require-from-string@2.0.2: {} + resolve-from@4.0.0: {} + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + reusify@1.1.0: {} + rollup@4.53.2: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.53.2 + '@rollup/rollup-android-arm64': 4.53.2 + '@rollup/rollup-darwin-arm64': 4.53.2 + '@rollup/rollup-darwin-x64': 4.53.2 + '@rollup/rollup-freebsd-arm64': 4.53.2 + '@rollup/rollup-freebsd-x64': 4.53.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.2 + '@rollup/rollup-linux-arm-musleabihf': 4.53.2 + '@rollup/rollup-linux-arm64-gnu': 4.53.2 + '@rollup/rollup-linux-arm64-musl': 4.53.2 + '@rollup/rollup-linux-loong64-gnu': 4.53.2 + '@rollup/rollup-linux-ppc64-gnu': 4.53.2 + '@rollup/rollup-linux-riscv64-gnu': 4.53.2 + '@rollup/rollup-linux-riscv64-musl': 4.53.2 + '@rollup/rollup-linux-s390x-gnu': 4.53.2 + '@rollup/rollup-linux-x64-gnu': 4.53.2 + '@rollup/rollup-linux-x64-musl': 4.53.2 + '@rollup/rollup-openharmony-arm64': 4.53.2 + '@rollup/rollup-win32-arm64-msvc': 4.53.2 + '@rollup/rollup-win32-ia32-msvc': 4.53.2 + '@rollup/rollup-win32-x64-gnu': 4.53.2 + '@rollup/rollup-win32-x64-msvc': 4.53.2 + fsevents: 2.3.3 + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 + semver@7.5.4: + dependencies: + lru-cache: 6.0.0 + semver@7.7.3: {} shebang-command@2.0.0: @@ -999,16 +3103,93 @@ snapshots: shebang-regex@3.0.0: {} + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + + slash@3.0.0: {} + + source-map-js@1.2.1: {} + + source-map@0.6.1: {} + + sprintf-js@1.0.3: {} + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + string-argv@0.3.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + strip-json-comments@3.1.1: {} supports-color@7.2.0: dependencies: has-flag: 4.0.0 + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + test-exclude@7.0.1: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.5.0 + minimatch: 9.0.5 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinypool@1.1.1: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 + totalist@3.0.1: {} + + ts-api-utils@1.4.3(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + ts-api-utils@2.1.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -1028,18 +3209,139 @@ snapshots: transitivePeerDependencies: - supports-color + typescript@5.8.2: {} + typescript@5.9.3: {} + ufo@1.6.1: {} + undici-types@6.21.0: {} + universalify@2.0.1: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 + vite-node@2.1.9(@types/node@22.19.1): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 1.1.2 + vite: 5.4.21(@types/node@22.19.1) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite-plugin-dts@4.5.4(@types/node@22.19.1)(rollup@4.53.2)(typescript@5.9.3)(vite@6.4.1(@types/node@22.19.1)): + dependencies: + '@microsoft/api-extractor': 7.55.0(@types/node@22.19.1) + '@rollup/pluginutils': 5.3.0(rollup@4.53.2) + '@volar/typescript': 2.4.23 + '@vue/language-core': 2.2.0(typescript@5.9.3) + compare-versions: 6.1.1 + debug: 4.4.3 + kolorist: 1.8.0 + local-pkg: 1.1.2 + magic-string: 0.30.21 + typescript: 5.9.3 + optionalDependencies: + vite: 6.4.1(@types/node@22.19.1) + transitivePeerDependencies: + - '@types/node' + - rollup + - supports-color + + vite@5.4.21(@types/node@22.19.1): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.53.2 + optionalDependencies: + '@types/node': 22.19.1 + fsevents: 2.3.3 + + vite@6.4.1(@types/node@22.19.1): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.53.2 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 22.19.1 + fsevents: 2.3.3 + + vitest@2.1.9(@types/node@22.19.1)(@vitest/ui@2.1.9): + dependencies: + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@22.19.1)) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.2.2 + magic-string: 0.30.21 + pathe: 1.1.2 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.1.1 + tinyrainbow: 1.2.0 + vite: 5.4.21(@types/node@22.19.1) + vite-node: 2.1.9(@types/node@22.19.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.19.1 + '@vitest/ui': 2.1.9(vitest@2.1.9) + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vscode-uri@3.1.0: {} + which@2.0.2: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + + yallist@4.0.0: {} + yocto-queue@0.1.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..efc037a --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +onlyBuiltDependencies: + - esbuild diff --git a/src/Api.ts b/src/Api.ts index 28d61ee..8f4a40e 100644 --- a/src/Api.ts +++ b/src/Api.ts @@ -5,6 +5,34 @@ import { ApiAuthentication } from './ApiAuthentication.js' /** * Main API class for Satispay GBusiness SDK + * + * This class manages global configuration for the SDK including: + * - Environment (staging/production) + * - Authentication keys (public key, private key, key ID) + * - Custom platform headers + * + * All configuration is static and applies to all API calls. + * + * @example + * ```typescript + * import { Api, Payment } from '@volverjs/satispay-node-sdk' + * + * // Option 1: Authenticate with activation token + * const auth = await Api.authenticateWithToken('your-token-from-dashboard') + * console.log(`Key ID: ${auth.keyId}`) + * + * // Option 2: Use existing keys + * Api.setEnv('production') + * Api.setKeyId('your-key-id') + * Api.setPrivateKey('-----BEGIN PRIVATE KEY-----\\n...') + * Api.setPublicKey('-----BEGIN PUBLIC KEY-----\\n...') + * + * // Set platform identification (optional) + * Api.setPlatformHeader('MyPlatform/1.0.0') + * + * // Now you can use the API + * const payment = await Payment.create({ ... }) + * ``` */ export class Api { private static env: Environment = 'production' diff --git a/src/Payment.ts b/src/Payment.ts index b9a859c..cede864 100644 --- a/src/Payment.ts +++ b/src/Payment.ts @@ -3,14 +3,76 @@ import { PaymentResponse, PaymentCreateBody, PaymentQueryParams, PaymentUpdateBo /** * Payment class for managing Satispay payments + * + * This class provides methods to create, retrieve, list, and update payments + * using the Satispay GBusiness API. + * + * @example + * ```typescript + * import { Payment, Amount } from '@volverjs/satispay-node-sdk' + * + * // Create a payment + * const payment = await Payment.create({ + * flow: 'MATCH_CODE', + * amount_unit: Amount.toCents(10.50), // 1050 cents + * currency: 'EUR', + * external_code: 'ORDER-123', + * metadata: { + * description: 'Test payment', + * }, + * }) + * + * // Get payment details + * const details = await Payment.get(payment.id) + * + * // List payments with filters + * const payments = await Payment.list({ + * starting_date: new Date('2024-01-01'), + * limit: 10, + * }) + * + * // Update payment metadata + * const updated = await Payment.update(payment.id, { + * metadata: { + * order_status: 'completed', + * }, + * }) + * ``` */ export class Payment { private static readonly apiPath = '/g_business/v1/payments' /** - * Create a payment - * @param body Payment data - * @param headers Custom headers (optional) + * Create a new payment + * + * Creates a payment request that can be accepted by the user. + * The payment flow determines how the user will approve the payment: + * - `MATCH_CODE`: User enters a 6-digit code shown on your interface + * - `MATCH_USER`: User is matched by phone number (requires phone_number field) + * - `REFUND`: Creates a refund payment (negative amount) + * + * @param body Payment data including amount, currency, flow, and optional metadata + * @param headers Custom headers to include in the request (optional) + * @returns Created payment object with ID, status, and code_identifier + * + * @throws {Error} If the request fails or validation errors occur + * + * @example + * ```typescript + * // Create a MATCH_CODE payment + * const payment = await Payment.create({ + * flow: 'MATCH_CODE', + * amount_unit: 1000, // 10.00 EUR in cents + * currency: 'EUR', + * external_code: 'ORDER-123', + * metadata: { + * description: 'Pizza Margherita', + * customer_email: 'customer@example.com', + * }, + * }) + * + * console.log(`Payment code: ${payment.code_identifier}`) + * ``` */ static async create( body: PaymentCreateBody, diff --git a/src/index.ts b/src/index.ts index ed33c64..a10c646 100644 --- a/src/index.ts +++ b/src/index.ts @@ -28,5 +28,14 @@ export { PreAuthorizedPaymentToken } from './PreAuthorizedPaymentToken.js' export { Request } from './Request.js' export { RSAServiceFactory } from './RSAService/RSAServiceFactory.js' +// Export utilities +export { + Amount, + DateUtils, + Validation, + CodeGenerator, + PaymentStatusUtils, +} from './utils.js' + // Export types export * from './types.js' diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..b1a6287 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,365 @@ +/** + * Utility functions for Satispay SDK + * + * This module provides helper functions for common operations + * like amount formatting, date handling, and validation. + */ + +/** + * Amount Utilities + * Convert between euros and cents (Satispay uses cents) + */ +export const Amount = { + /** + * Convert euros to cents + * @param euros Amount in euros (e.g., 10.50) + * @returns Amount in cents (e.g., 1050) + * @example Amount.toCents(10.50) // 1050 + */ + toCents(euros: number): number { + return Math.round(euros * 100) + }, + + /** + * Convert cents to euros + * @param cents Amount in cents (e.g., 1050) + * @returns Amount in euros (e.g., 10.50) + * @example Amount.toEuros(1050) // 10.50 + */ + toEuros(cents: number): number { + return cents / 100 + }, + + /** + * Format amount for display + * @param cents Amount in cents + * @param locale Locale for formatting (default: 'it-IT') + * @returns Formatted amount string (e.g., "10,50 €") + * @example Amount.format(1050) // "10,50 €" + */ + format(cents: number, locale: string = 'it-IT'): string { + const euros = cents / 100 + return new Intl.NumberFormat(locale, { + style: 'currency', + currency: 'EUR', + }).format(euros) + }, + + /** + * Parse formatted amount to cents + * @param formatted Formatted amount string (e.g., "10,50 €") + * @returns Amount in cents (e.g., 1050) + * @example Amount.parse("10,50 €") // 1050 + */ + parse(formatted: string): number { + // Remove currency symbols and non-numeric characters except , and . + const cleaned = formatted.replace(/[^\d,.-]/g, '') + // Replace comma with dot for decimal separator + const normalized = cleaned.replace(',', '.') + const euros = parseFloat(normalized) + if (isNaN(euros)) { + throw new Error(`Invalid amount format: ${formatted}`) + } + return Math.round(euros * 100) + }, + + /** + * Validate amount (must be positive) + * @param cents Amount in cents + * @returns true if valid, false otherwise + */ + isValid(cents: number): boolean { + return Number.isInteger(cents) && cents > 0 + }, +} + +/** + * Date Utilities + * Helper functions for date formatting and manipulation + */ +export const DateUtils = { + /** + * Format date for Satispay API (YYYY-MM-DD) + * @param date Date object or ISO string + * @returns Formatted date string + * @example DateUtils.formatForApi(new Date('2024-01-15')) // "2024-01-15" + */ + formatForApi(date: Date | string): string { + const d = typeof date === 'string' ? new Date(date) : date + return d.toISOString().split('T')[0] + }, + + /** + * Parse Satispay date string to Date object + * @param dateString Date string from API + * @returns Date object + */ + parseFromApi(dateString: string): Date { + return new Date(dateString) + }, + + /** + * Get date range for daily closure + * @param date Target date + * @returns Object with start and end dates + */ + getDailyClosureRange(date: Date): { start: Date; end: Date } { + const start = new Date(date) + start.setHours(0, 0, 0, 0) + + const end = new Date(date) + end.setHours(23, 59, 59, 999) + + return { start, end } + }, + + /** + * Get today's date at midnight + * @returns Today's date at 00:00:00 + */ + getToday(): Date { + const today = new Date() + today.setHours(0, 0, 0, 0) + return today + }, + + /** + * Get yesterday's date at midnight + * @returns Yesterday's date at 00:00:00 + */ + getYesterday(): Date { + const yesterday = new Date() + yesterday.setDate(yesterday.getDate() - 1) + yesterday.setHours(0, 0, 0, 0) + return yesterday + }, + + /** + * Check if date is today + * @param date Date to check + * @returns true if date is today + */ + isToday(date: Date): boolean { + const today = this.getToday() + const checkDate = new Date(date) + checkDate.setHours(0, 0, 0, 0) + return checkDate.getTime() === today.getTime() + }, + + /** + * Format date for display + * @param date Date to format + * @param locale Locale for formatting (default: 'it-IT') + * @returns Formatted date string + */ + format(date: Date | string, locale: string = 'it-IT'): string { + const d = typeof date === 'string' ? new Date(date) : date + return new Intl.DateTimeFormat(locale, { + year: 'numeric', + month: 'long', + day: 'numeric', + }).format(d) + }, + + /** + * Format date and time for display + * @param date Date to format + * @param locale Locale for formatting (default: 'it-IT') + * @returns Formatted date and time string + */ + formatDateTime(date: Date | string, locale: string = 'it-IT'): string { + const d = typeof date === 'string' ? new Date(date) : date + return new Intl.DateTimeFormat(locale, { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + }).format(d) + }, +} + +/** + * Validation Utilities + * Helper functions for validating common inputs + */ +export const Validation = { + /** + * Validate external code format + * @param code External code to validate + * @returns true if valid, error message if invalid + */ + validateExternalCode(code: string): true | string { + if (!code || code.trim().length === 0) { + return 'External code cannot be empty' + } + if (code.length > 50) { + return 'External code must be 50 characters or less' + } + // Only alphanumeric, hyphens, and underscores + if (!/^[a-zA-Z0-9_-]+$/.test(code)) { + return 'External code can only contain letters, numbers, hyphens, and underscores' + } + return true + }, + + /** + * Validate payment flow + * @param flow Payment flow to validate + * @returns true if valid, false otherwise + */ + validateFlow(flow: string): boolean { + return ['MATCH_CODE', 'MATCH_USER', 'REFUND'].includes(flow) + }, + + /** + * Validate currency code + * @param currency Currency code to validate + * @returns true if valid, false otherwise + */ + validateCurrency(currency: string): boolean { + return currency === 'EUR' + }, + + /** + * Validate phone number (Italian format) + * @param phone Phone number to validate + * @returns true if valid, error message if invalid + */ + validatePhone(phone: string): true | string { + // Remove spaces and special characters + const cleaned = phone.replace(/[\s()-]/g, '') + + // Check Italian mobile format: +39 followed by 9-10 digits + if (!/^\+39[0-9]{9,10}$/.test(cleaned)) { + return 'Invalid Italian phone number format. Use +39 followed by 9-10 digits' + } + + return true + }, + + /** + * Validate metadata object + * @param metadata Metadata to validate + * @returns true if valid, error message if invalid + */ + validateMetadata(metadata: Record): true | string { + if (!metadata || typeof metadata !== 'object') { + return 'Metadata must be an object' + } + + const jsonString = JSON.stringify(metadata) + if (jsonString.length > 1000) { + return 'Metadata must be 1000 characters or less when stringified' + } + + return true + }, +} + +/** + * Code Generator Utilities + * Helper functions for generating codes and identifiers + */ +export const CodeGenerator = { + /** + * Generate external code with timestamp + * @param prefix Optional prefix (default: 'ORDER') + * @returns Generated external code + * @example CodeGenerator.generateExternalCode() // "ORDER-1704123456789" + */ + generateExternalCode(prefix: string = 'ORDER'): string { + return `${prefix}-${Date.now()}` + }, + + /** + * Generate external code with random suffix + * @param prefix Optional prefix (default: 'ORDER') + * @returns Generated external code + * @example CodeGenerator.generateRandomExternalCode() // "ORDER-a1b2c3d4" + */ + generateRandomExternalCode(prefix: string = 'ORDER'): string { + const random = Math.random().toString(36).substring(2, 10) + return `${prefix}-${random}` + }, + + /** + * Generate external code with UUID + * @param prefix Optional prefix (default: 'ORDER') + * @returns Generated external code + * @example CodeGenerator.generateUuidExternalCode() // "ORDER-550e8400-e29b-41d4-a716-446655440000" + */ + generateUuidExternalCode(prefix: string = 'ORDER'): string { + // Simple UUID v4 generator + const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { + const r = (Math.random() * 16) | 0 + const v = c === 'x' ? r : (r & 0x3) | 0x8 + return v.toString(16) + }) + return `${prefix}-${uuid}` + }, +} + +/** + * Payment Status Utilities + * Helper functions for payment status handling + */ +export const PaymentStatusUtils = { + /** + * Check if payment is pending + */ + isPending(status: string): boolean { + return status === 'PENDING' + }, + + /** + * Check if payment is accepted + */ + isAccepted(status: string): boolean { + return status === 'ACCEPTED' + }, + + /** + * Check if payment is canceled + */ + isCanceled(status: string): boolean { + return status === 'CANCELED' + }, + + /** + * Check if payment is expired + */ + isExpired(status: string): boolean { + return status === 'EXPIRED' + }, + + /** + * Check if payment is in final state + */ + isFinal(status: string): boolean { + return ['ACCEPTED', 'CANCELED', 'EXPIRED'].includes(status) + }, + + /** + * Get human-readable status + */ + getLabel(status: string, locale: string = 'it-IT'): string { + const labels: Record> = { + 'it-IT': { + PENDING: 'In attesa', + ACCEPTED: 'Accettato', + CANCELED: 'Annullato', + EXPIRED: 'Scaduto', + }, + 'en-US': { + PENDING: 'Pending', + ACCEPTED: 'Accepted', + CANCELED: 'Canceled', + EXPIRED: 'Expired', + }, + } + + return labels[locale]?.[status] || status + }, +} diff --git a/tests/Api.test.ts b/tests/Api.test.ts new file mode 100644 index 0000000..3eeb2e5 --- /dev/null +++ b/tests/Api.test.ts @@ -0,0 +1,93 @@ +import { describe, it, expect, beforeEach } from 'vitest' +import { Api } from '../src/Api' +import type { Environment } from '../src/types' + +describe('Api', () => { + beforeEach(() => { + // Reset to default state + Api.setEnv('production') + }) + + describe('environment management', () => { + it('should have production as default environment', () => { + expect(Api.getEnv()).toBe('production') + }) + + it('should set and get environment correctly', () => { + const environments: Environment[] = ['production', 'staging', 'test'] + + environments.forEach((env) => { + Api.setEnv(env) + expect(Api.getEnv()).toBe(env) + }) + }) + }) + + describe('key management', () => { + const mockPrivateKey = '-----BEGIN PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----' + const mockPublicKey = '-----BEGIN PUBLIC KEY-----\ntest\n-----END PUBLIC KEY-----' + const mockKeyId = 'test-key-id' + + it('should set and get private key', () => { + Api.setPrivateKey(mockPrivateKey) + expect(Api.getPrivateKey()).toBe(mockPrivateKey) + }) + + it('should set and get public key', () => { + Api.setPublicKey(mockPublicKey) + expect(Api.getPublicKey()).toBe(mockPublicKey) + }) + + it('should set and get key ID', () => { + Api.setKeyId(mockKeyId) + expect(Api.getKeyId()).toBe(mockKeyId) + }) + }) + + describe('header management', () => { + it('should set and get platform version header', () => { + const version = '1.0.0' + Api.setPlatformVersionHeader(version) + expect(Api.getPlatformVersionHeader()).toBe(version) + }) + + it('should set and get platform header', () => { + const platform = 'Node.js' + Api.setPlatformHeader(platform) + expect(Api.getPlatformHeader()).toBe(platform) + }) + + it('should set and get plugin version header', () => { + const version = '2.0.0' + Api.setPluginVersionHeader(version) + expect(Api.getPluginVersionHeader()).toBe(version) + }) + + it('should set and get plugin name header', () => { + const name = 'my-plugin' + Api.setPluginNameHeader(name) + expect(Api.getPluginNameHeader()).toBe(name) + }) + + it('should set and get type header', () => { + const type = 'POS' + Api.setTypeHeader(type) + expect(Api.getTypeHeader()).toBe(type) + }) + + it('should set and get tracking header', () => { + const tracking = 'tracking-code-123' + Api.setTrackingHeader(tracking) + expect(Api.getTrackingHeader()).toBe(tracking) + }) + }) + + describe('getVersion', () => { + it('should return SDK version', () => { + const version = Api.getVersion() + expect(version).toBeTruthy() + expect(typeof version).toBe('string') + expect(version).toMatch(/^\d+\.\d+\.\d+$/) + }) + }) +}) diff --git a/tests/ApiAuthentication.test.ts b/tests/ApiAuthentication.test.ts new file mode 100644 index 0000000..cea66ca --- /dev/null +++ b/tests/ApiAuthentication.test.ts @@ -0,0 +1,47 @@ +import { describe, it, expect } from 'vitest' +import { ApiAuthentication } from '../src/ApiAuthentication' + +describe('ApiAuthentication', () => { + const mockPrivateKey = '-----BEGIN PRIVATE KEY-----\ntest-private\n-----END PRIVATE KEY-----' + const mockPublicKey = '-----BEGIN PUBLIC KEY-----\ntest-public\n-----END PUBLIC KEY-----' + const mockKeyId = 'test-key-id-123' + + describe('constructor', () => { + it('should create an instance with provided keys', () => { + const auth = new ApiAuthentication(mockPrivateKey, mockPublicKey, mockKeyId) + + expect(auth).toBeInstanceOf(ApiAuthentication) + expect(auth.privateKey).toBe(mockPrivateKey) + expect(auth.publicKey).toBe(mockPublicKey) + expect(auth.keyId).toBe(mockKeyId) + }) + }) + + describe('properties', () => { + it('should return the private key', () => { + const auth = new ApiAuthentication(mockPrivateKey, mockPublicKey, mockKeyId) + expect(auth.privateKey).toBe(mockPrivateKey) + }) + + it('should return the public key', () => { + const auth = new ApiAuthentication(mockPrivateKey, mockPublicKey, mockKeyId) + expect(auth.publicKey).toBe(mockPublicKey) + }) + + it('should return the key ID', () => { + const auth = new ApiAuthentication(mockPrivateKey, mockPublicKey, mockKeyId) + expect(auth.keyId).toBe(mockKeyId) + }) + }) + + describe('multiple instances', () => { + it('should maintain separate state for different instances', () => { + const auth1 = new ApiAuthentication(mockPrivateKey, mockPublicKey, 'key-id-1') + const auth2 = new ApiAuthentication('other-private', 'other-public', 'key-id-2') + + expect(auth1.keyId).toBe('key-id-1') + expect(auth2.keyId).toBe('key-id-2') + expect(auth1.privateKey).not.toBe(auth2.privateKey) + }) + }) +}) diff --git a/tests/Consumer.test.ts b/tests/Consumer.test.ts new file mode 100644 index 0000000..d0d2419 --- /dev/null +++ b/tests/Consumer.test.ts @@ -0,0 +1,70 @@ +import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest' +import { Consumer } from '../src/Consumer' +import { Request } from '../src/Request' +import type { Consumer as ConsumerType } from '../src/types' + +// Mock Request module +vi.mock('../src/Request', () => ({ + Request: { + get: vi.fn(), + }, +})) + +describe('Consumer', () => { + const mockConsumer: ConsumerType = { + id: 'consumer-123', + type: 'CONSUMER', + name: 'John Doe', + phone_number: '+393331234567', + } + + beforeEach(() => { + vi.clearAllMocks() + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + describe('get', () => { + it('should get consumer by id', async () => { + const consumerId = 'consumer-123' + vi.mocked(Request.get).mockResolvedValue(mockConsumer) + + const result = await Consumer.get(consumerId) + + expect(Request.get).toHaveBeenCalledWith('/g_business/v1/consumers/consumer-123', { + headers: {}, + sign: true, + }) + expect(result).toEqual(mockConsumer) + }) + + it('should get consumer with custom headers', async () => { + const consumerId = 'consumer-123' + const customHeaders = { 'X-Custom': 'value' } + vi.mocked(Request.get).mockResolvedValue(mockConsumer) + + await Consumer.get(consumerId, customHeaders) + + expect(Request.get).toHaveBeenCalledWith('/g_business/v1/consumers/consumer-123', { + headers: customHeaders, + sign: true, + }) + }) + + it('should handle consumer without optional fields', async () => { + const minimalConsumer: ConsumerType = { + id: 'consumer-456', + type: 'CONSUMER', + } + vi.mocked(Request.get).mockResolvedValue(minimalConsumer) + + const result = await Consumer.get('consumer-456') + + expect(result).toEqual(minimalConsumer) + expect(result.name).toBeUndefined() + expect(result.phone_number).toBeUndefined() + }) + }) +}) diff --git a/tests/DailyClosure.test.ts b/tests/DailyClosure.test.ts new file mode 100644 index 0000000..d00c5fe --- /dev/null +++ b/tests/DailyClosure.test.ts @@ -0,0 +1,67 @@ +import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest' +import { DailyClosure } from '../src/DailyClosure' +import { Request } from '../src/Request' +import type { DailyClosure as DailyClosureType } from '../src/types' + +// Mock Request module +vi.mock('../src/Request', () => ({ + Request: { + get: vi.fn(), + }, +})) + +describe('DailyClosure', () => { + const mockDailyClosure: DailyClosureType = { + date: '2025-11-18', + total_amount_unit: 15000, + currency: 'EUR', + payments_count: 25, + } + + beforeEach(() => { + vi.clearAllMocks() + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + describe('get', () => { + it('should get daily closure by date', async () => { + const date = '20251118' + vi.mocked(Request.get).mockResolvedValue(mockDailyClosure) + + const result = await DailyClosure.get(date) + + expect(Request.get).toHaveBeenCalledWith('/g_business/v1/daily_closure/20251118', { + headers: {}, + sign: true, + }) + expect(result).toEqual(mockDailyClosure) + }) + + it('should get daily closure with query parameters', async () => { + const date = '20251118' + const query = { limit: 10 } + vi.mocked(Request.get).mockResolvedValue(mockDailyClosure) + + await DailyClosure.get(date, query) + + expect(Request.get).toHaveBeenCalledWith( + '/g_business/v1/daily_closure/20251118?limit=10', + expect.objectContaining({ + sign: true, + }) + ) + }) + + it('should use today date when no date provided', async () => { + vi.mocked(Request.get).mockResolvedValue(mockDailyClosure) + + await DailyClosure.get() + + const callArg = vi.mocked(Request.get).mock.calls[0][0] + expect(callArg).toMatch(/^\/g_business\/v1\/daily_closure\/\d{8}/) + }) + }) +}) diff --git a/tests/Payment.test.ts b/tests/Payment.test.ts new file mode 100644 index 0000000..805ed60 --- /dev/null +++ b/tests/Payment.test.ts @@ -0,0 +1,237 @@ +import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest' +import { Payment } from '../src/Payment' +import { Request } from '../src/Request' +import type { PaymentCreateBody, PaymentResponse } from '../src/types' + +// Mock Request module +vi.mock('../src/Request', () => ({ + Request: { + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), + }, +})) + +describe('Payment', () => { + const mockPaymentResponse: PaymentResponse = { + id: 'payment-123', + code_identifier: 'CODE123', + type: 'TO_BUSINESS', + amount_unit: 1000, + currency: 'EUR', + status: 'PENDING', + expired: false, + metadata: {}, + sender: { + id: 'sender-123', + type: 'CONSUMER', + }, + receiver: { + id: 'receiver-456', + type: 'SHOP', + }, + daily_closure: 'closure-789', + insert_date: '2025-11-18T10:00:00.000Z', + expire_date: '2025-11-18T11:00:00.000Z', + } + + beforeEach(() => { + vi.clearAllMocks() + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + describe('create', () => { + it('should create a payment with MATCH_CODE flow', async () => { + const mockBody: PaymentCreateBody = { + flow: 'MATCH_CODE', + amount_unit: 1000, + currency: 'EUR', + } + + vi.mocked(Request.post).mockResolvedValue(mockPaymentResponse) + + const result = await Payment.create(mockBody) + + expect(Request.post).toHaveBeenCalledWith('/g_business/v1/payments', { + headers: {}, + body: mockBody, + sign: true, + }) + expect(result).toEqual(mockPaymentResponse) + }) + + it('should create a payment with custom headers', async () => { + const mockBody: PaymentCreateBody = { + flow: 'MATCH_CODE', + amount_unit: 1000, + currency: 'EUR', + } + const customHeaders = { + 'Idempotency-Key': 'unique-key-123', + } + + vi.mocked(Request.post).mockResolvedValue(mockPaymentResponse) + + await Payment.create(mockBody, customHeaders) + + expect(Request.post).toHaveBeenCalledWith('/g_business/v1/payments', { + headers: customHeaders, + body: mockBody, + sign: true, + }) + }) + + it('should create a REFUND payment with parent_payment_uid', async () => { + const mockBody: PaymentCreateBody = { + flow: 'REFUND', + amount_unit: 500, + currency: 'EUR', + parent_payment_uid: 'parent-payment-123', + } + + vi.mocked(Request.post).mockResolvedValue(mockPaymentResponse) + + await Payment.create(mockBody) + + expect(Request.post).toHaveBeenCalledWith( + '/g_business/v1/payments', + expect.objectContaining({ + body: mockBody, + sign: true, + }) + ) + }) + }) + + describe('get', () => { + it('should get a payment by id', async () => { + const paymentId = 'payment-123' + vi.mocked(Request.get).mockResolvedValue(mockPaymentResponse) + + const result = await Payment.get(paymentId) + + expect(Request.get).toHaveBeenCalledWith('/g_business/v1/payments/payment-123', { + headers: {}, + sign: true, + }) + expect(result).toEqual(mockPaymentResponse) + }) + + it('should get a payment with custom headers', async () => { + const paymentId = 'payment-123' + const customHeaders = { 'X-Custom': 'value' } + vi.mocked(Request.get).mockResolvedValue(mockPaymentResponse) + + await Payment.get(paymentId, customHeaders) + + expect(Request.get).toHaveBeenCalledWith('/g_business/v1/payments/payment-123', { + headers: customHeaders, + sign: true, + }) + }) + }) + + describe('all', () => { + const mockListResponse = { + list: [mockPaymentResponse], + has_more: false, + } + + it('should get all payments without query params', async () => { + vi.mocked(Request.get).mockResolvedValue(mockListResponse) + + const result = await Payment.all() + + expect(Request.get).toHaveBeenCalledWith('/g_business/v1/payments', { + headers: {}, + sign: true, + }) + expect(result).toEqual(mockListResponse) + }) + + it('should get payments with query parameters', async () => { + const query = { + limit: 10, + starting_after: 'payment-100', + } + vi.mocked(Request.get).mockResolvedValue(mockListResponse) + + await Payment.all(query) + + expect(Request.get).toHaveBeenCalledWith( + '/g_business/v1/payments?limit=10&starting_after=payment-100', + expect.objectContaining({ + sign: true, + }) + ) + }) + + it('should handle custom headers', async () => { + const customHeaders = { 'X-Custom': 'header' } + vi.mocked(Request.get).mockResolvedValue(mockListResponse) + + await Payment.all({}, customHeaders) + + expect(Request.get).toHaveBeenCalledWith('/g_business/v1/payments', { + headers: customHeaders, + sign: true, + }) + }) + }) + + describe('update', () => { + it('should update a payment', async () => { + const paymentId = 'payment-123' + const updateBody = { + action: 'ACCEPT' as const, + } + vi.mocked(Request.put).mockResolvedValue(mockPaymentResponse) + + const result = await Payment.update(paymentId, updateBody) + + expect(Request.put).toHaveBeenCalledWith('/g_business/v1/payments/payment-123', { + headers: {}, + body: updateBody, + sign: true, + }) + expect(result).toEqual(mockPaymentResponse) + }) + + it('should update a payment with amount', async () => { + const paymentId = 'payment-123' + const updateBody = { + action: 'ACCEPT' as const, + amount_unit: 800, + } + vi.mocked(Request.put).mockResolvedValue(mockPaymentResponse) + + await Payment.update(paymentId, updateBody) + + expect(Request.put).toHaveBeenCalledWith( + '/g_business/v1/payments/payment-123', + expect.objectContaining({ + body: updateBody, + sign: true, + }) + ) + }) + + it('should handle custom headers in update', async () => { + const paymentId = 'payment-123' + const updateBody = { action: 'CANCEL' as const } + const customHeaders = { 'X-Reason': 'customer-request' } + vi.mocked(Request.put).mockResolvedValue(mockPaymentResponse) + + await Payment.update(paymentId, updateBody, customHeaders) + + expect(Request.put).toHaveBeenCalledWith('/g_business/v1/payments/payment-123', { + headers: customHeaders, + body: updateBody, + sign: true, + }) + }) + }) +}) diff --git a/tests/PreAuthorizedPaymentToken.test.ts b/tests/PreAuthorizedPaymentToken.test.ts new file mode 100644 index 0000000..ba70080 --- /dev/null +++ b/tests/PreAuthorizedPaymentToken.test.ts @@ -0,0 +1,187 @@ +import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest' +import { PreAuthorizedPaymentToken } from '../src/PreAuthorizedPaymentToken' +import { Request } from '../src/Request' +import type { + PreAuthorizedPaymentResponse, + PreAuthorizedPaymentTokenCreateBody, +} from '../src/types' + +// Mock Request module +vi.mock('../src/Request', () => ({ + Request: { + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), + delete: vi.fn(), + }, +})) + +describe('PreAuthorizedPaymentToken', () => { + const mockTokenResponse: PreAuthorizedPaymentResponse = { + id: 'token-123', + token: 'TOKEN_ABC123', + status: 'PENDING', + consumer_uid: 'consumer-456', + expire_date: '2025-12-18T10:00:00.000Z', + metadata: { note: 'test' }, + } + + beforeEach(() => { + vi.clearAllMocks() + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + describe('create', () => { + it('should create a pre-authorized payment token', async () => { + const createBody: PreAuthorizedPaymentTokenCreateBody = { + reason: 'Subscription payment', + callback_url: 'https://example.com/callback', + } + vi.mocked(Request.post).mockResolvedValue(mockTokenResponse) + + const result = await PreAuthorizedPaymentToken.create(createBody) + + expect(Request.post).toHaveBeenCalledWith( + '/g_business/v1/pre_authorized_payment_tokens', + { + headers: {}, + body: createBody, + sign: true, + } + ) + expect(result).toEqual(mockTokenResponse) + }) + + it('should create token with custom headers', async () => { + const createBody: PreAuthorizedPaymentTokenCreateBody = { + reason: 'Subscription', + } + const customHeaders = { 'Idempotency-Key': 'unique-123' } + vi.mocked(Request.post).mockResolvedValue(mockTokenResponse) + + await PreAuthorizedPaymentToken.create(createBody, customHeaders) + + expect(Request.post).toHaveBeenCalledWith( + '/g_business/v1/pre_authorized_payment_tokens', + { + headers: customHeaders, + body: createBody, + sign: true, + } + ) + }) + + it('should create token with redirect_url', async () => { + const createBody: PreAuthorizedPaymentTokenCreateBody = { + reason: 'Setup', + redirect_url: 'https://example.com/success', + } + vi.mocked(Request.post).mockResolvedValue(mockTokenResponse) + + await PreAuthorizedPaymentToken.create(createBody) + + expect(Request.post).toHaveBeenCalledWith( + '/g_business/v1/pre_authorized_payment_tokens', + expect.objectContaining({ + body: createBody, + }) + ) + }) + }) + + describe('get', () => { + it('should get a pre-authorized payment token by id', async () => { + const tokenId = 'token-123' + vi.mocked(Request.get).mockResolvedValue(mockTokenResponse) + + const result = await PreAuthorizedPaymentToken.get(tokenId) + + expect(Request.get).toHaveBeenCalledWith( + '/g_business/v1/pre_authorized_payment_tokens/token-123', + { + headers: {}, + sign: true, + } + ) + expect(result).toEqual(mockTokenResponse) + }) + + it('should get token with custom headers', async () => { + const tokenId = 'token-123' + const customHeaders = { 'X-Custom': 'value' } + vi.mocked(Request.get).mockResolvedValue(mockTokenResponse) + + await PreAuthorizedPaymentToken.get(tokenId, customHeaders) + + expect(Request.get).toHaveBeenCalledWith( + '/g_business/v1/pre_authorized_payment_tokens/token-123', + { + headers: customHeaders, + sign: true, + } + ) + }) + }) + + describe('update', () => { + it('should update a pre-authorized payment token', async () => { + const tokenId = 'token-123' + const updateBody = { + status: 'ACCEPTED', + } + vi.mocked(Request.put).mockResolvedValue(mockTokenResponse) + + const result = await PreAuthorizedPaymentToken.update(tokenId, updateBody) + + expect(Request.put).toHaveBeenCalledWith( + '/g_business/v1/pre_authorized_payment_tokens/token-123', + { + headers: {}, + body: updateBody, + sign: true, + } + ) + expect(result).toEqual(mockTokenResponse) + }) + + it('should update token with metadata', async () => { + const tokenId = 'token-123' + const updateBody = { + metadata: { updated: true }, + } + vi.mocked(Request.put).mockResolvedValue(mockTokenResponse) + + await PreAuthorizedPaymentToken.update(tokenId, updateBody) + + expect(Request.put).toHaveBeenCalledWith( + '/g_business/v1/pre_authorized_payment_tokens/token-123', + expect.objectContaining({ + body: updateBody, + }) + ) + }) + + it('should update token with custom headers', async () => { + const tokenId = 'token-123' + const updateBody = { + status: 'CANCELED', + } + const customHeaders = { 'X-Reason': 'expired' } + vi.mocked(Request.put).mockResolvedValue(mockTokenResponse) + + await PreAuthorizedPaymentToken.update(tokenId, updateBody, customHeaders) + + expect(Request.put).toHaveBeenCalledWith( + '/g_business/v1/pre_authorized_payment_tokens/token-123', + { + headers: customHeaders, + body: updateBody, + sign: true, + } + ) + }) + }) +}) diff --git a/tests/RSAServiceCrypto.test.ts b/tests/RSAServiceCrypto.test.ts new file mode 100644 index 0000000..2bf5dd0 --- /dev/null +++ b/tests/RSAServiceCrypto.test.ts @@ -0,0 +1,80 @@ +import { describe, it, expect } from 'vitest' +import { RSAServiceCrypto } from '../src/RSAService/RSAServiceCrypto' + +describe('RSAServiceCrypto', () => { + const rsaService = new RSAServiceCrypto() + + describe('isAvailable', () => { + it('should return true in Node.js environment', () => { + expect(rsaService.isAvailable()).toBe(true) + }) + }) + + describe('generateKeys', () => { + it('should generate a valid RSA key pair', () => { + const keys = rsaService.generateKeys() + + expect(keys).toHaveProperty('privateKey') + expect(keys).toHaveProperty('publicKey') + expect(keys.privateKey).toContain('BEGIN PRIVATE KEY') + expect(keys.privateKey).toContain('END PRIVATE KEY') + expect(keys.publicKey).toContain('BEGIN PUBLIC KEY') + expect(keys.publicKey).toContain('END PUBLIC KEY') + }) + + it('should generate different keys each time', () => { + const keys1 = rsaService.generateKeys() + const keys2 = rsaService.generateKeys() + + expect(keys1.privateKey).not.toBe(keys2.privateKey) + expect(keys1.publicKey).not.toBe(keys2.publicKey) + }) + }) + + describe('sign', () => { + it('should sign a message with private key', () => { + const keys = rsaService.generateKeys() + const message = 'test message' + + const signature = rsaService.sign(keys.privateKey, message) + + expect(signature).toBeTruthy() + expect(signature).toBeInstanceOf(Buffer) + expect(signature.length).toBeGreaterThan(0) + }) + + it('should produce different signatures for different messages', () => { + const keys = rsaService.generateKeys() + const message1 = 'test message 1' + const message2 = 'test message 2' + + const signature1 = rsaService.sign(keys.privateKey, message1) + const signature2 = rsaService.sign(keys.privateKey, message2) + + expect(signature1).not.toBe(signature2) + }) + + it('should produce the same signature for the same message', () => { + const keys = rsaService.generateKeys() + const message = 'test message' + + const signature1 = rsaService.sign(keys.privateKey, message) + const signature2 = rsaService.sign(keys.privateKey, message) + + expect(signature1).toStrictEqual(signature2) + }) + + it('should throw error with invalid private key', () => { + expect(() => { + rsaService.sign('invalid-key', 'message') + }).toThrow() + }) + + it('should return a Buffer', () => { + const keys = rsaService.generateKeys() + const signature = rsaService.sign(keys.privateKey, 'test') + + expect(signature).toBeInstanceOf(Buffer) + }) + }) +}) diff --git a/tests/RSAServiceFactory.test.ts b/tests/RSAServiceFactory.test.ts new file mode 100644 index 0000000..63c7e64 --- /dev/null +++ b/tests/RSAServiceFactory.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from 'vitest' +import { RSAServiceFactory } from '../src/RSAService/RSAServiceFactory' +import { RSAServiceCrypto } from '../src/RSAService/RSAServiceCrypto' + +describe('RSAServiceFactory', () => { + describe('get', () => { + it('should return an RSA service instance', () => { + const service = RSAServiceFactory.get() + + expect(service).toBeDefined() + expect(service).toBeInstanceOf(RSAServiceCrypto) + }) + + it('should return the same instance on multiple calls', () => { + const service1 = RSAServiceFactory.get() + const service2 = RSAServiceFactory.get() + + expect(service1).toBe(service2) + }) + + it('should return a service that can generate keys', () => { + const service = RSAServiceFactory.get() + const keys = service.generateKeys() + + expect(keys).toHaveProperty('privateKey') + expect(keys).toHaveProperty('publicKey') + }) + }) +}) diff --git a/tests/Request.test.ts b/tests/Request.test.ts new file mode 100644 index 0000000..09c31f4 --- /dev/null +++ b/tests/Request.test.ts @@ -0,0 +1,261 @@ +import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest' +import { Request } from '../src/Request' +import { Api } from '../src/Api' + +// Mock fetch globally +const mockFetch = vi.fn() +global.fetch = mockFetch as any + +describe('Request', () => { + beforeEach(() => { + vi.clearAllMocks() + Api.setEnv('test') + Api.setPrivateKey( + '-----BEGIN PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----' + ) + Api.setPublicKey('-----BEGIN PUBLIC KEY-----\ntest\n-----END PUBLIC KEY-----') + Api.setKeyId('test-key-id') + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + describe('HTTP Methods', () => { + it('should make a GET request', async () => { + const mockResponse = { data: 'test' } + mockFetch.mockResolvedValue({ + ok: true, + status: 200, + text: async () => JSON.stringify(mockResponse), + json: async () => mockResponse, + headers: new Headers(), + }) + + const result = await Request.get('/test/path') + + expect(mockFetch).toHaveBeenCalledWith( + expect.stringContaining('/test/path'), + expect.objectContaining({ + method: 'GET', + }) + ) + expect(result).toEqual(mockResponse) + }) + + it('should make a POST request', async () => { + const mockResponse = { created: true } + const postData = { name: 'test' } + mockFetch.mockResolvedValue({ + ok: true, + status: 201, + text: async () => JSON.stringify(mockResponse), + json: async () => mockResponse, + headers: new Headers(), + }) + + const result = await Request.post('/test/path', { body: postData }) + + expect(mockFetch).toHaveBeenCalledWith( + expect.stringContaining('/test/path'), + expect.objectContaining({ + method: 'POST', + body: JSON.stringify(postData), + }) + ) + expect(result).toEqual(mockResponse) + }) + + it('should make a PUT request', async () => { + const mockResponse = { updated: true } + const putData = { name: 'updated' } + mockFetch.mockResolvedValue({ + ok: true, + status: 200, + text: async () => JSON.stringify(mockResponse), + json: async () => mockResponse, + headers: new Headers(), + }) + + const result = await Request.put('/test/path', { body: putData }) + + expect(mockFetch).toHaveBeenCalledWith( + expect.stringContaining('/test/path'), + expect.objectContaining({ + method: 'PUT', + body: JSON.stringify(putData), + }) + ) + expect(result).toEqual(mockResponse) + }) + + it('should make a PATCH request', async () => { + const mockResponse = { patched: true } + const patchData = { field: 'value' } + mockFetch.mockResolvedValue({ + ok: true, + status: 200, + text: async () => JSON.stringify(mockResponse), + json: async () => mockResponse, + headers: new Headers(), + }) + + const result = await Request.patch('/test/path', { body: patchData }) + + expect(mockFetch).toHaveBeenCalledWith( + expect.stringContaining('/test/path'), + expect.objectContaining({ + method: 'PATCH', + body: JSON.stringify(patchData), + }) + ) + expect(result).toEqual(mockResponse) + }) + }) + + describe('Headers', () => { + it('should include custom headers', async () => { + const mockResponse = { success: true } + mockFetch.mockResolvedValue({ + ok: true, + status: 200, + text: async () => JSON.stringify(mockResponse), + json: async () => mockResponse, + headers: new Headers(), + }) + + const customHeaders = { + 'X-Custom-Header': 'custom-value', + } + + await Request.get('/test/path', { headers: customHeaders }) + + expect(mockFetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: expect.objectContaining(customHeaders), + }) + ) + }) + + it('should not include authentication headers when not signing', async () => { + const mockResponse = { success: true } + mockFetch.mockResolvedValue({ + ok: true, + status: 200, + text: async () => JSON.stringify(mockResponse), + json: async () => mockResponse, + headers: new Headers(), + }) + + await Request.get('/test/path', { sign: false }) + + const fetchCall = mockFetch.mock.calls[0] + const headers = fetchCall[1].headers + + expect(headers).not.toHaveProperty('Digest') + expect(headers).not.toHaveProperty('Authorization') + }) + }) + + describe('Error Handling', () => { + it('should throw error on 4xx response', async () => { + mockFetch.mockResolvedValue({ + ok: false, + status: 404, + text: async () => + JSON.stringify({ message: 'Not found', code: 'NOT_FOUND' }), + json: async () => ({ message: 'Not found', code: 'NOT_FOUND' }), + headers: new Headers(), + }) + + await expect(Request.get('/test/path')).rejects.toThrow() + }) + + it('should throw error on 5xx response', async () => { + mockFetch.mockResolvedValue({ + ok: false, + status: 500, + text: async () => + JSON.stringify({ message: 'Server error', code: 'SERVER_ERROR' }), + json: async () => ({ message: 'Server error', code: 'SERVER_ERROR' }), + headers: new Headers(), + }) + + await expect(Request.get('/test/path')).rejects.toThrow() + }) + + it('should throw error on network failure', async () => { + mockFetch.mockRejectedValue(new Error('Network error')) + + await expect(Request.get('/test/path')).rejects.toThrow('Network error') + }) + + it('should handle non-JSON responses', async () => { + mockFetch.mockResolvedValue({ + ok: true, + status: 200, + text: async () => 'Plain text response', + json: async () => { + throw new Error('Invalid JSON') + }, + headers: new Headers(), + }) + + // Request.ts handles plain text by returning it as-is when JSON parsing fails + const result = await Request.get('/test/path') + expect(result).toBe('Plain text response') + }) + }) + + describe('Request Body', () => { + it('should not include body for GET requests', async () => { + const mockResponse = { data: 'test' } + mockFetch.mockResolvedValue({ + ok: true, + status: 200, + text: async () => JSON.stringify(mockResponse), + json: async () => mockResponse, + headers: new Headers(), + }) + + await Request.get('/test/path') + + const fetchCall = mockFetch.mock.calls[0] + expect(fetchCall[1].body).toBeUndefined() + }) + + it('should serialize body as JSON for POST', async () => { + const mockResponse = { success: true } + const body = { key: 'value' } + mockFetch.mockResolvedValue({ + ok: true, + status: 200, + text: async () => JSON.stringify(mockResponse), + json: async () => mockResponse, + headers: new Headers(), + }) + + await Request.post('/test/path', { body }) + + const fetchCall = mockFetch.mock.calls[0] + expect(fetchCall[1].body).toBe(JSON.stringify(body)) + }) + + it('should handle empty body', async () => { + const mockResponse = { success: true } + mockFetch.mockResolvedValue({ + ok: true, + status: 200, + text: async () => JSON.stringify(mockResponse), + json: async () => mockResponse, + headers: new Headers(), + }) + + await Request.post('/test/path') + + const fetchCall = mockFetch.mock.calls[0] + expect(fetchCall[1].body).toBeUndefined() + }) + }) +}) diff --git a/tsconfig.json b/tsconfig.json index 12f3e84..896c46f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,24 +1,25 @@ { "compilerOptions": { "target": "ES2020", - "module": "nodenext", + "module": "ESNext", "lib": [ "ES2020" ], - "declaration": true, - "declarationMap": true, "outDir": "./dist", - "rootDir": ".", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, - "moduleResolution": "nodenext", + "moduleResolution": "Bundler", "resolveJsonModule": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "types": ["node", "vitest/globals"], + "paths": { + "~/*": ["./src/*"] + } }, "include": [ "src/**/*", @@ -27,6 +28,7 @@ "exclude": [ "node_modules", "dist", - "examples" + "examples", + "tests" ] } \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..7175ebe --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,41 @@ +import { defineConfig } from 'vite' +import { resolve } from 'path' +import { builtinModules } from 'module' +import dts from 'vite-plugin-dts' + +export default defineConfig({ + plugins: [ + dts({ + include: ['src/**/*'], + outDir: 'dist', + copyDtsFiles: true, + }), + ], + build: { + lib: { + entry: resolve(__dirname, 'src/index.ts'), + formats: ['es'], + fileName: 'index', + }, + outDir: 'dist', + sourcemap: true, + rollupOptions: { + external: [ + ...builtinModules, + ...builtinModules.map((m) => `node:${m}`), + ], + output: { + preserveModules: true, + preserveModulesRoot: 'src', + entryFileNames: '[name].js', + }, + }, + target: 'node18', + minify: false, + }, + resolve: { + alias: { + '~': resolve(__dirname, 'src'), + }, + }, +}) diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..e2968f2 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,28 @@ +import { defineConfig } from 'vitest/config' +import { resolve } from 'path' + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['tests/**/*.test.ts'], + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + include: ['src/**/*.ts'], + exclude: [ + 'node_modules/', + 'dist/', + 'examples/', + 'tests/', + '**/*.config.*', + '**/*.d.ts', + ], + }, + }, + resolve: { + alias: { + '~': resolve(__dirname, 'src'), + }, + }, +}) From 4bb96fdf36f85f615924042b3e964e9ae795e271 Mon Sep 17 00:00:00 2001 From: Alessandro Bellesia Date: Mon, 1 Dec 2025 22:51:13 +0100 Subject: [PATCH 3/5] feat: implement Reports, Sessions, and enhanced Payment features Major features: - Add Report API for payment/fee report generation (CSV, PDF, XLSX) - Add Session API for POS integration with fund lock payments - Extend Payment API with meal voucher and fringe benefits parameters - Support euro-to-cent amount conversion in Payment.create() and Payment.update() - Accept Date objects in Payment.all() for starting_after_timestamp parameter API classes: - Report: create(), all(), get() for merchant-level reports - Session: open(), get(), update(), createEvent() for POS sessions - Enhanced Payment types with meal_voucher_max_amount_unit and meal_voucher_max_quantity TypeScript: - Add ReportType, ReportFormatType, ReportStatus types - Add SessionStatus, SessionEventType types - Extend PaymentCreateBody and PaymentUpdateBody with meal voucher support - Make amount/amount_unit mutually exclusive in payment types Testing: - Add 23 new unit tests for Report and Session classes (163 total passing) - Add E2E test suite for staging environment integration testing - Add setup helper functions for E2E test configuration - Improve test coverage with edge cases and error handling Documentation: - Update CHANGELOG.md with comprehensive feature list - Add examples for reports (reports.ts) and POS sessions (pos-session.ts) - Update README with Reports, Sessions, and Meal Voucher sections Fixes: - Change Environment type from 'test' to 'staging' for consistency - Fix Payment.all() return type from 'list' to 'data' (API compliance) - Add proper SSL verification disable for staging environment --- .env.example | 14 + .github/workflows/ci.yml | 86 ++ .github/workflows/release.yml | 61 ++ .gitignore | 2 +- CHANGELOG.md | 85 +- README.md | 175 +++- examples/create-payment-with-amount.ts | 96 +++ examples/create-payment.ts | 12 +- examples/get-payments.ts | 5 +- examples/payment-date-filtering.ts | 103 +++ examples/pos-session.ts | 152 ++++ examples/reports.ts | 101 +++ package.json | 25 +- pnpm-lock.yaml | 1060 ++++++------------------ src/Payment.ts | 87 +- src/Report.ts | 142 ++++ src/Request.ts | 6 +- src/Session.ts | 171 ++++ src/index.ts | 2 + src/types.ts | 127 ++- tests/Api.test.ts | 41 +- tests/Payment.test.ts | 124 ++- tests/Report.test.ts | 222 +++++ tests/Request.test.ts | 41 +- tests/Session.test.ts | 301 +++++++ tests/e2e/authentication.e2e.test.ts | 58 ++ tests/e2e/payment.e2e.test.ts | 230 +++++ tests/setup.ts | 63 ++ tests/utils.test.ts | 345 ++++++++ vitest.config.ts | 12 +- 30 files changed, 3069 insertions(+), 880 deletions(-) create mode 100644 .env.example create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 examples/create-payment-with-amount.ts create mode 100644 examples/payment-date-filtering.ts create mode 100644 examples/pos-session.ts create mode 100644 examples/reports.ts create mode 100644 src/Report.ts create mode 100644 src/Session.ts create mode 100644 tests/Report.test.ts create mode 100644 tests/Session.test.ts create mode 100644 tests/e2e/authentication.e2e.test.ts create mode 100644 tests/e2e/payment.e2e.test.ts create mode 100644 tests/setup.ts create mode 100644 tests/utils.test.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..75b718e --- /dev/null +++ b/.env.example @@ -0,0 +1,14 @@ +# Example .env file for local E2E tests +# Copy this file to .env.local and insert your real values + +# IMPORTANT: E2E tests require pre-configured keys +# The activation code must NOT be included because it can only be used once +# Generate the keys manually using: npx satispay-keygen + +# Satispay authentication keys (REQUIRED for E2E tests) +SATISPAY_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nyour_public_key_here\n-----END PUBLIC KEY-----" +SATISPAY_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nyour_private_key_here\n-----END PRIVATE KEY-----" +SATISPAY_KEY_ID=your_key_id_here + +# NOTE: The environment is forced to 'staging' for security +# It cannot be modified in E2E tests diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ac8e93b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,86 @@ +name: CI + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + node-version: [18.x, 20.x, 22.x] + + steps: + - uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Lint + run: pnpm lint + + - name: Build + run: pnpm build + + - name: Test + run: pnpm test + env: + SATISPAY_PUBLIC_KEY: ${{ secrets.SATISPAY_PUBLIC_KEY }} + SATISPAY_PRIVATE_KEY: ${{ secrets.SATISPAY_PRIVATE_KEY }} + SATISPAY_KEY_ID: ${{ secrets.SATISPAY_KEY_ID }} + + - name: Test Coverage + run: pnpm test:coverage + env: + SATISPAY_PUBLIC_KEY: ${{ secrets.SATISPAY_PUBLIC_KEY }} + SATISPAY_PRIVATE_KEY: ${{ secrets.SATISPAY_PRIVATE_KEY }} + SATISPAY_KEY_ID: ${{ secrets.SATISPAY_KEY_ID }} + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + if: matrix.os == 'ubuntu-latest' && matrix.node-version == '20.x' + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./coverage/coverage-final.json + flags: unittests + name: codecov-umbrella + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Check formatting + run: pnpm format --check + + - name: Lint + run: pnpm lint diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..47a6a67 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,61 @@ +name: Release + +on: + push: + tags: + - 'v*' + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + cache: 'pnpm' + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build + run: pnpm build + + - name: Test + run: pnpm test + env: + SATISPAY_PUBLIC_KEY: ${{ secrets.SATISPAY_PUBLIC_KEY }} + SATISPAY_PRIVATE_KEY: ${{ secrets.SATISPAY_PRIVATE_KEY }} + SATISPAY_KEY_ID: ${{ secrets.SATISPAY_KEY_ID }} + + - name: Test Coverage + run: pnpm test:coverage + env: + SATISPAY_PUBLIC_KEY: ${{ secrets.SATISPAY_PUBLIC_KEY }} + SATISPAY_PRIVATE_KEY: ${{ secrets.SATISPAY_PRIVATE_KEY }} + SATISPAY_KEY_ID: ${{ secrets.SATISPAY_KEY_ID }} + + - name: Publish to npm + run: pnpm publish --access public --no-git-checks + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true + draft: false + prerelease: false diff --git a/.gitignore b/.gitignore index 15c4506..4812680 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ examples/authentication.json node_modules dist coverage -*.env.local \ No newline at end of file +*.local \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 37cbf6a..b41f327 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,67 +5,36 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] - -### Added -- Comprehensive test suite with 66 tests and 72% coverage -- GitHub Actions CI/CD workflows for automated testing and npm publishing -- Vitest test framework with UI and coverage reporting -- Support for multiple runtimes: Node.js 18+, Deno 1.30+, Bun 1.0+ - -### Changed -- Migrated build system from TypeScript compiler to Vite -- Improved TypeScript declaration generation with vite-plugin-dts -- Tests moved to dedicated `tests/` folder for better organization - -### Fixed -- TypeScript declaration files output to correct location in dist/ - -## [0.0.1] - TBD +## [0.0.1] - 2025-12-01 ### Added - Initial implementation of Satispay GBusiness Node.js SDK -- Api class for configuration and environment management -- ApiAuthentication for token-based authentication -- Payment operations (create, get, list, update) -- Consumer operations (get consumer details) -- DailyClosure operations (get daily closure reports) -- PreAuthorizedPaymentToken operations (create, get, list, accept, reject) -- Request class for HTTP operations with automatic signing -- RSA service for key generation and cryptographic operations -- Support for both Node.js crypto and Web Crypto API -- CLI tool `satispay-keygen` for generating RSA key pairs -- Comprehensive examples for common operations -- Zero runtime dependencies - -### API Methods - -#### Authentication -- `Api.authenticateWithToken(token: string)` - Generate keys and authenticate - -#### Configuration -- `Api.setEnv(env: Environment)` - Set environment (staging/production) -- `Api.setKeys(keyId: string, privateKey: string)` - Set authentication keys -- `Api.setPlatformHeader(value: string)` - Set platform identification header - -#### Payment Operations -- `Payment.create(options)` - Create a new payment -- `Payment.get(id: string)` - Get payment details -- `Payment.list(options)` - List payments with filters -- `Payment.update(id: string, options)` - Update payment metadata - -#### Consumer Operations -- `Consumer.get(id: string)` - Get consumer details - -#### Daily Closure Operations -- `DailyClosure.get(date: Date)` - Get daily closure report +- Zero runtime dependencies - uses only native APIs (fetch, crypto) +- Multi-runtime support: Node.js 18+, Deno 1.30+, Bun 1.0+ +- Complete TypeScript definitions with full type safety + +#### API Classes +- `Api` - Configuration and environment management +- `ApiAuthentication` - Token-based authentication and key generation +- `Payment` - Create, retrieve, list, and update payments (including meal vouchers and fringe benefits) +- `Consumer` - Retrieve consumer information +- `DailyClosure` - Get daily closure reports +- `PreAuthorizedPaymentToken` - Manage pre-authorized payment tokens +- `Report` - Generate and retrieve payment reports (CSV, PDF, XLSX) +- `Session` - POS integration for fund lock payments with incremental charging +- `Request` - HTTP operations with automatic RSA-SHA256 signing +- `RSAService` - Key generation and cryptographic operations + +#### Tools & Testing +- CLI tool `satispay-keygen` for RSA key pair generation +- Comprehensive test suite with 163 tests using Vitest +- E2E tests for integration testing with staging environment +- GitHub Actions CI/CD workflows for automated testing and npm publishing +- Vite-based build system with optimized TypeScript declaration generation -#### Pre-Authorized Payment Tokens -- `PreAuthorizedPaymentToken.create(options)` - Create token -- `PreAuthorizedPaymentToken.get(token: string)` - Get token details -- `PreAuthorizedPaymentToken.list()` - List tokens -- `PreAuthorizedPaymentToken.accept(token: string)` - Accept token -- `PreAuthorizedPaymentToken.reject(token: string)` - Reject token +#### Documentation & Examples +- Complete API documentation in README +- Example files for all major operations (payments, reports, sessions, webhooks, etc.) +- Runtime-specific examples for Node.js, Deno, and Bun -[Unreleased]: https://github.com/volverjs/satispay-node-sdk/compare/v0.0.1...HEAD [0.0.1]: https://github.com/volverjs/satispay-node-sdk/releases/tag/v0.0.1 diff --git a/README.md b/README.md index 545c4d5..ed50ff9 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,9 @@ Universal (but unofficial) TypeScript SDK for Satispay GBusiness API integration - **Type-safe** - Complete TypeScript definitions - **Modern** - Fetch API, async/await, ES Modules - **Secure** - Native RSA-SHA256 encryption +- **Developer-friendly** - Intuitive API with automatic conversions: + - 💶 Use `amount` (euros) instead of `amount_unit` (cents) + - 📅 Use `Date` objects instead of timestamp strings ## Installation @@ -102,7 +105,15 @@ SATISPAY_KEY_ID="your-key-id" ```typescript import { Payment } from '@volverjs/satispay-node-sdk'; +// Using amount in euros (recommended) const payment = await Payment.create({ + flow: 'MATCH_CODE', + amount: 1.99, // Amount in euros (automatically converted to cents) + currency: 'EUR', +}); + +// Or using amount_unit in cents (still supported) +const payment2 = await Payment.create({ flow: 'MATCH_CODE', amount_unit: 199, // Amount in cents (1.99 EUR) currency: 'EUR', @@ -119,9 +130,20 @@ console.log('Code:', payment.code_identifier); #### Create Payment ```typescript +// Using amount in euros (recommended) const payment = await Payment.create({ flow: 'MATCH_CODE', - amount_unit: 100, + amount: 1.00, // Automatically converted to 100 cents + currency: 'EUR', + callback_url: 'https://your-site.com/callback', + external_code: 'ORDER-123', + metadata: { order_id: '12345' }, +}); + +// Or using amount_unit in cents (still supported) +const payment2 = await Payment.create({ + flow: 'MATCH_CODE', + amount_unit: 100, // Amount in cents currency: 'EUR', callback_url: 'https://your-site.com/callback', external_code: 'ORDER-123', @@ -129,6 +151,10 @@ const payment = await Payment.create({ }); ``` +**💡 Tip**: Use `amount` (euros) for more intuitive code. The SDK automatically converts it to cents. + +See [examples/create-payment-with-amount.ts](./examples/create-payment-with-amount.ts) for more examples. + #### Get Payment ```typescript @@ -145,16 +171,48 @@ const result = await Payment.all({ from_date: '2024-01-01', }); -result.list.forEach(payment => { +result.data.forEach(payment => { console.log(`${payment.id}: ${payment.status}`); }); ``` +##### Filter by Date + +You can filter payments using `Date` objects or timestamp strings: + +```typescript +// Using Date objects (recommended) +const yesterday = new Date(); +yesterday.setDate(yesterday.getDate() - 1); + +const recentPayments = await Payment.all({ + starting_after_timestamp: yesterday, // Date is automatically converted to milliseconds + limit: 10, +}); + +// Or using timestamp string (milliseconds) +const timestampString = new Date('2024-01-01').getTime().toString(); +const paymentsFromDate = await Payment.all({ + starting_after_timestamp: timestampString, + limit: 10, +}); +``` + +See [examples/payment-date-filtering.ts](./examples/payment-date-filtering.ts) for more examples. + #### Update Payment ```typescript +// Using amount in euros const payment = await Payment.update('PAYMENT_ID', { - metadata: { order_id: '67890' }, + action: 'ACCEPT', + amount: 5.50, // Automatically converted to 550 cents +}); + +// Or using amount_unit in cents +const payment2 = await Payment.update('PAYMENT_ID', { + action: 'ACCEPT', + amount_unit: 550, }); ``` @@ -203,6 +261,117 @@ const updatedToken = await PreAuthorizedPaymentToken.update(token.id, { }); ``` +### Reports + +> **⚠️ Special Authentication Required**: Report APIs require special authentication keys. Contact tech@satispay.com to enable access. + +```typescript +import { Report } from '@volverjs/satispay-node-sdk'; + +// Create a new report +const report = await Report.create({ + type: 'PAYMENT_FEE', + format: 'CSV', // or 'PDF', 'XLSX' + from_date: '2025-11-01', + to_date: '2025-11-30', + columns: ['transaction_id', 'transaction_date', 'total_amount'], // Optional +}); + +// Get list of reports +const reports = await Report.all({ + limit: 10, + starting_after: 'report-123', +}); + +// Get specific report +const reportDetails = await Report.get('report-123'); + +if (reportDetails.status === 'READY' && reportDetails.download_url) { + console.log('Download URL:', reportDetails.download_url); +} +``` + +**Important Notes:** +- Reports are extracted at merchant level (includes all shops) +- Reports for the previous day should be generated at least 4 hours after midnight +- Report status: `PENDING`, `READY`, or `FAILED` + +### Sessions (POS Integration) + +Sessions are used for POS/device integration to manage fund lock payments incrementally: + +```typescript +import { Session } from '@volverjs/satispay-node-sdk'; + +// Open a session from a fund lock +const session = await Session.open({ + fund_lock_id: 'payment-fund-lock-123', +}); + +console.log('Session ID:', session.id); +console.log('Available amount:', session.residual_amount_unit); + +// Add items to the session +await Session.createEvent(session.id, { + type: 'ADD_ITEM', + amount_unit: 500, + description: 'Coffee', + metadata: { sku: 'COFFEE-001' }, +}); + +// Remove items +await Session.createEvent(session.id, { + type: 'REMOVE_ITEM', + amount_unit: 200, + description: 'Discount', +}); + +// Update total +await Session.createEvent(session.id, { + type: 'UPDATE_TOTAL', + amount_unit: 300, +}); + +// Get session details +const details = await Session.get(session.id); +console.log('Residual amount:', details.residual_amount_unit); + +// Close the session +const closedSession = await Session.update(session.id, { + status: 'CLOSE', +}); +``` + +### Meal Voucher & Fringe Benefits + +Meal Voucher and Fringe Benefits payments use the same `Payment` API with additional parameters: + +```typescript +import { Payment } from '@volverjs/satispay-node-sdk'; + +// Create payment with Meal Voucher limits +const payment = await Payment.create({ + flow: 'MATCH_CODE', + amount: 50.00, + currency: 'EUR', + meal_voucher_max_amount_unit: 4000, // Max 40 EUR with meal vouchers + meal_voucher_max_quantity: 8, // Max 8 vouchers +}); + +// Update payment with Meal Voucher limits +const updated = await Payment.update(payment.id, { + action: 'ACCEPT', + meal_voucher_max_amount_unit: 3000, + meal_voucher_max_quantity: 6, +}); +``` + +**Important Notes:** +- Meal Vouchers and Fringe Benefits are mutually exclusive +- Meal Voucher refunds: Only on the same day, full amount only +- Fringe Benefits refunds: Only within the same month, full amount only +- Default limit: 8 meal vouchers per payment if not specified + ## Runtime-Specific Examples ### Node.js Server diff --git a/examples/create-payment-with-amount.ts b/examples/create-payment-with-amount.ts new file mode 100644 index 0000000..124943d --- /dev/null +++ b/examples/create-payment-with-amount.ts @@ -0,0 +1,96 @@ +import { Api, Payment } from '../src'; +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * Example: Create payment using amount in euros + * + * This example demonstrates how to create payments using the 'amount' field + * which accepts floating-point numbers in euros instead of cents. + */ + +async function main() { + try { + // Enable sandbox mode + Api.setSandbox(true); + + // Load authentication keys + const authFilePath = path.join(__dirname, 'authentication.json'); + const authData = JSON.parse(fs.readFileSync(authFilePath, 'utf-8')); + + Api.setPublicKey(authData.public_key); + Api.setPrivateKey(authData.private_key); + Api.setKeyId(authData.key_id); + + console.log('Creating payments using amount field...\n'); + + // Example 1: Simple payment with amount in euros + console.log('1. Creating payment with 10.50 EUR:'); + const payment1 = await Payment.create({ + flow: 'MATCH_CODE', + amount: 10.50, // Automatically converted to 1050 cents + currency: 'EUR', + external_code: `EXAMPLE-${Date.now()}-1`, + }); + + console.log(` Payment ID: ${payment1.id}`); + console.log(` Code: ${payment1.code_identifier}`); + console.log(` Amount: ${payment1.amount_unit / 100} EUR (${payment1.amount_unit} cents)`); + console.log(` Status: ${payment1.status}\n`); + + // Example 2: Payment with decimal precision + console.log('2. Creating payment with 99.99 EUR:'); + const payment2 = await Payment.create({ + flow: 'MATCH_CODE', + amount: 99.99, // Handles decimal precision correctly + currency: 'EUR', + external_code: `EXAMPLE-${Date.now()}-2`, + metadata: { + description: 'Premium product', + category: 'electronics', + }, + }); + + console.log(` Payment ID: ${payment2.id}`); + console.log(` Code: ${payment2.code_identifier}`); + console.log(` Amount: ${payment2.amount_unit / 100} EUR (${payment2.amount_unit} cents)`); + console.log(` Status: ${payment2.status}\n`); + + // Example 3: Small amount (0.50 EUR) + console.log('3. Creating payment with 0.50 EUR:'); + const payment3 = await Payment.create({ + flow: 'MATCH_CODE', + amount: 0.50, // 50 cents + currency: 'EUR', + external_code: `EXAMPLE-${Date.now()}-3`, + }); + + console.log(` Payment ID: ${payment3.id}`); + console.log(` Code: ${payment3.code_identifier}`); + console.log(` Amount: ${payment3.amount_unit / 100} EUR (${payment3.amount_unit} cents)`); + console.log(` Status: ${payment3.status}\n`); + + // Example 4: Comparing with amount_unit (old way) + console.log('4. Using amount_unit (cents) - still supported:'); + const payment4 = await Payment.create({ + flow: 'MATCH_CODE', + amount_unit: 2500, // 25.00 EUR in cents + currency: 'EUR', + external_code: `EXAMPLE-${Date.now()}-4`, + }); + + console.log(` Payment ID: ${payment4.id}`); + console.log(` Code: ${payment4.code_identifier}`); + console.log(` Amount: ${payment4.amount_unit / 100} EUR (${payment4.amount_unit} cents)`); + console.log(` Status: ${payment4.status}\n`); + + console.log('✓ All payments created successfully!'); + console.log('\n💡 Tip: Using "amount" (euros) is more intuitive than "amount_unit" (cents)'); + + } catch (error) { + console.error('Error:', error instanceof Error ? error.message : String(error)); + process.exit(1); + } +} + +main(); diff --git a/examples/create-payment.ts b/examples/create-payment.ts index c687637..092c629 100644 --- a/examples/create-payment.ts +++ b/examples/create-payment.ts @@ -30,16 +30,24 @@ async function main() { console.log('Creating payment...'); - // Create a payment + // Create a payment using amount in euros (recommended) const payment = await Payment.create({ flow: 'MATCH_CODE', - amount_unit: 199, // Amount in cents (1.99 EUR) + amount: 1.99, // Amount in euros (automatically converted to cents) currency: 'EUR', }); + // Or using amount_unit in cents (still supported) + // const payment = await Payment.create({ + // flow: 'MATCH_CODE', + // amount_unit: 199, // Amount in cents (1.99 EUR) + // currency: 'EUR', + // }); + console.log('Payment created successfully!'); console.log('Payment ID:', payment.id); console.log('Payment Code:', payment.code_identifier); + console.log('Amount:', payment.amount_unit / 100, 'EUR'); console.log('Status:', payment.status); console.log('\nFull payment object:'); console.log(JSON.stringify(payment, null, 2)); diff --git a/examples/get-payments.ts b/examples/get-payments.ts index 95302a4..2a76ca2 100644 --- a/examples/get-payments.ts +++ b/examples/get-payments.ts @@ -28,12 +28,13 @@ async function main() { limit: 10, // status: 'ACCEPTED', // Optional: filter by status // from_date: '2024-01-01', // Optional: filter by date + // starting_after_timestamp: new Date('2024-01-01'), // Optional: filter by timestamp using Date object }); - console.log(`Found ${result.list.length} payments`); + console.log(`Found ${result.data.length} payments`); console.log('Has more:', result.has_more); console.log('\nPayments:'); - result.list.forEach((payment, index) => { + result.data.forEach((payment, index) => { console.log(`\n${index + 1}. Payment ${payment.id}`); console.log(` Status: ${payment.status}`); console.log(` Amount: ${payment.amount_unit / 100} ${payment.currency}`); diff --git a/examples/payment-date-filtering.ts b/examples/payment-date-filtering.ts new file mode 100644 index 0000000..2e2e473 --- /dev/null +++ b/examples/payment-date-filtering.ts @@ -0,0 +1,103 @@ +import { Api, Payment } from '../src'; +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * Example: Filter payments by date + * + * This example demonstrates how to filter payments using Date objects + * with the starting_after_timestamp parameter. + */ + +async function main() { + try { + // Enable sandbox mode + Api.setSandbox(true); + + // Load authentication keys + const authFilePath = path.join(__dirname, 'authentication.json'); + const authData = JSON.parse(fs.readFileSync(authFilePath, 'utf-8')); + + Api.setPublicKey(authData.public_key); + Api.setPrivateKey(authData.private_key); + Api.setKeyId(authData.key_id); + + console.log('Filtering payments by date...\n'); + + // Example 1: Payments from yesterday + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + + console.log('1. Payments from yesterday:'); + console.log(` Filter date: ${yesterday.toISOString()}`); + + const paymentsFromYesterday = await Payment.all({ + starting_after_timestamp: yesterday, // Date object is automatically converted + limit: 10, + }); + + console.log(` Found ${paymentsFromYesterday.data.length} payments`); + console.log(` Has more: ${paymentsFromYesterday.has_more}\n`); + + // Example 2: Payments from last week + const lastWeek = new Date(); + lastWeek.setDate(lastWeek.getDate() - 7); + + console.log('2. Payments from last week:'); + console.log(` Filter date: ${lastWeek.toISOString()}`); + + const paymentsFromLastWeek = await Payment.all({ + starting_after_timestamp: lastWeek, + limit: 20, + }); + + console.log(` Found ${paymentsFromLastWeek.data.length} payments`); + console.log(` Has more: ${paymentsFromLastWeek.has_more}\n`); + + // Example 3: Payments from a specific date + const specificDate = new Date('2024-01-01T00:00:00Z'); + + console.log('3. Payments from a specific date:'); + console.log(` Filter date: ${specificDate.toISOString()}`); + + const paymentsFromDate = await Payment.all({ + starting_after_timestamp: specificDate, + limit: 5, + }); + + console.log(` Found ${paymentsFromDate.data.length} payments`); + console.log(` Has more: ${paymentsFromDate.has_more}\n`); + + // Example 4: You can still use timestamp strings if needed + const timestampString = new Date('2024-06-01').getTime().toString(); + + console.log('4. Using timestamp string:'); + console.log(` Timestamp: ${timestampString}`); + + const paymentsWithString = await Payment.all({ + starting_after_timestamp: timestampString, + limit: 10, + }); + + console.log(` Found ${paymentsWithString.data.length} payments`); + console.log(` Has more: ${paymentsWithString.has_more}\n`); + + // Example 5: Combine with other filters + console.log('5. Date filter with status filter:'); + + const combinedFilters = await Payment.all({ + starting_after_timestamp: yesterday, + status: 'ACCEPTED', + limit: 10, + }); + + console.log(` Found ${combinedFilters.data.length} accepted payments from yesterday`); + console.log(` Has more: ${combinedFilters.has_more}`); + + } catch (error) { + console.error('Error:', error instanceof Error ? error.message : String(error)); + process.exit(1); + } +} + +main(); diff --git a/examples/pos-session.ts b/examples/pos-session.ts new file mode 100644 index 0000000..cce4973 --- /dev/null +++ b/examples/pos-session.ts @@ -0,0 +1,152 @@ +import { Api, Payment, Session } from '../src/index.js' + +/** + * Example: POS Session Management + * + * Sessions allow managing fund lock payments incrementally, + * useful for POS/device integrations where items are added over time. + */ + +// Configure API +Api.setSandbox(true) +Api.setPublicKey(process.env.SATISPAY_PUBLIC_KEY || '') +Api.setPrivateKey(process.env.SATISPAY_PRIVATE_KEY || '') +Api.setKeyId(process.env.SATISPAY_KEY_ID || '') + +async function main() { + try { + console.log('🏪 POS Session Example\n') + + // Step 1: Create a fund lock payment + console.log('1️⃣ Creating fund lock payment...\n') + const fundLock = await Payment.create({ + flow: 'FUND_LOCK', + amount: 100.00, // Lock 100 EUR + currency: 'EUR', + external_code: `POS-SESSION-${Date.now()}`, + }) + + console.log('✅ Fund lock created!') + console.log('Payment ID:', fundLock.id) + console.log('Amount locked:', fundLock.amount_unit / 100, 'EUR') + console.log('Status:', fundLock.status) + console.log('Code:', fundLock.code_identifier) + console.log('\n💡 Customer needs to authorize this fund lock in their Satispay app.\n') + + // In a real scenario, wait for customer authorization via webhook or polling + console.log('⏳ Waiting for customer authorization...') + console.log(' (In production, use webhooks or poll Payment.get())\n') + + // For this example, we'll simulate that the payment is not yet authorized + // In production, you would check the status until it becomes 'AUTHORIZED' + const currentStatus = await Payment.get(fundLock.id) + + if (currentStatus.status !== 'AUTHORIZED') { + console.log('⚠️ Payment not authorized yet.') + console.log(' Status:', currentStatus.status) + console.log('\n To continue this example:') + console.log(' 1. Authorize the payment in Satispay app') + console.log(' 2. Wait for status to become AUTHORIZED') + console.log(' 3. Then run the session operations\n') + + console.log(' For this demo, we\'ll show how sessions work once authorized:\n') + } + + // Step 2: Open a session (only works if fund lock is AUTHORIZED) + console.log('2️⃣ Opening POS session...\n') + console.log(' (This will fail until payment is AUTHORIZED)\n') + + try { + const session = await Session.open({ + fund_lock_id: fundLock.id, + }) + + console.log('✅ Session opened!') + console.log('Session ID:', session.id) + console.log('Total amount:', session.amount_unit / 100, 'EUR') + console.log('Residual amount:', session.residual_amount_unit / 100, 'EUR') + console.log('Status:', session.status) + + // Step 3: Add items to the session + console.log('\n3️⃣ Adding items to session...\n') + + // Add coffee + await Session.createEvent(session.id, { + type: 'ADD_ITEM', + amount_unit: 300, // 3.00 EUR + description: 'Espresso', + metadata: { sku: 'COFFEE-001', category: 'beverages' }, + }) + console.log('✅ Added: Espresso (3.00 EUR)') + + // Add croissant + await Session.createEvent(session.id, { + type: 'ADD_ITEM', + amount_unit: 250, // 2.50 EUR + description: 'Croissant', + metadata: { sku: 'PASTRY-042', category: 'food' }, + }) + console.log('✅ Added: Croissant (2.50 EUR)') + + // Add water + await Session.createEvent(session.id, { + type: 'ADD_ITEM', + amount_unit: 150, // 1.50 EUR + description: 'Water', + metadata: { sku: 'DRINK-010', category: 'beverages' }, + }) + console.log('✅ Added: Water (1.50 EUR)') + + // Apply discount + console.log('\n4️⃣ Applying discount...\n') + await Session.createEvent(session.id, { + type: 'REMOVE_ITEM', + amount_unit: 100, // -1.00 EUR discount + description: 'Happy Hour Discount', + }) + console.log('✅ Applied discount: -1.00 EUR') + + // Check session status + console.log('\n5️⃣ Checking session status...\n') + const sessionDetails = await Session.get(session.id) + console.log('Total amount:', sessionDetails.amount_unit / 100, 'EUR') + console.log('Amount charged:', (sessionDetails.amount_unit - sessionDetails.residual_amount_unit) / 100, 'EUR') + console.log('Residual available:', sessionDetails.residual_amount_unit / 100, 'EUR') + + // Close the session + console.log('\n6️⃣ Closing session...\n') + const closedSession = await Session.update(session.id, { + status: 'CLOSE', + }) + + console.log('✅ Session closed!') + console.log('Final status:', closedSession.status) + console.log('Final amount charged:', (closedSession.amount_unit - closedSession.residual_amount_unit) / 100, 'EUR') + console.log('\n🎉 Transaction complete!') + + } catch (sessionError) { + if (sessionError instanceof Error) { + console.log('⚠️ Could not open session (expected if payment not authorized)') + console.log(' Error:', sessionError.message) + console.log('\n💡 Session Workflow:') + console.log(' 1. Create FUND_LOCK payment') + console.log(' 2. Customer authorizes in Satispay app') + console.log(' 3. Payment status becomes AUTHORIZED') + console.log(' 4. Open session with fund_lock_id') + console.log(' 5. Add/remove items with createEvent()') + console.log(' 6. Close session to finalize charge') + } + } + + } catch (error) { + console.error('❌ Error:', error) + + if (error instanceof Error) { + console.error('Message:', error.message) + } + + process.exit(1) + } +} + +main() diff --git a/examples/reports.ts b/examples/reports.ts new file mode 100644 index 0000000..987e759 --- /dev/null +++ b/examples/reports.ts @@ -0,0 +1,101 @@ +import { Api, Report } from '../src/index.js' + +/** + * Example: Create and manage reports + * + * ⚠️ IMPORTANT: Report APIs require special authentication keys. + * Contact tech@satispay.com to enable report access for your account. + */ + +// Configure API +Api.setSandbox(true) +Api.setPublicKey(process.env.SATISPAY_PUBLIC_KEY || '') +Api.setPrivateKey(process.env.SATISPAY_PRIVATE_KEY || '') +Api.setKeyId(process.env.SATISPAY_KEY_ID || '') + +async function main() { + try { + console.log('📊 Creating a new report...\n') + + // Create a new report for the last month + const today = new Date() + const lastMonth = new Date(today.getFullYear(), today.getMonth() - 1, 1) + const lastMonthEnd = new Date(today.getFullYear(), today.getMonth(), 0) + + const report = await Report.create({ + type: 'PAYMENT_FEE', + format: 'CSV', + from_date: lastMonth.toISOString().split('T')[0], + to_date: lastMonthEnd.toISOString().split('T')[0], + columns: [ + 'transaction_id', + 'transaction_date', + 'total_amount', + 'fee_amount', + 'transaction_type', + 'external_code', + ], + }) + + console.log('✅ Report created successfully!') + console.log('Report ID:', report.id) + console.log('Status:', report.status) + console.log('Format:', report.format) + console.log('Period:', `${report.from_date} to ${report.to_date}\n`) + + // Poll for report completion + console.log('⏳ Waiting for report to be ready...\n') + let reportDetails = report + let attempts = 0 + const maxAttempts = 30 + + while (reportDetails.status === 'PENDING' && attempts < maxAttempts) { + await new Promise((resolve) => setTimeout(resolve, 5000)) // Wait 5 seconds + reportDetails = await Report.get(report.id) + attempts++ + console.log(`Status check ${attempts}/${maxAttempts}: ${reportDetails.status}`) + } + + if (reportDetails.status === 'READY') { + console.log('\n✅ Report is ready!') + console.log('Download URL:', reportDetails.download_url) + console.log('\n💡 The download URL is pre-signed and will expire.') + } else if (reportDetails.status === 'FAILED') { + console.log('\n❌ Report generation failed.') + } else { + console.log('\n⏱️ Report is still pending after maximum attempts.') + } + + // List all reports + console.log('\n📋 Fetching list of reports...\n') + const reportsList = await Report.all({ limit: 10 }) + console.log(`Found ${reportsList.list.length} reports:`) + reportsList.list.forEach((r, index) => { + console.log(`${index + 1}. ID: ${r.id}`) + console.log(` Status: ${r.status}`) + console.log(` Period: ${r.from_date} to ${r.to_date}`) + console.log(` Created: ${r.created_at}\n`) + }) + + if (reportsList.has_more) { + console.log('💡 More reports available. Use pagination to fetch them.') + } + } catch (error) { + console.error('❌ Error:', error) + + if (error instanceof Error) { + console.error('Message:', error.message) + + // Special authentication error handling + if (error.message.includes('401') || error.message.includes('403')) { + console.error('\n⚠️ Authentication Error:') + console.error('Report APIs require special authentication keys.') + console.error('Contact tech@satispay.com to enable report access.') + } + } + + process.exit(1) + } +} + +main() diff --git a/package.json b/package.json index 25f97ba..7bb5021 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,8 @@ "test:watch": "vitest", "test:ui": "vitest --ui", "test:coverage": "vitest run --coverage", + "test:e2e": "vitest run tests/e2e", + "test:e2e:watch": "vitest tests/e2e", "lint": "eslint src", "lint:fix": "eslint src --fix", "format": "prettier --write \"src/**/*.ts\"" @@ -58,17 +60,18 @@ "node": ">=18.0.0" }, "devDependencies": { - "@eslint/js": "^9.17.0", - "@types/node": "^22.10.2", - "@vitest/ui": "^2.1.8", - "@vitest/coverage-v8": "^2.1.8", - "eslint": "^9.17.0", + "@eslint/js": "^9.39.1", + "@types/node": "^24.10.1", + "@vitest/coverage-v8": "^4.0.14", + "@vitest/ui": "^4.0.14", + "dotenv": "^17.2.3", + "eslint": "^9.39.1", "eslint-plugin-vitest": "^0.5.4", - "prettier": "^3.4.2", - "typescript": "^5.7.2", - "typescript-eslint": "^8.19.1", - "vite": "^6.0.3", - "vite-plugin-dts": "^4.3.0", - "vitest": "^2.1.8" + "prettier": "^3.7.3", + "typescript": "^5.9.3", + "typescript-eslint": "^8.48.0", + "vite": "^7.2.6", + "vite-plugin-dts": "^4.5.4", + "vitest": "^4.0.14" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 21408f0..d866d4b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,48 +9,47 @@ importers: .: devDependencies: '@eslint/js': - specifier: ^9.17.0 + specifier: ^9.39.1 version: 9.39.1 '@types/node': - specifier: ^22.10.2 - version: 22.19.1 + specifier: ^24.10.1 + version: 24.10.1 '@vitest/coverage-v8': - specifier: ^2.1.8 - version: 2.1.9(vitest@2.1.9) + specifier: ^4.0.14 + version: 4.0.14(vitest@4.0.14) '@vitest/ui': - specifier: ^2.1.8 - version: 2.1.9(vitest@2.1.9) + specifier: ^4.0.14 + version: 4.0.14(vitest@4.0.14) + dotenv: + specifier: ^17.2.3 + version: 17.2.3 eslint: - specifier: ^9.17.0 + specifier: ^9.39.1 version: 9.39.1 eslint-plugin-vitest: specifier: ^0.5.4 - version: 0.5.4(eslint@9.39.1)(typescript@5.9.3)(vitest@2.1.9) + version: 0.5.4(eslint@9.39.1)(typescript@5.9.3)(vitest@4.0.14) prettier: - specifier: ^3.4.2 - version: 3.6.2 + specifier: ^3.7.3 + version: 3.7.3 typescript: - specifier: ^5.7.2 + specifier: ^5.9.3 version: 5.9.3 typescript-eslint: - specifier: ^8.19.1 - version: 8.47.0(eslint@9.39.1)(typescript@5.9.3) + specifier: ^8.48.0 + version: 8.48.0(eslint@9.39.1)(typescript@5.9.3) vite: - specifier: ^6.0.3 - version: 6.4.1(@types/node@22.19.1) + specifier: ^7.2.6 + version: 7.2.6(@types/node@24.10.1) vite-plugin-dts: - specifier: ^4.3.0 - version: 4.5.4(@types/node@22.19.1)(rollup@4.53.2)(typescript@5.9.3)(vite@6.4.1(@types/node@22.19.1)) + specifier: ^4.5.4 + version: 4.5.4(@types/node@24.10.1)(rollup@4.53.2)(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.1)) vitest: - specifier: ^2.1.8 - version: 2.1.9(@types/node@22.19.1)(@vitest/ui@2.1.9) + specifier: ^4.0.14 + version: 4.0.14(@types/node@24.10.1)(@vitest/ui@4.0.14) packages: - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -68,14 +67,9 @@ packages: resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} - '@bcoe/v8-coverage@0.2.3': - resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - - '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} '@esbuild/aix-ppc64@0.25.12': resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} @@ -83,192 +77,96 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.25.12': resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.25.12': resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.25.12': resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.25.12': resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.25.12': resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.25.12': resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.25.12': resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.25.12': resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.25.12': resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.25.12': resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.25.12': resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.25.12': resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.25.12': resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.25.12': resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.25.12': resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.25.12': resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} engines: {node: '>=18'} @@ -281,12 +179,6 @@ packages: cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.25.12': resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} engines: {node: '>=18'} @@ -299,12 +191,6 @@ packages: cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.25.12': resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} engines: {node: '>=18'} @@ -317,48 +203,24 @@ packages: cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.25.12': resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.25.12': resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.25.12': resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.25.12': resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} engines: {node: '>=18'} @@ -427,17 +289,6 @@ packages: resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} engines: {node: 20 || >=22} - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@istanbuljs/schema@0.1.3': - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} - engines: {node: '>=8'} - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -473,10 +324,6 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} @@ -629,35 +476,44 @@ packages: '@rushstack/ts-command-line@5.1.3': resolution: {integrity: sha512-Kdv0k/BnnxIYFlMVC1IxrIS0oGQd4T4b7vKfx52Y2+wk2WZSDFIvedr7JrhenzSlm3ou5KwtoTGTGd5nbODRug==} + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@types/argparse@1.0.38': resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@22.19.1': - resolution: {integrity: sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==} + '@types/node@24.10.1': + resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} - '@typescript-eslint/eslint-plugin@8.47.0': - resolution: {integrity: sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA==} + '@typescript-eslint/eslint-plugin@8.48.0': + resolution: {integrity: sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.47.0 + '@typescript-eslint/parser': ^8.48.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.47.0': - resolution: {integrity: sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==} + '@typescript-eslint/parser@8.48.0': + resolution: {integrity: sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.47.0': - resolution: {integrity: sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==} + '@typescript-eslint/project-service@8.48.0': + resolution: {integrity: sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' @@ -666,18 +522,18 @@ packages: resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/scope-manager@8.47.0': - resolution: {integrity: sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==} + '@typescript-eslint/scope-manager@8.48.0': + resolution: {integrity: sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.47.0': - resolution: {integrity: sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==} + '@typescript-eslint/tsconfig-utils@8.48.0': + resolution: {integrity: sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.47.0': - resolution: {integrity: sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A==} + '@typescript-eslint/type-utils@8.48.0': + resolution: {integrity: sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -687,8 +543,8 @@ packages: resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/types@8.47.0': - resolution: {integrity: sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A==} + '@typescript-eslint/types@8.48.0': + resolution: {integrity: sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript-eslint/typescript-estree@7.18.0': @@ -700,8 +556,8 @@ packages: typescript: optional: true - '@typescript-eslint/typescript-estree@8.47.0': - resolution: {integrity: sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg==} + '@typescript-eslint/typescript-estree@8.48.0': + resolution: {integrity: sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' @@ -712,8 +568,8 @@ packages: peerDependencies: eslint: ^8.56.0 - '@typescript-eslint/utils@8.47.0': - resolution: {integrity: sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==} + '@typescript-eslint/utils@8.48.0': + resolution: {integrity: sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -723,52 +579,52 @@ packages: resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/visitor-keys@8.47.0': - resolution: {integrity: sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==} + '@typescript-eslint/visitor-keys@8.48.0': + resolution: {integrity: sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vitest/coverage-v8@2.1.9': - resolution: {integrity: sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==} + '@vitest/coverage-v8@4.0.14': + resolution: {integrity: sha512-EYHLqN/BY6b47qHH7gtMxAg++saoGmsjWmAq9MlXxAz4M0NcHh9iOyKhBZyU4yxZqOd8Xnqp80/5saeitz4Cng==} peerDependencies: - '@vitest/browser': 2.1.9 - vitest: 2.1.9 + '@vitest/browser': 4.0.14 + vitest: 4.0.14 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@2.1.9': - resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + '@vitest/expect@4.0.14': + resolution: {integrity: sha512-RHk63V3zvRiYOWAV0rGEBRO820ce17hz7cI2kDmEdfQsBjT2luEKB5tCOc91u1oSQoUOZkSv3ZyzkdkSLD7lKw==} - '@vitest/mocker@2.1.9': - resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + '@vitest/mocker@4.0.14': + resolution: {integrity: sha512-RzS5NujlCzeRPF1MK7MXLiEFpkIXeMdQ+rN3Kk3tDI9j0mtbr7Nmuq67tpkOJQpgyClbOltCXMjLZicJHsH5Cg==} peerDependencies: msw: ^2.4.9 - vite: ^5.0.0 + vite: ^6.0.0 || ^7.0.0-0 peerDependenciesMeta: msw: optional: true vite: optional: true - '@vitest/pretty-format@2.1.9': - resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + '@vitest/pretty-format@4.0.14': + resolution: {integrity: sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ==} - '@vitest/runner@2.1.9': - resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + '@vitest/runner@4.0.14': + resolution: {integrity: sha512-BsAIk3FAqxICqREbX8SetIteT8PiaUL/tgJjmhxJhCsigmzzH8xeadtp7LRnTpCVzvf0ib9BgAfKJHuhNllKLw==} - '@vitest/snapshot@2.1.9': - resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + '@vitest/snapshot@4.0.14': + resolution: {integrity: sha512-aQVBfT1PMzDSA16Y3Fp45a0q8nKexx6N5Amw3MX55BeTeZpoC08fGqEZqVmPcqN0ueZsuUQ9rriPMhZ3Mu19Ag==} - '@vitest/spy@2.1.9': - resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + '@vitest/spy@4.0.14': + resolution: {integrity: sha512-JmAZT1UtZooO0tpY3GRyiC/8W7dCs05UOq9rfsUUgEZEdq+DuHLmWhPsrTt0TiW7WYeL/hXpaE07AZ2RCk44hg==} - '@vitest/ui@2.1.9': - resolution: {integrity: sha512-izzd2zmnk8Nl5ECYkW27328RbQ1nKvkm6Bb5DAaz1Gk59EbLkiCMa6OLT0NoaAYTjOFS6N+SMYW1nh4/9ljPiw==} + '@vitest/ui@4.0.14': + resolution: {integrity: sha512-fvDz8o7SQpFLoSBo6Cudv+fE85/fPCkwTnLAN85M+Jv7k59w2mSIjT9Q5px7XwGrmYqqKBEYxh/09IBGd1E7AQ==} peerDependencies: - vitest: 2.1.9 + vitest: 4.0.14 - '@vitest/utils@2.1.9': - resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + '@vitest/utils@4.0.14': + resolution: {integrity: sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw==} '@volar/language-core@2.4.23': resolution: {integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==} @@ -837,22 +693,10 @@ packages: alien-signals@0.4.14: resolution: {integrity: sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==} - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} - engines: {node: '>=12'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} - argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -867,6 +711,9 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + ast-v8-to-istanbul@0.3.8: + resolution: {integrity: sha512-szgSZqUxI5T8mLKvS7WTjF9is+MVbOeLADU73IseOcrqhxr/VAvy6wfoVE39KnKzA7JRhjF5eUagNlHwvZPlKQ==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -880,26 +727,18 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - chai@5.3.3: - resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + chai@6.2.1: + resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==} engines: {node: '>=18'} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - check-error@2.1.1: - resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} - engines: {node: '>= 16'} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -935,10 +774,6 @@ packages: supports-color: optional: true - deep-eql@5.0.2: - resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} - engines: {node: '>=6'} - deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -950,14 +785,9 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dotenv@17.2.3: + resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} + engines: {node: '>=12'} entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} @@ -966,11 +796,6 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - esbuild@0.21.5: - resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} - engines: {node: '>=12'} - hasBin: true - esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} @@ -1095,10 +920,6 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - fs-extra@11.3.2: resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} engines: {node: '>=14.14'} @@ -1119,10 +940,6 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@10.5.0: - resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} - hasBin: true - globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -1180,10 +997,6 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -1211,12 +1024,12 @@ packages: resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} engines: {node: '>=8'} - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jju@1.4.0: resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@4.1.1: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true @@ -1260,12 +1073,6 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - loupe@3.2.1: - resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} - - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -1273,8 +1080,8 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - magicast@0.3.5: - resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + magicast@0.5.1: + resolution: {integrity: sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==} make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} @@ -1299,10 +1106,6 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} - mlly@1.8.0: resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} @@ -1324,6 +1127,9 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1336,9 +1142,6 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -1357,24 +1160,13 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pathval@2.0.1: - resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} - engines: {node: '>= 14.16'} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1400,8 +1192,8 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier@3.6.2: - resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + prettier@3.7.3: + resolution: {integrity: sha512-QgODejq9K3OzoBbuyobZlUhznP5SKwPqp+6Q6xw6o8gnhr4O85L2U915iM2IDcfF2NPXVaM9zlo9tdwipnYwzg==} engines: {node: '>=14'} hasBin: true @@ -1461,10 +1253,6 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - sirv@3.0.2: resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} engines: {node: '>=18'} @@ -1494,22 +1282,6 @@ packages: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.1.2: - resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} - engines: {node: '>=12'} - strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -1526,10 +1298,6 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - test-exclude@7.0.1: - resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} - engines: {node: '>=18'} - tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -1540,16 +1308,8 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} - tinypool@1.1.1: - resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} - engines: {node: ^18.0.0 || >=20.0.0} - - tinyrainbow@1.2.0: - resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} - engines: {node: '>=14.0.0'} - - tinyspy@3.0.2: - resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} to-regex-range@5.0.1: @@ -1576,8 +1336,8 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - typescript-eslint@8.47.0: - resolution: {integrity: sha512-Lwe8i2XQ3WoMjua/r1PHrCTpkubPYJCAfOurtn+mtTzqB6jNd+14n9UN1bJ4s3F49x9ixAm0FLflB/JzQ57M8Q==} + typescript-eslint@8.48.0: + resolution: {integrity: sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -1596,8 +1356,8 @@ packages: ufo@1.6.1: resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} @@ -1606,11 +1366,6 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - vite-node@2.1.9: - resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - vite-plugin-dts@4.5.4: resolution: {integrity: sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==} peerDependencies: @@ -1620,50 +1375,19 @@ packages: vite: optional: true - vite@5.4.21: - resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} - engines: {node: ^18.0.0 || >=20.0.0} + vite@7.2.6: + resolution: {integrity: sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - - vite@6.4.1: - resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@types/node': ^20.19.0 || >=22.12.0 jiti: '>=1.21.0' - less: '*' + less: ^4.0.0 lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 terser: ^5.16.0 tsx: ^4.8.1 yaml: ^2.4.2 @@ -1691,23 +1415,32 @@ packages: yaml: optional: true - vitest@2.1.9: - resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} - engines: {node: ^18.0.0 || >=20.0.0} + vitest@4.0.14: + resolution: {integrity: sha512-d9B2J9Cm9dN9+6nxMnnNJKJCtcyKfnHj15N6YNJfaFHRLua/d3sRKU9RuKmO9mB0XdFtUizlxfz/VPbd3OxGhw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 2.1.9 - '@vitest/ui': 2.1.9 + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.14 + '@vitest/browser-preview': 4.0.14 + '@vitest/browser-webdriverio': 4.0.14 + '@vitest/ui': 4.0.14 happy-dom: '*' jsdom: '*' peerDependenciesMeta: '@edge-runtime/vm': optional: true + '@opentelemetry/api': + optional: true '@types/node': optional: true - '@vitest/browser': + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': optional: true '@vitest/ui': optional: true @@ -1733,14 +1466,6 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} @@ -1750,11 +1475,6 @@ packages: snapshots: - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.28.5': {} @@ -1768,152 +1488,83 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@bcoe/v8-coverage@0.2.3': {} - - '@esbuild/aix-ppc64@0.21.5': - optional: true + '@bcoe/v8-coverage@1.0.2': {} '@esbuild/aix-ppc64@0.25.12': optional: true - '@esbuild/android-arm64@0.21.5': - optional: true - '@esbuild/android-arm64@0.25.12': optional: true - '@esbuild/android-arm@0.21.5': - optional: true - '@esbuild/android-arm@0.25.12': optional: true - '@esbuild/android-x64@0.21.5': - optional: true - '@esbuild/android-x64@0.25.12': optional: true - '@esbuild/darwin-arm64@0.21.5': - optional: true - '@esbuild/darwin-arm64@0.25.12': optional: true - '@esbuild/darwin-x64@0.21.5': - optional: true - '@esbuild/darwin-x64@0.25.12': optional: true - '@esbuild/freebsd-arm64@0.21.5': - optional: true - '@esbuild/freebsd-arm64@0.25.12': optional: true - '@esbuild/freebsd-x64@0.21.5': - optional: true - '@esbuild/freebsd-x64@0.25.12': optional: true - '@esbuild/linux-arm64@0.21.5': - optional: true - '@esbuild/linux-arm64@0.25.12': optional: true - '@esbuild/linux-arm@0.21.5': - optional: true - '@esbuild/linux-arm@0.25.12': optional: true - '@esbuild/linux-ia32@0.21.5': - optional: true - '@esbuild/linux-ia32@0.25.12': optional: true - '@esbuild/linux-loong64@0.21.5': - optional: true - '@esbuild/linux-loong64@0.25.12': optional: true - '@esbuild/linux-mips64el@0.21.5': - optional: true - '@esbuild/linux-mips64el@0.25.12': optional: true - '@esbuild/linux-ppc64@0.21.5': - optional: true - '@esbuild/linux-ppc64@0.25.12': optional: true - '@esbuild/linux-riscv64@0.21.5': - optional: true - '@esbuild/linux-riscv64@0.25.12': optional: true - '@esbuild/linux-s390x@0.21.5': - optional: true - '@esbuild/linux-s390x@0.25.12': optional: true - '@esbuild/linux-x64@0.21.5': - optional: true - '@esbuild/linux-x64@0.25.12': optional: true '@esbuild/netbsd-arm64@0.25.12': optional: true - '@esbuild/netbsd-x64@0.21.5': - optional: true - '@esbuild/netbsd-x64@0.25.12': optional: true '@esbuild/openbsd-arm64@0.25.12': optional: true - '@esbuild/openbsd-x64@0.21.5': - optional: true - '@esbuild/openbsd-x64@0.25.12': optional: true '@esbuild/openharmony-arm64@0.25.12': optional: true - '@esbuild/sunos-x64@0.21.5': - optional: true - '@esbuild/sunos-x64@0.25.12': optional: true - '@esbuild/win32-arm64@0.21.5': - optional: true - '@esbuild/win32-arm64@0.25.12': optional: true - '@esbuild/win32-ia32@0.21.5': - optional: true - '@esbuild/win32-ia32@0.25.12': optional: true - '@esbuild/win32-x64@0.21.5': - optional: true - '@esbuild/win32-x64@0.25.12': optional: true @@ -1980,22 +1631,6 @@ snapshots: dependencies: '@isaacs/balanced-match': 4.0.1 - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.2 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - - '@istanbuljs/schema@0.1.3': {} - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/sourcemap-codec@1.5.5': {} @@ -2005,23 +1640,23 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@microsoft/api-extractor-model@7.32.0(@types/node@22.19.1)': + '@microsoft/api-extractor-model@7.32.0(@types/node@24.10.1)': dependencies: '@microsoft/tsdoc': 0.16.0 '@microsoft/tsdoc-config': 0.18.0 - '@rushstack/node-core-library': 5.18.0(@types/node@22.19.1) + '@rushstack/node-core-library': 5.18.0(@types/node@24.10.1) transitivePeerDependencies: - '@types/node' - '@microsoft/api-extractor@7.55.0(@types/node@22.19.1)': + '@microsoft/api-extractor@7.55.0(@types/node@24.10.1)': dependencies: - '@microsoft/api-extractor-model': 7.32.0(@types/node@22.19.1) + '@microsoft/api-extractor-model': 7.32.0(@types/node@24.10.1) '@microsoft/tsdoc': 0.16.0 '@microsoft/tsdoc-config': 0.18.0 - '@rushstack/node-core-library': 5.18.0(@types/node@22.19.1) + '@rushstack/node-core-library': 5.18.0(@types/node@24.10.1) '@rushstack/rig-package': 0.6.0 - '@rushstack/terminal': 0.19.3(@types/node@22.19.1) - '@rushstack/ts-command-line': 5.1.3(@types/node@22.19.1) + '@rushstack/terminal': 0.19.3(@types/node@24.10.1) + '@rushstack/ts-command-line': 5.1.3(@types/node@24.10.1) diff: 8.0.2 lodash: 4.17.21 minimatch: 10.0.3 @@ -2053,9 +1688,6 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 - '@pkgjs/parseargs@0.11.0': - optional: true - '@polka/url@1.0.0-next.29': {} '@rollup/pluginutils@5.3.0(rollup@4.53.2)': @@ -2132,7 +1764,7 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.53.2': optional: true - '@rushstack/node-core-library@5.18.0(@types/node@22.19.1)': + '@rushstack/node-core-library@5.18.0(@types/node@24.10.1)': dependencies: ajv: 8.13.0 ajv-draft-04: 1.0.0(ajv@8.13.0) @@ -2143,52 +1775,61 @@ snapshots: resolve: 1.22.11 semver: 7.5.4 optionalDependencies: - '@types/node': 22.19.1 + '@types/node': 24.10.1 - '@rushstack/problem-matcher@0.1.1(@types/node@22.19.1)': + '@rushstack/problem-matcher@0.1.1(@types/node@24.10.1)': optionalDependencies: - '@types/node': 22.19.1 + '@types/node': 24.10.1 '@rushstack/rig-package@0.6.0': dependencies: resolve: 1.22.11 strip-json-comments: 3.1.1 - '@rushstack/terminal@0.19.3(@types/node@22.19.1)': + '@rushstack/terminal@0.19.3(@types/node@24.10.1)': dependencies: - '@rushstack/node-core-library': 5.18.0(@types/node@22.19.1) - '@rushstack/problem-matcher': 0.1.1(@types/node@22.19.1) + '@rushstack/node-core-library': 5.18.0(@types/node@24.10.1) + '@rushstack/problem-matcher': 0.1.1(@types/node@24.10.1) supports-color: 8.1.1 optionalDependencies: - '@types/node': 22.19.1 + '@types/node': 24.10.1 - '@rushstack/ts-command-line@5.1.3(@types/node@22.19.1)': + '@rushstack/ts-command-line@5.1.3(@types/node@24.10.1)': dependencies: - '@rushstack/terminal': 0.19.3(@types/node@22.19.1) + '@rushstack/terminal': 0.19.3(@types/node@24.10.1) '@types/argparse': 1.0.38 argparse: 1.0.10 string-argv: 0.3.2 transitivePeerDependencies: - '@types/node' + '@standard-schema/spec@1.0.0': {} + '@types/argparse@1.0.38': {} + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + '@types/estree@1.0.8': {} '@types/json-schema@7.0.15': {} - '@types/node@22.19.1': + '@types/node@24.10.1': dependencies: - undici-types: 6.21.0 + undici-types: 7.16.0 - '@typescript-eslint/eslint-plugin@8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.47.0(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.47.0 - '@typescript-eslint/type-utils': 8.47.0(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/utils': 8.47.0(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.47.0 + '@typescript-eslint/parser': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/type-utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.48.0 eslint: 9.39.1 graphemer: 1.4.0 ignore: 7.0.5 @@ -2198,22 +1839,22 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/parser@8.48.0(eslint@9.39.1)(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.47.0 - '@typescript-eslint/types': 8.47.0 - '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.47.0 + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.48.0 debug: 4.4.3 eslint: 9.39.1 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.47.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.48.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.9.3) - '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.9.3) + '@typescript-eslint/types': 8.48.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: @@ -2224,20 +1865,20 @@ snapshots: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 - '@typescript-eslint/scope-manager@8.47.0': + '@typescript-eslint/scope-manager@8.48.0': dependencies: - '@typescript-eslint/types': 8.47.0 - '@typescript-eslint/visitor-keys': 8.47.0 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/visitor-keys': 8.48.0 - '@typescript-eslint/tsconfig-utils@8.47.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.48.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.47.0(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.48.0(eslint@9.39.1)(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.47.0 - '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.47.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.1 ts-api-utils: 2.1.0(typescript@5.9.3) @@ -2247,7 +1888,7 @@ snapshots: '@typescript-eslint/types@7.18.0': {} - '@typescript-eslint/types@8.47.0': {} + '@typescript-eslint/types@8.48.0': {} '@typescript-eslint/typescript-estree@7.18.0(typescript@5.9.3)': dependencies: @@ -2264,17 +1905,16 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.47.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.48.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.47.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.9.3) - '@typescript-eslint/types': 8.47.0 - '@typescript-eslint/visitor-keys': 8.47.0 + '@typescript-eslint/project-service': 8.48.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.9.3) + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/visitor-keys': 8.48.0 debug: 4.4.3 - fast-glob: 3.3.3 - is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.7.3 + tinyglobby: 0.2.15 ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -2291,12 +1931,12 @@ snapshots: - supports-color - typescript - '@typescript-eslint/utils@8.47.0(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/utils@8.48.0(eslint@9.39.1)(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) - '@typescript-eslint/scope-manager': 8.47.0 - '@typescript-eslint/types': 8.47.0 - '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) eslint: 9.39.1 typescript: 5.9.3 transitivePeerDependencies: @@ -2307,79 +1947,77 @@ snapshots: '@typescript-eslint/types': 7.18.0 eslint-visitor-keys: 3.4.3 - '@typescript-eslint/visitor-keys@8.47.0': + '@typescript-eslint/visitor-keys@8.48.0': dependencies: - '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/types': 8.48.0 eslint-visitor-keys: 4.2.1 - '@vitest/coverage-v8@2.1.9(vitest@2.1.9)': + '@vitest/coverage-v8@4.0.14(vitest@4.0.14)': dependencies: - '@ampproject/remapping': 2.3.0 - '@bcoe/v8-coverage': 0.2.3 - debug: 4.4.3 + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.0.14 + ast-v8-to-istanbul: 0.3.8 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 istanbul-reports: 3.2.0 - magic-string: 0.30.21 - magicast: 0.3.5 + magicast: 0.5.1 + obug: 2.1.1 std-env: 3.10.0 - test-exclude: 7.0.1 - tinyrainbow: 1.2.0 - vitest: 2.1.9(@types/node@22.19.1)(@vitest/ui@2.1.9) + tinyrainbow: 3.0.3 + vitest: 4.0.14(@types/node@24.10.1)(@vitest/ui@4.0.14) transitivePeerDependencies: - supports-color - '@vitest/expect@2.1.9': + '@vitest/expect@4.0.14': dependencies: - '@vitest/spy': 2.1.9 - '@vitest/utils': 2.1.9 - chai: 5.3.3 - tinyrainbow: 1.2.0 + '@standard-schema/spec': 1.0.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.14 + '@vitest/utils': 4.0.14 + chai: 6.2.1 + tinyrainbow: 3.0.3 - '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@22.19.1))': + '@vitest/mocker@4.0.14(vite@7.2.6(@types/node@24.10.1))': dependencies: - '@vitest/spy': 2.1.9 + '@vitest/spy': 4.0.14 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 5.4.21(@types/node@22.19.1) + vite: 7.2.6(@types/node@24.10.1) - '@vitest/pretty-format@2.1.9': + '@vitest/pretty-format@4.0.14': dependencies: - tinyrainbow: 1.2.0 + tinyrainbow: 3.0.3 - '@vitest/runner@2.1.9': + '@vitest/runner@4.0.14': dependencies: - '@vitest/utils': 2.1.9 - pathe: 1.1.2 + '@vitest/utils': 4.0.14 + pathe: 2.0.3 - '@vitest/snapshot@2.1.9': + '@vitest/snapshot@4.0.14': dependencies: - '@vitest/pretty-format': 2.1.9 + '@vitest/pretty-format': 4.0.14 magic-string: 0.30.21 - pathe: 1.1.2 + pathe: 2.0.3 - '@vitest/spy@2.1.9': - dependencies: - tinyspy: 3.0.2 + '@vitest/spy@4.0.14': {} - '@vitest/ui@2.1.9(vitest@2.1.9)': + '@vitest/ui@4.0.14(vitest@4.0.14)': dependencies: - '@vitest/utils': 2.1.9 + '@vitest/utils': 4.0.14 fflate: 0.8.2 flatted: 3.3.3 - pathe: 1.1.2 + pathe: 2.0.3 sirv: 3.0.2 tinyglobby: 0.2.15 - tinyrainbow: 1.2.0 - vitest: 2.1.9(@types/node@22.19.1)(@vitest/ui@2.1.9) + tinyrainbow: 3.0.3 + vitest: 4.0.14(@types/node@24.10.1)(@vitest/ui@4.0.14) - '@vitest/utils@2.1.9': + '@vitest/utils@4.0.14': dependencies: - '@vitest/pretty-format': 2.1.9 - loupe: 3.2.1 - tinyrainbow: 1.2.0 + '@vitest/pretty-format': 4.0.14 + tinyrainbow: 3.0.3 '@volar/language-core@2.4.23': dependencies: @@ -2463,16 +2101,10 @@ snapshots: alien-signals@0.4.14: {} - ansi-regex@5.0.1: {} - - ansi-regex@6.2.2: {} - ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 - ansi-styles@6.2.3: {} - argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -2483,6 +2115,12 @@ snapshots: assertion-error@2.0.1: {} + ast-v8-to-istanbul@0.3.8: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 9.0.1 + balanced-match@1.0.2: {} brace-expansion@1.1.12: @@ -2498,25 +2136,15 @@ snapshots: dependencies: fill-range: 7.1.1 - cac@6.7.14: {} - callsites@3.1.0: {} - chai@5.3.3: - dependencies: - assertion-error: 2.0.1 - check-error: 2.1.1 - deep-eql: 5.0.2 - loupe: 3.2.1 - pathval: 2.0.1 + chai@6.2.1: {} chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - check-error@2.1.1: {} - color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -2543,8 +2171,6 @@ snapshots: dependencies: ms: 2.1.3 - deep-eql@5.0.2: {} - deep-is@0.1.4: {} diff@8.0.2: {} @@ -2553,42 +2179,12 @@ snapshots: dependencies: path-type: 4.0.0 - eastasianwidth@0.2.0: {} - - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} + dotenv@17.2.3: {} entities@4.5.0: {} es-module-lexer@1.7.0: {} - esbuild@0.21.5: - optionalDependencies: - '@esbuild/aix-ppc64': 0.21.5 - '@esbuild/android-arm': 0.21.5 - '@esbuild/android-arm64': 0.21.5 - '@esbuild/android-x64': 0.21.5 - '@esbuild/darwin-arm64': 0.21.5 - '@esbuild/darwin-x64': 0.21.5 - '@esbuild/freebsd-arm64': 0.21.5 - '@esbuild/freebsd-x64': 0.21.5 - '@esbuild/linux-arm': 0.21.5 - '@esbuild/linux-arm64': 0.21.5 - '@esbuild/linux-ia32': 0.21.5 - '@esbuild/linux-loong64': 0.21.5 - '@esbuild/linux-mips64el': 0.21.5 - '@esbuild/linux-ppc64': 0.21.5 - '@esbuild/linux-riscv64': 0.21.5 - '@esbuild/linux-s390x': 0.21.5 - '@esbuild/linux-x64': 0.21.5 - '@esbuild/netbsd-x64': 0.21.5 - '@esbuild/openbsd-x64': 0.21.5 - '@esbuild/sunos-x64': 0.21.5 - '@esbuild/win32-arm64': 0.21.5 - '@esbuild/win32-ia32': 0.21.5 - '@esbuild/win32-x64': 0.21.5 - esbuild@0.25.12: optionalDependencies: '@esbuild/aix-ppc64': 0.25.12 @@ -2620,12 +2216,12 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-vitest@0.5.4(eslint@9.39.1)(typescript@5.9.3)(vitest@2.1.9): + eslint-plugin-vitest@0.5.4(eslint@9.39.1)(typescript@5.9.3)(vitest@4.0.14): dependencies: '@typescript-eslint/utils': 7.18.0(eslint@9.39.1)(typescript@5.9.3) eslint: 9.39.1 optionalDependencies: - vitest: 2.1.9(@types/node@22.19.1)(@vitest/ui@2.1.9) + vitest: 4.0.14(@types/node@24.10.1)(@vitest/ui@4.0.14) transitivePeerDependencies: - supports-color - typescript @@ -2750,11 +2346,6 @@ snapshots: flatted@3.3.3: {} - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - fs-extra@11.3.2: dependencies: graceful-fs: 4.2.11 @@ -2774,15 +2365,6 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@10.5.0: - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - globals@14.0.0: {} globby@11.1.0: @@ -2827,8 +2409,6 @@ snapshots: is-extglob@2.1.1: {} - is-fullwidth-code-point@3.0.0: {} - is-glob@4.0.3: dependencies: is-extglob: 2.1.1 @@ -2858,14 +2438,10 @@ snapshots: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - jju@1.4.0: {} + js-tokens@9.0.1: {} + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -2909,10 +2485,6 @@ snapshots: lodash@4.17.21: {} - loupe@3.2.1: {} - - lru-cache@10.4.3: {} - lru-cache@6.0.0: dependencies: yallist: 4.0.0 @@ -2921,7 +2493,7 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - magicast@0.3.5: + magicast@0.5.1: dependencies: '@babel/parser': 7.28.5 '@babel/types': 7.28.5 @@ -2950,8 +2522,6 @@ snapshots: dependencies: brace-expansion: 2.0.2 - minipass@7.1.2: {} - mlly@1.8.0: dependencies: acorn: 8.15.0 @@ -2969,6 +2539,8 @@ snapshots: natural-compare@1.4.0: {} + obug@2.1.1: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -2986,8 +2558,6 @@ snapshots: dependencies: p-limit: 3.1.0 - package-json-from-dist@1.0.1: {} - parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -3000,19 +2570,10 @@ snapshots: path-parse@1.0.7: {} - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.2 - path-type@4.0.0: {} - pathe@1.1.2: {} - pathe@2.0.3: {} - pathval@2.0.1: {} - picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -3039,7 +2600,7 @@ snapshots: prelude-ls@1.2.1: {} - prettier@3.6.2: {} + prettier@3.7.3: {} punycode@2.3.1: {} @@ -3105,8 +2666,6 @@ snapshots: siginfo@2.0.0: {} - signal-exit@4.1.0: {} - sirv@3.0.2: dependencies: '@polka/url': 1.0.0-next.29 @@ -3127,26 +2686,6 @@ snapshots: string-argv@0.3.2: {} - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.2 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-ansi@7.1.2: - dependencies: - ansi-regex: 6.2.2 - strip-json-comments@3.1.1: {} supports-color@7.2.0: @@ -3159,12 +2698,6 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - test-exclude@7.0.1: - dependencies: - '@istanbuljs/schema': 0.1.3 - glob: 10.5.0 - minimatch: 9.0.5 - tinybench@2.9.0: {} tinyexec@0.3.2: {} @@ -3174,11 +2707,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 - tinypool@1.1.1: {} - - tinyrainbow@1.2.0: {} - - tinyspy@3.0.2: {} + tinyrainbow@3.0.3: {} to-regex-range@5.0.1: dependencies: @@ -3198,12 +2727,12 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.47.0(eslint@9.39.1)(typescript@5.9.3): + typescript-eslint@8.48.0(eslint@9.39.1)(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/parser': 8.47.0(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.47.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3) eslint: 9.39.1 typescript: 5.9.3 transitivePeerDependencies: @@ -3215,7 +2744,7 @@ snapshots: ufo@1.6.1: {} - undici-types@6.21.0: {} + undici-types@7.16.0: {} universalify@2.0.1: {} @@ -3223,27 +2752,9 @@ snapshots: dependencies: punycode: 2.3.1 - vite-node@2.1.9(@types/node@22.19.1): - dependencies: - cac: 6.7.14 - debug: 4.4.3 - es-module-lexer: 1.7.0 - pathe: 1.1.2 - vite: 5.4.21(@types/node@22.19.1) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - vite-plugin-dts@4.5.4(@types/node@22.19.1)(rollup@4.53.2)(typescript@5.9.3)(vite@6.4.1(@types/node@22.19.1)): + vite-plugin-dts@4.5.4(@types/node@24.10.1)(rollup@4.53.2)(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.1)): dependencies: - '@microsoft/api-extractor': 7.55.0(@types/node@22.19.1) + '@microsoft/api-extractor': 7.55.0(@types/node@24.10.1) '@rollup/pluginutils': 5.3.0(rollup@4.53.2) '@volar/typescript': 2.4.23 '@vue/language-core': 2.2.0(typescript@5.9.3) @@ -3254,22 +2765,13 @@ snapshots: magic-string: 0.30.21 typescript: 5.9.3 optionalDependencies: - vite: 6.4.1(@types/node@22.19.1) + vite: 7.2.6(@types/node@24.10.1) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite@5.4.21(@types/node@22.19.1): - dependencies: - esbuild: 0.21.5 - postcss: 8.5.6 - rollup: 4.53.2 - optionalDependencies: - '@types/node': 22.19.1 - fsevents: 2.3.3 - - vite@6.4.1(@types/node@22.19.1): + vite@7.2.6(@types/node@24.10.1): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -3278,35 +2780,36 @@ snapshots: rollup: 4.53.2 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 22.19.1 + '@types/node': 24.10.1 fsevents: 2.3.3 - vitest@2.1.9(@types/node@22.19.1)(@vitest/ui@2.1.9): + vitest@4.0.14(@types/node@24.10.1)(@vitest/ui@4.0.14): dependencies: - '@vitest/expect': 2.1.9 - '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@22.19.1)) - '@vitest/pretty-format': 2.1.9 - '@vitest/runner': 2.1.9 - '@vitest/snapshot': 2.1.9 - '@vitest/spy': 2.1.9 - '@vitest/utils': 2.1.9 - chai: 5.3.3 - debug: 4.4.3 + '@vitest/expect': 4.0.14 + '@vitest/mocker': 4.0.14(vite@7.2.6(@types/node@24.10.1)) + '@vitest/pretty-format': 4.0.14 + '@vitest/runner': 4.0.14 + '@vitest/snapshot': 4.0.14 + '@vitest/spy': 4.0.14 + '@vitest/utils': 4.0.14 + es-module-lexer: 1.7.0 expect-type: 1.2.2 magic-string: 0.30.21 - pathe: 1.1.2 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 std-env: 3.10.0 tinybench: 2.9.0 tinyexec: 0.3.2 - tinypool: 1.1.1 - tinyrainbow: 1.2.0 - vite: 5.4.21(@types/node@22.19.1) - vite-node: 2.1.9(@types/node@22.19.1) + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.2.6(@types/node@24.10.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.19.1 - '@vitest/ui': 2.1.9(vitest@2.1.9) + '@types/node': 24.10.1 + '@vitest/ui': 4.0.14(vitest@4.0.14) transitivePeerDependencies: + - jiti - less - lightningcss - msw @@ -3314,8 +2817,9 @@ snapshots: - sass-embedded - stylus - sugarss - - supports-color - terser + - tsx + - yaml vscode-uri@3.1.0: {} @@ -3330,18 +2834,6 @@ snapshots: word-wrap@1.2.5: {} - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 5.1.2 - strip-ansi: 7.1.2 - yallist@4.0.0: {} yocto-queue@0.1.0: {} diff --git a/src/Payment.ts b/src/Payment.ts index cede864..95f3a3b 100644 --- a/src/Payment.ts +++ b/src/Payment.ts @@ -59,10 +59,10 @@ export class Payment { * * @example * ```typescript - * // Create a MATCH_CODE payment + * // Create a payment using amount in euros (recommended) * const payment = await Payment.create({ * flow: 'MATCH_CODE', - * amount_unit: 1000, // 10.00 EUR in cents + * amount: 10.50, // 10.50 EUR (automatically converted to cents) * currency: 'EUR', * external_code: 'ORDER-123', * metadata: { @@ -71,6 +71,13 @@ export class Payment { * }, * }) * + * // Or using amount_unit in cents + * const payment2 = await Payment.create({ + * flow: 'MATCH_CODE', + * amount_unit: 1050, // 10.50 EUR in cents + * currency: 'EUR', + * }) + * * console.log(`Payment code: ${payment.code_identifier}`) * ``` */ @@ -78,9 +85,16 @@ export class Payment { body: PaymentCreateBody, headers: Record = {} ): Promise { + // Convert amount (euros) to amount_unit (cents) if provided + const processedBody: any = { ...body } + if ('amount' in body && body.amount !== undefined) { + processedBody.amount_unit = Math.round(body.amount * 100) + delete processedBody.amount + } + return Request.post(this.apiPath, { headers, - body, + body: processedBody, sign: true, }) } @@ -99,17 +113,53 @@ export class Payment { /** * Get the payments list + * * @param query Query parameters (optional) * @param headers Custom headers (optional) + * + * @example + * ```typescript + * // List all payments + * const payments = await Payment.all({ limit: 20 }) + * + * // Filter by date using Date object (recommended) + * const yesterday = new Date() + * yesterday.setDate(yesterday.getDate() - 1) + * const filtered = await Payment.all({ + * starting_after_timestamp: yesterday, + * limit: 10 + * }) + * + * // Or using timestamp string in milliseconds + * const filteredByString = await Payment.all({ + * starting_after_timestamp: yesterday.getTime().toString(), + * limit: 10 + * }) + * + * // Pagination using payment ID + * const nextPage = await Payment.all({ + * starting_after: 'last-payment-id', + * limit: 20 + * }) + * ``` + * + * @note The `starting_after_timestamp` parameter accepts both Date objects and timestamp strings in milliseconds. + * Date objects are automatically converted to milliseconds timestamp. */ static async all( query: PaymentQueryParams = {}, headers: Record = {} - ): Promise<{ list: PaymentResponse[]; has_more: boolean }> { + ): Promise<{ data: PaymentResponse[]; has_more: boolean }> { let path = this.apiPath if (Object.keys(query).length > 0) { - const queryString = new URLSearchParams(query as unknown as Record).toString() + // Convert Date object to timestamp string if necessary + const processedQuery = { ...query } + if (processedQuery.starting_after_timestamp instanceof Date) { + processedQuery.starting_after_timestamp = processedQuery.starting_after_timestamp.getTime().toString() + } + + const queryString = new URLSearchParams(processedQuery as unknown as Record).toString() path += `?${queryString}` } @@ -121,18 +171,41 @@ export class Payment { /** * Update a payment + * * @param id Payment ID - * @param body Update data + * @param body Update data (action and optionally amount or amount_unit) * @param headers Custom headers (optional) + * + * @example + * ```typescript + * // Update using amount in euros + * const updated = await Payment.update('payment-id', { + * action: 'ACCEPT', + * amount: 5.50, // Automatically converted to 550 cents + * }) + * + * // Or using amount_unit in cents + * const updated2 = await Payment.update('payment-id', { + * action: 'ACCEPT', + * amount_unit: 550, + * }) + * ``` */ static async update( id: string, body: Partial, headers: Record = {} ): Promise { + // Convert amount (euros) to amount_unit (cents) if provided + const processedBody: any = { ...body } + if ('amount' in body && body.amount !== undefined) { + processedBody.amount_unit = Math.round(body.amount * 100) + delete processedBody.amount + } + return Request.put(`${this.apiPath}/${id}`, { headers, - body, + body: processedBody, sign: true, }) } diff --git a/src/Report.ts b/src/Report.ts new file mode 100644 index 0000000..0277d74 --- /dev/null +++ b/src/Report.ts @@ -0,0 +1,142 @@ +import { Request } from './Request.js' +import type { + ReportCreateBody, + ReportResponse, + ReportListQueryParams, +} from './types.js' + +/** + * Report class for managing Satispay payment reports + * + * @remarks + * The Report API requires special authentication keys that are different from + * standard API keys. Contact tech@satispay.com to enable report access. + * + * Reports are extracted at merchant level and include all shops under the merchant. + * Reports for the previous day should be generated at least 4 hours after midnight + * to ensure complete data. + * + * @example + * ```typescript + * import { Report } from '@volverjs/satispay-node-sdk'; + * + * // Create a new report + * const report = await Report.create({ + * type: 'PAYMENT_FEE', + * format: 'CSV', + * from_date: '2025-11-01', + * to_date: '2025-11-30' + * }); + * + * // Get list of reports + * const reports = await Report.all(); + * + * // Get a specific report + * const reportDetails = await Report.get('report-id-123'); + * ``` + */ +export class Report { + /** + * Create a new report + * + * @param body - Report creation parameters + * @param headers - Optional custom headers + * @returns Promise resolving to the created report + * + * @example + * ```typescript + * const report = await Report.create({ + * type: 'PAYMENT_FEE', + * format: 'CSV', + * from_date: '2025-11-01', + * to_date: '2025-11-30', + * columns: ['transaction_id', 'transaction_date', 'total_amount'] + * }); + * ``` + */ + static async create( + body: ReportCreateBody, + headers: Record = {}, + ): Promise { + return await Request.post('/g_business/v1/reports', { + headers, + body, + sign: true, + }) + } + + /** + * Retrieve a list of previously created reports + * + * @param query - Optional query parameters for pagination + * @param headers - Optional custom headers + * @returns Promise resolving to an object containing list of reports + * + * @example + * ```typescript + * // Get all reports + * const result = await Report.all(); + * + * // Get reports with pagination + * const result = await Report.all({ + * limit: 10, + * starting_after: 'report-123' + * }); + * ``` + */ + static async all( + query: ReportListQueryParams = {}, + headers: Record = {}, + ): Promise<{ list: ReportResponse[]; has_more: boolean }> { + const queryParams = new URLSearchParams() + + if (query.limit) { + queryParams.append('limit', query.limit.toString()) + } + if (query.starting_after) { + queryParams.append('starting_after', query.starting_after) + } + + const queryString = queryParams.toString() + const url = queryString + ? `/g_business/v1/reports?${queryString}` + : '/g_business/v1/reports' + + return await Request.get<{ list: ReportResponse[]; has_more: boolean }>( + url, + { + headers, + sign: true, + }, + ) + } + + /** + * Retrieve details of a specific report + * + * @param id - The report ID + * @param headers - Optional custom headers + * @returns Promise resolving to the report details + * + * @example + * ```typescript + * const report = await Report.get('report-id-123'); + * + * if (report.status === 'READY' && report.download_url) { + * console.log('Download URL:', report.download_url); + * } + * ``` + */ + static async get( + id: string, + headers: Record = {}, + ): Promise { + return await Request.get( + `/g_business/v1/reports/${id}`, + { + headers, + sign: true, + }, + ) + } +} diff --git a/src/Request.ts b/src/Request.ts index 66f9429..297f088 100644 --- a/src/Request.ts +++ b/src/Request.ts @@ -191,11 +191,11 @@ export class Request { fetchOptions.body = body } - // Disable SSL verification in test mode (Node.js only) + // Disable SSL verification in staging mode (Node.js only) // Note: Deno and Bun don't support this directly via fetch - if (Api.getEnv() === 'test' && typeof process !== 'undefined' && process.versions?.node) { + if (Api.getEnv() === 'staging' && typeof process !== 'undefined' && process.versions?.node) { // For Node.js 18+ with fetch, we need to use a custom agent - // This is a workaround for test environments only + // This is a workaround for staging/test environments only const https = await import('https') const agent = new https.Agent({ rejectUnauthorized: false, diff --git a/src/Session.ts b/src/Session.ts new file mode 100644 index 0000000..6dd918e --- /dev/null +++ b/src/Session.ts @@ -0,0 +1,171 @@ +import { Request } from './Request.js' +import type { + SessionCreateBody, + SessionResponse, + SessionUpdateBody, + SessionEventCreateBody, +} from './types.js' + +/** + * Session class for managing Satispay POS sessions + * + * @remarks + * Sessions are used for POS/device integration to manage fund lock payments + * across multiple transactions. A session allows charging a fund lock incrementally + * through multiple events. + * + * @example + * ```typescript + * import { Session } from '@volverjs/satispay-node-sdk'; + * + * // Open a session from a fund lock + * const session = await Session.open({ + * fund_lock_id: 'payment-123' + * }); + * + * // Add items to the session + * await Session.createEvent(session.id, { + * type: 'ADD_ITEM', + * amount_unit: 500, + * description: 'Product A' + * }); + * + * // Get session details + * const details = await Session.get(session.id); + * + * // Close the session + * await Session.update(session.id, { status: 'CLOSE' }); + * ``` + */ +export class Session { + /** + * Open a new session from a fund lock payment + * + * @param body - Session creation parameters containing the fund lock ID + * @param headers - Optional custom headers + * @returns Promise resolving to the created session + * + * @example + * ```typescript + * const session = await Session.open({ + * fund_lock_id: 'payment-fund-lock-123' + * }); + * console.log('Session ID:', session.id); + * console.log('Residual amount:', session.residual_amount_unit); + * ``` + */ + static async open( + body: SessionCreateBody, + headers: Record = {}, + ): Promise { + return await Request.post('/g_business/v1/sessions', { + headers, + body, + sign: true, + }) + } + + /** + * Retrieve details of a specific session + * + * @param id - The session ID + * @param headers - Optional custom headers + * @returns Promise resolving to the session details + * + * @example + * ```typescript + * const session = await Session.get('session-123'); + * console.log('Status:', session.status); + * console.log('Residual amount:', session.residual_amount_unit); + * ``` + */ + static async get( + id: string, + headers: Record = {}, + ): Promise { + return await Request.get( + `/g_business/v1/sessions/${id}`, + { + headers, + sign: true, + }, + ) + } + + /** + * Update a session (typically to close it) + * + * @param id - The session ID + * @param body - Session update parameters + * @param headers - Optional custom headers + * @returns Promise resolving to the updated session + * + * @example + * ```typescript + * // Close a session + * const closedSession = await Session.update('session-123', { + * status: 'CLOSE' + * }); + * console.log('Final status:', closedSession.status); + * ``` + */ + static async update( + id: string, + body: SessionUpdateBody, + headers: Record = {}, + ): Promise { + return await Request.patch( + `/g_business/v1/sessions/${id}`, + { + headers, + body, + sign: true, + }, + ) + } + + /** + * Create an event within a session + * + * @remarks + * Events are used to add, remove, or update items in a POS session. + * Each event can modify the total amount being charged from the fund lock. + * + * @param id - The session ID + * @param body - Event creation parameters + * @param headers - Optional custom headers + * @returns Promise resolving to the updated session after the event + * + * @example + * ```typescript + * // Add an item to the session + * await Session.createEvent('session-123', { + * type: 'ADD_ITEM', + * amount_unit: 1000, + * description: 'Coffee', + * metadata: { sku: 'COFFEE-001' } + * }); + * + * // Remove an item + * await Session.createEvent('session-123', { + * type: 'REMOVE_ITEM', + * amount_unit: 500, + * description: 'Discount applied' + * }); + * ``` + */ + static async createEvent( + id: string, + body: SessionEventCreateBody, + headers: Record = {}, + ): Promise { + return await Request.post( + `/g_business/v1/sessions/${id}/events`, + { + headers, + body, + sign: true, + }, + ) + } +} diff --git a/src/index.ts b/src/index.ts index a10c646..9359a3f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,6 +25,8 @@ export { Payment } from './Payment.js' export { Consumer } from './Consumer.js' export { DailyClosure } from './DailyClosure.js' export { PreAuthorizedPaymentToken } from './PreAuthorizedPaymentToken.js' +export { Report } from './Report.js' +export { Session } from './Session.js' export { Request } from './Request.js' export { RSAServiceFactory } from './RSAService/RSAServiceFactory.js' diff --git a/src/types.ts b/src/types.ts index 9f4dd96..49b7959 100644 --- a/src/types.ts +++ b/src/types.ts @@ -23,7 +23,7 @@ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' /** * Environment */ -export type Environment = 'production' | 'staging' | 'test' +export type Environment = 'production' | 'staging' /** * Payment status @@ -42,6 +42,31 @@ export type PaymentFlow = | 'PRE_AUTHORIZED_FUND_LOCK' | 'HOTP_AUTH' +/** + * Report type + */ +export type ReportType = 'PAYMENT_FEE' + +/** + * Report format type + */ +export type ReportFormatType = 'CSV' | 'PDF' | 'XLSX' + +/** + * Report status + */ +export type ReportStatus = 'PENDING' | 'READY' | 'FAILED' + +/** + * Session status + */ +export type SessionStatus = 'OPEN' | 'CLOSE' + +/** + * Session event type + */ +export type SessionEventType = 'ADD_ITEM' | 'REMOVE_ITEM' | 'UPDATE_TOTAL' + /** * Payment action */ @@ -65,10 +90,14 @@ export type PreAuthorizedTokenStatus = 'PENDING' | 'ACCEPTED' | 'CANCELED' /** * Payment creation body + * + * @property amount - Amount in euros (e.g., 10.50). Will be automatically converted to amount_unit. + * @property amount_unit - Amount in cents (e.g., 1050). Use either 'amount' or 'amount_unit', not both. + * @property meal_voucher_max_amount_unit - Maximum amount in cents that can be paid with meal vouchers + * @property meal_voucher_max_quantity - Maximum number of meal vouchers that can be used */ export type PaymentCreateBody = { flow: PaymentFlow - amount_unit: number currency: string callback_url?: string external_code?: string @@ -77,7 +106,12 @@ export type PaymentCreateBody = { consumer_uid?: string required_success_email?: string pre_authorized_payments_token?: string + meal_voucher_max_amount_unit?: number + meal_voucher_max_quantity?: number } & ( + | { amount: number; amount_unit?: never } + | { amount?: never; amount_unit: number } +) & ( | { flow: 'HOTP_AUTH' | 'PRE_AUTHORIZED' | 'PRE_AUTHORIZED_FUND_LOCK'; token: string } | { flow: 'REFUND'; parent_payment_uid: string } | { flow: 'MATCH_USER'; consumer_uid: string } @@ -86,11 +120,21 @@ export type PaymentCreateBody = { /** * Payment update body + * + * @property amount - Amount in euros (e.g., 10.50). Will be automatically converted to amount_unit. + * @property amount_unit - Amount in cents (e.g., 1050). Use either 'amount' or 'amount_unit', not both. + * @property meal_voucher_max_amount_unit - Maximum amount in cents that can be paid with meal vouchers + * @property meal_voucher_max_quantity - Maximum number of meal vouchers that can be used */ export type PaymentUpdateBody = { action: PaymentAction - amount_unit?: number -} + meal_voucher_max_amount_unit?: number + meal_voucher_max_quantity?: number +} & ( + | { amount?: number; amount_unit?: never } + | { amount?: never; amount_unit?: number } + | { amount?: never; amount_unit?: never } +) /** * Pre-authorized payment token creation body @@ -176,7 +220,7 @@ export type PreAuthorizedPaymentResponse = { export type PaymentQueryParams = { limit?: number starting_after?: string - starting_after_timestamp?: string + starting_after_timestamp?: string | Date consumer_uid?: string payment_type?: string status?: PaymentStatus @@ -192,3 +236,76 @@ export type DailyClosureQueryParams = { limit?: number starting_after?: string } + +/** + * Report creation body + */ +export type ReportCreateBody = { + type: ReportType + format?: ReportFormatType + from_date: string + to_date: string + columns?: string[] +} + +/** + * Report response + */ +export type ReportResponse = { + id: string + type: ReportType + format: ReportFormatType + status: ReportStatus + from_date: string + to_date: string + download_url?: string + created_at: string + updated_at: string +} + +/** + * Report list query parameters + */ +export type ReportListQueryParams = { + limit?: number + starting_after?: string +} + +/** + * Session creation body + */ +export type SessionCreateBody = { + fund_lock_id: string +} + +/** + * Session response + */ +export type SessionResponse = { + id: string + amount_unit: number + residual_amount_unit: number + currency: string + status: SessionStatus + type: string + consumer_uid?: string + available?: boolean + expiration_date?: string +} + +/** + * Session update body + */ +export type SessionUpdateBody = { + status: SessionStatus +} + +/** + * Session event creation body + */ +export type SessionEventCreateBody = { + type: SessionEventType + amount_unit?: number + description?: string + metadata?: Record +} diff --git a/tests/Api.test.ts b/tests/Api.test.ts index 3eeb2e5..bcfc3b8 100644 --- a/tests/Api.test.ts +++ b/tests/Api.test.ts @@ -14,7 +14,7 @@ describe('Api', () => { }) it('should set and get environment correctly', () => { - const environments: Environment[] = ['production', 'staging', 'test'] + const environments: Environment[] = ['production', 'staging'] environments.forEach((env) => { Api.setEnv(env) @@ -90,4 +90,43 @@ describe('Api', () => { expect(version).toMatch(/^\d+\.\d+\.\d+$/) }) }) + + describe('sandbox management', () => { + it('should check if sandbox is enabled', () => { + Api.setEnv('staging') + expect(Api.getSandbox()).toBe(true) + + Api.setEnv('production') + expect(Api.getSandbox()).toBe(false) + }) + + it('should enable sandbox', () => { + Api.setSandbox(true) + expect(Api.getEnv()).toBe('staging') + expect(Api.getSandbox()).toBe(true) + }) + + it('should disable sandbox', () => { + Api.setSandbox(false) + expect(Api.getEnv()).toBe('production') + expect(Api.getSandbox()).toBe(false) + }) + + it('should enable sandbox by default', () => { + Api.setSandbox() + expect(Api.getEnv()).toBe('staging') + }) + }) + + describe('getAuthservicesUrl', () => { + it('should return production URL', () => { + Api.setEnv('production') + expect(Api.getAuthservicesUrl()).toBe('https://authservices.satispay.com') + }) + + it('should return staging URL', () => { + Api.setEnv('staging') + expect(Api.getAuthservicesUrl()).toBe('https://staging.authservices.satispay.com') + }) + }) }) diff --git a/tests/Payment.test.ts b/tests/Payment.test.ts index 805ed60..15589da 100644 --- a/tests/Payment.test.ts +++ b/tests/Payment.test.ts @@ -63,6 +63,49 @@ describe('Payment', () => { expect(result).toEqual(mockPaymentResponse) }) + it('should create a payment using amount in euros', async () => { + const mockBody: PaymentCreateBody = { + flow: 'MATCH_CODE', + amount: 10.50, // 10.50 euros + currency: 'EUR', + } + + vi.mocked(Request.post).mockResolvedValue(mockPaymentResponse) + + const result = await Payment.create(mockBody) + + expect(Request.post).toHaveBeenCalledWith('/g_business/v1/payments', { + headers: {}, + body: { + flow: 'MATCH_CODE', + amount_unit: 1050, // Converted to cents + currency: 'EUR', + }, + sign: true, + }) + expect(result).toEqual(mockPaymentResponse) + }) + + it('should handle decimal amounts correctly', async () => { + const mockBody: PaymentCreateBody = { + flow: 'MATCH_CODE', + amount: 99.99, + currency: 'EUR', + } + + vi.mocked(Request.post).mockResolvedValue(mockPaymentResponse) + + await Payment.create(mockBody) + + expect(Request.post).toHaveBeenCalledWith('/g_business/v1/payments', + expect.objectContaining({ + body: expect.objectContaining({ + amount_unit: 9999, + }), + }) + ) + }) + it('should create a payment with custom headers', async () => { const mockBody: PaymentCreateBody = { flow: 'MATCH_CODE', @@ -180,6 +223,43 @@ describe('Payment', () => { sign: true, }) }) + + it('should convert Date object to timestamp in milliseconds', async () => { + const testDate = new Date('2024-01-15T10:30:00.000Z') + const expectedTimestamp = testDate.getTime().toString() + + vi.mocked(Request.get).mockResolvedValue(mockListResponse) + + await Payment.all({ + starting_after_timestamp: testDate, + limit: 5, + }) + + expect(Request.get).toHaveBeenCalledWith( + `/g_business/v1/payments?starting_after_timestamp=${expectedTimestamp}&limit=5`, + expect.objectContaining({ + sign: true, + }) + ) + }) + + it('should accept timestamp as string without conversion', async () => { + const timestampString = '1705315800000' + + vi.mocked(Request.get).mockResolvedValue(mockListResponse) + + await Payment.all({ + starting_after_timestamp: timestampString, + limit: 5, + }) + + expect(Request.get).toHaveBeenCalledWith( + `/g_business/v1/payments?starting_after_timestamp=${timestampString}&limit=5`, + expect.objectContaining({ + sign: true, + }) + ) + }) }) describe('update', () => { @@ -200,7 +280,7 @@ describe('Payment', () => { expect(result).toEqual(mockPaymentResponse) }) - it('should update a payment with amount', async () => { + it('should update a payment with amount_unit', async () => { const paymentId = 'payment-123' const updateBody = { action: 'ACCEPT' as const, @@ -219,6 +299,48 @@ describe('Payment', () => { ) }) + it('should update a payment with amount in euros', async () => { + const paymentId = 'payment-123' + const updateBody = { + action: 'ACCEPT' as const, + amount: 7.50, // 7.50 euros + } + vi.mocked(Request.put).mockResolvedValue(mockPaymentResponse) + + await Payment.update(paymentId, updateBody) + + expect(Request.put).toHaveBeenCalledWith( + '/g_business/v1/payments/payment-123', + expect.objectContaining({ + body: { + action: 'ACCEPT', + amount_unit: 750, // Converted to cents + }, + sign: true, + }) + ) + }) + + it('should handle decimal amounts in update', async () => { + const paymentId = 'payment-123' + const updateBody = { + action: 'ACCEPT' as const, + amount: 12.99, + } + vi.mocked(Request.put).mockResolvedValue(mockPaymentResponse) + + await Payment.update(paymentId, updateBody) + + expect(Request.put).toHaveBeenCalledWith( + '/g_business/v1/payments/payment-123', + expect.objectContaining({ + body: expect.objectContaining({ + amount_unit: 1299, + }), + }) + ) + }) + it('should handle custom headers in update', async () => { const paymentId = 'payment-123' const updateBody = { action: 'CANCEL' as const } diff --git a/tests/Report.test.ts b/tests/Report.test.ts new file mode 100644 index 0000000..505855f --- /dev/null +++ b/tests/Report.test.ts @@ -0,0 +1,222 @@ +import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest' +import { Report } from '../src/Report' +import { Request } from '../src/Request' +import type { ReportResponse, ReportCreateBody } from '../src/types' + +// Mock Request module +vi.mock('../src/Request', () => ({ + Request: { + get: vi.fn(), + post: vi.fn(), + }, +})) + +describe('Report', () => { + const mockReportResponse: ReportResponse = { + id: 'report-123', + type: 'PAYMENT_FEE', + format: 'CSV', + status: 'READY', + from_date: '2025-11-01', + to_date: '2025-11-30', + download_url: 'https://example.com/report.csv', + created_at: '2025-12-01T10:00:00.000Z', + updated_at: '2025-12-01T10:30:00.000Z', + } + + beforeEach(() => { + vi.clearAllMocks() + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + describe('create', () => { + it('should create a new report', async () => { + const mockBody: ReportCreateBody = { + type: 'PAYMENT_FEE', + format: 'CSV', + from_date: '2025-11-01', + to_date: '2025-11-30', + } + + vi.mocked(Request.post).mockResolvedValue(mockReportResponse) + + const result = await Report.create(mockBody) + + expect(Request.post).toHaveBeenCalledWith('/g_business/v1/reports', { + headers: {}, + body: mockBody, + sign: true, + }) + expect(result).toEqual(mockReportResponse) + }) + + it('should create a report with custom columns', async () => { + const mockBody: ReportCreateBody = { + type: 'PAYMENT_FEE', + format: 'XLSX', + from_date: '2025-11-01', + to_date: '2025-11-30', + columns: ['transaction_id', 'transaction_date', 'total_amount'], + } + + vi.mocked(Request.post).mockResolvedValue(mockReportResponse) + + await Report.create(mockBody) + + expect(Request.post).toHaveBeenCalledWith('/g_business/v1/reports', { + headers: {}, + body: mockBody, + sign: true, + }) + }) + + it('should create a report with custom headers', async () => { + const mockBody: ReportCreateBody = { + type: 'PAYMENT_FEE', + format: 'PDF', + from_date: '2025-11-01', + to_date: '2025-11-30', + } + const customHeaders = { + 'Idempotency-Key': 'report-unique-123', + } + + vi.mocked(Request.post).mockResolvedValue(mockReportResponse) + + await Report.create(mockBody, customHeaders) + + expect(Request.post).toHaveBeenCalledWith('/g_business/v1/reports', { + headers: customHeaders, + body: mockBody, + sign: true, + }) + }) + }) + + describe('all', () => { + const mockListResponse = { + list: [mockReportResponse], + has_more: false, + } + + it('should get all reports without query params', async () => { + vi.mocked(Request.get).mockResolvedValue(mockListResponse) + + const result = await Report.all() + + expect(Request.get).toHaveBeenCalledWith('/g_business/v1/reports', { + headers: {}, + sign: true, + }) + expect(result).toEqual(mockListResponse) + }) + + it('should get reports with pagination', async () => { + const query = { + limit: 10, + starting_after: 'report-100', + } + vi.mocked(Request.get).mockResolvedValue(mockListResponse) + + await Report.all(query) + + expect(Request.get).toHaveBeenCalledWith( + '/g_business/v1/reports?limit=10&starting_after=report-100', + expect.objectContaining({ + sign: true, + }), + ) + }) + + it('should handle custom headers', async () => { + const customHeaders = { 'X-Custom': 'header' } + vi.mocked(Request.get).mockResolvedValue(mockListResponse) + + await Report.all({}, customHeaders) + + expect(Request.get).toHaveBeenCalledWith('/g_business/v1/reports', { + headers: customHeaders, + sign: true, + }) + }) + + it('should get reports with limit only', async () => { + const query = { limit: 5 } + vi.mocked(Request.get).mockResolvedValue(mockListResponse) + + await Report.all(query) + + expect(Request.get).toHaveBeenCalledWith( + '/g_business/v1/reports?limit=5', + expect.objectContaining({ + sign: true, + }), + ) + }) + }) + + describe('get', () => { + it('should get a report by id', async () => { + const reportId = 'report-123' + vi.mocked(Request.get).mockResolvedValue(mockReportResponse) + + const result = await Report.get(reportId) + + expect(Request.get).toHaveBeenCalledWith( + '/g_business/v1/reports/report-123', + { + headers: {}, + sign: true, + }, + ) + expect(result).toEqual(mockReportResponse) + }) + + it('should get a report with custom headers', async () => { + const reportId = 'report-123' + const customHeaders = { 'X-Custom': 'value' } + vi.mocked(Request.get).mockResolvedValue(mockReportResponse) + + await Report.get(reportId, customHeaders) + + expect(Request.get).toHaveBeenCalledWith( + '/g_business/v1/reports/report-123', + { + headers: customHeaders, + sign: true, + }, + ) + }) + + it('should handle pending report status', async () => { + const pendingReport: ReportResponse = { + ...mockReportResponse, + status: 'PENDING', + download_url: undefined, + } + vi.mocked(Request.get).mockResolvedValue(pendingReport) + + const result = await Report.get('report-123') + + expect(result.status).toBe('PENDING') + expect(result.download_url).toBeUndefined() + }) + + it('should handle failed report status', async () => { + const failedReport: ReportResponse = { + ...mockReportResponse, + status: 'FAILED', + download_url: undefined, + } + vi.mocked(Request.get).mockResolvedValue(failedReport) + + const result = await Report.get('report-123') + + expect(result.status).toBe('FAILED') + expect(result.download_url).toBeUndefined() + }) + }) +}) diff --git a/tests/Request.test.ts b/tests/Request.test.ts index 09c31f4..0cae5fc 100644 --- a/tests/Request.test.ts +++ b/tests/Request.test.ts @@ -9,7 +9,7 @@ global.fetch = mockFetch as any describe('Request', () => { beforeEach(() => { vi.clearAllMocks() - Api.setEnv('test') + Api.setEnv('staging') Api.setPrivateKey( '-----BEGIN PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----' ) @@ -185,12 +185,51 @@ describe('Request', () => { await expect(Request.get('/test/path')).rejects.toThrow() }) + it('should throw error with request id when available', async () => { + mockFetch.mockResolvedValue({ + ok: false, + status: 400, + text: async () => + JSON.stringify({ + message: 'Bad request', + code: 'BAD_REQUEST', + wlt: 'req-123', + }), + headers: new Headers(), + }) + + await expect(Request.get('/test/path')).rejects.toThrow( + 'Bad request, request id: req-123' + ) + }) + + it('should throw generic error when error details not available', async () => { + mockFetch.mockResolvedValue({ + ok: false, + status: 500, + text: async () => 'Internal Server Error', + headers: new Headers(), + }) + + await expect(Request.get('/test/path')).rejects.toThrow( + 'HTTP status is not 2xx: 500' + ) + }) + it('should throw error on network failure', async () => { mockFetch.mockRejectedValue(new Error('Network error')) await expect(Request.get('/test/path')).rejects.toThrow('Network error') }) + it('should handle non-Error thrown values', async () => { + mockFetch.mockRejectedValue('string error') + + await expect(Request.get('/test/path')).rejects.toThrow( + 'Request failed: string error' + ) + }) + it('should handle non-JSON responses', async () => { mockFetch.mockResolvedValue({ ok: true, diff --git a/tests/Session.test.ts b/tests/Session.test.ts new file mode 100644 index 0000000..36ee35d --- /dev/null +++ b/tests/Session.test.ts @@ -0,0 +1,301 @@ +import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest' +import { Session } from '../src/Session' +import { Request } from '../src/Request' +import type { + SessionResponse, + SessionCreateBody, + SessionUpdateBody, + SessionEventCreateBody, +} from '../src/types' + +// Mock Request module +vi.mock('../src/Request', () => ({ + Request: { + get: vi.fn(), + post: vi.fn(), + patch: vi.fn(), + }, +})) + +describe('Session', () => { + const mockSessionResponse: SessionResponse = { + id: 'session-123', + amount_unit: 5000, + residual_amount_unit: 5000, + currency: 'EUR', + status: 'OPEN', + type: 'TO_BUSINESS_CHARGE', + consumer_uid: 'consumer-456', + available: true, + expiration_date: '2025-12-01T18:00:00.000Z', + } + + beforeEach(() => { + vi.clearAllMocks() + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + describe('open', () => { + it('should open a new session', async () => { + const mockBody: SessionCreateBody = { + fund_lock_id: 'payment-fund-lock-123', + } + + vi.mocked(Request.post).mockResolvedValue(mockSessionResponse) + + const result = await Session.open(mockBody) + + expect(Request.post).toHaveBeenCalledWith('/g_business/v1/sessions', { + headers: {}, + body: mockBody, + sign: true, + }) + expect(result).toEqual(mockSessionResponse) + }) + + it('should open a session with custom headers', async () => { + const mockBody: SessionCreateBody = { + fund_lock_id: 'payment-fund-lock-456', + } + const customHeaders = { + 'Idempotency-Key': 'session-unique-123', + } + + vi.mocked(Request.post).mockResolvedValue(mockSessionResponse) + + await Session.open(mockBody, customHeaders) + + expect(Request.post).toHaveBeenCalledWith('/g_business/v1/sessions', { + headers: customHeaders, + body: mockBody, + sign: true, + }) + }) + }) + + describe('get', () => { + it('should get session details by id', async () => { + const sessionId = 'session-123' + vi.mocked(Request.get).mockResolvedValue(mockSessionResponse) + + const result = await Session.get(sessionId) + + expect(Request.get).toHaveBeenCalledWith( + '/g_business/v1/sessions/session-123', + { + headers: {}, + sign: true, + }, + ) + expect(result).toEqual(mockSessionResponse) + }) + + it('should get session with custom headers', async () => { + const sessionId = 'session-123' + const customHeaders = { 'X-Custom': 'value' } + vi.mocked(Request.get).mockResolvedValue(mockSessionResponse) + + await Session.get(sessionId, customHeaders) + + expect(Request.get).toHaveBeenCalledWith( + '/g_business/v1/sessions/session-123', + { + headers: customHeaders, + sign: true, + }, + ) + }) + + it('should get session with reduced residual amount', async () => { + const sessionWithCharges: SessionResponse = { + ...mockSessionResponse, + residual_amount_unit: 3000, + } + vi.mocked(Request.get).mockResolvedValue(sessionWithCharges) + + const result = await Session.get('session-123') + + expect(result.residual_amount_unit).toBe(3000) + expect(result.amount_unit).toBe(5000) + }) + }) + + describe('update', () => { + it('should close a session', async () => { + const sessionId = 'session-123' + const updateBody: SessionUpdateBody = { + status: 'CLOSE', + } + const closedSession: SessionResponse = { + ...mockSessionResponse, + status: 'CLOSE', + residual_amount_unit: 0, + } + + vi.mocked(Request.patch).mockResolvedValue(closedSession) + + const result = await Session.update(sessionId, updateBody) + + expect(Request.patch).toHaveBeenCalledWith( + '/g_business/v1/sessions/session-123', + { + headers: {}, + body: updateBody, + sign: true, + }, + ) + expect(result).toEqual(closedSession) + expect(result.status).toBe('CLOSE') + }) + + it('should update session with custom headers', async () => { + const sessionId = 'session-123' + const updateBody: SessionUpdateBody = { + status: 'CLOSE', + } + const customHeaders = { 'X-Reason': 'customer-request' } + + vi.mocked(Request.patch).mockResolvedValue(mockSessionResponse) + + await Session.update(sessionId, updateBody, customHeaders) + + expect(Request.patch).toHaveBeenCalledWith( + '/g_business/v1/sessions/session-123', + { + headers: customHeaders, + body: updateBody, + sign: true, + }, + ) + }) + }) + + describe('createEvent', () => { + it('should add an item to the session', async () => { + const sessionId = 'session-123' + const eventBody: SessionEventCreateBody = { + type: 'ADD_ITEM', + amount_unit: 1000, + description: 'Coffee', + } + const updatedSession: SessionResponse = { + ...mockSessionResponse, + residual_amount_unit: 4000, + } + + vi.mocked(Request.post).mockResolvedValue(updatedSession) + + const result = await Session.createEvent(sessionId, eventBody) + + expect(Request.post).toHaveBeenCalledWith( + '/g_business/v1/sessions/session-123/events', + { + headers: {}, + body: eventBody, + sign: true, + }, + ) + expect(result.residual_amount_unit).toBe(4000) + }) + + it('should remove an item from the session', async () => { + const sessionId = 'session-123' + const eventBody: SessionEventCreateBody = { + type: 'REMOVE_ITEM', + amount_unit: 500, + description: 'Discount applied', + } + + vi.mocked(Request.post).mockResolvedValue(mockSessionResponse) + + await Session.createEvent(sessionId, eventBody) + + expect(Request.post).toHaveBeenCalledWith( + '/g_business/v1/sessions/session-123/events', + { + headers: {}, + body: eventBody, + sign: true, + }, + ) + }) + + it('should update the total', async () => { + const sessionId = 'session-123' + const eventBody: SessionEventCreateBody = { + type: 'UPDATE_TOTAL', + amount_unit: 3500, + description: 'Total updated', + } + + vi.mocked(Request.post).mockResolvedValue(mockSessionResponse) + + await Session.createEvent(sessionId, eventBody) + + expect(Request.post).toHaveBeenCalledWith( + '/g_business/v1/sessions/session-123/events', + { + headers: {}, + body: eventBody, + sign: true, + }, + ) + }) + + it('should create event with metadata', async () => { + const sessionId = 'session-123' + const eventBody: SessionEventCreateBody = { + type: 'ADD_ITEM', + amount_unit: 1200, + description: 'Espresso', + metadata: { + sku: 'COFFEE-001', + category: 'beverages', + }, + } + + vi.mocked(Request.post).mockResolvedValue(mockSessionResponse) + + await Session.createEvent(sessionId, eventBody) + + expect(Request.post).toHaveBeenCalledWith( + '/g_business/v1/sessions/session-123/events', + expect.objectContaining({ + body: expect.objectContaining({ + metadata: { + sku: 'COFFEE-001', + category: 'beverages', + }, + }), + }), + ) + }) + + it('should create event with custom headers', async () => { + const sessionId = 'session-123' + const eventBody: SessionEventCreateBody = { + type: 'ADD_ITEM', + amount_unit: 800, + } + const customHeaders = { + 'Idempotency-Key': 'event-unique-123', + } + + vi.mocked(Request.post).mockResolvedValue(mockSessionResponse) + + await Session.createEvent(sessionId, eventBody, customHeaders) + + expect(Request.post).toHaveBeenCalledWith( + '/g_business/v1/sessions/session-123/events', + { + headers: customHeaders, + body: eventBody, + sign: true, + }, + ) + }) + }) +}) diff --git a/tests/e2e/authentication.e2e.test.ts b/tests/e2e/authentication.e2e.test.ts new file mode 100644 index 0000000..e9b8856 --- /dev/null +++ b/tests/e2e/authentication.e2e.test.ts @@ -0,0 +1,58 @@ +import { describe, it, expect } from 'vitest' +import { Api } from '../../src/Api' +import { canRunE2ETests } from '../setup' + +/** + * E2E tests for Satispay authentication + * + * These tests require: + * - SATISPAY_PUBLIC_KEY, SATISPAY_PRIVATE_KEY, SATISPAY_KEY_ID configured + * + * NOTE: The activation code is not tested because it can only be used once. + * Keys must be manually generated before running E2E tests. + * + * Tests run ONLY in staging environment for safety. + */ + +describe.skipIf(!canRunE2ETests())('E2E: Authentication', () => { + describe('Authentication with existing keys', () => { + it( + 'should have valid authentication keys configured', + () => { + // Verify that keys are configured correctly + const privateKey = Api.getPrivateKey() + const publicKey = Api.getPublicKey() + const keyId = Api.getKeyId() + + expect(privateKey).toBeTruthy() + expect(publicKey).toBeTruthy() + expect(keyId).toBeTruthy() + + // Verify format + expect(privateKey).toContain('-----BEGIN PRIVATE KEY-----') + expect(privateKey).toContain('-----END PRIVATE KEY-----') + expect(publicKey).toContain('-----BEGIN PUBLIC KEY-----') + expect(publicKey).toContain('-----END PUBLIC KEY-----') + expect(keyId).toMatch(/^[a-z0-9]+$/) + + console.log('\n✓ Valid authentication keys') + console.log('Key ID:', keyId) + } + ) + }) + + describe('Environment configuration', () => { + it('should be forced to staging environment', () => { + expect(Api.getEnv()).toBe('staging') + }) + + it('should use staging authservices URL', () => { + const url = Api.getAuthservicesUrl() + expect(url).toBe('https://staging.authservices.satispay.com') + }) + + it('should have sandbox mode enabled', () => { + expect(Api.getSandbox()).toBe(true) + }) + }) +}) diff --git a/tests/e2e/payment.e2e.test.ts b/tests/e2e/payment.e2e.test.ts new file mode 100644 index 0000000..37acc0b --- /dev/null +++ b/tests/e2e/payment.e2e.test.ts @@ -0,0 +1,230 @@ +import { describe, it, expect, beforeAll } from 'vitest' +import { Payment } from '../../src/Payment' +import { CodeGenerator } from '../../src/utils' +import { canRunE2ETests, hasAuthenticationKeys } from '../setup' + +/** + * E2E tests for complete payment flow with Satispay + * + * These tests require: + * - SATISPAY_PUBLIC_KEY, SATISPAY_PRIVATE_KEY, SATISPAY_KEY_ID configured + * - Staging or test environment + * + * NOTE: These tests create real payments in the configured environment. + * Use only with test/staging environments. + */ + +describe.skipIf(!canRunE2ETests() || !hasAuthenticationKeys())('E2E: Payment Flow', () => { + let testPaymentId: string | undefined + + beforeAll(() => { + console.log('\n⚠️ WARNING: These tests create real payments') + console.log('Make sure you are in staging/test environment\n') + }) + + describe('Create Payment', () => { + it( + 'should create a new payment', + async () => { + const externalCode = CodeGenerator.generateExternalCode('E2E-TEST') + const amount = 1.00 // 1 euro + + const payment = await Payment.create({ + flow: 'MATCH_CODE', + amount: amount, // Using amount in euros + currency: 'EUR', + external_code: externalCode, + metadata: { + test: 'e2e-test', + timestamp: new Date().toISOString(), + }, + }) + + // Verifica la risposta + expect(payment.id).toBeTruthy() + expect(payment.amount_unit).toBe(100) // Converted to cents + expect(payment.currency).toBe('EUR') + expect(payment.external_code).toBe(externalCode) + expect(payment.status).toBe('PENDING') + + // Save ID for subsequent tests + testPaymentId = payment.id + + console.log('\n✓ Payment created successfully') + console.log('Payment ID:', payment.id) + console.log('External Code:', payment.external_code) + console.log('Amount:', amount, 'EUR') + console.log('Status:', payment.status) + }, + 30000 + ) + }) + + describe('Get Payment', () => { + it( + 'should retrieve an existing payment', + async () => { + if (!testPaymentId) { + console.log('⚠️ Skipped: no payment ID available from previous test') + return + } + + const payment = await Payment.get(testPaymentId) + + // Verify response + expect(payment.id).toBe(testPaymentId) + expect(payment.amount_unit).toBeTruthy() + expect(payment.currency).toBe('EUR') + expect(payment.status).toBeTruthy() + + console.log('\n✓ Payment retrieved successfully') + console.log('Payment ID:', payment.id) + console.log('Status:', payment.status) + }, + 30000 + ) + }) + + describe('List Payments', () => { + it( + 'should list payments with pagination', + async () => { + const result = await Payment.all({ + limit: 10, + }) + + // Verify response structure + expect(result).toBeDefined() + expect(result.data).toBeDefined() + expect(Array.isArray(result.data)).toBe(true) + expect(result.has_more).toBeDefined() + + console.log('\n📋 Payment list response:') + console.log('Total payments found:', result.data.length) + console.log('Has more pages:', result.has_more) + + if (result.data.length > 0) { + const firstPayment = result.data[0] + expect(firstPayment.id).toBeTruthy() + expect(firstPayment.amount_unit).toBeDefined() + expect(firstPayment.currency).toBe('EUR') + expect(firstPayment.status).toBeTruthy() + + console.log('✓ Payment list retrieved successfully') + console.log('First payment ID:', firstPayment.id) + console.log('First payment status:', firstPayment.status) + console.log('First payment date:', firstPayment.insert_date) + } else { + console.log('⚠️ No payments found in staging environment') + console.log('💡 Note: Staging environment may have limited or no historical data') + console.log('💡 The payment created in this test run should appear in subsequent calls') + } + }, + 30000 + ) + + it( + 'should find the payment created in this test run', + async () => { + if (!testPaymentId) { + console.log('⚠️ Skipped: no payment ID available') + return + } + + // Try to find the payment in the list + const result = await Payment.all({ + limit: 100, // Increase limit to find recent payment + }) + + const createdPayment = result.data.find(p => p.id === testPaymentId) + + if (createdPayment) { + console.log('\n✓ Found the payment created in this test run') + console.log('Payment ID:', createdPayment.id) + console.log('Status:', createdPayment.status) + console.log('Amount:', createdPayment.amount_unit / 100, 'EUR') + expect(createdPayment.id).toBe(testPaymentId) + } else { + console.log('\n⚠️ Payment not found in list') + console.log('Expected payment ID:', testPaymentId) + console.log('Total payments in list:', result.data.length) + console.log('💡 This might be due to API indexing delay or staging environment behavior') + } + }, + 30000 + ) + + // Note: starting_after_timestamp now supports Date objects directly + it( + 'should filter payments by date using starting_after_timestamp', + async () => { + const yesterday = new Date() + yesterday.setDate(yesterday.getDate() - 1) + + const result = await Payment.all({ + starting_after_timestamp: yesterday, // Date object is automatically converted + limit: 5, + }) + + expect(result).toBeDefined() + expect(result.data).toBeDefined() + expect(Array.isArray(result.data)).toBe(true) + + console.log('\n✓ Date filter applied successfully') + console.log('Payments found:', result.data.length) + console.log('Filter date:', yesterday.toISOString()) + }, + 30000 + ) + }) + + describe('Update Payment', () => { + it( + 'should update payment action', + async () => { + if (!testPaymentId) { + console.log('⚠️ Skipped: no payment ID available') + return + } + + const payment = await Payment.update(testPaymentId, { + action: 'CANCEL', + }) + + // Verify response + expect(payment.id).toBe(testPaymentId) + + console.log('\n✓ Payment updated') + console.log('Payment ID:', payment.id) + console.log('Status:', payment.status) + }, + 30000 + ) + }) + + describe('Payment by External Code', () => { + it( + 'should create and retrieve payment by external code', + async () => { + // Crea un nuovo pagamento con external code univoco + const externalCode = CodeGenerator.generateUuidExternalCode('E2E-SEARCH') + + const createdPayment = await Payment.create({ + flow: 'MATCH_CODE', + amount: 0.50, // 0.50 euros + currency: 'EUR', + external_code: externalCode, + }) + + // Verify it was created with correct external code + expect(createdPayment.external_code).toBe(externalCode) + expect(createdPayment.amount_unit).toBe(50) // 0.50 EUR = 50 cents + + console.log('\n✓ Payment created and found by external code') + console.log('External Code:', createdPayment.external_code) + console.log('Payment ID:', createdPayment.id) + }, + 30000 + ) + }) +}) diff --git a/tests/setup.ts b/tests/setup.ts new file mode 100644 index 0000000..76b1c3a --- /dev/null +++ b/tests/setup.ts @@ -0,0 +1,63 @@ +import { beforeAll } from 'vitest' +import { Api } from '../src/Api' +import { config } from 'dotenv' + +// Load environment variables from .env.local (if exists) +// dotenv automatically converts \n to real newlines +config({ path: '.env.local', override: true, quiet: true }) + +/** + * Setup for E2E tests + * Loads environment variables needed for integration tests + * + * NOTE: E2E tests require pre-configured keys and run ONLY in staging environment + */ +beforeAll(() => { + // Check that environment variables are available for E2E tests + const hasE2EConfig = + process.env.SATISPAY_PUBLIC_KEY && + process.env.SATISPAY_PRIVATE_KEY && + process.env.SATISPAY_KEY_ID + + if (hasE2EConfig) { + // Force staging environment for safety + Api.setEnv('staging') + + // Configure keys if available + if (process.env.SATISPAY_PUBLIC_KEY) { + Api.setPublicKey(process.env.SATISPAY_PUBLIC_KEY) + } + + if (process.env.SATISPAY_PRIVATE_KEY) { + Api.setPrivateKey(process.env.SATISPAY_PRIVATE_KEY) + } + + if (process.env.SATISPAY_KEY_ID) { + Api.setKeyId(process.env.SATISPAY_KEY_ID) + } + + return + } + + console.log('⚠ E2E configuration not found, E2E tests will be skipped') +}) + +/** + * Helper to check if E2E tests can be executed + * Requires all three keys to be configured + */ +export function canRunE2ETests(): boolean { + return !!( + process.env.SATISPAY_PUBLIC_KEY && + process.env.SATISPAY_PRIVATE_KEY && + process.env.SATISPAY_KEY_ID + ) +} + +/** + * Helper to check if keys are configured + * (Alias of canRunE2ETests for backwards compatibility) + */ +export function hasAuthenticationKeys(): boolean { + return canRunE2ETests() +} diff --git a/tests/utils.test.ts b/tests/utils.test.ts new file mode 100644 index 0000000..5d6941a --- /dev/null +++ b/tests/utils.test.ts @@ -0,0 +1,345 @@ +import { describe, it, expect } from 'vitest' +import { + Amount, + DateUtils, + Validation, + CodeGenerator, + PaymentStatusUtils, +} from '../src/utils' + +describe('Amount', () => { + describe('toCents', () => { + it('should convert euros to cents', () => { + expect(Amount.toCents(10.5)).toBe(1050) + expect(Amount.toCents(1)).toBe(100) + expect(Amount.toCents(0.01)).toBe(1) + }) + + it('should round to nearest cent', () => { + expect(Amount.toCents(10.505)).toBe(1051) + expect(Amount.toCents(10.504)).toBe(1050) + }) + }) + + describe('toEuros', () => { + it('should convert cents to euros', () => { + expect(Amount.toEuros(1050)).toBe(10.5) + expect(Amount.toEuros(100)).toBe(1) + expect(Amount.toEuros(1)).toBe(0.01) + }) + }) + + describe('format', () => { + it('should format amount with default locale', () => { + const formatted = Amount.format(1050) + expect(formatted).toContain('10') + expect(formatted).toContain('50') + }) + + it('should format amount with custom locale', () => { + const formatted = Amount.format(1050, 'en-US') + expect(formatted).toContain('10') + expect(formatted).toContain('50') + }) + }) + + describe('parse', () => { + it('should parse formatted amount', () => { + expect(Amount.parse('10,50 €')).toBe(1050) + expect(Amount.parse('10.50')).toBe(1050) + expect(Amount.parse('1,00')).toBe(100) + }) + + it('should throw error on invalid format', () => { + expect(() => Amount.parse('invalid')).toThrow('Invalid amount format') + }) + }) + + describe('isValid', () => { + it('should validate positive integers', () => { + expect(Amount.isValid(100)).toBe(true) + expect(Amount.isValid(1)).toBe(true) + }) + + it('should reject invalid amounts', () => { + expect(Amount.isValid(0)).toBe(false) + expect(Amount.isValid(-100)).toBe(false) + expect(Amount.isValid(10.5)).toBe(false) + }) + }) +}) + +describe('DateUtils', () => { + describe('formatForApi', () => { + it('should format Date object', () => { + const date = new Date('2024-01-15T10:30:00Z') + expect(DateUtils.formatForApi(date)).toBe('2024-01-15') + }) + + it('should format ISO string', () => { + expect(DateUtils.formatForApi('2024-01-15T10:30:00Z')).toBe('2024-01-15') + }) + }) + + describe('parseFromApi', () => { + it('should parse date string', () => { + const date = DateUtils.parseFromApi('2024-01-15T10:30:00Z') + expect(date).toBeInstanceOf(Date) + expect(date.getFullYear()).toBe(2024) + expect(date.getMonth()).toBe(0) + expect(date.getDate()).toBe(15) + }) + }) + + describe('getDailyClosureRange', () => { + it('should return start and end of day', () => { + const date = new Date('2024-01-15T10:30:00Z') + const range = DateUtils.getDailyClosureRange(date) + + expect(range.start.getHours()).toBe(0) + expect(range.start.getMinutes()).toBe(0) + expect(range.start.getSeconds()).toBe(0) + + expect(range.end.getHours()).toBe(23) + expect(range.end.getMinutes()).toBe(59) + expect(range.end.getSeconds()).toBe(59) + }) + }) + + describe('getToday', () => { + it('should return today at midnight', () => { + const today = DateUtils.getToday() + expect(today.getHours()).toBe(0) + expect(today.getMinutes()).toBe(0) + expect(today.getSeconds()).toBe(0) + }) + }) + + describe('getYesterday', () => { + it('should return yesterday at midnight', () => { + const yesterday = DateUtils.getYesterday() + const expectedDate = new Date() + expectedDate.setDate(expectedDate.getDate() - 1) + expectedDate.setHours(0, 0, 0, 0) + + expect(yesterday.getDate()).toBe(expectedDate.getDate()) + expect(yesterday.getHours()).toBe(0) + }) + }) + + describe('isToday', () => { + it('should check if date is today', () => { + const today = new Date() + expect(DateUtils.isToday(today)).toBe(true) + + const yesterday = new Date() + yesterday.setDate(yesterday.getDate() - 1) + expect(DateUtils.isToday(yesterday)).toBe(false) + }) + }) + + describe('format', () => { + it('should format date with default locale', () => { + const date = new Date('2024-01-15T10:30:00Z') + const formatted = DateUtils.format(date) + expect(formatted).toBeTruthy() + }) + + it('should format date with custom locale', () => { + const date = new Date('2024-01-15T10:30:00Z') + const formatted = DateUtils.format(date, 'en-US') + expect(formatted).toContain('January') + }) + }) + + describe('formatDateTime', () => { + it('should format date and time', () => { + const date = new Date('2024-01-15T10:30:00Z') + const formatted = DateUtils.formatDateTime(date) + expect(formatted).toBeTruthy() + }) + }) +}) + +describe('Validation', () => { + describe('validateExternalCode', () => { + it('should validate correct external codes', () => { + expect(Validation.validateExternalCode('ORDER-123')).toBe(true) + expect(Validation.validateExternalCode('test_code')).toBe(true) + expect(Validation.validateExternalCode('ABC123')).toBe(true) + }) + + it('should reject empty codes', () => { + expect(Validation.validateExternalCode('')).toContain('cannot be empty') + expect(Validation.validateExternalCode(' ')).toContain('cannot be empty') + }) + + it('should reject too long codes', () => { + const longCode = 'a'.repeat(51) + expect(Validation.validateExternalCode(longCode)).toContain('50 characters') + }) + + it('should reject invalid characters', () => { + expect(Validation.validateExternalCode('code with spaces')).toContain( + 'letters, numbers' + ) + expect(Validation.validateExternalCode('code@invalid')).toContain( + 'letters, numbers' + ) + }) + }) + + describe('validateFlow', () => { + it('should validate correct flows', () => { + expect(Validation.validateFlow('MATCH_CODE')).toBe(true) + expect(Validation.validateFlow('MATCH_USER')).toBe(true) + expect(Validation.validateFlow('REFUND')).toBe(true) + }) + + it('should reject invalid flows', () => { + expect(Validation.validateFlow('INVALID')).toBe(false) + expect(Validation.validateFlow('')).toBe(false) + }) + }) + + describe('validateCurrency', () => { + it('should validate EUR', () => { + expect(Validation.validateCurrency('EUR')).toBe(true) + }) + + it('should reject other currencies', () => { + expect(Validation.validateCurrency('USD')).toBe(false) + expect(Validation.validateCurrency('GBP')).toBe(false) + }) + }) + + describe('validatePhone', () => { + it('should validate Italian phone numbers', () => { + expect(Validation.validatePhone('+393331234567')).toBe(true) + expect(Validation.validatePhone('+39 333 123 4567')).toBe(true) + }) + + it('should reject invalid phone numbers', () => { + expect(Validation.validatePhone('123456789')).toContain('Invalid') + expect(Validation.validatePhone('+1234567890')).toContain('Invalid') + }) + }) + + describe('validateMetadata', () => { + it('should validate correct metadata', () => { + expect(Validation.validateMetadata({ key: 'value' })).toBe(true) + expect(Validation.validateMetadata({ a: 1, b: 'test' })).toBe(true) + }) + + it('should reject non-object metadata', () => { + expect(Validation.validateMetadata(null as any)).toContain('must be an object') + expect(Validation.validateMetadata('string' as any)).toContain('must be an object') + }) + + it('should reject too large metadata', () => { + const largeMetadata = { data: 'a'.repeat(1000) } + expect(Validation.validateMetadata(largeMetadata)).toContain('1000 characters') + }) + }) +}) + +describe('CodeGenerator', () => { + describe('generateExternalCode', () => { + it('should generate code with timestamp', () => { + const code = CodeGenerator.generateExternalCode() + expect(code).toMatch(/^ORDER-\d+$/) + }) + + it('should use custom prefix', () => { + const code = CodeGenerator.generateExternalCode('PAYMENT') + expect(code).toMatch(/^PAYMENT-\d+$/) + }) + }) + + describe('generateRandomExternalCode', () => { + it('should generate code with random suffix', () => { + const code = CodeGenerator.generateRandomExternalCode() + expect(code).toMatch(/^ORDER-[a-z0-9]+$/) + }) + + it('should use custom prefix', () => { + const code = CodeGenerator.generateRandomExternalCode('TEST') + expect(code).toMatch(/^TEST-[a-z0-9]+$/) + }) + + it('should generate unique codes', () => { + const code1 = CodeGenerator.generateRandomExternalCode() + const code2 = CodeGenerator.generateRandomExternalCode() + expect(code1).not.toBe(code2) + }) + }) + + describe('generateUuidExternalCode', () => { + it('should generate UUID code', () => { + const code = CodeGenerator.generateUuidExternalCode() + expect(code).toMatch(/^ORDER-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/) + }) + + it('should use custom prefix', () => { + const code = CodeGenerator.generateUuidExternalCode('INVOICE') + expect(code).toMatch(/^INVOICE-[0-9a-f]{8}-/) + }) + + it('should generate unique codes', () => { + const code1 = CodeGenerator.generateUuidExternalCode() + const code2 = CodeGenerator.generateUuidExternalCode() + expect(code1).not.toBe(code2) + }) + }) +}) + +describe('PaymentStatusUtils', () => { + describe('status checkers', () => { + it('should check if payment is pending', () => { + expect(PaymentStatusUtils.isPending('PENDING')).toBe(true) + expect(PaymentStatusUtils.isPending('ACCEPTED')).toBe(false) + }) + + it('should check if payment is accepted', () => { + expect(PaymentStatusUtils.isAccepted('ACCEPTED')).toBe(true) + expect(PaymentStatusUtils.isAccepted('PENDING')).toBe(false) + }) + + it('should check if payment is canceled', () => { + expect(PaymentStatusUtils.isCanceled('CANCELED')).toBe(true) + expect(PaymentStatusUtils.isCanceled('PENDING')).toBe(false) + }) + + it('should check if payment is expired', () => { + expect(PaymentStatusUtils.isExpired('EXPIRED')).toBe(true) + expect(PaymentStatusUtils.isExpired('PENDING')).toBe(false) + }) + + it('should check if payment is final', () => { + expect(PaymentStatusUtils.isFinal('ACCEPTED')).toBe(true) + expect(PaymentStatusUtils.isFinal('CANCELED')).toBe(true) + expect(PaymentStatusUtils.isFinal('EXPIRED')).toBe(true) + expect(PaymentStatusUtils.isFinal('PENDING')).toBe(false) + }) + }) + + describe('getLabel', () => { + it('should return Italian labels by default', () => { + expect(PaymentStatusUtils.getLabel('PENDING')).toBe('In attesa') + expect(PaymentStatusUtils.getLabel('ACCEPTED')).toBe('Accettato') + expect(PaymentStatusUtils.getLabel('CANCELED')).toBe('Annullato') + expect(PaymentStatusUtils.getLabel('EXPIRED')).toBe('Scaduto') + }) + + it('should return English labels', () => { + expect(PaymentStatusUtils.getLabel('PENDING', 'en-US')).toBe('Pending') + expect(PaymentStatusUtils.getLabel('ACCEPTED', 'en-US')).toBe('Accepted') + expect(PaymentStatusUtils.getLabel('CANCELED', 'en-US')).toBe('Canceled') + expect(PaymentStatusUtils.getLabel('EXPIRED', 'en-US')).toBe('Expired') + }) + + it('should return status as-is for unknown status', () => { + expect(PaymentStatusUtils.getLabel('UNKNOWN')).toBe('UNKNOWN') + }) + }) +}) diff --git a/vitest.config.ts b/vitest.config.ts index e2968f2..5281984 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -6,9 +6,10 @@ export default defineConfig({ globals: true, environment: 'node', include: ['tests/**/*.test.ts'], + setupFiles: ['./tests/setup.ts'], coverage: { provider: 'v8', - reporter: ['text', 'json', 'html'], + reporter: ['text', 'json', 'html', 'lcov'], include: ['src/**/*.ts'], exclude: [ 'node_modules/', @@ -17,7 +18,16 @@ export default defineConfig({ 'tests/', '**/*.config.*', '**/*.d.ts', + 'src/index.ts', + 'src/types.ts', + 'src/bin/**', ], + thresholds: { + lines: 80, + functions: 80, + branches: 80, + statements: 80, + }, }, }, resolve: { From 00747584a615def5f4954acc9eb61ddc99190938 Mon Sep 17 00:00:00 2001 From: Alessandro Bellesia Date: Tue, 2 Dec 2025 00:03:38 +0100 Subject: [PATCH 4/5] Upload workflows --- .github/workflows/build.yml | 36 ++++++++++++ .github/workflows/ci.yml | 86 ---------------------------- .github/workflows/main.yml | 57 ++++++++++++++++++ .github/workflows/pr-check-suite.yml | 26 +++++++++ .github/workflows/release-tag.yml | 26 +++++++++ .github/workflows/release.yml | 61 -------------------- .github/workflows/sonarcloud.yml | 22 +++++++ .github/workflows/test.yml | 49 ++++++++++++++++ 8 files changed, 216 insertions(+), 147 deletions(-) create mode 100644 .github/workflows/build.yml delete mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/main.yml create mode 100644 .github/workflows/pr-check-suite.yml create mode 100644 .github/workflows/release-tag.yml delete mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/sonarcloud.yml create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..468fcbb --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,36 @@ +name: Build library + +on: + workflow_call: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --no-frozen-lockfile + + - name: Build release + run: pnpm build + + - name: Bump version with release tag name + run: pnpm version --no-git-tag-version ${{ github.event.release.tag_name }} + + - name: Pack package + run: pnpm pack + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: package + path: 'volverjs-satispay-node-sdk-*.tgz' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index ac8e93b..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: CI - -on: - push: - branches: [main, develop] - pull_request: - branches: [main, develop] - -jobs: - test: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - node-version: [18.x, 20.x, 22.x] - - steps: - - uses: actions/checkout@v4 - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 9 - - - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - cache: 'pnpm' - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Lint - run: pnpm lint - - - name: Build - run: pnpm build - - - name: Test - run: pnpm test - env: - SATISPAY_PUBLIC_KEY: ${{ secrets.SATISPAY_PUBLIC_KEY }} - SATISPAY_PRIVATE_KEY: ${{ secrets.SATISPAY_PRIVATE_KEY }} - SATISPAY_KEY_ID: ${{ secrets.SATISPAY_KEY_ID }} - - - name: Test Coverage - run: pnpm test:coverage - env: - SATISPAY_PUBLIC_KEY: ${{ secrets.SATISPAY_PUBLIC_KEY }} - SATISPAY_PRIVATE_KEY: ${{ secrets.SATISPAY_PRIVATE_KEY }} - SATISPAY_KEY_ID: ${{ secrets.SATISPAY_KEY_ID }} - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 - if: matrix.os == 'ubuntu-latest' && matrix.node-version == '20.x' - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage/coverage-final.json - flags: unittests - name: codecov-umbrella - - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 9 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20.x' - cache: 'pnpm' - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Check formatting - run: pnpm format --check - - - name: Lint - run: pnpm lint diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..11af7e7 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,57 @@ +name: Main pipeline + +on: + # Runs on release publish + release: + types: [published] + +jobs: + analysis: + uses: ./.github/workflows/sonarcloud.yml + secrets: inherit + + build: + uses: ./.github/workflows/build.yml + + test: + needs: build + uses: ./.github/workflows/test.yml + + publish-npm: + needs: [test, analysis] + runs-on: ubuntu-latest + steps: + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: package + + - uses: actions/setup-node@v4 + with: + node-version: 20 + registry-url: https://registry.npmjs.org/ + + - run: npm publish $(ls *.tgz) --access=public --tag ${{ github.event.release.prerelease && 'next' || 'latest'}} + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + + publish-gpr: + needs: [test, analysis] + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: package + + - uses: actions/setup-node@v4 + with: + node-version: 20 + registry-url: https://npm.pkg.github.com/ + + - run: npm publish $(ls *.tgz) --access=public --tag ${{ github.event.release.prerelease && 'next' || 'latest'}} + env: + NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/pr-check-suite.yml b/.github/workflows/pr-check-suite.yml new file mode 100644 index 0000000..2328813 --- /dev/null +++ b/.github/workflows/pr-check-suite.yml @@ -0,0 +1,26 @@ +name: Check PR + +on: + # Run on pull request + pull_request: + branches: [main, develop] + +# Sets permissions of the GITHUB_TOKEN +permissions: + contents: write + pages: write + id-token: write + pull-requests: write + +jobs: + analysis: + uses: ./.github/workflows/sonarcloud.yml + secrets: inherit + + build: + uses: ./.github/workflows/build.yml + + test: + needs: build + uses: ./.github/workflows/test.yml + diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml new file mode 100644 index 0000000..f31b94a --- /dev/null +++ b/.github/workflows/release-tag.yml @@ -0,0 +1,26 @@ +on: + push: + tags: + - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 + +name: Create Release + +jobs: + build: + permissions: + contents: write # to create release (yyx990803/release-tag) + + name: Create Release + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@master + - name: Create Release for Tag + id: release_tag + uses: yyx990803/release-tag@master + env: + GITHUB_TOKEN: ${{ secrets.RELEASE_TAG_GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + body: | + Please refer to [CHANGELOG.md](https://github.com/volverjs/satispay-node-sdk/blob/${{ contains(github.ref, 'beta') && 'develop' || 'main'}}/CHANGELOG.md) for details. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 47a6a67..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Release - -on: - push: - tags: - - 'v*' - -permissions: - contents: write - -jobs: - release: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 9 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20.x' - cache: 'pnpm' - registry-url: 'https://registry.npmjs.org' - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Build - run: pnpm build - - - name: Test - run: pnpm test - env: - SATISPAY_PUBLIC_KEY: ${{ secrets.SATISPAY_PUBLIC_KEY }} - SATISPAY_PRIVATE_KEY: ${{ secrets.SATISPAY_PRIVATE_KEY }} - SATISPAY_KEY_ID: ${{ secrets.SATISPAY_KEY_ID }} - - - name: Test Coverage - run: pnpm test:coverage - env: - SATISPAY_PUBLIC_KEY: ${{ secrets.SATISPAY_PUBLIC_KEY }} - SATISPAY_PRIVATE_KEY: ${{ secrets.SATISPAY_PRIVATE_KEY }} - SATISPAY_KEY_ID: ${{ secrets.SATISPAY_KEY_ID }} - - - name: Publish to npm - run: pnpm publish --access public --no-git-checks - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - generate_release_notes: true - draft: false - prerelease: false diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml new file mode 100644 index 0000000..82eee80 --- /dev/null +++ b/.github/workflows/sonarcloud.yml @@ -0,0 +1,22 @@ +name: SonarCloud analysis + +on: + workflow_call: + +jobs: + sonarcloud: + name: SonarCloud + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + if: env.SONAR_TOKEN + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: SonarCloud Scan + if: env.SONAR_TOKEN + uses: SonarSource/sonarcloud-github-action@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..0d71cce --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,49 @@ +name: Run library test + +on: + workflow_call: + +jobs: + vitest: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - uses: pnpm/action-setup@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --no-frozen-lockfile + + - name: Test + run: pnpm test + env: + SATISPAY_PUBLIC_KEY: ${{ secrets.SATISPAY_PUBLIC_KEY }} + SATISPAY_PRIVATE_KEY: ${{ secrets.SATISPAY_PRIVATE_KEY }} + SATISPAY_KEY_ID: ${{ secrets.SATISPAY_KEY_ID }} + + - name: Test Coverage + run: pnpm test:coverage + env: + SATISPAY_PUBLIC_KEY: ${{ secrets.SATISPAY_PUBLIC_KEY }} + SATISPAY_PRIVATE_KEY: ${{ secrets.SATISPAY_PRIVATE_KEY }} + SATISPAY_KEY_ID: ${{ secrets.SATISPAY_KEY_ID }} + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + if: matrix.os == 'ubuntu-latest' && matrix.node-version == '20.x' + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./coverage/coverage-final.json + flags: unittests + name: codecov-umbrella \ No newline at end of file From 847971630fbbe08ca9ca42507f8d192ec8122b40 Mon Sep 17 00:00:00 2001 From: Alessandro Bellesia Date: Tue, 2 Dec 2025 00:06:40 +0100 Subject: [PATCH 5/5] Add pnpm version --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 7bb5021..b558ac5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "@volverjs/satispay-node-sdk", "version": "0.0.1", + "packageManager": "pnpm@10.24.0", "description": "(Unofficial) Satispay GBusiness Node.js API SDK", "type": "module", "main": "dist/index.js",