From 7765bafb13f12f7b8c38c75a6bc629412013cb3b Mon Sep 17 00:00:00 2001 From: Pekka Enberg Date: Mon, 21 Jul 2025 21:48:10 +0300 Subject: [PATCH] Add `@tursodatabase/serverless` package This package is for serverless access to the Turso Cloud using SQL over HTTP protocol. The purpose of this package is to provide the same interface as `@tursodatabase/turso`, but for serverless environments that cannot host the database engine. The package also provides a `@libsql/client` compatibility layer in the `@tursodatabase/serverless/compat` module for drop-in replacement for existing clients. --- packages/turso-serverless/AGENT.md | 192 ++ packages/turso-serverless/README.md | 78 + .../examples/remote-compat/README.md | 19 + .../examples/remote-compat/index.mjs | 20 + .../examples/remote-compat/package-lock.json | 30 + .../examples/remote-compat/package.json | 10 + .../examples/remote/README.md | 19 + .../examples/remote/index.mjs | 36 + .../examples/remote/package-lock.json | 29 + .../examples/remote/package.json | 10 + .../integration-tests/compat.test.mjs | 54 + .../integration-tests/serverless.test.mjs | 119 + packages/turso-serverless/package-lock.json | 2394 +++++++++++++++++ packages/turso-serverless/package.json | 35 + packages/turso-serverless/src/compat.ts | 322 +++ packages/turso-serverless/src/compat/index.ts | 1 + packages/turso-serverless/src/connection.ts | 100 + packages/turso-serverless/src/index.ts | 3 + packages/turso-serverless/src/protocol.ts | 245 ++ packages/turso-serverless/src/session.ts | 216 ++ packages/turso-serverless/src/statement.ts | 107 + packages/turso-serverless/tsconfig.json | 19 + 22 files changed, 4058 insertions(+) create mode 100644 packages/turso-serverless/AGENT.md create mode 100644 packages/turso-serverless/README.md create mode 100644 packages/turso-serverless/examples/remote-compat/README.md create mode 100644 packages/turso-serverless/examples/remote-compat/index.mjs create mode 100644 packages/turso-serverless/examples/remote-compat/package-lock.json create mode 100644 packages/turso-serverless/examples/remote-compat/package.json create mode 100644 packages/turso-serverless/examples/remote/README.md create mode 100644 packages/turso-serverless/examples/remote/index.mjs create mode 100644 packages/turso-serverless/examples/remote/package-lock.json create mode 100644 packages/turso-serverless/examples/remote/package.json create mode 100644 packages/turso-serverless/integration-tests/compat.test.mjs create mode 100644 packages/turso-serverless/integration-tests/serverless.test.mjs create mode 100644 packages/turso-serverless/package-lock.json create mode 100644 packages/turso-serverless/package.json create mode 100644 packages/turso-serverless/src/compat.ts create mode 100644 packages/turso-serverless/src/compat/index.ts create mode 100644 packages/turso-serverless/src/connection.ts create mode 100644 packages/turso-serverless/src/index.ts create mode 100644 packages/turso-serverless/src/protocol.ts create mode 100644 packages/turso-serverless/src/session.ts create mode 100644 packages/turso-serverless/src/statement.ts create mode 100644 packages/turso-serverless/tsconfig.json diff --git a/packages/turso-serverless/AGENT.md b/packages/turso-serverless/AGENT.md new file mode 100644 index 000000000..be02c64a4 --- /dev/null +++ b/packages/turso-serverless/AGENT.md @@ -0,0 +1,192 @@ +# Agent Development Guide + +This document provides guidance for LLMs working on the `@tursodatabase/serverless` TypeScript driver. + +## Project Overview + +This is a **fetch() API-compatible serverless database driver** for Turso Cloud that implements the SQL over HTTP protocol (internally called "hrana"). It's designed for serverless and edge compute environments like Cloudflare Workers and Vercel Edge Functions. + +### Key Features +- **HTTP-based SQL execution** using the v3 cursor endpoint for streaming +- **Native streaming API** with Connection/Statement pattern +- **LibSQL compatibility layer** for drop-in replacement +- **TypeScript-first** with full type safety +- **Edge-optimized** using only `fetch()` API + +## Architecture + +### Core Files Structure +``` +src/ +├── connection.ts # Connection class and connect() function +├── statement.ts # Statement class with get()/all()/iterate() methods +├── protocol.ts # Low-level SQL over HTTP protocol implementation +├── compat.ts # LibSQL API compatibility layer +├── compat/index.ts # Compatibility layer exports +└── index.ts # Main package exports +``` + +### Package Exports +- **Main API**: `@tursodatabase/serverless` - Native streaming API +- **Compatibility**: `@tursodatabase/serverless/compat` - LibSQL-compatible API + +## Native API Design + +### Connection/Statement Pattern +```typescript +import { connect } from "@tursodatabase/serverless"; + +const client = connect({ url, authToken }); +const stmt = client.prepare("SELECT * FROM users WHERE id = ?", [123]); + +// Three execution modes: +const row = await stmt.get(); // First row or null +const rows = await stmt.all(); // All rows as array +for await (const row of stmt.iterate()) { ... } // Streaming iterator +``` + +### Key Classes + +#### Connection +- **Purpose**: Database connection and session management +- **Methods**: `prepare()`, `execute()`, `batch()`, `executeRaw()` +- **Internal**: Manages baton tokens, base URL updates, cursor streaming + +#### Statement +- **Purpose**: Prepared statement execution with multiple access patterns +- **Methods**: `get()`, `all()`, `iterate()` +- **Streaming**: `iterate()` provides row-by-row streaming via AsyncGenerator + +#### Protocol Layer +- **Purpose**: HTTP cursor endpoint communication +- **Key Function**: `executeCursor()` returns streaming cursor entries +- **Protocol**: Uses v3 cursor endpoint (`/v3/cursor`) with newline-delimited JSON + +## LibSQL Compatibility Layer + +### Purpose +Provides drop-in compatibility with the standard libSQL client API for existing applications. + +### Key Differences +- **Entry Point**: `createClient()` instead of `connect()` +- **Import Path**: `@tursodatabase/serverless/compat` +- **API Surface**: Matches libSQL client interface exactly +- **Config Validation**: Only supports `url` and `authToken`, validates against unsupported options + +### Supported vs Unsupported +```typescript +// ✅ Supported +const client = createClient({ url, authToken }); +await client.execute(sql, args); +await client.batch(statements); + +// ❌ Unsupported (throws LibsqlError) +createClient({ url, authToken, encryptionKey: "..." }); // Validation error +await client.transaction(); // Not implemented +await client.sync(); // Not supported for remote +``` + +## Protocol Implementation + +### SQL over HTTP (v3 Cursor) +- **Endpoint**: `POST /v3/cursor` +- **Request**: JSON with baton, batch steps +- **Response**: Streaming newline-delimited JSON entries +- **Entry Types**: `step_begin`, `row`, `step_end`, `step_error`, `error` + +### Session Management +- **Baton Tokens**: Maintain session continuity across requests +- **Base URL Updates**: Handle server-side redirects/load balancing +- **URL Normalization**: Convert `libsql://` to `https://` automatically + +## Testing Strategy + +### Integration Tests +``` +integration-tests/ +├── serverless.test.mjs # Native API tests +└── compat.test.mjs # Compatibility layer tests +``` + +### Test Requirements +- **Environment Variables**: `TURSO_DATABASE_URL`, `TURSO_AUTH_TOKEN` +- **Serial Execution**: All tests use `test.serial()` to avoid conflicts +- **Real Database**: Tests run against actual Turso instance + +### Running Tests +```bash +npm test # Runs all integration tests +npm run build # TypeScript compilation +``` + +## Development Guidelines + +### Code Organization +- **Single Responsibility**: Each file has a clear, focused purpose +- **Type Safety**: Full TypeScript coverage with proper imports +- **Error Handling**: Use proper error classes (`LibsqlError` for compat) +- **Streaming First**: Leverage AsyncGenerator for memory efficiency + +### Key Patterns +- **Protocol Abstraction**: Keep protocol details in `protocol.ts` +- **Compatibility Isolation**: LibSQL compatibility in separate module +- **Row Objects**: Arrays with column name properties (non-enumerable) +- **Config Validation**: Explicit validation with helpful error messages + +### Performance Considerations +- **Streaming**: Use `iterate()` for large result sets +- **Memory**: Cursor endpoint provides constant memory usage +- **Latency**: First results available immediately with streaming + +## Common Tasks + +### Adding New Features +1. **Protocol**: Add to `protocol.ts` if it requires HTTP changes +2. **Connection**: Add to `connection.ts` for connection-level features +3. **Statement**: Add to `statement.ts` for statement-level features +4. **Compatibility**: Update `compat.ts` if LibSQL compatibility needed +5. **Tests**: Add integration tests for new functionality + +### Debugging Issues +1. **Check Protocol**: Use `executeRaw()` to inspect cursor entries +2. **Validate Config**: Ensure URL/auth token are correct +3. **Test Streaming**: Compare `all()` vs `iterate()` behavior +4. **Review Errors**: Check for `LibsqlError` vs generic errors + +### Extending Compatibility +1. **Research LibSQL**: Check `resources/libsql-client-ts` for API patterns +2. **Validate Config**: Add validation for unsupported options +3. **Map Interfaces**: Convert between LibSQL and native formats +4. **Test Coverage**: Ensure compatibility tests cover new features + +## Important Notes + +### Security +- **No Secret Logging**: Never log auth tokens or sensitive data +- **Validation**: Always validate inputs, especially in compatibility layer +- **Error Messages**: Don't expose internal implementation details + +### Compatibility +- **Breaking Changes**: Avoid breaking the native API +- **LibSQL Parity**: Match LibSQL behavior exactly in compatibility layer +- **Version Support**: Document which libSQL features are supported + +### Edge Cases +- **Large Results**: Test with large datasets to verify streaming +- **Network Issues**: Handle connection failures gracefully +- **Protocol Evolution**: Be prepared for protocol version updates + +## Future Considerations + +### Potential Enhancements +- **Transaction Support**: Interactive transactions in compatibility layer +- **Prepared Statement Caching**: Cache prepared statements +- **Connection Pooling**: Multiple concurrent connections +- **Protocol Negotiation**: Support multiple protocol versions + +### Monitoring +- **Performance Metrics**: Track query latency and throughput +- **Error Rates**: Monitor protocol and application errors +- **Resource Usage**: Memory and CPU usage in serverless environments + +This guide should help future contributors understand the architecture and maintain consistency across the codebase. \ No newline at end of file diff --git a/packages/turso-serverless/README.md b/packages/turso-serverless/README.md new file mode 100644 index 000000000..79c407319 --- /dev/null +++ b/packages/turso-serverless/README.md @@ -0,0 +1,78 @@ +# Turso serverless JavaScript driver + +A serverless database driver for Turso Cloud, using only `fetch()`. Connect to your database from serverless and edge functions, such as Cloudflare Workers and Vercel. + +> [!NOTE] +> This driver is experimental and, therefore, subject to change at any time. + +## Installation + +```bash +npm install @tursodatabase/serverless +``` + +## Usage + +```javascript +import { connect } from "@tursodatabase/serverless"; + +const conn = connect({ + url: process.env.TURSO_DATABASE_URL, + authToken: process.env.TURSO_AUTH_TOKEN, +}); + +// Prepare a statement +const stmt = conn.prepare("SELECT * FROM users WHERE id = ?"); + +// Get first row +const row = await stmt.get([123]); +console.log(row); + +// Get all rows +const rows = await stmt.all([123]); +console.log(rows); + +// Iterate through rows (streaming) +for await (const row of stmt.iterate([123])) { + console.log(row); +} + +// Execute multiple statements in a batch +await conn.batch([ + "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, email TEXT)", + "INSERT INTO users (email) VALUES ('user@example.com')", + "INSERT INTO users (email) VALUES ('admin@example.com')", +]); +``` + +### Compatibility layer for libSQL API + +This driver supports the libSQL API as a compatibility layer. + +```javascript +import { createClient } from "@tursodatabase/serverless/compat"; + +const client = createClient({ + url: process.env.TURSO_DATABASE_URL, + authToken: process.env.TURSO_AUTH_TOKEN, +}); + +// Execute a single SQL statement +const result = await client.execute("SELECT * FROM users WHERE id = ?", [123]); +console.log(result.rows); + +// Execute multiple statements in a batch +await client.batch([ + "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, email TEXT)", + "INSERT INTO users (email) VALUES ('user@example.com')", + "INSERT INTO users (email) VALUES ('admin@example.com')", +]); +``` + +## Examples + +Check out the `examples/` directory for complete usage examples. + +## License + +MIT diff --git a/packages/turso-serverless/examples/remote-compat/README.md b/packages/turso-serverless/examples/remote-compat/README.md new file mode 100644 index 000000000..43ac57360 --- /dev/null +++ b/packages/turso-serverless/examples/remote-compat/README.md @@ -0,0 +1,19 @@ +# Remote + +This example demonstrates how to use Turso Cloud. + +## Install Dependencies + +```bash +npm i +``` + +## Running + +Execute the example: + +```bash +TURSO_DATABASE_URL="..." TURSO_AUTH_TOKEN="..." node index.mjs +``` + +This will connect to a remote SQLite database, insert some data, and then query the results. diff --git a/packages/turso-serverless/examples/remote-compat/index.mjs b/packages/turso-serverless/examples/remote-compat/index.mjs new file mode 100644 index 000000000..b20983c31 --- /dev/null +++ b/packages/turso-serverless/examples/remote-compat/index.mjs @@ -0,0 +1,20 @@ +import { createClient } from "@tursodatabase/serverless/compat"; + +const client = createClient({ + url: process.env.TURSO_DATABASE_URL, + authToken: process.env.TURSO_AUTH_TOKEN, +}); + +await client.batch( + [ + "CREATE TABLE IF NOT EXISTS users (email TEXT)", + "INSERT INTO users VALUES ('first@example.com')", + "INSERT INTO users VALUES ('second@example.com')", + "INSERT INTO users VALUES ('third@example.com')", + ], + "write", +); + +const result = await client.execute("SELECT * FROM users"); + +console.log("Users:", result.rows); diff --git a/packages/turso-serverless/examples/remote-compat/package-lock.json b/packages/turso-serverless/examples/remote-compat/package-lock.json new file mode 100644 index 000000000..d4b83e5d6 --- /dev/null +++ b/packages/turso-serverless/examples/remote-compat/package-lock.json @@ -0,0 +1,30 @@ +{ + "name": "batch", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "batch", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@tursodatabase/serverless": "../.." + } + }, + "../..": { + "name": "@tursodatabase/serverless", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "@types/node": "^24.0.13", + "ava": "^6.4.1", + "typescript": "^5.8.3" + } + }, + "node_modules/@tursodatabase/serverless": { + "resolved": "../..", + "link": true + } + } +} diff --git a/packages/turso-serverless/examples/remote-compat/package.json b/packages/turso-serverless/examples/remote-compat/package.json new file mode 100644 index 000000000..be627975f --- /dev/null +++ b/packages/turso-serverless/examples/remote-compat/package.json @@ -0,0 +1,10 @@ +{ + "name": "batch", + "version": "1.0.0", + "main": "index.mjs", + "author": "Giovanni Benussi", + "license": "MIT", + "dependencies": { + "@tursodatabase/serverless": "../.." + } +} diff --git a/packages/turso-serverless/examples/remote/README.md b/packages/turso-serverless/examples/remote/README.md new file mode 100644 index 000000000..43ac57360 --- /dev/null +++ b/packages/turso-serverless/examples/remote/README.md @@ -0,0 +1,19 @@ +# Remote + +This example demonstrates how to use Turso Cloud. + +## Install Dependencies + +```bash +npm i +``` + +## Running + +Execute the example: + +```bash +TURSO_DATABASE_URL="..." TURSO_AUTH_TOKEN="..." node index.mjs +``` + +This will connect to a remote SQLite database, insert some data, and then query the results. diff --git a/packages/turso-serverless/examples/remote/index.mjs b/packages/turso-serverless/examples/remote/index.mjs new file mode 100644 index 000000000..1c4f58596 --- /dev/null +++ b/packages/turso-serverless/examples/remote/index.mjs @@ -0,0 +1,36 @@ +import { connect } from "@tursodatabase/serverless"; + +const client = connect({ + url: process.env.TURSO_DATABASE_URL, + authToken: process.env.TURSO_AUTH_TOKEN, +}); + +await client.batch( + [ + "CREATE TABLE IF NOT EXISTS users (email TEXT)", + "INSERT INTO users VALUES ('first@example.com')", + "INSERT INTO users VALUES ('second@example.com')", + "INSERT INTO users VALUES ('third@example.com')", + ], + "write", +); + +// Using execute method +const result = await client.execute("SELECT * FROM users"); +console.log("Users (execute):", result.rows); + +// Using prepare and get method +const stmt = client.prepare("SELECT * FROM users LIMIT 1"); +const firstUser = await stmt.get(); +console.log("First user:", firstUser); + +// Using prepare and all method +const allUsers = await stmt.all(); +console.log("All users (all):", allUsers); + +// Using prepare and iterate method +console.log("Users (iterate):"); +const iterateStmt = client.prepare("SELECT * FROM users"); +for await (const user of iterateStmt.iterate()) { + console.log(" -", user[0]); +} diff --git a/packages/turso-serverless/examples/remote/package-lock.json b/packages/turso-serverless/examples/remote/package-lock.json new file mode 100644 index 000000000..f804230e2 --- /dev/null +++ b/packages/turso-serverless/examples/remote/package-lock.json @@ -0,0 +1,29 @@ +{ + "name": "remote", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "remote", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@tursodatabase/serverless": "../.." + } + }, + "../..": { + "name": "@tursodatabase/serverless", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@types/node": "^24.0.13", + "typescript": "^5.8.3" + } + }, + "node_modules/@tursodatabase/serverless": { + "resolved": "../..", + "link": true + } + } +} diff --git a/packages/turso-serverless/examples/remote/package.json b/packages/turso-serverless/examples/remote/package.json new file mode 100644 index 000000000..f55d03d96 --- /dev/null +++ b/packages/turso-serverless/examples/remote/package.json @@ -0,0 +1,10 @@ +{ + "name": "remote", + "version": "1.0.0", + "main": "index.mjs", + "author": "Giovanni Benussi", + "license": "MIT", + "dependencies": { + "@tursodatabase/serverless": "../.." + } +} diff --git a/packages/turso-serverless/integration-tests/compat.test.mjs b/packages/turso-serverless/integration-tests/compat.test.mjs new file mode 100644 index 000000000..da8d00e7f --- /dev/null +++ b/packages/turso-serverless/integration-tests/compat.test.mjs @@ -0,0 +1,54 @@ +import test from 'ava'; +import { createClient, LibsqlError } from '../dist/compat/index.js'; + +test.serial('createClient validates supported config options', async t => { + // Valid config should work + t.notThrows(() => { + const client = createClient({ + url: process.env.TURSO_DATABASE_URL, + authToken: process.env.TURSO_AUTH_TOKEN, + }); + client.close(); + }); +}); + +test.serial('createClient rejects unsupported config options', async t => { + const error = t.throws(() => { + createClient({ + url: process.env.TURSO_DATABASE_URL, + authToken: process.env.TURSO_AUTH_TOKEN, + encryptionKey: 'some-key', + syncUrl: 'https://sync.example.com', + }); + }, { instanceOf: LibsqlError }); + + t.is(error.code, 'UNSUPPORTED_CONFIG'); + t.regex(error.message, /encryptionKey.*syncUrl/); + t.regex(error.message, /Only 'url' and 'authToken' are supported/); +}); + +test.serial('createClient requires url config option', async t => { + const error = t.throws(() => { + createClient({ + authToken: process.env.TURSO_AUTH_TOKEN, + }); + }, { instanceOf: LibsqlError }); + + t.is(error.code, 'MISSING_URL'); + t.regex(error.message, /Missing required 'url'/); +}); + +test.serial('createClient works with basic libSQL API', async t => { + const client = createClient({ + url: process.env.TURSO_DATABASE_URL, + authToken: process.env.TURSO_AUTH_TOKEN, + }); + + // Test basic functionality + const result = await client.execute('SELECT 42 as answer'); + t.is(result.rows[0][0], 42); + t.is(result.columns[0], 'answer'); + + client.close(); + t.true(client.closed); +}); \ No newline at end of file diff --git a/packages/turso-serverless/integration-tests/serverless.test.mjs b/packages/turso-serverless/integration-tests/serverless.test.mjs new file mode 100644 index 000000000..a0419ba87 --- /dev/null +++ b/packages/turso-serverless/integration-tests/serverless.test.mjs @@ -0,0 +1,119 @@ +import test from 'ava'; +import { connect } from '../dist/index.js'; + +const client = connect({ + url: process.env.TURSO_DATABASE_URL, + authToken: process.env.TURSO_AUTH_TOKEN, +}); + +test.serial('execute() method creates table and inserts data', async t => { + await client.execute('DROP TABLE IF EXISTS test_users'); + + await client.execute('CREATE TABLE test_users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)'); + + const insertResult = await client.execute( + 'INSERT INTO test_users (name, email) VALUES (?, ?)', + ['John Doe', 'john@example.com'] + ); + + t.is(insertResult.rowsAffected, 1); + t.is(typeof insertResult.lastInsertRowid, 'number'); +}); + +test.serial('execute() method queries data correctly', async t => { + const queryResult = await client.execute('SELECT * FROM test_users WHERE name = ?', ['John Doe']); + + t.is(queryResult.columns.length, 3); + t.true(queryResult.columns.includes('id')); + t.true(queryResult.columns.includes('name')); + t.true(queryResult.columns.includes('email')); + + t.is(queryResult.rows.length, 1); + t.is(queryResult.rows[0][1], 'John Doe'); + t.is(queryResult.rows[0][2], 'john@example.com'); +}); + +test.serial('prepare() method creates statement', async t => { + const stmt = client.prepare('SELECT * FROM test_users WHERE name = ?'); + + const row = await stmt.get(['John Doe']); + t.is(row[1], 'John Doe'); + t.is(row[2], 'john@example.com'); + + const rows = await stmt.all(['John Doe']); + t.is(rows.length, 1); + t.is(rows[0][1], 'John Doe'); +}); + +test.serial('statement iterate() method works', async t => { + // Ensure test data exists + await client.execute('CREATE TABLE IF NOT EXISTS test_users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)'); + await client.execute('INSERT OR IGNORE INTO test_users (name, email) VALUES (?, ?)', ['John Doe', 'john@example.com']); + + const stmt = client.prepare('SELECT * FROM test_users'); + + const rows = []; + for await (const row of stmt.iterate()) { + rows.push(row); + } + + t.true(rows.length >= 1); + t.is(rows[0][1], 'John Doe'); +}); + +test.serial('batch() method executes multiple statements', async t => { + await client.execute('DROP TABLE IF EXISTS test_products'); + + const batchResult = await client.batch([ + 'CREATE TABLE test_products (id INTEGER PRIMARY KEY, name TEXT, price REAL)', + 'INSERT INTO test_products (name, price) VALUES ("Widget", 9.99)', + 'INSERT INTO test_products (name, price) VALUES ("Gadget", 19.99)', + 'INSERT INTO test_products (name, price) VALUES ("Tool", 29.99)' + ]); + + t.is(batchResult.rowsAffected, 3); + + const queryResult = await client.execute('SELECT COUNT(*) as count FROM test_products'); + t.is(queryResult.rows[0][0], 3); +}); + +test.serial('execute() method queries a single value', async t => { + const rs = await client.execute('SELECT 42'); + + t.is(rs.columns.length, 1); + t.is(rs.columnTypes.length, 1); + t.is(rs.rows.length, 1); + t.is(rs.rows[0].length, 1); + t.is(rs.rows[0][0], 42); +}); + +test.serial('execute() method queries a single row', async t => { + const rs = await client.execute( + "SELECT 1 AS one, 'two' AS two, 0.5 AS three" + ); + + t.deepEqual(rs.columns, ["one", "two", "three"]); + t.deepEqual(rs.columnTypes, ["", "", ""]); + t.is(rs.rows.length, 1); + + const r = rs.rows[0]; + t.is(r.length, 3); + t.deepEqual(Array.from(r), [1, "two", 0.5]); + t.deepEqual(Object.entries(r), [ + ["0", 1], + ["1", "two"], + ["2", 0.5], + ]); + + // Test column name access + t.is(r.one, 1); + t.is(r.two, "two"); + t.is(r.three, 0.5); +}); + +test.serial('error handling works correctly', async t => { + const error = await t.throwsAsync( + () => client.execute('SELECT * FROM nonexistent_table') + ); + t.regex(error.message, /SQLite error.*no such table|no such table|HTTP error/); +}); \ No newline at end of file diff --git a/packages/turso-serverless/package-lock.json b/packages/turso-serverless/package-lock.json new file mode 100644 index 000000000..c2869163e --- /dev/null +++ b/packages/turso-serverless/package-lock.json @@ -0,0 +1,2394 @@ +{ + "name": "@tursodatabase/serverless", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@tursodatabase/serverless", + "version": "0.1.0", + "license": "MIT", + "devDependencies": { + "@types/node": "^24.0.13", + "ava": "^6.4.1", + "typescript": "^5.8.3" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.0.tgz", + "integrity": "sha512-llMXd39jtP0HpQLVI37Bf1m2ADlEb35GYSh1SDSLsBhR+5iCxiNGlT31yqbNtVHygHAtMy6dWFERpU2JgufhPg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "consola": "^3.2.3", + "detect-libc": "^2.0.0", + "https-proxy-agent": "^7.0.5", + "node-fetch": "^2.6.7", + "nopt": "^8.0.0", + "semver": "^7.5.3", + "tar": "^7.4.0" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.2.0.tgz", + "integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/node": { + "version": "24.0.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.13.tgz", + "integrity": "sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/@vercel/nft": { + "version": "0.29.4", + "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.29.4.tgz", + "integrity": "sha512-6lLqMNX3TuycBPABycx7A9F1bHQR7kiQln6abjFbPrf5C/05qHM9M5E4PeTE59c7z8g6vHnx1Ioihb2AQl7BTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mapbox/node-pre-gyp": "^2.0.0", + "@rollup/pluginutils": "^5.1.3", + "acorn": "^8.6.0", + "acorn-import-attributes": "^1.9.5", + "async-sema": "^3.1.1", + "bindings": "^1.4.0", + "estree-walker": "2.0.2", + "glob": "^10.4.5", + "graceful-fs": "^4.2.9", + "node-gyp-build": "^4.2.2", + "picomatch": "^4.0.2", + "resolve-from": "^5.0.0" + }, + "bin": { + "nft": "out/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/abbrev": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.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-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arrgv": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/arrgv/-/arrgv-1.0.2.tgz", + "integrity": "sha512-a4eg4yhp7mmruZDQFqVMlxNRFGi/i1r87pt8SDHy0/I8PqSXoUTlWZRdAZo0VXgvEARcujbtTk8kiZRi1uDGRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/arrify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-3.0.0.tgz", + "integrity": "sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/async-sema": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/async-sema/-/async-sema-3.1.1.tgz", + "integrity": "sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ava": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/ava/-/ava-6.4.1.tgz", + "integrity": "sha512-vxmPbi1gZx9zhAjHBgw81w/iEDKcrokeRk/fqDTyA2DQygZ0o+dUGRHFOtX8RA5N0heGJTTsIk7+xYxitDb61Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vercel/nft": "^0.29.4", + "acorn": "^8.15.0", + "acorn-walk": "^8.3.4", + "ansi-styles": "^6.2.1", + "arrgv": "^1.0.2", + "arrify": "^3.0.0", + "callsites": "^4.2.0", + "cbor": "^10.0.9", + "chalk": "^5.4.1", + "chunkd": "^2.0.1", + "ci-info": "^4.3.0", + "ci-parallel-vars": "^1.0.1", + "cli-truncate": "^4.0.0", + "code-excerpt": "^4.0.0", + "common-path-prefix": "^3.0.0", + "concordance": "^5.0.4", + "currently-unhandled": "^0.4.1", + "debug": "^4.4.1", + "emittery": "^1.2.0", + "figures": "^6.1.0", + "globby": "^14.1.0", + "ignore-by-default": "^2.1.0", + "indent-string": "^5.0.0", + "is-plain-object": "^5.0.0", + "is-promise": "^4.0.0", + "matcher": "^5.0.0", + "memoize": "^10.1.0", + "ms": "^2.1.3", + "p-map": "^7.0.3", + "package-config": "^5.0.0", + "picomatch": "^4.0.2", + "plur": "^5.1.0", + "pretty-ms": "^9.2.0", + "resolve-cwd": "^3.0.0", + "stack-utils": "^2.0.6", + "strip-ansi": "^7.1.0", + "supertap": "^3.0.1", + "temp-dir": "^3.0.0", + "write-file-atomic": "^6.0.0", + "yargs": "^17.7.2" + }, + "bin": { + "ava": "entrypoints/cli.mjs" + }, + "engines": { + "node": "^18.18 || ^20.8 || ^22 || ^23 || >=24" + }, + "peerDependencies": { + "@ava/typescript": "*" + }, + "peerDependenciesMeta": { + "@ava/typescript": { + "optional": true + } + } + }, + "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/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/blueimp-md5": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz", + "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-4.2.0.tgz", + "integrity": "sha512-kfzR4zzQtAE9PC7CzZsjl3aBNbXWuXiSeOCdLcPpBfGW8YuCqQHcRPFDbr/BPVmd3EEPVpuFzLyuT/cUhPr4OQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cbor": { + "version": "10.0.9", + "resolved": "https://registry.npmjs.org/cbor/-/cbor-10.0.9.tgz", + "integrity": "sha512-KEWYehb/vJkRmigctVQLsz73Us2RNnITo/wOwQV5AtZpLGH1r2PPlsNHdsX460YuHZCyhLklbYzAOuJfOeg34Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "nofilter": "^3.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/chunkd": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/chunkd/-/chunkd-2.0.1.tgz", + "integrity": "sha512-7d58XsFmOq0j6el67Ug9mHf9ELUXsQXYJBkyxhH/k+6Ke0qXRnv0kbemx+Twc6fRJ07C49lcbdgm9FL1Ei/6SQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", + "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ci-parallel-vars": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ci-parallel-vars/-/ci-parallel-vars-1.0.1.tgz", + "integrity": "sha512-uvzpYrpmidaoxvIQHM+rKSrigjOe9feHYbw4uOI2gdfe1C3xIlxO+kVXq83WQWNniTf8bAxVpy+cQeFQsMERKg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/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/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/code-excerpt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", + "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "convert-to-spaces": "^2.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "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/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "dev": true, + "license": "ISC" + }, + "node_modules/concordance": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/concordance/-/concordance-5.0.4.tgz", + "integrity": "sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "date-time": "^3.1.0", + "esutils": "^2.0.3", + "fast-diff": "^1.2.0", + "js-string-escape": "^1.0.1", + "lodash": "^4.17.15", + "md5-hex": "^3.0.1", + "semver": "^7.3.2", + "well-known-symbols": "^2.0.0" + }, + "engines": { + "node": ">=10.18.0 <11 || >=12.14.0 <13 || >=14" + } + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/convert-to-spaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", + "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-find-index": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/date-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/date-time/-/date-time-3.1.0.tgz", + "integrity": "sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "time-zone": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emittery": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-1.2.0.tgz", + "integrity": "sha512-KxdRyyFcS85pH3dnU8Y5yFUm2YJdaHwcBZWrfG8o89ZY9a13/f9itbN+YG3ELbBo9Pg5zvIozstmuV8bX13q6g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, + "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": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up-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/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "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/ignore-by-default": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-2.1.0.tgz", + "integrity": "sha512-yiWd4GVmJp0Q6ghmM2B/V3oZGRmjrKLXvHR3TE1nfoXsmoggllfZUQe74EN0fJdPFZu2NIvNdrMMLm3OsV7Ohw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10 <11 || >=12 <13 || >=14" + } + }, + "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/irregular-plurals": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.5.0.tgz", + "integrity": "sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-string-escape": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", + "integrity": "sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/load-json-file": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-7.0.1.tgz", + "integrity": "sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/matcher": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-5.0.0.tgz", + "integrity": "sha512-s2EMBOWtXFc8dgqvoAzKJXxNHibcdJMV0gwqKUaw9E2JBJuGUK7DrNKrA6g/i+v72TT16+6sVm5mS3thaMLQUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/md5-hex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.1.tgz", + "integrity": "sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==", + "dev": true, + "license": "MIT", + "dependencies": { + "blueimp-md5": "^2.10.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/memoize": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/memoize/-/memoize-10.1.0.tgz", + "integrity": "sha512-MMbFhJzh4Jlg/poq1si90XRlTZRDHVqdlz2mPyGJ6kqMpyHUyVpDd5gpFAvVehW64+RA1eKE9Yt8aSLY7w2Kgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/memoize?sponsor=1" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "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/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "dev": true, + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/nofilter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", + "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.19" + } + }, + "node_modules/nopt": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/p-map": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-config": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/package-config/-/package-config-5.0.0.tgz", + "integrity": "sha512-GYTTew2slBcYdvRHqjhwaaydVMvn/qrGC323+nKclYioNSLTDUM/lGgtGTgyHVtYcozb+XkE8CNhwcraOmZ9Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up-simple": "^1.0.0", + "load-json-file": "^7.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/plur": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/plur/-/plur-5.1.0.tgz", + "integrity": "sha512-VP/72JeXqak2KiOzjgKtQen5y3IZHn+9GOuLDafPv0eXa47xq0At93XahYBs26MsifCQ4enGKwbjBTKgb9QJXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "irregular-plurals": "^3.3.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pretty-ms": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz", + "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/supertap": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/supertap/-/supertap-3.0.1.tgz", + "integrity": "sha512-u1ZpIBCawJnO+0QePsEiOknOfCRq0yERxiAchT0i4li0WHNUJbf0evXXSXOcCAR4M8iMDoajXYmstm/qO81Isw==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^5.0.0", + "js-yaml": "^3.14.1", + "serialize-error": "^7.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/temp-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", + "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/time-zone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz", + "integrity": "sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/well-known-symbols": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/well-known-symbols/-/well-known-symbols-2.0.0.tgz", + "integrity": "sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=6" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/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/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/write-file-atomic": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-6.0.0.tgz", + "integrity": "sha512-GmqrO8WJ1NuzJ2DrziEI2o57jKAVIQNf8a18W3nCYU3H7PNWqCCVTeH6/NQE93CIllIgQS98rrmVkYgTX9fFJQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/packages/turso-serverless/package.json b/packages/turso-serverless/package.json new file mode 100644 index 000000000..fc116d711 --- /dev/null +++ b/packages/turso-serverless/package.json @@ -0,0 +1,35 @@ +{ + "name": "@tursodatabase/serverless", + "version": "0.1.0", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist", + "README.md" + ], + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + }, + "./compat": { + "import": "./dist/compat/index.js", + "types": "./dist/compat/index.d.ts" + } + }, + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "test": "ava integration-tests/*.test.mjs" + }, + "keywords": [], + "author": "", + "license": "MIT", + "description": "", + "devDependencies": { + "@types/node": "^24.0.13", + "ava": "^6.4.1", + "typescript": "^5.8.3" + } +} diff --git a/packages/turso-serverless/src/compat.ts b/packages/turso-serverless/src/compat.ts new file mode 100644 index 000000000..53fc8444a --- /dev/null +++ b/packages/turso-serverless/src/compat.ts @@ -0,0 +1,322 @@ +import { Connection, connect, type Config as TursoConfig } from './connection.js'; + +/** + * Configuration options for creating a libSQL-compatible client. + * + * @remarks + * This interface matches the libSQL client configuration but only `url` and `authToken` + * are supported in the serverless compatibility layer. Other options will throw validation errors. + */ +export interface Config { + /** Database URL (required) */ + url: string; + /** Authentication token for the database */ + authToken?: string; + /** @deprecated Database encryption key - not supported in serverless mode */ + encryptionKey?: string; + /** @deprecated Sync server URL - not supported in serverless mode */ + syncUrl?: string; + /** @deprecated Sync frequency in seconds - not supported in serverless mode */ + syncInterval?: number; + /** @deprecated Consistency mode - not supported in serverless mode */ + readYourWrites?: boolean; + /** @deprecated Offline mode support - not supported in serverless mode */ + offline?: boolean; + /** @deprecated TLS settings - not supported in serverless mode */ + tls?: boolean; + /** @deprecated Integer handling mode - not supported in serverless mode */ + intMode?: "number" | "bigint" | "string"; + /** @deprecated Custom fetch implementation - not supported in serverless mode */ + fetch?: Function; + /** @deprecated Concurrent request limit - not supported in serverless mode */ + concurrency?: number; +} + +/** Input value types accepted by libSQL statements */ +export type InValue = null | string | number | bigint | ArrayBuffer | boolean | Uint8Array | Date; + +/** Input arguments - either positional array or named object */ +export type InArgs = Array | Record; + +/** Input statement - either SQL string or object with sql and args */ +export type InStatement = { sql: string; args?: InArgs } | string; + +/** Transaction execution modes */ +export type TransactionMode = "write" | "read" | "deferred"; + +/** + * A result row that can be accessed both as an array and as an object. + * Supports both numeric indexing (row[0]) and column name access (row.column_name). + */ +export interface Row { + length: number; + [index: number]: InValue; + [name: string]: InValue; +} + +/** + * Result set returned from SQL statement execution. + */ +export interface ResultSet { + /** Column names in the result set */ + columns: Array; + /** Column type information */ + columnTypes: Array; + /** Result rows */ + rows: Array; + /** Number of rows affected by the statement */ + rowsAffected: number; + /** ID of the last inserted row (for INSERT statements) */ + lastInsertRowid: bigint | undefined; + /** Convert result set to JSON */ + toJSON(): any; +} + +/** + * libSQL-compatible error class with error codes. + */ +export class LibsqlError extends Error { + /** Machine-readable error code */ + code: string; + /** Raw numeric error code (if available) */ + rawCode?: number; + + constructor(message: string, code: string, rawCode?: number) { + super(message); + this.name = 'LibsqlError'; + this.code = code; + this.rawCode = rawCode; + } +} + +/** + * Interactive transaction interface (not implemented in serverless mode). + * + * @remarks + * Transactions are not supported in the serverless compatibility layer. + * Calling transaction() will throw a LibsqlError. + */ +export interface Transaction { + execute(stmt: InStatement): Promise; + batch(stmts: Array): Promise>; + executeMultiple(sql: string): Promise; + commit(): Promise; + rollback(): Promise; + close(): void; + closed: boolean; +} + +/** + * libSQL-compatible client interface. + * + * This interface matches the standard libSQL client API for drop-in compatibility. + * Some methods are not implemented in the serverless compatibility layer. + */ +export interface Client { + execute(stmt: InStatement): Promise; + execute(sql: string, args?: InArgs): Promise; + batch(stmts: Array, mode?: TransactionMode): Promise>; + migrate(stmts: Array): Promise>; + transaction(mode?: TransactionMode): Promise; + executeMultiple(sql: string): Promise; + sync(): Promise; + close(): void; + closed: boolean; + protocol: string; +} + +class LibSQLClient implements Client { + private connection: Connection; + private _closed = false; + + constructor(config: Config) { + this.validateConfig(config); + + const tursoConfig: TursoConfig = { + url: config.url, + authToken: config.authToken || '' + }; + this.connection = connect(tursoConfig); + } + + private validateConfig(config: Config): void { + // Check for unsupported config options + const unsupportedOptions: Array<{ key: keyof Config; value: any }> = []; + + if (config.encryptionKey !== undefined) { + unsupportedOptions.push({ key: 'encryptionKey', value: config.encryptionKey }); + } + if (config.syncUrl !== undefined) { + unsupportedOptions.push({ key: 'syncUrl', value: config.syncUrl }); + } + if (config.syncInterval !== undefined) { + unsupportedOptions.push({ key: 'syncInterval', value: config.syncInterval }); + } + if (config.readYourWrites !== undefined) { + unsupportedOptions.push({ key: 'readYourWrites', value: config.readYourWrites }); + } + if (config.offline !== undefined) { + unsupportedOptions.push({ key: 'offline', value: config.offline }); + } + if (config.tls !== undefined) { + unsupportedOptions.push({ key: 'tls', value: config.tls }); + } + if (config.intMode !== undefined) { + unsupportedOptions.push({ key: 'intMode', value: config.intMode }); + } + if (config.fetch !== undefined) { + unsupportedOptions.push({ key: 'fetch', value: config.fetch }); + } + if (config.concurrency !== undefined) { + unsupportedOptions.push({ key: 'concurrency', value: config.concurrency }); + } + + if (unsupportedOptions.length > 0) { + const optionsList = unsupportedOptions.map(opt => `'${opt.key}'`).join(', '); + throw new LibsqlError( + `Unsupported configuration options: ${optionsList}. Only 'url' and 'authToken' are supported in the serverless compatibility layer.`, + "UNSUPPORTED_CONFIG" + ); + } + + // Validate required options + if (!config.url) { + throw new LibsqlError("Missing required 'url' configuration option", "MISSING_URL"); + } + } + + get closed(): boolean { + return this._closed; + } + + get protocol(): string { + return "http"; + } + + private normalizeStatement(stmt: InStatement): { sql: string; args: any[] } { + if (typeof stmt === 'string') { + return { sql: stmt, args: [] }; + } + + const args = stmt.args || []; + if (Array.isArray(args)) { + return { sql: stmt.sql, args }; + } + + // Convert named args to positional args (simplified) + return { sql: stmt.sql, args: Object.values(args) }; + } + + private convertResult(result: any): ResultSet { + const resultSet: ResultSet = { + columns: result.columns || [], + columnTypes: result.columnTypes || [], + rows: result.rows || [], + rowsAffected: result.rowsAffected || 0, + lastInsertRowid: result.lastInsertRowid ? BigInt(result.lastInsertRowid) : undefined, + toJSON() { + return { + columns: this.columns, + columnTypes: this.columnTypes, + rows: this.rows, + rowsAffected: this.rowsAffected, + lastInsertRowid: this.lastInsertRowid?.toString() + }; + } + }; + + return resultSet; + } + + async execute(stmt: InStatement): Promise; + async execute(sql: string, args?: InArgs): Promise; + async execute(stmtOrSql: InStatement | string, args?: InArgs): Promise { + try { + if (this._closed) { + throw new LibsqlError("Client is closed", "CLIENT_CLOSED"); + } + + let normalizedStmt: { sql: string; args: any[] }; + + if (typeof stmtOrSql === 'string') { + const normalizedArgs = args ? (Array.isArray(args) ? args : Object.values(args)) : []; + normalizedStmt = { sql: stmtOrSql, args: normalizedArgs }; + } else { + normalizedStmt = this.normalizeStatement(stmtOrSql); + } + + const result = await this.connection.execute(normalizedStmt.sql, normalizedStmt.args); + return this.convertResult(result); + } catch (error: any) { + throw new LibsqlError(error.message, "EXECUTE_ERROR"); + } + } + + async batch(stmts: Array, mode?: TransactionMode): Promise> { + try { + if (this._closed) { + throw new LibsqlError("Client is closed", "CLIENT_CLOSED"); + } + + const sqlStatements = stmts.map(stmt => { + const normalized = this.normalizeStatement(stmt); + return normalized.sql; // For now, ignore args in batch + }); + + const result = await this.connection.batch(sqlStatements, mode); + + // Return array of result sets (simplified - actual implementation would be more complex) + return [this.convertResult(result)]; + } catch (error: any) { + throw new LibsqlError(error.message, "BATCH_ERROR"); + } + } + + async migrate(stmts: Array): Promise> { + // For now, just call batch - in a real implementation this would disable foreign keys + return this.batch(stmts, "write"); + } + + async transaction(mode?: TransactionMode): Promise { + throw new LibsqlError("Transactions not implemented", "NOT_IMPLEMENTED"); + } + + async executeMultiple(sql: string): Promise { + throw new LibsqlError("Execute multiple not implemented", "NOT_IMPLEMENTED"); + } + + async sync(): Promise { + throw new LibsqlError("Sync not supported for remote databases", "NOT_SUPPORTED"); + } + + close(): void { + this._closed = true; + } +} + +/** + * Create a libSQL-compatible client for Turso database access. + * + * This function provides compatibility with the standard libSQL client API + * while using the Turso serverless driver under the hood. + * + * @param config - Configuration object (only url and authToken are supported) + * @returns A Client instance compatible with libSQL API + * @throws LibsqlError if unsupported configuration options are provided + * + * @example + * ```typescript + * import { createClient } from "@tursodatabase/serverless/compat"; + * + * const client = createClient({ + * url: process.env.TURSO_DATABASE_URL, + * authToken: process.env.TURSO_AUTH_TOKEN + * }); + * + * const result = await client.execute("SELECT * FROM users"); + * console.log(result.rows); + * ``` + */ +export function createClient(config: Config): Client { + return new LibSQLClient(config); +} \ No newline at end of file diff --git a/packages/turso-serverless/src/compat/index.ts b/packages/turso-serverless/src/compat/index.ts new file mode 100644 index 000000000..c9b10a724 --- /dev/null +++ b/packages/turso-serverless/src/compat/index.ts @@ -0,0 +1 @@ +export * from '../compat.js'; \ No newline at end of file diff --git a/packages/turso-serverless/src/connection.ts b/packages/turso-serverless/src/connection.ts new file mode 100644 index 000000000..d2021cea6 --- /dev/null +++ b/packages/turso-serverless/src/connection.ts @@ -0,0 +1,100 @@ +import { Session, type SessionConfig } from './session.js'; +import { Statement } from './statement.js'; + +/** + * Configuration options for connecting to a Turso database. + */ +export interface Config extends SessionConfig {} + +/** + * A connection to a Turso database. + * + * Provides methods for executing SQL statements and managing prepared statements. + * Uses the SQL over HTTP protocol with streaming cursor support for optimal performance. + */ +export class Connection { + private config: Config; + private session: Session; + + constructor(config: Config) { + this.config = config; + this.session = new Session(config); + } + + /** + * Prepare a SQL statement for execution. + * + * Each prepared statement gets its own session to avoid conflicts during concurrent execution. + * + * @param sql - The SQL statement to prepare + * @returns A Statement object that can be executed multiple ways + * + * @example + * ```typescript + * const stmt = client.prepare("SELECT * FROM users WHERE id = ?"); + * const user = await stmt.get([123]); + * const allUsers = await stmt.all(); + * ``` + */ + prepare(sql: string): Statement { + return new Statement(this.config, sql); + } + + /** + * Execute a SQL statement and return all results. + * + * @param sql - The SQL statement to execute + * @param args - Optional array of parameter values + * @returns Promise resolving to the complete result set + * + * @example + * ```typescript + * const result = await client.execute("SELECT * FROM users"); + * console.log(result.rows); + * ``` + */ + async execute(sql: string, args: any[] = []): Promise { + return this.session.execute(sql, args); + } + + + /** + * Execute multiple SQL statements in a batch. + * + * @param statements - Array of SQL statements to execute + * @param mode - Optional transaction mode (currently unused) + * @returns Promise resolving to batch execution results + * + * @example + * ```typescript + * await client.batch([ + * "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)", + * "INSERT INTO users (name) VALUES ('Alice')", + * "INSERT INTO users (name) VALUES ('Bob')" + * ]); + * ``` + */ + async batch(statements: string[], mode?: string): Promise { + return this.session.batch(statements); + } +} + +/** + * Create a new connection to a Turso database. + * + * @param config - Configuration object with database URL and auth token + * @returns A new Connection instance + * + * @example + * ```typescript + * import { connect } from "@tursodatabase/serverless"; + * + * const client = connect({ + * url: process.env.TURSO_DATABASE_URL, + * authToken: process.env.TURSO_AUTH_TOKEN + * }); + * ``` + */ +export function connect(config: Config): Connection { + return new Connection(config); +} diff --git a/packages/turso-serverless/src/index.ts b/packages/turso-serverless/src/index.ts new file mode 100644 index 000000000..996212c40 --- /dev/null +++ b/packages/turso-serverless/src/index.ts @@ -0,0 +1,3 @@ +// Turso serverless driver entry point +export { Connection, connect, type Config } from './connection.js'; +export { Statement } from './statement.js'; \ No newline at end of file diff --git a/packages/turso-serverless/src/protocol.ts b/packages/turso-serverless/src/protocol.ts new file mode 100644 index 000000000..dfe85c4e0 --- /dev/null +++ b/packages/turso-serverless/src/protocol.ts @@ -0,0 +1,245 @@ +export interface Value { + type: 'null' | 'integer' | 'float' | 'text' | 'blob'; + value?: string | number; + base64?: string; +} + +export interface Column { + name: string; + decltype: string; +} + +export interface ExecuteResult { + cols: Column[]; + rows: Value[][]; + affected_row_count: number; + last_insert_rowid?: string; +} + +export interface ExecuteRequest { + type: 'execute'; + stmt: { + sql: string; + args: Value[]; + named_args: Value[]; + want_rows: boolean; + }; +} + +export interface BatchStep { + stmt: { + sql: string; + args: Value[]; + want_rows: boolean; + }; + condition?: { + type: 'ok'; + step: number; + }; +} + +export interface BatchRequest { + type: 'batch'; + batch: { + steps: BatchStep[]; + }; +} + +export interface PipelineRequest { + baton: string | null; + requests: (ExecuteRequest | BatchRequest)[]; +} + +export interface PipelineResponse { + baton: string | null; + base_url: string | null; + results: Array<{ + type: 'ok' | 'error'; + response?: { + type: 'execute' | 'batch'; + result: ExecuteResult; + }; + error?: { + message: string; + code: string; + }; + }>; +} + +export function encodeValue(value: any): Value { + if (value === null || value === undefined) { + return { type: 'null' }; + } + + if (typeof value === 'number') { + if (Number.isInteger(value)) { + return { type: 'integer', value: value.toString() }; + } + return { type: 'float', value }; + } + + if (typeof value === 'string') { + return { type: 'text', value }; + } + + if (value instanceof ArrayBuffer || value instanceof Uint8Array) { + const base64 = btoa(String.fromCharCode(...new Uint8Array(value))); + return { type: 'blob', base64 }; + } + + return { type: 'text', value: String(value) }; +} + +export function decodeValue(value: Value): any { + switch (value.type) { + case 'null': + return null; + case 'integer': + return parseInt(value.value as string, 10); + case 'float': + return value.value as number; + case 'text': + return value.value as string; + case 'blob': + if (value.base64) { + const binaryString = atob(value.base64); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes; + } + return null; + default: + return null; + } +} + +export interface CursorRequest { + baton: string | null; + batch: { + steps: BatchStep[]; + }; +} + +export interface CursorResponse { + baton: string | null; + base_url: string | null; +} + +export interface CursorEntry { + type: 'step_begin' | 'step_end' | 'step_error' | 'row' | 'error'; + step?: number; + cols?: Column[]; + row?: Value[]; + affected_row_count?: number; + last_insert_rowid?: string; + error?: { + message: string; + code: string; + }; +} + +export async function executeCursor( + url: string, + authToken: string, + request: CursorRequest +): Promise<{ response: CursorResponse; entries: AsyncGenerator }> { + const response = await fetch(`${url}/v3/cursor`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${authToken}`, + }, + body: JSON.stringify(request), + }); + + if (!response.ok) { + let errorMessage = `HTTP error! status: ${response.status}`; + try { + const errorBody = await response.text(); + const errorData = JSON.parse(errorBody); + if (errorData.message) { + errorMessage = errorData.message; + } + } catch { + // If we can't parse the error body, use the default HTTP error message + } + throw new Error(errorMessage); + } + + const reader = response.body?.getReader(); + if (!reader) { + throw new Error('No response body'); + } + + const decoder = new TextDecoder(); + let buffer = ''; + let isFirstLine = true; + let cursorResponse: CursorResponse; + + async function* parseEntries(): AsyncGenerator { + try { + while (true) { + const { done, value } = await reader!.read(); + if (done) break; + + buffer += decoder.decode(value, { stream: true }); + + let newlineIndex; + while ((newlineIndex = buffer.indexOf('\n')) !== -1) { + const line = buffer.slice(0, newlineIndex).trim(); + buffer = buffer.slice(newlineIndex + 1); + + if (line) { + if (isFirstLine) { + cursorResponse = JSON.parse(line); + isFirstLine = false; + } else { + yield JSON.parse(line) as CursorEntry; + } + } + } + } + } finally { + reader!.releaseLock(); + } + } + + const entries = parseEntries(); + + // Get the first entry to parse the cursor response + const firstEntry = await entries.next(); + if (!firstEntry.done) { + // Put the first entry back + const generator = (async function* () { + yield firstEntry.value; + yield* entries; + })(); + + return { response: cursorResponse!, entries: generator }; + } + + return { response: cursorResponse!, entries }; +} + +export async function executePipeline( + url: string, + authToken: string, + request: PipelineRequest +): Promise { + const response = await fetch(`${url}/v3/pipeline`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${authToken}`, + }, + body: JSON.stringify(request), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return response.json(); +} \ No newline at end of file diff --git a/packages/turso-serverless/src/session.ts b/packages/turso-serverless/src/session.ts new file mode 100644 index 000000000..afddee401 --- /dev/null +++ b/packages/turso-serverless/src/session.ts @@ -0,0 +1,216 @@ +import { + executeCursor, + encodeValue, + decodeValue, + type CursorRequest, + type CursorResponse, + type CursorEntry +} from './protocol.js'; + +/** + * Configuration options for a session. + */ +export interface SessionConfig { + /** Database URL */ + url: string; + /** Authentication token */ + authToken: string; +} + +function normalizeUrl(url: string): string { + return url.replace(/^libsql:\/\//, 'https://'); +} + +function isValidIdentifier(str: string): boolean { + return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(str); +} + +/** + * A database session that manages the connection state and baton. + * + * Each session maintains its own connection state and can execute SQL statements + * independently without interfering with other sessions. + */ +export class Session { + private config: SessionConfig; + private baton: string | null = null; + private baseUrl: string; + + constructor(config: SessionConfig) { + this.config = config; + this.baseUrl = normalizeUrl(config.url); + } + + /** + * Execute a SQL statement and return all results. + * + * @param sql - The SQL statement to execute + * @param args - Optional array of parameter values + * @returns Promise resolving to the complete result set + */ + async execute(sql: string, args: any[] = []): Promise { + const { response, entries } = await this.executeRaw(sql, args); + const result = await this.processCursorEntries(entries); + return result; + } + + /** + * Execute a SQL statement and return the raw response and entries. + * + * @param sql - The SQL statement to execute + * @param args - Optional array of parameter values + * @returns Promise resolving to the raw response and cursor entries + */ + async executeRaw(sql: string, args: any[] = []): Promise<{ response: CursorResponse; entries: AsyncGenerator }> { + const request: CursorRequest = { + baton: this.baton, + batch: { + steps: [{ + stmt: { + sql, + args: args.map(encodeValue), + want_rows: true + } + }] + } + }; + + const { response, entries } = await executeCursor(this.baseUrl, this.config.authToken, request); + + this.baton = response.baton; + if (response.base_url) { + this.baseUrl = response.base_url; + } + + return { response, entries }; + } + + /** + * Process cursor entries into a structured result. + * + * @param entries - Async generator of cursor entries + * @returns Promise resolving to the processed result + */ + async processCursorEntries(entries: AsyncGenerator): Promise { + let columns: string[] = []; + let columnTypes: string[] = []; + let rows: any[] = []; + let rowsAffected = 0; + let lastInsertRowid: number | undefined; + + for await (const entry of entries) { + switch (entry.type) { + case 'step_begin': + if (entry.cols) { + columns = entry.cols.map(col => col.name); + columnTypes = entry.cols.map(col => col.decltype || ''); + } + break; + case 'row': + if (entry.row) { + const decodedRow = entry.row.map(decodeValue); + const rowObject = this.createRowObject(decodedRow, columns); + rows.push(rowObject); + } + break; + case 'step_end': + if (entry.affected_row_count !== undefined) { + rowsAffected = entry.affected_row_count; + } + if (entry.last_insert_rowid) { + lastInsertRowid = parseInt(entry.last_insert_rowid, 10); + } + break; + case 'step_error': + case 'error': + throw new Error(entry.error?.message || 'SQL execution failed'); + } + } + + return { + columns, + columnTypes, + rows, + rowsAffected, + lastInsertRowid + }; + } + + /** + * Create a row object with both array and named property access. + * + * @param values - Array of column values + * @param columns - Array of column names + * @returns Row object with dual access patterns + */ + createRowObject(values: any[], columns: string[]): any { + const row = [...values]; + + // Add column name properties to the array as non-enumerable + // Only add valid identifier names to avoid conflicts + columns.forEach((column, index) => { + if (column && isValidIdentifier(column)) { + Object.defineProperty(row, column, { + value: values[index], + enumerable: false, + writable: false, + configurable: true + }); + } + }); + + return row; + } + + /** + * Execute multiple SQL statements in a batch. + * + * @param statements - Array of SQL statements to execute + * @returns Promise resolving to batch execution results + */ + async batch(statements: string[]): Promise { + const request: CursorRequest = { + baton: this.baton, + batch: { + steps: statements.map(sql => ({ + stmt: { + sql, + args: [], + want_rows: false + } + })) + } + }; + + const { response, entries } = await executeCursor(this.baseUrl, this.config.authToken, request); + + this.baton = response.baton; + if (response.base_url) { + this.baseUrl = response.base_url; + } + + let totalRowsAffected = 0; + let lastInsertRowid: number | undefined; + + for await (const entry of entries) { + switch (entry.type) { + case 'step_end': + if (entry.affected_row_count !== undefined) { + totalRowsAffected += entry.affected_row_count; + } + if (entry.last_insert_rowid) { + lastInsertRowid = parseInt(entry.last_insert_rowid, 10); + } + break; + case 'step_error': + case 'error': + throw new Error(entry.error?.message || 'Batch execution failed'); + } + } + + return { + rowsAffected: totalRowsAffected, + lastInsertRowid + }; + } +} \ No newline at end of file diff --git a/packages/turso-serverless/src/statement.ts b/packages/turso-serverless/src/statement.ts new file mode 100644 index 000000000..b285c7fd3 --- /dev/null +++ b/packages/turso-serverless/src/statement.ts @@ -0,0 +1,107 @@ +import { + decodeValue, + type CursorEntry +} from './protocol.js'; +import { Session, type SessionConfig } from './session.js'; + +/** + * A prepared SQL statement that can be executed in multiple ways. + * + * Each statement has its own session to avoid conflicts during concurrent execution. + * Provides three execution modes: + * - `get(args?)`: Returns the first row or null + * - `all(args?)`: Returns all rows as an array + * - `iterate(args?)`: Returns an async iterator for streaming results + */ +export class Statement { + private session: Session; + private sql: string; + + constructor(sessionConfig: SessionConfig, sql: string) { + this.session = new Session(sessionConfig); + this.sql = sql; + } + + /** + * Execute the statement and return the first row. + * + * @param args - Optional array of parameter values for the SQL statement + * @returns Promise resolving to the first row or null if no results + * + * @example + * ```typescript + * const stmt = client.prepare("SELECT * FROM users WHERE id = ?"); + * const user = await stmt.get([123]); + * if (user) { + * console.log(user.name); + * } + * ``` + */ + async get(args: any[] = []): Promise { + const result = await this.session.execute(this.sql, args); + return result.rows[0] || null; + } + + /** + * Execute the statement and return all rows. + * + * @param args - Optional array of parameter values for the SQL statement + * @returns Promise resolving to an array of all result rows + * + * @example + * ```typescript + * const stmt = client.prepare("SELECT * FROM users WHERE active = ?"); + * const activeUsers = await stmt.all([true]); + * console.log(`Found ${activeUsers.length} active users`); + * ``` + */ + async all(args: any[] = []): Promise { + const result = await this.session.execute(this.sql, args); + return result.rows; + } + + /** + * Execute the statement and return an async iterator for streaming results. + * + * This method provides memory-efficient processing of large result sets + * by streaming rows one at a time instead of loading everything into memory. + * + * @param args - Optional array of parameter values for the SQL statement + * @returns AsyncGenerator that yields individual rows + * + * @example + * ```typescript + * const stmt = client.prepare("SELECT * FROM large_table WHERE category = ?"); + * for await (const row of stmt.iterate(['electronics'])) { + * // Process each row individually + * console.log(row.id, row.name); + * } + * ``` + */ + async *iterate(args: any[] = []): AsyncGenerator { + const { response, entries } = await this.session.executeRaw(this.sql, args); + + let columns: string[] = []; + + for await (const entry of entries) { + switch (entry.type) { + case 'step_begin': + if (entry.cols) { + columns = entry.cols.map(col => col.name); + } + break; + case 'row': + if (entry.row) { + const decodedRow = entry.row.map(decodeValue); + const rowObject = this.session.createRowObject(decodedRow, columns); + yield rowObject; + } + break; + case 'step_error': + case 'error': + throw new Error(entry.error?.message || 'SQL execution failed'); + } + } + } + +} diff --git a/packages/turso-serverless/tsconfig.json b/packages/turso-serverless/tsconfig.json new file mode 100644 index 000000000..85590f642 --- /dev/null +++ b/packages/turso-serverless/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "lib": ["ES2020", "DOM"], + "declaration": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file