diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 0000000..f899ee1
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1,9 @@
+{
+ "semi": false,
+ "singleQuote": true,
+ "tabWidth": 4,
+ "useTabs": false,
+ "trailingComma": "es5",
+ "printWidth": 100,
+ "arrowParens": "avoid"
+}
diff --git a/demo/README.md b/demo/README.md
new file mode 100644
index 0000000..82d485e
--- /dev/null
+++ b/demo/README.md
@@ -0,0 +1,141 @@
+# Rondevu WebRTC Local Test
+
+Simple side-by-side demo for testing `WebRTCRondevuConnection` with **local signaling** (no server required).
+
+## Quick Start
+
+```bash
+npm run dev
+```
+
+Opens browser at `http://localhost:3000`
+
+## How It Works
+
+This demo uses **local in-memory signaling** to test WebRTC connections between two peers on the same page. The `LocalSignaler` class simulates a signaling server by directly exchanging ICE candidates and SDP between peers.
+
+### Architecture
+
+- **LocalSignaler**: Implements the `Signaler` interface with local peer-to-peer communication
+- **Host (Peer A)**: Creates the offer (offerer role)
+- **Client (Peer B)**: Receives the offer and creates answer (answerer role)
+- **ICE Exchange**: Candidates are automatically exchanged between peers through the linked signalers
+
+## Usage Steps
+
+1. **Create Host** (Peer A)
+ - Click "1️⃣ Create Host Connection" on the left side
+ - The host will create an offer and display it
+ - Status changes to "Connecting"
+
+2. **Create Client** (Peer B)
+ - Click "2️⃣ Create Client Connection" on the right side
+ - The client receives the host's offer automatically
+ - Creates an answer and sends it back to the host
+ - Both peers exchange ICE candidates
+
+3. **Connection Established**
+ - Watch the status indicators turn green ("Connected")
+ - Activity logs show the connection progress
+
+4. **Send Messages**
+ - Type a message in either peer's input field
+ - Click "📤 Send" or press Enter
+ - Messages appear in the other peer's activity log
+
+## Features
+
+- ✅ **No signaling server required** - Everything runs locally
+- ✅ **Automatic ICE candidate exchange** - Signalers handle candidate exchange
+- ✅ **Real-time activity logs** - See exactly what's happening
+- ✅ **Connection state indicators** - Visual feedback for connection status
+- ✅ **Bidirectional messaging** - Send messages in both directions
+
+## Code Structure
+
+### demo.js
+
+```javascript
+// LocalSignaler - Implements local signaling
+class LocalSignaler {
+ addIceCandidate(candidate) // Called when local peer has a candidate
+ addListener(callback) // Listen for remote candidates
+ linkTo(remoteSignaler) // Connect two signalers together
+}
+
+// Create and link signalers
+const hostSignaler = new LocalSignaler('HOST', 'CLIENT')
+const clientSignaler = new LocalSignaler('CLIENT', 'HOST')
+hostSignaler.linkTo(clientSignaler)
+clientSignaler.linkTo(hostSignaler)
+
+// Create connections
+const hostConnection = new WebRTCRondevuConnection({
+ id: 'test-connection',
+ host: 'client-peer',
+ service: 'test.demo@1.0.0',
+ offer: null, // No offer = offerer role
+ context: new WebRTCContext(hostSignaler)
+})
+
+const clientConnection = new WebRTCRondevuConnection({
+ id: 'test-connection',
+ host: 'host-peer',
+ service: 'test.demo@1.0.0',
+ offer: hostConnection.connection.localDescription, // With offer = answerer role
+ context: new WebRTCContext(clientSignaler)
+})
+```
+
+### index.html
+
+- Side-by-side layout for Host and Client
+- Status indicators (disconnected/connecting/connected)
+- SDP display areas (offer/answer)
+- Message input and send buttons
+- Activity logs for each peer
+
+## Debugging
+
+Open the browser console to see detailed logs:
+
+- `[HOST]` - Logs from the host connection
+- `[CLIENT]` - Logs from the client connection
+- ICE candidate exchange
+- Connection state changes
+- Message send/receive events
+
+## Comparison: Local vs Remote Signaling
+
+### Local Signaling (This Demo)
+```javascript
+const signaler = new LocalSignaler('HOST', 'CLIENT')
+signaler.linkTo(remoteSignaler) // Direct link
+```
+
+**Pros**: No server, instant testing, no network latency
+**Cons**: Only works for same-page testing
+
+### Remote Signaling (Production)
+```javascript
+const api = new RondevuAPI('https://api.ronde.vu', credentials)
+const signaler = new RondevuSignaler(api, offerId)
+```
+
+**Pros**: Real peer discovery, works across networks
+**Cons**: Requires signaling server, network latency
+
+## Next Steps
+
+After testing locally, you can:
+
+1. Switch to `RondevuSignaler` for real signaling server testing
+2. Test across different browsers/devices
+3. Test with STUN/TURN servers for NAT traversal
+4. Implement production signaling with Rondevu API
+
+## Files
+
+- `index.html` - UI layout and styling
+- `demo.js` - Local signaling implementation and WebRTC logic
+- `README.md` - This file
diff --git a/demo/demo.js b/demo/demo.js
new file mode 100644
index 0000000..20afbd5
--- /dev/null
+++ b/demo/demo.js
@@ -0,0 +1,304 @@
+import { WebRTCRondevuConnection } from '../src/index.js'
+import { WebRTCContext } from '../src/webrtc-context.js'
+
+// Local signaling implementation for testing
+class LocalSignaler {
+ constructor(name, remoteName) {
+ this.name = name
+ this.remoteName = remoteName
+ this.iceCandidates = []
+ this.iceListeners = []
+ this.remote = null
+ this.remoteIceCandidates = []
+ this.offerCallbacks = []
+ this.answerCallbacks = []
+ }
+
+ // Link two signalers together
+ linkTo(remoteSignaler) {
+ this.remote = remoteSignaler
+ this.remoteIceCandidates = remoteSignaler.iceCandidates
+ }
+
+ // Set local offer (called when offer is created)
+ setOffer(offer) {
+ console.log(`[${this.name}] Setting offer`)
+ // Notify remote peer about the offer
+ if (this.remote) {
+ this.remote.offerCallbacks.forEach(callback => callback(offer))
+ }
+ }
+
+ // Set local answer (called when answer is created)
+ setAnswer(answer) {
+ console.log(`[${this.name}] Setting answer`)
+ // Notify remote peer about the answer
+ if (this.remote) {
+ this.remote.answerCallbacks.forEach(callback => callback(answer))
+ }
+ }
+
+ // Listen for offers from remote peer
+ addOfferListener(callback) {
+ this.offerCallbacks.push(callback)
+ return () => {
+ const index = this.offerCallbacks.indexOf(callback)
+ if (index > -1) {
+ this.offerCallbacks.splice(index, 1)
+ }
+ }
+ }
+
+ // Listen for answers from remote peer
+ addAnswerListener(callback) {
+ this.answerCallbacks.push(callback)
+ return () => {
+ const index = this.answerCallbacks.indexOf(callback)
+ if (index > -1) {
+ this.answerCallbacks.splice(index, 1)
+ }
+ }
+ }
+
+ // Add local ICE candidate (called by local connection)
+ addIceCandidate(candidate) {
+ console.log(`[${this.name}] Adding ICE candidate:`, candidate.candidate)
+ this.iceCandidates.push(candidate)
+
+ // Immediately send to remote peer if linked
+ if (this.remote) {
+ setTimeout(() => {
+ this.remote.iceListeners.forEach(listener => {
+ console.log(`[${this.name}] Sending ICE to ${this.remoteName}`)
+ listener(candidate)
+ })
+ }, 10)
+ }
+ }
+
+ // Listen for remote ICE candidates
+ addListener(callback) {
+ console.log(`[${this.name}] Adding ICE listener`)
+ this.iceListeners.push(callback)
+
+ // Send any existing remote candidates
+ this.remoteIceCandidates.forEach(candidate => {
+ setTimeout(() => callback(candidate), 10)
+ })
+
+ return () => {
+ const index = this.iceListeners.indexOf(callback)
+ if (index > -1) {
+ this.iceListeners.splice(index, 1)
+ }
+ }
+ }
+}
+
+// Create signalers for host and client
+const hostSignaler = new LocalSignaler('HOST', 'CLIENT')
+const clientSignaler = new LocalSignaler('CLIENT', 'HOST')
+
+// Link them together for bidirectional communication
+hostSignaler.linkTo(clientSignaler)
+clientSignaler.linkTo(hostSignaler)
+
+// Store connections
+let hostConnection = null
+let clientConnection = null
+
+// UI Update functions
+function updateStatus(peer, state) {
+ const statusEl = document.getElementById(`status-${peer}`)
+ if (statusEl) {
+ statusEl.className = `status ${state}`
+ statusEl.textContent = state.charAt(0).toUpperCase() + state.slice(1)
+ }
+}
+
+function addLog(peer, message) {
+ const logEl = document.getElementById(`log-${peer}`)
+ if (logEl) {
+ const time = new Date().toLocaleTimeString()
+ logEl.innerHTML += `
[${time}] ${message}
`
+ logEl.scrollTop = logEl.scrollHeight
+ }
+}
+
+// Create Host (Offerer)
+async function createHost() {
+ try {
+ addLog('a', 'Creating host connection (offerer)...')
+
+ const hostContext = new WebRTCContext(hostSignaler)
+
+ hostConnection = new WebRTCRondevuConnection({
+ id: 'test-connection',
+ host: 'client-peer',
+ service: 'test.demo@1.0.0',
+ offer: null,
+ context: hostContext,
+ })
+
+ // Listen for state changes
+ hostConnection.events.on('state-change', state => {
+ console.log('[HOST] State changed:', state)
+ updateStatus('a', state)
+ addLog('a', `State changed to: ${state}`)
+ })
+
+ // Listen for messages
+ hostConnection.events.on('message', message => {
+ console.log('[HOST] Received message:', message)
+ addLog('a', `📨 Received: ${message}`)
+ })
+
+ addLog('a', '✅ Host connection created')
+ updateStatus('a', 'connecting')
+
+ // Wait for host to be ready (offer created and set)
+ await hostConnection.ready
+ addLog('a', '✅ Host offer created')
+
+ // Get the offer
+ const offer = hostConnection.connection.localDescription
+ document.getElementById('offer-a').value = JSON.stringify(offer, null, 2)
+
+ addLog('a', 'Offer ready to send to client')
+ } catch (error) {
+ console.error('[HOST] Error:', error)
+ addLog('a', `❌ Error: ${error.message}`)
+ updateStatus('a', 'disconnected')
+ }
+}
+
+// Create Client (Answerer)
+async function createClient() {
+ try {
+ addLog('b', 'Creating client connection (answerer)...')
+
+ // Get offer from host
+ if (!hostConnection) {
+ alert('Please create host first!')
+ return
+ }
+
+ const offer = hostConnection.connection.localDescription
+ if (!offer) {
+ alert('Host offer not ready yet!')
+ return
+ }
+
+ addLog('b', 'Got offer from host')
+
+ const clientContext = new WebRTCContext(clientSignaler)
+
+ clientConnection = new WebRTCRondevuConnection({
+ id: 'test-connection',
+ host: 'host-peer',
+ service: 'test.demo@1.0.0',
+ offer: offer,
+ context: clientContext,
+ })
+
+ // Listen for state changes
+ clientConnection.events.on('state-change', state => {
+ console.log('[CLIENT] State changed:', state)
+ updateStatus('b', state)
+ addLog('b', `State changed to: ${state}`)
+ })
+
+ // Listen for messages
+ clientConnection.events.on('message', message => {
+ console.log('[CLIENT] Received message:', message)
+ addLog('b', `📨 Received: ${message}`)
+ })
+
+ addLog('b', '✅ Client connection created')
+ updateStatus('b', 'connecting')
+
+ // Wait for client to be ready
+ await clientConnection.ready
+ addLog('b', '✅ Client answer created')
+
+ // Get the answer
+ const answer = clientConnection.connection.localDescription
+ document.getElementById('answer-b').value = JSON.stringify(answer, null, 2)
+
+ // Set answer on host
+ addLog('b', 'Setting answer on host...')
+ await hostConnection.connection.setRemoteDescription(answer)
+ addLog('b', '✅ Answer set on host')
+ addLog('a', '✅ Answer received from client')
+ } catch (error) {
+ console.error('[CLIENT] Error:', error)
+ addLog('b', `❌ Error: ${error.message}`)
+ updateStatus('b', 'disconnected')
+ }
+}
+
+// Send test message from host to client
+function sendFromHost() {
+ if (!hostConnection) {
+ alert('Please create host first!')
+ return
+ }
+
+ const message = document.getElementById('message-a').value || 'Hello from Host!'
+ addLog('a', `📤 Sending: ${message}`)
+ hostConnection
+ .sendMessage(message)
+ .then(success => {
+ if (success) {
+ addLog('a', '✅ Message sent successfully')
+ } else {
+ addLog('a', '⚠️ Message queued (not connected)')
+ }
+ })
+ .catch(error => {
+ addLog('a', `❌ Error sending: ${error.message}`)
+ })
+}
+
+// Send test message from client to host
+function sendFromClient() {
+ if (!clientConnection) {
+ alert('Please create client first!')
+ return
+ }
+
+ const message = document.getElementById('message-b').value || 'Hello from Client!'
+ addLog('b', `📤 Sending: ${message}`)
+ clientConnection
+ .sendMessage(message)
+ .then(success => {
+ if (success) {
+ addLog('b', '✅ Message sent successfully')
+ } else {
+ addLog('b', '⚠️ Message queued (not connected)')
+ }
+ })
+ .catch(error => {
+ addLog('b', `❌ Error sending: ${error.message}`)
+ })
+}
+
+// Attach event listeners when DOM is ready
+document.addEventListener('DOMContentLoaded', () => {
+ // Clear all textareas on load
+ document.getElementById('offer-a').value = ''
+ document.getElementById('answer-b').value = ''
+
+ // Make functions globally available (for console testing)
+ window.createHost = createHost
+ window.createClient = createClient
+ window.sendFromHost = sendFromHost
+ window.sendFromClient = sendFromClient
+
+ console.log('🚀 Local signaling test loaded')
+ console.log('Steps:')
+ console.log('1. Click "Create Host" (Peer A)')
+ console.log('2. Click "Create Client" (Peer B)')
+ console.log('3. Wait for connection to establish')
+ console.log('4. Send messages between peers')
+})
diff --git a/demo/index.html b/demo/index.html
new file mode 100644
index 0000000..9bfdc52
--- /dev/null
+++ b/demo/index.html
@@ -0,0 +1,280 @@
+
+
+
+
+
+ Rondevu WebRTC Local Test
+
+
+
+
+
🔗 Rondevu WebRTC Local Test
+
+
+
+
+
Peer A (Host/Offerer)
+
+
Disconnected
+
+
+
+
+
+
+
Local Offer (SDP)
+
+
+
+
+
Send Message
+
+
+
+
+
+
+
+
+
+
+
+
Peer B (Client/Answerer)
+
+
Disconnected
+
+
+
+
+
+
+
Local Answer (SDP)
+
+
+
+
+
Send Message
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/eslint.config.js b/eslint.config.js
new file mode 100644
index 0000000..018b907
--- /dev/null
+++ b/eslint.config.js
@@ -0,0 +1,52 @@
+import js from '@eslint/js'
+import tsPlugin from '@typescript-eslint/eslint-plugin'
+import tsParser from '@typescript-eslint/parser'
+import prettierConfig from 'eslint-config-prettier'
+import prettierPlugin from 'eslint-plugin-prettier'
+import unicorn from 'eslint-plugin-unicorn'
+import globals from 'globals'
+
+export default [
+ js.configs.recommended,
+ {
+ files: ['**/*.ts', '**/*.tsx', '**/*.js'],
+ languageOptions: {
+ parser: tsParser,
+ parserOptions: {
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ },
+ globals: {
+ ...globals.browser,
+ ...globals.node,
+ RTCPeerConnection: 'readonly',
+ RTCIceCandidate: 'readonly',
+ RTCSessionDescriptionInit: 'readonly',
+ RTCIceCandidateInit: 'readonly',
+ BufferSource: 'readonly',
+ },
+ },
+ plugins: {
+ '@typescript-eslint': tsPlugin,
+ prettier: prettierPlugin,
+ unicorn: unicorn,
+ },
+ rules: {
+ ...tsPlugin.configs.recommended.rules,
+ ...prettierConfig.rules,
+ 'prettier/prettier': 'error',
+ '@typescript-eslint/no-explicit-any': 'off',
+ '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
+ 'unicorn/filename-case': [
+ 'error',
+ {
+ case: 'kebabCase',
+ ignore: ['^README\\.md$'],
+ },
+ ],
+ },
+ },
+ {
+ ignores: ['dist/**', 'node_modules/**', '*.config.js'],
+ },
+]
diff --git a/package-lock.json b/package-lock.json
index 84240b0..532e61f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,7 +13,723 @@
"@xtr-dev/rondevu-client": "^0.9.2"
},
"devDependencies": {
- "typescript": "^5.9.3"
+ "@eslint/js": "^9.39.1",
+ "@typescript-eslint/eslint-plugin": "^8.48.1",
+ "@typescript-eslint/parser": "^8.48.1",
+ "eslint": "^9.39.1",
+ "eslint-config-prettier": "^10.1.8",
+ "eslint-plugin-prettier": "^5.5.4",
+ "eslint-plugin-unicorn": "^62.0.0",
+ "globals": "^16.5.0",
+ "prettier": "^3.7.4",
+ "typescript": "^5.9.3",
+ "vite": "^7.2.6"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "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.3",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
+ "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.1",
+ "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/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/@eslint/eslintrc/node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.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/@noble/ed25519": {
@@ -25,6 +741,575 @@
"url": "https://paulmillr.com/funding/"
}
},
+ "node_modules/@pkgr/core": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
+ "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/pkgr"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz",
+ "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz",
+ "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz",
+ "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz",
+ "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz",
+ "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz",
+ "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz",
+ "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz",
+ "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz",
+ "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz",
+ "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz",
+ "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz",
+ "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz",
+ "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz",
+ "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz",
+ "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz",
+ "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz",
+ "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz",
+ "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz",
+ "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz",
+ "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz",
+ "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz",
+ "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "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/@typescript-eslint/eslint-plugin": {
+ "version": "8.48.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.1.tgz",
+ "integrity": "sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "8.48.1",
+ "@typescript-eslint/type-utils": "8.48.1",
+ "@typescript-eslint/utils": "8.48.1",
+ "@typescript-eslint/visitor-keys": "8.48.1",
+ "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.48.1",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.48.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.1.tgz",
+ "integrity": "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.48.1",
+ "@typescript-eslint/types": "8.48.1",
+ "@typescript-eslint/typescript-estree": "8.48.1",
+ "@typescript-eslint/visitor-keys": "8.48.1",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.48.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.1.tgz",
+ "integrity": "sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.48.1",
+ "@typescript-eslint/types": "^8.48.1",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.48.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.1.tgz",
+ "integrity": "sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.48.1",
+ "@typescript-eslint/visitor-keys": "8.48.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/tsconfig-utils": {
+ "version": "8.48.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.1.tgz",
+ "integrity": "sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==",
+ "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.48.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.1.tgz",
+ "integrity": "sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.48.1",
+ "@typescript-eslint/typescript-estree": "8.48.1",
+ "@typescript-eslint/utils": "8.48.1",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.48.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.1.tgz",
+ "integrity": "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.48.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.1.tgz",
+ "integrity": "sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.48.1",
+ "@typescript-eslint/tsconfig-utils": "8.48.1",
+ "@typescript-eslint/types": "8.48.1",
+ "@typescript-eslint/visitor-keys": "8.48.1",
+ "debug": "^4.3.4",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.48.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.1.tgz",
+ "integrity": "sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.7.0",
+ "@typescript-eslint/scope-manager": "8.48.1",
+ "@typescript-eslint/types": "8.48.1",
+ "@typescript-eslint/typescript-estree": "8.48.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.48.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.1.tgz",
+ "integrity": "sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.48.1",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
"node_modules/@xtr-dev/rondevu-client": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/@xtr-dev/rondevu-client/-/rondevu-client-0.9.2.tgz",
@@ -34,6 +1319,1511 @@
"@noble/ed25519": "^3.0.0"
}
},
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-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/baseline-browser-mapping": {
+ "version": "2.9.4",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.4.tgz",
+ "integrity": "sha512-ZCQ9GEWl73BVm8bu5Fts8nt7MHdbt5vY9bP6WGnUh+r3l8M7CgfyTlwsgCbMC66BNxPr6Xoce3j66Ms5YUQTNA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/builtin-modules": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-5.0.0.tgz",
+ "integrity": "sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/caniuse-lite": {
+ "version": "1.0.30001759",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz",
+ "integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "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/change-case": {
+ "version": "5.4.4",
+ "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz",
+ "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/ci-info": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz",
+ "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/clean-regexp": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz",
+ "integrity": "sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^1.0.5"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/clean-regexp/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/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/core-js-compat": {
+ "version": "3.47.0",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.47.0.tgz",
+ "integrity": "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.28.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
+ "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/electron-to-chromium": {
+ "version": "1.5.266",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.266.tgz",
+ "integrity": "sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "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"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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",
+ "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-config-prettier": {
+ "version": "10.1.8",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
+ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-config-prettier"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-prettier": {
+ "version": "5.5.4",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz",
+ "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prettier-linter-helpers": "^1.0.0",
+ "synckit": "^0.11.7"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-plugin-prettier"
+ },
+ "peerDependencies": {
+ "@types/eslint": ">=8.0.0",
+ "eslint": ">=8.0.0",
+ "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
+ "prettier": ">=3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/eslint": {
+ "optional": true
+ },
+ "eslint-config-prettier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-unicorn": {
+ "version": "62.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-62.0.0.tgz",
+ "integrity": "sha512-HIlIkGLkvf29YEiS/ImuDZQbP12gWyx5i3C6XrRxMvVdqMroCI9qoVYCoIl17ChN+U89pn9sVwLxhIWj5nEc7g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@eslint-community/eslint-utils": "^4.9.0",
+ "@eslint/plugin-kit": "^0.4.0",
+ "change-case": "^5.4.4",
+ "ci-info": "^4.3.1",
+ "clean-regexp": "^1.0.0",
+ "core-js-compat": "^3.46.0",
+ "esquery": "^1.6.0",
+ "find-up-simple": "^1.0.1",
+ "globals": "^16.4.0",
+ "indent-string": "^5.0.0",
+ "is-builtin-module": "^5.0.0",
+ "jsesc": "^3.1.0",
+ "pluralize": "^8.0.0",
+ "regexp-tree": "^0.1.27",
+ "regjsparser": "^0.13.0",
+ "semver": "^7.7.3",
+ "strip-indent": "^4.1.1"
+ },
+ "engines": {
+ "node": "^20.10.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1"
+ },
+ "peerDependencies": {
+ "eslint": ">=9.38.0"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/eslint/node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/eslint/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree/node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/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-diff": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
+ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "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/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "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/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/find-up-simple": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz",
+ "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "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/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/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": "16.5.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz",
+ "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==",
+ "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": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/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/indent-string": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz",
+ "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-builtin-module": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-5.0.0.tgz",
+ "integrity": "sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "builtin-modules": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/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.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-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/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/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/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
+ "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/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pluralize": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
+ "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "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.7.4",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz",
+ "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-linter-helpers": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+ "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-diff": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "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/regexp-tree": {
+ "version": "0.1.27",
+ "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz",
+ "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "regexp-tree": "bin/regexp-tree"
+ }
+ },
+ "node_modules/regjsparser": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz",
+ "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "jsesc": "~3.1.0"
+ },
+ "bin": {
+ "regjsparser": "bin/parser"
+ }
+ },
+ "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/rollup": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz",
+ "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.53.3",
+ "@rollup/rollup-android-arm64": "4.53.3",
+ "@rollup/rollup-darwin-arm64": "4.53.3",
+ "@rollup/rollup-darwin-x64": "4.53.3",
+ "@rollup/rollup-freebsd-arm64": "4.53.3",
+ "@rollup/rollup-freebsd-x64": "4.53.3",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.53.3",
+ "@rollup/rollup-linux-arm-musleabihf": "4.53.3",
+ "@rollup/rollup-linux-arm64-gnu": "4.53.3",
+ "@rollup/rollup-linux-arm64-musl": "4.53.3",
+ "@rollup/rollup-linux-loong64-gnu": "4.53.3",
+ "@rollup/rollup-linux-ppc64-gnu": "4.53.3",
+ "@rollup/rollup-linux-riscv64-gnu": "4.53.3",
+ "@rollup/rollup-linux-riscv64-musl": "4.53.3",
+ "@rollup/rollup-linux-s390x-gnu": "4.53.3",
+ "@rollup/rollup-linux-x64-gnu": "4.53.3",
+ "@rollup/rollup-linux-x64-musl": "4.53.3",
+ "@rollup/rollup-openharmony-arm64": "4.53.3",
+ "@rollup/rollup-win32-arm64-msvc": "4.53.3",
+ "@rollup/rollup-win32-ia32-msvc": "4.53.3",
+ "@rollup/rollup-win32-x64-gnu": "4.53.3",
+ "@rollup/rollup-win32-x64-msvc": "4.53.3",
+ "fsevents": "~2.3.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/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-indent": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.1.1.tgz",
+ "integrity": "sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/synckit": {
+ "version": "0.11.11",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
+ "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@pkgr/core": "^0.2.9"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/synckit"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "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/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",
@@ -47,6 +2837,161 @@
"engines": {
"node": ">=14.17"
}
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz",
+ "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "7.2.6",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.6.tgz",
+ "integrity": "sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "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"
+ },
+ "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
+ }
+ }
+ },
+ "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
index 9036d68..f29759c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@xtr-dev/rondevu-client",
- "version": "0.9.2",
+ "version": "0.10.0",
"description": "TypeScript client for Rondevu with durable WebRTC connections, automatic reconnection, and message queuing",
"type": "module",
"main": "dist/index.js",
@@ -8,6 +8,10 @@
"scripts": {
"build": "tsc",
"typecheck": "tsc --noEmit",
+ "dev": "vite",
+ "lint": "eslint src demo --ext .ts,.tsx,.js",
+ "lint:fix": "eslint src demo --ext .ts,.tsx,.js --fix",
+ "format": "prettier --write \"src/**/*.{ts,tsx,js}\" \"demo/**/*.{ts,tsx,js,html}\"",
"prepublishOnly": "npm run build"
},
"keywords": [
@@ -20,14 +24,23 @@
"author": "",
"license": "MIT",
"devDependencies": {
- "typescript": "^5.9.3"
+ "@eslint/js": "^9.39.1",
+ "@typescript-eslint/eslint-plugin": "^8.48.1",
+ "@typescript-eslint/parser": "^8.48.1",
+ "eslint": "^9.39.1",
+ "eslint-config-prettier": "^10.1.8",
+ "eslint-plugin-prettier": "^5.5.4",
+ "eslint-plugin-unicorn": "^62.0.0",
+ "globals": "^16.5.0",
+ "prettier": "^3.7.4",
+ "typescript": "^5.9.3",
+ "vite": "^7.2.6"
},
"files": [
"dist",
"README.md"
],
"dependencies": {
- "@noble/ed25519": "^3.0.0",
- "@xtr-dev/rondevu-client": "^0.9.2"
+ "@noble/ed25519": "^3.0.0"
}
}
diff --git a/src/api.ts b/src/api.ts
index 061fcda..09d2069 100644
--- a/src/api.ts
+++ b/src/api.ts
@@ -2,55 +2,83 @@
* Rondevu API Client - Single class for all API endpoints
*/
+import * as ed25519 from '@noble/ed25519'
+
+// Set SHA-512 hash function for ed25519 (required in @noble/ed25519 v3+)
+ed25519.hashes.sha512Async = async (message: Uint8Array) => {
+ return new Uint8Array(await crypto.subtle.digest('SHA-512', message as BufferSource))
+}
+
export interface Credentials {
- peerId: string;
- secret: string;
+ peerId: string
+ secret: string
+}
+
+export interface Keypair {
+ publicKey: string
+ privateKey: string
}
export interface OfferRequest {
- sdp: string;
- topics?: string[];
- ttl?: number;
- secret?: string;
+ sdp: string
+ topics?: string[]
+ ttl?: number
+ secret?: string
}
export interface Offer {
- id: string;
- peerId: string;
- sdp: string;
- topics: string[];
- ttl: number;
- createdAt: number;
- expiresAt: number;
- answererPeerId?: string;
+ id: string
+ peerId: string
+ sdp: string
+ topics: string[]
+ ttl: number
+ createdAt: number
+ expiresAt: number
+ answererPeerId?: string
}
export interface ServiceRequest {
- username: string;
- serviceFqn: string;
- sdp: string;
- ttl?: number;
- isPublic?: boolean;
- metadata?: Record;
- signature: string;
- message: string;
+ username: string
+ serviceFqn: string
+ sdp: string
+ ttl?: number
+ isPublic?: boolean
+ metadata?: Record
+ signature: string
+ message: string
}
export interface Service {
- serviceId: string;
- uuid: string;
- offerId: string;
- username: string;
- serviceFqn: string;
- isPublic: boolean;
- metadata?: Record;
- createdAt: number;
- expiresAt: number;
+ serviceId: string
+ uuid: string
+ offerId: string
+ username: string
+ serviceFqn: string
+ isPublic: boolean
+ metadata?: Record
+ createdAt: number
+ expiresAt: number
}
export interface IceCandidate {
- candidate: RTCIceCandidateInit;
- createdAt: number;
+ candidate: RTCIceCandidateInit
+ createdAt: number
+}
+
+/**
+ * Helper: Convert Uint8Array to base64 string
+ */
+function bytesToBase64(bytes: Uint8Array): string {
+ const binString = Array.from(bytes, byte => String.fromCodePoint(byte)).join('')
+ return btoa(binString)
+}
+
+/**
+ * Helper: Convert base64 string to Uint8Array
+ */
+function base64ToBytes(base64: string): Uint8Array {
+ const binString = atob(base64)
+ return Uint8Array.from(binString, char => char.codePointAt(0)!)
}
/**
@@ -67,11 +95,56 @@ export class RondevuAPI {
*/
private getAuthHeader(): Record {
if (!this.credentials) {
- return {};
+ return {}
}
return {
- 'Authorization': `Bearer ${this.credentials.peerId}:${this.credentials.secret}`
- };
+ Authorization: `Bearer ${this.credentials.peerId}:${this.credentials.secret}`,
+ }
+ }
+
+ // ============================================
+ // Ed25519 Cryptography Helpers
+ // ============================================
+
+ /**
+ * Generate an Ed25519 keypair for username claiming and service publishing
+ */
+ static async generateKeypair(): Promise {
+ const privateKey = ed25519.utils.randomSecretKey()
+ const publicKey = await ed25519.getPublicKeyAsync(privateKey)
+
+ return {
+ publicKey: bytesToBase64(publicKey),
+ privateKey: bytesToBase64(privateKey),
+ }
+ }
+
+ /**
+ * Sign a message with an Ed25519 private key
+ */
+ static async signMessage(message: string, privateKeyBase64: string): Promise {
+ const privateKey = base64ToBytes(privateKeyBase64)
+ const encoder = new TextEncoder()
+ const messageBytes = encoder.encode(message)
+
+ const signature = await ed25519.signAsync(messageBytes, privateKey)
+ return bytesToBase64(signature)
+ }
+
+ /**
+ * Verify a signature
+ */
+ static async verifySignature(
+ message: string,
+ signatureBase64: string,
+ publicKeyBase64: string
+ ): Promise {
+ const publicKey = base64ToBytes(publicKeyBase64)
+ const signature = base64ToBytes(signatureBase64)
+ const encoder = new TextEncoder()
+ const messageBytes = encoder.encode(message)
+
+ return await ed25519.verifyAsync(signature, messageBytes, publicKey)
}
// ============================================
@@ -84,15 +157,15 @@ export class RondevuAPI {
async register(): Promise {
const response = await fetch(`${this.baseUrl}/register`, {
method: 'POST',
- headers: { 'Content-Type': 'application/json' }
- });
+ headers: { 'Content-Type': 'application/json' },
+ })
if (!response.ok) {
- const error = await response.json().catch(() => ({ error: 'Unknown error' }));
- throw new Error(`Registration failed: ${error.error || response.statusText}`);
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }))
+ throw new Error(`Registration failed: ${error.error || response.statusText}`)
}
- return await response.json();
+ return await response.json()
}
// ============================================
@@ -107,17 +180,17 @@ export class RondevuAPI {
method: 'POST',
headers: {
'Content-Type': 'application/json',
- ...this.getAuthHeader()
+ ...this.getAuthHeader(),
},
- body: JSON.stringify({ offers })
- });
+ body: JSON.stringify({ offers }),
+ })
if (!response.ok) {
- const error = await response.json().catch(() => ({ error: 'Unknown error' }));
- throw new Error(`Failed to create offers: ${error.error || response.statusText}`);
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }))
+ throw new Error(`Failed to create offers: ${error.error || response.statusText}`)
}
- return await response.json();
+ return await response.json()
}
/**
@@ -125,15 +198,15 @@ export class RondevuAPI {
*/
async getOffer(offerId: string): Promise {
const response = await fetch(`${this.baseUrl}/offers/${offerId}`, {
- headers: this.getAuthHeader()
- });
+ headers: this.getAuthHeader(),
+ })
if (!response.ok) {
- const error = await response.json().catch(() => ({ error: 'Unknown error' }));
- throw new Error(`Failed to get offer: ${error.error || response.statusText}`);
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }))
+ throw new Error(`Failed to get offer: ${error.error || response.statusText}`)
}
- return await response.json();
+ return await response.json()
}
/**
@@ -144,14 +217,14 @@ export class RondevuAPI {
method: 'POST',
headers: {
'Content-Type': 'application/json',
- ...this.getAuthHeader()
+ ...this.getAuthHeader(),
},
- body: JSON.stringify({ sdp, secret })
- });
+ body: JSON.stringify({ sdp, secret }),
+ })
if (!response.ok) {
- const error = await response.json().catch(() => ({ error: 'Unknown error' }));
- throw new Error(`Failed to answer offer: ${error.error || response.statusText}`);
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }))
+ throw new Error(`Failed to answer offer: ${error.error || response.statusText}`)
}
}
@@ -160,19 +233,19 @@ export class RondevuAPI {
*/
async getAnswer(offerId: string): Promise<{ sdp: string } | null> {
const response = await fetch(`${this.baseUrl}/offers/${offerId}/answer`, {
- headers: this.getAuthHeader()
- });
+ headers: this.getAuthHeader(),
+ })
if (response.status === 404) {
- return null; // No answer yet
+ return null // No answer yet
}
if (!response.ok) {
- const error = await response.json().catch(() => ({ error: 'Unknown error' }));
- throw new Error(`Failed to get answer: ${error.error || response.statusText}`);
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }))
+ throw new Error(`Failed to get answer: ${error.error || response.statusText}`)
}
- return await response.json();
+ return await response.json()
}
/**
@@ -180,15 +253,15 @@ export class RondevuAPI {
*/
async searchOffers(topic: string): Promise {
const response = await fetch(`${this.baseUrl}/offers?topic=${encodeURIComponent(topic)}`, {
- headers: this.getAuthHeader()
- });
+ headers: this.getAuthHeader(),
+ })
if (!response.ok) {
- const error = await response.json().catch(() => ({ error: 'Unknown error' }));
- throw new Error(`Failed to search offers: ${error.error || response.statusText}`);
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }))
+ throw new Error(`Failed to search offers: ${error.error || response.statusText}`)
}
- return await response.json();
+ return await response.json()
}
// ============================================
@@ -203,14 +276,14 @@ export class RondevuAPI {
method: 'POST',
headers: {
'Content-Type': 'application/json',
- ...this.getAuthHeader()
+ ...this.getAuthHeader(),
},
- body: JSON.stringify({ candidates })
- });
+ body: JSON.stringify({ candidates }),
+ })
if (!response.ok) {
- const error = await response.json().catch(() => ({ error: 'Unknown error' }));
- throw new Error(`Failed to add ICE candidates: ${error.error || response.statusText}`);
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }))
+ throw new Error(`Failed to add ICE candidates: ${error.error || response.statusText}`)
}
}
@@ -221,14 +294,14 @@ export class RondevuAPI {
const response = await fetch(
`${this.baseUrl}/offers/${offerId}/ice-candidates?since=${since}`,
{ headers: this.getAuthHeader() }
- );
+ )
if (!response.ok) {
- const error = await response.json().catch(() => ({ error: 'Unknown error' }));
- throw new Error(`Failed to get ICE candidates: ${error.error || response.statusText}`);
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }))
+ throw new Error(`Failed to get ICE candidates: ${error.error || response.statusText}`)
}
- return await response.json();
+ return await response.json()
}
// ============================================
@@ -243,17 +316,17 @@ export class RondevuAPI {
method: 'POST',
headers: {
'Content-Type': 'application/json',
- ...this.getAuthHeader()
+ ...this.getAuthHeader(),
},
- body: JSON.stringify(service)
- });
+ body: JSON.stringify(service),
+ })
if (!response.ok) {
- const error = await response.json().catch(() => ({ error: 'Unknown error' }));
- throw new Error(`Failed to publish service: ${error.error || response.statusText}`);
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }))
+ throw new Error(`Failed to publish service: ${error.error || response.statusText}`)
}
- return await response.json();
+ return await response.json()
}
/**
@@ -261,15 +334,15 @@ export class RondevuAPI {
*/
async getService(uuid: string): Promise {
const response = await fetch(`${this.baseUrl}/services/${uuid}`, {
- headers: this.getAuthHeader()
- });
+ headers: this.getAuthHeader(),
+ })
if (!response.ok) {
- const error = await response.json().catch(() => ({ error: 'Unknown error' }));
- throw new Error(`Failed to get service: ${error.error || response.statusText}`);
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }))
+ throw new Error(`Failed to get service: ${error.error || response.statusText}`)
}
- return await response.json();
+ return await response.json()
}
/**
@@ -279,14 +352,14 @@ export class RondevuAPI {
const response = await fetch(
`${this.baseUrl}/services?username=${encodeURIComponent(username)}`,
{ headers: this.getAuthHeader() }
- );
+ )
if (!response.ok) {
- const error = await response.json().catch(() => ({ error: 'Unknown error' }));
- throw new Error(`Failed to search services: ${error.error || response.statusText}`);
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }))
+ throw new Error(`Failed to search services: ${error.error || response.statusText}`)
}
- return await response.json();
+ return await response.json()
}
/**
@@ -296,14 +369,14 @@ export class RondevuAPI {
const response = await fetch(
`${this.baseUrl}/services?serviceFqn=${encodeURIComponent(serviceFqn)}`,
{ headers: this.getAuthHeader() }
- );
+ )
if (!response.ok) {
- const error = await response.json().catch(() => ({ error: 'Unknown error' }));
- throw new Error(`Failed to search services: ${error.error || response.statusText}`);
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }))
+ throw new Error(`Failed to search services: ${error.error || response.statusText}`)
}
- return await response.json();
+ return await response.json()
}
/**
@@ -313,14 +386,14 @@ export class RondevuAPI {
const response = await fetch(
`${this.baseUrl}/services?username=${encodeURIComponent(username)}&serviceFqn=${encodeURIComponent(serviceFqn)}`,
{ headers: this.getAuthHeader() }
- );
+ )
if (!response.ok) {
- const error = await response.json().catch(() => ({ error: 'Unknown error' }));
- throw new Error(`Failed to search services: ${error.error || response.statusText}`);
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }))
+ throw new Error(`Failed to search services: ${error.error || response.statusText}`)
}
- return await response.json();
+ return await response.json()
}
// ============================================
@@ -333,14 +406,14 @@ export class RondevuAPI {
async checkUsername(username: string): Promise<{ available: boolean; owner?: string }> {
const response = await fetch(
`${this.baseUrl}/usernames/${encodeURIComponent(username)}/check`
- );
+ )
if (!response.ok) {
- const error = await response.json().catch(() => ({ error: 'Unknown error' }));
- throw new Error(`Failed to check username: ${error.error || response.statusText}`);
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }))
+ throw new Error(`Failed to check username: ${error.error || response.statusText}`)
}
- return await response.json();
+ return await response.json()
}
/**
@@ -356,20 +429,20 @@ export class RondevuAPI {
method: 'POST',
headers: {
'Content-Type': 'application/json',
- ...this.getAuthHeader()
+ ...this.getAuthHeader(),
},
body: JSON.stringify({
publicKey,
signature,
- message
- })
- });
+ message,
+ }),
+ })
if (!response.ok) {
- const error = await response.json().catch(() => ({ error: 'Unknown error' }));
- throw new Error(`Failed to claim username: ${error.error || response.statusText}`);
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }))
+ throw new Error(`Failed to claim username: ${error.error || response.statusText}`)
}
- return await response.json();
+ return await response.json()
}
}
diff --git a/src/bin.ts b/src/bin.ts
index d29ef24..9d02d65 100644
--- a/src/bin.ts
+++ b/src/bin.ts
@@ -1,15 +1,42 @@
-
+/**
+ * Binnable - A cleanup function that can be synchronous or asynchronous
+ *
+ * Used to unsubscribe from events, close connections, or perform other cleanup operations.
+ */
export type Binnable = () => void | Promise
+/**
+ * Create a cleanup function collector (garbage bin)
+ *
+ * Collects cleanup functions and provides a single `clean()` method to execute all of them.
+ * Useful for managing multiple cleanup operations in a single place.
+ *
+ * @returns A function that accepts cleanup functions and has a `clean()` method
+ *
+ * @example
+ * ```typescript
+ * const bin = createBin();
+ *
+ * // Add cleanup functions
+ * bin(
+ * () => console.log('Cleanup 1'),
+ * () => connection.close(),
+ * () => clearInterval(timer)
+ * );
+ *
+ * // Later, clean everything
+ * bin.clean(); // Executes all cleanup functions
+ * ```
+ */
export const createBin = () => {
const bin: Binnable[] = []
- return Object.assign(
- (...rubbish: Binnable[]) => bin.push(...rubbish),
- {
- clean: (): void => {
- bin.forEach(binnable => binnable())
- bin.length = 0
- }
- }
- )
-}
\ No newline at end of file
+ return Object.assign((...rubbish: Binnable[]) => bin.push(...rubbish), {
+ /**
+ * Execute all cleanup functions and clear the bin
+ */
+ clean: (): void => {
+ bin.forEach(binnable => binnable())
+ bin.length = 0
+ },
+ })
+}
diff --git a/src/connection-manager.ts b/src/connection-manager.ts
deleted file mode 100644
index 5faa882..0000000
--- a/src/connection-manager.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-/**
- * ConnectionManager - Manages WebRTC peer connections
- */
-
-export class ConnectionManager {
- constructor() {
- // TODO: Initialize connection manager
- }
-}
diff --git a/src/connection.ts b/src/connection.ts
index 666733e..5f69dcd 100644
--- a/src/connection.ts
+++ b/src/connection.ts
@@ -1,76 +1,229 @@
-import {ConnectionEvents, ConnectionInterface, Message, QueueMessageOptions, Signaler} from "./types";
-import {EventBus} from "./event-bus";
-import {createBin} from "./bin";
+import {
+ ConnectionEvents,
+ ConnectionInterface,
+ ConnectionStates,
+ isConnectionState,
+ Message,
+ QueueMessageOptions,
+ Signaler,
+} from './types.js'
+import { EventBus } from './event-bus.js'
+import { createBin } from './bin.js'
+import { WebRTCContext } from './webrtc-context'
+export type WebRTCRondevuConnectionOptions = {
+ id: string
+ service: string
+ offer: RTCSessionDescriptionInit | null
+ context: WebRTCContext
+}
+
+/**
+ * WebRTCRondevuConnection - WebRTC peer connection wrapper with Rondevu signaling
+ *
+ * Manages a WebRTC peer connection lifecycle including:
+ * - Automatic offer/answer creation based on role
+ * - ICE candidate exchange via Rondevu signaling server
+ * - Connection state management with type-safe events
+ * - Data channel creation and message handling
+ *
+ * The connection automatically determines its role (offerer or answerer) based on whether
+ * an offer is provided in the constructor. The offerer creates the data channel, while
+ * the answerer receives it via the 'datachannel' event.
+ *
+ * @example
+ * ```typescript
+ * // Offerer side (creates offer)
+ * const connection = new WebRTCRondevuConnection(
+ * 'conn-123',
+ * 'peer-username',
+ * 'chat.service@1.0.0'
+ * );
+ *
+ * await connection.ready; // Wait for local offer
+ * const sdp = connection.connection.localDescription!.sdp!;
+ * // Send sdp to signaling server...
+ *
+ * // Answerer side (receives offer)
+ * const connection = new WebRTCRondevuConnection(
+ * 'conn-123',
+ * 'peer-username',
+ * 'chat.service@1.0.0',
+ * { type: 'offer', sdp: remoteOfferSdp }
+ * );
+ *
+ * await connection.ready; // Wait for local answer
+ * const answerSdp = connection.connection.localDescription!.sdp!;
+ * // Send answer to signaling server...
+ *
+ * // Both sides: Set up signaler and listen for state changes
+ * connection.setSignaler(signaler);
+ * connection.events.on('state-change', (state) => {
+ * console.log('Connection state:', state);
+ * });
+ * ```
+ */
export class WebRTCRondevuConnection implements ConnectionInterface {
- private readonly connection: RTCPeerConnection;
- private readonly side: 'offer' | 'answer';
- public readonly expiresAt: number = 0;
- public readonly lastActive: number = 0;
- public readonly events: EventBus = new EventBus();
- private signaler!: Signaler; // Will be set by setSignaler()
- private readonly _ready: Promise;
- private _state: ConnectionInterface['state'] = 'disconnected';
+ private readonly side: 'offer' | 'answer'
+ public readonly expiresAt: number = 0
+ public readonly lastActive: number = 0
+ public readonly events: EventBus = new EventBus()
+ public readonly ready: Promise
private iceBin = createBin()
+ private ctx: WebRTCContext
+ public id: string
+ public service: string
+ private _conn: RTCPeerConnection | null = null
+ private _state: ConnectionInterface['state'] = 'disconnected'
- constructor(
- public readonly id: string,
- public readonly host: string,
- public readonly service: string,
- offer?: RTCSessionDescriptionInit) {
- this.connection = new RTCPeerConnection();
- this.side = offer ? 'answer' : 'offer';
- const ready = offer
- ? this.connection.setRemoteDescription(offer)
- .then(() => this.connection.createAnswer())
- .then(answer => this.connection.setLocalDescription(answer))
- : this.connection.createOffer()
- .then(offer => this.connection.setLocalDescription(offer));
- this._ready = ready.then(() => this.setState('connecting'))
- .then(() => this.startIceListeners())
- }
+ constructor({ context: ctx, offer, id, service }: WebRTCRondevuConnectionOptions) {
+ this.ctx = ctx
+ this.id = id
+ this.service = service
+ this._conn = ctx.createPeerConnection()
+ this.side = offer ? 'answer' : 'offer'
- private setState(state: ConnectionInterface['state']) {
- this._state = state;
- this.events.emit('state-change', state);
- }
-
- private startIceListeners() {
- const listener = ({candidate}: {candidate: RTCIceCandidate | null}) => {
- if (candidate) this.signaler.addIceCandidate(candidate)
+ // setup data channel
+ if (offer) {
+ this._conn.addEventListener('datachannel', e => {
+ const channel = e.channel
+ channel.addEventListener('message', e => {
+ console.log('Message from peer:', e)
+ })
+ channel.addEventListener('open', () => {
+ channel.send('I am ' + this.side)
+ })
+ })
+ } else {
+ const channel = this._conn.createDataChannel('vu.ronde.protocol')
+ channel.addEventListener('message', e => {
+ console.log('Message from peer:', e)
+ })
+ channel.addEventListener('open', () => {
+ channel.send('I am ' + this.side)
+ })
}
- this.connection.addEventListener('icecandidate', listener)
+
+ // setup description exchange
+ this.ready = offer
+ ? this._conn
+ .setRemoteDescription(offer)
+ .then(() => this._conn?.createAnswer())
+ .then(async answer => {
+ if (!answer || !this._conn) throw new Error('Connection disappeared')
+ await this._conn.setLocalDescription(answer)
+ return await ctx.signaler.setAnswer(answer)
+ })
+ : this._conn.createOffer().then(async offer => {
+ if (!this._conn) throw new Error('Connection disappeared')
+ await this._conn.setLocalDescription(offer)
+ return await ctx.signaler.setOffer(offer)
+ })
+
+ // propagate connection state changes
+ this._conn.addEventListener('connectionstatechange', () => {
+ console.log(this.side, 'connection state changed: ', this._conn!.connectionState)
+ const state = isConnectionState(this._conn!.connectionState)
+ ? this._conn!.connectionState
+ : 'disconnected'
+ this.setState(state)
+ })
+
+ this._conn.addEventListener('iceconnectionstatechange', () => {
+ console.log(this.side, 'ice connection state changed: ', this._conn!.iceConnectionState)
+ })
+
+ // start ICE candidate exchange when gathering begins
+ this._conn.addEventListener('icegatheringstatechange', () => {
+ if (this._conn!.iceGatheringState === 'gathering') {
+ this.startIce()
+ } else if (this._conn!.iceGatheringState === 'complete') {
+ this.stopIce()
+ }
+ })
+ }
+
+ /**
+ * Getter method for retrieving the current connection.
+ *
+ * @return {RTCPeerConnection|null} The current connection instance.
+ */
+ public get connection(): RTCPeerConnection | null {
+ return this._conn
+ }
+
+ /**
+ * Update connection state and emit state-change event
+ */
+ private setState(state: ConnectionInterface['state']) {
+ this._state = state
+ this.events.emit('state-change', state)
+ }
+
+ /**
+ * Start ICE candidate exchange when gathering begins
+ */
+ private startIce() {
+ const listener = ({ candidate }: { candidate: RTCIceCandidate | null }) => {
+ if (candidate) this.ctx.signaler.addIceCandidate(candidate)
+ }
+ if (!this._conn) throw new Error('Connection disappeared')
+ this._conn.addEventListener('icecandidate', listener)
this.iceBin(
- this.signaler.addListener((candidate: RTCIceCandidate) => this.connection.addIceCandidate(candidate)),
- () => this.connection.removeEventListener('icecandidate', listener)
+ this.ctx.signaler.addListener((candidate: RTCIceCandidate) =>
+ this._conn?.addIceCandidate(candidate)
+ ),
+ () => this._conn?.removeEventListener('icecandidate', listener)
)
}
- private stopIceListeners() {
+ /**
+ * Stop ICE candidate exchange when gathering completes
+ */
+ private stopIce() {
this.iceBin.clean()
}
/**
- * Set the signaler for ICE candidate exchange
- * Must be called before connection is ready
+ * Disconnects the current connection and cleans up resources.
+ * Closes the active connection if it exists, resets the connection instance to null,
+ * stops the ICE process, and updates the state to 'disconnected'.
+ *
+ * @return {void} No return value.
*/
- setSignaler(signaler: Signaler): void {
- this.signaler = signaler;
+ disconnect(): void {
+ this._conn?.close()
+ this._conn = null
+ this.stopIce()
+ this.setState('disconnected')
}
+ /**
+ * Current connection state
+ */
get state() {
- return this._state;
- }
-
- get ready(): Promise {
- return this._ready;
+ return this._state
}
+ /**
+ * Queue a message for sending when connection is established
+ *
+ * @param message - Message to queue (string or ArrayBuffer)
+ * @param options - Queue options (e.g., expiration time)
+ */
queueMessage(message: Message, options: QueueMessageOptions = {}): Promise {
- return Promise.resolve(undefined);
+ // TODO: Implement message queuing
+ return Promise.resolve(undefined)
}
+ /**
+ * Send a message immediately
+ *
+ * @param message - Message to send (string or ArrayBuffer)
+ * @returns Promise resolving to true if sent successfully
+ */
sendMessage(message: Message): Promise {
- return Promise.resolve(false);
+ // TODO: Implement message sending via data channel
+ return Promise.resolve(false)
}
-}
\ No newline at end of file
+}
diff --git a/src/event-bus.ts b/src/event-bus.ts
index 893756a..410374d 100644
--- a/src/event-bus.ts
+++ b/src/event-bus.ts
@@ -2,7 +2,7 @@
* Type-safe EventBus with event name to payload type mapping
*/
-type EventHandler = (data: T) => void;
+type EventHandler = (data: T) => void
/**
* EventBus - Type-safe event emitter with inferred event data types
@@ -27,64 +27,68 @@ type EventHandler = (data: T) => void;
* });
*/
export class EventBus> {
- private handlers: Map>;
+ private handlers: Map>
- constructor() {
- this.handlers = new Map();
- }
-
- /**
- * Subscribe to an event
- */
- on(event: K, handler: EventHandler): void {
- if (!this.handlers.has(event)) {
- this.handlers.set(event, new Set());
+ constructor() {
+ this.handlers = new Map()
}
- this.handlers.get(event)!.add(handler);
- }
- /**
- * Subscribe to an event once (auto-unsubscribe after first call)
- */
- once(event: K, handler: EventHandler): void {
- const wrappedHandler = (data: TEvents[K]) => {
- handler(data);
- this.off(event, wrappedHandler);
- };
- this.on(event, wrappedHandler);
- }
+ /**
+ * Subscribe to an event
+ * Returns a cleanup function to unsubscribe
+ */
+ on(event: K, handler: EventHandler): () => void {
+ if (!this.handlers.has(event)) {
+ this.handlers.set(event, new Set())
+ }
+ this.handlers.get(event)!.add(handler)
- /**
- * Unsubscribe from an event
- */
- off(event: K, handler: EventHandler): void {
- const eventHandlers = this.handlers.get(event);
- if (eventHandlers) {
- eventHandlers.delete(handler);
- if (eventHandlers.size === 0) {
- this.handlers.delete(event);
- }
+ // Return cleanup function
+ return () => this.off(event, handler)
}
- }
- /**
- * Emit an event with data
- */
- emit(event: K, data: TEvents[K]): void {
- const eventHandlers = this.handlers.get(event);
- if (eventHandlers) {
- eventHandlers.forEach(handler => handler(data));
+ /**
+ * Subscribe to an event once (auto-unsubscribe after first call)
+ */
+ once(event: K, handler: EventHandler): void {
+ const wrappedHandler = (data: TEvents[K]) => {
+ handler(data)
+ this.off(event, wrappedHandler)
+ }
+ this.on(event, wrappedHandler)
}
- }
- /**
- * Remove all handlers for a specific event, or all handlers if no event specified
- */
- clear(event?: K): void {
- if (event !== undefined) {
- this.handlers.delete(event);
- } else {
- this.handlers.clear();
+ /**
+ * Unsubscribe from an event
+ */
+ off(event: K, handler: EventHandler): void {
+ const eventHandlers = this.handlers.get(event)
+ if (eventHandlers) {
+ eventHandlers.delete(handler)
+ if (eventHandlers.size === 0) {
+ this.handlers.delete(event)
+ }
+ }
}
- }
-}
\ No newline at end of file
+
+ /**
+ * Emit an event with data
+ */
+ emit(event: K, data: TEvents[K]): void {
+ const eventHandlers = this.handlers.get(event)
+ if (eventHandlers) {
+ eventHandlers.forEach(handler => handler(data))
+ }
+ }
+
+ /**
+ * Remove all handlers for a specific event, or all handlers if no event specified
+ */
+ clear(event?: K): void {
+ if (event !== undefined) {
+ this.handlers.delete(event)
+ } else {
+ this.handlers.clear()
+ }
+ }
+}
diff --git a/src/index.ts b/src/index.ts
index 3431a8a..8d84bc5 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -3,29 +3,38 @@
* WebRTC peer signaling client
*/
-export { ConnectionManager } from './connection-manager.js';
-export { EventBus } from './event-bus.js';
-export { RondevuAPI } from './api.js';
-export { RondevuSignaler } from './signaler.js';
-export { WebRTCRondevuConnection } from './connection.js';
-export { createBin } from './bin.js';
+export { EventBus } from './event-bus.js'
+export { RondevuAPI } from './api.js'
+export { RondevuService } from './rondevu-service.js'
+export { RondevuSignaler } from './signaler.js'
+export { ServiceHost } from './service-host.js'
+export { ServiceClient } from './service-client.js'
+export { WebRTCRondevuConnection } from './connection.js'
+export { createBin } from './bin.js'
// Export types
export type {
- ConnectionInterface,
- QueueMessageOptions,
- Message,
- ConnectionEvents,
- Signaler
-} from './types.js';
+ ConnectionInterface,
+ QueueMessageOptions,
+ Message,
+ ConnectionEvents,
+ Signaler,
+} from './types.js'
export type {
- Credentials,
- OfferRequest,
- Offer,
- ServiceRequest,
- Service,
- IceCandidate
-} from './api.js';
+ Credentials,
+ Keypair,
+ OfferRequest,
+ Offer,
+ ServiceRequest,
+ Service,
+ IceCandidate,
+} from './api.js'
-export type { Binnable } from './bin.js';
+export type { Binnable } from './bin.js'
+
+export type { RondevuServiceOptions, PublishServiceOptions } from './rondevu-service.js'
+
+export type { ServiceHostOptions, ServiceHostEvents } from './service-host.js'
+
+export type { ServiceClientOptions, ServiceClientEvents } from './service-client.js'
diff --git a/src/noop-signaler.ts b/src/noop-signaler.ts
new file mode 100644
index 0000000..b6441fd
--- /dev/null
+++ b/src/noop-signaler.ts
@@ -0,0 +1,35 @@
+import { Signaler } from './types.js'
+import { Binnable } from './bin.js'
+
+/**
+ * NoOpSignaler - A signaler that does nothing
+ * Used as a placeholder during connection setup before the real signaler is available
+ */
+export class NoOpSignaler implements Signaler {
+ addIceCandidate(_candidate: RTCIceCandidate): void {
+ // No-op
+ }
+
+ addListener(_callback: (candidate: RTCIceCandidate) => void): Binnable {
+ // Return no-op cleanup function
+ return () => {}
+ }
+
+ addOfferListener(_callback: (offer: RTCSessionDescriptionInit) => void): Binnable {
+ // Return no-op cleanup function
+ return () => {}
+ }
+
+ addAnswerListener(_callback: (answer: RTCSessionDescriptionInit) => void): Binnable {
+ // Return no-op cleanup function
+ return () => {}
+ }
+
+ async setOffer(_offer: RTCSessionDescriptionInit): Promise {
+ // No-op
+ }
+
+ async setAnswer(_answer: RTCSessionDescriptionInit): Promise {
+ // No-op
+ }
+}
diff --git a/src/rondevu-service.ts b/src/rondevu-service.ts
new file mode 100644
index 0000000..7ebd055
--- /dev/null
+++ b/src/rondevu-service.ts
@@ -0,0 +1,168 @@
+import { RondevuAPI, Credentials, Keypair, Service, ServiceRequest } from './api.js'
+
+export interface RondevuServiceOptions {
+ apiUrl: string
+ username: string
+ keypair?: Keypair
+ credentials?: Credentials
+}
+
+export interface PublishServiceOptions {
+ serviceFqn: string
+ sdp: string
+ ttl?: number
+ isPublic?: boolean
+ metadata?: Record
+}
+
+/**
+ * RondevuService - High-level service management with automatic signature handling
+ *
+ * Provides a simplified API for:
+ * - Username claiming with Ed25519 signatures
+ * - Service publishing with automatic signature generation
+ * - Keypair management
+ *
+ * @example
+ * ```typescript
+ * // Initialize service (generates keypair automatically)
+ * const service = new RondevuService({
+ * apiUrl: 'https://signal.example.com',
+ * username: 'myusername',
+ * })
+ *
+ * await service.initialize()
+ *
+ * // Claim username (one time)
+ * await service.claimUsername()
+ *
+ * // Publish a service
+ * const publishedService = await service.publishService({
+ * serviceFqn: 'chat.app@1.0.0',
+ * sdp: offerSdp,
+ * ttl: 300000,
+ * isPublic: true,
+ * })
+ * ```
+ */
+export class RondevuService {
+ private readonly api: RondevuAPI
+ private readonly username: string
+ private keypair: Keypair | null = null
+ private usernameClaimed = false
+
+ constructor(options: RondevuServiceOptions) {
+ this.username = options.username
+ this.keypair = options.keypair || null
+ this.api = new RondevuAPI(options.apiUrl, options.credentials)
+ }
+
+ /**
+ * Initialize the service - generates keypair if not provided
+ * Call this before using other methods
+ */
+ async initialize(): Promise {
+ if (!this.keypair) {
+ this.keypair = await RondevuAPI.generateKeypair()
+ }
+
+ // Register with API if no credentials provided
+ if (!this.api['credentials']) {
+ const credentials = await this.api.register()
+ ;(this.api as any).credentials = credentials
+ }
+ }
+
+ /**
+ * Claim the username with Ed25519 signature
+ * Should be called once before publishing services
+ */
+ async claimUsername(): Promise {
+ if (!this.keypair) {
+ throw new Error('Service not initialized. Call initialize() first.')
+ }
+
+ // Check if username is already claimed
+ const check = await this.api.checkUsername(this.username)
+ if (!check.available) {
+ // Verify it's claimed by us
+ if (check.owner === this.keypair.publicKey) {
+ this.usernameClaimed = true
+ return
+ }
+ throw new Error(`Username "${this.username}" is already claimed by another user`)
+ }
+
+ // Generate signature for username claim
+ const message = `claim-username-${this.username}-${Date.now()}`
+ const signature = await RondevuAPI.signMessage(message, this.keypair.privateKey)
+
+ // Claim the username
+ await this.api.claimUsername(this.username, this.keypair.publicKey, signature, message)
+ this.usernameClaimed = true
+ }
+
+ /**
+ * Publish a service with automatic signature generation
+ */
+ async publishService(options: PublishServiceOptions): Promise {
+ if (!this.keypair) {
+ throw new Error('Service not initialized. Call initialize() first.')
+ }
+
+ if (!this.usernameClaimed) {
+ throw new Error(
+ 'Username not claimed. Call claimUsername() first or the server will reject the service.'
+ )
+ }
+
+ const { serviceFqn, sdp, ttl, isPublic, metadata } = options
+
+ // Generate signature for service publication
+ const message = `publish-${this.username}-${serviceFqn}-${Date.now()}`
+ const signature = await RondevuAPI.signMessage(message, this.keypair.privateKey)
+
+ // Create service request
+ const serviceRequest: ServiceRequest = {
+ username: this.username,
+ serviceFqn,
+ sdp,
+ signature,
+ message,
+ ttl,
+ isPublic,
+ metadata,
+ }
+
+ // Publish to server
+ return await this.api.publishService(serviceRequest)
+ }
+
+ /**
+ * Get the current keypair (for backup/storage)
+ */
+ getKeypair(): Keypair | null {
+ return this.keypair
+ }
+
+ /**
+ * Get the public key
+ */
+ getPublicKey(): string | null {
+ return this.keypair?.publicKey || null
+ }
+
+ /**
+ * Check if username has been claimed
+ */
+ isUsernameClaimed(): boolean {
+ return this.usernameClaimed
+ }
+
+ /**
+ * Access to underlying API for advanced operations
+ */
+ getAPI(): RondevuAPI {
+ return this.api
+ }
+}
diff --git a/src/service-client.ts b/src/service-client.ts
new file mode 100644
index 0000000..8eabf96
--- /dev/null
+++ b/src/service-client.ts
@@ -0,0 +1,244 @@
+import { WebRTCRondevuConnection } from './connection.js'
+import { WebRTCContext } from './webrtc-context.js'
+import { RondevuService } from './rondevu-service.js'
+import { RondevuSignaler } from './signaler.js'
+import { EventBus } from './event-bus.js'
+import { createBin } from './bin.js'
+import { ConnectionInterface } from './types.js'
+
+export interface ServiceClientOptions {
+ username: string
+ serviceFqn: string
+ rondevuService: RondevuService
+ autoReconnect?: boolean
+ reconnectDelay?: number
+ maxReconnectAttempts?: number
+}
+
+export interface ServiceClientEvents {
+ connected: ConnectionInterface
+ disconnected: { reason: string }
+ reconnecting: { attempt: number; maxAttempts: number }
+ error: Error
+}
+
+/**
+ * ServiceClient - Connects to a hosted service
+ *
+ * Searches for available service offers and establishes a WebRTC connection.
+ * Optionally supports automatic reconnection on failure.
+ *
+ * @example
+ * ```typescript
+ * const rondevuService = new RondevuService({
+ * apiUrl: 'https://signal.example.com',
+ * username: 'client-user',
+ * })
+ *
+ * await rondevuService.initialize()
+ *
+ * const client = new ServiceClient({
+ * username: 'host-user',
+ * serviceFqn: 'chat.app@1.0.0',
+ * rondevuService,
+ * autoReconnect: true,
+ * })
+ *
+ * await client.connect()
+ *
+ * client.events.on('connected', (conn) => {
+ * console.log('Connected to service')
+ * conn.sendMessage('Hello!')
+ * })
+ * ```
+ */
+export class ServiceClient {
+ private readonly username: string
+ private readonly serviceFqn: string
+ private readonly rondevuService: RondevuService
+ private readonly autoReconnect: boolean
+ private readonly reconnectDelay: number
+ private readonly maxReconnectAttempts: number
+ private connection: WebRTCRondevuConnection | null = null
+ private reconnectAttempts = 0
+ private reconnectTimeout: ReturnType | null = null
+ private readonly bin = createBin()
+ private isConnecting = false
+
+ public readonly events = new EventBus()
+
+ constructor(options: ServiceClientOptions) {
+ this.username = options.username
+ this.serviceFqn = options.serviceFqn
+ this.rondevuService = options.rondevuService
+ this.autoReconnect = options.autoReconnect !== false
+ this.reconnectDelay = options.reconnectDelay || 2000
+ this.maxReconnectAttempts = options.maxReconnectAttempts || 5
+ }
+
+ /**
+ * Connect to the service
+ */
+ async connect(): Promise {
+ if (this.isConnecting) {
+ throw new Error('Already connecting')
+ }
+
+ if (this.connection && this.connection.state === 'connected') {
+ return this.connection
+ }
+
+ this.isConnecting = true
+
+ try {
+ // Search for available services
+ const services = await this.rondevuService
+ .getAPI()
+ .searchServices(this.username, this.serviceFqn)
+
+ if (services.length === 0) {
+ throw new Error(`No services found for ${this.username}/${this.serviceFqn}`)
+ }
+
+ // Get the first available service
+ const service = services[0]
+
+ // Get service details including SDP
+ const serviceDetails = await this.rondevuService.getAPI().getService(service.uuid)
+
+ // Create WebRTC context with signaler for this offer
+ const signaler = new RondevuSignaler(
+ this.rondevuService.getAPI(),
+ serviceDetails.offerId
+ )
+ const context = new WebRTCContext(signaler)
+
+ // Create connection (answerer role)
+ const conn = new WebRTCRondevuConnection({
+ id: `client-${this.serviceFqn}-${Date.now()}`,
+ service: this.serviceFqn,
+ offer: {
+ type: 'offer',
+ sdp: serviceDetails.sdp,
+ },
+ context,
+ })
+
+ // Wait for answer to be created
+ await conn.ready
+
+ // Get answer SDP
+ if (!conn.connection?.localDescription?.sdp) {
+ throw new Error('Failed to create answer SDP')
+ }
+
+ const answerSdp = conn.connection.localDescription.sdp
+
+ // Send answer to server
+ await this.rondevuService.getAPI().answerOffer(serviceDetails.offerId, answerSdp)
+
+ // Track connection
+ this.connection = conn
+ this.reconnectAttempts = 0
+
+ // Listen for state changes
+ const cleanup = conn.events.on('state-change', state => {
+ this.handleConnectionStateChange(state)
+ })
+ this.bin(cleanup)
+
+ this.isConnecting = false
+
+ // Emit connected event when actually connected
+ if (conn.state === 'connected') {
+ this.events.emit('connected', conn)
+ }
+
+ return conn
+ } catch (error) {
+ this.isConnecting = false
+ this.events.emit('error', error as Error)
+ throw error
+ }
+ }
+
+ /**
+ * Disconnect from the service
+ */
+ disconnect(): void {
+ if (this.reconnectTimeout) {
+ clearTimeout(this.reconnectTimeout)
+ this.reconnectTimeout = null
+ }
+
+ if (this.connection) {
+ this.connection.disconnect()
+ this.connection = null
+ }
+
+ this.bin.clean()
+ this.reconnectAttempts = 0
+ }
+
+ /**
+ * Get the current connection
+ */
+ getConnection(): WebRTCRondevuConnection | null {
+ return this.connection
+ }
+
+ /**
+ * Check if currently connected
+ */
+ isConnected(): boolean {
+ return this.connection?.state === 'connected'
+ }
+
+ /**
+ * Handle connection state changes
+ */
+ private handleConnectionStateChange(state: ConnectionInterface['state']): void {
+ if (state === 'connected') {
+ this.events.emit('connected', this.connection!)
+ this.reconnectAttempts = 0
+ } else if (state === 'disconnected') {
+ this.events.emit('disconnected', { reason: 'Connection closed' })
+
+ // Attempt reconnection if enabled
+ if (this.autoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
+ this.scheduleReconnect()
+ }
+ }
+ }
+
+ /**
+ * Schedule a reconnection attempt
+ */
+ private scheduleReconnect(): void {
+ if (this.reconnectTimeout) {
+ return
+ }
+
+ this.reconnectAttempts++
+
+ this.events.emit('reconnecting', {
+ attempt: this.reconnectAttempts,
+ maxAttempts: this.maxReconnectAttempts,
+ })
+
+ // Exponential backoff
+ const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1)
+
+ this.reconnectTimeout = setTimeout(() => {
+ this.reconnectTimeout = null
+ this.connect().catch(error => {
+ this.events.emit('error', error as Error)
+
+ // Schedule next attempt if we haven't exceeded max attempts
+ if (this.reconnectAttempts < this.maxReconnectAttempts) {
+ this.scheduleReconnect()
+ }
+ })
+ }, delay)
+ }
+}
diff --git a/src/service-host.ts b/src/service-host.ts
new file mode 100644
index 0000000..fb2449f
--- /dev/null
+++ b/src/service-host.ts
@@ -0,0 +1,236 @@
+import { WebRTCRondevuConnection } from './connection.js'
+import { WebRTCContext } from './webrtc-context.js'
+import { RondevuService } from './rondevu-service.js'
+import { RondevuSignaler } from './signaler.js'
+import { NoOpSignaler } from './noop-signaler.js'
+import { EventBus } from './event-bus.js'
+import { createBin } from './bin.js'
+import { ConnectionInterface } from './types.js'
+
+export interface ServiceHostOptions {
+ service: string
+ rondevuService: RondevuService
+ maxPeers?: number
+ ttl?: number
+ isPublic?: boolean
+ metadata?: Record
+}
+
+export interface ServiceHostEvents {
+ connection: ConnectionInterface
+ 'connection-closed': { connectionId: string; reason: string }
+ error: Error
+}
+
+/**
+ * ServiceHost - Manages a pool of WebRTC offers for a service
+ *
+ * Maintains up to maxPeers concurrent offers, automatically replacing
+ * them when connections are established or expire.
+ *
+ * @example
+ * ```typescript
+ * const rondevuService = new RondevuService({
+ * apiUrl: 'https://signal.example.com',
+ * username: 'myusername',
+ * })
+ *
+ * await rondevuService.initialize()
+ * await rondevuService.claimUsername()
+ *
+ * const host = new ServiceHost({
+ * service: 'chat.app@1.0.0',
+ * rondevuService,
+ * maxPeers: 5,
+ * })
+ *
+ * await host.start()
+ *
+ * host.events.on('connection', (conn) => {
+ * console.log('New connection:', conn.id)
+ * conn.events.on('message', (msg) => {
+ * console.log('Message:', msg)
+ * })
+ * })
+ * ```
+ */
+export class ServiceHost {
+ private connections = new Map()
+ private readonly service: string
+ private readonly rondevuService: RondevuService
+ private readonly maxPeers: number
+ private readonly ttl: number
+ private readonly isPublic: boolean
+ private readonly metadata?: Record
+ private readonly bin = createBin()
+ private isStarted = false
+
+ public readonly events = new EventBus()
+
+ constructor(options: ServiceHostOptions) {
+ this.service = options.service
+ this.rondevuService = options.rondevuService
+ this.maxPeers = options.maxPeers || 20
+ this.ttl = options.ttl || 300000
+ this.isPublic = options.isPublic !== false
+ this.metadata = options.metadata
+ }
+
+ /**
+ * Start hosting the service - creates initial pool of offers
+ */
+ async start(): Promise {
+ if (this.isStarted) {
+ throw new Error('ServiceHost already started')
+ }
+
+ this.isStarted = true
+ await this.fillOfferPool()
+ }
+
+ /**
+ * Stop hosting - closes all connections and cleans up
+ */
+ stop(): void {
+ this.isStarted = false
+ this.connections.forEach(conn => conn.disconnect())
+ this.connections.clear()
+ this.bin.clean()
+ }
+
+ /**
+ * Get current number of active connections
+ */
+ getConnectionCount(): number {
+ return Array.from(this.connections.values()).filter(conn => conn.state === 'connected')
+ .length
+ }
+
+ /**
+ * Get current number of pending offers
+ */
+ getPendingOfferCount(): number {
+ return Array.from(this.connections.values()).filter(conn => conn.state === 'connecting')
+ .length
+ }
+
+ /**
+ * Fill the offer pool up to maxPeers
+ */
+ private async fillOfferPool(): Promise {
+ const currentOffers = this.connections.size
+ const needed = this.maxPeers - currentOffers
+
+ if (needed <= 0) {
+ return
+ }
+
+ // Create multiple offers in parallel
+ const offerPromises: Promise[] = []
+ for (let i = 0; i < needed; i++) {
+ offerPromises.push(this.createOffer())
+ }
+
+ await Promise.allSettled(offerPromises)
+ }
+
+ /**
+ * Create a single offer and publish it
+ */
+ private async createOffer(): Promise {
+ try {
+ // Create temporary context with NoOp signaler
+ const tempContext = new WebRTCContext(new NoOpSignaler())
+
+ // Create connection (offerer role)
+ const conn = new WebRTCRondevuConnection({
+ id: `${this.service}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
+ service: this.service,
+ offer: null,
+ context: tempContext,
+ })
+
+ // Wait for offer to be created
+ await conn.ready
+
+ // Get offer SDP
+ if (!conn.connection?.localDescription?.sdp) {
+ throw new Error('Failed to create offer SDP')
+ }
+
+ const sdp = conn.connection.localDescription.sdp
+
+ // Publish service offer
+ const service = await this.rondevuService.publishService({
+ serviceFqn: this.service,
+ sdp,
+ ttl: this.ttl,
+ isPublic: this.isPublic,
+ metadata: this.metadata,
+ })
+
+ // Replace with real signaler now that we have offerId
+ const realSignaler = new RondevuSignaler(this.rondevuService.getAPI(), service.offerId)
+ ;(tempContext as any).signaler = realSignaler
+
+ // Track connection
+ this.connections.set(conn.id, conn)
+
+ // Listen for state changes
+ const cleanup = conn.events.on('state-change', state => {
+ this.handleConnectionStateChange(conn, state)
+ })
+
+ this.bin(cleanup)
+ } catch (error) {
+ this.events.emit('error', error as Error)
+ }
+ }
+
+ /**
+ * Handle connection state changes
+ */
+ private handleConnectionStateChange(
+ conn: WebRTCRondevuConnection,
+ state: ConnectionInterface['state']
+ ): void {
+ if (state === 'connected') {
+ // Connection established - emit event
+ this.events.emit('connection', conn)
+
+ // Create new offer to replace this one
+ if (this.isStarted) {
+ this.fillOfferPool().catch(error => {
+ this.events.emit('error', error as Error)
+ })
+ }
+ } else if (state === 'disconnected') {
+ // Connection closed - remove and create new offer
+ this.connections.delete(conn.id)
+ this.events.emit('connection-closed', {
+ connectionId: conn.id,
+ reason: state,
+ })
+
+ if (this.isStarted) {
+ this.fillOfferPool().catch(error => {
+ this.events.emit('error', error as Error)
+ })
+ }
+ }
+ }
+
+ /**
+ * Get all active connections
+ */
+ getConnections(): WebRTCRondevuConnection[] {
+ return Array.from(this.connections.values())
+ }
+
+ /**
+ * Get a specific connection by ID
+ */
+ getConnection(connectionId: string): WebRTCRondevuConnection | undefined {
+ return this.connections.get(connectionId)
+ }
+}
diff --git a/src/signaler.ts b/src/signaler.ts
index 997ab62..0559673 100644
--- a/src/signaler.ts
+++ b/src/signaler.ts
@@ -1,6 +1,6 @@
-import {Signaler} from "./types";
-import {Binnable} from "./bin";
-import {RondevuAPI} from "./api";
+import { Signaler } from './types.js'
+import { Binnable } from './bin.js'
+import { RondevuAPI } from './api.js'
/**
* RondevuSignaler - Handles ICE candidate exchange via Rondevu API
@@ -12,18 +12,31 @@ export class RondevuSignaler implements Signaler {
private offerId: string
) {}
+ addOfferListener(callback: (offer: RTCSessionDescriptionInit) => void): Binnable {
+ throw new Error('Method not implemented.')
+ }
+ addAnswerListener(callback: (answer: RTCSessionDescriptionInit) => void): Binnable {
+ throw new Error('Method not implemented.')
+ }
+ setOffer(offer: RTCSessionDescriptionInit): Promise {
+ throw new Error('Method not implemented.')
+ }
+ setAnswer(answer: RTCSessionDescriptionInit): Promise {
+ throw new Error('Method not implemented.')
+ }
+
/**
- * Send local ICE candidate to signaling server
+ * Send a local ICE candidate to signaling server
*/
async addIceCandidate(candidate: RTCIceCandidate): Promise {
- const candidateData = candidate.toJSON();
+ const candidateData = candidate.toJSON()
// Skip empty candidates
if (!candidateData.candidate || candidateData.candidate === '') {
- return;
+ return
}
- await this.api.addIceCandidates(this.offerId, [candidateData]);
+ await this.api.addIceCandidates(this.offerId, [candidateData])
}
/**
@@ -31,52 +44,61 @@ export class RondevuSignaler implements Signaler {
* Returns cleanup function to stop polling
*/
addListener(callback: (candidate: RTCIceCandidate) => void): Binnable {
- let lastTimestamp = 0;
- let polling = true;
+ let lastTimestamp = 0
+ let polling = true
const poll = async () => {
while (polling) {
try {
- const candidates = await this.api.getIceCandidates(this.offerId, lastTimestamp);
+ const candidates = await this.api.getIceCandidates(this.offerId, lastTimestamp)
// Process each candidate
for (const item of candidates) {
- if (item.candidate && item.candidate.candidate && item.candidate.candidate !== '') {
+ if (
+ item.candidate &&
+ item.candidate.candidate &&
+ item.candidate.candidate !== ''
+ ) {
try {
- const rtcCandidate = new RTCIceCandidate(item.candidate);
- callback(rtcCandidate);
- lastTimestamp = item.createdAt;
+ const rtcCandidate = new RTCIceCandidate(item.candidate)
+ callback(rtcCandidate)
+ lastTimestamp = item.createdAt
} catch (err) {
- console.warn('Failed to process ICE candidate:', err);
- lastTimestamp = item.createdAt;
+ console.warn('Failed to process ICE candidate:', err)
+ lastTimestamp = item.createdAt
}
} else {
- lastTimestamp = item.createdAt;
+ lastTimestamp = item.createdAt
}
}
} catch (err) {
// If offer not found or expired, stop polling
- if (err instanceof Error && (err.message.includes('404') || err.message.includes('410'))) {
- console.warn('Offer not found or expired, stopping ICE polling');
- polling = false;
- break;
+ if (
+ err instanceof Error &&
+ (err.message.includes('404') || err.message.includes('410'))
+ ) {
+ console.warn('Offer not found or expired, stopping ICE polling')
+ polling = false
+ break
}
- console.error('Error polling for ICE candidates:', err);
+ console.error('Error polling for ICE candidates:', err)
}
// Poll every second
if (polling) {
- await new Promise(resolve => setTimeout(resolve, 1000));
+ await new Promise(resolve => setTimeout(resolve, 1000))
}
}
- };
+ }
- // Start polling in background
- poll();
+ // Start polling in the background
+ poll().then(() => {
+ console.log('ICE polling started')
+ })
// Return cleanup function
return () => {
- polling = false;
- };
+ polling = false
+ }
}
-}
\ No newline at end of file
+}
diff --git a/src/types.ts b/src/types.ts
index 66d2511..2691992 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,34 +1,42 @@
/**
* Core connection types
*/
-import {EventBus} from "./event-bus";
-import {Binnable} from "./bin";
+import { EventBus } from './event-bus.js'
+import { Binnable } from './bin.js'
-export type Message = string | ArrayBuffer;
+export type Message = string | ArrayBuffer
export interface QueueMessageOptions {
- expiresAt?: number;
+ expiresAt?: number
}
export interface ConnectionEvents {
'state-change': ConnectionInterface['state']
- 'message': Message;
+ message: Message
}
-export interface ConnectionInterface {
- id: string;
- host: string;
- service: string;
- state: 'connected' | 'disconnected' | 'connecting';
- lastActive: number;
- expiresAt?: number;
- events: EventBus;
+export const ConnectionStates = ['connected', 'disconnected', 'connecting'] as const
- queueMessage(message: Message, options?: QueueMessageOptions): Promise;
- sendMessage(message: Message): Promise;
+export const isConnectionState = (state: string): state is (typeof ConnectionStates)[number] =>
+ ConnectionStates.includes(state as any)
+
+export interface ConnectionInterface {
+ id: string
+ service: string
+ state: (typeof ConnectionStates)[number]
+ lastActive: number
+ expiresAt?: number
+ events: EventBus
+
+ queueMessage(message: Message, options?: QueueMessageOptions): Promise
+ sendMessage(message: Message): Promise
}
export interface Signaler {
- addIceCandidate(candidate: RTCIceCandidate): Promise | void;
- addListener(callback: (candidate: RTCIceCandidate) => void): Binnable;
-}
\ No newline at end of file
+ addIceCandidate(candidate: RTCIceCandidate): Promise | void
+ addListener(callback: (candidate: RTCIceCandidate) => void): Binnable
+ addOfferListener(callback: (offer: RTCSessionDescriptionInit) => void): Binnable
+ addAnswerListener(callback: (answer: RTCSessionDescriptionInit) => void): Binnable
+ setOffer(offer: RTCSessionDescriptionInit): Promise
+ setAnswer(answer: RTCSessionDescriptionInit): Promise
+}
diff --git a/src/webrtc-context.ts b/src/webrtc-context.ts
new file mode 100644
index 0000000..d16687f
--- /dev/null
+++ b/src/webrtc-context.ts
@@ -0,0 +1,35 @@
+import { Signaler } from './types'
+
+export class WebRTCContext {
+ constructor(public readonly signaler: Signaler) {}
+
+ createPeerConnection(): RTCPeerConnection {
+ return new RTCPeerConnection({
+ iceServers: [
+ {
+ urls: 'stun:stun.relay.metered.ca:80',
+ },
+ {
+ urls: 'turn:standard.relay.metered.ca:80',
+ username: 'c53a9c971da5e6f3bc959d8d',
+ credential: 'QaccPqtPPaxyokXp',
+ },
+ {
+ urls: 'turn:standard.relay.metered.ca:80?transport=tcp',
+ username: 'c53a9c971da5e6f3bc959d8d',
+ credential: 'QaccPqtPPaxyokXp',
+ },
+ {
+ urls: 'turn:standard.relay.metered.ca:443',
+ username: 'c53a9c971da5e6f3bc959d8d',
+ credential: 'QaccPqtPPaxyokXp',
+ },
+ {
+ urls: 'turns:standard.relay.metered.ca:443?transport=tcp',
+ username: 'c53a9c971da5e6f3bc959d8d',
+ credential: 'QaccPqtPPaxyokXp',
+ },
+ ],
+ })
+ }
+}
diff --git a/vite.config.js b/vite.config.js
new file mode 100644
index 0000000..62c9f36
--- /dev/null
+++ b/vite.config.js
@@ -0,0 +1,10 @@
+import { defineConfig } from 'vite';
+
+export default defineConfig({
+ root: 'demo',
+ server: {
+ port: 3000,
+ open: true,
+ allowedHosts: ['241284034b20.ngrok-free.app']
+ }
+});