diff --git a/.env b/.env new file mode 100644 index 0000000..e69de29 diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..013a35c --- /dev/null +++ b/.envrc @@ -0,0 +1,3 @@ +layout node +use node +[ -f .env ] && dotenv \ No newline at end of file diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..de2c9e7 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,31 @@ +# Ensures packages test correctly +name: Test Packages + +on: + push: + +jobs: + test: + name: Test Packages + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - uses: pnpm/action-setup@v4 + name: Install pnpm + + - name: Setup NodeJS + uses: actions/setup-node@v4 + with: + node-version-file: '.node-version' + + - name: Install dependencies + run: pnpm install + + - name: Build + run: pnpm build + + - name: Test + run: pnpm test diff --git a/.gitignore b/.gitignore index 33f2769..b5f3ab6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ node_modules/ test-db/ *.db +lib/ +tsconfig.tsbuildinfo +benchmarks/db diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..3d3475a --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +v22.5.1 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..271d70d --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +node_modules/ +lib/ +pnpm-lock.yaml diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..1387fd0 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "trailingComma": "none", + "tabWidth": 2, + "semi": true, + "singleQuote": true +} diff --git a/DRIVER-API.md b/DRIVER-API.md new file mode 100644 index 0000000..8e57090 --- /dev/null +++ b/DRIVER-API.md @@ -0,0 +1,139 @@ +## Driver API + +The driver API aims to have a small surface area, with little performance overhead. Ease of use is not important. + +To support all potential implementations, the main APIs are asynchronous. This does add overhead, but this is unavoidable when our goal is to have a universal driver API. We do however aim to keep the performance overhead as low as possible. + +The driver API primarily exposes: + +1. Connection pooling. Even when using a single connection, that connection should be locked for exclusive use by one consumer at a time. +2. Prepared statements. Even if the underlying implementation does not use actual prepared statements, the same APIs can be exposed. + +In general, the setup of prepared statements (preparing a statement, binding parameters) are synchronous APIs, and don't throw on invalid queries. Executing the statement is asynchronous, and this is where errors are thrown. + +The driver API does not include transaction management. This is easily implemented on top of connection pooling/locking + prepared statements for begin/commit/rollback. + +### The API + +This is a simplified version of the API. For full details, see: +[packages/driver/src/driver-api.ts](packages/driver/src/driver-api.ts). + +```ts +export interface SqliteDriverConnectionPool { + /** + * Reserve a connection for exclusive use. + * + * If there is no available connection, this will wait until one is available. + */ + reserveConnection( + options?: ReserveConnectionOptions + ): Promise; + + close(): Promise; + + [Symbol.asyncDispose](): Promise; +} + +export interface ReservedConnection { + /** Direct handle to the underlying connection. */ + connection: SqliteDriverConnection; + + /** Proxied to the underlying connection */ + prepare(sql: string, options?: PrepareOptions): SqliteDriverStatement; + + [Symbol.asyncDispose](): Promise; +} + +export interface SqliteDriverConnection { + /** + * Prepare a statement. + * + * Does not return any errors. + */ + prepare(sql: string, options?: PrepareOptions): SqliteDriverStatement; +} + +/** + * Represents a single prepared statement. + * Loosely modeled on the SQLite API. + */ +export interface SqliteDriverStatement { + bind(parameters: SqliteParameterBinding): void; + + step(n?: number, options?: StepOptions): Promise; + getColumns(): Promise; + finalize(): void; + + reset(options?: ResetOptions): void; + + [Symbol.dispose](): void; +} +``` + +## Design decisions + +### Small surface area + +We want the driver to have as small surface area as possible. In rare cases we do allow exceptions for performance or simplicity reasons. + +### Reusability + +The same driver connection pool should be usable by multiple different consumers within the same process. For example, the same connection pool can be used directly, by an ORM, and/or by a sync library, without running into concurrency issues. This specifically affects connection pooling (see below). + +### Synchronous vs asynchronous + +Many implementations can only support asynchronous methods. However, having _every_ method asynchronous can add significant overhead, if you need to chain multiple methods to run a single query. We therefore aim to have a single asynchronous call per query for most use cases. This does mean that we defer errors until that asynchronous call, and do not throw errors in `prepare()` or `bind()`. + +### Transactions + +Full transaction support requires a large surface area, with many design possibilities. For example, do we support nested transactions (savepoints in SQLite)? Do we expose immediate/defferred/exclusive transactions? Do we use a wrapper function, explicit resource management, or manual commit/rollback calls to manage transactions? + +Instead, the driver API just provides the building blocks for transactions - connection pooling and prepared statements. + +### Connection pooling + +The driver API requires a connection pooling implementation, even if there is only a single underlying connection. Even in that case, it is important that a connection can be "reserved" for a single consumer at a time. This is needed for example to implement transactions, without requiring additional locking mechanisms (which would break the reusability requirement). + +Connection pooling also supports specifically requesting a read-only vs read-write connection. This is important for concurrency in SQLite, which can only support a single writer at a time, but any number of concurrent readers. + +### Read vs write queries + +There is no fundamental distinction between read and write queries in the driver prepared statement API. This is important for use cases such as `INSERT INTO ... RETURNING *` - a "write" api that also returns data. However, read vs write locks are taken into account with connection pooling. + +### "run" with results + +The `run` API that returns the last insert row id and number of changes are primarily for compatibility with current libraries/APIs. Many libraries in use return that automatically for any "run" statement, and splitting that out into a separate prepared statement could add significant performance overhead (requiring two prepared statements for every single "write" query). + +### Row arrays vs objects + +Returning an array of cells for each row, along with a separate "columns" array, is more flexible than just using an object per row. It is always possible to convert the array to an object, given the columns header. + +However, many current SQLite bindings do not expose the raw array calls. Even if they do, this path may be slower than using objects from the start. Since using the results as an array is quite rare in practice, this is left as an optional configuration, rather than a requirement for the all queries. + +### Separate bind/step/reset + +This allows a lot of flexibility, for example partial rebinding of parameters instead of specifying all parameters each time a prepared statement is used. However, those type of use cases are rare, and this is not important in the overall architecture. These could all be combined into a single "query with parameters" call, but would need to take into account optional streaming of results. + +### bigint + +SQLite supports up to 8-byte signed integers (up to 2^64-1), while JavaScript's number is limited to 2^53-1. General approaches include: + +1. Always use JS numbers. This requires using TEXT for larger integers, but can still store as INTEGER and cast when inserting or returning results. +2. Automatically switching to bigint if the number is `>= 2^53`. This can easily introduce issues in the client, since `bigint` an `number` are not interoperable. +3. Require an explicit option to get `bigint` results. This is the approach we went for here. +4. Always use `number` for `REAL`, and `bigint` for `INTEGER`. You can use `cast(n to REAL)` to get a value back as a `number`. Since many users will just use small integers, this may not be ideal. + +### Pipelining + +The APIs guarantee that statements on a connection will be ordered in the order that calls were made. This allows pipelining statements to improve performance - the client can issue many queries before waiting for the results. One place where this breaks down is within transactions: It is possible for one statement to trigger a transaction rollback, in which case the next pipelined statement will run outside the transaction. + +The current API includes a flag to indicate a statement may only be run within a transaction to work around this issue, but other suggestions are welcome. + +## Driver implementation helpers + +The driver package also includes helpers to assist in implementating drivers. These are optional, and not part of the driver spec. It does however make it simple to support: + +1. Connection pooling - the driver itself just needs to implement logic for a single connection, and the utilities will handle connection pooling. +2. Worker threads - this can assist in spawing a separate worker thread per conneciton, to get true concurrency. The same approaches could work to support web workers in browsers in the future. + +Some drivers may use different approaches for concurrency and connection pooling, without using these utilities. diff --git a/README.md b/README.md new file mode 100644 index 0000000..39245ce --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# sqlite-js + +Universal SQLite APIs for JavaScript. + +The project provides two primary APIs: + +1. The driver API. This aims to expose a minimum API for drivers to implement, while supporting a rich set of functionality. This should have as little as possible performance overhead, while still supporting asynchronous implementations. + +2. The end-user API. This is a library built on top of the driver API, that exposes higher-level functionality such as transactions, convenience methods, template strings (later), pipelining. + +## @sqlite-js/driver + +This is a universal driver API and utilities for implementing drivers. + +The APIs here are low-level. These are intended to be implemented by drivers, and used by higher-level libraries. + +See [DRIVER-API.md](./DRIVER-API.md) for details on the design. + +### @sqlite-js/driver/node + +This is a driver implementation for NodeJS based on the experimental `node:sqlite` package. + +## @sqlite-js/better-sqlite3-driver + +This is a driver implementation for NodeJS implementation based `better-sqlite3`. + +## @sqlite-js/api + +This contains a higher-level API, with simple methods to execute queries, and supports transactions and pipelining. + +This is largely a proof-of-concept to validate and test the underlying driver APIs, rather than having a fixed design. + +The current iteration of the APIs is visible at [packages/api/src/api.ts](packages/api/src/api.ts). + +# Why split the APIs? + +A previous iteration used a single API for both the end-user API and the driver API. This had serveral disadvantages: + +1. The implementation per driver requires a lot more effort. +2. Iterating on the API becomes much more difficult. + 1. Implementing minor quality-of-life improvements for the end user becomes a required change in every driver. +3. Optimizing the end-user API for performance is difficult. To cover all the different use cases, it requires implementing many different features such as prepared statements, batching, pipelining. This becomes a very large API for drivers to implement. +4. The goals for the end-user API is different from the driver API: + 1. End-users want a rich but simple-to-use API to access the database. + 2. Drivers want a small surface area, that doesn't change often. + +Splitting out a separate driver API, and implementing the end-user API as a separate library, avoids all the above issues. diff --git a/benchmarks/package.json b/benchmarks/package.json new file mode 100644 index 0000000..474dd11 --- /dev/null +++ b/benchmarks/package.json @@ -0,0 +1,21 @@ +{ + "name": "benchmarks", + "type": "module", + "scripts": { + "build": "tsc -b", + "start": "NODE_OPTIONS=\"--experimental-sqlite --disable-warning=ExperimentalWarning\" node lib/index.js" + }, + "dependencies": { + "better-sqlite3": "^11.0.0", + "prando": "^6.0.1", + "sqlite": "^5.1.1", + "sqlite3": "^5.1.7", + "@sqlite-js/driver": "workspace:^", + "@sqlite-js/better-sqlite3-driver": "workspace:^", + "@sqlite-js/api": "workspace:^" + }, + "devDependencies": { + "@types/node": "^20.14.2", + "typescript": "^5.4.5" + } +} diff --git a/benchmarks/src/Benchmark.ts b/benchmarks/src/Benchmark.ts new file mode 100644 index 0000000..8382708 --- /dev/null +++ b/benchmarks/src/Benchmark.ts @@ -0,0 +1,108 @@ +import { BenchmarkResults } from './BenchmarkResults.js'; + +export abstract class Benchmark { + abstract name: string; + + async runAll(): Promise { + let results = new BenchmarkResults(this.name); + let droppedFrames = 0; + let last = performance.now(); + var timer = setInterval(() => { + const now = performance.now(); + const diff = now - last; + last = now; + if (diff >= 16) { + droppedFrames += Math.floor(diff / 16); + } + }, 1); + + await this.setUp(); + + await results.record('Test 1: 1000 INSERTs', this.test1.bind(this)); + await results.record( + 'Test 2: 25000 INSERTs in a transaction', + this.test2.bind(this) + ); + await results.record( + 'Test 3: 25000 INSERTs into an indexed table', + this.test3.bind(this) + ); + await results.record( + 'Test 4: 100 SELECTs without an index', + this.test4.bind(this) + ); + await results.record( + 'Test 5: 100 SELECTs on a string comparison', + this.test5.bind(this) + ); + await results.record( + 'Test 7: 5000 SELECTs with an index', + this.test7.bind(this) + ); + await results.record( + 'Test 8: 1000 UPDATEs without an index', + this.test8.bind(this) + ); + await results.record( + 'Test 9: 25000 UPDATEs with an index', + this.test9.bind(this) + ); + await results.record( + 'Test 10: 25000 text UPDATEs with an index', + this.test10.bind(this) + ); + await results.record( + 'Test 11: INSERTs from a SELECT', + this.test11.bind(this) + ); + await results.record( + 'Test 12: DELETE without an index', + this.test12.bind(this) + ); + await results.record( + 'Test 13: DELETE with an index', + this.test13.bind(this) + ); + await results.record( + 'Test 14: A big INSERT after a big DELETE', + this.test14.bind(this) + ); + await results.record( + 'Test 15: A big DELETE followed by many small INSERTs', + this.test15.bind(this) + ); + await results.record('Test 16: Clear table', this.test16.bind(this)); + + await this.tearDown(); + + clearInterval(timer); + + const diff = performance.now() - last; + if (diff >= 16) { + droppedFrames += Math.floor(diff / 16); + } + + console.log(`Dropped frames: ${droppedFrames} (diff ${diff})`); + return results; + } + + abstract setUp(): Promise; + + abstract test1(): Promise; + abstract test2(): Promise; + abstract test3(): Promise; + abstract test4(): Promise; + abstract test5(): Promise; + abstract test7(): Promise; + abstract test8(): Promise; + abstract test9(): Promise; + abstract test10(): Promise; + abstract test11(): Promise; + abstract test12(): Promise; + abstract test13(): Promise; + abstract test14(): Promise; + abstract test15(): Promise; + abstract test16(): Promise; + + abstract tearDown(): Promise; +} diff --git a/benchmarks/src/BenchmarkResults.ts b/benchmarks/src/BenchmarkResults.ts new file mode 100644 index 0000000..8a72b1f --- /dev/null +++ b/benchmarks/src/BenchmarkResults.ts @@ -0,0 +1,43 @@ +export class BenchmarkResults { + suite: string; + + results: BenchmarkResult[] = []; + + constructor(suite: string) { + this.suite = suite; + } + + async record(name: string, callback: () => Promise) { + const start = performance.now(); + await callback(); + const end = performance.now(); + const elapsed = end - start; + this.results.push(new BenchmarkResult(name, elapsed)); + console.log(`${name} :: ${Math.round(elapsed)}ms`); + // yield the event loop + await new Promise((resolve) => setTimeout(resolve, 0)); + } + + toString() { + return this.results.map((r) => r.toString()).join('\n'); + } + + toCsv() { + return this.results.map((r) => r.toCsv()).join('\n'); + } +} + +export class BenchmarkResult { + constructor( + public test: string, + public duration: number + ) {} + + toString() { + return `${this.test}: ${this.duration / 1000.0}s`; + } + + toCsv() { + return `${this.test},${this.duration}`; + } +} diff --git a/benchmarks/src/implementations/better-sqlite3.ts b/benchmarks/src/implementations/better-sqlite3.ts new file mode 100644 index 0000000..beb9849 --- /dev/null +++ b/benchmarks/src/implementations/better-sqlite3.ts @@ -0,0 +1,269 @@ +import type * as bsqlite from 'better-sqlite3'; +import DatabaseConstructor from 'better-sqlite3'; +import { promises as fs } from 'fs'; +import assert from 'node:assert'; +import { join } from 'path'; +import Prando from 'prando'; +import { Benchmark } from '../Benchmark.js'; +import { numberName } from '../util.js'; + +export class BetterSqlite3Impl extends Benchmark { + private db!: bsqlite.Database; + private dir: string; + private random = new Prando.default(0); + + constructor( + public name: string, + dir: string + ) { + super(); + this.dir = dir; + } + + async setUp(): Promise { + const dbPath = join(this.dir, this.name + '.db'); + + try { + await fs.unlink(dbPath); + await fs.unlink(dbPath + '-wal'); + } catch (e) { + // Ignore + } + + const db = new DatabaseConstructor(dbPath); + this.db = db; + db.exec('PRAGMA journal_mode = WAL'); + db.exec('PRAGMA synchronous = normal'); + + db.exec( + 'CREATE TABLE t1(id INTEGER PRIMARY KEY, a INTEGER, b INTEGER, c TEXT)' + ); + db.exec( + 'CREATE TABLE t2(id INTEGER PRIMARY KEY, a INTEGER, b INTEGER, c TEXT)' + ); + db.exec( + 'CREATE TABLE t3(id INTEGER PRIMARY KEY, a INTEGER, b INTEGER, c TEXT)' + ); + db.exec('CREATE INDEX i3a ON t3(a)'); + db.exec('CREATE INDEX i3b ON t3(b)'); + } + + async tearDown(): Promise { + this.db.close(); + } + + // Test 1: 1000 INSERTs + async test1(): Promise { + const db = this.db; + const stmt = db.prepare('INSERT INTO t1(a, b, c) VALUES(?, ?, ?)'); + for (let i = 0; i < 1000; i++) { + const n = this.random.nextInt(0, 100000); + stmt.run([i + 1, n, numberName(n)]); + } + db.exec('PRAGMA wal_checkpoint(RESTART)'); + const total = db.prepare('select count() as count from t1').get() as { + count: number; + }; + + assert(total.count == 1000); + } + + // Test 2: 25000 INSERTs in a transaction + async test2(): Promise { + const db = this.db; + const stmt = db.prepare('INSERT INTO t2(a, b, c) VALUES(?, ?, ?)'); + db.transaction(() => { + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + stmt.run([i + 1, n, numberName(n)]); + } + })(); + db.exec('PRAGMA wal_checkpoint(RESTART)'); + const total = db.prepare('select count() as count from t2').get() as { + count: number; + }; + + assert(total.count == 25000); + } + + // Test 3: 25000 INSERTs into an indexed table + async test3(): Promise { + const db = this.db; + const stmt = db.prepare('INSERT INTO t3(a, b, c) VALUES(?, ?, ?)'); + db.transaction(() => { + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + stmt.run([i + 1, n, numberName(n)]); + } + })(); + db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 4: 100 SELECTs without an index + async test4(): Promise { + const db = this.db; + const stmt = db.prepare( + 'SELECT count(*) count, avg(b) avg FROM t2 WHERE b>=? AND b { + for (let i = 0; i < 100; i++) { + const row = stmt.get([i * 100, i * 100 + 1000]) as { + count: number; + avg: number; + }; + + assert(row.count > 200); + assert(row.count < 300); + assert(row.avg > i * 100); + assert(row.avg < i * 100 + 1000); + } + })(); + } + + // Test 5: 100 SELECTs on a string comparison + async test5(): Promise { + const db = this.db; + const stmt = db.prepare( + 'SELECT count(*) count, avg(b) avg FROM t2 WHERE c LIKE ?' + ); + db.transaction(() => { + for (let i = 0; i < 100; i++) { + const row = stmt.get([`%${numberName(i + 1)}%`]) as { + count: number; + avg: number; + }; + assert(row.count > 400); + assert(row.count < 12000); + assert(row.avg > 25000); + } + })(); + } + + // Test 7: 5000 SELECTs with an index + async test7(): Promise { + const db = this.db; + const stmt = db.prepare( + 'SELECT count(*) count, avg(b) avg FROM t3 WHERE b>=? AND b { + for (let i = 0; i < 5000; i++) { + const row = stmt.get([i * 100, i * 100 + 100]) as { + count: number; + avg: number; + }; + if (i < 1000) { + assert(row.count > 8); + assert(row.count < 100); + } else { + assert(row.count === 0); + } + } + })(); + } + + // Test 8: 1000 UPDATEs without an index + async test8(): Promise { + const db = this.db; + const stmt = db.prepare('UPDATE t1 SET b=b*2 WHERE a>=? AND a { + for (let i = 0; i < 1000; i++) { + stmt.run([i * 10, i * 10 + 10]); + } + })(); + db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 9: 25000 UPDATEs with an index + async test9(): Promise { + const db = this.db; + const stmt = db.prepare('UPDATE t3 SET b=? WHERE a=?'); + db.transaction(() => { + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + stmt.run([n, i + 1]); + } + })(); + db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 10: 25000 text UPDATEs with an index + async test10(): Promise { + const db = this.db; + const stmt = db.prepare('UPDATE t3 SET c=? WHERE a=?'); + db.transaction(() => { + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + stmt.run([numberName(n), i + 1]); + } + })(); + db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 11: INSERTs from a SELECT + async test11(): Promise { + const db = this.db; + db.transaction(() => { + db.exec('INSERT INTO t1(a, b, c) SELECT b,a,c FROM t3'); + db.exec('INSERT INTO t3(a, b, c) SELECT b,a,c FROM t1'); + })(); + db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 12: DELETE without an index + async test12(): Promise { + const db = this.db; + db.exec("DELETE FROM t3 WHERE c LIKE '%fifty%'"); + db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 13: DELETE with an index + async test13(): Promise { + const db = this.db; + db.exec('DELETE FROM t3 WHERE a>10 AND a<20000'); + db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 14: A big INSERT after a big DELETE + async test14(): Promise { + const db = this.db; + db.exec('INSERT INTO t3(a, b, c) SELECT a, b, c FROM t1'); + db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 15: A big DELETE followed by many small INSERTs + async test15(): Promise { + const db = this.db; + const stmt = db.prepare('INSERT INTO t1(a, b, c) VALUES(?, ?, ?)'); + db.transaction(() => { + db.exec('DELETE FROM t1'); + for (let i = 0; i < 12000; i++) { + const n = this.random.nextInt(0, 100000); + stmt.run([i + 1, n, numberName(n)]); + } + })(); + db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 16: Clear table + async test16(): Promise { + const db = this.db; + const row1 = db.prepare('SELECT count() count FROM t1').get() as { + count: number; + }; + const row2 = db.prepare('SELECT count() count FROM t2').get() as { + count: number; + }; + const row3 = db.prepare('SELECT count() count FROM t3').get() as { + count: number; + }; + assert(row1.count === 12000); + assert(row2.count === 25000); + assert(row3.count > 34000); + assert(row3.count < 36000); + + db.exec('DELETE FROM t1'); + db.exec('DELETE FROM t2'); + db.exec('DELETE FROM t3'); + db.exec('PRAGMA wal_checkpoint(RESTART)'); + } +} diff --git a/benchmarks/src/implementations/node-sqlite.ts b/benchmarks/src/implementations/node-sqlite.ts new file mode 100644 index 0000000..c71ae87 --- /dev/null +++ b/benchmarks/src/implementations/node-sqlite.ts @@ -0,0 +1,283 @@ +import { promises as fs } from 'fs'; +import assert from 'node:assert'; +import { join } from 'path'; +import Prando from 'prando'; +import { Benchmark } from '../Benchmark.js'; +import { numberName } from '../util.js'; + +//@ts-ignore +import * as sqlite from 'node:sqlite'; + +export class NodeSqliteImpl extends Benchmark { + private db!: sqlite.DatabaseSync; + private dir: string; + private random = new Prando.default(0); + + constructor( + public name: string, + dir: string + ) { + super(); + this.dir = dir; + } + + async setUp(): Promise { + const dbPath = join(this.dir, this.name + '.db'); + + try { + await fs.unlink(dbPath); + await fs.unlink(dbPath + '-wal'); + } catch (e) { + // Ignore + } + + const db = new sqlite.DatabaseSync(dbPath); + this.db = db; + db.exec('PRAGMA journal_mode = wal'); + db.exec('PRAGMA synchronous = normal'); + + db.exec( + 'CREATE TABLE t1(id INTEGER PRIMARY KEY, a INTEGER, b INTEGER, c TEXT)' + ); + db.exec( + 'CREATE TABLE t2(id INTEGER PRIMARY KEY, a INTEGER, b INTEGER, c TEXT)' + ); + db.exec( + 'CREATE TABLE t3(id INTEGER PRIMARY KEY, a INTEGER, b INTEGER, c TEXT)' + ); + db.exec('CREATE INDEX i3a ON t3(a)'); + db.exec('CREATE INDEX i3b ON t3(b)'); + } + + async tearDown(): Promise { + this.db.close(); + } + + // Test 1: 1000 INSERTs + async test1(): Promise { + const db = this.db; + const stmt = db.prepare('INSERT INTO t1(a, b, c) VALUES(?, ?, ?)'); + for (let i = 0; i < 1000; i++) { + const n = this.random.nextInt(0, 100000); + stmt.run(i + 1, n, numberName(n)); + } + db.exec('PRAGMA wal_checkpoint(RESTART)'); + const total = db.prepare('select count() as count from t1').get() as { + count: number; + }; + + assert(total.count == 1000); + } + + // Test 2: 25000 INSERTs in a transaction + async test2(): Promise { + const db = this.db; + const stmt = db.prepare('INSERT INTO t2(a, b, c) VALUES(?, ?, ?)'); + this.transaction(() => { + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + stmt.run(i + 1, n, numberName(n)); + } + }); + db.exec('PRAGMA wal_checkpoint(RESTART)'); + const total = db.prepare('select count() as count from t2').get() as { + count: number; + }; + + assert(total.count == 25000); + } + + // Test 3: 25000 INSERTs into an indexed table + async test3(): Promise { + const db = this.db; + const stmt = db.prepare('INSERT INTO t3(a, b, c) VALUES(?, ?, ?)'); + this.transaction(() => { + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + stmt.run(i + 1, n, numberName(n)); + } + }); + db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 4: 100 SELECTs without an index + async test4(): Promise { + const db = this.db; + const stmt = db.prepare( + 'SELECT count(*) count, avg(b) avg FROM t2 WHERE b>=? AND b { + for (let i = 0; i < 100; i++) { + const row = stmt.get(i * 100, i * 100 + 1000) as { + count: number; + avg: number; + }; + + assert(row.count > 200); + assert(row.count < 300); + assert(row.avg > i * 100); + assert(row.avg < i * 100 + 1000); + } + }); + } + + // Test 5: 100 SELECTs on a string comparison + async test5(): Promise { + const db = this.db; + const stmt = db.prepare( + 'SELECT count(*) count, avg(b) avg FROM t2 WHERE c LIKE ?' + ); + this.transaction(() => { + for (let i = 0; i < 100; i++) { + const row = stmt.get(`%${numberName(i + 1)}%`) as { + count: number; + avg: number; + }; + assert(row.count > 400); + assert(row.count < 12000); + assert(row.avg > 25000); + } + }); + } + + // Test 7: 5000 SELECTs with an index + async test7(): Promise { + const db = this.db; + const stmt = db.prepare( + 'SELECT count(*) count, avg(b) avg FROM t3 WHERE b>=? AND b { + for (let i = 0; i < 5000; i++) { + const row = stmt.get(i * 100, i * 100 + 100) as { + count: number; + avg: number; + }; + if (i < 1000) { + assert(row.count > 8); + assert(row.count < 100); + } else { + assert(row.count === 0); + } + } + }); + } + + // Test 8: 1000 UPDATEs without an index + async test8(): Promise { + const db = this.db; + const stmt = db.prepare('UPDATE t1 SET b=b*2 WHERE a>=? AND a { + for (let i = 0; i < 1000; i++) { + stmt.run(i * 10, i * 10 + 10); + } + }); + db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 9: 25000 UPDATEs with an index + async test9(): Promise { + const db = this.db; + const stmt = db.prepare('UPDATE t3 SET b=? WHERE a=?'); + this.transaction(() => { + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + stmt.run(n, i + 1); + } + }); + db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 10: 25000 text UPDATEs with an index + async test10(): Promise { + const db = this.db; + const stmt = db.prepare('UPDATE t3 SET c=? WHERE a=?'); + this.transaction(() => { + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + stmt.run(numberName(n), i + 1); + } + }); + db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 11: INSERTs from a SELECT + async test11(): Promise { + const db = this.db; + this.transaction(() => { + db.exec('INSERT INTO t1(a, b, c) SELECT b,a,c FROM t3'); + db.exec('INSERT INTO t3(a, b, c) SELECT b,a,c FROM t1'); + }); + db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 12: DELETE without an index + async test12(): Promise { + const db = this.db; + db.exec("DELETE FROM t3 WHERE c LIKE '%fifty%'"); + db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 13: DELETE with an index + async test13(): Promise { + const db = this.db; + db.exec('DELETE FROM t3 WHERE a>10 AND a<20000'); + db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 14: A big INSERT after a big DELETE + async test14(): Promise { + const db = this.db; + db.exec('INSERT INTO t3(a, b, c) SELECT a, b, c FROM t1'); + db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 15: A big DELETE followed by many small INSERTs + async test15(): Promise { + const db = this.db; + const stmt = db.prepare('INSERT INTO t1(a, b, c) VALUES(?, ?, ?)'); + this.transaction(() => { + db.exec('DELETE FROM t1'); + for (let i = 0; i < 12000; i++) { + const n = this.random.nextInt(0, 100000); + stmt.run(i + 1, n, numberName(n)); + } + }); + db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 16: Clear table + async test16(): Promise { + const db = this.db; + const row1 = db.prepare('SELECT count() count FROM t1').get() as { + count: number; + }; + const row2 = db.prepare('SELECT count() count FROM t2').get() as { + count: number; + }; + const row3 = db.prepare('SELECT count() count FROM t3').get() as { + count: number; + }; + assert(row1.count === 12000); + assert(row2.count === 25000); + assert(row3.count > 34000); + assert(row3.count < 36000); + + db.exec('DELETE FROM t1'); + db.exec('DELETE FROM t2'); + db.exec('DELETE FROM t3'); + db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + transaction(callback: () => void) { + this.db.exec('begin'); + try { + callback(); + this.db.exec('commit'); + } catch (e) { + try { + this.db.exec('rollback'); + } catch (e2) {} + throw e; + } + } +} diff --git a/benchmarks/src/implementations/node-sqlite3.ts b/benchmarks/src/implementations/node-sqlite3.ts new file mode 100644 index 0000000..d7fae3d --- /dev/null +++ b/benchmarks/src/implementations/node-sqlite3.ts @@ -0,0 +1,298 @@ +import { Benchmark } from '../Benchmark.js'; +import { join } from 'path'; +import { promises as fs } from 'fs'; +import Prando from 'prando'; +import assert from 'node:assert'; +import { numberName } from '../util.js'; +import sqlite3 from 'sqlite3'; +import { open, Database } from 'sqlite'; + +export class NodeSqlite3Impl extends Benchmark { + private db!: Database; + private dir: string; + private random = new Prando.default(0); + + constructor( + public name: string, + dir: string + ) { + super(); + this.dir = dir; + } + + async setUp(): Promise { + const dbPath = join(this.dir, this.name + '.db'); + + try { + await fs.unlink(dbPath); + await fs.unlink(dbPath + '-wal'); + } catch (e) { + // Ignore + } + + const db = await open({ driver: sqlite3.Database, filename: dbPath }); + this.db = db; + await db.exec('PRAGMA journal_mode = WAL'); + await db.exec('PRAGMA synchronous = normal'); + + await db.exec( + 'CREATE TABLE t1(id INTEGER PRIMARY KEY, a INTEGER, b INTEGER, c TEXT)' + ); + await db.exec( + 'CREATE TABLE t2(id INTEGER PRIMARY KEY, a INTEGER, b INTEGER, c TEXT)' + ); + await db.exec( + 'CREATE TABLE t3(id INTEGER PRIMARY KEY, a INTEGER, b INTEGER, c TEXT)' + ); + await db.exec('CREATE INDEX i3a ON t3(a)'); + await db.exec('CREATE INDEX i3b ON t3(b)'); + } + + async tearDown(): Promise { + await this.db.close(); + } + + // Test 1: 1000 INSERTs + async test1(): Promise { + const db = this.db; + const stmt = await db.prepare('INSERT INTO t1(a, b, c) VALUES(?, ?, ?)'); + try { + for (let i = 0; i < 1000; i++) { + const n = this.random.nextInt(0, 100000); + await stmt.run(i + 1, n, numberName(n)); + } + } finally { + await stmt.finalize(); + } + // await db.run('PRAGMA wal_checkpoint(RESTART)'); + const total = await db.get<{ count: number }>( + 'select count() as count from t1' + ); + assert(total!.count == 1000); + } + + // Test 2: 25000 INSERTs in a transaction + async test2(): Promise { + const db = this.db; + const stmt = await db.prepare('INSERT INTO t2(a, b, c) VALUES(?, ?, ?)'); + try { + await this.transaction(async () => { + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + await stmt.run(i + 1, n, numberName(n)); + } + }); + await db.exec('PRAGMA wal_checkpoint(RESTART)'); + const total = await db.get<{ count: number }>( + 'select count() as count from t2' + ); + assert(total!.count == 25000); + } finally { + await stmt.finalize(); + } + } + + // Test 3: 25000 INSERTs into an indexed table + async test3(): Promise { + const db = this.db; + const stmt = await db.prepare('INSERT INTO t3(a, b, c) VALUES(?, ?, ?)'); + try { + await this.transaction(async () => { + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + await stmt.run([i + 1, n, numberName(n)]); + } + }); + } finally { + await stmt.finalize(); + } + await db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 4: 100 SELECTs without an index + async test4(): Promise { + const db = this.db; + const stmt = await db.prepare( + 'SELECT count(*) count, avg(b) avg FROM t2 WHERE b>=? AND b { + for (let i = 0; i < 100; i++) { + const row = await stmt.get<{ count: number; avg: number }>([ + i * 100, + i * 100 + 1000 + ]); + + assert(row!.count > 200); + assert(row!.count < 300); + assert(row!.avg > i * 100); + assert(row!.avg < i * 100 + 1000); + } + }); + await stmt.finalize(); + } + + // Test 5: 100 SELECTs on a string comparison + async test5(): Promise { + const db = this.db; + const stmt = await db.prepare( + 'SELECT count(*) count, avg(b) avg FROM t2 WHERE c LIKE ?' + ); + await this.transaction(async () => { + for (let i = 0; i < 100; i++) { + const row = await stmt.get<{ count: number; avg: number }>([ + `%${numberName(i + 1)}%` + ]); + assert(row!.count > 400); + assert(row!.count < 12000); + assert(row!.avg > 25000); + } + }); + await stmt.finalize(); + } + + // Test 7: 5000 SELECTs with an index + async test7(): Promise { + const db = this.db; + const stmt = await db.prepare( + 'SELECT count(*) count, avg(b) avg FROM t3 WHERE b>=? AND b { + for (let i = 0; i < 5000; i++) { + const row = await stmt.get<{ count: number; avg: number }>([ + i * 100, + i * 100 + 100 + ]); + if (i < 1000) { + assert(row!.count > 8); + assert(row!.count < 100); + } else { + assert(row!.count === 0); + } + } + }); + await stmt.finalize(); + } + + // Test 8: 1000 UPDATEs without an index + async test8(): Promise { + const db = this.db; + const stmt = await db.prepare('UPDATE t1 SET b=b*2 WHERE a>=? AND a { + for (let i = 0; i < 1000; i++) { + await stmt.run([i * 10, i * 10 + 10]); + } + }); + await stmt.finalize(); + await db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 9: 25000 UPDATEs with an index + async test9(): Promise { + const db = this.db; + const stmt = await db.prepare('UPDATE t3 SET b=? WHERE a=?'); + await this.transaction(async () => { + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + await stmt.run([n, i + 1]); + } + }); + await stmt.finalize(); + await db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 10: 25000 text UPDATEs with an index + async test10(): Promise { + const db = this.db; + const stmt = await db.prepare('UPDATE t3 SET c=? WHERE a=?'); + await this.transaction(async () => { + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + await stmt.run([numberName(n), i + 1]); + } + }); + await stmt.finalize(); + await db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 11: INSERTs from a SELECT + async test11(): Promise { + const db = this.db; + await this.transaction(async () => { + await db.exec('INSERT INTO t1(a, b, c) SELECT b,a,c FROM t3'); + await db.exec('INSERT INTO t3(a, b, c) SELECT b,a,c FROM t1'); + }); + await db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 12: DELETE without an index + async test12(): Promise { + const db = this.db; + await db.exec("DELETE FROM t3 WHERE c LIKE '%fifty%'"); + await db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 13: DELETE with an index + async test13(): Promise { + const db = this.db; + await db.exec('DELETE FROM t3 WHERE a>10 AND a<20000'); + await db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 14: A big INSERT after a big DELETE + async test14(): Promise { + const db = this.db; + await db.exec('INSERT INTO t3(a, b, c) SELECT a, b, c FROM t1'); + await db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 15: A big DELETE followed by many small INSERTs + async test15(): Promise { + const db = this.db; + const stmt = await db.prepare('INSERT INTO t1(a, b, c) VALUES(?, ?, ?)'); + await this.transaction(async () => { + await db.run('DELETE FROM t1'); + for (let i = 0; i < 12000; i++) { + const n = this.random.nextInt(0, 100000); + await stmt.run([i + 1, n, numberName(n)]); + } + }); + await stmt.finalize(); + await db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 16: Clear table + async test16(): Promise { + const db = this.db; + const row1 = await db.get<{ count: number }>( + 'SELECT count() count FROM t1' + ); + const row2 = await db.get<{ count: number }>( + 'SELECT count() count FROM t2' + ); + const row3 = await db.get<{ count: number }>( + 'SELECT count() count FROM t3' + ); + assert(row1!.count === 12000); + assert(row2!.count === 25000); + assert(row3!.count > 34000); + assert(row3!.count < 36000); + + await db.exec('DELETE FROM t1'); + await db.exec('DELETE FROM t2'); + await db.exec('DELETE FROM t3'); + await db.exec('PRAGMA wal_checkpoint(RESTART)'); + } + + async transaction(callback: () => Promise) { + await this.db.exec('begin'); + try { + await callback(); + await this.db.exec('commit'); + } catch (e) { + try { + await this.db.exec('rollback'); + } catch (e2) {} + throw e; + } + } +} diff --git a/benchmarks/src/implementations/sjp-json.ts b/benchmarks/src/implementations/sjp-json.ts new file mode 100644 index 0000000..6ba7200 --- /dev/null +++ b/benchmarks/src/implementations/sjp-json.ts @@ -0,0 +1,317 @@ +import { promises as fs } from 'fs'; +import assert from 'node:assert'; +import { join } from 'path'; +import Prando from 'prando'; +import { SqliteConnectionPool } from '@sqlite-js/api'; +import { Benchmark } from '../Benchmark.js'; +import { numberName } from '../util.js'; + +export class JSPJsonImpl extends Benchmark { + private db!: SqliteConnectionPool; + private dir: string; + private random = new Prando.default(0); + + constructor( + public name: string, + dir: string, + private driver: (path: string) => SqliteConnectionPool + ) { + super(); + this.dir = dir; + } + + async setUp(): Promise { + const dbPath = join(this.dir, this.name + '.db'); + + try { + await fs.unlink(dbPath); + await fs.unlink(dbPath + '-wal'); + } catch (e) { + // Ignore + } + + const db = this.driver(dbPath); + this.db = db; + + { + await using c = await db.reserveConnection(); + + await c.run( + 'CREATE TABLE t1(id INTEGER PRIMARY KEY, a INTEGER, b INTEGER, c TEXT)' + ); + await c.run( + 'CREATE TABLE t2(id INTEGER PRIMARY KEY, a INTEGER, b INTEGER, c TEXT)' + ); + await c.run( + 'CREATE TABLE t3(id INTEGER PRIMARY KEY, a INTEGER, b INTEGER, c TEXT)' + ); + await c.run('CREATE INDEX i3a ON t3(a)'); + await c.run('CREATE INDEX i3b ON t3(b)'); + } + + // Pre-create a bunch of read connections + let promises = []; + for (let i = 0; i < 10; i++) { + promises.push( + (async () => { + await using c = await db.reserveConnection({ readonly: true }); + await new Promise((resolve) => setTimeout(resolve, 1)); + })() + ); + } + await Promise.all(promises); + } + + async tearDown(): Promise { + await this.db.close(); + } + + // Test 1: 1000 INSERTs + async test1(): Promise { + await using db = await this.db.reserveConnection(); + using s = db.prepare('INSERT INTO t1(a, b, c) VALUES(?, ?, ?)'); + + for (let i = 0; i < 1000; i++) { + const n = this.random.nextInt(0, 100000); + await s.run([i + 1, n, numberName(n)]); + } + await db.run('PRAGMA wal_checkpoint(RESTART)'); + const total = await db.get<{ count: number }>( + 'select count() as count from t1' + ); + + assert(total.count == 1000); + } + + // Test 2: 25000 INSERTs in a transaction + async test2(): Promise { + await using tx = await this.db.begin(); + using s = tx.prepare( + 'INSERT INTO t2(a, b, c) SELECT e.value ->> 0, e.value ->> 1, e.value ->> 2 from json_each(?) e' + ); + let buffer: any[][] = []; + + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + buffer.push([i + 1, n, numberName(n)]); + + if (buffer.length >= 100) { + await s.run([JSON.stringify(buffer)]); + buffer = []; + } + } + await s.run([JSON.stringify(buffer)]); + await tx.commit(); + await this.db.run('PRAGMA wal_checkpoint(RESTART)'); + const total = await this.db.get<{ count: number }>( + 'select count() as count from t2' + ); + + assert(total.count == 25000); + } + + // Test 3: 25000 INSERTs into an indexed table + async test3(): Promise { + await using tx = await this.db.begin(); + using s = tx.prepare( + 'INSERT INTO t3(a, b, c) SELECT e.value ->> 0, e.value ->> 1, e.value ->> 2 from json_each(?) e' + ); + let buffer: any[][] = []; + + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + buffer.push([i + 1, n, numberName(n)]); + if (buffer.length >= 100) { + await s.run([JSON.stringify(buffer)]); + buffer = []; + } + } + await s.run([JSON.stringify(buffer)]); + await tx.commit(); + await this.db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 4: 100 SELECTs without an index + async test4(): Promise { + await using tx = await this.db.begin({ readonly: true }); + using s = tx.prepare<{ count: number; avg: number }>( + 'SELECT count(*) count, avg(b) avg FROM t2 WHERE b>=? AND b 200); + assert(row.count < 300); + assert(row.avg > i * 100); + assert(row.avg < i * 100 + 1000); + } + } + + // Test 5: 100 SELECTs on a string comparison + async test5(): Promise { + await using tx = await this.db.begin({ readonly: true }); + using s = tx.prepare<{ count: number; avg: number }>( + 'SELECT count(*) count, avg(b) avg FROM t2 WHERE c LIKE ?' + ); + for (let i = 0; i < 100; i++) { + const row = (await s.select([`%${numberName(i + 1)}%`]))[0]; + assert(row.count > 400); + assert(row.count < 12000); + assert(row.avg > 25000); + } + } + + // Test 7: 5000 SELECTs with an index + async test7(): Promise { + let promises: Promise[] = []; + const batchSize = 500; + const batches = Math.ceil(5000 / batchSize); + for (let batch = 0; batch < batches; batch++) { + const promise = this.db.transaction( + async (tx) => { + using s = tx.prepare<{ count: number; avg: number }>( + 'SELECT count(*) count, avg(b) avg FROM t3 WHERE b>=? AND b 8); + assert(row.count < 100); + } else { + assert(row.count === 0); + } + } + }, + { readonly: true } + ); + promises.push(promise); + } + await Promise.all(promises); + } + + // Test 8: 1000 UPDATEs without an index + async test8(): Promise { + await using tx = await this.db.begin(); + using s = tx.prepare('UPDATE t1 SET b=b*2 WHERE a>=? AND a { + await using tx = await this.db.begin(); + using s = tx.prepare( + 'UPDATE t3 SET b = e.value ->> 1 FROM json_each(?) e WHERE a = e.value ->> 0' + ); + let batch: any[][] = []; + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + batch.push([i + 1, n]); + if (batch.length >= 100) { + await s.run([JSON.stringify(batch)]); + batch = []; + } + } + await s.run([JSON.stringify(batch)]); + await tx.commit(); + await this.db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 10: 25000 text UPDATEs with an index + async test10(): Promise { + await using tx = await this.db.begin(); + using s = tx.prepare( + 'UPDATE t3 SET c = e.value ->> 1 FROM json_each(?) e WHERE a = e.value ->> 0' + ); + + let batch: any[][] = []; + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + batch.push([i + 1, numberName(n)]); + if (batch.length >= 100) { + await s.run([JSON.stringify(batch)]); + batch = []; + } + } + await s.run([JSON.stringify(batch)]); + await tx.commit(); + await this.db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 11: INSERTs from a SELECT + async test11(): Promise { + await using tx = await this.db.begin(); + await tx.run('INSERT INTO t1(a, b, c) SELECT b,a,c FROM t3'); + await tx.run('INSERT INTO t3(a, b, c) SELECT b,a,c FROM t1'); + await tx.commit(); + await this.db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 12: DELETE without an index + async test12(): Promise { + await this.db.run("DELETE FROM t3 WHERE c LIKE '%fifty%'"); + await this.db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 13: DELETE with an index + async test13(): Promise { + await this.db.run('DELETE FROM t3 WHERE a>10 AND a<20000'); + await this.db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 14: A big INSERT after a big DELETE + async test14(): Promise { + await this.db.run('INSERT INTO t3(a, b, c) SELECT a, b, c FROM t1'); + await this.db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 15: A big DELETE followed by many small INSERTs + async test15(): Promise { + await using tx = await this.db.begin(); + using s = tx.prepare( + 'INSERT INTO t1(a, b, c) SELECT value ->> 0, value ->> 1, value ->> 2 FROM json_each(?)' + ); + await tx.run('DELETE FROM t1'); + let batch: any[][] = []; + for (let i = 0; i < 12000; i++) { + const n = this.random.nextInt(0, 100000); + batch.push([i + 1, n, numberName(n)]); + if (batch.length >= 100) { + await s.run([JSON.stringify(batch)]); + batch = []; + } + } + await s.run([JSON.stringify(batch)]); + await tx.commit(); + await this.db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 16: Clear table + async test16(): Promise { + await using db = await this.db.reserveConnection(); + const row1 = await db.get<{ count: number }>( + 'SELECT count() count FROM t1' + ); + const row2 = await db.get<{ count: number }>( + 'SELECT count() count FROM t2' + ); + const row3 = await db.get<{ count: number }>( + 'SELECT count() count FROM t3' + ); + assert(row1.count === 12000); + assert(row2.count === 25000); + assert(row3.count > 34000); + assert(row3.count < 36000); + + await db.run('DELETE FROM t1'); + await db.run('DELETE FROM t2'); + await db.run('DELETE FROM t3'); + await db.run('PRAGMA wal_checkpoint(RESTART)'); + } +} diff --git a/benchmarks/src/implementations/sjp-optimized.ts b/benchmarks/src/implementations/sjp-optimized.ts new file mode 100644 index 0000000..e752e97 --- /dev/null +++ b/benchmarks/src/implementations/sjp-optimized.ts @@ -0,0 +1,291 @@ +import { promises as fs } from 'fs'; +import assert from 'node:assert'; +import { join } from 'path'; +import Prando from 'prando'; +import { SqliteConnectionPool } from '@sqlite-js/api'; +import { Benchmark } from '../Benchmark.js'; +import { numberName } from '../util.js'; + +export class JSPOptimizedImpl extends Benchmark { + private db!: SqliteConnectionPool; + private dir: string; + private random = new Prando.default(0); + + constructor( + public name: string, + dir: string, + private driver: (path: string) => SqliteConnectionPool + ) { + super(); + this.dir = dir; + } + + async setUp(): Promise { + const dbPath = join(this.dir, this.name + '.db'); + + try { + await fs.unlink(dbPath); + await fs.unlink(dbPath + '-wal'); + } catch (e) { + // Ignore + } + + const db = this.driver(dbPath); + this.db = db; + + { + await using c = await db.reserveConnection(); + + await c.run( + 'CREATE TABLE t1(id INTEGER PRIMARY KEY, a INTEGER, b INTEGER, c TEXT)' + ); + await c.run( + 'CREATE TABLE t2(id INTEGER PRIMARY KEY, a INTEGER, b INTEGER, c TEXT)' + ); + await c.run( + 'CREATE TABLE t3(id INTEGER PRIMARY KEY, a INTEGER, b INTEGER, c TEXT)' + ); + await c.run('CREATE INDEX i3a ON t3(a)'); + await c.run('CREATE INDEX i3b ON t3(b)'); + } + + // Pre-create a bunch of read connections + let promises = []; + for (let i = 0; i < 10; i++) { + promises.push( + (async () => { + await using c = await db.reserveConnection({ readonly: true }); + await new Promise((resolve) => setTimeout(resolve, 1)); + })() + ); + } + await Promise.all(promises); + } + + async tearDown(): Promise { + await this.db.close(); + } + + // Test 1: 1000 INSERTs + async test1(): Promise { + await using db = await this.db.reserveConnection(); + using s = db.prepare('INSERT INTO t1(a, b, c) VALUES(?, ?, ?)'); + + for (let i = 0; i < 1000; i++) { + const n = this.random.nextInt(0, 100000); + await s.run([i + 1, n, numberName(n)]); + } + await db.run('PRAGMA wal_checkpoint(RESTART)'); + const total = await db.get<{ count: number }>( + 'select count() as count from t1' + ); + assert(total.count == 1000); + } + + // Test 2: 25000 INSERTs in a transaction + async test2(): Promise { + await using tx = await this.db.begin(); + using s = tx.prepare('INSERT INTO t2(a, b, c) VALUES(?, ?, ?)'); + const pipeline = tx.pipeline(); + + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + pipeline.run(s, [i + 1, n, numberName(n)]); + if (pipeline.count > 100) { + await pipeline.flush(); + } + } + await pipeline.flush(); + await tx.commit(); + await this.db.run('PRAGMA wal_checkpoint(RESTART)'); + const total = await this.db.get<{ count: number }>( + 'select count() as count from t2' + ); + assert(total.count == 25000); + } + + // Test 3: 25000 INSERTs into an indexed table + async test3(): Promise { + await using tx = await this.db.begin(); + using s = tx.prepare('INSERT INTO t3(a, b, c) VALUES(?, ?, ?)'); + const pipeline = tx.pipeline(); + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + pipeline.run(s, [i + 1, n, numberName(n)]); + if (pipeline.count > 100) { + await pipeline.flush(); + } + } + await pipeline.flush(); + await tx.commit(); + await this.db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 4: 100 SELECTs without an index + async test4(): Promise { + await using tx = await this.db.begin({ readonly: true }); + using s = tx.prepare<{ count: number; avg: number }>( + 'SELECT count(*) count, avg(b) avg FROM t2 WHERE b>=? AND b 200); + assert(row.count < 300); + assert(row.avg > i * 100); + assert(row.avg < i * 100 + 1000); + } + } + + // Test 5: 100 SELECTs on a string comparison + async test5(): Promise { + await using tx = await this.db.begin({ readonly: true }); + using s = tx.prepare<{ count: number; avg: number }>( + 'SELECT count(*) count, avg(b) avg FROM t2 WHERE c LIKE ?' + ); + for (let i = 0; i < 100; i++) { + const row = (await s.select([`%${numberName(i + 1)}%`]))[0]; + assert(row.count > 400); + assert(row.count < 12000); + assert(row.avg > 25000); + } + } + + // Test 7: 5000 SELECTs with an index + async test7(): Promise { + let promises: Promise[] = []; + for (let batch = 0; batch < 5; batch++) { + const promise = this.db.transaction( + async (tx) => { + using s = tx.prepare<{ count: number; avg: number }>( + 'SELECT count(*) count, avg(b) avg FROM t3 WHERE b>=? AND b 8); + assert(row.count < 100); + } else { + assert(row.count === 0); + } + } + }, + { readonly: true } + ); + promises.push(promise); + } + await Promise.all(promises); + } + + // Test 8: 1000 UPDATEs without an index + async test8(): Promise { + await using tx = await this.db.begin(); + using s = tx.prepare('UPDATE t1 SET b=b*2 WHERE a>=? AND a { + await using tx = await this.db.begin(); + using s = tx.prepare('UPDATE t3 SET b=? WHERE a=?'); + const pipeline = tx.pipeline(); + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + pipeline.run(s, [n, i + 1]); + if (pipeline.count > 100) { + await pipeline.flush(); + } + } + await pipeline.flush(); + await tx.commit(); + await this.db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 10: 25000 text UPDATEs with an index + async test10(): Promise { + await using tx = await this.db.begin(); + using s = tx.prepare('UPDATE t3 SET c=? WHERE a=?'); + const pipeline = tx.pipeline(); + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + pipeline.run(s, [numberName(n), i + 1]); + if (pipeline.count > 100) { + await pipeline.flush(); + } + } + await pipeline.flush(); + await tx.commit(); + await this.db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 11: INSERTs from a SELECT + async test11(): Promise { + await using tx = await this.db.begin(); + await tx.run('INSERT INTO t1(a, b, c) SELECT b,a,c FROM t3'); + await tx.run('INSERT INTO t3(a, b, c) SELECT b,a,c FROM t1'); + await tx.commit(); + await this.db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 12: DELETE without an index + async test12(): Promise { + await using db = await this.db.reserveConnection(); + await db.run("DELETE FROM t3 WHERE c LIKE '%fifty%'"); + await db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 13: DELETE with an index + async test13(): Promise { + await using db = await this.db.reserveConnection(); + await db.run('DELETE FROM t3 WHERE a>10 AND a<20000'); + await db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 14: A big INSERT after a big DELETE + async test14(): Promise { + await using db = await this.db.reserveConnection(); + await db.run('INSERT INTO t3(a, b, c) SELECT a, b, c FROM t1'); + await db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 15: A big DELETE followed by many small INSERTs + async test15(): Promise { + await using tx = await this.db.begin(); + using s = tx.prepare('INSERT INTO t1(a, b, c) VALUES(?, ?, ?)'); + const pipeline = tx.pipeline(); + await tx.run('DELETE FROM t1'); + for (let i = 0; i < 12000; i++) { + const n = this.random.nextInt(0, 100000); + pipeline.run(s, [i + 1, n, numberName(n)]); + } + await pipeline.flush(); + await tx.commit(); + await this.db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 16: Clear table + async test16(): Promise { + await using db = await this.db.reserveConnection(); + const row1 = ( + await db.select<{ count: number }>('SELECT count() count FROM t1') + )[0]; + const row2 = ( + await db.select<{ count: number }>('SELECT count() count FROM t2') + )[0]; + const row3 = ( + await db.select<{ count: number }>('SELECT count() count FROM t3') + )[0]; + assert(row1.count === 12000); + assert(row2.count === 25000); + assert(row3.count > 34000); + assert(row3.count < 36000); + + await db.run('DELETE FROM t1'); + await db.run('DELETE FROM t2'); + await db.run('DELETE FROM t3'); + await db.run('PRAGMA wal_checkpoint(RESTART)'); + } +} diff --git a/benchmarks/src/implementations/sjp.ts b/benchmarks/src/implementations/sjp.ts new file mode 100644 index 0000000..e12e2aa --- /dev/null +++ b/benchmarks/src/implementations/sjp.ts @@ -0,0 +1,261 @@ +import { promises as fs } from 'fs'; +import assert from 'node:assert'; +import { join } from 'path'; +import Prando from 'prando'; +import { SqliteConnectionPool } from '@sqlite-js/api'; +import { Benchmark } from '../Benchmark.js'; +import { numberName } from '../util.js'; + +export class JSPImpl extends Benchmark { + private db!: SqliteConnectionPool; + private dir: string; + private random = new Prando.default(0); + + constructor( + public name: string, + dir: string, + private driver: (path: string) => SqliteConnectionPool + ) { + super(); + this.dir = dir; + } + + async setUp(): Promise { + const dbPath = join(this.dir, this.name + '.db'); + + try { + await fs.unlink(dbPath); + await fs.unlink(dbPath + '-wal'); + } catch (e) { + // Ignore + } + + const db = this.driver(dbPath); + this.db = db; + + await using c = await db.reserveConnection(); + + await c.run( + 'CREATE TABLE t1(id INTEGER PRIMARY KEY, a INTEGER, b INTEGER, c TEXT)' + ); + await c.run( + 'CREATE TABLE t2(id INTEGER PRIMARY KEY, a INTEGER, b INTEGER, c TEXT)' + ); + await c.run( + 'CREATE TABLE t3(id INTEGER PRIMARY KEY, a INTEGER, b INTEGER, c TEXT)' + ); + await c.run('CREATE INDEX i3a ON t3(a)'); + await c.run('CREATE INDEX i3b ON t3(b)'); + } + + async tearDown(): Promise { + await this.db.close(); + } + + // Test 1: 1000 INSERTs + async test1(): Promise { + await using db = await this.db.reserveConnection(); + for (let i = 0; i < 1000; i++) { + const n = this.random.nextInt(0, 100000); + await db.run('INSERT INTO t1(a, b, c) VALUES(?, ?, ?)', [ + i + 1, + n, + numberName(n) + ]); + } + await db.run('PRAGMA wal_checkpoint(RESTART)'); + const total = await db.get<{ count: number }>( + 'select count() as count from t1' + ); + assert(total.count == 1000); + } + + // Test 2: 25000 INSERTs in a transaction + async test2(): Promise { + await using db = await this.db.reserveConnection(); + await db.transaction(async (tx) => { + using s = tx.prepare('INSERT INTO t2(a, b, c) VALUES(?, ?, ?)'); + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + await s.run([i + 1, n, numberName(n)]); + } + }); + await db.run('PRAGMA wal_checkpoint(RESTART)'); + const total = await db.get<{ count: number }>( + 'select count() as count from t2' + ); + assert(total.count == 25000); + } + + // Test 3: 25000 INSERTs into an indexed table + async test3(): Promise { + await using tx = await this.db.begin(); + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + await tx.run('INSERT INTO t3(a, b, c) VALUES(?, ?, ?)', [ + i + 1, + n, + numberName(n) + ]); + } + await tx.commit(); + await this.db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 4: 100 SELECTs without an index + async test4(): Promise { + await using tx = await this.db.begin({ readonly: true }); + for (let i = 0; i < 100; i++) { + const row = ( + await tx.select<{ count: number; avg: number }>( + 'SELECT count(*) count, avg(b) avg FROM t2 WHERE b>=? AND b 200); + assert(row.count < 300); + assert(row.avg > i * 100); + assert(row.avg < i * 100 + 1000); + } + } + + // Test 5: 100 SELECTs on a string comparison + async test5(): Promise { + await using tx = await this.db.begin({ readonly: true }); + for (let i = 0; i < 100; i++) { + const row = ( + await tx.select<{ count: number; avg: number }>( + 'SELECT count(*) count, avg(b) avg FROM t2 WHERE c LIKE ?', + [`%${numberName(i + 1)}%`] + ) + )[0]; + assert(row.count > 400); + assert(row.count < 12000); + assert(row.avg > 25000); + } + } + + // Test 7: 5000 SELECTs with an index + async test7(): Promise { + await this.db.transaction( + async (tx) => { + for (let i = 0; i < 5000; i++) { + const row = ( + await tx.select<{ count: number; avg: number }>( + 'SELECT count(*) count, avg(b) avg FROM t3 WHERE b>=? AND b 8); + assert(row.count < 100); + } else { + assert(row.count === 0); + } + } + }, + { readonly: true } + ); + } + + // Test 8: 1000 UPDATEs without an index + async test8(): Promise { + await using tx = await this.db.begin(); + for (let i = 0; i < 1000; i++) { + await tx.run('UPDATE t1 SET b=b*2 WHERE a>=? AND a { + await using tx = await this.db.begin(); + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + await tx.run('UPDATE t3 SET b=? WHERE a=?', [n, i + 1]); + } + await tx.commit(); + await this.db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 10: 25000 text UPDATEs with an index + async test10(): Promise { + await using tx = await this.db.begin(); + for (let i = 0; i < 25000; i++) { + const n = this.random.nextInt(0, 100000); + await tx.run('UPDATE t3 SET c=? WHERE a=?', [numberName(n), i + 1]); + } + await tx.commit(); + await this.db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 11: INSERTs from a SELECT + async test11(): Promise { + await using tx = await this.db.begin(); + await tx.run('INSERT INTO t1(a, b, c) SELECT b,a,c FROM t3'); + await tx.run('INSERT INTO t3(a, b, c) SELECT b,a,c FROM t1'); + await tx.commit(); + await this.db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 12: DELETE without an index + async test12(): Promise { + await this.db.run("DELETE FROM t3 WHERE c LIKE '%fifty%'"); + await this.db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 13: DELETE with an index + async test13(): Promise { + await this.db.run('DELETE FROM t3 WHERE a>10 AND a<20000'); + await this.db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 14: A big INSERT after a big DELETE + async test14(): Promise { + await this.db.run('INSERT INTO t3(a, b, c) SELECT a, b, c FROM t1'); + await this.db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 15: A big DELETE followed by many small INSERTs + async test15(): Promise { + await using tx = await this.db.begin(); + await tx.run('DELETE FROM t1'); + for (let i = 0; i < 12000; i++) { + const n = this.random.nextInt(0, 100000); + await tx.run('INSERT INTO t1(a, b, c) VALUES(?, ?, ?)', [ + i + 1, + n, + numberName(n) + ]); + } + await tx.commit(); + await this.db.run('PRAGMA wal_checkpoint(RESTART)'); + } + + // Test 16: Clear table + async test16(): Promise { + await using db = await this.db.reserveConnection(); + const row1 = await db.get<{ count: number }>( + 'SELECT count() count FROM t1' + ); + const row2 = await db.get<{ count: number }>( + 'SELECT count() count FROM t2' + ); + const row3 = await db.get<{ count: number }>( + 'SELECT count() count FROM t3' + ); + assert(row1.count === 12000); + assert(row2.count === 25000); + assert(row3.count > 34000); + assert(row3.count < 36000); + + await db.run('DELETE FROM t1'); + await db.run('DELETE FROM t2'); + await db.run('DELETE FROM t3'); + await db.run('PRAGMA wal_checkpoint(RESTART)'); + } +} diff --git a/benchmarks/src/index.ts b/benchmarks/src/index.ts new file mode 100644 index 0000000..b73a9b5 --- /dev/null +++ b/benchmarks/src/index.ts @@ -0,0 +1,113 @@ +import { NodeSqliteDriver } from '@sqlite-js/driver/node'; +import { ConnectionPoolImpl } from '@sqlite-js/api'; +import { BetterSqliteDriver } from '@sqlite-js/better-sqlite3-driver'; +import { Benchmark } from './Benchmark.js'; +import { BenchmarkResults } from './BenchmarkResults.js'; +import { BetterSqlite3Impl } from './implementations/better-sqlite3.js'; +import { NodeSqliteImpl } from './implementations/node-sqlite.js'; +import { NodeSqlite3Impl } from './implementations/node-sqlite3.js'; +import { JSPJsonImpl } from './implementations/sjp-json.js'; +import { JSPOptimizedImpl } from './implementations/sjp-optimized.js'; +import { JSPImpl } from './implementations/sjp.js'; + +async function main() { + const dir = 'db'; + + const results: BenchmarkResults[] = [ + await test( + new JSPImpl('sjp-sync', dir, (path) => { + const db = new ConnectionPoolImpl( + BetterSqliteDriver.openInProcess(path) + ); + return db; + }) + ), + await test( + new JSPImpl('sjp-async', dir, (path) => { + const db = new ConnectionPoolImpl(BetterSqliteDriver.open(path)); + return db; + }) + ), + await test( + new JSPOptimizedImpl('sjp-sync-optimized', dir, (path) => { + const db = new ConnectionPoolImpl( + BetterSqliteDriver.openInProcess(path) + ); + return db; + }) + ), + await test( + new JSPOptimizedImpl('sjp-async-optimized', dir, (path) => { + const db = new ConnectionPoolImpl(BetterSqliteDriver.open(path)); + return db; + }) + ), + await test( + new JSPJsonImpl('sjp-sync-json', dir, (path) => { + const db = new ConnectionPoolImpl( + BetterSqliteDriver.openInProcess(path) + ); + return db; + }) + ), + await test( + new JSPJsonImpl('sjp-async-json', dir, (path) => { + const db = new ConnectionPoolImpl(BetterSqliteDriver.open(path)); + return db; + }) + ), + await test( + new JSPImpl('node-sjp-sync', dir, (path) => { + const db = new ConnectionPoolImpl(NodeSqliteDriver.openInProcess(path)); + return db; + }) + ), + await test( + new JSPImpl('node-sjp-async', dir, (path) => { + const db = new ConnectionPoolImpl(NodeSqliteDriver.open(path)); + return db; + }) + ), + await test( + new JSPOptimizedImpl('node-sjp-sync-optimized', dir, (path) => { + const db = new ConnectionPoolImpl(NodeSqliteDriver.openInProcess(path)); + return db; + }) + ), + await test( + new JSPOptimizedImpl('node-sjp-async-optimized', dir, (path) => { + const db = new ConnectionPoolImpl(NodeSqliteDriver.open(path)); + return db; + }) + ), + await test(new BetterSqlite3Impl('better-sqlite3', dir)), + await test(new NodeSqlite3Impl('node-sqlite3', dir)), + await test(new NodeSqliteImpl('node:sqlite', dir)) + ]; + + const first = results[0]; + let s = 'Test'; + for (const rr of results) { + s += `,${rr.suite}`; + } + console.log(''); + console.log(s); + for (let i = 0; i < first.results.length; i++) { + const test = first.results[i].test; + let s = `${test}`; + for (const rr of results) { + const r3 = rr.results[i].duration; + s += `,${r3}`; + } + console.log(s); + } + console.log(''); +} + +async function test(bm: Benchmark): Promise { + console.log(bm.name); + const results = await bm.runAll(); + return results; +} + +await main(); diff --git a/benchmarks/src/util.ts b/benchmarks/src/util.ts new file mode 100644 index 0000000..5456c77 --- /dev/null +++ b/benchmarks/src/util.ts @@ -0,0 +1,63 @@ +const digits = [ + '', + 'one', + 'two', + 'three', + 'four', + 'five', + 'six', + 'seven', + 'eight', + 'nine' +]; +const names100 = [ + ...digits, + ...[ + 'ten', + 'eleven', + 'twelve', + 'thirteen', + 'fourteen', + 'fifteen', + 'sixteen', + 'seventeen', + 'eighteen', + 'nineteen' + ], + ...digits.map((digit) => `twenty${digit != '' ? '-' + digit : ''}`), + ...digits.map((digit) => `thirty${digit != '' ? '-' + digit : ''}`), + ...digits.map((digit) => `forty${digit != '' ? '-' + digit : ''}`), + ...digits.map((digit) => `fifty${digit != '' ? '-' + digit : ''}`), + ...digits.map((digit) => `sixty${digit != '' ? '-' + digit : ''}`), + ...digits.map((digit) => `seventy${digit != '' ? '-' + digit : ''}`), + ...digits.map((digit) => `eighty${digit != '' ? '-' + digit : ''}`), + ...digits.map((digit) => `ninety${digit != '' ? '-' + digit : ''}`) +]; + +export function numberName(n: number): string { + if (n == 0) { + return 'zero'; + } + + let name: string[] = []; + const d43 = Math.floor(n / 1000); + if (d43 != 0) { + name.push(names100[d43]); + name.push('thousand'); + n -= d43 * 1000; + } + + const d2 = Math.floor(n / 100); + if (d2 != 0) { + name.push(names100[d2]); + name.push('hundred'); + n -= d2 * 100; + } + + const d10 = n; + if (d10 != 0) { + name.push(names100[d10]); + } + + return name.join(' '); +} diff --git a/benchmarks/tsconfig.json b/benchmarks/tsconfig.json new file mode 100644 index 0000000..971a02f --- /dev/null +++ b/benchmarks/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "composite": true, + "lib": ["ES2022", "esnext.disposable", "DOM"], + "target": "ES2022", + "strict": true, + "module": "NodeNext", + "outDir": "lib", + "sourceMap": true, + "declaration": true, + "rootDir": "src", + "typeRoots": ["./src/types", "./node_modules/@types"] + }, + "include": ["src"], + "references": [{ "path": "../packages/api" }] +} diff --git a/package.json b/package.json index 836799b..2848d6a 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,24 @@ { "scripts": { - "test": "vitest" - }, - "dependencies": { - "better-sqlite3": "^9.5.0", - "event-iterator": "^2.0.0", - "sqlite3": "^5.1.7" + "build": "pnpm run -r build", + "clean": "pnpm run -r clean", + "test": "CI=1 pnpm run -r test", + "format": "prettier . --write" }, + "dependencies": {}, + "private": true, "type": "module", "devDependencies": { "@types/better-sqlite3": "^7.6.9", "@types/mocha": "^10.0.6", - "chai": "^5.1.0", + "@types/node": "^20.14.2", + "expect": "^29.7.0", "mocha": "^10.4.0", + "prettier": "^3.2.5", + "ts-node": "^10.9.2", + "tsx": "^4.16.2", "typescript": "^5.4.5", "vitest": "^1.5.0" - } + }, + "packageManager": "pnpm@9.6.0+sha512.38dc6fba8dba35b39340b9700112c2fe1e12f10b17134715a4aa98ccf7bb035e76fd981cf0bb384dfa98f8d6af5481c2bef2f4266a24bfa20c34eb7147ce0b5e" } diff --git a/packages/api/README.md b/packages/api/README.md new file mode 100644 index 0000000..434de64 --- /dev/null +++ b/packages/api/README.md @@ -0,0 +1,11 @@ +# @sqlite-js/api + +This provides higher-level SQLite APIs, built on the lower-level driver APIs. + +This supports: + +1. Simple asynchronous queries. +2. Transactions. +3. Connection-level locking. +4. Pipelining operations. +5. Re-using prepared statements. diff --git a/packages/api/package.json b/packages/api/package.json new file mode 100644 index 0000000..5ff4ba7 --- /dev/null +++ b/packages/api/package.json @@ -0,0 +1,27 @@ +{ + "name": "@sqlite-js/api", + "version": "0.0.1", + "description": "", + "type": "module", + "scripts": { + "build": "tsc -b", + "test": "vitest", + "clean": "tsc -b --clean && rm -rf lib" + }, + "exports": { + ".": "./lib/index.js" + }, + "keywords": [], + "author": "", + "license": "MIT", + "dependencies": { + "@sqlite-js/driver": "workspace:^" + }, + "devDependencies": { + "@types/node": "^22.3.0", + "typescript": "^5.5.4", + "vitest": "^2.0.5", + "@sqlite-js/better-sqlite3-driver": "workspace:^", + "@sqlite-js/driver-tests": "workspace:^" + } +} diff --git a/packages/api/src/api.ts b/packages/api/src/api.ts new file mode 100644 index 0000000..4dfc6b3 --- /dev/null +++ b/packages/api/src/api.ts @@ -0,0 +1,287 @@ +import { SqliteArguments, SqliteRowObject } from '@sqlite-js/driver'; + +export type SqliteDatabase = SqliteConnectionPool & SqliteConnection; + +export interface SqliteConnectionPool extends SqliteConnection { + /** + * Reserve a connection for the duration of the callback. + * + * @param callback + * @param options + */ + withReservedConnection( + callback: (connection: SqliteConnection) => Promise, + options?: ReserveConnectionOptions + ): Promise; + + /** + * Reserve a connection until released. + * + * @param options + */ + reserveConnection( + options?: ReserveConnectionOptions + ): Promise; + + /** + * Start a transaction. + */ + transaction( + callback: (tx: SqliteTransaction) => Promise, + options?: TransactionOptions & ReserveConnectionOptions + ): Promise; + + /** + * Usage: + * + * await using tx = await db.usingTransaction(); + * ... + * await tx.commit(); + * + * If commit is not called, the transaction is rolled back automatically. + */ + begin( + options?: TransactionOptions & ReserveConnectionOptions + ): Promise; + + close(): Promise; + [Symbol.asyncDispose](): Promise; +} + +export interface ReserveConnectionOptions { + readonly?: boolean; +} + +export interface ReservedSqliteConnection extends SqliteConnection { + /** Direct handle to the underlying connection. */ + connection: SqliteConnection; + + release(): Promise; + [Symbol.asyncDispose](): Promise; +} + +export interface QueryInterface { + prepare( + query: string, + args?: SqliteArguments, + options?: QueryOptions + ): PreparedQuery; + + run( + query: string, + args?: SqliteArguments, + options?: ReserveConnectionOptions + ): Promise; + + stream( + query: string, + args: SqliteArguments, + options?: StreamOptions & ReserveConnectionOptions + ): AsyncGenerator; + + /** + * Convenience method, same as query(query, args).select(options). + * + * When called on a connection pool, uses readonly: true by default. + */ + select( + query: string, + args?: SqliteArguments, + options?: QueryOptions & ReserveConnectionOptions + ): Promise; + + /** + * Get a single row. + * + * Throws an exception if the query returns no results. + * + * @param query + * @param args + * @param options + */ + get( + query: string, + args?: SqliteArguments, + options?: QueryOptions & ReserveConnectionOptions + ): Promise; + + /** + * Get a single row. + * + * Returns null if the query returns no results. + * + * @param query + * @param args + * @param options + */ + getOptional( + query: string, + args?: SqliteArguments, + options?: QueryOptions & ReserveConnectionOptions + ): Promise; + + pipeline(options?: ReserveConnectionOptions): QueryPipeline; +} + +export interface SqliteConnection extends QueryInterface { + /** + * Start a transaction. + */ + transaction( + callback: (tx: SqliteTransaction) => Promise, + options?: TransactionOptions + ): Promise; + + /** + * Usage: + * + * await using tx = await db.begin(); + * ... + * await tx.commit(); + * + * If commit is not called, the transaction is rolled back automatically. + */ + begin(options?: TransactionOptions): Promise; + + /** + * Listen for individual update events as they occur. + * + * For efficiency, multiple updates may be batched together. + * + * These events may be batched together for efficiency. + * Either way, all events in a transaction must be emitted before + * "onTransactionClose" is emitted for that transaction. + * + * Use options.tables to limit the events to specific tables. + * + * Use options.batchLimit == 1 to disable event batching. + */ + onUpdate( + listener: UpdateListener, + options?: { tables?: string[]; batchLimit?: number } + ): () => void; + + /** + * Listen for transaction events. Fired when either: + * 1. Transaction is rolled back. + * 2. Transaction is committed and persisted. + * + * @param listener + */ + onTransactionClose(listener: TransactionCloseListener): () => void; + + /** + * Listen for committed updates to tables. + * + * This can be achieved by combining `onUpdate()` and `onTransactionClose()`, although + * implementations may optimize this version for large transactions. + */ + onTablesChanged(listener: TablesChangedListener): () => void; + + close(): Promise; +} + +export interface BatchedUpdateEvent { + events: UpdateEvent[]; +} + +export interface UpdateEvent { + table: string; + type: 'insert' | 'update' | 'delete'; + rowId: bigint; +} + +export interface TablesChangedEvent { + tables: string[]; +} + +export type UpdateListener = (event: BatchedUpdateEvent) => void; +export type TablesChangedListener = (event: TablesChangedEvent) => void; + +export interface TransactionCloseEvent { + rollback: boolean; +} + +export type TransactionCloseListener = (event: TransactionCloseEvent) => void; + +export interface SqliteTransaction extends QueryInterface { + rollback(): Promise; +} + +export interface SqliteBeginTransaction extends SqliteTransaction { + commit(): Promise; + + /** + * Rolls back the transaction. + * + * Does nothing if the transansaction is already committed or rolled back. + */ + dispose(): Promise; + [Symbol.asyncDispose](): Promise; +} + +export interface TransactionOptions { + /** + * See SQLite docs on the type. + * + * For WAL mode, immediate and exclusive are the same. + * + * Read transactions should use "deferred". + */ + type?: 'exclusive' | 'immediate' | 'deferred'; +} + +export interface RunResult { + changes: number; + lastInsertRowId: bigint; +} + +export interface PreparedQuery { + parse(): Promise<{ columns: string[] }>; + + /** + * Run the statement and stream results back. + * + * @param options.chunkSize size of each chunk to stream + */ + stream(args?: SqliteArguments, options?: StreamOptions): AsyncGenerator; + + /** + * Returns an array of rows. + */ + select(args?: SqliteArguments): Promise; + + /** + * Run the statement and return the number of changes. + */ + run(args?: SqliteArguments): Promise; + + dispose(): void; + [Symbol.dispose](): void; +} + +export interface QueryOptions { + /** true to return all integers as bigint */ + bigint?: boolean; +} + +export interface StreamOptions extends QueryOptions { + /** Size limit in bytes for each chunk */ + chunkSize?: number; +} + +export interface QueryPipeline { + /** + * Enqueue a query. + */ + run(query: string | PreparedQuery, args?: SqliteArguments): void; + + /** + * Flush all existing queries, wait for the last query to complete. + * + * TODO: define error handling. + */ + flush(): Promise; + + readonly count: number; +} diff --git a/packages/api/src/deferred.ts b/packages/api/src/deferred.ts new file mode 100644 index 0000000..2508399 --- /dev/null +++ b/packages/api/src/deferred.ts @@ -0,0 +1,12 @@ +export class Deferred { + promise: Promise; + resolve: (result: T) => void = undefined as any; + reject: (error: any) => void = undefined as any; + + constructor() { + this.promise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); + } +} diff --git a/packages/api/src/impl.ts b/packages/api/src/impl.ts new file mode 100644 index 0000000..8edff3d --- /dev/null +++ b/packages/api/src/impl.ts @@ -0,0 +1,776 @@ +import { + BatchedUpdateEvent, + PreparedQuery, + QueryInterface, + QueryOptions, + QueryPipeline, + ReserveConnectionOptions, + ReservedSqliteConnection, + RunResult, + SqliteBeginTransaction, + SqliteConnection, + SqliteConnectionPool, + SqliteTransaction, + StreamOptions, + TablesChangedEvent, + TablesChangedListener, + TransactionCloseEvent, + TransactionCloseListener, + TransactionOptions, + UpdateListener +} from './api.js'; +import { SqliteArguments } from '@sqlite-js/driver'; +import { Deferred } from './deferred.js'; +import { + PrepareOptions, + SqliteDriverConnection, + SqliteDriverConnectionPool, + SqliteDriverStatement, + SqliteRowObject +} from '@sqlite-js/driver'; + +export class ConnectionPoolImpl + implements SqliteConnectionPool, QueryInterface +{ + private connections = new WeakMap(); + [Symbol.asyncDispose]: () => Promise = undefined as any; + + constructor(private driver: SqliteDriverConnectionPool) { + if (typeof Symbol.asyncDispose != 'undefined') { + this[Symbol.asyncDispose] = () => this.close(); + } + } + + onUpdate( + listener: UpdateListener, + options?: { tables?: string[]; batchLimit?: number } + ): () => void { + throw new Error('Method not implemented.'); + } + onTransactionClose(listener: TransactionCloseListener): () => void { + throw new Error('Method not implemented.'); + } + onTablesChanged(listener: TablesChangedListener): () => void { + throw new Error('Method not implemented.'); + } + + prepare( + sql: string, + args?: SqliteArguments + ): PreparedQuery { + return new ConnectionPoolPreparedQueryImpl(this, sql, args); + } + + pipeline(options?: ReserveConnectionOptions | undefined): QueryPipeline { + throw new Error('pipeline not supported here'); + } + + async run( + query: string, + args?: SqliteArguments, + options?: (QueryOptions & ReserveConnectionOptions) | undefined + ): Promise { + const r = await this.reserveConnection({ readonly: false, ...options }); + try { + return r.connection.run(query, args, options); + } finally { + await r.release(); + } + } + + async transaction( + callback: (tx: SqliteTransaction) => Promise, + options?: (TransactionOptions & ReserveConnectionOptions) | undefined + ): Promise { + const r = await this.reserveConnection(options); + try { + return await r.transaction(callback, { + type: options?.type ?? (options?.readonly ? 'deferred' : 'exclusive') + }); + } finally { + await r.release(); + } + } + + async begin( + options?: (TransactionOptions & ReserveConnectionOptions) | undefined + ): Promise { + const r = await this.reserveConnection(options); + const tx = await r.connection.begin(options); + (tx as BeginTransactionImpl).onComplete.finally(() => { + return r.release(); + }); + return tx; + } + + async *stream( + query: string, + args?: SqliteArguments, + options?: (StreamOptions & ReserveConnectionOptions) | undefined + ): AsyncGenerator { + const r = await this.reserveConnection({ readonly: true, ...options }); + try { + return r.stream(query, args, options); + } finally { + await r.release(); + } + } + + async select( + query: string, + args?: SqliteArguments | undefined, + options?: (QueryOptions & ReserveConnectionOptions) | undefined + ): Promise { + const r = await this.reserveConnection({ readonly: true, ...options }); + try { + return r.select(query, args, options); + } finally { + await r.release(); + } + } + + async get( + query: string, + args?: SqliteArguments | undefined, + options?: (QueryOptions & ReserveConnectionOptions) | undefined + ): Promise { + const r = await this.reserveConnection({ readonly: true, ...options }); + try { + return r.connection.get(query, args, options); + } finally { + await r.release(); + } + } + + async getOptional( + query: string, + args?: SqliteArguments | undefined, + options?: (QueryOptions & ReserveConnectionOptions) | undefined + ): Promise { + const r = await this.reserveConnection({ readonly: true, ...options }); + try { + return r.connection.getOptional(query, args, options); + } finally { + await r.release(); + } + } + + async withReservedConnection( + callback: (connection: SqliteConnection) => Promise, + options?: ReserveConnectionOptions | undefined + ): Promise { + const con = await this.driver.reserveConnection(options ?? {}); + let wrapped = this.connections.get(con.connection); + if (wrapped == null) { + wrapped = new ConnectionImpl(con.connection); + this.connections.set(con.connection, wrapped); + } + try { + return await callback(wrapped); + } finally { + await con.release(); + } + } + + async reserveConnection( + options?: ReserveConnectionOptions | undefined + ): Promise { + const con = await this.driver.reserveConnection(options ?? {}); + let wrapped = this.connections.get(con.connection); + if (wrapped == null) { + wrapped = new ConnectionImpl(con.connection); + this.connections.set(con.connection, wrapped); + } + + return new ReservedConnectionImpl(wrapped, () => con.release()); + } + + close(): Promise { + return this.driver.close(); + } +} + +export class ReservedConnectionImpl implements ReservedSqliteConnection { + [Symbol.asyncDispose]: () => Promise = undefined as any; + + constructor( + public connection: SqliteConnection, + public release: () => Promise + ) { + if (typeof Symbol.asyncDispose != 'undefined') { + this[Symbol.asyncDispose] = release; + } + } + + prepare( + sql: string, + args?: SqliteArguments, + options?: QueryOptions + ): PreparedQuery { + return this.connection.prepare(sql, args, options); + } + + pipeline(): QueryPipeline { + return this.connection.pipeline(); + } + + transaction( + callback: (tx: SqliteTransaction) => Promise, + options?: TransactionOptions | undefined + ): Promise { + return this.connection.transaction(callback, options); + } + + begin( + options?: TransactionOptions | undefined + ): Promise { + return this.connection.begin(options); + } + + onUpdate( + listener: UpdateListener, + options?: + | { tables?: string[] | undefined; batchLimit?: number | undefined } + | undefined + ): () => void { + return this.connection.onUpdate(listener, options); + } + + onTransactionClose(listener: TransactionCloseListener): () => void { + return this.connection.onTransactionClose(listener); + } + + onTablesChanged(listener: TablesChangedListener): () => void { + return this.connection.onTablesChanged(listener); + } + + close(): Promise { + return this.connection.close(); + } + + run(query: string, args?: SqliteArguments | undefined): Promise { + return this.connection.run(query, args); + } + + stream( + query: string, + args: SqliteArguments | undefined, + options?: StreamOptions | undefined + ): AsyncGenerator { + return this.connection.stream(query, args, options); + } + + select( + query: string, + args?: SqliteArguments | undefined, + options?: QueryOptions | undefined + ): Promise { + return this.connection.select(query, args, options); + } + + get( + query: string, + args?: SqliteArguments, + options?: QueryOptions + ): Promise { + return this.connection.get(query, args, options); + } + + getOptional( + query: string, + args?: SqliteArguments, + options?: QueryOptions + ): Promise { + return this.connection.getOptional(query, args, options); + } +} + +export class ConnectionImpl implements SqliteConnection { + private _begin: PreparedQuery<{}> | undefined; + private _beginExclusive: PreparedQuery<{}> | undefined; + public commit: PreparedQuery<{}> | undefined; + public rollback: PreparedQuery<{}> | undefined; + + constructor(private driver: SqliteDriverConnection) {} + + private init() { + this._beginExclusive ??= this.prepare('BEGIN EXCLUSIVE', undefined, { + persist: true + }); + this._begin ??= this.prepare('BEGIN', undefined, { persist: true }); + this.commit ??= this.prepare('COMMIT', undefined, { persist: true }); + this.rollback ??= this.prepare('ROLLBACK', undefined, { persist: true }); + } + + async begin(options?: TransactionOptions): Promise { + await this.init(); + + if ((options?.type ?? 'exclusive') == 'exclusive') { + await this._beginExclusive!.select(); + } else { + await this._begin!.select(); + } + + return new BeginTransactionImpl(this); + } + + async transaction( + callback: (tx: SqliteTransaction) => Promise, + options?: TransactionOptions + ): Promise { + this.init(); + + if ((options?.type ?? 'exclusive') == 'exclusive') { + await this._beginExclusive!.select(); + } else { + await this._begin!.select(); + } + try { + const tx = new TransactionImpl(this); + const result = await callback(tx); + + await this.commit!.select(); + return result; + } catch (e) { + await this.rollback!.select(); + throw e; + } + } + + onUpdate( + listener: (event: BatchedUpdateEvent) => void, + options?: + | { tables?: string[] | undefined; batchLimit?: number | undefined } + | undefined + ): () => void { + throw new Error('Method not implemented.'); + } + onTransactionClose( + listener: (event: TransactionCloseEvent) => void + ): () => void { + throw new Error('Method not implemented.'); + } + + onTablesChanged(listener: (event: TablesChangedEvent) => void): () => void { + throw new Error('Method not implemented.'); + } + + async close(): Promise { + this._beginExclusive?.dispose(); + this._begin?.dispose(); + this.commit?.dispose(); + this.rollback?.dispose(); + } + + prepare( + sql: string, + args?: SqliteArguments, + options?: PrepareOptions + ): PreparedQuery { + const statement = this.driver.prepare(sql, options); + if (args) { + statement.bind(args); + } + return new ConnectionPreparedQueryImpl( + this, + this.driver, + statement, + sql, + args + ); + } + + pipeline(): QueryPipeline { + return new QueryPipelineImpl(this.driver); + } + + async run(query: string, args: SqliteArguments): Promise { + using statement = this.driver.prepare(query); + if (args != null) { + statement.bind(args); + } + return await statement.run(); + } + + async *stream( + query: string | PreparedQuery, + args: SqliteArguments | undefined, + options?: StreamOptions | undefined + ): AsyncGenerator { + using statement = this.driver.prepare(query as string, { + bigint: options?.bigint + }); + if (args != null) { + statement.bind(args); + } + const chunkSize = options?.chunkSize ?? 100; + + while (true) { + const { rows, done } = await statement.step(chunkSize); + if (rows != null) { + yield rows as T[]; + } + if (done) { + break; + } + } + } + + async select( + query: string, + args?: SqliteArguments, + options?: (QueryOptions & ReserveConnectionOptions) | undefined + ): Promise { + using statement = this.driver.prepare(query, { + bigint: options?.bigint, + rawResults: false + }); + if (args != null) { + statement.bind(args); + } + const { rows } = await statement.step(); + return rows as T[]; + } + + async get( + query: string, + args?: SqliteArguments, + options?: (QueryOptions & ReserveConnectionOptions) | undefined + ): Promise { + const row = await this.getOptional(query, args, options); + if (row == null) { + throw new Error('Query returned 0 rows'); + } + return row; + } + + async getOptional( + query: string, + args?: SqliteArguments, + options?: (QueryOptions & ReserveConnectionOptions) | undefined + ): Promise { + const rows = await this.select(query, args, options); + return rows[0]; + } +} + +export class TransactionImpl implements SqliteTransaction { + private preparedQueries: PreparedQuery[] = []; + + constructor(public con: ConnectionImpl) {} + + getAutoCommit(): Promise { + throw new Error('Method not implemented.'); + } + + async rollback(): Promise { + await this.con.rollback!.select(); + } + + prepare( + sql: string, + args?: SqliteArguments, + options?: QueryOptions + ): PreparedQuery { + const q = this.con.prepare(sql, args, options); + // FIXME: auto-dispose these after transaction commit / rollback + this.preparedQueries.push(q); + return q; + } + + pipeline(): QueryPipeline { + return this.con.pipeline(); + } + + run(query: string, args: SqliteArguments): Promise { + return this.con.run(query, args); + } + + stream( + query: string, + args: SqliteArguments, + options?: StreamOptions | undefined + ): AsyncGenerator { + return this.con.stream(query, args, options); + } + + select( + query: string, + args?: SqliteArguments, + options?: QueryOptions | undefined + ): Promise { + return this.con.select(query, args, options); + } + + get( + query: string, + args?: SqliteArguments, + options?: QueryOptions | undefined + ): Promise { + return this.con.get(query, args, options); + } + + getOptional( + query: string, + args?: SqliteArguments, + options?: QueryOptions | undefined + ): Promise { + return this.con.getOptional(query, args, options); + } +} + +class BeginTransactionImpl + extends TransactionImpl + implements SqliteBeginTransaction +{ + [Symbol.asyncDispose]: () => Promise = undefined as any; + + private didCommit = false; + + private completeDeferred = new Deferred(); + private didGetDispose = false; + + get onComplete(): Promise { + return this.completeDeferred.promise; + } + + constructor(connection: ConnectionImpl) { + super(connection); + if (typeof Symbol.asyncDispose != 'undefined') { + Object.defineProperty(this, Symbol.asyncDispose, { + configurable: false, + enumerable: false, + get: () => { + this.didGetDispose = true; + return this.dispose; + } + }); + } + } + + private checkDispose() { + if (!this.didGetDispose) { + throw new Error( + 'Transaction dispose handler is not registered. Usage:\n await using tx = await db.begin()' + ); + } + } + + async select( + query: string, + args?: SqliteArguments, + options?: (QueryOptions & ReserveConnectionOptions) | undefined + ): Promise { + this.checkDispose(); + return super.select(query, args, options); + } + + async commit(): Promise { + this.checkDispose(); + if (this.didCommit) { + return; + } + await this.con.commit!.select(); + this.didCommit = true; + this.completeDeferred.resolve(); + } + + async rollback(): Promise { + if (this.didCommit) { + return; + } + await super.rollback(); + + this.didCommit = true; + this.completeDeferred.resolve(); + } + + async dispose(): Promise { + await this.rollback(); + } +} + +class ConnectionPoolPreparedQueryImpl + implements PreparedQuery +{ + [Symbol.dispose]: () => void = undefined as any; + + private byConnection: Map> = new Map(); + + constructor( + private context: ConnectionPoolImpl, + public sql: string, + public args: SqliteArguments + ) { + if (typeof Symbol.dispose != 'undefined') { + this[Symbol.dispose] = () => this.dispose(); + } + } + + async parse(): Promise<{ columns: string[] }> { + const r = await this.context.reserveConnection(); + try { + const q = this.cachedQuery(r); + return q.parse(); + } finally { + await r.release(); + } + } + + async *stream( + args?: SqliteArguments, + options?: StreamOptions | undefined + ): AsyncGenerator { + const r = await this.context.reserveConnection(); + try { + const q = this.cachedQuery(r); + yield* q.stream(args, options); + } finally { + await r.release(); + } + } + + async run(args?: SqliteArguments): Promise { + const r = await this.context.reserveConnection(); + try { + const q = this.cachedQuery(r); + return q.run(args); + } finally { + await r.release(); + } + } + + async select(args?: SqliteArguments): Promise { + const r = await this.context.reserveConnection(); + try { + const q = this.cachedQuery(r); + return q.select(args); + } finally { + await r.release(); + } + } + + dispose(): void { + for (let sub of this.byConnection.values()) { + sub.dispose(); + } + this.byConnection.clear(); + } + + private cachedQuery(connection: SqliteConnection) { + const cimpl = connection as ConnectionImpl; + let sub = this.byConnection.get(cimpl); + if (sub == null) { + sub = cimpl.prepare(this.sql, this.args); + this.byConnection.set(cimpl, sub); + } + return sub; + } +} + +class ConnectionPreparedQueryImpl + implements PreparedQuery +{ + [Symbol.dispose]: () => void = undefined as any; + + private columnsPromise: Promise; + + constructor( + private context: ConnectionImpl, + private driver: SqliteDriverConnection, + public statement: SqliteDriverStatement, + public sql: string, + public args: SqliteArguments + ) { + if (typeof Symbol.dispose != 'undefined') { + this[Symbol.dispose] = () => this.dispose(); + } + this.columnsPromise = statement.getColumns(); + } + + async parse(): Promise<{ columns: string[] }> { + return { + columns: await this.columnsPromise + }; + } + + async *stream( + args?: SqliteArguments, + options?: StreamOptions | undefined + ): AsyncGenerator { + const chunkSize = options?.chunkSize ?? 10; + if (args != null) { + this.statement.bind(args); + } + try { + while (true) { + const { rows, done } = await this.statement.step(chunkSize); + if (rows != null) { + yield rows as T[]; + } + if (done) { + break; + } + } + } finally { + this.statement.reset(); + } + } + + async run(args?: SqliteArguments): Promise { + if (args != null) { + this.statement.bind(args); + } + return await this.statement.run(); + } + + async select(args?: SqliteArguments): Promise { + try { + if (args != null) { + this.statement.bind(args); + } + const { rows } = await this.statement.step(); + return rows as T[]; + } finally { + this.statement.reset(); + } + } + + dispose(): void { + this.statement.finalize(); + } +} + +class QueryPipelineImpl implements QueryPipeline { + count: number = 0; + private lastPromise: Promise | undefined = undefined; + + constructor(private driver: SqliteDriverConnection) {} + + run(query: string | PreparedQuery, args?: SqliteArguments): void { + this.count += 1; + if (typeof query == 'string') { + using statement = this.driver.prepare(query); + if (args) { + statement.bind(args); + } + this.lastPromise = statement.step(undefined, { + requireTransaction: true + }); + } else if (query instanceof ConnectionPreparedQueryImpl) { + const statement = query.statement; + statement.bind(args ?? []); + this.lastPromise = statement.step(undefined, { + requireTransaction: true + }); + statement.reset(); + } else { + throw new Error('not implemented yet'); + } + } + + async flush(): Promise { + this.count = 0; + await this.lastPromise; + } +} diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts new file mode 100644 index 0000000..e9c3f4f --- /dev/null +++ b/packages/api/src/index.ts @@ -0,0 +1,2 @@ +export * from './api.js'; +export * from './impl.js'; diff --git a/packages/api/test/src/better-sqlite3.test.ts b/packages/api/test/src/better-sqlite3.test.ts new file mode 100644 index 0000000..c5720ac --- /dev/null +++ b/packages/api/test/src/better-sqlite3.test.ts @@ -0,0 +1,9 @@ +import { BetterSqliteDriver } from '@sqlite-js/better-sqlite3-driver'; + +import { ConnectionPoolImpl } from '../../lib/impl.js'; +import { describeImplTests } from './impl-tests.js'; + +describeImplTests( + 'better-sqlite3', + (path) => new ConnectionPoolImpl(BetterSqliteDriver.open(path)) +); diff --git a/packages/api/test/src/impl-tests.ts b/packages/api/test/src/impl-tests.ts new file mode 100644 index 0000000..d09c847 --- /dev/null +++ b/packages/api/test/src/impl-tests.ts @@ -0,0 +1,196 @@ +import * as fs from 'node:fs/promises'; +import * as path from 'node:path'; +import { beforeEach, describe, test } from '@sqlite-js/driver-tests/test'; +import { expect } from 'expect'; +import { ConnectionPoolImpl } from '../../lib/impl.js'; + +export function describeImplTests( + name: string, + factory: (path: string) => ConnectionPoolImpl +) { + describe(`${name} - api tests`, () => { + let dbPath: string; + + const open = async () => { + const dir = path.dirname(dbPath); + try { + await fs.mkdir(dir); + } catch (e) {} + try { + await fs.rm(dbPath); + } catch (e) {} + return factory(dbPath); + }; + + beforeEach((context) => { + const testNameSanitized = context.fullName.replaceAll( + /[\s\/\\>\.\-]+/g, + '_' + ); + dbPath = `test-db/${testNameSanitized}.db`; + }); + + test('basic select', async () => { + await using db = await open(); + await using connection = await db.reserveConnection(); + const results = await connection.select('select 1 as one'); + expect(results).toEqual([{ one: 1 }]); + }); + + test('big number', async () => { + await using db = await open(); + await using connection = await db.reserveConnection(); + const results = await connection.select( + 'select 9223372036854775807 as bignumber' + ); + + expect(results).toEqual([{ bignumber: 9223372036854776000 }]); + const results2 = await connection.select('select ? as bignumber', [ + 9223372036854775807n + ]); + + expect(results2).toEqual([{ bignumber: 9223372036854776000 }]); + }); + + test('bigint', async () => { + await using db = await open(); + await using connection = await db.reserveConnection(); + + const rows1 = await connection.select( + 'select ? as bignumber', + [9223372036854775807n], + { bigint: true } + ); + expect(rows1).toEqual([{ bignumber: 9223372036854775807n }]); + + const rows2 = await connection.select( + 'select 9223372036854775807 as bignumber', + undefined, + { bigint: true } + ); + + expect(rows2).toEqual([{ bignumber: 9223372036854775807n }]); + }); + + test('insert returning', async () => { + await using db = await open(); + await using connection = await db.reserveConnection(); + await connection.select( + 'create table test_data(id integer primary key, data text)' + ); + const results = await connection.select( + 'insert into test_data(data) values(123) returning id' + ); + + expect(results).toEqual([{ id: 1 }]); + }); + + test('run', async () => { + await using db = await open(); + await using connection = await db.reserveConnection(); + await connection.run( + 'create table test_data(id integer primary key, data text)' + ); + const results = await connection.run( + 'insert into test_data(data) values(123)' + ); + + expect(results.changes).toEqual(1); + expect(results.lastInsertRowId).toEqual(1n); + }); + + test('run - select', async () => { + await using db = await open(); + await using connection = await db.reserveConnection(); + const results = await connection.run('select 1 as one'); + + expect(results.changes).toEqual(0); + expect(results.lastInsertRowId).toEqual(0n); + }); + + test('transaction', async () => { + await using db = await open(); + await using connection = await db.reserveConnection(); + const results1 = await connection.transaction(async () => { + return await connection.select('select 1 as one'); + }); + const results2 = await connection.transaction(async () => { + return await connection.select('select 1 as one'); + }); + + expect(results1).toEqual([{ one: 1 }]); + expect(results2).toEqual([{ one: 1 }]); + }); + + test('begin', async () => { + await using db = await open(); + { + await using tx = await db.begin(); + const results1 = await tx.select('select 1 as one'); + expect(results1).toEqual([{ one: 1 }]); + await tx.commit(); + } + { + await using tx = await db.begin(); + const results1 = await tx.select('select 1 as one'); + expect(results1).toEqual([{ one: 1 }]); + await tx.commit(); + } + { + await using tx = await db.begin(); + const results = await tx.select('select 1 as one'); + expect(results).toEqual([{ one: 1 }]); + await tx.commit(); + } + }); + + test('begin - explicit commit in sequence', async () => { + await using db = await open(); + { + await using tx1 = await db.begin(); + const results1 = await tx1.select('select 1 as one'); + expect(results1).toEqual([{ one: 1 }]); + await tx1.commit(); + await using tx2 = await db.begin(); + const results2 = await tx2.select('select 1 as one'); + expect(results2).toEqual([{ one: 1 }]); + await tx2.commit(); + await using tx3 = await db.begin(); + const results3 = await tx3.select('select 1 as one'); + expect(results3).toEqual([{ one: 1 }]); + await tx3.commit(); + } + }); + + test('begin - error when not using asyncDispose', async () => { + await using db = await open(); + const tx = await db.begin(); + try { + await expect(() => tx.select('select 1 as one')).rejects.toMatchObject({ + message: expect.stringContaining('dispose handler is not registered') + }); + } finally { + // Just for the test itself + await tx.dispose(); + } + }); + + test.skip('onUpdate', async () => { + // Skipped: Not properly implemented yet. + + await using db = await open(); + await using connection = await db.reserveConnection(); + // await connection.run( + // "create table test_data(id integer primary key, data text)" + // ); + // // TODO: test the results + // connection.onUpdate(({ events }) => { + // console.log("update", events); + // }); + // await connection.run( + // "insert into test_data(data) values(123) returning id" + // ); + // await connection.run("update test_data set data = data || 'test'"); + }); + }); +} diff --git a/packages/api/test/src/node-sqlite.test.ts b/packages/api/test/src/node-sqlite.test.ts new file mode 100644 index 0000000..c77a3e6 --- /dev/null +++ b/packages/api/test/src/node-sqlite.test.ts @@ -0,0 +1,8 @@ +import { NodeSqliteDriver } from '@sqlite-js/driver/node'; +import { ConnectionPoolImpl } from '../../lib/impl.js'; +import { describeImplTests } from './impl-tests.js'; + +describeImplTests( + 'node:sqlite', + (path) => new ConnectionPoolImpl(NodeSqliteDriver.open(path)) +); diff --git a/packages/api/test/tsconfig.json b/packages/api/test/tsconfig.json new file mode 100644 index 0000000..42305e6 --- /dev/null +++ b/packages/api/test/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2022", "DOM"], + "noEmit": true + }, + "include": ["src"], + "references": [{ "path": "../" }] +} diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json new file mode 100644 index 0000000..4db1838 --- /dev/null +++ b/packages/api/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "lib", + "rootDir": "src" + }, + "include": ["src"], + "references": [ + { "path": "../driver" }, + { "path": "../driver-tests" }, + { "path": "../better-sqlite3-driver" } + ] +} diff --git a/packages/api/vitest.config.ts b/packages/api/vitest.config.ts new file mode 100644 index 0000000..8932301 --- /dev/null +++ b/packages/api/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + esbuild: { target: 'es2022' }, + test: { + environment: 'node', + include: ['test/src/**/*.test.ts'] + } +}); diff --git a/packages/better-sqlite3-driver/README.md b/packages/better-sqlite3-driver/README.md new file mode 100644 index 0000000..f9c9a4b --- /dev/null +++ b/packages/better-sqlite3-driver/README.md @@ -0,0 +1,17 @@ +# @sqlite-js/better-sqlite3-driver + +This contains a driver implementation based on better-sqlite3. + +There are two main options for using the driver: + +```ts +const driver = BetterSqliteDriver.open(path); +``` + +This opens a connection pool using worker_threads, giving asynchronous I/O. + +```ts +const driver = BetterSqliteDriver.openInProcess(path); +``` + +This opens a single in-process connection, with blocking I/O. This can give lower latency and higher throughput, at the cost of blocking the process and not supporting concurrent operations. diff --git a/packages/better-sqlite3-driver/package.json b/packages/better-sqlite3-driver/package.json new file mode 100644 index 0000000..2e00018 --- /dev/null +++ b/packages/better-sqlite3-driver/package.json @@ -0,0 +1,27 @@ +{ + "name": "@sqlite-js/better-sqlite3-driver", + "version": "0.0.1", + "description": "", + "type": "module", + "scripts": { + "build": "tsc -b", + "test": "pnpm build && vitest", + "clean": "tsc -b --clean && tsc -b ./test/tsconfig.json --clean && rm -rf lib test/lib" + }, + "exports": { + ".": "./lib/index.js" + }, + "keywords": [], + "author": "", + "license": "MIT", + "dependencies": { + "better-sqlite3": "^11.1.2", + "@sqlite-js/driver": "workspace:^" + }, + "devDependencies": { + "@types/node": "^22.3.0", + "typescript": "^5.5.4", + "vitest": "^2.0.5", + "@sqlite-js/driver-tests": "workspace:^" + } +} diff --git a/packages/better-sqlite3-driver/src/driver.ts b/packages/better-sqlite3-driver/src/driver.ts new file mode 100644 index 0000000..898e51b --- /dev/null +++ b/packages/better-sqlite3-driver/src/driver.ts @@ -0,0 +1,85 @@ +import { + ReserveConnectionOptions, + ReservedConnection, + SqliteDriverConnectionPool, + UpdateListener +} from '@sqlite-js/driver'; +import { + ConnectionPoolOptions, + LazyConnectionPool, + ReadWriteConnectionPool +} from '@sqlite-js/driver/util'; +import { WorkerDriverConnection } from '@sqlite-js/driver/worker_threads'; +import type * as bsqlite from 'better-sqlite3'; + +export interface BetterSqliteDriverOptions extends ConnectionPoolOptions { + /** + * Specify a custom path to a worker script, to customize the loading process. + */ + workerPath?: string | URL; + + loadExtensions?: string[]; +} + +export class BetterSqliteDriver implements SqliteDriverConnectionPool { + /** + * Opens a single in-process connection. + * + * Uses blocking I/O. + */ + static openInProcess( + path: string, + options?: bsqlite.Options + ): BetterSqliteDriver { + const connection = new LazyConnectionPool(async () => { + const { BetterSqliteConnection } = await import('./sync-driver.js'); + return BetterSqliteConnection.open(path, options); + }); + return new BetterSqliteDriver(connection); + } + + /** + * Opens a connection pool with non-blocking I/O using worker_threads. + */ + static open( + path: string, + options?: BetterSqliteDriverOptions + ): BetterSqliteDriver { + const workerPath = + options?.workerPath ?? new URL('./worker.js', import.meta.url); + + const connection = new ReadWriteConnectionPool({ + async openConnection(connectionOptions) { + return new WorkerDriverConnection(workerPath, { + path, + ...options, + readonly: connectionOptions?.readonly || false + }); + } + }); + return new BetterSqliteDriver(connection); + } + + private constructor(private connection: SqliteDriverConnectionPool) {} + + reserveConnection( + options?: ReserveConnectionOptions + ): Promise { + return this.connection.reserveConnection(options); + } + + close(): Promise { + return this.connection.close(); + } + + onUpdate( + listener: UpdateListener, + options?: { tables?: string[]; batchLimit?: number } + ): () => void { + return this.connection.onUpdate(listener, options); + } + + [Symbol.asyncDispose](): Promise { + return this.connection[Symbol.asyncDispose](); + } +} diff --git a/packages/better-sqlite3-driver/src/index.ts b/packages/better-sqlite3-driver/src/index.ts new file mode 100644 index 0000000..64baa0d --- /dev/null +++ b/packages/better-sqlite3-driver/src/index.ts @@ -0,0 +1 @@ +export * from './driver.js'; diff --git a/packages/better-sqlite3-driver/src/sync-driver.ts b/packages/better-sqlite3-driver/src/sync-driver.ts new file mode 100644 index 0000000..8a0bac9 --- /dev/null +++ b/packages/better-sqlite3-driver/src/sync-driver.ts @@ -0,0 +1,288 @@ +import { + PrepareOptions, + ResetOptions, + SqliteChanges, + SqliteDriverConnection, + SqliteDriverStatement, + SqliteParameterBinding, + SqliteStepResult, + SqliteValue, + StepOptions, + UpdateListener +} from '@sqlite-js/driver'; +import type * as bsqlite from 'better-sqlite3'; +import DatabaseConstructor from 'better-sqlite3'; + +import { ErrorStatement, mapError } from '@sqlite-js/driver/util'; +import { BetterSqliteDriverOptions } from './driver.js'; + +interface InternalStatement extends SqliteDriverStatement { + readonly source: string; + readonly persisted: boolean; +} + +class BetterSqlitePreparedStatement implements InternalStatement { + public statement: bsqlite.Statement; + private options: PrepareOptions; + private bindPositional: SqliteValue[] = []; + private bindNamed: Record = {}; + private statementDone = false; + private iterator: Iterator | undefined = undefined; + + readonly persisted: boolean; + + [Symbol.dispose]: () => void = undefined as any; + + constructor(statement: bsqlite.Statement, options: PrepareOptions) { + this.statement = statement; + this.options = options; + this.persisted = options.persist ?? false; + + if (typeof Symbol.dispose != 'undefined') { + this[Symbol.dispose] = () => this.finalize(); + } + } + + get source() { + return this.statement.source; + } + + async getColumns(): Promise { + const existing = this.statement; + if (existing.reader) { + const columns = existing.columns().map((c) => c.name); + return columns; + } else { + return []; + } + } + + bind(parameters: SqliteParameterBinding): void { + if (parameters == null) { + return; + } + if (Array.isArray(parameters)) { + let bindArray = this.bindPositional; + + for (let i = 0; i < parameters.length; i++) { + if (typeof parameters[i] != 'undefined') { + bindArray[i] = parameters[i]!; + } + } + } else { + for (let key in parameters) { + const value = parameters[key]; + let name = key; + const prefix = key[0]; + // better-sqlite doesn't support the explicit prefix - strip it + if (prefix == ':' || prefix == '?' || prefix == '$' || prefix == '@') { + name = key.substring(1); + } + this.bindNamed[name] = value; + } + } + } + + async step(n?: number, options?: StepOptions): Promise { + try { + return this.stepSync(n, options); + } catch (e) { + throw mapError(e); + } + } + + async run(options?: StepOptions): Promise { + try { + return this.runSync(options); + } catch (e) { + throw mapError(e); + } + } + + runSync(options?: StepOptions): SqliteChanges { + if (options?.requireTransaction) { + if (!this.statement.database.inTransaction) { + throw new Error('Transaction has been rolled back'); + } + } + + const statement = this.statement; + this.reset(); + + try { + const bindNamed = this.bindNamed; + const bindPositional = this.bindPositional; + const bind = [bindPositional, bindNamed].filter((b) => b != null); + + statement.safeIntegers(true); + const r = statement.run(...bind); + return { + changes: r.changes, + lastInsertRowId: r.lastInsertRowid as bigint + }; + } finally { + this.reset(); + } + } + + stepSync(n?: number, options?: StepOptions): SqliteStepResult { + const all = n == null; + + const statement = this.statement; + if (this.statementDone) { + return { done: true } as SqliteStepResult; + } + + if (options?.requireTransaction) { + if (!this.statement.database.inTransaction) { + throw new Error('Transaction has been rolled back'); + } + } + + const bindNamed = this.bindNamed; + const bindPositional = this.bindPositional; + const bind = [bindPositional, bindNamed].filter((b) => b != null); + if (!statement.reader) { + statement.run(...bind); + this.statementDone = true; + return { rows: [], done: true } as SqliteStepResult; + } + let iterator = this.iterator; + const num_rows = n ?? 1; + if (iterator == null) { + statement.raw(this.options.rawResults ?? false); + statement.safeIntegers(this.options.bigint ?? false); + iterator = statement.iterate(...bind); + this.iterator = iterator; + } + let rows = []; + let isDone = false; + for (let i = 0; i < num_rows || all; i++) { + const { value, done } = iterator.next(); + if (done) { + isDone = true; + break; + } + rows.push(value); + } + if (isDone) { + this.statementDone = true; + } + return { rows, done: isDone } as SqliteStepResult; + } + + finalize(): void { + const existingIter = this.iterator; + if (existingIter != null) { + existingIter.return?.(); + } + this.iterator = undefined; + this.statementDone = false; + } + + reset(options?: ResetOptions): void { + if (this.iterator) { + const iter = this.iterator; + iter.return!(); + this.iterator = undefined; + } + if (options?.clearBindings) { + this.bindNamed = {}; + this.bindPositional = []; + } + this.statementDone = false; + } +} + +export class BetterSqliteConnection implements SqliteDriverConnection { + con: bsqlite.Database; + private changeStatement: bsqlite.Statement; + + static open( + path: string, + options?: bsqlite.Options & BetterSqliteDriverOptions + ): BetterSqliteConnection { + const con = new DatabaseConstructor(path, options); + con.exec('PRAGMA journal_mode = WAL'); + con.exec('PRAGMA synchronous = normal'); + + if (options?.loadExtensions) { + for (let extension of options.loadExtensions) { + con.loadExtension(extension); + } + } + return new BetterSqliteConnection(con); + } + + constructor(con: bsqlite.Database) { + this.con = con; + + this.changeStatement = this.con.prepare( + 'select last_insert_rowid() as l, changes() as c' + ); + this.changeStatement.safeIntegers(true); + } + + async getLastChanges(): Promise { + const r = this.changeStatement.get() as any; + return { + lastInsertRowId: r!.l, + changes: Number(r!.c) + }; + } + + async close() { + this.con.close(); + } + + prepare(sql: string, options?: PrepareOptions): InternalStatement { + try { + const statement = this.con.prepare(sql); + return new BetterSqlitePreparedStatement(statement, options ?? {}); + } catch (error) { + return new ErrorStatement(sql, mapError(error), options ?? {}); + } + } + + onUpdate( + listener: UpdateListener, + options?: + | { tables?: string[] | undefined; batchLimit?: number | undefined } + | undefined + ): () => void { + // Proof-of-concept implementation, based on the idea here: + // https://github.com/WiseLibs/better-sqlite3/issues/62 + // TODO: + // 1. Handle multiple registrations. + // 2. Don't re-register triggers. + // 3. De-register listener. + // 4. Batching. + // + // More fundamental limitations: + // 1. The table needs to exist before registering the listener. + // 2. Deleting and re-creating the same will dereigster the listener for that table. + + this.con.function('_logger', function (table: any, type: any, rowid: any) { + listener({ events: [{ table, rowId: rowid, type }] }); + }); + let tables = options?.tables; + if (tables == null) { + tables = this.con + .prepare(`select name from sqlite_master where type = 'table'`) + .all() + .map((row) => (row as any).name as string); + } + for (let table of tables) { + this.con.exec( + `CREATE TEMPORARY TRIGGER IF NOT EXISTS _logger_notification_${table}__update AFTER UPDATE ON ${table} BEGIN SELECT _logger('${table}', 'update', NEW.rowid); END` + ); + this.con.exec( + `CREATE TEMPORARY TRIGGER IF NOT EXISTS _logger_notification_${table}__insert AFTER INSERT ON ${table} BEGIN SELECT _logger('${table}', 'insert', NEW.rowid); END` + ); + this.con.exec( + `CREATE TEMPORARY TRIGGER IF NOT EXISTS _logger_notification_${table}__delete AFTER DELETE ON ${table} BEGIN SELECT _logger('${table}', 'delete', OLD.rowid); END` + ); + } + return () => {}; + } +} diff --git a/packages/better-sqlite3-driver/src/worker.ts b/packages/better-sqlite3-driver/src/worker.ts new file mode 100644 index 0000000..c85d5ae --- /dev/null +++ b/packages/better-sqlite3-driver/src/worker.ts @@ -0,0 +1,17 @@ +import { BetterSqliteDriverOptions } from './driver.js'; +import { BetterSqliteConnection } from './sync-driver.js'; +import { + retriedOpen, + setupDriverWorker, + WorkerDriverConnectionOptions +} from '@sqlite-js/driver/worker_threads/setup'; + +setupDriverWorker({ + async openConnection( + options: WorkerDriverConnectionOptions & BetterSqliteDriverOptions + ) { + return retriedOpen(() => { + return BetterSqliteConnection.open(options.path, options); + }, 2_000); + } +}); diff --git a/packages/better-sqlite3-driver/test/src/better-sqlite3-async.test.ts b/packages/better-sqlite3-driver/test/src/better-sqlite3-async.test.ts new file mode 100644 index 0000000..d08bab3 --- /dev/null +++ b/packages/better-sqlite3-driver/test/src/better-sqlite3-async.test.ts @@ -0,0 +1,8 @@ +import { describeDriverTests } from '@sqlite-js/driver-tests'; +import { BetterSqliteDriver } from '../../lib/index.js'; + +describeDriverTests( + 'better-sqlite3-async-pool', + { getColumns: true, rawResults: true, allowsMissingParameters: false }, + (path) => BetterSqliteDriver.open(path) +); diff --git a/packages/better-sqlite3-driver/test/src/better-sqlite3.test.ts b/packages/better-sqlite3-driver/test/src/better-sqlite3.test.ts new file mode 100644 index 0000000..d9d5e91 --- /dev/null +++ b/packages/better-sqlite3-driver/test/src/better-sqlite3.test.ts @@ -0,0 +1,8 @@ +import { BetterSqliteDriver } from '../../lib/index.js'; +import { describeDriverTests } from '@sqlite-js/driver-tests'; + +describeDriverTests( + 'better-sqlite3', + { getColumns: true, rawResults: true, allowsMissingParameters: false }, + (path) => BetterSqliteDriver.openInProcess(path) +); diff --git a/packages/better-sqlite3-driver/test/tsconfig.json b/packages/better-sqlite3-driver/test/tsconfig.json new file mode 100644 index 0000000..42305e6 --- /dev/null +++ b/packages/better-sqlite3-driver/test/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2022", "DOM"], + "noEmit": true + }, + "include": ["src"], + "references": [{ "path": "../" }] +} diff --git a/packages/better-sqlite3-driver/tsconfig.json b/packages/better-sqlite3-driver/tsconfig.json new file mode 100644 index 0000000..058feba --- /dev/null +++ b/packages/better-sqlite3-driver/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "lib", + "rootDir": "src" + }, + "include": ["src"], + "references": [{ "path": "../driver" }, { "path": "../driver-tests" }] +} diff --git a/packages/better-sqlite3-driver/vitest.config.ts b/packages/better-sqlite3-driver/vitest.config.ts new file mode 100644 index 0000000..8932301 --- /dev/null +++ b/packages/better-sqlite3-driver/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + esbuild: { target: 'es2022' }, + test: { + environment: 'node', + include: ['test/src/**/*.test.ts'] + } +}); diff --git a/packages/driver-tests/.mocharc.cjs b/packages/driver-tests/.mocharc.cjs new file mode 100644 index 0000000..4e6f417 --- /dev/null +++ b/packages/driver-tests/.mocharc.cjs @@ -0,0 +1,4 @@ +module.exports = { + extension: ['js'], + spec: ['test/lib/*.test.js'] +}; diff --git a/packages/driver-tests/README.md b/packages/driver-tests/README.md new file mode 100644 index 0000000..8273fa5 --- /dev/null +++ b/packages/driver-tests/README.md @@ -0,0 +1,5 @@ +# @sqlite-js/driver-tests + +This package contains a standard test suite to test any driver. + +The tests can run under vitest or mocha. diff --git a/packages/driver-tests/package.json b/packages/driver-tests/package.json new file mode 100644 index 0000000..e5dedd9 --- /dev/null +++ b/packages/driver-tests/package.json @@ -0,0 +1,28 @@ +{ + "name": "@sqlite-js/driver-tests", + "version": "0.0.1", + "description": "", + "main": "index.js", + "type": "module", + "exports": { + ".": "./lib/index.js", + "./test": "./lib/test.js" + }, + "scripts": { + "build": "tsc -b", + "clean": "tsc -b --clean && tsc -b ./test/tsconfig.json --clean && rm -rf lib test/lib", + "test": "tsc -b ./test/tsconfig.json && NODE_OPTIONS=\"--experimental-sqlite --disable-warning=ExperimentalWarning\" mocha" + }, + "keywords": [], + "author": "", + "license": "MIT", + "dependencies": { + "vitest": "^2.0.5", + "mocha": "^10.7.3", + "@sqlite-js/driver": "workspace:^" + }, + "devDependencies": { + "@types/node": "^22.3.0", + "typescript": "^5.5.4" + } +} diff --git a/packages/driver-tests/src/driver-tests.ts b/packages/driver-tests/src/driver-tests.ts new file mode 100644 index 0000000..6e185e5 --- /dev/null +++ b/packages/driver-tests/src/driver-tests.ts @@ -0,0 +1,322 @@ +import * as fs from 'node:fs/promises'; +import * as path from 'node:path'; +import { beforeEach, describe, test } from './test.js'; +import { expect } from 'expect'; +import { SqliteDriverConnectionPool } from '@sqlite-js/driver'; + +export interface DriverFeatures { + getColumns: boolean; + rawResults: boolean; + allowsMissingParameters: boolean; +} + +export function describeDriverTests( + name: string, + features: DriverFeatures, + factory: (path: string) => SqliteDriverConnectionPool +) { + describe(`${name} - driver tests`, () => { + let dbPath: string; + + const open = async () => { + const dir = path.dirname(dbPath); + try { + await fs.mkdir(dir); + } catch (e) {} + try { + await fs.rm(dbPath); + } catch (e) {} + const db = factory(dbPath); + return db; + }; + + beforeEach((context) => { + const testNameSanitized = context.fullName.replaceAll( + /[\s\/\\>\.\-\:]+/g, + '_' + ); + dbPath = `test-db/${testNameSanitized}.db`; + }); + + test.skipIf(!features.rawResults)('basic select - raw', async () => { + await using driver = await open(); + await using connection = await driver.reserveConnection(); + using s = connection.prepare('select 1 as one', { + rawResults: true + }); + const { rows } = await s.step(); + + expect(rows).toEqual([[1]]); + + if (features.getColumns) { + const columns = await s.getColumns(); + expect(columns).toEqual(['one']); + } + }); + + test('basic select - object', async () => { + await using driver = await open(); + await using connection = await driver.reserveConnection(); + using s = connection.prepare('select 1 as one'); + const { rows } = await s.step(); + expect(rows).toEqual([{ one: 1 }]); + }); + + test('big number', async () => { + await using driver = await open(); + await using connection = await driver.reserveConnection(); + using s = connection.prepare('select 9223372036854775807 as bignumber'); + const { rows } = await s.step(); + + expect(rows).toEqual([{ bignumber: 9223372036854776000 }]); + + using s2 = connection.prepare('select ? as bignumber'); + s2.bind([9223372036854775807n]); + const { rows: rows2 } = await s2.step(); + + expect(rows2).toEqual([{ bignumber: 9223372036854776000 }]); + }); + + test('bigint', async () => { + await using driver = await open(); + await using connection = await driver.reserveConnection(); + using s = connection.prepare('select ? as bignumber', { bigint: true }); + s.bind([9223372036854775807n]); + const { rows: rows1 } = await s.step(); + expect(rows1).toEqual([{ bignumber: 9223372036854775807n }]); + + using s2 = connection.prepare('select 9223372036854775807 as bignumber', { + bigint: true + }); + const { rows: rows2 } = await s2.step(); + expect(rows2).toEqual([{ bignumber: 9223372036854775807n }]); + }); + + test('insert returning', async () => { + await using driver = await open(); + await using connection = await driver.reserveConnection(); + using s1 = connection.prepare( + 'create table test_data(id integer primary key, data text)' + ); + await s1.step(); + using s2 = connection.prepare( + 'insert into test_data(data) values(123) returning id' + ); + const { rows } = await s2.step(); + + expect(rows).toEqual([{ id: 1 }]); + + expect(await connection.connection.getLastChanges()).toEqual({ + changes: 1, + lastInsertRowId: 1n + }); + + if (features.getColumns) { + const columns = await s2.getColumns(); + expect(columns).toEqual(['id']); + } + }); + + test('bind named args', async () => { + await using driver = await open(); + await using connection = await driver.reserveConnection(); + using s = connection.prepare('select :one as one, :two as two'); + s.bind({ one: 1, two: 2 }); + const { rows } = await s.step(); + expect(rows).toEqual([{ one: 1, two: 2 }]); + }); + + test('bind named args - explicit names', async () => { + await using driver = await open(); + await using connection = await driver.reserveConnection(); + using s = connection.prepare('select $one as one, $two as two'); + s.bind({ $one: 1, $two: 2 }); + const { rows } = await s.step(); + expect(rows).toEqual([{ one: 1, two: 2 }]); + }); + + test.skipIf(!features.allowsMissingParameters)( + 'skip named arg', + async () => { + await using driver = await open(); + await using connection = await driver.reserveConnection(); + using s = connection.prepare('select :one as one, :two as two'); + s.bind({ two: 2 }); + + const { rows } = await s.step(); + expect(rows).toEqual([{ one: null, two: 2 }]); + } + ); + + test('rebind arg', async () => { + await using driver = await open(); + await using connection = await driver.reserveConnection(); + using s = connection.prepare('select :one as one, :two as two'); + s.bind({ one: 1, two: 2 }); + s.bind({ one: 11, two: 22 }); + const { rows } = await s.step(); + expect(rows).toEqual([{ one: 11, two: 22 }]); + }); + + test('partial rebind', async () => { + await using driver = await open(); + await using connection = await driver.reserveConnection(); + using s = connection.prepare('select :one as one, :two as two'); + s.bind({ one: 1, two: 2 }); + s.bind({ two: 22 }); + const { rows } = await s.step(); + expect(rows).toEqual([{ one: 1, two: 22 }]); + }); + + test('positional parameters', async () => { + await using driver = await open(); + await using connection = await driver.reserveConnection(); + using s = connection.prepare('select ? as one, ? as two'); + s.bind([1, 2]); + const { rows } = await s.step(); + expect(rows).toEqual([{ one: 1, two: 2 }]); + }); + + test('positional specific parameters', async () => { + await using driver = await open(); + await using connection = await driver.reserveConnection(); + using s = connection.prepare('select ?2 as two, ?1 as one'); + s.bind({ '1': 1, '2': 2 }); + const { rows } = await s.step(); + expect(rows).toEqual([{ two: 2, one: 1 }]); + }); + + test('positional parameters partial rebind', async () => { + await using driver = await open(); + await using connection = await driver.reserveConnection(); + using s = connection.prepare('select ? as one, ? as two'); + s.bind([1, 2]); + s.bind([undefined, 22]); + const { rows } = await s.step(); + expect(rows).toEqual([{ one: 1, two: 22 }]); + }); + + test('named and positional parameters', async () => { + await using driver = await open(); + await using connection = await driver.reserveConnection(); + using s = connection.prepare( + 'select ? as one, @three as three, ? as two' + ); + s.bind([1, 2]); + s.bind({ three: 3 }); + const { rows } = await s.step(); + expect(rows).toEqual([{ one: 1, three: 3, two: 2 }]); + }); + + test('reset parameters', async () => { + await using driver = await open(); + await using connection = await driver.reserveConnection(); + using s = connection.prepare('select ? as one, ? as two'); + s.bind([1, 2]); + const { rows: rows1 } = await s.step(); + s.reset(); + const { rows: rows2 } = await s.step(); + s.reset({ clearBindings: true }); + + expect(rows1).toEqual([{ one: 1, two: 2 }]); + expect(rows2).toEqual([{ one: 1, two: 2 }]); + + if (features.allowsMissingParameters) { + const { rows: rows3 } = await s.step(); + expect(rows3).toEqual([{ one: null, two: null }]); + } + }); + + test('partial reset', async () => { + await using driver = await open(); + await using connection = await driver.reserveConnection(); + using s = connection.prepare( + "select json_each.value as v from json_each('[1,2,3,4,5]')" + ); + const { rows: rows1 } = await s.step(3); + s.reset(); + const { rows: rows2 } = await s.step(3); + const { rows: rows3 } = await s.step(3); + const { rows: rows4 } = await s.step(3); + s.reset(); + const { rows: rows5 } = await s.step(); + + expect(rows1).toEqual([{ v: 1 }, { v: 2 }, { v: 3 }]); + expect(rows2).toEqual([{ v: 1 }, { v: 2 }, { v: 3 }]); + expect(rows3).toEqual([{ v: 4 }, { v: 5 }]); + expect(rows4).toBe(undefined); + expect(rows5).toEqual([{ v: 1 }, { v: 2 }, { v: 3 }, { v: 4 }, { v: 5 }]); + }); + + test('multiple insert step', async () => { + await using driver = await open(); + await using connection = await driver.reserveConnection(); + + using s1 = connection.prepare( + 'create table test_data(id integer primary key, data text)' + ); + await s1.step(); + using s2 = connection.prepare( + "insert into test_data(data) values('test')" + ); + const { rows: rows1 } = await s2.step(); + const { rows: rows2 } = await s2.step(); + s2.reset(); + const { rows: rows3 } = await s2.step(); + using s3 = connection.prepare('select count(*) as count from test_data'); + const { rows: rows4 } = await s3.step(); + + expect(rows1).toEqual([]); + expect(rows2).toBe(undefined); + expect(rows3).toEqual([]); + expect(rows4).toEqual([{ count: 2 }]); + }); + + test('error handling - prepare', async () => { + await using driver = await open(); + await using connection = await driver.reserveConnection(); + using s = connection.prepare('select foobar'); + expect(await s.getColumns().catch((e) => e)).toMatchObject({ + code: 'SQLITE_ERROR', + message: 'no such column: foobar' + }); + expect(await s.step().catch((e) => e)).toMatchObject({ + code: 'SQLITE_ERROR', + message: 'no such column: foobar' + }); + }); + + test('error handling - step', async () => { + await using driver = await open(); + await using connection = await driver.reserveConnection(); + using s = connection.prepare( + "select json_each.value from json_each('test')" + ); + if (features.getColumns) { + expect(await s.getColumns()).toEqual(['value']); + } + expect(await s.step().catch((e) => e)).toMatchObject({ + code: 'SQLITE_ERROR', + message: 'malformed JSON' + }); + }); + + test.skip('onUpdate', async () => { + // Skipped: Not properly implemented yet. + + await using driver = await open(); + await using connection = await driver.reserveConnection(); + // await connection.run( + // "create table test_data(id integer primary key, data text)" + // ); + // // TODO: test the results + // connection.onUpdate(({ events }) => { + // console.log("update", events); + // }); + // await connection.run( + // "insert into test_data(data) values(123) returning id" + // ); + // await connection.run("update test_data set data = data || 'test'"); + }); + }); +} diff --git a/packages/driver-tests/src/index.ts b/packages/driver-tests/src/index.ts new file mode 100644 index 0000000..47a51c7 --- /dev/null +++ b/packages/driver-tests/src/index.ts @@ -0,0 +1 @@ +export * from './driver-tests.js'; diff --git a/packages/driver-tests/src/setup-mocha.ts b/packages/driver-tests/src/setup-mocha.ts new file mode 100644 index 0000000..94f9bc8 --- /dev/null +++ b/packages/driver-tests/src/setup-mocha.ts @@ -0,0 +1,20 @@ +import type { TestContext } from './test.js'; + +import { beforeEach as originalBeforeEach } from 'mocha'; +export { describe, test } from 'mocha'; +import { describe, test } from 'mocha'; + +export function beforeEach(callback: (context: TestContext) => any) { + originalBeforeEach(function () { + const testName = this.currentTest!.fullTitle(); + return callback({ fullName: testName }); + }); +} + +(test as any).skipIf = function (condition: boolean) { + if (condition) { + return test.skip; + } else { + return test; + } +}; diff --git a/packages/driver-tests/src/setup-vitest.ts b/packages/driver-tests/src/setup-vitest.ts new file mode 100644 index 0000000..e2119b8 --- /dev/null +++ b/packages/driver-tests/src/setup-vitest.ts @@ -0,0 +1,12 @@ +import type { TestContext } from './test.js'; + +import { beforeEach as originalBeforeEach } from 'vitest'; + +export { describe, test } from 'vitest'; + +export function beforeEach(callback: (context: TestContext) => any) { + originalBeforeEach(({ expect }) => { + const testName = expect.getState().currentTestName!; + return callback({ fullName: testName }); + }); +} diff --git a/packages/driver-tests/src/test.ts b/packages/driver-tests/src/test.ts new file mode 100644 index 0000000..c929f99 --- /dev/null +++ b/packages/driver-tests/src/test.ts @@ -0,0 +1,30 @@ +// A lib to allow testing with vitest or mocha +import type { test as testType, describe as describeType } from 'vitest'; + +export interface TestContext { + fullName: string; +} + +export const isVitest = process.env.VITEST == 'true'; +export const isMocha = !isVitest; + +let testImpl, describeImpl, beforeEachImpl; + +if (isMocha) { + const { test, describe, beforeEach } = await import('./setup-mocha.js'); + testImpl = test; + describeImpl = describe; + beforeEachImpl = beforeEach; +} else { + const { test, describe, beforeEach } = await import('./setup-vitest.js'); + testImpl = test; + describeImpl = describe; + beforeEachImpl = beforeEach; +} + +export function beforeEach(callback: (context: TestContext) => any) { + return beforeEachImpl!(callback); +} + +export const test = testImpl as typeof testType; +export const describe = describeImpl as typeof describeType; diff --git a/packages/driver-tests/test/src/node-sqlite-async.test.ts b/packages/driver-tests/test/src/node-sqlite-async.test.ts new file mode 100644 index 0000000..448a648 --- /dev/null +++ b/packages/driver-tests/test/src/node-sqlite-async.test.ts @@ -0,0 +1,8 @@ +import { NodeSqliteDriver } from '@sqlite-js/driver/node'; +import { describeDriverTests } from '../../lib/index.js'; + +describeDriverTests( + 'node:sqlite worker', + { getColumns: false, rawResults: false, allowsMissingParameters: true }, + (path) => NodeSqliteDriver.open(path) +); diff --git a/packages/driver-tests/test/src/node-sqlite-sync.test.ts b/packages/driver-tests/test/src/node-sqlite-sync.test.ts new file mode 100644 index 0000000..f305b2a --- /dev/null +++ b/packages/driver-tests/test/src/node-sqlite-sync.test.ts @@ -0,0 +1,14 @@ +import { NodeSqliteDriver } from '@sqlite-js/driver/node'; +import { describeDriverTests } from '../../lib/index.js'; + +import { isMocha, test } from '../../lib/test.js'; + +if (isMocha) { + describeDriverTests( + 'node:sqlite direct', + { getColumns: false, rawResults: false, allowsMissingParameters: true }, + (path) => NodeSqliteDriver.openInProcess(path) + ); +} else { + test.skip('only running in mocha'); +} diff --git a/packages/driver-tests/test/tsconfig.json b/packages/driver-tests/test/tsconfig.json new file mode 100644 index 0000000..2736d1c --- /dev/null +++ b/packages/driver-tests/test/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2022", "DOM"], + "outDir": "lib", + "rootDir": "src" + }, + "include": ["src"], + "references": [{ "path": "../" }] +} diff --git a/packages/driver-tests/tsconfig.json b/packages/driver-tests/tsconfig.json new file mode 100644 index 0000000..1c95501 --- /dev/null +++ b/packages/driver-tests/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2022", "DOM"], + "outDir": "lib", + "rootDir": "src" + }, + "include": ["src"], + "references": [{ "path": "../driver" }] +} diff --git a/packages/driver-tests/vitest.config.ts b/packages/driver-tests/vitest.config.ts new file mode 100644 index 0000000..8932301 --- /dev/null +++ b/packages/driver-tests/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + esbuild: { target: 'es2022' }, + test: { + environment: 'node', + include: ['test/src/**/*.test.ts'] + } +}); diff --git a/packages/driver/README.md b/packages/driver/README.md new file mode 100644 index 0000000..8194fe6 --- /dev/null +++ b/packages/driver/README.md @@ -0,0 +1,35 @@ +# @sqlite-js/driver + +This contains a driver API, as well as driver utilities. + +## @sqlite-js/driver + +The main export contains the driver API. The API is centered around connection pooling and prepared statements. + +## @sqlite-js/driver/node + +This is a driver implementation for NodeJS based on the experimental `node:sqlite` package. + +There are two main options for using the driver: + +```ts +const driver = NodeSqliteDriver.open(path); +``` + +This opens a connection pool using worker_threads, giving asynchronous I/O. + +```ts +const driver = NodeSqliteDriver.openInProcess(path); +``` + +This opens a single in-process connection, with blocking I/O. This can give lower latency and higher throughput, at the cost of blocking the process and not supporting concurrent operations. + +## @sqlite-js/driver/util + +This contains utilities for driver implementations, such as connection pooling. + +## @sqlite-js/driver/worker_threads + +This contains utilities for running a driver implementation in a NodeJS worker_thread, to get non-blocking I/O. + +The APIs here are not stable, and expected to change. diff --git a/packages/driver/package.json b/packages/driver/package.json new file mode 100644 index 0000000..ca43081 --- /dev/null +++ b/packages/driver/package.json @@ -0,0 +1,26 @@ +{ + "name": "@sqlite-js/driver", + "version": "0.0.1", + "description": "", + "main": "index.js", + "type": "module", + "scripts": { + "build": "tsc -b", + "clean": "tsc -b --clean && rm -rf lib" + }, + "exports": { + ".": "./lib/index.js", + "./worker_threads": "./lib/worker_threads/index.js", + "./worker_threads/setup": "./lib/worker_threads/setup.js", + "./util": "./lib/util/index.js", + "./node": "./lib/node/index.js" + }, + "keywords": [], + "author": "", + "license": "MIT", + "dependencies": {}, + "devDependencies": { + "@types/node": "^22.3.0", + "typescript": "^5.5.4" + } +} diff --git a/packages/driver/src/deferred.ts b/packages/driver/src/deferred.ts new file mode 100644 index 0000000..2508399 --- /dev/null +++ b/packages/driver/src/deferred.ts @@ -0,0 +1,12 @@ +export class Deferred { + promise: Promise; + resolve: (result: T) => void = undefined as any; + reject: (error: any) => void = undefined as any; + + constructor() { + this.promise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); + } +} diff --git a/packages/driver/src/driver-api.ts b/packages/driver/src/driver-api.ts new file mode 100644 index 0000000..15e2266 --- /dev/null +++ b/packages/driver/src/driver-api.ts @@ -0,0 +1,135 @@ +export type SqliteValue = null | string | number | bigint | Uint8Array; +export type SqliteArguments = + | SqliteValue[] + | Record + | null + | undefined; + +export type SqliteRowRaw = SqliteValue[]; +export type SqliteRowObject = Record; +export type SqliteRow = SqliteRowRaw | SqliteRowObject; + +export interface PrepareOptions { + bigint?: boolean; + rawResults?: boolean; + persist?: boolean; +} + +export interface ResetOptions { + clearBindings?: boolean; +} + +export interface SqliteDriverConnection { + /** + * Prepare a statement. + * + * Does not return any errors. + */ + prepare(sql: string, options?: PrepareOptions): SqliteDriverStatement; + + onUpdate( + listener: UpdateListener, + options?: { tables?: string[]; batchLimit?: number } + ): () => void; + + getLastChanges(): Promise; + + close(): Promise; +} + +export type SqliteParameterBinding = + | (SqliteValue | undefined)[] + | Record + | null + | undefined; + +export interface SqliteStepResult { + rows?: SqliteRow[]; + done?: boolean; +} + +export interface SqliteDriverStatement { + getColumns(): Promise; + + bind(parameters: SqliteParameterBinding): void; + step(n?: number, options?: StepOptions): Promise; + finalize(): void; + reset(options?: ResetOptions): void; + + /** + * Similar to step, followed by reset, and returning number of changed rows. + * + * Avoids the need to use a separate statement to get changes. + */ + run(options?: StepOptions): Promise; + + [Symbol.dispose](): void; +} + +export interface StepOptions { + requireTransaction?: boolean; +} + +export interface SqliteDriverConnectionPool { + /** + * Reserve a connection for exclusive use. + * + * If there is no available connection, this will wait until one is available. + * @param options + */ + reserveConnection( + options?: ReserveConnectionOptions + ): Promise; + + close(): Promise; + + [Symbol.asyncDispose](): Promise; + + onUpdate( + listener: UpdateListener, + options?: { tables?: string[]; batchLimit?: number } + ): () => void; +} + +export type UpdateListener = (event: BatchedUpdateEvent) => void; + +export interface BatchedUpdateEvent { + events: UpdateEvent[]; +} + +export interface UpdateEvent { + table: string; + type: 'insert' | 'update' | 'delete'; + rowId: bigint; +} + +export interface ReservedConnection { + /** Direct handle to the underlying connection. */ + connection: SqliteDriverConnection; + + /** Proxied to the underlying connection */ + prepare(sql: string, options?: PrepareOptions): SqliteDriverStatement; + + release(): Promise; + [Symbol.asyncDispose](): Promise; +} + +export interface ReserveConnectionOptions { + readonly?: boolean; + signal?: AbortSignal; +} + +export interface SqliteChanges { + changes: number; + lastInsertRowId: bigint; +} + +export interface ResultSet { + columns: string[]; + rows: SqliteValue[][]; +} + +export interface ExecuteOptions { + chunkSize?: number; + bigint?: boolean; +} diff --git a/packages/driver/src/index.ts b/packages/driver/src/index.ts new file mode 100644 index 0000000..ca796bb --- /dev/null +++ b/packages/driver/src/index.ts @@ -0,0 +1,2 @@ +export * from './driver-api.js'; +export * from './sqlite-error.js'; diff --git a/packages/driver/src/node/driver.ts b/packages/driver/src/node/driver.ts new file mode 100644 index 0000000..8b57490 --- /dev/null +++ b/packages/driver/src/node/driver.ts @@ -0,0 +1,92 @@ +import { + ReserveConnectionOptions, + ReservedConnection, + SqliteDriverConnectionPool, + UpdateListener +} from '../driver-api.js'; +import { ConnectionPoolOptions } from '../util/connection-pools.js'; +import { LazyConnectionPool } from '../util/LazyConnectionPool.js'; +import { ReadWriteConnectionPool } from '../util/ReadWriteConnectionPool.js'; +import { WorkerDriverConnection } from '../worker_threads/worker-driver.js'; +import { NodeSqliteConnection } from './impl.js'; +import { loadNodeSqlite } from './node-sqlite.js'; + +export interface NodeSqliteDriverOptions extends ConnectionPoolOptions { + /** + * Specify a custom path to a worker script, to customize the loading process. + */ + workerPath?: string | URL; +} + +export class NodeSqliteDriver implements SqliteDriverConnectionPool { + /** + * Opens a single in-process connection. + * + * Uses blocking I/O. + * + * This requires `NODE_OPTIONS=--experimental-sqlite`. + */ + static openInProcess(path: string): NodeSqliteDriver { + const connection = new LazyConnectionPool(async () => { + const sqlite = await loadNodeSqlite(); + const db = new sqlite.DatabaseSync(path); + return new NodeSqliteConnection(db); + }); + return new NodeSqliteDriver(connection); + } + + /** + * Opens a connection pool with non-blocking I/O using worker_threads. + */ + static open( + path: string, + options?: NodeSqliteDriverOptions + ): NodeSqliteDriver { + const workerPath = + options?.workerPath ?? new URL('./worker.js', import.meta.url); + + const connection = new ReadWriteConnectionPool( + { + openConnection: async (connectionOptions) => { + return new WorkerDriverConnection(workerPath, { + path, + readonly: connectionOptions?.readonly ?? false, + connectionName: connectionOptions?.connectionName, + workerOptions: { + env: { + ...process.env, + NODE_OPTIONS: + '--experimental-sqlite --disable-warning=ExperimentalWarning' + } + } + }); + } + }, + options + ); + return new NodeSqliteDriver(connection); + } + + private constructor(private connection: SqliteDriverConnectionPool) {} + + reserveConnection( + options?: ReserveConnectionOptions + ): Promise { + return this.connection.reserveConnection(options); + } + + close(): Promise { + return this.connection.close(); + } + + onUpdate( + listener: UpdateListener, + options?: { tables?: string[]; batchLimit?: number } + ): () => void { + return this.connection.onUpdate(listener, options); + } + + [Symbol.asyncDispose](): Promise { + return this.connection[Symbol.asyncDispose](); + } +} diff --git a/packages/driver/src/node/impl.ts b/packages/driver/src/node/impl.ts new file mode 100644 index 0000000..fc457f1 --- /dev/null +++ b/packages/driver/src/node/impl.ts @@ -0,0 +1,245 @@ +import type * as sqlite from './node-sqlite.js'; + +import { + PrepareOptions, + ResetOptions, + SqliteChanges, + SqliteDriverConnection, + SqliteDriverConnectionPool, + SqliteDriverStatement, + SqliteParameterBinding, + SqliteRow, + SqliteStepResult, + SqliteValue, + StepOptions, + UpdateListener +} from '../driver-api.js'; + +import { + ErrorStatement, + mapError, + ReadWriteConnectionPool +} from '../util/index.js'; +import { loadNodeSqlite } from './node-sqlite.js'; + +export function nodeSqlitePool(path: string): SqliteDriverConnectionPool { + return new ReadWriteConnectionPool({ + async openConnection(options) { + const sqlite = await loadNodeSqlite(); + const db = new sqlite.DatabaseSync(path); + return new NodeSqliteConnection(db, { + readonly: options?.readonly, + name: options?.connectionName + }); + } + }); +} + +interface InternalStatement extends SqliteDriverStatement { + readonly source: string; + + readonly persisted: boolean; +} + +class NodeSqliteSyncStatement implements InternalStatement { + public statement: sqlite.StatementSync; + private options: PrepareOptions; + private bindPositional: SqliteValue[] = []; + private bindNamed: Record = {}; + private statementDone = false; + private iterator: Iterator | undefined = undefined; + + readonly persisted: boolean; + + [Symbol.dispose]: () => void = undefined as any; + + constructor(statement: sqlite.StatementSync, options: PrepareOptions) { + this.statement = statement; + this.options = options; + this.persisted = options.persist ?? false; + + if (typeof Symbol.dispose != 'undefined') { + this[Symbol.dispose] = () => this.finalize(); + } + } + + get source() { + return this.statement.sourceSQL(); + } + + async getColumns(): Promise { + // Not supported + return []; + } + + bind(parameters: SqliteParameterBinding): void { + if (parameters == null) { + return; + } + if (Array.isArray(parameters)) { + let bindArray = this.bindPositional; + + for (let i = 0; i < parameters.length; i++) { + if (typeof parameters[i] != 'undefined') { + bindArray[i] = parameters[i]!; + } + } + } else { + let previous = this.bindNamed; + this.bindNamed = { ...previous, ...parameters }; + } + } + + async run(options?: StepOptions): Promise { + try { + if (options?.requireTransaction) { + // TODO: Implement + } + + const statement = this.statement; + this.reset(); + + try { + const bindNamed = this.bindNamed; + const bindPositional = this.bindPositional; + + statement.setReadBigInts(true); + const r = statement.run(bindNamed, ...bindPositional); + return { + changes: Number(r.changes), + lastInsertRowId: r.lastInsertRowid as bigint + }; + } finally { + this.reset(); + } + } catch (e) { + throw mapError(e); + } + } + + async step(n?: number, options?: StepOptions): Promise { + try { + const all = n == null; + + const statement = this.statement; + if (this.statementDone) { + return { done: true }; + } + + if (options?.requireTransaction) { + // TODO: implement + } + + const bindNamed = this.bindNamed; + const bindPositional = this.bindPositional; + + let iterator = this.iterator; + const num_rows = n ?? 1; + if (iterator == null) { + if (this.options.rawResults) { + // Not supported + } + if (this.options.bigint) { + statement.setReadBigInts(true); + } + iterator = statement + .all(bindNamed, ...bindPositional) + [Symbol.iterator](); + this.iterator = iterator; + } + let rows: SqliteRow[] = []; + let isDone = false; + for (let i = 0; i < num_rows || all; i++) { + const { value, done } = iterator.next(); + if (done) { + isDone = true; + break; + } + rows.push(value as SqliteRow); + } + if (isDone) { + this.statementDone = true; + } + return { rows, done: isDone }; + } catch (e) { + throw mapError(e); + } + } + + finalize(): void { + const existingIter = this.iterator; + if (existingIter != null) { + existingIter.return?.(); + } + this.iterator = undefined; + this.statementDone = false; + } + + reset(options?: ResetOptions): void { + if (this.iterator) { + const iter = this.iterator; + iter.return?.(); + this.iterator = undefined; + } + if (options?.clearBindings) { + this.bindNamed = {}; + this.bindPositional = []; + } + this.statementDone = false; + } +} + +export class NodeSqliteConnection implements SqliteDriverConnection { + con: sqlite.DatabaseSync; + name: string; + + changeStatement: sqlite.StatementSync; + + constructor( + db: sqlite.DatabaseSync, + options?: { readonly?: boolean; name?: string } + ) { + this.con = db; + this.con.exec('PRAGMA journal_mode = WAL'); + this.con.exec('PRAGMA synchronous = normal'); + this.con.exec('PRAGMA busy_timeout = 5000'); + if (options?.readonly) { + this.con.exec('PRAGMA query_only = true'); + } + this.name = options?.name ?? ''; + this.changeStatement = this.con.prepare( + 'select last_insert_rowid() as l, changes() as c' + ); + this.changeStatement.setReadBigInts(true); + } + + async getLastChanges(): Promise { + const r = this.changeStatement.get() as any; + return { + lastInsertRowId: r!.l, + changes: Number(r!.c) + }; + } + + async close() { + this.con.close(); + } + + prepare(sql: string, options?: PrepareOptions): InternalStatement { + try { + const statement = this.con.prepare(sql); + return new NodeSqliteSyncStatement(statement, options ?? {}); + } catch (e) { + return new ErrorStatement(sql, mapError(e), options ?? {}); + } + } + + onUpdate( + listener: UpdateListener, + options?: + | { tables?: string[] | undefined; batchLimit?: number | undefined } + | undefined + ): () => void { + throw new Error('not supported yet'); + } +} diff --git a/packages/driver/src/node/index.ts b/packages/driver/src/node/index.ts new file mode 100644 index 0000000..64baa0d --- /dev/null +++ b/packages/driver/src/node/index.ts @@ -0,0 +1 @@ +export * from './driver.js'; diff --git a/packages/driver/src/node/node-sqlite.ts b/packages/driver/src/node/node-sqlite.ts new file mode 100644 index 0000000..2b58d77 --- /dev/null +++ b/packages/driver/src/node/node-sqlite.ts @@ -0,0 +1,50 @@ +export declare class DatabaseSync { + constructor(location: string, options?: { open?: boolean }); + + close(): void; + exec(sql: string): void; + open(): void; + prepare(sql: string): StatementSync; +} + +export declare class StatementSync { + all(namedParameters?: object, ...anonymousParameters: any[]): any[]; + expandedSQL(): string; + get(...anonymousParameters: any[]): object | undefined; + run(...anonymousParameters: any[]): { + changes: number | bigint; + lastInsertRowid: number | bigint; + }; + setAllowBareNamedParameters(enabled: boolean): void; + setReadBigInts(enabled: boolean): void; + sourceSQL(): string; +} + +export async function loadNodeSqlite() { + try { + return await import('node:sqlite' as any); + } catch (e: any) { + if (!isNodeVersionAtLeast('22.5.0')) { + throw new Error(`${e.message}\nNode >= 22.5.0 is required`); + } + if (!process.env.NODE_OPTIONS?.includes('--experimental-sqlite')) { + throw new Error(`${e.message}\nUse NODE_OPTIONS=--experimental-sqlite`); + } + throw e; + } +} + +function isNodeVersionAtLeast(requiredVersion: string) { + const currentVersion = process.version.slice(1).split('.').map(Number); + const [requiredMajor, requiredMinor, requiredPatch] = requiredVersion + .split('.') + .map(Number); + + return ( + currentVersion[0] > requiredMajor || + (currentVersion[0] === requiredMajor && + (currentVersion[1] > requiredMinor || + (currentVersion[1] === requiredMinor && + currentVersion[2] >= requiredPatch))) + ); +} diff --git a/packages/driver/src/node/worker.ts b/packages/driver/src/node/worker.ts new file mode 100644 index 0000000..cac206c --- /dev/null +++ b/packages/driver/src/node/worker.ts @@ -0,0 +1,15 @@ +import { NodeSqliteConnection } from './impl.js'; +import { retriedOpen, setupDriverWorker } from '../worker_threads/setup.js'; +import { loadNodeSqlite } from './node-sqlite.js'; + +setupDriverWorker({ + async openConnection(options) { + const sqlite = await loadNodeSqlite(); + return retriedOpen(() => { + return new NodeSqliteConnection(new sqlite.DatabaseSync(options.path), { + readonly: options.readonly, + name: options.connectionName + }); + }, 2_000); + } +}); diff --git a/packages/driver/src/sqlite-error.ts b/packages/driver/src/sqlite-error.ts new file mode 100644 index 0000000..6e0242e --- /dev/null +++ b/packages/driver/src/sqlite-error.ts @@ -0,0 +1,20 @@ +export interface SerializedDriverError { + message: string; + code: string; + stack?: string; +} + +export class SqliteError extends Error { + code: string; + cause: SerializedDriverError; + + constructor(cause: SerializedDriverError) { + super(cause.message); + this.code = cause.code; + this.cause = cause; + + if (cause.stack) { + this.stack = cause.stack; + } + } +} diff --git a/packages/driver/src/util/ErrorStatement.ts b/packages/driver/src/util/ErrorStatement.ts new file mode 100644 index 0000000..6f401d7 --- /dev/null +++ b/packages/driver/src/util/ErrorStatement.ts @@ -0,0 +1,58 @@ +import { + PrepareOptions, + ResetOptions, + SqliteChanges, + SqliteDriverStatement, + SqliteParameterBinding, + SqliteStepResult, + StepOptions +} from '../driver-api.js'; +import { SqliteDriverError } from '../worker_threads/async-commands.js'; + +/** + * Represents a statement that failed on prepare. + * + * Since the error can only be surfaced at step(), run() or getColumns(), + * this statement is just holds on to the error until one of those are called. + */ +export class ErrorStatement implements SqliteDriverStatement { + readonly error: SqliteDriverError; + readonly source: string; + readonly persisted: boolean; + + constructor( + source: string, + error: SqliteDriverError, + options: PrepareOptions + ) { + this.error = error; + this.source = source; + this.persisted = options.persist ?? false; + } + + async getColumns(): Promise { + throw this.error; + } + bind(parameters: SqliteParameterBinding): void { + // no-op + } + async step(n?: number, options?: StepOptions): Promise { + throw this.error; + } + + async run(options?: StepOptions): Promise { + throw this.error; + } + + finalize(): void { + // no-op + } + + reset(options?: ResetOptions): void { + // no-op + } + + [Symbol.dispose](): void { + // no-op + } +} diff --git a/packages/driver/src/util/LazyConnectionPool.ts b/packages/driver/src/util/LazyConnectionPool.ts new file mode 100644 index 0000000..7031374 --- /dev/null +++ b/packages/driver/src/util/LazyConnectionPool.ts @@ -0,0 +1,46 @@ +import { + ReserveConnectionOptions, + ReservedConnection, + SqliteDriverConnection, + SqliteDriverConnectionPool, + UpdateListener +} from '../driver-api.js'; +import { SingleConnectionPool } from './SingleConnectionPool.js'; + +/** + * Manages locks for a single connection, created asynchronously. + */ +export class LazyConnectionPool implements SqliteDriverConnectionPool { + private initPromise: Promise; + private connection?: SingleConnectionPool; + + [Symbol.asyncDispose]: () => Promise = undefined as any; + constructor(open: () => Promise) { + if (typeof Symbol.asyncDispose != 'undefined') { + this[Symbol.asyncDispose] = () => this.close(); + } + + this.initPromise = open().then((c) => { + this.connection = new SingleConnectionPool(c); + }); + } + + async reserveConnection( + options?: ReserveConnectionOptions + ): Promise { + await this.initPromise; + return this.connection!.reserveConnection(options); + } + + async close(): Promise { + await this.initPromise; + await this.connection!.close(); + } + + onUpdate( + listener: UpdateListener, + options?: { tables?: string[]; batchLimit?: number } + ): () => void { + throw new Error('Method not implemented.'); + } +} diff --git a/packages/driver/src/util/MultiConnectionPool.ts b/packages/driver/src/util/MultiConnectionPool.ts new file mode 100644 index 0000000..c67c738 --- /dev/null +++ b/packages/driver/src/util/MultiConnectionPool.ts @@ -0,0 +1,115 @@ +import { + SqliteDriverConnectionPool, + SqliteDriverConnection, + ReserveConnectionOptions, + ReservedConnection, + UpdateListener +} from '../driver-api.js'; +import { + QueuedPoolItem, + DriverFactory, + ConnectionPoolOptions, + ReservedConnectionImpl +} from './connection-pools.js'; + +/** + * A connection pool with multiple connections. + */ +export class MultiConnectionPool implements SqliteDriverConnectionPool { + private _allConnections = new Set(); + private _availableReadConnections: SqliteDriverConnection[] = []; + private _queue: QueuedPoolItem[] = []; + private _maxConnections: number; + + private options: ConnectionPoolOptions; + + [Symbol.asyncDispose]: () => Promise = undefined as any; + + constructor( + private factory: DriverFactory, + options?: ConnectionPoolOptions + ) { + if (typeof Symbol.asyncDispose != 'undefined') { + this[Symbol.asyncDispose] = () => this.close(); + } + this._maxConnections = options?.maxConnections ?? 2; + this.options = options ?? {}; + } + + reserveConnection( + options?: ReserveConnectionOptions | undefined + ): Promise { + const promise = new Promise((resolve, reject) => { + this._queue.push({ + resolve, + reject + }); + }); + + Promise.resolve().then(() => this.next()); + + return promise; + } + + private async expandPool( + options?: ReserveConnectionOptions + ): Promise { + const connection = await this.factory.openConnection({ + ...this.options, + ...options, + connectionName: `connection-${this._allConnections.size + 1}` + }); + this._allConnections.add(connection); + return connection; + } + + private async next() { + if (this._queue.length == 0) { + // Nothing queued + return; + } + + if ( + this._availableReadConnections.length == 0 && + this._allConnections.size >= this._maxConnections + ) { + // No connections available + return; + } + + const item = this._queue.shift()!; + + let connection: SqliteDriverConnection; + if (this._availableReadConnections.length == 0) { + // FIXME: prevent opening more than the max + connection = await this.expandPool(); + } else { + connection = this._availableReadConnections.shift()!; + } + + item.resolve( + new ReservedConnectionImpl(connection, async () => { + /// TODO: sync + this._availableReadConnections.push(connection); + Promise.resolve().then(() => this.next()); + }) + ); + } + + async close() { + // TODO: Wait for statements to finish + for (let con of this._allConnections) { + await con.close(); + } + } + + onUpdate( + listener: UpdateListener, + options?: + | { tables?: string[] | undefined; batchLimit?: number | undefined } + | undefined + ): () => void { + // No-op + return () => {}; + } +} diff --git a/packages/driver/src/util/ReadWriteConnectionPool.ts b/packages/driver/src/util/ReadWriteConnectionPool.ts new file mode 100644 index 0000000..b1729a6 --- /dev/null +++ b/packages/driver/src/util/ReadWriteConnectionPool.ts @@ -0,0 +1,60 @@ +import { + SqliteDriverConnectionPool, + ReserveConnectionOptions, + ReservedConnection, + UpdateListener +} from '../driver-api.js'; +import { DriverFactory, ConnectionPoolOptions } from './connection-pools.js'; +import { MultiConnectionPool } from './MultiConnectionPool.js'; +import { SingleConnectionPool } from './SingleConnectionPool.js'; + +/** + * A connection pool with a single write connection, and multiple read + * connections. + */ +export class ReadWriteConnectionPool implements SqliteDriverConnectionPool { + private writePool?: SqliteDriverConnectionPool; + private readPool: SqliteDriverConnectionPool; + + private initPromise: Promise; + [Symbol.asyncDispose]: () => Promise = undefined as any; + + constructor(factory: DriverFactory, options?: ConnectionPoolOptions) { + this.readPool = new MultiConnectionPool(factory, options); + this.initPromise = factory + .openConnection({ ...options, readonly: false, connectionName: 'writer' }) + .then((con) => { + this.writePool = new SingleConnectionPool(con); + }); + + if (typeof Symbol.asyncDispose != 'undefined') { + this[Symbol.asyncDispose] = () => this.close(); + } + } + + async reserveConnection( + options?: ReserveConnectionOptions + ): Promise { + await this.initPromise; + + if (options?.readonly) { + return this.readPool.reserveConnection(options); + } else { + return this.writePool!.reserveConnection(options); + } + } + + async close() { + await this.readPool.close(); + await this.writePool?.close(); + } + + onUpdate( + listener: UpdateListener, + options?: + | { tables?: string[] | undefined; batchLimit?: number | undefined } + | undefined + ): () => void { + return this.writePool!.onUpdate(listener, options); + } +} diff --git a/packages/driver/src/util/SingleConnectionPool.ts b/packages/driver/src/util/SingleConnectionPool.ts new file mode 100644 index 0000000..5eb78cc --- /dev/null +++ b/packages/driver/src/util/SingleConnectionPool.ts @@ -0,0 +1,99 @@ +import { + SqliteDriverConnectionPool, + ReservedConnection, + SqliteDriverConnection, + ReserveConnectionOptions, + UpdateListener +} from '../driver-api.js'; +import { QueuedItem, ReservedConnectionImpl } from './connection-pools.js'; + +/** + * Provides lock management for a single connection. + */ +export class SingleConnectionPool implements SqliteDriverConnectionPool { + private queue: QueuedItem[] = []; + private inUse: ReservedConnection | null = null; + + [Symbol.asyncDispose]: () => Promise = undefined as any; + + constructor(private connection: SqliteDriverConnection) { + if (typeof Symbol.asyncDispose != 'undefined') { + this[Symbol.asyncDispose] = () => this.close(); + } + } + + async close() { + await this.connection.close(); + } + + reserveConnection( + options?: ReserveConnectionOptions + ): Promise { + if (options?.signal?.aborted) { + throw new Error('Aborted'); + } + const reserved: ReservedConnection = new ReservedConnectionImpl( + this.connection, + async () => { + // TODO: sync + if (this.inUse === reserved) { + this.inUse = null; + Promise.resolve().then(() => this.next()); + } + } + ); + + if (this.inUse == null) { + this.inUse = reserved; + return Promise.resolve(reserved); + } else { + const promise = new Promise((resolve, reject) => { + const item: QueuedItem = { + reserved, + resolve, + reject + }; + this.queue.push(item); + options?.signal?.addEventListener( + 'abort', + () => { + item.reserved = null; + item.reject(new Error('Aborted')); + }, + { once: true } + ); + }); + + return promise.then((r) => { + this.inUse = reserved; + return r; + }); + } + } + + private next() { + while (true) { + const item = this.queue.shift(); + if (item == null) { + break; + } + + if (item.reserved == null) { + // Aborted + continue; + } + + item.resolve(item.reserved); + break; + } + } + + onUpdate( + listener: UpdateListener, + options?: + | { tables?: string[] | undefined; batchLimit?: number | undefined } + | undefined + ): () => void { + return this.connection.onUpdate(listener, options); + } +} diff --git a/packages/driver/src/util/connection-pools.ts b/packages/driver/src/util/connection-pools.ts new file mode 100644 index 0000000..c98d642 --- /dev/null +++ b/packages/driver/src/util/connection-pools.ts @@ -0,0 +1,60 @@ +import { + PrepareOptions, + ReserveConnectionOptions, + ReservedConnection, + SqliteDriverConnection, + SqliteDriverStatement, + UpdateListener +} from '../driver-api.js'; + +export interface QueuedItem { + reserved: ReservedConnection | null; + resolve: (reserved: ReservedConnection) => void; + reject: (err: any) => void; +} + +export interface DriverFactory { + openConnection( + options?: ReserveConnectionOptions & { connectionName?: string } + ): Promise; +} + +export interface QueuedPoolItem { + resolve: (reserved: ReservedConnection) => void; + reject: (err: any) => void; +} + +export class ReservedConnectionImpl implements ReservedConnection { + [Symbol.asyncDispose]: () => Promise = undefined as any; + + constructor( + public connection: SqliteDriverConnection, + public release: () => Promise + ) { + if (typeof Symbol.asyncDispose != 'undefined') { + this[Symbol.asyncDispose] = release; + } + } + + /** Proxied to the underlying connection */ + prepare(sql: string, options?: PrepareOptions): SqliteDriverStatement { + return this.connection.prepare(sql, options); + } + + onUpdate( + listener: UpdateListener, + options?: + | { tables?: string[] | undefined; batchLimit?: number | undefined } + | undefined + ): () => void { + return this.connection.onUpdate(listener, options); + } + + close(): Promise { + return this.connection.close(); + } +} + +export interface ConnectionPoolOptions { + maxConnections?: number; +} diff --git a/packages/driver/src/util/errors.ts b/packages/driver/src/util/errors.ts new file mode 100644 index 0000000..131981c --- /dev/null +++ b/packages/driver/src/util/errors.ts @@ -0,0 +1,14 @@ +import { SqliteError } from '../sqlite-error.js'; + +export function mapError(error: unknown): SqliteError { + const e = error as any; + let code = e.code ?? 'SQLITE_ERROR'; + if (code == 'ERR_SQLITE_ERROR') { + code = 'SQLITE_ERROR'; + } + return new SqliteError({ + code, + message: e.message!, + stack: e.stack + }); +} diff --git a/packages/driver/src/util/index.ts b/packages/driver/src/util/index.ts new file mode 100644 index 0000000..9add2a9 --- /dev/null +++ b/packages/driver/src/util/index.ts @@ -0,0 +1,7 @@ +export * from './connection-pools.js'; +export * from './errors.js'; +export * from './SingleConnectionPool.js'; +export * from './MultiConnectionPool.js'; +export * from './ReadWriteConnectionPool.js'; +export * from './LazyConnectionPool.js'; +export * from './ErrorStatement.js'; diff --git a/packages/driver/src/worker_threads/WorkerDriverAdapter.ts b/packages/driver/src/worker_threads/WorkerDriverAdapter.ts new file mode 100644 index 0000000..85f867b --- /dev/null +++ b/packages/driver/src/worker_threads/WorkerDriverAdapter.ts @@ -0,0 +1,147 @@ +import { + SqliteChanges, + SqliteDriverConnection, + SqliteDriverStatement, + SqliteStepResult, + UpdateListener +} from '../driver-api.js'; +import { mapError } from '../util/errors.js'; +import { + InferBatchResult, + SqliteBind, + SqliteCommand, + SqliteCommandResponse, + SqliteCommandType, + SqliteFinalize, + SqliteParse, + SqliteParseResult, + SqlitePrepare, + SqliteReset, + SqliteRun, + SqliteStep, + WorkerDriver +} from './async-commands.js'; + +export class WorkerConnectionAdapter implements WorkerDriver { + constructor(public connnection: SqliteDriverConnection) {} + + statements = new Map(); + + async close() { + await this.connnection.close(); + } + + private requireStatement(id: number) { + const statement = this.statements.get(id); + if (statement == null) { + throw new Error(`statement not found: ${id}`); + } + return statement; + } + + private _prepare(command: SqlitePrepare): void { + const { id, sql } = command; + + const existing = this.statements.get(id); + if (existing != null) { + throw new Error( + `Replacing statement ${id} without finalizing the previous one` + ); + } + + const statement = this.connnection.prepare(sql, { + bigint: command.bigint, + persist: command.persist, + rawResults: command.rawResults + }); + this.statements.set(id, statement); + } + + private async _parse(command: SqliteParse): Promise { + const { id } = command; + const statement = this.requireStatement(id); + return { columns: await statement.getColumns() }; + } + + private _bind(command: SqliteBind): void { + const { id, parameters } = command; + const statement = this.requireStatement(id); + statement.bind(parameters); + } + + private _step(command: SqliteStep): Promise { + const { id, n, requireTransaction } = command; + const statement = this.requireStatement(id); + return statement.step(n, { requireTransaction }); + } + + private _run(command: SqliteRun): Promise { + const { id } = command; + const statement = this.requireStatement(id); + return statement.run(command); + } + + private _reset(command: SqliteReset): void { + const { id } = command; + const statement = this.requireStatement(id); + statement.reset(command); + } + + private _finalize(command: SqliteFinalize): void { + const { id } = command; + const statement = this.requireStatement(id); + statement.finalize(); + this.statements.delete(id); + } + + private async _executeCommand(command: SqliteCommand): Promise { + switch (command.type) { + case SqliteCommandType.prepare: + return this._prepare(command); + case SqliteCommandType.bind: + return this._bind(command); + case SqliteCommandType.step: + return this._step(command); + case SqliteCommandType.run: + return this._run(command); + case SqliteCommandType.reset: + return this._reset(command); + case SqliteCommandType.finalize: + return this._finalize(command); + case SqliteCommandType.parse: + return this._parse(command); + case SqliteCommandType.changes: + return this.connnection.getLastChanges(); + default: + throw new Error(`Unknown command: ${command.type}`); + } + } + + async execute( + commands: T + ): Promise> { + let results: SqliteCommandResponse[] = []; + + for (let command of commands) { + try { + const result = await this._executeCommand(command); + results.push({ value: result }); + } catch (e: any) { + const err = mapError(e); + results.push({ + error: { message: err.message, stack: err.stack, code: err.code } + }); + } + } + return results as InferBatchResult; + } + + onUpdate( + listener: UpdateListener, + options?: + | { tables?: string[] | undefined; batchLimit?: number | undefined } + | undefined + ): () => void { + throw new Error('Not implemented yet'); + } +} diff --git a/packages/driver/src/worker_threads/async-commands.ts b/packages/driver/src/worker_threads/async-commands.ts new file mode 100644 index 0000000..70a352f --- /dev/null +++ b/packages/driver/src/worker_threads/async-commands.ts @@ -0,0 +1,128 @@ +import { + SqliteParameterBinding, + SqliteChanges, + SqliteStepResult +} from '../driver-api.js'; +import { SerializedDriverError } from '../sqlite-error.js'; + +export enum SqliteCommandType { + prepare = 1, + bind = 2, + step = 3, + reset = 4, + finalize = 5, + sync = 6, + parse = 7, + run = 8, + changes = 9 +} + +export type SqliteDriverError = SerializedDriverError; + +export type SqliteCommandResponse = SqliteErrorResponse | SqliteValueResponse; + +export interface SqliteErrorResponse { + error: SqliteDriverError; +} + +export interface SqliteValueResponse { + value: T; +} + +export interface SqliteBaseCommand { + type: SqliteCommandType; +} + +export interface SqlitePrepare extends SqliteBaseCommand { + type: SqliteCommandType.prepare; + id: number; + sql: string; + bigint?: boolean; + persist?: boolean; + rawResults?: boolean; +} + +export interface SqliteParseResult { + columns: string[]; +} + +export interface SqliteBind extends SqliteBaseCommand { + type: SqliteCommandType.bind; + id: number; + parameters: SqliteParameterBinding; +} + +export interface SqliteParse extends SqliteBaseCommand { + type: SqliteCommandType.parse; + id: number; +} + +export interface SqliteStep extends SqliteBaseCommand { + type: SqliteCommandType.step; + id: number; + n?: number; + requireTransaction?: boolean; +} + +export interface SqliteRun extends SqliteBaseCommand { + type: SqliteCommandType.run; + id: number; + requireTransaction?: boolean; +} + +export interface SqliteReset extends SqliteBaseCommand { + type: SqliteCommandType.reset; + id: number; + clearBindings?: boolean; +} + +export interface SqliteFinalize extends SqliteBaseCommand { + type: SqliteCommandType.finalize; + id: number; +} + +export interface SqliteSync { + type: SqliteCommandType.sync; +} + +export interface SqliteGetChanges { + type: SqliteCommandType.changes; +} + +export type SqliteCommand = + | SqlitePrepare + | SqliteBind + | SqliteStep + | SqliteRun + | SqliteReset + | SqliteFinalize + | SqliteSync + | SqliteParse + | SqliteGetChanges; + +export type InferCommandResult = T extends SqliteRun + ? SqliteChanges + : T extends SqliteStep + ? SqliteStepResult + : T extends SqliteParse + ? SqliteParseResult + : T extends SqliteGetChanges + ? SqliteChanges + : void; + +export type InferBatchResult = { + [i in keyof T]: + | SqliteErrorResponse + | SqliteValueResponse>; +}; + +export function isErrorResponse( + response: SqliteCommandResponse +): response is SqliteErrorResponse { + return (response as SqliteErrorResponse).error != null; +} + +export interface WorkerDriver { + execute(commands: SqliteCommand[]): Promise; + close(): Promise; +} diff --git a/packages/driver/src/worker_threads/index.ts b/packages/driver/src/worker_threads/index.ts new file mode 100644 index 0000000..7d1c108 --- /dev/null +++ b/packages/driver/src/worker_threads/index.ts @@ -0,0 +1,3 @@ +export * from './async-commands.js'; +export * from './worker-driver.js'; +export * from './setup.js'; diff --git a/packages/driver/src/worker_threads/setup.ts b/packages/driver/src/worker_threads/setup.ts new file mode 100644 index 0000000..20abb27 --- /dev/null +++ b/packages/driver/src/worker_threads/setup.ts @@ -0,0 +1,107 @@ +import { Deferred } from '../deferred.js'; +import { isErrorResponse, WorkerDriver } from './async-commands.js'; +import { setTimeout } from 'timers/promises'; + +import * as worker_threads from 'worker_threads'; +import type { WorkerDriverConnectionOptions } from './worker-driver.js'; +import { SqliteDriverConnection } from '../driver-api.js'; +import { WorkerConnectionAdapter } from './WorkerDriverAdapter.js'; + +export type { WorkerDriverConnectionOptions }; + +export interface WorkerDriverConfig { + openConnection: ( + options: WorkerDriverConnectionOptions + ) => Promise; +} + +export function setupDriverWorker(config: WorkerDriverConfig) { + if (worker_threads.parentPort != null) { + return setupDriverPort(worker_threads.parentPort, config); + } +} + +export function setupDriverPort( + port: worker_threads.MessagePort, + config: WorkerDriverConfig +) { + let db: WorkerDriver | null = null; + let opened = new Deferred(); + + const listener = async (value: any) => { + const [message, id, args] = value; + + if (message == 'open') { + try { + const connection = await config.openConnection( + args as WorkerDriverConnectionOptions + ); + db = new WorkerConnectionAdapter(connection); + port.postMessage({ id }); + opened.resolve(); + } catch (e: any) { + opened.reject(e); + port.postMessage({ id, value: { error: { message: e.message } } }); + } + } else if (message == 'close') { + try { + await opened.promise; + await db?.close(); + port.postMessage({ id }); + } catch (e: any) { + port.postMessage({ id, value: { error: { message: e.message } } }); + } + } else if (message == 'execute') { + await opened.promise; + const commands = args; + + const results = (await db!.execute(commands)).map((r) => { + if (isErrorResponse(r)) { + const error = r.error; + return { + error: { + code: error.code, + message: error.message, + stack: error.stack + } + }; + } else { + return r; + } + }); + port.postMessage({ + id, + value: results + }); + } else { + throw new Error(`Unknown message: ${message}`); + } + }; + + port.addListener('message', listener); + port.postMessage({ id: 0, value: 'ready' }); + + return () => { + port.removeListener('message', listener); + }; +} + +export async function retriedOpen( + open: () => SqliteDriverConnection, + timeout: number +) { + const endTime = performance.now() + timeout; + let delay = 1; + while (true) { + try { + return open(); + } catch (e) { + console.error(e); + if (performance.now() >= endTime) { + throw e; + } + await setTimeout(delay); + delay = Math.min(delay * 1.5, 60); + } + } +} diff --git a/packages/driver/src/worker_threads/worker-driver.ts b/packages/driver/src/worker_threads/worker-driver.ts new file mode 100644 index 0000000..a0392a8 --- /dev/null +++ b/packages/driver/src/worker_threads/worker-driver.ts @@ -0,0 +1,264 @@ +import * as worker_threads from 'worker_threads'; +import { + PrepareOptions, + ResetOptions, + SqliteDriverConnection, + SqliteDriverStatement, + SqliteParameterBinding, + SqliteChanges, + SqliteStepResult, + StepOptions, + UpdateListener +} from '../driver-api.js'; + +import { Deferred } from '../deferred.js'; +import { SqliteError } from '../sqlite-error.js'; +import { + InferBatchResult, + InferCommandResult, + isErrorResponse, + SqliteCommand, + SqliteCommandType, + SqliteDriverError +} from './async-commands.js'; + +export interface WorkerDriverConnectionOptions { + path: string; + connectionName?: string; + readonly?: boolean; + workerOptions?: worker_threads.WorkerOptions; +} + +/** + * Driver connection using worker_threads. + */ +export class WorkerDriverConnection implements SqliteDriverConnection { + worker: worker_threads.Worker; + private callbacks = new Map void>(); + private nextCallbackId = 1; + private ready: Promise; + private closing = false; + private nextId = 1; + + buffer: CommandQueueItem[] = []; + + constructor( + workerPath: string | URL, + options: WorkerDriverConnectionOptions + ) { + const worker = new worker_threads.Worker( + workerPath, + options?.workerOptions + ); + this.post('open', options); + worker.addListener('error', (err) => { + console.error('worker error', err); + }); + this.ready = new Promise((resolve) => { + worker.addListener('message', (event) => { + const { id, value } = event; + if (id == 0) { + resolve(); + return; + } + const callback = this.callbacks.get(id); + if (callback == null) { + throw new Error(`No callback with id ${id}`); + } + this.callbacks.delete(id); + callback(value); + }); + }); + this.worker = worker; + } + + prepare(sql: string, options?: PrepareOptions): WorkerDriverStatement { + const id = this.nextId++; + this.buffer.push({ + cmd: { + type: SqliteCommandType.prepare, + id, + bigint: options?.bigint, + persist: options?.persist, + rawResults: options?.rawResults, + sql + } + }); + return new WorkerDriverStatement(this, id); + } + + async getLastChanges(): Promise { + return await this._push({ + type: SqliteCommandType.changes + }); + } + + async sync(): Promise { + await this._push({ + type: SqliteCommandType.sync + }); + } + + _push(cmd: T): Promise> { + const d = new Deferred(); + this.buffer.push({ cmd, resolve: d.resolve, reject: d.reject }); + this._maybeFlush(); + return d.promise as Promise>; + } + + _send(cmd: SqliteCommand): void { + this.buffer.push({ cmd }); + } + + private registerCallback(callback: (value: any) => void) { + const id = this.nextCallbackId++; + this.callbacks.set(id, callback); + return id; + } + + private async post(command: string, args: any): Promise { + await this.ready; + let id: number; + const p = new Promise((resolve) => { + id = this.registerCallback(resolve); + }); + this.worker.postMessage([command, id!, args]); + const result = await p; + const error = (result as any)?.error; + if (error != null) { + return { + error: new SqliteError(error) + } as any; + } + return p; + } + + async close() { + if (this.closing) { + return; + } + this.closing = true; + await this._flush(); + const r: any = await this.post('close', {}); + if (r?.error) { + throw r.error; + } + await this.worker.terminate(); + } + + private inProgress = 0; + + async _flush() { + const commands = this.buffer; + if (commands.length == 0) { + return; + } + this.buffer = []; + const r = await this._execute(commands.map((c) => c.cmd)); + for (let i = 0; i < commands.length; i++) { + const c = commands[i]; + const rr = r[i]; + if (isErrorResponse(rr)) { + c.reject?.(rr.error); + } else if (c.resolve) { + c.resolve!(rr.value); + } + } + } + + async _maybeFlush() { + if (this.inProgress <= 2) { + this.inProgress += 1; + try { + while (this.buffer.length > 0) { + await this._flush(); + } + } finally { + this.inProgress -= 1; + } + } + } + + async _execute( + commands: T + ): Promise> { + return await this.post('execute', commands); + } + + onUpdate( + listener: UpdateListener, + options?: + | { tables?: string[] | undefined; batchLimit?: number | undefined } + | undefined + ): () => void { + throw new Error('Not implemented'); + } +} + +class WorkerDriverStatement implements SqliteDriverStatement { + [Symbol.dispose]: () => void = undefined as any; + + constructor( + private driver: WorkerDriverConnection, + private id: number + ) { + if (typeof Symbol.dispose != 'undefined') { + this[Symbol.dispose] = () => this.finalize(); + } + } + + async getColumns(): Promise { + return this.driver + ._push({ + type: SqliteCommandType.parse, + id: this.id + }) + .then((r) => r.columns); + } + + bind(parameters: SqliteParameterBinding): void { + this.driver._send({ + type: SqliteCommandType.bind, + id: this.id, + parameters: parameters + }); + } + + async step(n?: number, options?: StepOptions): Promise { + return this.driver._push({ + type: SqliteCommandType.step, + id: this.id, + n: n, + requireTransaction: options?.requireTransaction + }); + } + + async run(options?: StepOptions): Promise { + return this.driver._push({ + type: SqliteCommandType.run, + id: this.id, + requireTransaction: options?.requireTransaction + }); + } + + finalize(): void { + this.driver._send({ + type: SqliteCommandType.finalize, + id: this.id + }); + } + + reset(options?: ResetOptions): void { + this.driver._send({ + type: SqliteCommandType.reset, + id: this.id, + clearBindings: options?.clearBindings + }); + } +} + +interface CommandQueueItem { + cmd: SqliteCommand; + resolve?: (r: any) => void; + reject?: (e: SqliteDriverError) => void; +} diff --git a/packages/driver/tsconfig.json b/packages/driver/tsconfig.json new file mode 100644 index 0000000..da8c7f5 --- /dev/null +++ b/packages/driver/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "lib", + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/pipelining.md b/pipelining.md new file mode 100644 index 0000000..0795d68 --- /dev/null +++ b/pipelining.md @@ -0,0 +1,48 @@ +# Idea: Stream/Pipeline + +Some background literature: + +libsql hrana protocol: + +- https://github.com/tursodatabase/libsql-client-ts/blob/main/packages/libsql-client/src/hrana.ts +- https://github.com/libsql/hrana-client-ts/blob/main/src/batch.ts + +Postgres extended query protocol: + +- https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-FLOW-PIPELINING + +In both those cases the protocol is optimized for network usage which could have high latency. +We're working with much lower latency, but the same principles still apply. + +# Pipelining + +The main idea is using a pipeline: +There are still individual requests/responses (Promise-style). +However, you can pipeline multiple requests without waiting for the previous one to complete. + +Using those semantics, we can implement very granular requests, while still maintaining performance. For example, getting the last row id can be a separate request after a statement, without adding significant overhead. + +With pipelining multiple requests, error handling becomes more difficult. Without pipelining, you can just stop and rollback a transaction or batch if there is an error. With pipelining, you may only get the error after submitting more statements. So you need a way to not execute those statements if an earlier one errored. + +## Approaches + +Postgres-style: +When one request errors, ignore all future requests. +To reset the state, the client must make a "sync" request. + +libsql hrana style: +Each statement can have a conditional call. This could be: + +1. Require that the previous statement succeeded. +2. Require still having an active transaction. + +This is more flexible, but also adds complexity to the protocol. + +## Requests + +prepare_statement +bind_statement +step_n # return next n rows +step_all # return all rows +reset_statement +close_statement diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fc666bf..40e2e3d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,665 +1,2292 @@ -lockfileVersion: '6.0' +lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false -dependencies: - better-sqlite3: - specifier: ^9.5.0 - version: 9.5.0 - event-iterator: - specifier: ^2.0.0 - version: 2.0.0 - sqlite3: - specifier: ^5.1.7 - version: 5.1.7 - -devDependencies: - '@types/better-sqlite3': - specifier: ^7.6.9 - version: 7.6.9 - '@types/mocha': - specifier: ^10.0.6 - version: 10.0.6 - chai: - specifier: ^5.1.0 - version: 5.1.0 - mocha: - specifier: ^10.4.0 - version: 10.4.0 - typescript: - specifier: ^5.4.5 - version: 5.4.5 - vitest: - specifier: ^1.5.0 - version: 1.5.0 +importers: + + .: + devDependencies: + '@types/better-sqlite3': + specifier: ^7.6.9 + version: 7.6.11 + '@types/mocha': + specifier: ^10.0.6 + version: 10.0.7 + '@types/node': + specifier: ^20.14.2 + version: 20.14.15 + expect: + specifier: ^29.7.0 + version: 29.7.0 + mocha: + specifier: ^10.4.0 + version: 10.7.3 + prettier: + specifier: ^3.2.5 + version: 3.3.3 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.14.15)(typescript@5.5.4) + tsx: + specifier: ^4.16.2 + version: 4.17.0 + typescript: + specifier: ^5.4.5 + version: 5.5.4 + vitest: + specifier: ^1.5.0 + version: 1.6.0(@types/node@20.14.15) + + benchmarks: + dependencies: + '@sqlite-js/api': + specifier: workspace:^ + version: link:../packages/api + '@sqlite-js/better-sqlite3-driver': + specifier: workspace:^ + version: link:../packages/better-sqlite3-driver + '@sqlite-js/driver': + specifier: workspace:^ + version: link:../packages/driver + better-sqlite3: + specifier: ^11.0.0 + version: 11.1.2 + prando: + specifier: ^6.0.1 + version: 6.0.1 + sqlite: + specifier: ^5.1.1 + version: 5.1.1 + sqlite3: + specifier: ^5.1.7 + version: 5.1.7 + devDependencies: + '@types/node': + specifier: ^20.14.2 + version: 20.14.15 + typescript: + specifier: ^5.4.5 + version: 5.5.4 + + packages/api: + dependencies: + '@sqlite-js/driver': + specifier: workspace:^ + version: link:../driver + devDependencies: + '@sqlite-js/better-sqlite3-driver': + specifier: workspace:^ + version: link:../better-sqlite3-driver + '@sqlite-js/driver-tests': + specifier: workspace:^ + version: link:../driver-tests + '@types/node': + specifier: ^22.3.0 + version: 22.3.0 + typescript: + specifier: ^5.5.4 + version: 5.5.4 + vitest: + specifier: ^2.0.5 + version: 2.0.5(@types/node@22.3.0) + + packages/better-sqlite3-driver: + dependencies: + '@sqlite-js/driver': + specifier: workspace:^ + version: link:../driver + better-sqlite3: + specifier: ^11.1.2 + version: 11.1.2 + devDependencies: + '@sqlite-js/driver-tests': + specifier: workspace:^ + version: link:../driver-tests + '@types/node': + specifier: ^22.3.0 + version: 22.3.0 + typescript: + specifier: ^5.5.4 + version: 5.5.4 + vitest: + specifier: ^2.0.5 + version: 2.0.5(@types/node@22.3.0) + + packages/driver: + devDependencies: + '@types/node': + specifier: ^22.3.0 + version: 22.3.0 + typescript: + specifier: ^5.5.4 + version: 5.5.4 + + packages/driver-tests: + dependencies: + '@sqlite-js/driver': + specifier: workspace:^ + version: link:../driver + mocha: + specifier: ^10.7.3 + version: 10.7.3 + vitest: + specifier: ^2.0.5 + version: 2.0.5(@types/node@22.3.0) + devDependencies: + '@types/node': + specifier: ^22.3.0 + version: 22.3.0 + typescript: + specifier: ^5.5.4 + version: 5.5.4 packages: - /@esbuild/aix-ppc64@0.20.2: - resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.24.7': + resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.24.7': + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + engines: {node: '>=6.9.0'} + + '@babel/highlight@7.24.7': + resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} + engines: {node: '>=6.9.0'} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm64@0.20.2: - resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} + '@esbuild/aix-ppc64@0.23.0': + resolution: {integrity: sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} cpu: [arm64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm@0.20.2: - resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} + '@esbuild/android-arm64@0.23.0': + resolution: {integrity: sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} cpu: [arm] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-x64@0.20.2: - resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} + '@esbuild/android-arm@0.23.0': + resolution: {integrity: sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} cpu: [x64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-arm64@0.20.2: - resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} + '@esbuild/android-x64@0.23.0': + resolution: {integrity: sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-x64@0.20.2: - resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} + '@esbuild/darwin-arm64@0.23.0': + resolution: {integrity: sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-arm64@0.20.2: - resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} + '@esbuild/darwin-x64@0.23.0': + resolution: {integrity: sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-x64@0.20.2: - resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} + '@esbuild/freebsd-arm64@0.23.0': + resolution: {integrity: sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm64@0.20.2: - resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} + '@esbuild/freebsd-x64@0.23.0': + resolution: {integrity: sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm@0.20.2: - resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} + '@esbuild/linux-arm64@0.23.0': + resolution: {integrity: sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} cpu: [arm] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ia32@0.20.2: - resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} + '@esbuild/linux-arm@0.23.0': + resolution: {integrity: sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-loong64@0.20.2: - resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} + '@esbuild/linux-ia32@0.23.0': + resolution: {integrity: sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-mips64el@0.20.2: - resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} + '@esbuild/linux-loong64@0.23.0': + resolution: {integrity: sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ppc64@0.20.2: - resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} + '@esbuild/linux-mips64el@0.23.0': + resolution: {integrity: sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-riscv64@0.20.2: - resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} + '@esbuild/linux-ppc64@0.23.0': + resolution: {integrity: sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-s390x@0.20.2: - resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} + '@esbuild/linux-riscv64@0.23.0': + resolution: {integrity: sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} cpu: [s390x] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-x64@0.20.2: - resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} + '@esbuild/linux-s390x@0.23.0': + resolution: {integrity: sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/netbsd-x64@0.20.2: - resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} + '@esbuild/linux-x64@0.23.0': + resolution: {integrity: sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/openbsd-x64@0.20.2: - resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} + '@esbuild/netbsd-x64@0.23.0': + resolution: {integrity: sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.23.0': + resolution: {integrity: sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/sunos-x64@0.20.2: - resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} + '@esbuild/openbsd-x64@0.23.0': + resolution: {integrity: sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-arm64@0.20.2: - resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} + '@esbuild/sunos-x64@0.23.0': + resolution: {integrity: sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-ia32@0.20.2: - resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} + '@esbuild/win32-arm64@0.23.0': + resolution: {integrity: sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} cpu: [ia32] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-x64@0.20.2: - resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} + '@esbuild/win32-ia32@0.23.0': + resolution: {integrity: sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} cpu: [x64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@gar/promisify@1.1.3: + '@esbuild/win32-x64@0.23.0': + resolution: {integrity: sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@gar/promisify@1.1.3': resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} - requiresBuild: true - dev: false - optional: true - /@jest/schemas@29.6.3: + '@jest/expect-utils@29.7.0': + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/schemas@29.6.3': resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@sinclair/typebox': 0.27.8 - dev: true - /@jridgewell/sourcemap-codec@1.4.15: - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - dev: true + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - /@npmcli/fs@1.1.1: + '@npmcli/fs@1.1.1': resolution: {integrity: sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==} - requiresBuild: true - dependencies: - '@gar/promisify': 1.1.3 - semver: 7.6.0 - dev: false - optional: true - /@npmcli/move-file@1.1.2: + '@npmcli/move-file@1.1.2': resolution: {integrity: sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==} engines: {node: '>=10'} deprecated: This functionality has been moved to @npmcli/fs - requiresBuild: true - dependencies: - mkdirp: 1.0.4 - rimraf: 3.0.2 - dev: false - optional: true - /@rollup/rollup-android-arm-eabi@4.14.3: - resolution: {integrity: sha512-X9alQ3XM6I9IlSlmC8ddAvMSyG1WuHk5oUnXGw+yUBs3BFoTizmG1La/Gr8fVJvDWAq+zlYTZ9DBgrlKRVY06g==} + '@rollup/rollup-android-arm-eabi@4.20.0': + resolution: {integrity: sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==} cpu: [arm] os: [android] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-android-arm64@4.14.3: - resolution: {integrity: sha512-eQK5JIi+POhFpzk+LnjKIy4Ks+pwJ+NXmPxOCSvOKSNRPONzKuUvWE+P9JxGZVxrtzm6BAYMaL50FFuPe0oWMQ==} + '@rollup/rollup-android-arm64@4.20.0': + resolution: {integrity: sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==} cpu: [arm64] os: [android] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-darwin-arm64@4.14.3: - resolution: {integrity: sha512-Od4vE6f6CTT53yM1jgcLqNfItTsLt5zE46fdPaEmeFHvPs5SjZYlLpHrSiHEKR1+HdRfxuzXHjDOIxQyC3ptBA==} + '@rollup/rollup-darwin-arm64@4.20.0': + resolution: {integrity: sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==} cpu: [arm64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-darwin-x64@4.14.3: - resolution: {integrity: sha512-0IMAO21axJeNIrvS9lSe/PGthc8ZUS+zC53O0VhF5gMxfmcKAP4ESkKOCwEi6u2asUrt4mQv2rjY8QseIEb1aw==} + '@rollup/rollup-darwin-x64@4.20.0': + resolution: {integrity: sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==} cpu: [x64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.14.3: - resolution: {integrity: sha512-ge2DC7tHRHa3caVEoSbPRJpq7azhG+xYsd6u2MEnJ6XzPSzQsTKyXvh6iWjXRf7Rt9ykIUWHtl0Uz3T6yXPpKw==} + '@rollup/rollup-linux-arm-gnueabihf@4.20.0': + resolution: {integrity: sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==} cpu: [arm] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm-musleabihf@4.14.3: - resolution: {integrity: sha512-ljcuiDI4V3ySuc7eSk4lQ9wU8J8r8KrOUvB2U+TtK0TiW6OFDmJ+DdIjjwZHIw9CNxzbmXY39wwpzYuFDwNXuw==} + '@rollup/rollup-linux-arm-musleabihf@4.20.0': + resolution: {integrity: sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==} cpu: [arm] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm64-gnu@4.14.3: - resolution: {integrity: sha512-Eci2us9VTHm1eSyn5/eEpaC7eP/mp5n46gTRB3Aar3BgSvDQGJZuicyq6TsH4HngNBgVqC5sDYxOzTExSU+NjA==} + '@rollup/rollup-linux-arm64-gnu@4.20.0': + resolution: {integrity: sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm64-musl@4.14.3: - resolution: {integrity: sha512-UrBoMLCq4E92/LCqlh+blpqMz5h1tJttPIniwUgOFJyjWI1qrtrDhhpHPuFxULlUmjFHfloWdixtDhSxJt5iKw==} + '@rollup/rollup-linux-arm64-musl@4.20.0': + resolution: {integrity: sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-powerpc64le-gnu@4.14.3: - resolution: {integrity: sha512-5aRjvsS8q1nWN8AoRfrq5+9IflC3P1leMoy4r2WjXyFqf3qcqsxRCfxtZIV58tCxd+Yv7WELPcO9mY9aeQyAmw==} + '@rollup/rollup-linux-powerpc64le-gnu@4.20.0': + resolution: {integrity: sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==} cpu: [ppc64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-riscv64-gnu@4.14.3: - resolution: {integrity: sha512-sk/Qh1j2/RJSX7FhEpJn8n0ndxy/uf0kI/9Zc4b1ELhqULVdTfN6HL31CDaTChiBAOgLcsJ1sgVZjWv8XNEsAQ==} + '@rollup/rollup-linux-riscv64-gnu@4.20.0': + resolution: {integrity: sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==} cpu: [riscv64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-s390x-gnu@4.14.3: - resolution: {integrity: sha512-jOO/PEaDitOmY9TgkxF/TQIjXySQe5KVYB57H/8LRP/ux0ZoO8cSHCX17asMSv3ruwslXW/TLBcxyaUzGRHcqg==} + '@rollup/rollup-linux-s390x-gnu@4.20.0': + resolution: {integrity: sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==} cpu: [s390x] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-x64-gnu@4.14.3: - resolution: {integrity: sha512-8ybV4Xjy59xLMyWo3GCfEGqtKV5M5gCSrZlxkPGvEPCGDLNla7v48S662HSGwRd6/2cSneMQWiv+QzcttLrrOA==} + '@rollup/rollup-linux-x64-gnu@4.20.0': + resolution: {integrity: sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-x64-musl@4.14.3: - resolution: {integrity: sha512-s+xf1I46trOY10OqAtZ5Rm6lzHre/UiLA1J2uOhCFXWkbZrJRkYBPO6FhvGfHmdtQ3Bx793MNa7LvoWFAm93bg==} + '@rollup/rollup-linux-x64-musl@4.20.0': + resolution: {integrity: sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-win32-arm64-msvc@4.14.3: - resolution: {integrity: sha512-+4h2WrGOYsOumDQ5S2sYNyhVfrue+9tc9XcLWLh+Kw3UOxAvrfOrSMFon60KspcDdytkNDh7K2Vs6eMaYImAZg==} + '@rollup/rollup-win32-arm64-msvc@4.20.0': + resolution: {integrity: sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==} cpu: [arm64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-win32-ia32-msvc@4.14.3: - resolution: {integrity: sha512-T1l7y/bCeL/kUwh9OD4PQT4aM7Bq43vX05htPJJ46RTI4r5KNt6qJRzAfNfM+OYMNEVBWQzR2Gyk+FXLZfogGw==} + '@rollup/rollup-win32-ia32-msvc@4.20.0': + resolution: {integrity: sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==} cpu: [ia32] os: [win32] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-win32-x64-msvc@4.14.3: - resolution: {integrity: sha512-/BypzV0H1y1HzgYpxqRaXGBRqfodgoBBCcsrujT6QRcakDQdfU+Lq9PENPh5jB4I44YWq+0C2eHsHya+nZY1sA==} + '@rollup/rollup-win32-x64-msvc@4.20.0': + resolution: {integrity: sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==} cpu: [x64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@sinclair/typebox@0.27.8: + '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - dev: true - /@tootallnate/once@1.1.2: + '@tootallnate/once@1.1.2': resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} engines: {node: '>= 6'} - requiresBuild: true - dev: false - optional: true - /@types/better-sqlite3@7.6.9: - resolution: {integrity: sha512-FvktcujPDj9XKMJQWFcl2vVl7OdRIqsSRX9b0acWwTmwLK9CF2eqo/FRcmMLNpugKoX/avA6pb7TorDLmpgTnQ==} - dependencies: - '@types/node': 20.12.7 - dev: true + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - /@types/estree@1.0.5: + '@types/better-sqlite3@7.6.11': + resolution: {integrity: sha512-i8KcD3PgGtGBLl3+mMYA8PdKkButvPyARxA7IQAd6qeslht13qxb1zzO8dRCtE7U3IoJS782zDBAeoKiM695kg==} + + '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - dev: true - /@types/mocha@10.0.6: - resolution: {integrity: sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==} - dev: true + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} - /@types/node@20.12.7: - resolution: {integrity: sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==} - dependencies: - undici-types: 5.26.5 - dev: true + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} - /@vitest/expect@1.5.0: - resolution: {integrity: sha512-0pzuCI6KYi2SIC3LQezmxujU9RK/vwC1U9R0rLuGlNGcOuDWxqWKu6nUdFsX9tH1WU0SXtAxToOsEjeUn1s3hA==} - dependencies: - '@vitest/spy': 1.5.0 - '@vitest/utils': 1.5.0 - chai: 4.4.1 - dev: true + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} - /@vitest/runner@1.5.0: - resolution: {integrity: sha512-7HWwdxXP5yDoe7DTpbif9l6ZmDwCzcSIK38kTSIt6CFEpMjX4EpCgT6wUmS0xTXqMI6E/ONmfgRKmaujpabjZQ==} - dependencies: - '@vitest/utils': 1.5.0 - p-limit: 5.0.0 - pathe: 1.1.2 - dev: true + '@types/mocha@10.0.7': + resolution: {integrity: sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==} - /@vitest/snapshot@1.5.0: - resolution: {integrity: sha512-qpv3fSEuNrhAO3FpH6YYRdaECnnRjg9VxbhdtPwPRnzSfHVXnNzzrpX4cJxqiwgRMo7uRMWDFBlsBq4Cr+rO3A==} - dependencies: - magic-string: 0.30.9 - pathe: 1.1.2 - pretty-format: 29.7.0 - dev: true + '@types/node@20.14.15': + resolution: {integrity: sha512-Fz1xDMCF/B00/tYSVMlmK7hVeLh7jE5f3B7X1/hmV0MJBwE27KlS7EvD/Yp+z1lm8mVhwV5w+n8jOZG8AfTlKw==} - /@vitest/spy@1.5.0: - resolution: {integrity: sha512-vu6vi6ew5N5MMHJjD5PoakMRKYdmIrNJmyfkhRpQt5d9Ewhw9nZ5Aqynbi3N61bvk9UvZ5UysMT6ayIrZ8GA9w==} - dependencies: - tinyspy: 2.2.1 - dev: true + '@types/node@22.3.0': + resolution: {integrity: sha512-nrWpWVaDZuaVc5X84xJ0vNrLvomM205oQyLsRt7OHNZbSHslcWsvgFR7O7hire2ZonjLrWBbedmotmIlJDVd6g==} - /@vitest/utils@1.5.0: - resolution: {integrity: sha512-BDU0GNL8MWkRkSRdNFvCUCAVOeHaUlVJ9Tx0TYBZyXaaOTmGtUFObzchCivIBrIwKzvZA7A9sCejVhXM2aY98A==} - dependencies: - diff-sequences: 29.6.3 - estree-walker: 3.0.3 - loupe: 2.3.7 - pretty-format: 29.7.0 - dev: true + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.33': + resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} + + '@vitest/expect@1.6.0': + resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==} - /abbrev@1.1.1: + '@vitest/expect@2.0.5': + resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==} + + '@vitest/pretty-format@2.0.5': + resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==} + + '@vitest/runner@1.6.0': + resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==} + + '@vitest/runner@2.0.5': + resolution: {integrity: sha512-TfRfZa6Bkk9ky4tW0z20WKXFEwwvWhRY+84CnSEtq4+3ZvDlJyY32oNTJtM7AW9ihW90tX/1Q78cb6FjoAs+ig==} + + '@vitest/snapshot@1.6.0': + resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==} + + '@vitest/snapshot@2.0.5': + resolution: {integrity: sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==} + + '@vitest/spy@1.6.0': + resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==} + + '@vitest/spy@2.0.5': + resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==} + + '@vitest/utils@1.6.0': + resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==} + + '@vitest/utils@2.0.5': + resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==} + + abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} - requiresBuild: true - dev: false - optional: true - /acorn-walk@8.3.2: - resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} + acorn-walk@8.3.3: + resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==} engines: {node: '>=0.4.0'} - dev: true - /acorn@8.11.3: - resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} + acorn@8.12.1: + resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} engines: {node: '>=0.4.0'} hasBin: true - dev: true - /agent-base@6.0.2: + agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} - requiresBuild: true - dependencies: - debug: 4.3.4(supports-color@8.1.1) - transitivePeerDependencies: - - supports-color - dev: false - optional: true - /agentkeepalive@4.5.0: + agentkeepalive@4.5.0: resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} engines: {node: '>= 8.0.0'} - requiresBuild: true - dependencies: - humanize-ms: 1.2.1 - dev: false - optional: true - /aggregate-error@3.1.0: + aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} - requiresBuild: true - dependencies: - clean-stack: 2.2.0 - indent-string: 4.0.0 - dev: false - optional: true - /ansi-colors@4.1.1: - resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} - dev: true - /ansi-regex@5.0.1: + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - /ansi-styles@4.3.0: + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - dependencies: - color-convert: 2.0.1 - dev: true - /ansi-styles@5.2.0: + ansi-styles@5.2.0: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} - dev: true - /anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + aproba@2.0.0: + resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + + are-we-there-yet@3.0.1: + resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + better-sqlite3@11.1.2: + resolution: {integrity: sha512-gujtFwavWU4MSPT+h9B+4pkvZdyOUkH54zgLdIrMmmmd4ZqiBIrRNBzNzYVFO417xo882uP5HBu4GjOfaSrIQw==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browser-stdout@1.3.1: + resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + cacache@15.3.0: + resolution: {integrity: sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==} + engines: {node: '>= 10'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + chai@4.5.0: + resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} + engines: {node: '>=4'} + + chai@5.1.1: + resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==} + engines: {node: '>=12'} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + confbox@0.1.7: + resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==} + + console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + + debug@4.3.6: + resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize@4.0.0: + resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} + engines: {node: '>=10'} + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deep-eql@4.1.4: + resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} + engines: {node: '>=6'} + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + + detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + + diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} + engines: {node: '>=0.3.1'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + + end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.23.0: + resolution: {integrity: sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + engines: {node: '>=6'} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gauge@4.0.4: + resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + + get-tsconfig@4.7.6: + resolution: {integrity: sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==} + + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + http-cache-semantics@4.1.1: + resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} + + http-proxy-agent@4.0.1: + resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} + engines: {node: '>= 6'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + infer-owner@1.0.4: + resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + ip-address@9.0.5: + resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} + engines: {node: '>= 12'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-lambda@1.0.1: + resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-tokens@9.0.0: + resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsbn@1.1.0: + resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} + + local-pkg@0.5.0: + resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} + engines: {node: '>=14'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + + loupe@3.1.1: + resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + magic-string@0.30.11: + resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + make-fetch-happen@9.1.0: + resolution: {integrity: sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==} + engines: {node: '>= 10'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + micromatch@4.0.7: + resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + engines: {node: '>=8.6'} + + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass-collect@1.0.2: + resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} + engines: {node: '>= 8'} + + minipass-fetch@1.4.1: + resolution: {integrity: sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==} + engines: {node: '>=8'} + + minipass-flush@1.0.5: + resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} + engines: {node: '>= 8'} + + minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + + minipass-sized@1.0.3: + resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + engines: {node: '>=8'} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + mlly@1.7.1: + resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==} + + mocha@10.7.3: + resolution: {integrity: sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==} + engines: {node: '>= 14.0.0'} + hasBin: true + + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + napi-build-utils@1.0.2: + resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + node-abi@3.65.0: + resolution: {integrity: sha512-ThjYBfoDNr08AWx6hGaRbfPwxKV9kVzAzOzlLKbk2CuqXE2xnCh+cbAGnwM3t8Lq4v9rUB7VfondlkBckcJrVA==} + engines: {node: '>=10'} + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + node-gyp@8.4.1: + resolution: {integrity: sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==} + engines: {node: '>= 10.12.0'} + hasBin: true + + nopt@5.0.0: + resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} + engines: {node: '>=6'} + hasBin: true + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + npmlog@6.0.2: + resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-limit@5.0.0: + resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} + engines: {node: '>=18'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + + picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pkg-types@1.1.3: + resolution: {integrity: sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==} + + postcss@8.4.41: + resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==} + engines: {node: ^10 || ^12 || >=14} + + prando@6.0.1: + resolution: {integrity: sha512-ghUWxQ1T9IJmPu6eshc3VU0OwveUtXQ33ZLXYUcz1Oc5ppKLDXKp0TBDj6b0epwhEctzcQSNGR2iHyvQSn4W5A==} + + prebuild-install@7.1.2: + resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} + engines: {node: '>=10'} + hasBin: true + + prettier@3.3.3: + resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} + engines: {node: '>=14'} + hasBin: true + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + promise-inflight@1.0.1: + resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} + peerDependencies: + bluebird: '*' + peerDependenciesMeta: + bluebird: + optional: true + + promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + + pump@3.0.0: + resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rollup@4.20.0: + resolution: {integrity: sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@6.2.1: + resolution: {integrity: sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==} + engines: {node: '>= 10'} + + socks@2.8.3: + resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} + + sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + + sqlite3@5.1.7: + resolution: {integrity: sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==} + + sqlite@5.1.1: + resolution: {integrity: sha512-oBkezXa2hnkfuJwUo44Hl9hS3er+YFtueifoajrgidvqsJRQFpc5fKoAkAor1O5ZnLoa28GBScfHXs8j0K358Q==} + + ssri@8.0.1: + resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==} + engines: {node: '>= 8'} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strip-literal@2.1.0: + resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + tar-fs@2.1.1: + resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinypool@0.8.4: + resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} + engines: {node: '>=14.0.0'} + + tinypool@1.0.0: + resolution: {integrity: sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@2.2.1: + resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.0: + resolution: {integrity: sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==} + engines: {node: '>=14.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + tsx@4.17.0: + resolution: {integrity: sha512-eN4mnDA5UMKDt4YZixo9tBioibaMBpoxBkD+rIPAjVmYERSG0/dWEY1CEFuV89CgASlKL499q8AhmkMnnjtOJg==} + engines: {node: '>=18.0.0'} + hasBin: true + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + type-detect@4.1.0: + resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} + engines: {node: '>=4'} + + typescript@5.5.4: + resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.5.4: + resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} + + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + undici-types@6.18.2: + resolution: {integrity: sha512-5ruQbENj95yDYJNS3TvcaxPMshV7aizdv/hWYjGIKoANWKjhWNBsr2YEuYZKodQulB1b8l7ILOuDQep3afowQQ==} + + unique-filename@1.1.1: + resolution: {integrity: sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==} + + unique-slug@2.0.2: + resolution: {integrity: sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + vite-node@1.6.0: + resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite-node@2.0.5: + resolution: {integrity: sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite@5.4.1: + resolution: {integrity: sha512-1oE6yuNXssjrZdblI9AfBbHCC41nnyoVoEZxQnID6yvQZAFBzxxkqoFLtHUMkYunL8hwOLEjgTuxpkRxvba3kA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vitest@1.6.0: + resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 1.6.0 + '@vitest/ui': 1.6.0 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + vitest@2.0.5: + resolution: {integrity: sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.0.5 + '@vitest/ui': 2.0.5 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + + workerpool@6.5.1: + resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs-unparser@2.0.0: + resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} + engines: {node: '>=10'} + + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yocto-queue@1.1.1: + resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==} + engines: {node: '>=12.20'} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@babel/code-frame@7.24.7': + dependencies: + '@babel/highlight': 7.24.7 + picocolors: 1.0.1 + + '@babel/helper-validator-identifier@7.24.7': {} + + '@babel/highlight@7.24.7': + dependencies: + '@babel/helper-validator-identifier': 7.24.7 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.0.1 + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/aix-ppc64@0.23.0': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.23.0': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-arm@0.23.0': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/android-x64@0.23.0': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.23.0': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.23.0': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.23.0': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.23.0': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.23.0': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-arm@0.23.0': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.23.0': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.23.0': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.23.0': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.23.0': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.23.0': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.23.0': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/linux-x64@0.23.0': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.23.0': + optional: true + + '@esbuild/openbsd-arm64@0.23.0': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.23.0': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.23.0': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.23.0': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.23.0': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@esbuild/win32-x64@0.23.0': + optional: true + + '@gar/promisify@1.1.3': + optional: true + + '@jest/expect-utils@29.7.0': + dependencies: + jest-get-type: 29.6.3 + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.8 + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 22.3.0 + '@types/yargs': 17.0.33 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@npmcli/fs@1.1.1': + dependencies: + '@gar/promisify': 1.1.3 + semver: 7.6.3 + optional: true + + '@npmcli/move-file@1.1.2': + dependencies: + mkdirp: 1.0.4 + rimraf: 3.0.2 + optional: true + + '@rollup/rollup-android-arm-eabi@4.20.0': + optional: true + + '@rollup/rollup-android-arm64@4.20.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.20.0': + optional: true + + '@rollup/rollup-darwin-x64@4.20.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.20.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.20.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.20.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.20.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.20.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.20.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.20.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.20.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.20.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.20.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.20.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.20.0': + optional: true + + '@sinclair/typebox@0.27.8': {} + + '@tootallnate/once@1.1.2': + optional: true + + '@tsconfig/node10@1.0.11': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/better-sqlite3@7.6.11': + dependencies: + '@types/node': 20.14.15 + + '@types/estree@1.0.5': {} + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/mocha@10.0.7': {} + + '@types/node@20.14.15': + dependencies: + undici-types: 5.26.5 + + '@types/node@22.3.0': + dependencies: + undici-types: 6.18.2 + + '@types/stack-utils@2.0.3': {} + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.33': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@vitest/expect@1.6.0': + dependencies: + '@vitest/spy': 1.6.0 + '@vitest/utils': 1.6.0 + chai: 4.5.0 + + '@vitest/expect@2.0.5': + dependencies: + '@vitest/spy': 2.0.5 + '@vitest/utils': 2.0.5 + chai: 5.1.1 + tinyrainbow: 1.2.0 + + '@vitest/pretty-format@2.0.5': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@1.6.0': + dependencies: + '@vitest/utils': 1.6.0 + p-limit: 5.0.0 + pathe: 1.1.2 + + '@vitest/runner@2.0.5': + dependencies: + '@vitest/utils': 2.0.5 + pathe: 1.1.2 + + '@vitest/snapshot@1.6.0': + dependencies: + magic-string: 0.30.11 + pathe: 1.1.2 + pretty-format: 29.7.0 + + '@vitest/snapshot@2.0.5': + dependencies: + '@vitest/pretty-format': 2.0.5 + magic-string: 0.30.11 + pathe: 1.1.2 + + '@vitest/spy@1.6.0': + dependencies: + tinyspy: 2.2.1 + + '@vitest/spy@2.0.5': + dependencies: + tinyspy: 3.0.0 + + '@vitest/utils@1.6.0': + dependencies: + diff-sequences: 29.6.3 + estree-walker: 3.0.3 + loupe: 2.3.7 + pretty-format: 29.7.0 + + '@vitest/utils@2.0.5': + dependencies: + '@vitest/pretty-format': 2.0.5 + estree-walker: 3.0.3 + loupe: 3.1.1 + tinyrainbow: 1.2.0 + + abbrev@1.1.1: + optional: true + + acorn-walk@8.3.3: + dependencies: + acorn: 8.12.1 + + acorn@8.12.1: {} + + agent-base@6.0.2: + dependencies: + debug: 4.3.6(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + optional: true + + agentkeepalive@4.5.0: + dependencies: + humanize-ms: 1.2.1 + optional: true + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + optional: true + + ansi-colors@4.1.3: {} + + ansi-regex@5.0.1: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 - dev: true - /aproba@2.0.0: - resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} - requiresBuild: true - dev: false + aproba@2.0.0: optional: true - /are-we-there-yet@3.0.1: - resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - requiresBuild: true + are-we-there-yet@3.0.1: dependencies: delegates: 1.0.0 readable-stream: 3.6.2 - dev: false optional: true - /argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - dev: true + arg@4.1.3: {} - /assertion-error@1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} - dev: true + argparse@2.0.1: {} - /assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - dev: true + assertion-error@1.1.0: {} - /balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + assertion-error@2.0.1: {} - /base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - dev: false + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} - /better-sqlite3@9.5.0: - resolution: {integrity: sha512-01qVcM4gPNwE+PX7ARNiHINwzVuD6nx0gdldaAAcu+MrzyIAukQ31ZDKEpzRO/CNA9sHpxoTZ8rdjoyAin4dyg==} - requiresBuild: true + better-sqlite3@11.1.2: dependencies: bindings: 1.5.0 prebuild-install: 7.1.2 - dev: false - /binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - dev: true + binary-extensions@2.3.0: {} - /bindings@1.5.0: - resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + bindings@1.5.0: dependencies: file-uri-to-path: 1.0.0 - dev: false - /bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + bl@4.1.0: dependencies: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.2 - dev: false - /brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - requiresBuild: true + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - dev: false optional: true - /brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + brace-expansion@2.0.1: dependencies: balanced-match: 1.0.2 - dev: true - /braces@3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} + braces@3.0.3: dependencies: - fill-range: 7.0.1 - dev: true + fill-range: 7.1.1 - /browser-stdout@1.3.1: - resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} - dev: true + browser-stdout@1.3.1: {} - /buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + buffer@5.7.1: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 - dev: false - /cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - dev: true + cac@6.7.14: {} - /cacache@15.3.0: - resolution: {integrity: sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==} - engines: {node: '>= 10'} - requiresBuild: true + cacache@15.3.0: dependencies: '@npmcli/fs': 1.1.1 '@npmcli/move-file': 1.1.2 @@ -681,63 +2308,49 @@ packages: unique-filename: 1.1.1 transitivePeerDependencies: - bluebird - dev: false optional: true - /camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - dev: true + camelcase@6.3.0: {} - /chai@4.4.1: - resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} - engines: {node: '>=4'} + chai@4.5.0: dependencies: assertion-error: 1.1.0 check-error: 1.0.3 - deep-eql: 4.1.3 + deep-eql: 4.1.4 get-func-name: 2.0.2 loupe: 2.3.7 pathval: 1.1.1 - type-detect: 4.0.8 - dev: true + type-detect: 4.1.0 - /chai@5.1.0: - resolution: {integrity: sha512-kDZ7MZyM6Q1DhR9jy7dalKohXQ2yrlXkk59CR52aRKxJrobmlBNqnFQxX9xOX8w+4mz8SYlKJa/7D7ddltFXCw==} - engines: {node: '>=12'} + chai@5.1.1: dependencies: assertion-error: 2.0.1 - check-error: 2.0.0 - deep-eql: 5.0.1 - loupe: 3.1.0 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.1 pathval: 2.0.0 - dev: true - /chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - dev: true - /check-error@1.0.3: - resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + check-error@1.0.3: dependencies: get-func-name: 2.0.2 - dev: true - /check-error@2.0.0: - resolution: {integrity: sha512-tjLAOBHKVxtPoHe/SA7kNOMvhCRdCJ3vETdeY0RuAc9popf+hyaSV6ZEg9hr4cpWF7jmo/JSWEnLDrnijS9Tog==} - engines: {node: '>= 16'} - dev: true + check-error@2.1.1: {} - /chokidar@3.5.3: - resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} - engines: {node: '>= 8.10.0'} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 - braces: 3.0.2 + braces: 3.0.3 glob-parent: 5.1.2 is-binary-path: 2.1.0 is-glob: 4.0.3 @@ -745,217 +2358,167 @@ packages: readdirp: 3.6.0 optionalDependencies: fsevents: 2.3.3 - dev: true - /chownr@1.1.4: - resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - dev: false + chownr@1.1.4: {} - /chownr@2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} - dev: false + chownr@2.0.0: {} - /clean-stack@2.2.0: - resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} - engines: {node: '>=6'} - requiresBuild: true - dev: false + ci-info@3.9.0: {} + + clean-stack@2.2.0: optional: true - /cliui@7.0.4: - resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + cliui@7.0.4: dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - dev: true - /color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: dependencies: color-name: 1.1.4 - dev: true - /color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - dev: true + color-name@1.1.3: {} - /color-support@1.1.3: - resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} - hasBin: true - requiresBuild: true - dev: false + color-name@1.1.4: {} + + color-support@1.1.3: optional: true - /concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - requiresBuild: true - dev: false + concat-map@0.0.1: optional: true - /console-control-strings@1.1.0: - resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} - requiresBuild: true - dev: false + confbox@0.1.7: {} + + console-control-strings@1.1.0: optional: true - /cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} + create-require@1.1.1: {} + + cross-spawn@7.0.3: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - dev: true - /debug@4.3.4(supports-color@8.1.1): - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + debug@4.3.6(supports-color@8.1.1): dependencies: ms: 2.1.2 + optionalDependencies: supports-color: 8.1.1 - /decamelize@4.0.0: - resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} - engines: {node: '>=10'} - dev: true + decamelize@4.0.0: {} - /decompress-response@6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} + decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 - dev: false - /deep-eql@4.1.3: - resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} - engines: {node: '>=6'} + deep-eql@4.1.4: dependencies: - type-detect: 4.0.8 - dev: true + type-detect: 4.1.0 - /deep-eql@5.0.1: - resolution: {integrity: sha512-nwQCf6ne2gez3o1MxWifqkciwt0zhl0LO1/UwVu4uMBuPmflWM4oQ70XMqHqnBJA+nhzncaqL9HVL6KkHJ28lw==} - engines: {node: '>=6'} - dev: true + deep-eql@5.0.2: {} - /deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} - dev: false + deep-extend@0.6.0: {} - /delegates@1.0.0: - resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} - requiresBuild: true - dev: false + delegates@1.0.0: optional: true - /detect-libc@2.0.3: - resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} - engines: {node: '>=8'} - dev: false + detect-libc@2.0.3: {} - /diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: true + diff-sequences@29.6.3: {} - /diff@5.0.0: - resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} - engines: {node: '>=0.3.1'} - dev: true + diff@4.0.2: {} - /emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + diff@5.2.0: {} - /encoding@0.1.13: - resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} - requiresBuild: true + emoji-regex@8.0.0: {} + + encoding@0.1.13: dependencies: iconv-lite: 0.6.3 - dev: false optional: true - /end-of-stream@1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + end-of-stream@1.4.4: dependencies: once: 1.4.0 - dev: false - /env-paths@2.2.1: - resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} - engines: {node: '>=6'} - requiresBuild: true - dev: false + env-paths@2.2.1: optional: true - /err-code@2.0.3: - resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} - requiresBuild: true - dev: false + err-code@2.0.3: optional: true - /esbuild@0.20.2: - resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true + esbuild@0.21.5: optionalDependencies: - '@esbuild/aix-ppc64': 0.20.2 - '@esbuild/android-arm': 0.20.2 - '@esbuild/android-arm64': 0.20.2 - '@esbuild/android-x64': 0.20.2 - '@esbuild/darwin-arm64': 0.20.2 - '@esbuild/darwin-x64': 0.20.2 - '@esbuild/freebsd-arm64': 0.20.2 - '@esbuild/freebsd-x64': 0.20.2 - '@esbuild/linux-arm': 0.20.2 - '@esbuild/linux-arm64': 0.20.2 - '@esbuild/linux-ia32': 0.20.2 - '@esbuild/linux-loong64': 0.20.2 - '@esbuild/linux-mips64el': 0.20.2 - '@esbuild/linux-ppc64': 0.20.2 - '@esbuild/linux-riscv64': 0.20.2 - '@esbuild/linux-s390x': 0.20.2 - '@esbuild/linux-x64': 0.20.2 - '@esbuild/netbsd-x64': 0.20.2 - '@esbuild/openbsd-x64': 0.20.2 - '@esbuild/sunos-x64': 0.20.2 - '@esbuild/win32-arm64': 0.20.2 - '@esbuild/win32-ia32': 0.20.2 - '@esbuild/win32-x64': 0.20.2 - dev: true - - /escalade@3.1.2: - resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} - engines: {node: '>=6'} - dev: true - - /escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - dev: true - - /estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + esbuild@0.23.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.0 + '@esbuild/android-arm': 0.23.0 + '@esbuild/android-arm64': 0.23.0 + '@esbuild/android-x64': 0.23.0 + '@esbuild/darwin-arm64': 0.23.0 + '@esbuild/darwin-x64': 0.23.0 + '@esbuild/freebsd-arm64': 0.23.0 + '@esbuild/freebsd-x64': 0.23.0 + '@esbuild/linux-arm': 0.23.0 + '@esbuild/linux-arm64': 0.23.0 + '@esbuild/linux-ia32': 0.23.0 + '@esbuild/linux-loong64': 0.23.0 + '@esbuild/linux-mips64el': 0.23.0 + '@esbuild/linux-ppc64': 0.23.0 + '@esbuild/linux-riscv64': 0.23.0 + '@esbuild/linux-s390x': 0.23.0 + '@esbuild/linux-x64': 0.23.0 + '@esbuild/netbsd-x64': 0.23.0 + '@esbuild/openbsd-arm64': 0.23.0 + '@esbuild/openbsd-x64': 0.23.0 + '@esbuild/sunos-x64': 0.23.0 + '@esbuild/win32-arm64': 0.23.0 + '@esbuild/win32-ia32': 0.23.0 + '@esbuild/win32-x64': 0.23.0 + + escalade@3.1.2: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@2.0.0: {} + + escape-string-regexp@4.0.0: {} + + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.5 - dev: true - - /event-iterator@2.0.0: - resolution: {integrity: sha512-KGft0ldl31BZVV//jj+IAIGCxkvvUkkON+ScH6zfoX+l+omX6001ggyRSpI0Io2Hlro0ThXotswCtfzS8UkIiQ==} - dev: false - /execa@8.0.1: - resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} - engines: {node: '>=16.17'} + execa@8.0.1: dependencies: cross-spawn: 7.0.3 get-stream: 8.0.1 @@ -966,63 +2529,42 @@ packages: onetime: 6.0.0 signal-exit: 4.1.0 strip-final-newline: 3.0.0 - dev: true - /expand-template@2.0.3: - resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} - engines: {node: '>=6'} - dev: false + expand-template@2.0.3: {} - /file-uri-to-path@1.0.0: - resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - dev: false + expect@29.7.0: + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 - /fill-range@7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} + file-uri-to-path@1.0.0: {} + + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 - dev: true - /find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} + find-up@5.0.0: dependencies: locate-path: 6.0.0 path-exists: 4.0.0 - dev: true - /flat@5.0.2: - resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} - hasBin: true - dev: true + flat@5.0.2: {} - /fs-constants@1.0.0: - resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - dev: false + fs-constants@1.0.0: {} - /fs-minipass@2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} + fs-minipass@2.1.0: dependencies: minipass: 3.3.6 - dev: false - /fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fs.realpath@1.0.0: {} - /fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - dev: true + fsevents@2.3.3: optional: true - /gauge@4.0.4: - resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - requiresBuild: true + gauge@4.0.4: dependencies: aproba: 2.0.0 color-support: 1.1.3 @@ -1032,37 +2574,25 @@ packages: string-width: 4.2.3 strip-ansi: 6.0.1 wide-align: 1.1.5 - dev: false optional: true - /get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - dev: true + get-caller-file@2.0.5: {} - /get-func-name@2.0.2: - resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - dev: true + get-func-name@2.0.2: {} - /get-stream@8.0.1: - resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} - engines: {node: '>=16'} - dev: true + get-stream@8.0.1: {} - /github-from-package@0.0.0: - resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} - dev: false + get-tsconfig@4.7.6: + dependencies: + resolve-pkg-maps: 1.0.0 - /glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} + github-from-package@0.0.0: {} + + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 - dev: true - /glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - requiresBuild: true + glob@7.2.3: dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -1070,267 +2600,192 @@ packages: minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 - dev: false optional: true - /glob@8.1.0: - resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} - engines: {node: '>=12'} + glob@8.1.0: dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 5.0.1 + minimatch: 5.1.6 once: 1.4.0 - dev: true - /graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - requiresBuild: true - dev: false - optional: true + graceful-fs@4.2.11: {} - /has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} + has-flag@3.0.0: {} - /has-unicode@2.0.1: - resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} - requiresBuild: true - dev: false + has-flag@4.0.0: {} + + has-unicode@2.0.1: optional: true - /he@1.2.0: - resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} - hasBin: true - dev: true + he@1.2.0: {} - /http-cache-semantics@4.1.1: - resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} - requiresBuild: true - dev: false + http-cache-semantics@4.1.1: optional: true - /http-proxy-agent@4.0.1: - resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} - engines: {node: '>= 6'} - requiresBuild: true + http-proxy-agent@4.0.1: dependencies: '@tootallnate/once': 1.1.2 agent-base: 6.0.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color - dev: false optional: true - /https-proxy-agent@5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} - requiresBuild: true + https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color - dev: false optional: true - /human-signals@5.0.0: - resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} - engines: {node: '>=16.17.0'} - dev: true + human-signals@5.0.0: {} - /humanize-ms@1.2.1: - resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} - requiresBuild: true + humanize-ms@1.2.1: dependencies: ms: 2.1.3 - dev: false optional: true - /iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - requiresBuild: true + iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 - dev: false optional: true - /ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - dev: false + ieee754@1.2.1: {} - /imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - requiresBuild: true - dev: false + imurmurhash@0.1.4: optional: true - /indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - requiresBuild: true - dev: false + indent-string@4.0.0: optional: true - /infer-owner@1.0.4: - resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} - requiresBuild: true - dev: false + infer-owner@1.0.4: optional: true - /inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + inflight@1.0.6: dependencies: once: 1.4.0 wrappy: 1.0.2 - /inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + inherits@2.0.4: {} - /ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - dev: false + ini@1.3.8: {} - /ip-address@9.0.5: - resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} - engines: {node: '>= 12'} - requiresBuild: true + ip-address@9.0.5: dependencies: jsbn: 1.1.0 sprintf-js: 1.1.3 - dev: false optional: true - /is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 - dev: true - /is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - dev: true + is-extglob@2.1.1: {} - /is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} + is-fullwidth-code-point@3.0.0: {} - /is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 - dev: true - /is-lambda@1.0.1: - resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} - requiresBuild: true - dev: false + is-lambda@1.0.1: optional: true - /is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - dev: true + is-number@7.0.0: {} - /is-plain-obj@2.1.0: - resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} - engines: {node: '>=8'} - dev: true + is-plain-obj@2.1.0: {} - /is-stream@3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: true + is-stream@3.0.0: {} - /is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - dev: true + is-unicode-supported@0.1.0: {} - /isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isexe@2.0.0: {} - /js-tokens@9.0.0: - resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==} - dev: true + jest-diff@29.7.0: + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 - /js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true + jest-get-type@29.6.3: {} + + jest-matcher-utils@29.7.0: + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.24.7 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.7 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 22.3.0 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + + js-tokens@4.0.0: {} + + js-tokens@9.0.0: {} + + js-yaml@4.1.0: dependencies: argparse: 2.0.1 - dev: true - /jsbn@1.1.0: - resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} - requiresBuild: true - dev: false + jsbn@1.1.0: optional: true - /jsonc-parser@3.2.1: - resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} - dev: true - - /local-pkg@0.5.0: - resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} - engines: {node: '>=14'} + local-pkg@0.5.0: dependencies: - mlly: 1.6.1 - pkg-types: 1.0.3 - dev: true + mlly: 1.7.1 + pkg-types: 1.1.3 - /locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 - dev: true - /log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} + log-symbols@4.1.0: dependencies: chalk: 4.1.2 is-unicode-supported: 0.1.0 - dev: true - /loupe@2.3.7: - resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + loupe@2.3.7: dependencies: get-func-name: 2.0.2 - dev: true - /loupe@3.1.0: - resolution: {integrity: sha512-qKl+FrLXUhFuHUoDJG7f8P8gEMHq9NFS0c6ghXG1J0rldmZFQZoNVv/vyirE9qwCIhWZDsvEFd1sbFu3GvRQFg==} + loupe@3.1.1: dependencies: get-func-name: 2.0.2 - dev: true - /lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} + lru-cache@6.0.0: dependencies: yallist: 4.0.0 - dev: false + optional: true - /magic-string@0.30.9: - resolution: {integrity: sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==} - engines: {node: '>=12'} + magic-string@0.30.11: dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 - dev: true + '@jridgewell/sourcemap-codec': 1.5.0 - /make-fetch-happen@9.1.0: - resolution: {integrity: sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==} - engines: {node: '>= 10'} - requiresBuild: true + make-error@1.3.6: {} + + make-fetch-happen@9.1.0: dependencies: agentkeepalive: 4.5.0 cacache: 15.3.0 @@ -1351,197 +2806,122 @@ packages: transitivePeerDependencies: - bluebird - supports-color - dev: false optional: true - /merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - dev: true + merge-stream@2.0.0: {} - /mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - dev: true + micromatch@4.0.7: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 - /mimic-response@3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} - engines: {node: '>=10'} - dev: false + mimic-fn@4.0.0: {} - /minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - requiresBuild: true + mimic-response@3.1.0: {} + + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 - dev: false optional: true - /minimatch@5.0.1: - resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==} - engines: {node: '>=10'} + minimatch@5.1.6: dependencies: brace-expansion: 2.0.1 - dev: true - /minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - dev: false + minimist@1.2.8: {} - /minipass-collect@1.0.2: - resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} - engines: {node: '>= 8'} - requiresBuild: true + minipass-collect@1.0.2: dependencies: minipass: 3.3.6 - dev: false optional: true - /minipass-fetch@1.4.1: - resolution: {integrity: sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==} - engines: {node: '>=8'} - requiresBuild: true + minipass-fetch@1.4.1: dependencies: minipass: 3.3.6 minipass-sized: 1.0.3 minizlib: 2.1.2 optionalDependencies: encoding: 0.1.13 - dev: false optional: true - /minipass-flush@1.0.5: - resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} - engines: {node: '>= 8'} - requiresBuild: true + minipass-flush@1.0.5: dependencies: minipass: 3.3.6 - dev: false optional: true - /minipass-pipeline@1.2.4: - resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} - engines: {node: '>=8'} - requiresBuild: true + minipass-pipeline@1.2.4: dependencies: minipass: 3.3.6 - dev: false optional: true - /minipass-sized@1.0.3: - resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} - engines: {node: '>=8'} - requiresBuild: true + minipass-sized@1.0.3: dependencies: minipass: 3.3.6 - dev: false optional: true - /minipass@3.3.6: - resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} - engines: {node: '>=8'} + minipass@3.3.6: dependencies: yallist: 4.0.0 - dev: false - /minipass@5.0.0: - resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} - engines: {node: '>=8'} - dev: false + minipass@5.0.0: {} - /minizlib@2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} + minizlib@2.1.2: dependencies: minipass: 3.3.6 yallist: 4.0.0 - dev: false - /mkdirp-classic@0.5.3: - resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - dev: false + mkdirp-classic@0.5.3: {} - /mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - dev: false + mkdirp@1.0.4: {} - /mlly@1.6.1: - resolution: {integrity: sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==} + mlly@1.7.1: dependencies: - acorn: 8.11.3 + acorn: 8.12.1 pathe: 1.1.2 - pkg-types: 1.0.3 - ufo: 1.5.3 - dev: true + pkg-types: 1.1.3 + ufo: 1.5.4 - /mocha@10.4.0: - resolution: {integrity: sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==} - engines: {node: '>= 14.0.0'} - hasBin: true + mocha@10.7.3: dependencies: - ansi-colors: 4.1.1 + ansi-colors: 4.1.3 browser-stdout: 1.3.1 - chokidar: 3.5.3 - debug: 4.3.4(supports-color@8.1.1) - diff: 5.0.0 + chokidar: 3.6.0 + debug: 4.3.6(supports-color@8.1.1) + diff: 5.2.0 escape-string-regexp: 4.0.0 find-up: 5.0.0 glob: 8.1.0 he: 1.2.0 js-yaml: 4.1.0 log-symbols: 4.1.0 - minimatch: 5.0.1 + minimatch: 5.1.6 ms: 2.1.3 - serialize-javascript: 6.0.0 + serialize-javascript: 6.0.2 strip-json-comments: 3.1.1 supports-color: 8.1.1 - workerpool: 6.2.1 + workerpool: 6.5.1 yargs: 16.2.0 - yargs-parser: 20.2.4 + yargs-parser: 20.2.9 yargs-unparser: 2.0.0 - dev: true - - /ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - /ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + ms@2.1.2: {} - /nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - dev: true + ms@2.1.3: {} - /napi-build-utils@1.0.2: - resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} - dev: false + nanoid@3.3.7: {} - /negotiator@0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} - engines: {node: '>= 0.6'} - requiresBuild: true - dev: false + napi-build-utils@1.0.2: {} + + negotiator@0.6.3: optional: true - /node-abi@3.57.0: - resolution: {integrity: sha512-Dp+A9JWxRaKuHP35H77I4kCKesDy5HUDEmScia2FyncMTOXASMyg251F5PhFoDA5uqBrDDffiLpbqnrZmNXW+g==} - engines: {node: '>=10'} + node-abi@3.65.0: dependencies: - semver: 7.6.0 - dev: false + semver: 7.6.3 - /node-addon-api@7.1.0: - resolution: {integrity: sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==} - engines: {node: ^16 || ^18 || >= 20} - dev: false + node-addon-api@7.1.1: {} - /node-gyp@8.4.1: - resolution: {integrity: sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==} - engines: {node: '>= 10.12.0'} - hasBin: true - requiresBuild: true + node-gyp@8.4.1: dependencies: env-paths: 2.2.1 glob: 7.2.3 @@ -1550,156 +2930,92 @@ packages: nopt: 5.0.0 npmlog: 6.0.2 rimraf: 3.0.2 - semver: 7.6.0 + semver: 7.6.3 tar: 6.2.1 which: 2.0.2 transitivePeerDependencies: - bluebird - supports-color - dev: false optional: true - /nopt@5.0.0: - resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} - engines: {node: '>=6'} - hasBin: true - requiresBuild: true + nopt@5.0.0: dependencies: abbrev: 1.1.1 - dev: false optional: true - /normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - dev: true + normalize-path@3.0.0: {} - /npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + npm-run-path@5.3.0: dependencies: path-key: 4.0.0 - dev: true - /npmlog@6.0.2: - resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - requiresBuild: true + npmlog@6.0.2: dependencies: are-we-there-yet: 3.0.1 console-control-strings: 1.1.0 gauge: 4.0.4 set-blocking: 2.0.0 - dev: false optional: true - /once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + once@1.4.0: dependencies: wrappy: 1.0.2 - /onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} + onetime@6.0.0: dependencies: mimic-fn: 4.0.0 - dev: true - /p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 - dev: true - /p-limit@5.0.0: - resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} - engines: {node: '>=18'} + p-limit@5.0.0: dependencies: - yocto-queue: 1.0.0 - dev: true + yocto-queue: 1.1.1 - /p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} + p-locate@5.0.0: dependencies: p-limit: 3.1.0 - dev: true - /p-map@4.0.0: - resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} - engines: {node: '>=10'} - requiresBuild: true + p-map@4.0.0: dependencies: aggregate-error: 3.1.0 - dev: false optional: true - /path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - dev: true + path-exists@4.0.0: {} - /path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - requiresBuild: true - dev: false + path-is-absolute@1.0.1: optional: true - /path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - dev: true + path-key@3.1.1: {} - /path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - dev: true + path-key@4.0.0: {} - /pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - dev: true + pathe@1.1.2: {} - /pathval@1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} - dev: true + pathval@1.1.1: {} - /pathval@2.0.0: - resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} - engines: {node: '>= 14.16'} - dev: true + pathval@2.0.0: {} - /picocolors@1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - dev: true + picocolors@1.0.1: {} - /picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - dev: true + picomatch@2.3.1: {} - /pkg-types@1.0.3: - resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} + pkg-types@1.1.3: dependencies: - jsonc-parser: 3.2.1 - mlly: 1.6.1 + confbox: 0.1.7 + mlly: 1.7.1 pathe: 1.1.2 - dev: true - /postcss@8.4.38: - resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} - engines: {node: ^10 || ^12 || >=14} + postcss@8.4.41: dependencies: nanoid: 3.3.7 - picocolors: 1.0.0 + picocolors: 1.0.1 source-map-js: 1.2.0 - dev: true - /prebuild-install@7.1.2: - resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} - engines: {node: '>=10'} - hasBin: true + prando@6.0.1: {} + + prebuild-install@7.1.2: dependencies: detect-libc: 2.0.3 expand-template: 2.0.3 @@ -1707,252 +3023,156 @@ packages: minimist: 1.2.8 mkdirp-classic: 0.5.3 napi-build-utils: 1.0.2 - node-abi: 3.57.0 + node-abi: 3.65.0 pump: 3.0.0 rc: 1.2.8 simple-get: 4.0.1 tar-fs: 2.1.1 tunnel-agent: 0.6.0 - dev: false - /pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + prettier@3.3.3: {} + + pretty-format@29.7.0: dependencies: '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 - react-is: 18.2.0 - dev: true + react-is: 18.3.1 - /promise-inflight@1.0.1: - resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} - requiresBuild: true - peerDependencies: - bluebird: '*' - peerDependenciesMeta: - bluebird: - optional: true - dev: false + promise-inflight@1.0.1: optional: true - /promise-retry@2.0.1: - resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} - engines: {node: '>=10'} - requiresBuild: true + promise-retry@2.0.1: dependencies: err-code: 2.0.3 retry: 0.12.0 - dev: false optional: true - /pump@3.0.0: - resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + pump@3.0.0: dependencies: end-of-stream: 1.4.4 once: 1.4.0 - dev: false - /randombytes@2.1.0: - resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 - dev: true - /rc@1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} - hasBin: true + rc@1.2.8: dependencies: deep-extend: 0.6.0 ini: 1.3.8 minimist: 1.2.8 strip-json-comments: 2.0.1 - dev: false - /react-is@18.2.0: - resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - dev: true + react-is@18.3.1: {} - /readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} + readable-stream@3.6.2: dependencies: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 - dev: false - /readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} + readdirp@3.6.0: dependencies: picomatch: 2.3.1 - dev: true - /require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - dev: true + require-directory@2.1.1: {} - /retry@0.12.0: - resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} - engines: {node: '>= 4'} - requiresBuild: true - dev: false + resolve-pkg-maps@1.0.0: {} + + retry@0.12.0: optional: true - /rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - hasBin: true - requiresBuild: true + rimraf@3.0.2: dependencies: glob: 7.2.3 - dev: false optional: true - /rollup@4.14.3: - resolution: {integrity: sha512-ag5tTQKYsj1bhrFC9+OEWqb5O6VYgtQDO9hPDBMmIbePwhfSr+ExlcU741t8Dhw5DkPCQf6noz0jb36D6W9/hw==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true + rollup@4.20.0: dependencies: '@types/estree': 1.0.5 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.14.3 - '@rollup/rollup-android-arm64': 4.14.3 - '@rollup/rollup-darwin-arm64': 4.14.3 - '@rollup/rollup-darwin-x64': 4.14.3 - '@rollup/rollup-linux-arm-gnueabihf': 4.14.3 - '@rollup/rollup-linux-arm-musleabihf': 4.14.3 - '@rollup/rollup-linux-arm64-gnu': 4.14.3 - '@rollup/rollup-linux-arm64-musl': 4.14.3 - '@rollup/rollup-linux-powerpc64le-gnu': 4.14.3 - '@rollup/rollup-linux-riscv64-gnu': 4.14.3 - '@rollup/rollup-linux-s390x-gnu': 4.14.3 - '@rollup/rollup-linux-x64-gnu': 4.14.3 - '@rollup/rollup-linux-x64-musl': 4.14.3 - '@rollup/rollup-win32-arm64-msvc': 4.14.3 - '@rollup/rollup-win32-ia32-msvc': 4.14.3 - '@rollup/rollup-win32-x64-msvc': 4.14.3 + '@rollup/rollup-android-arm-eabi': 4.20.0 + '@rollup/rollup-android-arm64': 4.20.0 + '@rollup/rollup-darwin-arm64': 4.20.0 + '@rollup/rollup-darwin-x64': 4.20.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.20.0 + '@rollup/rollup-linux-arm-musleabihf': 4.20.0 + '@rollup/rollup-linux-arm64-gnu': 4.20.0 + '@rollup/rollup-linux-arm64-musl': 4.20.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.20.0 + '@rollup/rollup-linux-riscv64-gnu': 4.20.0 + '@rollup/rollup-linux-s390x-gnu': 4.20.0 + '@rollup/rollup-linux-x64-gnu': 4.20.0 + '@rollup/rollup-linux-x64-musl': 4.20.0 + '@rollup/rollup-win32-arm64-msvc': 4.20.0 + '@rollup/rollup-win32-ia32-msvc': 4.20.0 + '@rollup/rollup-win32-x64-msvc': 4.20.0 fsevents: 2.3.3 - dev: true - /safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-buffer@5.2.1: {} - /safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - requiresBuild: true - dev: false + safer-buffer@2.1.2: optional: true - /semver@7.6.0: - resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} - engines: {node: '>=10'} - hasBin: true - dependencies: - lru-cache: 6.0.0 - dev: false + semver@7.6.3: {} - /serialize-javascript@6.0.0: - resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} + serialize-javascript@6.0.2: dependencies: randombytes: 2.1.0 - dev: true - /set-blocking@2.0.0: - resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - requiresBuild: true - dev: false + set-blocking@2.0.0: optional: true - /shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 - dev: true - /shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - dev: true + shebang-regex@3.0.0: {} - /siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - dev: true + siginfo@2.0.0: {} - /signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - requiresBuild: true - dev: false + signal-exit@3.0.7: optional: true - /signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - dev: true + signal-exit@4.1.0: {} - /simple-concat@1.0.1: - resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - dev: false + simple-concat@1.0.1: {} - /simple-get@4.0.1: - resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + simple-get@4.0.1: dependencies: decompress-response: 6.0.0 once: 1.4.0 simple-concat: 1.0.1 - dev: false - /smart-buffer@4.2.0: - resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} - engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - requiresBuild: true - dev: false + slash@3.0.0: {} + + smart-buffer@4.2.0: optional: true - /socks-proxy-agent@6.2.1: - resolution: {integrity: sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==} - engines: {node: '>= 10'} - requiresBuild: true + socks-proxy-agent@6.2.1: dependencies: agent-base: 6.0.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) socks: 2.8.3 transitivePeerDependencies: - supports-color - dev: false optional: true - /socks@2.8.3: - resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==} - engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} - requiresBuild: true + socks@2.8.3: dependencies: ip-address: 9.0.5 smart-buffer: 4.2.0 - dev: false optional: true - /source-map-js@1.2.0: - resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} - engines: {node: '>=0.10.0'} - dev: true + source-map-js@1.2.0: {} - /sprintf-js@1.1.3: - resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} - requiresBuild: true - dev: false + sprintf-js@1.1.3: optional: true - /sqlite3@5.1.7: - resolution: {integrity: sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==} - requiresBuild: true - peerDependenciesMeta: - node-gyp: - optional: true + sqlite3@5.1.7: dependencies: bindings: 1.5.0 - node-addon-api: 7.1.0 + node-addon-api: 7.1.1 prebuild-install: 7.1.2 tar: 6.2.1 optionalDependencies: @@ -1960,102 +3180,74 @@ packages: transitivePeerDependencies: - bluebird - supports-color - dev: false - /ssri@8.0.1: - resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==} - engines: {node: '>= 8'} - requiresBuild: true + sqlite@5.1.1: {} + + ssri@8.0.1: dependencies: minipass: 3.3.6 - dev: false optional: true - /stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - dev: true + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 - /std-env@3.7.0: - resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} - dev: true + stackback@0.0.2: {} - /string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} + std-env@3.7.0: {} + + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - /string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 - dev: false - /strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 - /strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} - dev: true + strip-final-newline@3.0.0: {} - /strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} - dev: false + strip-json-comments@2.0.1: {} - /strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - dev: true + strip-json-comments@3.1.1: {} - /strip-literal@2.1.0: - resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==} + strip-literal@2.1.0: dependencies: js-tokens: 9.0.0 - dev: true - /supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: dependencies: has-flag: 4.0.0 - dev: true - /supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} + supports-color@8.1.1: dependencies: has-flag: 4.0.0 - /tar-fs@2.1.1: - resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} + tar-fs@2.1.1: dependencies: chownr: 1.1.4 mkdirp-classic: 0.5.3 pump: 3.0.0 tar-stream: 2.2.0 - dev: false - /tar-stream@2.2.0: - resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} - engines: {node: '>=6'} + tar-stream@2.2.0: dependencies: bl: 4.1.0 end-of-stream: 1.4.4 fs-constants: 1.0.0 inherits: 2.0.4 readable-stream: 3.6.2 - dev: false - /tar@6.2.1: - resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} - engines: {node: '>=10'} + tar@6.2.1: dependencies: chownr: 2.0.0 fs-minipass: 2.1.0 @@ -2063,252 +3255,235 @@ packages: minizlib: 2.1.2 mkdirp: 1.0.4 yallist: 4.0.0 - dev: false - /tinybench@2.7.0: - resolution: {integrity: sha512-Qgayeb106x2o4hNzNjsZEfFziw8IbKqtbXBjVh7VIZfBxfD5M4gWtpyx5+YTae2gJ6Y6Dz/KLepiv16RFeQWNA==} - dev: true + tinybench@2.9.0: {} - /tinypool@0.8.4: - resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} - engines: {node: '>=14.0.0'} - dev: true + tinypool@0.8.4: {} - /tinyspy@2.2.1: - resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} - engines: {node: '>=14.0.0'} - dev: true + tinypool@1.0.0: {} - /to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} + tinyrainbow@1.2.0: {} + + tinyspy@2.2.1: {} + + tinyspy@3.0.0: {} + + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 - dev: true - /tunnel-agent@0.6.0: - resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.14.15 + acorn: 8.12.1 + acorn-walk: 8.3.3 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.5.4 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + tsx@4.17.0: + dependencies: + esbuild: 0.23.0 + get-tsconfig: 4.7.6 + optionalDependencies: + fsevents: 2.3.3 + + tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 - dev: false - /type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - dev: true + type-detect@4.1.0: {} - /typescript@5.4.5: - resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} - engines: {node: '>=14.17'} - hasBin: true - dev: true + typescript@5.5.4: {} - /ufo@1.5.3: - resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} - dev: true + ufo@1.5.4: {} - /undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - dev: true + undici-types@5.26.5: {} - /unique-filename@1.1.1: - resolution: {integrity: sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==} - requiresBuild: true + undici-types@6.18.2: {} + + unique-filename@1.1.1: dependencies: unique-slug: 2.0.2 - dev: false optional: true - /unique-slug@2.0.2: - resolution: {integrity: sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==} - requiresBuild: true + unique-slug@2.0.2: dependencies: imurmurhash: 0.1.4 - dev: false optional: true - /util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - dev: false + util-deprecate@1.0.2: {} - /vite-node@1.5.0: - resolution: {integrity: sha512-tV8h6gMj6vPzVCa7l+VGq9lwoJjW8Y79vst8QZZGiuRAfijU+EEWuc0kFpmndQrWhMMhet1jdSF+40KSZUqIIw==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true + v8-compile-cache-lib@3.0.1: {} + + vite-node@1.6.0(@types/node@20.14.15): dependencies: cac: 6.7.14 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) pathe: 1.1.2 - picocolors: 1.0.0 - vite: 5.2.9 + picocolors: 1.0.1 + vite: 5.4.1(@types/node@20.14.15) transitivePeerDependencies: - '@types/node' - less - lightningcss - sass + - sass-embedded - stylus - sugarss - supports-color - terser - dev: true - /vite@5.2.9: - resolution: {integrity: sha512-uOQWfuZBlc6Y3W/DTuQ1Sr+oIXWvqljLvS881SVmAj00d5RdgShLcuXWxseWPd4HXwiYBFW/vXHfKFeqj9uQnw==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true + vite-node@2.0.5(@types/node@22.3.0): + dependencies: + cac: 6.7.14 + debug: 4.3.6(supports-color@8.1.1) + pathe: 1.1.2 + tinyrainbow: 1.2.0 + vite: 5.4.1(@types/node@22.3.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite@5.4.1(@types/node@20.14.15): dependencies: - esbuild: 0.20.2 - postcss: 8.4.38 - rollup: 4.14.3 + esbuild: 0.21.5 + postcss: 8.4.41 + rollup: 4.20.0 optionalDependencies: + '@types/node': 20.14.15 fsevents: 2.3.3 - dev: true - /vitest@1.5.0: - resolution: {integrity: sha512-d8UKgR0m2kjdxDWX6911uwxout6GHS0XaGH1cksSIVVG8kRlE7G7aBw7myKQCvDI5dT4j7ZMa+l706BIORMDLw==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 1.5.0 - '@vitest/ui': 1.5.0 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@types/node': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true + vite@5.4.1(@types/node@22.3.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.4.41 + rollup: 4.20.0 + optionalDependencies: + '@types/node': 22.3.0 + fsevents: 2.3.3 + + vitest@1.6.0(@types/node@20.14.15): dependencies: - '@vitest/expect': 1.5.0 - '@vitest/runner': 1.5.0 - '@vitest/snapshot': 1.5.0 - '@vitest/spy': 1.5.0 - '@vitest/utils': 1.5.0 - acorn-walk: 8.3.2 - chai: 4.4.1 - debug: 4.3.4(supports-color@8.1.1) + '@vitest/expect': 1.6.0 + '@vitest/runner': 1.6.0 + '@vitest/snapshot': 1.6.0 + '@vitest/spy': 1.6.0 + '@vitest/utils': 1.6.0 + acorn-walk: 8.3.3 + chai: 4.5.0 + debug: 4.3.6(supports-color@8.1.1) execa: 8.0.1 local-pkg: 0.5.0 - magic-string: 0.30.9 + magic-string: 0.30.11 pathe: 1.1.2 - picocolors: 1.0.0 + picocolors: 1.0.1 std-env: 3.7.0 strip-literal: 2.1.0 - tinybench: 2.7.0 + tinybench: 2.9.0 tinypool: 0.8.4 - vite: 5.2.9 - vite-node: 1.5.0 - why-is-node-running: 2.2.2 + vite: 5.4.1(@types/node@20.14.15) + vite-node: 1.6.0(@types/node@20.14.15) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 20.14.15 transitivePeerDependencies: - less - lightningcss - sass + - sass-embedded - stylus - sugarss - supports-color - terser - dev: true - /which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true + vitest@2.0.5(@types/node@22.3.0): + dependencies: + '@ampproject/remapping': 2.3.0 + '@vitest/expect': 2.0.5 + '@vitest/pretty-format': 2.0.5 + '@vitest/runner': 2.0.5 + '@vitest/snapshot': 2.0.5 + '@vitest/spy': 2.0.5 + '@vitest/utils': 2.0.5 + chai: 5.1.1 + debug: 4.3.6(supports-color@8.1.1) + execa: 8.0.1 + magic-string: 0.30.11 + pathe: 1.1.2 + std-env: 3.7.0 + tinybench: 2.9.0 + tinypool: 1.0.0 + tinyrainbow: 1.2.0 + vite: 5.4.1(@types/node@22.3.0) + vite-node: 2.0.5(@types/node@22.3.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.3.0 + transitivePeerDependencies: + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + which@2.0.2: dependencies: isexe: 2.0.0 - /why-is-node-running@2.2.2: - resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} - engines: {node: '>=8'} - hasBin: true + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 - dev: true - /wide-align@1.1.5: - resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} - requiresBuild: true + wide-align@1.1.5: dependencies: string-width: 4.2.3 - dev: false optional: true - /workerpool@6.2.1: - resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==} - dev: true + workerpool@6.5.1: {} - /wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: true - /wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + wrappy@1.0.2: {} - /y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - dev: true + y18n@5.0.8: {} - /yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - dev: false + yallist@4.0.0: {} - /yargs-parser@20.2.4: - resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} - engines: {node: '>=10'} - dev: true + yargs-parser@20.2.9: {} - /yargs-unparser@2.0.0: - resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} - engines: {node: '>=10'} + yargs-unparser@2.0.0: dependencies: camelcase: 6.3.0 decamelize: 4.0.0 flat: 5.0.2 is-plain-obj: 2.1.0 - dev: true - /yargs@16.2.0: - resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} - engines: {node: '>=10'} + yargs@16.2.0: dependencies: cliui: 7.0.4 escalade: 3.1.2 @@ -2316,15 +3491,10 @@ packages: require-directory: 2.1.1 string-width: 4.2.3 y18n: 5.0.8 - yargs-parser: 20.2.4 - dev: true + yargs-parser: 20.2.9 - /yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - dev: true + yn@3.1.1: {} - /yocto-queue@1.0.0: - resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} - engines: {node: '>=12.20'} - dev: true + yocto-queue@0.1.0: {} + + yocto-queue@1.1.1: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..ddacb9c --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - 'benchmarks' + - packages/** diff --git a/src/api.ts b/src/api.ts deleted file mode 100644 index cad89f1..0000000 --- a/src/api.ts +++ /dev/null @@ -1,205 +0,0 @@ -import { SqliteArguments, SqliteValue } from "./common.js"; - -export type SqliteDatabase = SqliteConnectionPool & SqliteConnection; - -export interface SqliteConnectionPool { - /** - * Reserve a connection for the duration of the callback. - * - * @param callback - * @param options - */ - reserveConnection( - callback: (connection: SqliteConnection) => Promise, - options?: ReserveConnectionOptions - ): Promise; -} - -export interface ReserveConnectionOptions { - readonly?: boolean; -} - -export interface QueryInterface { - /** - * Advanced usage: Prepare a query. The query can be executed directly on the connection, - * or in a transaction. - * - * The query must be disposed before closing the connection. - */ - prepare(query: string): PreparedQuery; - - /** - * Convenience method, same as query(query, args).execute(options). - * - * When called on a connection pool, uses readonly: true by default. - */ - execute( - query: string | PreparedQuery, - args?: SqliteArguments | undefined, - options?: ExecuteOptions & ReserveConnectionOptions - ): Promise>; - - /** - * Convenience method, same as query(query, args).executeStreamed(options). - */ - executeStreamed( - query: string | PreparedQuery, - args: SqliteArguments | undefined, - options?: StreamedExecuteOptions & ReserveConnectionOptions - ): AsyncGenerator>; - - /** - * Convenience method, same as query(query, args).select(options). - * - * When called on a connection pool, uses readonly: true by default. - */ - select( - query: string | PreparedQuery, - args?: SqliteArguments | undefined, - options?: QueryOptions & ReserveConnectionOptions - ): Promise; -} - -export interface SqliteConnection extends QueryInterface { - /** - * Start a transaction. - */ - transaction( - callback: (tx: SqliteTransaction) => Promise, - options?: TransactionOptions - ): Promise; - - /** - * Listen for individual update events as they occur. - * - * For efficiency, multiple updates may be batched together. - * - * These events may be batched together for efficiency. - * Either way, all events in a transaction must be emitted before - * "onTransactionClose" is emitted for that transaction. - * - * Use options.tables to limit the events to specific tables. - * - * Use options.batchLimit == 1 to disable event batching. - */ - onUpdate( - listener: UpdateListener, - options?: { tables?: string[]; batchLimit?: number } - ): () => void; - - /** - * Listen for transaction events. Fired when either: - * 1. Transaction is rolled back. - * 2. Transaction is committed and persisted. - * - * @param listener - */ - onTransactionClose(listener: TransactionCloseListener): () => void; - - /** - * Listen for committed updates to tables. - * - * This can be achieved by combining `onUpdate()` and `onTransactionClose()`, although - * implementations may optimize this version for large transactions. - */ - onTablesChanged(listener: TablesChangedListener): () => void; - - close(): Promise; -} - -export interface BatchedUpdateEvent { - events: UpdateEvent[]; -} - -export interface UpdateEvent { - table: string; - type: "insert" | "update" | "delete"; - rowId: bigint; -} - -export interface TablesChangedEvent { - tables: string[]; -} - -export type UpdateListener = (event: BatchedUpdateEvent) => void; -export type TablesChangedListener = (event: TablesChangedEvent) => void; - -export interface TransactionCloseEvent { - rollback: boolean; -} - -export type TransactionCloseListener = (event: TransactionCloseEvent) => void; - -export interface SqliteTransaction extends QueryInterface { - /** - * Returns true if auto-commit is enabled. - * This means the database is not currently in a transaction. - * This may be true even if a transaction lock is still held, - * when the transaction has been committed or rolled back. - */ - getAutoCommit(): Promise; - - rollback(): Promise; -} - -export interface TransactionOptions { - /** - * See SQLite docs on the type. - * - * For WAL mode, immediate and exclusive are the same. - * - * Read transactions should use "deferred". - */ - type?: "exclusive" | "immediate" | "deferred"; -} - -export interface ResultSet { - rowId?: number; - changes?: number; - - columns: (keyof T)[]; - raw_rows: SqliteValue[][]; - - /** - * Convenience method to combine columns and rows into objects. - */ - rows: T[]; -} - -export interface SqliteQuery { - // Implementation note: The implementation only needs to provide one execute method, - // The rest can be provided by utilities. - - executeStreamed(options?: StreamedExecuteOptions): AsyncGenerator; - - /** - * Convenience method. - */ - execute(options?: ExecuteOptions): Promise>; - - /** - * Convenience method. - * - * Same as execute, but returns an array of row objects directly. - */ - select(options?: QueryOptions): Promise; -} - -export interface PreparedQuery { - dispose(): Promise; -} - -export interface QueryOptions { - /** true to return all integers as bigint */ - bigint?: boolean; -} - -export interface ExecuteOptions extends QueryOptions { - includeRowId?: boolean; - includeChanges?: boolean; -} - -export interface StreamedExecuteOptions extends ExecuteOptions { - /** Size limit in bytes for each chunk */ - chunkSize?: number; -} diff --git a/src/common.ts b/src/common.ts deleted file mode 100644 index 6995fed..0000000 --- a/src/common.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type SqliteValue = null | string | number | bigint | Uint8Array; -export type SqliteArguments = - | SqliteValue[] - | Record - | null; diff --git a/src/driver-api.ts b/src/driver-api.ts deleted file mode 100644 index 5806602..0000000 --- a/src/driver-api.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { SqliteArguments, SqliteValue } from "./common.js"; - -export interface SqliteDriverConnection { - run(query: string, args?: SqliteArguments): Promise; - runWithResults(query: string, args?: SqliteArguments): Promise; - - selectStreamed( - query: string, - args?: SqliteArguments, - options?: ExecuteOptions - ): AsyncIterable; - - selectAll( - query: string, - args?: SqliteArguments, - options?: ExecuteOptions - ): Promise; - - onUpdate( - listener: UpdateListener, - options?: { tables?: string[]; batchLimit?: number } - ): () => void; - - close(): Promise; -} - -export interface SqliteDriverConnectionPool { - /** - * Reserve a connection for exclusive use. - * - * If there is no available connection, this will wait until one is available. - * @param options - */ - reserveConnection( - options?: ReserveConnectionOptions - ): Promise; - - close(): Promise; - - onUpdate( - listener: UpdateListener, - options?: { tables?: string[]; batchLimit?: number } - ): () => void; -} - -export type UpdateListener = (event: BatchedUpdateEvent) => void; - -export interface BatchedUpdateEvent { - events: UpdateEvent[]; -} - -export interface UpdateEvent { - table: string; - type: "insert" | "update" | "delete"; - rowId: bigint; -} - -export interface ReservedConnection { - connection: SqliteDriverConnection; - release(): void; -} - -export interface ReserveConnectionOptions { - readonly?: boolean; - signal?: AbortSignal; -} - -export interface RunResults { - changes: number; - lastInsertRowId: bigint; -} - -export interface ResultSet { - columns: string[]; - rows: SqliteValue[][]; -} - -export interface ExecuteOptions { - chunkSize?: number; - bigint?: boolean; -} diff --git a/src/driver-util.ts b/src/driver-util.ts deleted file mode 100644 index bb70975..0000000 --- a/src/driver-util.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { - ReserveConnectionOptions, - ReservedConnection, - SqliteDriverConnection, - SqliteDriverConnectionPool, - UpdateListener, -} from "./driver-api.js"; - -interface QueuedItem { - reserved: ReservedConnection | null; - resolve: (reserved: ReservedConnection) => void; - reject: (err: any) => void; -} - -export class SingleConnectionPool implements SqliteDriverConnectionPool { - private queue: QueuedItem[] = []; - private inUse: ReservedConnection | null = null; - - constructor(private connection: SqliteDriverConnection) {} - - async close() { - await this.connection.close(); - } - - reserveConnection( - options?: ReserveConnectionOptions - ): Promise { - if (options?.signal?.aborted) { - throw new Error("Aborted"); - } - const reserved: ReservedConnection = { - connection: this.connection, - release: () => { - if (this.inUse === reserved) { - this.inUse = null; - Promise.resolve().then(() => this.next()); - } - }, - }; - - if (this.inUse == null) { - this.inUse = reserved; - return Promise.resolve(reserved); - } else { - const promise = new Promise((resolve, reject) => { - const item: QueuedItem = { - reserved, - resolve, - reject, - }; - this.queue.push(item); - options?.signal?.addEventListener( - "abort", - () => { - item.reserved = null; - item.reject(new Error("Aborted")); - }, - { once: true } - ); - }); - - return promise; - } - } - - private next() { - while (true) { - const item = this.queue.shift(); - if (item == null) { - break; - } - - if (item.reserved == null) { - // Aborted - continue; - } - - item.resolve(item.reserved); - break; - } - } - - onUpdate( - listener: UpdateListener, - options?: - | { tables?: string[] | undefined; batchLimit?: number | undefined } - | undefined - ): () => void { - return this.connection.onUpdate(listener, options); - } -} - -export interface DriverFactory { - openConnection( - options?: ReserveConnectionOptions - ): Promise; -} - -interface QueuedPoolItem { - resolve: (reserved: ReservedConnection) => void; - reject: (err: any) => void; -} - -class MultiConnectionPool implements SqliteDriverConnectionPool { - private _allConnections = new Set(); - private _availableReadConnections: SqliteDriverConnection[] = []; - private _queue: QueuedPoolItem[] = []; - private _maxConnections: number = 5; - - constructor(private factory: DriverFactory) {} - - reserveConnection( - options?: ReserveConnectionOptions | undefined - ): Promise { - const promise = new Promise((resolve, reject) => { - this._queue.push({ - resolve, - reject, - }); - }); - - Promise.resolve().then(() => this.next()); - - return promise; - } - - private async expandPool( - options?: ReserveConnectionOptions - ): Promise { - const connection = await this.factory.openConnection(options); - this._allConnections.add(connection); - return connection; - } - - private async next() { - if (this._queue.length == 0) { - // Nothing queued - return; - } - - if ( - this._availableReadConnections.length == 0 && - this._allConnections.size >= this._maxConnections - ) { - // No connections available - return; - } - - const item = this._queue.shift()!; - - let connection: SqliteDriverConnection; - if (this._availableReadConnections.length == 0) { - // FIXME: prevent opening more than the max - connection = await this.expandPool(); - } else { - connection = this._availableReadConnections.shift()!; - } - - item.resolve({ - connection, - release: () => { - this._availableReadConnections.push(connection); - Promise.resolve().then(() => this.next()); - }, - }); - } - - async close() { - // TODO: Wait for statements to finish - for (let con of this._allConnections) { - await con.close(); - } - } - - onUpdate( - listener: UpdateListener, - options?: - | { tables?: string[] | undefined; batchLimit?: number | undefined } - | undefined - ): () => void { - // No-op - return () => {}; - } -} - -export class ReadWriteConnectionPool implements SqliteDriverConnectionPool { - private writePool?: SqliteDriverConnectionPool; - private readPool: SqliteDriverConnectionPool; - - private initPromise: Promise; - - constructor(private factory: DriverFactory) { - this.readPool = new MultiConnectionPool(factory); - - this.initPromise = factory.openConnection().then((con) => { - this.writePool = new SingleConnectionPool(con); - }); - } - - async reserveConnection( - options?: ReserveConnectionOptions - ): Promise { - await this.initPromise; - - if (options?.readonly) { - return this.readPool.reserveConnection(options); - } else { - return this.writePool!.reserveConnection(options); - } - } - - async close() { - await this.readPool.close(); - await this.writePool?.close(); - } - - onUpdate( - listener: UpdateListener, - options?: - | { tables?: string[] | undefined; batchLimit?: number | undefined } - | undefined - ): () => void { - return this.writePool!.onUpdate(listener, options); - } -} diff --git a/src/drivers/better-sqlite3-async-driver.ts b/src/drivers/better-sqlite3-async-driver.ts deleted file mode 100644 index b39de9e..0000000 --- a/src/drivers/better-sqlite3-async-driver.ts +++ /dev/null @@ -1,117 +0,0 @@ -import type * as bsqlite from "better-sqlite3"; -import * as worker_threads from "worker_threads"; -import { - ResultSet, - RunResults, - SqliteDriverConnection, - SqliteDriverConnectionPool, - UpdateListener, -} from "../driver-api.js"; - -import { EventIterator } from "event-iterator"; -import { StreamedExecuteOptions } from "../api.js"; -import { SqliteArguments } from "../common.js"; -import { ReadWriteConnectionPool } from "../driver-util.js"; - -export function betterSqliteAsyncPool( - path: string, - poolOptions?: bsqlite.Options -): SqliteDriverConnectionPool { - return new ReadWriteConnectionPool({ - async openConnection(options) { - return new BetterSqliteAsyncConnection(path, { - ...poolOptions, - readonly: (poolOptions?.readonly ?? options?.readonly) || false, - }); - }, - }); -} - -export class BetterSqliteAsyncConnection implements SqliteDriverConnection { - worker: worker_threads.Worker; - - constructor(path: string, options?: bsqlite.Options) { - const worker = new worker_threads.Worker( - require.resolve("./better-sqlite3-worker.js") - ); - worker.postMessage(["open", { path, options }]); - worker.addListener("error", (err) => { - console.error("worker error", err); - }); - this.worker = worker; - } - - async close() { - this.worker.postMessage(["close"]); - await new Promise((resolve, reject) => { - this.worker.once("message", (value) => { - resolve(); - }); - }); - await this.worker.terminate(); - } - - async run(query: string, args: SqliteArguments) { - this.worker.postMessage(["run", { query, args }]); - return new Promise((resolve, reject) => { - this.worker.once("message", (value) => { - resolve(); - }); - }); - } - async runWithResults(query: string, args: SqliteArguments) { - this.worker.postMessage(["run", { query, args }]); - return new Promise((resolve, reject) => { - this.worker.once("message", (value) => { - resolve(value); - }); - }); - } - async *selectStreamed( - query: string, - args: SqliteArguments, - options: StreamedExecuteOptions - ) { - this.worker.postMessage(["stream", { query, args, options }]); - const iter = new EventIterator(({ push }) => { - this.worker.addListener("message", push); - return () => this.worker.removeListener("message", push); - }); - let columns: string[] = []; - for await (let message of iter) { - const [type, args] = message as any; - if (type == "columns") { - columns = args; - } else if (type == "rows") { - yield { columns, rows: args }; - } else if (type == "close") { - break; - } - } - } - - async selectAll( - query: string, - args: SqliteArguments, - options: StreamedExecuteOptions - ): Promise { - let results: ResultSet | undefined = undefined; - for await (let rs of this.selectStreamed(query, args, options)) { - if (results == null) { - results = rs; - } else { - results!.rows.push(...rs.rows); - } - } - return results!; - } - - onUpdate( - listener: UpdateListener, - options?: - | { tables?: string[] | undefined; batchLimit?: number | undefined } - | undefined - ): () => void { - throw new Error("Not implemented"); - } -} diff --git a/src/drivers/better-sqlite3-driver.ts b/src/drivers/better-sqlite3-driver.ts deleted file mode 100644 index fbebba3..0000000 --- a/src/drivers/better-sqlite3-driver.ts +++ /dev/null @@ -1,164 +0,0 @@ -import type * as bsqlite from "better-sqlite3"; -import { SqliteArguments, SqliteValue } from "../common.js"; -import { - ExecuteOptions, - ResultSet, - RunResults, - SqliteDriverConnection, - SqliteDriverConnectionPool, - UpdateListener, -} from "../driver-api.js"; -const Database = require("better-sqlite3"); - -import { ReadWriteConnectionPool } from "../driver-util.js"; - -export function betterSqlitePool( - path: string, - poolOptions?: bsqlite.Options -): SqliteDriverConnectionPool { - return new ReadWriteConnectionPool({ - async openConnection(options) { - return new BetterSqliteConnection(path, { - ...poolOptions, - readonly: (poolOptions?.readonly ?? options?.readonly) || false, - }); - }, - }); -} - -export class BetterSqliteConnection implements SqliteDriverConnection { - con: bsqlite.Database; - - constructor(path: string, options?: bsqlite.Options) { - this.con = new Database(path, options); - } - - async close() { - this.con.close(); - } - - async selectAll( - query: string, - args?: SqliteArguments | undefined, - options?: ExecuteOptions | undefined - ): Promise { - const statement = this.con.prepare(query); - const bindArgs = args == undefined ? [] : [args]; - if (!statement.reader) { - statement.run(...bindArgs); - return { columns: [], rows: [] }; - } - statement.raw(); - if (options?.bigint) { - statement.safeIntegers(); - } - const columns = statement.columns().map((c) => c.name); - const rows = statement.all(...bindArgs) as SqliteValue[][]; - return { - columns, - rows, - }; - } - - async *selectStreamed( - query: string, - args?: SqliteArguments, - options?: ExecuteOptions - ): AsyncGenerator { - const bindArgs = args == undefined ? [] : [args]; - const statement = this.con.prepare(query); - if (!statement.reader) { - statement.run(...bindArgs); - return; - } - statement.raw(); - if (options?.bigint) { - statement.safeIntegers(); - } - const columns = statement.columns().map((c) => c.name); - let buffer: SqliteValue[][] = []; - let didYield = false; - for (let row of statement.iterate(...bindArgs)) { - buffer.push(row as SqliteValue[]); - if (buffer.length > (options?.chunkSize ?? 10)) { - yield { - columns, - rows: buffer, - }; - didYield = true; - buffer = []; - } - } - if (buffer.length > 0 || !didYield) { - yield { - columns, - rows: buffer, - }; - } - } - - async run(query: string, args?: SqliteArguments): Promise { - const bindArgs = args == undefined ? [] : [args]; - const statement = this.con.prepare(query); - statement.run(...bindArgs); - } - - async runWithResults( - query: string, - args?: SqliteArguments - ): Promise { - const statement = this.con.prepare(query); - const bindArgs = args == undefined ? [] : [args]; - const r = statement.run(...bindArgs); - return { - changes: r.changes, - lastInsertRowId: BigInt(r.lastInsertRowid), - }; - } - - dispose(): void { - // No-op - } - - onUpdate( - listener: UpdateListener, - options?: - | { tables?: string[] | undefined; batchLimit?: number | undefined } - | undefined - ): () => void { - // Proof-of-concept implementation, based on the idea here: - // https://github.com/WiseLibs/better-sqlite3/issues/62 - // TODO: - // 1. Handle multiple registrations. - // 2. Don't re-register triggers. - // 3. De-register listener. - // 4. Batching. - // - // More fundamental limitations: - // 1. The table needs to exist before registering the listener. - // 2. Deleting and re-creating the same will dereigster the listener for that table. - - this.con.function("_logger", function (table: any, type: any, rowid: any) { - listener({ events: [{ table, rowId: rowid, type }] }); - }); - let tables = options?.tables; - if (tables == null) { - tables = this.con - .prepare(`select name from sqlite_master where type = 'table'`) - .all() - .map((row) => (row as any).name as string); - } - for (let table of tables) { - this.con.exec( - `CREATE TEMPORARY TRIGGER IF NOT EXISTS _logger_notification_${table}__update AFTER UPDATE ON ${table} BEGIN SELECT _logger('${table}', 'update', NEW.rowid); END` - ); - this.con.exec( - `CREATE TEMPORARY TRIGGER IF NOT EXISTS _logger_notification_${table}__insert AFTER INSERT ON ${table} BEGIN SELECT _logger('${table}', 'insert', NEW.rowid); END` - ); - this.con.exec( - `CREATE TEMPORARY TRIGGER IF NOT EXISTS _logger_notification_${table}__delete AFTER DELETE ON ${table} BEGIN SELECT _logger('${table}', 'delete', OLD.rowid); END` - ); - } - return () => {}; - } -} diff --git a/src/drivers/better-sqlite3-worker.js b/src/drivers/better-sqlite3-worker.js deleted file mode 100644 index dff3cde..0000000 --- a/src/drivers/better-sqlite3-worker.js +++ /dev/null @@ -1,53 +0,0 @@ -import Database from "better-sqlite3"; - -import * as worker_threads from "worker_threads"; - -let db = null; - -worker_threads.parentPort.addListener("message", (value) => { - const [message, args] = value; - if (message == "open") { - db = new Database(args.path); - } else if (message == "close") { - db?.close(); - worker_threads.parentPort.postMessage(["closed"]); - } else if (message == "run") { - const bindArgs = args.args == undefined ? [] : [args.args]; - const rs = db.prepare(args.query).run(...bindArgs); - worker_threads.parentPort.postMessage({ - changes: rs.changes, - lastInsertRowId: BigInt(rs.lastInsertRowid), - }); - } else if (message == "selectAll") { - const bindArgs = args.args == undefined ? [] : [args.args]; - const rs = db.prepare(args.query).all(...bindArgs); - worker_threads.parentPort.postMessage(rs); - } else if (message == "stream") { - const bindArgs = args.args == undefined ? [] : [args.args]; - const statement = db.prepare(args.query); - if (!statement.reader) { - statement.run(...bindArgs); - worker_threads.parentPort.postMessage(["close"]); - return; - } - if (args.options?.bigint) { - statement.safeIntegers(); - } - statement.raw(); - const columns = statement.columns().map((c) => c.name); - worker_threads.parentPort.postMessage(["columns", columns]); - - let buffer = []; - for (let row of statement.iterate(...bindArgs)) { - buffer.push(row); - if (buffer.length > 10) { - worker_threads.parentPort.postMessage(["rows", buffer]); - buffer = []; - } - } - if (buffer.length > 0) { - worker_threads.parentPort.postMessage(["rows", buffer]); - } - worker_threads.parentPort.postMessage(["close"]); - } -}); diff --git a/src/drivers/sqlite3-driver.ts b/src/drivers/sqlite3-driver.ts deleted file mode 100644 index da8c1fd..0000000 --- a/src/drivers/sqlite3-driver.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { Database, OPEN_CREATE, OPEN_READONLY, OPEN_READWRITE } from "sqlite3"; -import { SqliteArguments, SqliteValue } from "../common.js"; -import { - ExecuteOptions, - ResultSet, - RunResults, - SqliteDriverConnection, - SqliteDriverConnectionPool, - UpdateListener, -} from "../driver-api.js"; - -import { ReadWriteConnectionPool } from "../driver-util.js"; - -export function sqlite3Pool( - path: string, - poolOptions?: { readonly?: boolean } -): SqliteDriverConnectionPool { - return new ReadWriteConnectionPool({ - async openConnection(options) { - return new Sqlite3Connection(path, { - ...poolOptions, - readonly: (poolOptions?.readonly ?? options?.readonly) || false, - }); - }, - }); -} - -/** - * This is a partial implementation, with several limitations: - * 1. bigint is not supported - * 2. column names are not available when zero rows are returned - * 3. duplicate column names are not supported - * - * This is due to limitations in the underlying driver. Rather use - * other drivers when possible. - */ -export class Sqlite3Connection implements SqliteDriverConnection { - con: Database; - - constructor(path: string, options?: { readonly?: boolean }) { - let mode = options?.readonly ? OPEN_READONLY : OPEN_CREATE | OPEN_READWRITE; - this.con = new Database(path, mode); - } - - async selectAll( - query: string, - args?: SqliteArguments | undefined, - options?: ExecuteOptions | undefined - ): Promise { - const bindArgs = args == undefined ? [] : [args]; - - return new Promise((resolve, reject) => { - this.con.all(query, bindArgs, (error, rows) => { - if (error) { - reject(error); - } else { - resolve(transformResults(rows, options)); - } - }); - }); - } - - async *selectStreamed( - query: string, - args?: SqliteArguments, - options?: ExecuteOptions - ): AsyncGenerator { - yield this.selectAll(query, args, options); - } - - async run(query: string, args?: SqliteArguments): Promise { - await this.runWithResults(query, args); - } - - async runWithResults( - query: string, - args?: SqliteArguments - ): Promise { - const bindArgs = args == undefined ? [] : [args]; - return new Promise((resolve, reject) => { - this.con.run(query, bindArgs, function (error) { - if (error) { - reject(error); - } else { - resolve({ - changes: this.changes, - lastInsertRowId: BigInt(this.lastID), - }); - } - }); - }); - } - - async close() { - this.con.close(); - } - - onUpdate( - listener: UpdateListener, - options?: - | { tables?: string[] | undefined; batchLimit?: number | undefined } - | undefined - ): () => void { - throw new Error("Not implemented"); - } -} - -function transformResults(rows: any[], options?: ExecuteOptions): ResultSet { - if (rows.length == 0) { - return { - columns: [], - rows: [], - }; - } else { - const columns = Object.keys(rows[0]); - return { - columns, - rows: rows.map((row) => { - if (options?.bigint) { - return columns.map((column) => { - const val = row[column]; - if (typeof val == "number") { - return BigInt(val); - } else { - return val; - } - }); - } else { - return columns.map((column) => row[column]); - } - }), - }; - } -} diff --git a/src/impl.ts b/src/impl.ts deleted file mode 100644 index 65a498e..0000000 --- a/src/impl.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { - BatchedUpdateEvent, - ExecuteOptions, - PreparedQuery, - QueryInterface, - QueryOptions, - ReserveConnectionOptions, - ResultSet, - SqliteConnection, - SqliteConnectionPool, - SqliteQuery, - SqliteTransaction, - StreamedExecuteOptions, - TablesChangedEvent, - TransactionCloseEvent, - TransactionOptions, -} from "./api.js"; -import { SqliteArguments, SqliteValue } from "./common.js"; -import { - SqliteDriverConnection, - SqliteDriverConnectionPool, -} from "./driver-api.js"; - -export class ConnectionPoolImpl - implements SqliteConnectionPool, QueryInterface -{ - constructor(private driver: SqliteDriverConnectionPool) {} - prepare(query: string): PreparedQuery { - throw new Error("Method not implemented."); - } - - execute( - query: string | PreparedQuery, - args?: SqliteArguments | undefined, - options?: (ExecuteOptions & ReserveConnectionOptions) | undefined - ): Promise> { - return this.reserveConnection((con) => { - return con.execute(query, args, options); - }); - } - - async *executeStreamed( - query: string | PreparedQuery, - args?: SqliteArguments | undefined, - options?: (StreamedExecuteOptions & ReserveConnectionOptions) | undefined - ): AsyncGenerator, any, unknown> { - const con = await this.driver.reserveConnection(options ?? {}); - try { - const c2 = new ConnectionImpl(con.connection); - for await (let chunk of c2.executeStreamed(query, args, options)) { - yield chunk; - } - } finally { - con.release(); - } - } - - select( - query: string | PreparedQuery, - args?: SqliteArguments | undefined, - options?: (QueryOptions & ReserveConnectionOptions) | undefined - ): Promise { - return this.reserveConnection((con) => { - return con.select(query, args, options); - }); - } - - async reserveConnection( - callback: (connection: SqliteConnection) => Promise, - options?: ReserveConnectionOptions | undefined - ): Promise { - const con = await this.driver.reserveConnection(options ?? {}); - try { - return await callback(new ConnectionImpl(con.connection)); - } finally { - con.release(); - } - } -} - -export class ConnectionImpl implements SqliteConnection { - constructor(private driver: SqliteDriverConnection) {} - - async transaction( - callback: (tx: SqliteTransaction) => Promise, - options: TransactionOptions - ): Promise { - await this.driver.run("BEGIN"); - try { - const tx = new TransactionImpl(this); - const result = await callback(tx); - - await this.driver.run("COMMIT"); - return result; - } catch (e) { - await this.driver.run("ROLLBACK"); - throw e; - } - } - - onUpdate( - listener: (event: BatchedUpdateEvent) => void, - options?: - | { tables?: string[] | undefined; batchLimit?: number | undefined } - | undefined - ): () => void { - throw new Error("Method not implemented."); - } - onTransactionClose( - listener: (event: TransactionCloseEvent) => void - ): () => void { - throw new Error("Method not implemented."); - } - onTablesChanged(listener: (event: TablesChangedEvent) => void): () => void { - throw new Error("Method not implemented."); - } - close(): Promise { - throw new Error("Method not implemented."); - } - prepare(query: string): PreparedQuery { - throw new Error("Method not implemented."); - } - query( - query: string | PreparedQuery, - args: SqliteArguments - ): SqliteQuery { - throw new Error("Method not implemented."); - } - async execute( - query: string | PreparedQuery, - args: SqliteArguments | undefined, - options?: (ExecuteOptions & ReserveConnectionOptions) | undefined - ): Promise> { - let result: ResultSet | null = null; - - for await (let rs of this.driver.selectStreamed(query as string, args)) { - if (result == null) { - result = new ResultSetImpl(rs.columns, [...rs.rows]); - } else { - result.raw_rows.push(...rs.rows); - } - } - return result!; - } - - async *executeStreamed( - query: string | PreparedQuery, - args: SqliteArguments | undefined, - options?: (StreamedExecuteOptions & ReserveConnectionOptions) | undefined - ): AsyncGenerator, void, unknown> { - for await (let rs of this.driver.selectStreamed( - query as string, - args, - options - )) { - yield new ResultSetImpl(rs.columns, rs.rows); - } - } - - async select( - query: string | PreparedQuery, - args?: SqliteArguments | undefined, - options?: (QueryOptions & ReserveConnectionOptions) | undefined - ): Promise { - const rs = await this.execute(query, args, options); - return rs.rows; - } -} - -export class TransactionImpl implements SqliteTransaction { - constructor(private con: ConnectionImpl) {} - - getAutoCommit(): Promise { - throw new Error("Method not implemented."); - } - - async rollback(): Promise { - await this.select("ROLLBACK"); - } - - prepare(query: string): PreparedQuery { - return this.con.prepare(query); - } - - query( - query: string | PreparedQuery, - args: SqliteArguments - ): SqliteQuery { - return this.con.query(query, args); - } - - execute( - query: string | PreparedQuery, - args: SqliteArguments | undefined, - options?: (ExecuteOptions & ReserveConnectionOptions) | undefined - ): Promise> { - return this.con.execute(query, args); - } - - executeStreamed( - query: string | PreparedQuery, - args: SqliteArguments | undefined, - options?: (StreamedExecuteOptions & ReserveConnectionOptions) | undefined - ): AsyncGenerator, any, unknown> { - return this.con.executeStreamed(query, args, options); - } - - select( - query: string | PreparedQuery, - args?: SqliteArguments | undefined, - options?: (QueryOptions & ReserveConnectionOptions) | undefined - ): Promise { - return this.con.select(query, args, options); - } -} - -class ResultSetImpl implements ResultSet { - columns: (keyof T)[]; - raw_rows: SqliteValue[][]; - private _rowObjects: T[] | undefined; - - constructor(columns: string[], rows?: SqliteValue[][]) { - this.columns = columns as any[] as (keyof T)[]; - this.raw_rows = rows ?? []; - } - - get rows() { - if (this._rowObjects == null) { - this._rowObjects = this.raw_rows.map((row) => { - return Object.fromEntries( - this.columns.map((column, i) => { - return [column, row[i]]; - }) - ) as T; - }); - } - return this._rowObjects; - } -} diff --git a/test/better-sqlite3-async.test.ts b/test/better-sqlite3-async.test.ts deleted file mode 100644 index 0d47db4..0000000 --- a/test/better-sqlite3-async.test.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { betterSqliteAsyncPool } from "../src/drivers/better-sqlite3-async-driver.js"; -import { describeDriverTests } from "./tests/drivers.js"; - -describeDriverTests("better-sqlite3-async-pool", betterSqliteAsyncPool); diff --git a/test/better-sqlite3.test.ts b/test/better-sqlite3.test.ts deleted file mode 100644 index db40d70..0000000 --- a/test/better-sqlite3.test.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { betterSqlitePool } from "../src/drivers/better-sqlite3-driver.js"; -import { describeDriverTests } from "./tests/drivers.js"; - -describeDriverTests("better-sqlite3", betterSqlitePool); diff --git a/test/sqlite3.test.ts b/test/sqlite3.test.ts deleted file mode 100644 index cb7c665..0000000 --- a/test/sqlite3.test.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { sqlite3Pool } from "../src/drivers/sqlite3-driver.js"; -import { describeDriverTests } from "./tests/drivers.js"; - -describeDriverTests("sqlite3", sqlite3Pool); diff --git a/test/tests/drivers.ts b/test/tests/drivers.ts deleted file mode 100644 index 17be5d9..0000000 --- a/test/tests/drivers.ts +++ /dev/null @@ -1,192 +0,0 @@ -import * as fs from "node:fs/promises"; -import * as path from "node:path"; -import { afterEach, beforeEach, describe, expect, test } from "vitest"; -import { SqliteDriverConnectionPool } from "../../src/driver-api.js"; - -export function describeDriverTests( - name: string, - factory: (path: string) => SqliteDriverConnectionPool -) { - describe(`${name} - driver tests`, () => { - let dbPath: string; - let _dbs: SqliteDriverConnectionPool[] = []; - - const open = async () => { - const dir = path.dirname(dbPath); - try { - await fs.mkdir(dir); - } catch (e) {} - try { - await fs.rm(dbPath); - } catch (e) {} - const db = factory(dbPath); - _dbs.push(db); - return db; - }; - - beforeEach(({ expect }) => { - const testNameSanitized = expect - .getState() - .currentTestName!.replaceAll(/[\s\/\\>\.\-]+/g, "_"); - dbPath = `test-db/${testNameSanitized}.db`; - }); - - afterEach(async () => { - const closeDbs = _dbs; - _dbs = []; - for (let db of closeDbs) { - await db.close(); - } - }); - - test("basic select", async () => { - const driver = await open(); - const { connection, release } = await driver.reserveConnection(); - try { - const rs = await connection.selectAll("select 1 as one"); - expect(rs.columns).toEqual(["one"]); - expect(rs.rows).toEqual([[1]]); - } finally { - release(); - } - }); - - test("big number", async () => { - const driver = await open(); - const { connection, release } = await driver.reserveConnection(); - try { - const rs = await connection.selectAll( - "select 9223372036854775807 as bignumber" - ); - expect(rs.rows).toEqual([[9223372036854776000]]); - const rs2 = await connection.selectAll("select ? as bignumber", [ - 9223372036854775807n, - ]); - expect(rs2.rows).toEqual([[9223372036854776000]]); - } finally { - release(); - } - }); - - test("bigint", async () => { - const driver = await open(); - const { connection, release } = await driver.reserveConnection(); - try { - const rs1 = await connection.selectAll( - "select 9223372036854775807 as bignumber", - undefined, - { bigint: true } - ); - expect(rs1.columns).toEqual(["bignumber"]); - expect(rs1.rows).toEqual([[9223372036854775807n]]); - - const rs2 = await connection.selectAll( - "select ? as bignumber", - [9223372036854775807n], - { - bigint: true, - } - ); - - expect(rs2.rows).toEqual([[9223372036854775807n]]); - } finally { - release(); - } - }); - - test("insert returning", async () => { - const driver = await open(); - const { connection, release } = await driver.reserveConnection(); - try { - await connection.run( - "create table test_data(id integer primary key, data text)" - ); - - const rs = await connection.selectAll( - "insert into test_data(data) values(123) returning id" - ); - expect(rs.columns).toEqual(["id"]); - expect(rs.rows).toEqual([[1]]); - } finally { - release(); - } - }); - - test("runWithResults", async () => { - const driver = await open(); - const { connection, release } = await driver.reserveConnection(); - try { - const r1 = await connection.runWithResults( - "create table test_data(id integer primary key, data text)" - ); - - expect(r1).toEqual({ changes: 0, lastInsertRowId: 0n }); - const r2 = await connection.runWithResults( - "insert into test_data(data) values(123)" - ); - expect(r2).toEqual({ - changes: 1, - lastInsertRowId: 1n, - }); - } finally { - release(); - } - }); - - test("runWithResults - returning statement", async () => { - const driver = await open(); - const { connection, release } = await driver.reserveConnection(); - try { - const r1 = await connection.runWithResults( - "create table test_data(id integer primary key, data text)" - ); - - expect(r1).toEqual({ changes: 0, lastInsertRowId: 0n }); - const r2 = await connection.runWithResults( - "insert into test_data(data) values(123) returning id" - ); - expect(r2).toEqual({ - changes: 1, - lastInsertRowId: 1n, - }); - } finally { - release(); - } - }); - - test("runWithResults - select", async () => { - const driver = await open(); - const { connection, release } = await driver.reserveConnection(); - try { - const r1 = await connection.runWithResults("select 1 as one"); - - expect(r1).toEqual({ changes: 0, lastInsertRowId: 0n }); - } finally { - release(); - } - }); - - test.skip("onUpdate", async () => { - // Skipped: Not properly implemented yet. - - const driver = await open(); - const { connection, release } = await driver.reserveConnection(); - try { - await connection.run( - "create table test_data(id integer primary key, data text)" - ); - // TODO: test the results - connection.onUpdate(({ events }) => { - console.log("update", events); - }); - - await connection.run( - "insert into test_data(data) values(123) returning id" - ); - await connection.run("update test_data set data = data || 'test'"); - } finally { - release(); - } - }); - }); -} diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..5eb85ad --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "composite": true, + "lib": ["ES2022", "esnext.disposable"], + "target": "ES2022", + "strict": true, + "module": "Node16", + "moduleResolution": "Node16", + "sourceMap": true, + "declaration": true + } +} diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index fa1984c..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "compilerOptions": { - "lib": ["ES2023", "DOM"], - "target": "ES2022", - "strict": true, - "module": "NodeNext" - } -}