From 749dc8077946965b2551e6bfc218d6584d69405e Mon Sep 17 00:00:00 2001 From: stevensJourney <51082125+stevensJourney@users.noreply.github.com> Date: Tue, 27 Feb 2024 13:58:15 +0200 Subject: [PATCH] Web tests with Vitest (#76) --- .github/workflows/test.yml | 47 + package.json | 16 +- .../src/client/AbstractPowerSyncDatabase.ts | 3 +- .../sync/bucket/BucketStorageAdapter.ts | 4 +- .../src/client/sync/bucket/OplogEntry.ts | 10 +- .../client/sync/bucket/SqliteBucketStorage.ts | 10 +- .../src/client/sync/bucket/SyncDataBucket.ts | 4 +- packages/powersync-sdk-common/src/index.ts | 1 + packages/powersync-sdk-web/package.json | 13 +- .../tests/bucket_storage.test.ts | 809 ++++++++++++ packages/powersync-sdk-web/tests/crud.test.ts | 253 ++++ packages/powersync-sdk-web/tests/main.test.ts | 50 + .../powersync-sdk-web/tests/schema.test.ts | 151 +++ .../powersync-sdk-web/tests/stream.test.ts | 179 +++ .../powersync-sdk-web/tests/test_schema.ts | 28 + .../powersync-sdk-web/tests/watch.test.ts | 105 ++ packages/powersync-sdk-web/tsconfig.json | 6 +- packages/powersync-sdk-web/vitest.config.ts | 41 + pnpm-lock.yaml | 1133 ++++++++++++++++- 19 files changed, 2828 insertions(+), 35 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 packages/powersync-sdk-web/tests/bucket_storage.test.ts create mode 100644 packages/powersync-sdk-web/tests/crud.test.ts create mode 100644 packages/powersync-sdk-web/tests/main.test.ts create mode 100644 packages/powersync-sdk-web/tests/schema.test.ts create mode 100644 packages/powersync-sdk-web/tests/stream.test.ts create mode 100644 packages/powersync-sdk-web/tests/test_schema.ts create mode 100644 packages/powersync-sdk-web/tests/watch.test.ts create mode 100644 packages/powersync-sdk-web/vitest.config.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..ad297720 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,47 @@ +# 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 + + - name: Setup NodeJS + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + + - uses: pnpm/action-setup@v2 + name: Install pnpm + with: + version: 8 + run_install: false + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - uses: actions/cache@v3 + name: Setup pnpm cache + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install dependencies + run: pnpm install + + - name: Build + run: pnpm build:packages + + - name: Test + run: pnpm test diff --git a/package.json b/package.json index cf2e3a61..152d306d 100644 --- a/package.json +++ b/package.json @@ -5,17 +5,17 @@ "description": "monorepo for powersync javascript sdks", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "lint": "eslint .", - "clean": "pnpm run -r clean", - "ci:version": "changeset version && pnpm install --no-frozen-lockfile", + "build:packages": "pnpm run --filter './packages/**' -r build", + "build": "pnpm run -r build", "ci:publish": "changeset publish && git push --follow-tags", - "docs:start": "pnpm --filter docs start", + "ci:version": "changeset version && pnpm install --no-frozen-lockfile", + "clean": "pnpm run -r clean", "docs:build": "pnpm --filter docs build", - "build": "pnpm run -r build", - "build:packages": "pnpm run --filter './packages/**' -r build", + "docs:start": "pnpm --filter docs start", "format": "prettier --write .", - "release": "pnpm build:packages && pnpm changeset publish" + "lint": "eslint .", + "release": "pnpm build:packages && pnpm changeset publish", + "test": "pnpm run -r test" }, "keywords": [], "type": "module", diff --git a/packages/powersync-sdk-common/src/client/AbstractPowerSyncDatabase.ts b/packages/powersync-sdk-common/src/client/AbstractPowerSyncDatabase.ts index 24376326..849fcd11 100644 --- a/packages/powersync-sdk-common/src/client/AbstractPowerSyncDatabase.ts +++ b/packages/powersync-sdk-common/src/client/AbstractPowerSyncDatabase.ts @@ -135,7 +135,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver { const r = await this.validateChecksums(checkpoint); if (!r.checkpointValid) { - this.logger.error('Checksums failed for', r.failures); - for (const b of r.failures ?? []) { + this.logger.error('Checksums failed for', r.checkpointFailures); + for (const b of r.checkpointFailures ?? []) { await this.deleteBucket(b); } - return { ready: false, checkpointValid: false, failures: r.failures }; + return { ready: false, checkpointValid: false, checkpointFailures: r.checkpointFailures }; } const bucketNames = checkpoint.buckets.map((b) => b.bucket); @@ -178,7 +178,7 @@ export class SqliteBucketStorage implements BucketStorageAdapter { return { checkpointValid: false, ready: false, - failures: [] + checkpointFailures: [] }; } @@ -190,7 +190,7 @@ export class SqliteBucketStorage implements BucketStorageAdapter { return { checkpointValid: false, ready: false, - failures: result['failed_buckets'] + checkpointFailures: result['failed_buckets'] }; } } diff --git a/packages/powersync-sdk-common/src/client/sync/bucket/SyncDataBucket.ts b/packages/powersync-sdk-common/src/client/sync/bucket/SyncDataBucket.ts index 3f5411ef..5f6a9c7b 100644 --- a/packages/powersync-sdk-common/src/client/sync/bucket/SyncDataBucket.ts +++ b/packages/powersync-sdk-common/src/client/sync/bucket/SyncDataBucket.ts @@ -32,11 +32,11 @@ export class SyncDataBucket { /** * The `after` specified in the request. */ - public after: OpId, + public after?: OpId, /** * Use this for the next request. */ - public next_after: OpId + public next_after?: OpId ) {} toJSON(): SyncDataBucketJSON { diff --git a/packages/powersync-sdk-common/src/index.ts b/packages/powersync-sdk-common/src/index.ts index 531aae0c..7ebfb1a2 100644 --- a/packages/powersync-sdk-common/src/index.ts +++ b/packages/powersync-sdk-common/src/index.ts @@ -10,6 +10,7 @@ export * from './client/sync/bucket/CrudTransaction'; export * from './client/sync/bucket/SyncDataBatch'; export * from './client/sync/bucket/SyncDataBucket'; export * from './client/sync/bucket/OpType'; +export * from './client/sync/bucket/OplogEntry'; export * from './client/sync/stream/AbstractRemote'; export * from './client/sync/stream/AbstractStreamingSyncImplementation'; export * from './client/sync/stream/streaming-sync-types'; diff --git a/packages/powersync-sdk-web/package.json b/packages/powersync-sdk-web/package.json index c3446672..5b23ea65 100644 --- a/packages/powersync-sdk-web/package.json +++ b/packages/powersync-sdk-web/package.json @@ -5,7 +5,8 @@ "main": "lib/src/index.js", "types": "lib/src/index.d.ts", "files": [ - "lib" + "lib", + "!lib/tests" ], "repository": "https://github.com/powersync-ja/powersync-js", "bugs": { @@ -20,7 +21,7 @@ "build": "tsc --build", "clean": "rm -rf dist tsconfig.tsbuildinfo", "watch": "tsc --build -w", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "pnpm build && vitest" }, "keywords": [ "data sync", @@ -35,7 +36,13 @@ "@journeyapps/wa-sqlite": "~0.1.1", "@types/lodash": "^4.14.200", "@types/uuid": "^9.0.6", - "typescript": "^5.2.2" + "@vitest/browser": "^1.3.1", + "typescript": "^5.2.2", + "vite": "^5.1.1", + "vite-plugin-top-level-await": "^1.4.1", + "vite-plugin-wasm": "^3.3.0", + "vitest": "^1.3.1", + "webdriverio": "^8.32.3" }, "peerDependencies": { "@journeyapps/wa-sqlite": "~0.1.1" diff --git a/packages/powersync-sdk-web/tests/bucket_storage.test.ts b/packages/powersync-sdk-web/tests/bucket_storage.test.ts new file mode 100644 index 00000000..67649ee0 --- /dev/null +++ b/packages/powersync-sdk-web/tests/bucket_storage.test.ts @@ -0,0 +1,809 @@ +import { + BucketStorageAdapter, + OpType, + OpTypeEnum, + OplogEntry, + Schema, + SqliteBucketStorage, + SyncDataBatch, + SyncDataBucket +} from '@journeyapps/powersync-sdk-common'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { AbstractPowerSyncDatabase, Checkpoint } from '@journeyapps/powersync-sdk-common'; +import { WASQLitePowerSyncDatabaseOpenFactory } from '@journeyapps/powersync-sdk-web'; +import { Mutex } from 'async-mutex'; +import { testSchema } from './test_schema'; + +const putAsset1_1 = OplogEntry.fromRow({ + op_id: '1', + op: new OpType(OpTypeEnum.PUT).toJSON(), + object_type: 'assets', + object_id: 'O1', + data: '{"description": "bar"}', + checksum: 1 +}); + +const putAsset2_2 = OplogEntry.fromRow({ + op_id: '2', + op: new OpType(OpTypeEnum.PUT).toJSON(), + object_type: 'assets', + object_id: 'O2', + data: '{"description": "bar"}', + checksum: 2 +}); + +const putAsset1_3 = OplogEntry.fromRow({ + op_id: '3', + op: new OpType(OpTypeEnum.PUT).toJSON(), + object_type: 'assets', + object_id: 'O1', + data: '{"description": "bard"}', + checksum: 3 +}); + +const removeAsset1_4 = OplogEntry.fromRow({ + op_id: '4', + op: new OpType(OpTypeEnum.REMOVE).toJSON(), + object_type: 'assets', + object_id: 'O1', + checksum: 4 +}); + +const removeAsset1_5 = OplogEntry.fromRow({ + op_id: '5', + op: new OpType(OpTypeEnum.REMOVE).toJSON(), + object_type: 'assets', + object_id: 'O1', + checksum: 5 +}); + +describe('Bucket Storage', () => { + const factory = new WASQLitePowerSyncDatabaseOpenFactory({ + dbFilename: 'test-bucket-storage.db', + flags: { + enableMultiTabs: false + }, + schema: testSchema + }); + + let db: AbstractPowerSyncDatabase; + let bucketStorage: BucketStorageAdapter; + + beforeEach(async () => { + db = factory.getInstance(); + await db.waitForReady(); + bucketStorage = new SqliteBucketStorage(db.database, new Mutex()); + }); + + afterEach(async () => { + await db.disconnectAndClear(); + await db.close(); + }); + + async function syncLocalChecked(checkpoint: Checkpoint) { + var result = await bucketStorage.syncLocalDatabase(checkpoint); + expect(result).deep.equals({ ready: true, checkpointValid: true }); + } + + async function expectAsset1_3(database = db) { + expect(await database.getAll("SELECT id, description, make FROM assets WHERE id = 'O1'")).deep.equals([ + { id: 'O1', description: 'bard', make: null } + ]); + } + + async function expectNoAsset1() { + expect(await db.getAll("SELECT id, description, make FROM assets WHERE id = 'O1'")).deep.equals([]); + } + + async function expectNoAssets() { + expect(await db.getAll('SELECT id, description, make FROM assets')).deep.equals([]); + } + + it('Basic Setup', async () => { + await db.waitForReady(); + + expect(await bucketStorage.getBucketStates()).empty; + + await bucketStorage.saveSyncData( + new SyncDataBatch([new SyncDataBucket('bucket1', [putAsset1_1, putAsset2_2, putAsset1_3], false)]) + ); + + const bucketStates = await bucketStorage.getBucketStates(); + + expect(bucketStates).deep.equals([ + { + bucket: 'bucket1', + op_id: '3' + } + ]); + + await syncLocalChecked({ + last_op_id: '3', + buckets: [{ bucket: 'bucket1', checksum: 6 }] + }); + + await expectAsset1_3(); + }); + + it('should get an object from multiple buckets', async () => { + await bucketStorage.saveSyncData( + new SyncDataBatch([ + new SyncDataBucket('bucket1', [putAsset1_3], false), + new SyncDataBucket('bucket2', [putAsset1_3], false) + ]) + ); + + await syncLocalChecked({ + last_op_id: '3', + buckets: [ + { bucket: 'bucket1', checksum: 3 }, + { bucket: 'bucket2', checksum: 3 } + ] + }); + await expectAsset1_3(); + }); + + it('should prioritize later updates', async () => { + // Test behaviour when the same object is present in multiple buckets. + // In this case, there are two different versions in the different buckets. + // While we should not get this with our server implementation, the client still specifies this behaviour: + // The largest op_id wins. + await bucketStorage.saveSyncData( + new SyncDataBatch([ + new SyncDataBucket('bucket1', [putAsset1_3], false), + new SyncDataBucket('bucket2', [putAsset1_1], false) + ]) + ); + + await syncLocalChecked({ + last_op_id: '3', + buckets: [ + { bucket: 'bucket1', checksum: 3 }, + { bucket: 'bucket2', checksum: 1 } + ] + }); + await expectAsset1_3(); + }); + + it('should ignore a remove from one bucket', async () => { + // When we have 1 PUT and 1 REMOVE, the object must be kept. + await bucketStorage.saveSyncData( + new SyncDataBatch([ + new SyncDataBucket('bucket1', [putAsset1_3], false), + new SyncDataBucket('bucket2', [putAsset1_3, removeAsset1_4], false) + ]) + ); + + await syncLocalChecked({ + last_op_id: '4', + buckets: [ + { bucket: 'bucket1', checksum: 3 }, + { bucket: 'bucket2', checksum: 7 } + ] + }); + await expectAsset1_3(); + }); + + it('should remove when removed from all buckets', async () => { + // When we only have REMOVE left for an object, it must be deleted. + await bucketStorage.saveSyncData( + new SyncDataBatch([ + new SyncDataBucket('bucket1', [putAsset1_3, removeAsset1_5], false), + new SyncDataBucket('bucket2', [putAsset1_3, removeAsset1_4], false) + ]) + ); + + await syncLocalChecked({ + last_op_id: '5', + buckets: [ + { bucket: 'bucket1', checksum: 8 }, + { bucket: 'bucket2', checksum: 7 } + ] + }); + + await expectNoAssets(); + }); + + it('should use subkeys', async () => { + // subkeys cause this to be treated as a separate entity in the oplog, + // but same entity in the local db. + var put4 = OplogEntry.fromRow({ + op_id: '4', + op: new OpType(OpTypeEnum.PUT).toJSON(), + subkey: 'b', + object_type: 'assets', + object_id: 'O1', + data: '{"description": "B"}', + checksum: 4 + }); + + var remove5 = OplogEntry.fromRow({ + op_id: '5', + op: new OpType(OpTypeEnum.REMOVE).toJSON(), + subkey: 'b', + object_type: 'assets', + object_id: 'O1', + checksum: 5 + }); + + await bucketStorage.saveSyncData( + new SyncDataBatch([new SyncDataBucket('bucket1', [putAsset1_1, putAsset1_3, put4], false)]) + ); + + await syncLocalChecked({ + last_op_id: '4', + buckets: [{ bucket: 'bucket1', checksum: 8 }] + }); + + expect(await db.getAll("SELECT id, description, make FROM assets WHERE id = 'O1'")).deep.equals([ + { id: 'O1', description: 'B', make: null } + ]); + + await bucketStorage.saveSyncData(new SyncDataBatch([new SyncDataBucket('bucket1', [remove5], false)])); + + await syncLocalChecked({ + last_op_id: '5', + buckets: [{ bucket: 'bucket1', checksum: 13 }] + }); + + await expectAsset1_3(); + }); + + it('should fail checksum validation', async () => { + // Simple checksum validation + await bucketStorage.saveSyncData( + new SyncDataBatch([new SyncDataBucket('bucket1', [putAsset1_1, putAsset2_2, putAsset1_3], false)]) + ); + + const result = await bucketStorage.syncLocalDatabase({ + last_op_id: '3', + buckets: [ + { bucket: 'bucket1', checksum: 10 }, + { bucket: 'bucket2', checksum: 1 } + ] + }); + + expect(result).deep.equals({ + ready: false, + checkpointValid: false, + checkpointFailures: ['bucket1', 'bucket2'] + }); + + await expectNoAssets(); + }); + + it('should delete buckets', async () => { + await bucketStorage.saveSyncData( + new SyncDataBatch([ + new SyncDataBucket('bucket1', [putAsset1_3], false), + new SyncDataBucket('bucket2', [putAsset1_3], false) + ]) + ); + + await bucketStorage.removeBuckets(['bucket2']); + // The delete only takes effect after syncLocal. + + await syncLocalChecked({ + last_op_id: '3', + buckets: [{ bucket: 'bucket1', checksum: 3 }] + }); + + // Bucket is deleted, but object is still present in other buckets. + await expectAsset1_3(); + + await bucketStorage.removeBuckets(['bucket1']); + await syncLocalChecked({ last_op_id: '3', buckets: [] }); + // Both buckets deleted - object removed. + await expectNoAssets(); + }); + + it('should delete and re-create buckets', async () => { + // Save some data + await bucketStorage.saveSyncData(new SyncDataBatch([new SyncDataBucket('bucket1', [putAsset1_1], false)])); + + // Delete the bucket + await bucketStorage.removeBuckets(['bucket1']); + + // Save some data again + await bucketStorage.saveSyncData( + new SyncDataBatch([new SyncDataBucket('bucket1', [putAsset1_1, putAsset1_3], false)]) + ); + // Delete again + await bucketStorage.removeBuckets(['bucket1']); + + // Final save of data + await bucketStorage.saveSyncData( + new SyncDataBatch([new SyncDataBucket('bucket1', [putAsset1_1, putAsset1_3], false)]) + ); + + // Check that the data is there + await syncLocalChecked({ + last_op_id: '3', + buckets: [{ bucket: 'bucket1', checksum: 4 }] + }); + await expectAsset1_3(); + + // Now final delete + await bucketStorage.removeBuckets(['bucket1']); + await syncLocalChecked({ last_op_id: '3', buckets: [] }); + await expectNoAssets(); + }); + + it('should handle MOVE', async () => { + await bucketStorage.saveSyncData( + new SyncDataBatch([ + new SyncDataBucket( + 'bucket1', + [ + OplogEntry.fromRow({ + op_id: '1', + op: new OpType(OpTypeEnum.MOVE).toJSON(), + checksum: 1, + data: '{"target": "3"}' + }) + ], + false + ) + ]) + ); + + // At this point, we have target: 3, but don't have that op yet, so we cannot sync. + var result = await bucketStorage.syncLocalDatabase({ + last_op_id: '2', + buckets: [{ bucket: 'bucket1', checksum: 1 }] + }); + // Checksum passes, but we don't have a complete checkpoint + expect(result).deep.equals({ ready: false, checkpointValid: true }); + + await bucketStorage.saveSyncData(new SyncDataBatch([new SyncDataBucket('bucket1', [putAsset1_3], false)])); + + await syncLocalChecked({ + last_op_id: '3', + buckets: [{ bucket: 'bucket1', checksum: 4 }] + }); + + await expectAsset1_3(); + }); + + it('should handle CLEAR', async () => { + // Save some data + await bucketStorage.saveSyncData(new SyncDataBatch([new SyncDataBucket('bucket1', [putAsset1_1], false)])); + + await syncLocalChecked({ + last_op_id: '1', + buckets: [{ bucket: 'bucket1', checksum: 1 }] + }); + + // CLEAR, then save new data + await bucketStorage.saveSyncData( + new SyncDataBatch([ + new SyncDataBucket( + 'bucket1', + [ + OplogEntry.fromRow({ + op_id: '2', + op: new OpType(OpTypeEnum.CLEAR).toJSON(), + checksum: 2 + }), + OplogEntry.fromRow({ + op_id: '3', + op: new OpType(OpTypeEnum.PUT).toJSON(), + checksum: 3, + data: putAsset2_2.data, + object_id: putAsset2_2.object_id, + object_type: putAsset2_2.object_type + }) + ], + false + ) + ]) + ); + + await syncLocalChecked({ + last_op_id: '3', + // 2 + 3. 1 is replaced with 2. + buckets: [{ bucket: 'bucket1', checksum: 5 }] + }); + + await expectNoAsset1(); + console.log(await db.getAll(`SELECT id, description FROM assets WHERE id = 'O2'`)); + expect(await db.get("SELECT id, description FROM assets WHERE id = 'O2'")).deep.equals({ + id: 'O2', + description: 'bar' + }); + }); + + it('update with new types', async () => { + const dbName = `test-bucket-storage-new-types.db`; + // Test case where a type is added to the schema after we already have the data. + const factory = new WASQLitePowerSyncDatabaseOpenFactory({ + dbFilename: dbName, + flags: { + enableMultiTabs: false + }, + schema: new Schema([]) + }); + + let powersync = factory.getInstance(); + await powersync.waitForReady(); + bucketStorage = new SqliteBucketStorage(powersync.database, new Mutex()); + + await bucketStorage.saveSyncData( + new SyncDataBatch([new SyncDataBucket('bucket1', [putAsset1_1, putAsset2_2, putAsset1_3], false)]) + ); + + await syncLocalChecked({ + last_op_id: '4', + buckets: [{ bucket: 'bucket1', checksum: 6 }] + }); + + await expect(powersync.getAll('SELECT * FROM assets')).rejects.toThrow('no such table'); + await powersync.close(); + + // Now open another instance with new schema + const factory2 = new WASQLitePowerSyncDatabaseOpenFactory({ + dbFilename: dbName, + flags: { + enableMultiTabs: false + }, + schema: testSchema + }); + + powersync = factory2.getInstance(); + + await expectAsset1_3(powersync); + + await powersync.disconnectAndClear(); + await powersync.close(); + }); + + it('should remove types', async () => { + const dbName = `test-bucket-storage-remove-types.db`; + // Test case where a type is added to the schema after we already have the data. + const factory = new WASQLitePowerSyncDatabaseOpenFactory({ + dbFilename: dbName, + flags: { + enableMultiTabs: false + }, + schema: testSchema + }); + + let powersync = factory.getInstance(); + await powersync.waitForReady(); + bucketStorage = new SqliteBucketStorage(powersync.database, new Mutex()); + + await bucketStorage.saveSyncData( + new SyncDataBatch([new SyncDataBucket('bucket1', [putAsset1_1, putAsset2_2, putAsset1_3], false)]) + ); + + await syncLocalChecked({ + last_op_id: '3', + buckets: [{ bucket: 'bucket1', checksum: 6 }] + }); + + await expectAsset1_3(powersync); + + await powersync.close(); + + // Now open another instance with new schema + const factory2 = new WASQLitePowerSyncDatabaseOpenFactory({ + dbFilename: dbName, + flags: { + enableMultiTabs: false + }, + schema: new Schema([]) + }); + + powersync = factory2.getInstance(); + + await expect(powersync.execute('SELECT * FROM assets')).rejects.toThrowError('no such table'); + await powersync.close(); + + // Add schema again + powersync = factory.getInstance(); + + await expectAsset1_3(powersync); + + await powersync.disconnectAndClear(); + await powersync.close(); + }); + + it('should compact', async () => { + // Test compacting behaviour. + // This test relies heavily on internals, and will have to be updated when the compact implementation is updated. + + await bucketStorage.saveSyncData( + new SyncDataBatch([new SyncDataBucket('bucket1', [putAsset1_1, putAsset2_2, removeAsset1_4], false)]) + ); + + await syncLocalChecked({ + last_op_id: '4', + write_checkpoint: '4', + buckets: [{ bucket: 'bucket1', checksum: 7 }] + }); + + await bucketStorage.forceCompact(); + + await syncLocalChecked({ + last_op_id: '4', + write_checkpoint: '4', + buckets: [{ bucket: 'bucket1', checksum: 7 }] + }); + + const stats = await db.getAll( + 'SELECT row_type as type, row_id as id, count(*) as count FROM ps_oplog GROUP BY row_type, row_id ORDER BY row_type, row_id' + ); + expect(stats).deep.equals([{ type: 'assets', id: 'O2', count: 1 }]); + }); + + it('should not sync local db with pending crud - server removed', async () => { + await bucketStorage.saveSyncData( + new SyncDataBatch([new SyncDataBucket('bucket1', [putAsset1_1, putAsset2_2, putAsset1_3], false)]) + ); + + await syncLocalChecked({ + last_op_id: '3', + buckets: [{ bucket: 'bucket1', checksum: 6 }] + }); + + // Local save + await db.execute('INSERT INTO assets(id) VALUES(?)', ['O3']); + expect(await db.getAll("SELECT id FROM assets WHERE id = 'O3'")).deep.equals([{ id: 'O3' }]); + + // At this point, we have data in the crud table, and are not able to sync the local db. + const result = await bucketStorage.syncLocalDatabase({ + last_op_id: '3', + write_checkpoint: '3', + buckets: [{ bucket: 'bucket1', checksum: 6 }] + }); + + expect(result).deep.equals({ ready: false, checkpointValid: true }); + + const batch = await bucketStorage.getCrudBatch(); + await batch!.complete(); + await bucketStorage.updateLocalTarget(async () => { + return '4'; + }); + + // At this point, the data has been uploaded, but not synced back yet. + const result3 = await bucketStorage.syncLocalDatabase({ + last_op_id: '3', + write_checkpoint: '3', + buckets: [{ bucket: 'bucket1', checksum: 6 }] + }); + expect(result3).deep.equals({ ready: false, checkpointValid: true }); + + // The data must still be present locally. + expect(await db.getAll("SELECT id FROM assets WHERE id = 'O3'")).deep.equals([{ id: 'O3' }]); + + await bucketStorage.saveSyncData(new SyncDataBatch([new SyncDataBucket('bucket1', [], false)])); + + // Now we have synced the data back (or lack of data in this case), + // so we can do a local sync. + await syncLocalChecked({ + last_op_id: '5', + write_checkpoint: '5', + buckets: [{ bucket: 'bucket1', checksum: 6 }] + }); + + // Since the object was not in the sync response, it is deleted. + expect(await db.getAll("SELECT id FROM assets WHERE id = 'O3'")).empty; + }); + + it('should not sync local db with pending crud when more crud is added (1)', async () => { + await bucketStorage.saveSyncData( + new SyncDataBatch([new SyncDataBucket('bucket1', [putAsset1_1, putAsset2_2, putAsset1_3], false)]) + ); + + await syncLocalChecked({ + last_op_id: '3', + write_checkpoint: '3', + buckets: [{ bucket: 'bucket1', checksum: 6 }] + }); + + // Local save + await db.execute('INSERT INTO assets(id) VALUES(?)', ['O3']); + + const batch = await bucketStorage.getCrudBatch(); + await batch!.complete(); + await bucketStorage.updateLocalTarget(async () => { + return '4'; + }); + + const result3 = await bucketStorage.syncLocalDatabase({ + last_op_id: '3', + write_checkpoint: '3', + buckets: [{ bucket: 'bucket1', checksum: 6 }] + }); + expect(result3).deep.equals({ ready: false, checkpointValid: true }); + + await bucketStorage.saveSyncData(new SyncDataBatch([new SyncDataBucket('bucket1', [], false)])); + + // Add more data before syncLocalDatabase. + await db.execute('INSERT INTO assets(id) VALUES(?)', ['O4']); + + const result4 = await bucketStorage.syncLocalDatabase({ + last_op_id: '5', + write_checkpoint: '5', + buckets: [{ bucket: 'bucket1', checksum: 6 }] + }); + expect(result4).deep.equals({ ready: false, checkpointValid: true }); + }); + + it('should not sync local db with pending crud when more crud is added (2)', async () => { + await bucketStorage.saveSyncData( + new SyncDataBatch([new SyncDataBucket('bucket1', [putAsset1_1, putAsset2_2, putAsset1_3], false)]) + ); + + await syncLocalChecked({ + last_op_id: '3', + write_checkpoint: '3', + buckets: [{ bucket: 'bucket1', checksum: 6 }] + }); + + // Local save + await db.execute('INSERT INTO assets(id) VALUES(?)', ['O3']); + const batch = await bucketStorage.getCrudBatch(); + // Add more data before the complete() call + + await db.execute('INSERT INTO assets(id) VALUES(?)', ['O4']); + await batch!.complete(); + await bucketStorage.updateLocalTarget(async () => { + return '4'; + }); + + await bucketStorage.saveSyncData(new SyncDataBatch([new SyncDataBucket('bucket1', [], false)])); + + const result4 = await bucketStorage.syncLocalDatabase({ + last_op_id: '5', + write_checkpoint: '5', + buckets: [{ bucket: 'bucket1', checksum: 6 }] + }); + expect(result4).deep.equals({ ready: false, checkpointValid: true }); + }); + + it('should not sync local db with pending crud - update on server', async () => { + await bucketStorage.saveSyncData( + new SyncDataBatch([new SyncDataBucket('bucket1', [putAsset1_1, putAsset2_2, putAsset1_3], false)]) + ); + + await syncLocalChecked({ + last_op_id: '3', + write_checkpoint: '3', + buckets: [{ bucket: 'bucket1', checksum: 6 }] + }); + + // Local save + await db.execute('INSERT INTO assets(id) VALUES(?)', ['O3']); + const batch = await bucketStorage.getCrudBatch(); + await batch!.complete(); + await bucketStorage.updateLocalTarget(async () => { + return '4'; + }); + + await bucketStorage.saveSyncData( + new SyncDataBatch([ + new SyncDataBucket( + 'bucket1', + [ + OplogEntry.fromRow({ + op_id: '5', + op: new OpType(OpTypeEnum.PUT).toJSON(), + object_type: 'assets', + object_id: 'O3', + checksum: 5, + data: '{"description": "server updated"}' + }) + ], + false + ) + ]) + ); + + await syncLocalChecked({ + last_op_id: '5', + write_checkpoint: '5', + buckets: [{ bucket: 'bucket1', checksum: 11 }] + }); + + expect(await db.getAll("SELECT description FROM assets WHERE id = 'O3'")).deep.equals([ + { description: 'server updated' } + ]); + }); + + it('should revert a failing insert', async () => { + await bucketStorage.saveSyncData( + new SyncDataBatch([new SyncDataBucket('bucket1', [putAsset1_1, putAsset2_2, putAsset1_3], false)]) + ); + + await syncLocalChecked({ + last_op_id: '3', + write_checkpoint: '3', + buckets: [{ bucket: 'bucket1', checksum: 6 }] + }); + + // Local insert, later rejected by server + await db.execute('INSERT INTO assets(id, description) VALUES(?, ?)', ['O3', 'inserted']); + const batch = await bucketStorage.getCrudBatch(); + await batch!.complete(); + await bucketStorage.updateLocalTarget(async () => { + return '4'; + }); + + expect(await db.getAll("SELECT description FROM assets WHERE id = 'O3'")).deep.equals([ + { description: 'inserted' } + ]); + + await syncLocalChecked({ + last_op_id: '3', + write_checkpoint: '4', + buckets: [{ bucket: 'bucket1', checksum: 6 }] + }); + + expect(await db.getAll("SELECT description FROM assets WHERE id = 'O3'")).empty; + }); + + it('should revert a failing delete', async () => { + await bucketStorage.saveSyncData( + new SyncDataBatch([new SyncDataBucket('bucket1', [putAsset1_1, putAsset2_2, putAsset1_3], false)]) + ); + + await syncLocalChecked({ + last_op_id: '3', + write_checkpoint: '3', + buckets: [{ bucket: 'bucket1', checksum: 6 }] + }); + + // Local delete, later rejected by server + await db.execute('DELETE FROM assets WHERE id = ?', ['O2']); + + expect(await db.getAll("SELECT description FROM assets WHERE id = 'O2'")).empty; + // Simulate a permissions error when uploading - data should be preserved. + const batch = await bucketStorage.getCrudBatch(); + await batch!.complete(); + + await bucketStorage.updateLocalTarget(async () => { + return '4'; + }); + + await syncLocalChecked({ + last_op_id: '3', + write_checkpoint: '4', + buckets: [{ bucket: 'bucket1', checksum: 6 }] + }); + + expect(await db.getAll("SELECT description FROM assets WHERE id = 'O2'")).deep.equals([{ description: 'bar' }]); + }); + + it('should revert a failing update', async () => { + await bucketStorage.saveSyncData( + new SyncDataBatch([new SyncDataBucket('bucket1', [putAsset1_1, putAsset2_2, putAsset1_3], false)]) + ); + + await syncLocalChecked({ + last_op_id: '3', + write_checkpoint: '3', + buckets: [{ bucket: 'bucket1', checksum: 6 }] + }); + + // Local update, later rejected by server + await db.execute('UPDATE assets SET description = ? WHERE id = ?', ['updated', 'O2']); + + expect(await db.getAll(`SELECT description FROM assets WHERE id = 'O2'`)).deep.equals([{ description: 'updated' }]); + // Simulate a permissions error when uploading - data should be preserved. + const batch = await bucketStorage.getCrudBatch(); + await batch!.complete(); + + await bucketStorage.updateLocalTarget(async () => { + return '4'; + }); + + await syncLocalChecked({ + last_op_id: '3', + write_checkpoint: '4', + buckets: [{ bucket: 'bucket1', checksum: 6 }] + }); + + expect(await db.getAll("SELECT description FROM assets WHERE id = 'O2'")).deep.equals([{ description: 'bar' }]); + }); +}); diff --git a/packages/powersync-sdk-web/tests/crud.test.ts b/packages/powersync-sdk-web/tests/crud.test.ts new file mode 100644 index 00000000..69225460 --- /dev/null +++ b/packages/powersync-sdk-web/tests/crud.test.ts @@ -0,0 +1,253 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { + AbstractPowerSyncDatabase, + Column, + ColumnType, + CrudEntry, + Index, + IndexedColumn, + Schema, + Table, + UpdateType +} from '@journeyapps/powersync-sdk-common'; +import { WASQLitePowerSyncDatabaseOpenFactory } from '@journeyapps/powersync-sdk-web'; +import { v4 as uuid } from 'uuid'; +import { testSchema } from './test_schema'; + +const testId = '2290de4f-0488-4e50-abed-f8e8eb1d0b42'; + +describe('CRUD Tests', () => { + let powersync: AbstractPowerSyncDatabase; + + beforeEach(async () => { + powersync = new WASQLitePowerSyncDatabaseOpenFactory({ + /** + * Deleting the IndexDB seems to freeze the test. + * Use a new DB for each run to keep CRUD counters + * consistent + */ + dbFilename: `test-crud-${uuid()}.db`, + schema: testSchema, + flags: { + enableMultiTabs: false + } + }).getInstance(); + }); + + afterEach(async () => { + await powersync.disconnectAndClear(); + await powersync.close(); + }); + + it('INSERT', async () => { + expect(await powersync.getAll('SELECT * FROM ps_crud')).empty; + + await powersync.execute('INSERT INTO assets(id, description) VALUES(?, ?)', [testId, 'test']); + + expect(await powersync.getAll('SELECT data FROM ps_crud ORDER BY id')).deep.equals([ + { + data: `{"op":"PUT","type":"assets","id":"${testId}","data":{"description":"test"}}` + } + ]); + + var tx = (await powersync.getNextCrudTransaction())!; + expect(tx.transactionId).equals(1); + const expectedCrudEntry = new CrudEntry(1, UpdateType.PUT, 'assets', testId, 1, { description: 'test' }); + expect(tx.crud[0].equals(expectedCrudEntry)).true; + }); + + it('INSERT OR REPLACE', async () => { + await powersync.execute('INSERT INTO assets(id, description) VALUES(?, ?)', [testId, 'test']); + await powersync.execute('DELETE FROM ps_crud WHERE 1'); + + // Replace + await powersync.execute('INSERT OR REPLACE INTO assets(id, description) VALUES(?, ?)', [testId, 'test2']); + + // This generates another PUT + expect(await powersync.getAll('SELECT data FROM ps_crud ORDER BY id')).deep.equals([ + { + data: `{"op":"PUT","type":"assets","id":"${testId}","data":{"description":"test2"}}` + } + ]); + + expect(await powersync.get('SELECT count(*) AS count FROM assets')).deep.equals({ count: 1 }); + + // Make sure uniqueness is enforced + expect(powersync.execute('INSERT INTO assets(id, description) VALUES(?, ?)', [testId, 'test3'])).rejects.toThrow( + /UNIQUE constraint failed/ + ); + }); + + it('UPDATE', async () => { + await powersync.execute('INSERT INTO assets(id, description, make) VALUES(?, ?, ?)', [testId, 'test', 'test']); + await powersync.execute('DELETE FROM ps_crud WHERE 1'); + + await powersync.execute('UPDATE assets SET description = ? WHERE id = ?', ['test2', testId]); + + expect(await powersync.getAll('SELECT data FROM ps_crud ORDER BY id')).deep.equals([ + { + data: `{"op":"PATCH","type":"assets","id":"${testId}","data":{"description":"test2"}}` + } + ]); + + var tx = (await powersync.getNextCrudTransaction())!; + expect(tx.transactionId).equals(2); + + const expectedCrudEntry = new CrudEntry(2, UpdateType.PATCH, 'assets', testId, 2, { description: 'test2' }); + + expect(tx.crud[0].equals(expectedCrudEntry)).true; + }); + + it('DELETE', async () => { + await powersync.execute('INSERT INTO assets(id, description, make) VALUES(?, ?, ?)', [testId, 'test', 'test']); + await powersync.execute('DELETE FROM ps_crud WHERE 1'); + + await powersync.execute('DELETE FROM assets WHERE id = ?', [testId]); + + expect(await powersync.getAll('SELECT data FROM ps_crud ORDER BY id')).deep.equals([ + { data: `{"op":"DELETE","type":"assets","id":"${testId}"}` } + ]); + + var tx = (await powersync.getNextCrudTransaction())!; + expect(tx.transactionId).equals(2); + const expectedCrudEntry = new CrudEntry(2, UpdateType.DELETE, 'assets', testId, 2); + expect(tx.crud[0].equals(expectedCrudEntry)).true; + }); + + it('UPSERT not supported', async () => { + // Just shows that we cannot currently do this + expect( + powersync.execute('INSERT INTO assets(id, description) VALUES(?, ?) ON CONFLICT DO UPDATE SET description = ?', [ + testId, + 'test2', + 'test3' + ]) + ).rejects.toThrowError('cannot UPSERT a view'); + }); + + it('INSERT-only tables', async () => { + await powersync.disconnectAndClear(); + + powersync = new WASQLitePowerSyncDatabaseOpenFactory({ + /** + * Deleting the IndexDB seems to freeze the test. + * Use a new DB for each run to keep CRUD counters + * consistent + */ + dbFilename: 'test.db' + uuid(), + schema: new Schema([ + new Table({ + name: 'logs', + insertOnly: true, + columns: [ + new Column({ name: 'level', type: ColumnType.TEXT }), + new Column({ name: 'content', type: ColumnType.TEXT }) + ] + }) + ]), + flags: { + enableMultiTabs: false + } + }).getInstance(); + + expect(await powersync.getAll('SELECT * FROM ps_crud')).empty; + + await powersync.execute('INSERT INTO logs(id, level, content) VALUES(?, ?, ?)', [testId, 'INFO', 'test log']); + + expect(await powersync.getAll('SELECT data FROM ps_crud ORDER BY id')).deep.equals([ + { + data: `{"op":"PUT","type":"logs","id":"${testId}","data":{"content":"test log","level":"INFO"}}` + } + ]); + + expect(await powersync.getAll('SELECT * FROM logs')).empty; + + var tx = (await powersync.getNextCrudTransaction())!; + expect(tx.transactionId).equals(1); + const expectedCrudEntry = new CrudEntry(1, UpdateType.PUT, 'logs', testId, 1, { + level: 'INFO', + content: 'test log' + }); + expect(tx.crud[0].equals(expectedCrudEntry)).true; + }); + + it('big numbers - integer', async () => { + const bigNumber = 1 << 62; + await powersync.execute('INSERT INTO assets(id, quantity) VALUES(?, ?)', [testId, bigNumber]); + + expect(await powersync.get('SELECT quantity FROM assets WHERE id = ?', [testId])).deep.equals({ + quantity: bigNumber + }); + expect(await powersync.getAll('SELECT data FROM ps_crud ORDER BY id')).deep.equals([ + { + data: `{"op":"PUT","type":"assets","id":"${testId}","data":{"quantity":${bigNumber}}}` + } + ]); + + var tx = (await powersync.getNextCrudTransaction())!; + expect(tx.transactionId).equals(1); + + expect(tx.crud[0].equals(new CrudEntry(1, UpdateType.PUT, 'assets', testId, 1, { quantity: bigNumber }))).equals( + true + ); + }); + + it('big numbers - text', async () => { + const bigNumber = 1 << 62; + await powersync.execute('INSERT INTO assets(id, quantity) VALUES(?, ?)', [testId, `${bigNumber}`]); + + // Cast as INTEGER when querying + expect(await powersync.get('SELECT quantity FROM assets WHERE id = ?', [testId])).deep.equals({ + quantity: bigNumber + }); + + // Not cast as part of crud / persistance + expect(await powersync.getAll('SELECT data FROM ps_crud ORDER BY id')).deep.equals([ + { + data: `{"op":"PUT","type":"assets","id":"${testId}","data":{"quantity":"${bigNumber}"}}` + } + ]); + + await powersync.execute('DELETE FROM ps_crud WHERE 1'); + + await powersync.execute('UPDATE assets SET description = ?, quantity = quantity + 1 WHERE id = ?', [ + 'updated', + testId + ]); + + expect(await powersync.getAll('SELECT data FROM ps_crud ORDER BY id')).deep.equals([ + { + data: `{"op":"PATCH","type":"assets","id":"${testId}","data":{"description":"updated","quantity":${bigNumber + 1}}}` + } + ]); + }); + + it('Transaction grouping', async () => { + expect(await powersync.getAll('SELECT * FROM ps_crud')).empty; + await powersync.writeTransaction(async (tx) => { + await tx.execute('INSERT INTO assets(id, description) VALUES(?, ?)', [testId, 'test1']); + await tx.execute('INSERT INTO assets(id, description) VALUES(?, ?)', ['test2', 'test2']); + }); + + await powersync.writeTransaction(async (tx) => { + await tx.execute('UPDATE assets SET description = ? WHERE id = ?', ['updated', testId]); + }); + + var tx1 = (await powersync.getNextCrudTransaction())!; + expect(tx1.transactionId).equals(1); + const expectedCrudEntries = [ + new CrudEntry(1, UpdateType.PUT, 'assets', testId, 1, { description: 'test1' }), + new CrudEntry(2, UpdateType.PUT, 'assets', 'test2', 1, { description: 'test2' }) + ]; + + expect(tx1.crud.map((entry, index) => entry.equals(expectedCrudEntries[index]))).deep.equals([true, true]); + await tx1.complete(); + + var tx2 = (await powersync.getNextCrudTransaction())!; + expect(tx2.transactionId).equals(2); + const expectedCrudEntry2 = new CrudEntry(3, UpdateType.PATCH, 'assets', testId, 2, { description: 'updated' }); + expect(tx2.crud[0].equals(expectedCrudEntry2)).true; + await tx2.complete(); + expect(await powersync.getNextCrudTransaction()).equals(null); + }); +}); diff --git a/packages/powersync-sdk-web/tests/main.test.ts b/packages/powersync-sdk-web/tests/main.test.ts new file mode 100644 index 00000000..71b400d5 --- /dev/null +++ b/packages/powersync-sdk-web/tests/main.test.ts @@ -0,0 +1,50 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { AbstractPowerSyncDatabase, Column, ColumnType, Schema, Table } from '@journeyapps/powersync-sdk-common'; +import { WASQLitePowerSyncDatabaseOpenFactory } from '@journeyapps/powersync-sdk-web'; +import { v4 as uuid } from 'uuid'; +// TODO import tests from a common package + +type User = { + name: string; +}; + +describe('Basic', () => { + const factory = new WASQLitePowerSyncDatabaseOpenFactory({ + dbFilename: 'test-basic.db', + flags: { + enableMultiTabs: false + }, + schema: new Schema([ + new Table({ + name: 'users', + columns: [new Column({ name: 'name', type: ColumnType.TEXT })] + }) + ]) + }); + + let db: AbstractPowerSyncDatabase; + + beforeEach(() => { + db = factory.getInstance(); + }); + + afterEach(async () => { + await db.disconnectAndClear(); + await db.close(); + }); + + describe('executeQuery', () => { + it('should execute a select query using getAll', async () => { + const result = await db.getAll('SELECT * FROM users'); + expect(result.length).toEqual(0); + }); + + it('should allow inserts', async () => { + const testName = 'Steven'; + await db.execute('INSERT INTO users (id, name) VALUES(?, ?)', [uuid(), testName]); + const result = await db.get('SELECT * FROM users'); + + expect(result.name).equals(testName); + }); + }); +}); diff --git a/packages/powersync-sdk-web/tests/schema.test.ts b/packages/powersync-sdk-web/tests/schema.test.ts new file mode 100644 index 00000000..10acce29 --- /dev/null +++ b/packages/powersync-sdk-web/tests/schema.test.ts @@ -0,0 +1,151 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { + AbstractPowerSyncDatabase, + Column, + ColumnType, + Index, + IndexedColumn, + Schema, + Table +} from '@journeyapps/powersync-sdk-common'; +import { WASQLitePowerSyncDatabaseOpenFactory } from '@journeyapps/powersync-sdk-web'; + +type SchemaVersionResult = { + schema_version: number; +}; + +/** + * Generates the Asset table with configurable options which + * will be modified later. + */ +const generateAssetsTable = (weightColumnName: string = 'weight', includeIndexes = true, indexAscending = true) => + new Table({ + name: 'assets', + columns: [ + new Column({ name: 'created_at', type: ColumnType.TEXT }), + new Column({ name: 'make', type: ColumnType.TEXT }), + new Column({ name: 'model', type: ColumnType.TEXT }), + new Column({ name: 'serial_number', type: ColumnType.TEXT }), + new Column({ name: 'quantity', type: ColumnType.INTEGER }), + new Column({ name: 'user_id', type: ColumnType.TEXT }), + new Column({ name: weightColumnName, type: ColumnType.REAL }), + new Column({ name: 'description', type: ColumnType.TEXT }) + ], + indexes: includeIndexes + ? [ + new Index({ + name: 'makemodel', + columns: [ + new IndexedColumn({ + name: 'make' + }), + new IndexedColumn({ name: 'model', ascending: indexAscending }) + ] + }) + ] + : [] + }); + +/** + * Generates all the schema tables. + * Allows for a custom assets table generator to be supplied. + */ +const generateSchemaTables = (assetsTableGenerator: () => Table = generateAssetsTable) => [ + assetsTableGenerator(), + new Table({ + name: 'customers', + columns: [new Column({ name: 'name', type: ColumnType.TEXT }), new Column({ name: 'email', type: ColumnType.TEXT })] + }), + new Table({ + name: 'logs', + insertOnly: true, + columns: [ + new Column({ name: 'level', type: ColumnType.TEXT }), + new Column({ name: 'content', type: ColumnType.TEXT }) + ] + }), + + new Table({ + name: 'credentials', + localOnly: true, + columns: [new Column({ name: 'key', type: ColumnType.TEXT }), new Column({ name: 'value', type: ColumnType.TEXT })] + }), + new Table({ + name: 'aliased', + columns: [new Column({ name: 'name', type: ColumnType.TEXT })], + viewName: 'test1' + }) +]; + +/** + * The default schema + */ +const schema = new Schema(generateSchemaTables()); + +describe('Schema Tests', () => { + const factory = new WASQLitePowerSyncDatabaseOpenFactory({ + dbFilename: 'test-schema.db', + schema, + flags: { + enableMultiTabs: false + } + }); + + let powersync: AbstractPowerSyncDatabase; + + beforeEach(async () => { + powersync = factory.getInstance(); + }); + + afterEach(async () => { + await powersync.disconnectAndClear(); + await powersync.close(); + }); + + it('Schema versioning', async () => { + // Test that powersync_replace_schema() is a no-op when the schema is not + // modified. + const versionBefore = await powersync.get('PRAGMA schema_version'); + await powersync.updateSchema(schema); + const versionAfter = await powersync.get('PRAGMA schema_version'); + + // No change + expect(versionAfter['schema_version']).equals(versionBefore['schema_version']); + + // The `weight` columns is now `weights` + const schema2 = new Schema(generateSchemaTables(() => generateAssetsTable('weights'))); + + await powersync.updateSchema(schema2); + + const versionAfter2 = await powersync.get('PRAGMA schema_version'); + + // Updated + expect(versionAfter2['schema_version']).greaterThan(versionAfter['schema_version']); + + // The index is now descending + const schema3 = new Schema(generateSchemaTables(() => generateAssetsTable('weights', true, false))); + + await powersync.updateSchema(schema3); + + const versionAfter3 = await powersync.get('PRAGMA schema_version'); + + // Updated + expect(versionAfter3['schema_version']).greaterThan(versionAfter2['schema_version']); + }); + + it('Indexing', async () => { + const results = await powersync.execute('EXPLAIN QUERY PLAN SELECT * FROM assets WHERE make = ?', ['test']); + + expect(results.rows?._array?.[0]['detail']).contains('USING INDEX ps_data__assets__makemodel'); + + // Now drop the index + const schema2 = new Schema(generateSchemaTables(() => generateAssetsTable('weight', false))); + await powersync.updateSchema(schema2); + + // Execute instead of getAll so that we don't get a cached query plan + // from a different connection + const results2 = await powersync.execute('EXPLAIN QUERY PLAN SELECT * FROM assets WHERE make = ?', ['test']); + + expect(results2.rows?._array?.[0]['detail']).contains('SCAN'); + }); +}); diff --git a/packages/powersync-sdk-web/tests/stream.test.ts b/packages/powersync-sdk-web/tests/stream.test.ts new file mode 100644 index 00000000..00925406 --- /dev/null +++ b/packages/powersync-sdk-web/tests/stream.test.ts @@ -0,0 +1,179 @@ +import { beforeAll, describe, expect, it } from 'vitest'; +import { + AbstractPowerSyncDatabase, + AbstractRemote, + AbstractStreamingSyncImplementation, + Column, + ColumnType, + PowerSyncBackendConnector, + PowerSyncCredentials, + PowerSyncDatabaseOptions, + RemoteConnector, + Schema, + Table +} from '@journeyapps/powersync-sdk-common'; +import { + PowerSyncDatabase, + WASQLitePowerSyncDatabaseOpenFactory, + WebPowerSyncDatabaseOptions, + WebStreamingSyncImplementation +} from '@journeyapps/powersync-sdk-web'; +import Logger from 'js-logger'; +import { WebPowerSyncOpenFactoryOptions } from 'src/db/adapters/AbstractWebPowerSyncDatabaseOpenFactory'; +import { v4 as uuid } from 'uuid'; + +class TestConnector implements PowerSyncBackendConnector { + async fetchCredentials(): Promise { + return { + endpoint: '', + token: '' + }; + } + async uploadData(database: AbstractPowerSyncDatabase): Promise { + return; + } +} + +class MockRemote extends AbstractRemote { + streamController: ReadableStreamDefaultController | null; + + constructor( + connector: RemoteConnector, + protected onStreamRequested: () => void + ) { + super(connector); + this.streamController = null; + } + + post(path: string, data: any, headers?: Record | undefined): Promise { + throw new Error('Method not implemented.'); + } + get(path: string, headers?: Record | undefined): Promise { + throw new Error('Method not implemented.'); + } + async postStreaming(): Promise { + return new Response(this.generateStream()).body; + } + + private generateStream() { + return new ReadableStream({ + start: (controller) => { + this.streamController = controller; + this.onStreamRequested(); + } + }); + } +} + +class MockedStreamPowerSync extends PowerSyncDatabase { + constructor( + options: WebPowerSyncDatabaseOptions, + protected remote: AbstractRemote + ) { + super(options); + } + + protected generateSyncStreamImplementation( + connector: PowerSyncBackendConnector + ): AbstractStreamingSyncImplementation { + return new WebStreamingSyncImplementation({ + adapter: this.bucketStorageAdapter, + remote: this.remote, + uploadCrud: async () => { + await this.waitForReady(); + await connector.uploadData(this); + }, + identifier: this.options.database.name + }); + } +} + +class MockOpenFactory extends WASQLitePowerSyncDatabaseOpenFactory { + constructor( + options: WebPowerSyncOpenFactoryOptions, + protected remote: AbstractRemote + ) { + super(options); + } + generateInstance(options: PowerSyncDatabaseOptions): AbstractPowerSyncDatabase { + return new MockedStreamPowerSync( + { + ...options + }, + this.remote + ); + } +} + +describe('Stream test', () => { + beforeAll(() => Logger.useDefaults()); + + it('PowerSync reconnect', async () => { + /** + * Very basic implementation of a listener pattern. + * Required since we cannot extend multiple classes. + */ + const callbacks: Map void> = new Map(); + const remote = new MockRemote(new TestConnector(), () => callbacks.forEach((c) => c())); + + const powersync = new MockOpenFactory( + { + dbFilename: 'test-stream-connection.db', + flags: { + enableMultiTabs: false + }, + schema: new Schema([ + new Table({ + name: 'users', + columns: [new Column({ name: 'name', type: ColumnType.TEXT })] + }) + ]) + }, + remote + ).getInstance(); + + const waitForStream = () => + new Promise((resolve) => { + const id = uuid(); + callbacks.set(id, () => { + resolve(); + callbacks.delete(id); + }); + }); + + const streamOpened = waitForStream(); + + powersync.connect(new TestConnector()); + + await streamOpened; + + remote.streamController?.enqueue(new TextEncoder().encode('{"token_expires_in":3426}\n')); + + // Wait for connected to be true + await new Promise((resolve) => { + if (powersync.connected) { + resolve(); + } + const l = powersync.registerListener({ + statusChanged: (status) => { + if (status.connected) { + resolve(); + l?.(); + } + } + }); + }); + + expect(powersync.connected).true; + + // Close the stream + const newStream = waitForStream(); + remote.streamController?.close(); + + // A new stream should be requested + await newStream; + + await powersync.disconnectAndClear(); + await powersync.close(); + }); +}); diff --git a/packages/powersync-sdk-web/tests/test_schema.ts b/packages/powersync-sdk-web/tests/test_schema.ts new file mode 100644 index 00000000..7ee88cca --- /dev/null +++ b/packages/powersync-sdk-web/tests/test_schema.ts @@ -0,0 +1,28 @@ +import { Column, ColumnType, Index, IndexedColumn, Schema, Table } from '@journeyapps/powersync-sdk-web'; + +export const testSchema = new Schema([ + new Table({ + name: 'assets', + columns: [ + new Column({ name: 'created_at', type: ColumnType.TEXT }), + new Column({ name: 'make', type: ColumnType.TEXT }), + new Column({ name: 'model', type: ColumnType.TEXT }), + new Column({ name: 'serial_number', type: ColumnType.TEXT }), + new Column({ name: 'quantity', type: ColumnType.INTEGER }), + new Column({ name: 'user_id', type: ColumnType.TEXT }), + new Column({ name: 'customer_id', type: ColumnType.TEXT }), + new Column({ name: 'description', type: ColumnType.TEXT }) + ], + indexes: [ + new Index({ + name: 'makemodel', + columns: [new IndexedColumn({ name: 'make' }), new IndexedColumn({ name: 'model' })] + }) + ] + }), + + new Table({ + name: 'customers', + columns: [new Column({ name: 'name', type: ColumnType.TEXT }), new Column({ name: 'email', type: ColumnType.TEXT })] + }) +]); diff --git a/packages/powersync-sdk-web/tests/watch.test.ts b/packages/powersync-sdk-web/tests/watch.test.ts new file mode 100644 index 00000000..5ca015e0 --- /dev/null +++ b/packages/powersync-sdk-web/tests/watch.test.ts @@ -0,0 +1,105 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +vi.useRealTimers(); +import { v4 as uuid } from 'uuid'; +import { AbstractPowerSyncDatabase, Schema } from '@journeyapps/powersync-sdk-common'; +import { WASQLitePowerSyncDatabaseOpenFactory } from '@journeyapps/powersync-sdk-web'; +import { testSchema } from './test_schema'; +import _ from 'lodash'; + +/** + * There seems to be an issue with Vitest browser mode's setTimeout and + * fake timer functionality. + * e.g. calling: + * await new Promise((resolve) => setTimeout(resolve, 10)); + * waits for 1 second instead of 10ms. + * Setting this to 1 second as a work around. + */ +const throttleDuration = 1000; + +describe('Watch Tests', () => { + const factory = new WASQLitePowerSyncDatabaseOpenFactory({ + dbFilename: 'test-watch.db', + schema: testSchema, + flags: { + enableMultiTabs: false + } + }); + + let powersync: AbstractPowerSyncDatabase; + + beforeEach(async () => { + powersync = factory.getInstance(); + }); + + afterEach(async () => { + await powersync.disconnectAndClear(); + await powersync.close(); + }); + + it('watch outside throttle limits', async () => { + const abortController = new AbortController(); + + const watch = powersync.watch( + 'SELECT count() AS count FROM assets INNER JOIN customers ON customers.id = assets.customer_id', + [], + { signal: abortController.signal, throttleMs: throttleDuration } + ); + + const updatesCount = 2; + let receivedUpdatesCount = 0; + + /** + * Promise which resolves once we received the same amount of update + * notifications as there are inserts. + */ + const receivedUpdates = new Promise(async (resolve) => { + for await (const update of watch) { + receivedUpdatesCount++; + if (receivedUpdatesCount == updatesCount) { + abortController.abort(); + resolve(); + } + } + }); + + for (let updateCount = 0; updateCount < updatesCount; updateCount++) { + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['test', uuid()]); + + // Wait the throttle duration, ensuring a watch update for each insert + await new Promise((resolve) => setTimeout(resolve, throttleDuration)); + } + + await receivedUpdates; + expect(receivedUpdatesCount).equals(updatesCount); + }); + + it('watch inside throttle limits', async () => { + const abortController = new AbortController(); + + const watch = powersync.watch( + 'SELECT count() AS count FROM assets INNER JOIN customers ON customers.id = assets.customer_id', + [], + { signal: abortController.signal, throttleMs: throttleDuration } + ); + + const updatesCount = 5; + let receivedUpdatesCount = 0; + // Listen to updates + (async () => { + for await (const update of watch) { + receivedUpdatesCount++; + } + })(); + + // Create the inserts as fast as possible + for (let updateCount = 0; updateCount < updatesCount; updateCount++) { + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['test', uuid()]); + } + + await new Promise((resolve) => setTimeout(resolve, throttleDuration * 2)); + abortController.abort(); + + // There should be one initial result plus one throttled result + expect(receivedUpdatesCount).equals(2); + }); +}); diff --git a/packages/powersync-sdk-web/tsconfig.json b/packages/powersync-sdk-web/tsconfig.json index dd7c0c64..09f01794 100644 --- a/packages/powersync-sdk-web/tsconfig.json +++ b/packages/powersync-sdk-web/tsconfig.json @@ -1,6 +1,10 @@ { "compilerOptions": { "composite": true, + "paths": { + "@journeyapps/powersync-sdk-web": ["src/index.ts"] + }, + "baseUrl": "./", "declaration": true /* Generates corresponding '.d.ts' file. */, "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, @@ -13,5 +17,5 @@ "strict": true /* Enable all strict type-checking options. */, "target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ }, - "include": ["src/**/*"] + "include": ["src/**/*", "tests/**/*"] } diff --git a/packages/powersync-sdk-web/vitest.config.ts b/packages/powersync-sdk-web/vitest.config.ts new file mode 100644 index 00000000..30de1d5d --- /dev/null +++ b/packages/powersync-sdk-web/vitest.config.ts @@ -0,0 +1,41 @@ +import wasm from 'vite-plugin-wasm'; +import topLevelAwait from 'vite-plugin-top-level-await'; +import path from 'path'; +import { defineConfig, UserConfigExport } from 'vitest/config'; + +const config: UserConfigExport = { + // This is only needed for local tests to resolve the package name correctly + resolve: { + alias: { + /** + * Note that this requires the Typescript to be compiled with `tsc` + * first. This is required due to the format of Webworker URIs + * they link to `.js` files. + */ + '@journeyapps/powersync-sdk-web': path.resolve(__dirname, './lib/src') + } + }, + worker: { + format: 'es', + plugins: () => [wasm(), topLevelAwait()] + }, + optimizeDeps: { + // Don't optimise these packages as they contain web workers and WASM files. + // https://github.com/vitejs/vite/issues/11672#issuecomment-1415820673 + exclude: ['@journeyapps/wa-sqlite', '@journeyapps/powersync-sdk-web'] + }, + plugins: [wasm(), topLevelAwait()], + test: { + globals: true, + setupFiles: [], + include: ['tests/**/*.test.ts'], + browser: { + enabled: true, + provider: 'webdriverio', + headless: true, + name: 'chrome' // browser name is required + } + } +}; + +export default defineConfig(config); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 37c743b9..09bfb7ec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -908,9 +908,27 @@ importers: '@types/uuid': specifier: ^9.0.6 version: 9.0.8 + '@vitest/browser': + specifier: ^1.3.1 + version: 1.3.1(vitest@1.3.1)(webdriverio@8.32.3) typescript: specifier: ^5.2.2 version: 5.3.3 + vite: + specifier: ^5.1.1 + version: 5.1.1 + vite-plugin-top-level-await: + specifier: ^1.4.1 + version: 1.4.1(vite@5.1.1) + vite-plugin-wasm: + specifier: ^3.3.0 + version: 3.3.0(vite@5.1.1) + vitest: + specifier: ^1.3.1 + version: 1.3.1(@vitest/browser@1.3.1) + webdriverio: + specifier: ^8.32.3 + version: 8.32.3(typescript@5.3.3) packages: @@ -7168,7 +7186,7 @@ packages: '@segment/loosely-validate-event': 2.0.0 fetch-retry: 4.1.1 md5: 2.3.0 - node-fetch: 2.6.7 + node-fetch: 2.7.0 remove-trailing-slash: 0.1.1 uuid: 8.3.2 transitivePeerDependencies: @@ -8449,6 +8467,44 @@ packages: resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} dev: false + /@puppeteer/browsers@1.4.6(typescript@5.3.3): + resolution: {integrity: sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==} + engines: {node: '>=16.3.0'} + hasBin: true + peerDependencies: + typescript: '>= 4.7.4' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + debug: 4.3.4(supports-color@8.1.1) + extract-zip: 2.0.1 + progress: 2.0.3 + proxy-agent: 6.3.0 + tar-fs: 3.0.4 + typescript: 5.3.3 + unbzip2-stream: 1.4.3 + yargs: 17.7.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@puppeteer/browsers@1.9.1: + resolution: {integrity: sha512-PuvK6xZzGhKPvlx3fpfdM2kYY3P/hB1URtK8wA7XUJ6prn6pp22zvJHu48th0SGcHL9SutbPHrFuQgfXTFobWA==} + engines: {node: '>=16.3.0'} + hasBin: true + dependencies: + debug: 4.3.4(supports-color@8.1.1) + extract-zip: 2.0.1 + progress: 2.0.3 + proxy-agent: 6.3.1 + tar-fs: 3.0.4 + unbzip2-stream: 1.4.3 + yargs: 17.7.2 + transitivePeerDependencies: + - supports-color + dev: true + /@radix-ui/react-compose-refs@1.0.0(react@18.2.0): resolution: {integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==} peerDependencies: @@ -11848,6 +11904,10 @@ packages: - '@tiptap/pm' dev: false + /@tootallnate/quickjs-emscripten@0.23.0: + resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + dev: true + /@trysound/sax@0.2.0: resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} @@ -12273,6 +12333,10 @@ packages: - webpack-cli dev: true + /@types/which@2.0.2: + resolution: {integrity: sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==} + dev: true + /@types/ws@8.5.10: resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} dependencies: @@ -12296,6 +12360,14 @@ packages: dependencies: '@types/yargs-parser': 21.0.3 + /@types/yauzl@2.10.3: + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + requiresBuild: true + dependencies: + '@types/node': 20.11.17 + dev: true + optional: true + /@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.55.0)(typescript@5.3.2): resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} engines: {node: ^16.0.0 || >=18.0.0} @@ -12521,6 +12593,131 @@ packages: vite: 5.0.12(@types/node@20.11.17)(less@4.2.0)(sass@1.69.7)(terser@5.26.0) dev: true + /@vitest/browser@1.3.1(vitest@1.3.1)(webdriverio@8.32.3): + resolution: {integrity: sha512-pRof8G8nqRWwg3ouyIctyhfIVk5jXgF056uF//sqdi37+pVtDz9kBI/RMu0xlc8tgCyJ2aEMfbgJZPUydlEVaQ==} + peerDependencies: + playwright: '*' + safaridriver: '*' + vitest: 1.3.1 + webdriverio: '*' + peerDependenciesMeta: + playwright: + optional: true + safaridriver: + optional: true + webdriverio: + optional: true + dependencies: + '@vitest/utils': 1.3.1 + magic-string: 0.30.5 + sirv: 2.0.4 + vitest: 1.3.1(@vitest/browser@1.3.1) + webdriverio: 8.32.3(typescript@5.3.3) + dev: true + + /@vitest/expect@1.3.1: + resolution: {integrity: sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==} + dependencies: + '@vitest/spy': 1.3.1 + '@vitest/utils': 1.3.1 + chai: 4.4.1 + dev: true + + /@vitest/runner@1.3.1: + resolution: {integrity: sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==} + dependencies: + '@vitest/utils': 1.3.1 + p-limit: 5.0.0 + pathe: 1.1.2 + dev: true + + /@vitest/snapshot@1.3.1: + resolution: {integrity: sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==} + dependencies: + magic-string: 0.30.5 + pathe: 1.1.2 + pretty-format: 29.7.0 + dev: true + + /@vitest/spy@1.3.1: + resolution: {integrity: sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==} + dependencies: + tinyspy: 2.2.1 + dev: true + + /@vitest/utils@1.3.1: + resolution: {integrity: sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==} + dependencies: + diff-sequences: 29.6.3 + estree-walker: 3.0.3 + loupe: 2.3.7 + pretty-format: 29.7.0 + dev: true + + /@wdio/config@8.32.3: + resolution: {integrity: sha512-hZkaz5Fd8830uniQvRgPus8yp9rp50MAsHa5kZ2Jt8y++Rp330FyJpQZE5oPjTATuz35G5Anprk2Q3fmFd0Ifw==} + engines: {node: ^16.13 || >=18} + dependencies: + '@wdio/logger': 8.28.0 + '@wdio/types': 8.32.2 + '@wdio/utils': 8.32.3 + decamelize: 6.0.0 + deepmerge-ts: 5.1.0 + glob: 10.3.10 + import-meta-resolve: 4.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@wdio/logger@8.28.0: + resolution: {integrity: sha512-/s6zNCqwy1hoc+K4SJypis0Ud0dlJ+urOelJFO1x0G0rwDRWyFiUP6ijTaCcFxAm29jYEcEPWijl2xkVIHwOyA==} + engines: {node: ^16.13 || >=18} + dependencies: + chalk: 5.3.0 + loglevel: 1.9.1 + loglevel-plugin-prefix: 0.8.4 + strip-ansi: 7.1.0 + dev: true + + /@wdio/protocols@8.32.0: + resolution: {integrity: sha512-inLJRrtIGdTz/YPbcsvpSvPlYQFTVtF3OYBwAXhG2FiP1ZwE1CQNLP/xgRGye1ymdGCypGkexRqIx3KBGm801Q==} + dev: true + + /@wdio/repl@8.24.12: + resolution: {integrity: sha512-321F3sWafnlw93uRTSjEBVuvWCxTkWNDs7ektQS15drrroL3TMeFOynu4rDrIz0jXD9Vas0HCD2Tq/P0uxFLdw==} + engines: {node: ^16.13 || >=18} + dependencies: + '@types/node': 20.11.17 + dev: true + + /@wdio/types@8.32.2: + resolution: {integrity: sha512-jq8LcBBQpBP9ZF5kECKEpXv8hN7irCGCjLFAN0Bd5ScRR6qu6MGWvwkDkau2sFPr0b++sKDCEaMzQlwrGFjZXg==} + engines: {node: ^16.13 || >=18} + dependencies: + '@types/node': 20.11.17 + dev: true + + /@wdio/utils@8.32.3: + resolution: {integrity: sha512-UnR9rPpR1W9U5wz2TU+6BQ2rlxtuK/e3fvdaiWIMZKleB/OCcEQFGiGPAGGVi4ShfaTPwz6hK1cTTgj1OtMXkg==} + engines: {node: ^16.13 || >=18} + dependencies: + '@puppeteer/browsers': 1.9.1 + '@wdio/logger': 8.28.0 + '@wdio/types': 8.32.2 + decamelize: 6.0.0 + deepmerge-ts: 5.1.0 + edgedriver: 5.3.10 + geckodriver: 4.3.3 + get-port: 7.0.0 + import-meta-resolve: 4.0.0 + locate-app: 2.2.19 + safaridriver: 0.1.2 + split2: 4.2.0 + wait-port: 1.1.0 + transitivePeerDependencies: + - supports-color + dev: true + /@web3-storage/multipart-parser@1.0.0: resolution: {integrity: sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw==} dev: false @@ -12942,6 +13139,31 @@ packages: resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} dev: true + /archiver-utils@4.0.1: + resolution: {integrity: sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==} + engines: {node: '>= 12.0.0'} + dependencies: + glob: 8.1.0 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash: 4.17.21 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + dev: true + + /archiver@6.0.1: + resolution: {integrity: sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==} + engines: {node: '>= 12.0.0'} + dependencies: + archiver-utils: 4.0.1 + async: 3.2.5 + buffer-crc32: 0.2.13 + readable-stream: 3.6.2 + readdir-glob: 1.1.3 + tar-stream: 3.1.7 + zip-stream: 5.0.1 + dev: true + /arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} dev: true @@ -13086,10 +13308,21 @@ packages: safer-buffer: 2.1.2 dev: true + /assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + dev: true + /ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} dev: true + /ast-types@0.13.4: + resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} + engines: {node: '>=4'} + dependencies: + tslib: 2.6.2 + dev: true + /ast-types@0.15.2: resolution: {integrity: sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==} engines: {node: '>=4'} @@ -13190,6 +13423,10 @@ packages: dequal: 2.0.3 dev: true + /b4a@1.6.6: + resolution: {integrity: sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==} + dev: true + /babel-core@7.0.0-bridge.0(@babel/core@7.23.9): resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} peerDependencies: @@ -13458,6 +13695,37 @@ packages: /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + /bare-events@2.2.0: + resolution: {integrity: sha512-Yyyqff4PIFfSuthCZqLlPISTWHmnQxoPuAvkmgzsJEmG3CesdIv6Xweayl0JkCZJSB2yYIdJyEz97tpxNhgjbg==} + requiresBuild: true + dev: true + optional: true + + /bare-fs@2.1.5: + resolution: {integrity: sha512-5t0nlecX+N2uJqdxe9d18A98cp2u9BETelbjKpiVgQqzzmVNFYWEAjQHqS+2Khgto1vcwhik9cXucaj5ve2WWA==} + requiresBuild: true + dependencies: + bare-events: 2.2.0 + bare-os: 2.2.0 + bare-path: 2.1.0 + streamx: 2.16.1 + dev: true + optional: true + + /bare-os@2.2.0: + resolution: {integrity: sha512-hD0rOPfYWOMpVirTACt4/nK8mC55La12K5fY1ij8HAdfQakD62M+H4o4tpfKzVGLgRDTuk3vjA4GqGXXCeFbag==} + requiresBuild: true + dev: true + optional: true + + /bare-path@2.1.0: + resolution: {integrity: sha512-DIIg7ts8bdRKwJRJrUMy/PICEaQZaPGZ26lsSx9MJSwIhSrcdHn7/C8W+XmnG/rKi6BaRcz+JO00CjZteybDtw==} + requiresBuild: true + dependencies: + bare-os: 2.2.0 + dev: true + optional: true + /base-64@1.0.0: resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==} dev: false @@ -13477,6 +13745,11 @@ packages: safe-buffer: 5.1.2 dev: true + /basic-ftp@5.0.4: + resolution: {integrity: sha512-8PzkB0arJFV4jJWSGOYR+OEic6aeKMu/osRhBULN6RY0ykby6LKhbmuQ5ublvaas5BOwboah5D87nrHyuh8PPA==} + engines: {node: '>=10.0.0'} + dev: true + /batch@0.6.1: resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} @@ -13514,6 +13787,13 @@ packages: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} + /binary@0.3.0: + resolution: {integrity: sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==} + dependencies: + buffers: 0.1.1 + chainsaw: 0.1.0 + dev: true + /bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} dependencies: @@ -13521,6 +13801,10 @@ packages: inherits: 2.0.4 readable-stream: 3.6.2 + /bluebird@3.4.7: + resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==} + dev: true + /blueimp-md5@2.19.0: resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==} dev: false @@ -13660,6 +13944,10 @@ packages: buffer-fill: 1.0.0 dev: false + /buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + dev: true + /buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} dev: true @@ -13671,12 +13959,22 @@ packages: /buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + /buffer-indexof-polyfill@1.0.2: + resolution: {integrity: sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==} + engines: {node: '>=0.10'} + dev: true + /buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + /buffers@0.1.1: + resolution: {integrity: sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==} + engines: {node: '>=0.2.0'} + dev: true + /builtin-modules@3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} @@ -13717,6 +14015,11 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + /cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + dev: true + /cacache@15.3.0: resolution: {integrity: sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==} engines: {node: '>= 10'} @@ -13876,6 +14179,25 @@ packages: /ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + /chai@4.4.1: + resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} + engines: {node: '>=4'} + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.3 + deep-eql: 4.1.3 + get-func-name: 2.0.2 + loupe: 2.3.7 + pathval: 1.1.1 + type-detect: 4.0.8 + dev: true + + /chainsaw@0.1.0: + resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==} + dependencies: + traverse: 0.3.9 + dev: true + /chalk-template@0.4.0: resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==} engines: {node: '>=12'} @@ -13930,6 +14252,12 @@ packages: /charenc@0.0.2: resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} + /check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + dependencies: + get-func-name: 2.0.2 + dev: true + /cheerio-select@2.1.0: resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} dependencies: @@ -13987,6 +14315,15 @@ packages: resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==} engines: {node: '>=6.0'} + /chromium-bidi@0.4.16(devtools-protocol@0.0.1147663): + resolution: {integrity: sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==} + peerDependencies: + devtools-protocol: '*' + dependencies: + devtools-protocol: 0.0.1147663 + mitt: 3.0.0 + dev: true + /chromium-edge-launcher@1.0.0: resolution: {integrity: sha512-pgtgjNKZ7i5U++1g1PWv75umkHvhVTDOQIZ+sjeUX9483S7Y6MUvO0lrd7ShGlQlFHMN4SwKTCq/X8hWrbv2KA==} dependencies: @@ -14281,6 +14618,16 @@ packages: /component-type@1.2.2: resolution: {integrity: sha512-99VUHREHiN5cLeHm3YLq312p6v+HUEcwtLCAtelvUDI6+SH5g5Cr85oNR2S1o6ywzL0ykMbuwLzM2ANocjEOIA==} + /compress-commons@5.0.1: + resolution: {integrity: sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==} + engines: {node: '>= 12.0.0'} + dependencies: + crc-32: 1.2.2 + crc32-stream: 5.0.0 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + dev: true + /compressible@2.0.18: resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} engines: {node: '>= 0.6'} @@ -14495,6 +14842,20 @@ packages: path-type: 4.0.0 typescript: 5.3.3 + /crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + dev: true + + /crc32-stream@5.0.0: + resolution: {integrity: sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==} + engines: {node: '>= 12.0.0'} + dependencies: + crc-32: 1.2.2 + readable-stream: 3.6.2 + dev: true + /create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} dev: true @@ -14523,6 +14884,14 @@ packages: - encoding dev: false + /cross-fetch@4.0.0: + resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==} + dependencies: + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + dev: true + /cross-spawn@5.1.0: resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} dependencies: @@ -14672,6 +15041,10 @@ packages: domutils: 3.1.0 nth-check: 2.1.1 + /css-shorthand-properties@1.1.1: + resolution: {integrity: sha512-Md+Juc7M3uOdbAFwOYlTrccIZ7oCFuzrhKYQjdeUEW/sE1hv17Jp/Bws+ReOPpGVBTYCBoYo+G17V5Qo8QQ75A==} + dev: true + /css-tree@1.1.3: resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} engines: {node: '>=8.0.0'} @@ -14679,6 +15052,10 @@ packages: mdn-data: 2.0.14 source-map: 0.6.1 + /css-value@0.0.1: + resolution: {integrity: sha512-FUV3xaJ63buRLgHrLQVlVgQnQdR4yqdLGaDu7g8CQcWjInDfM9plBTPI9FRfpahju1UBSaMckeb2/46ApS/V1Q==} + dev: true + /css-what@6.1.0: resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} engines: {node: '>= 6'} @@ -15059,6 +15436,11 @@ packages: engines: {node: '>= 12'} dev: true + /data-uri-to-buffer@6.0.2: + resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} + engines: {node: '>= 14'} + dev: true + /date-fns@2.30.0: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} @@ -15120,6 +15502,11 @@ packages: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} + /decamelize@6.0.0: + resolution: {integrity: sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + /decode-named-character-reference@1.0.2: resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} dependencies: @@ -15136,6 +15523,13 @@ packages: dependencies: mimic-response: 3.1.0 + /deep-eql@4.1.3: + resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} + engines: {node: '>=6'} + dependencies: + type-detect: 4.0.8 + dev: true + /deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -15144,6 +15538,11 @@ packages: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true + /deepmerge-ts@5.1.0: + resolution: {integrity: sha512-eS8dRJOckyo9maw9Tu5O5RUi/4inFLrnoLkBe3cPfDMx3WZioXtmOew4TXQaxq7Rhl4xjDtR7c6x8nNTxOvbFw==} + engines: {node: '>=16.0.0'} + dev: true + /deepmerge@2.2.1: resolution: {integrity: sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==} engines: {node: '>=0.10.0'} @@ -15197,6 +15596,15 @@ packages: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + /degenerator@5.0.1: + resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} + engines: {node: '>= 14'} + dependencies: + ast-types: 0.13.4 + escodegen: 2.1.0 + esprima: 4.0.1 + dev: true + /del@4.1.1: resolution: {integrity: sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==} engines: {node: '>=6'} @@ -15310,6 +15718,14 @@ packages: dependencies: dequal: 2.0.3 + /devtools-protocol@0.0.1147663: + resolution: {integrity: sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==} + dev: true + + /devtools-protocol@0.0.1262051: + resolution: {integrity: sha512-YJe4CT5SA8on3Spa+UDtNhEqtuV6Epwz3OZ4HQVLhlRccpZ9/PAYk0/cy/oKxFKRrZPBUPyxympQci4yWNWZ9g==} + dev: true + /dicer@0.3.1: resolution: {integrity: sha512-ObioMtXnmjYs3aRtpIJt9rgQSPCIhKVkFPip+E9GUDyWl8N435znUxK/JfNwGZJ2wnn5JKQ7Ly3vOK5Q5dylGA==} engines: {node: '>=10.0.0'} @@ -15321,6 +15737,11 @@ packages: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} dev: true + /diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + /diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -15466,6 +15887,12 @@ packages: dev: true optional: true + /duplexer2@0.1.4: + resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + dependencies: + readable-stream: 2.3.8 + dev: true + /duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} @@ -15568,6 +15995,27 @@ packages: safe-buffer: 5.2.1 dev: true + /edge-paths@3.0.5: + resolution: {integrity: sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==} + engines: {node: '>=14.0.0'} + dependencies: + '@types/which': 2.0.2 + which: 2.0.2 + dev: true + + /edgedriver@5.3.10: + resolution: {integrity: sha512-RFSHYMNtcF1PjaGZCA2rdQQ8hSTLPZgcYgeY1V6dC+tR4NhZXwFAku+8hCbRYh7ZlwKKrTbVu9FwknjFddIuuw==} + hasBin: true + requiresBuild: true + dependencies: + '@wdio/logger': 8.28.0 + decamelize: 6.0.0 + edge-paths: 3.0.5 + node-fetch: 3.3.2 + unzipper: 0.10.14 + which: 4.0.0 + dev: true + /ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -15617,7 +16065,6 @@ packages: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} dependencies: once: 1.4.0 - dev: false /enhanced-resolve@5.15.0: resolution: {integrity: sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==} @@ -15914,6 +16361,18 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} + /escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + dev: true + /eslint-config-next@14.0.0(eslint@8.55.0)(typescript@5.3.3): resolution: {integrity: sha512-jtXeE+/pGQ3h9n11QyyuPN50kO13GO5XvjU5ZRq6W+XTpOMjyobWmK2s7aowy0FtzA49krJzYzEU9s1RMwoJ6g==} peerDependencies: @@ -16902,10 +17361,28 @@ packages: tmp: 0.0.33 dev: true + /extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + dependencies: + debug: 4.3.4(supports-color@8.1.1) + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + dev: true + /fast-base64-decode@1.0.0: resolution: {integrity: sha512-qwaScUgUGBYeDNRnbc/KyllVU88Jk1pRHPStuF/lO7B0/RTRLj7U0lkdTAutlBblY08rwZDff6tNU9cjv6j//Q==} dev: false + /fast-deep-equal@2.0.1: + resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==} + dev: true + /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -16913,6 +17390,10 @@ packages: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} dev: true + /fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + dev: true + /fast-glob@3.3.1: resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} engines: {node: '>=8.6.0'} @@ -17007,6 +17488,12 @@ packages: - encoding dev: false + /fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + dependencies: + pend: 1.2.0 + dev: true + /feed@4.2.2: resolution: {integrity: sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==} engines: {node: '>=0.4.0'} @@ -17422,6 +17909,16 @@ packages: requiresBuild: true optional: true + /fstream@1.0.12: + resolution: {integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==} + engines: {node: '>=0.6'} + dependencies: + graceful-fs: 4.2.11 + inherits: 2.0.4 + mkdirp: 0.5.6 + rimraf: 2.7.1 + dev: true + /function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -17437,6 +17934,24 @@ packages: /functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + /geckodriver@4.3.3: + resolution: {integrity: sha512-we2c2COgxFkLVuoknJNx+ioP+7VDq0sr6SCqWHTzlA4kzIbzR0EQ1Pps34s8WrsOnQqPC8a4sZV9dRPROOrkSg==} + engines: {node: ^16.13 || >=18 || >=20} + hasBin: true + requiresBuild: true + dependencies: + '@wdio/logger': 8.28.0 + decamelize: 6.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.4 + node-fetch: 3.3.2 + tar-fs: 3.0.5 + unzipper: 0.10.14 + which: 4.0.0 + transitivePeerDependencies: + - supports-color + dev: true + /gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -17450,6 +17965,10 @@ packages: engines: {node: '>=18'} dev: true + /get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + dev: true + /get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} @@ -17478,6 +17997,11 @@ packages: engines: {node: '>=4'} dev: false + /get-port@7.0.0: + resolution: {integrity: sha512-mDHFgApoQd+azgMdwylJrv2DX47ywGq1i5VFJE7fZ0dttNq3iQMfsU4IvEgBHojA3KqEudyu7Vq+oN8kNaNkWw==} + engines: {node: '>=16'} + dev: true + /get-stream@4.1.0: resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==} engines: {node: '>=6'} @@ -17485,6 +18009,13 @@ packages: pump: 3.0.0 dev: false + /get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + dependencies: + pump: 3.0.0 + dev: true + /get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -17507,6 +18038,18 @@ packages: dependencies: resolve-pkg-maps: 1.0.0 + /get-uri@6.0.3: + resolution: {integrity: sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==} + engines: {node: '>= 14'} + dependencies: + basic-ftp: 5.0.4 + data-uri-to-buffer: 6.0.2 + debug: 4.3.4(supports-color@8.1.1) + fs-extra: 11.2.0 + transitivePeerDependencies: + - supports-color + dev: true + /getenv@1.0.0: resolution: {integrity: sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg==} engines: {node: '>=6'} @@ -17583,6 +18126,17 @@ packages: once: 1.4.0 path-is-absolute: 1.0.1 + /glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.2 + once: 1.4.0 + dev: true + /global-dirs@3.0.1: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} engines: {node: '>=10'} @@ -17627,7 +18181,7 @@ packages: array-union: 2.1.0 dir-glob: 3.0.1 fast-glob: 3.3.2 - ignore: 5.3.0 + ignore: 5.3.1 merge2: 1.4.1 slash: 3.0.0 @@ -18147,6 +18701,16 @@ packages: - supports-color dev: true + /http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.0 + debug: 4.3.4(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + dev: true + /http-proxy-middleware@2.0.6(@types/express@4.17.21): resolution: {integrity: sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==} engines: {node: '>=12.0.0'} @@ -18234,6 +18798,16 @@ packages: - supports-color dev: true + /https-proxy-agent@7.0.4: + resolution: {integrity: sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.0 + debug: 4.3.4(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + dev: true + /human-id@1.0.2: resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} dev: true @@ -18299,6 +18873,7 @@ packages: /ignore@5.3.0: resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==} engines: {node: '>= 4'} + dev: true /ignore@5.3.1: resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} @@ -18351,6 +18926,10 @@ packages: pkg-dir: 4.2.0 resolve-cwd: 3.0.0 + /import-meta-resolve@4.0.0: + resolution: {integrity: sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==} + dev: true + /imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -19101,6 +19680,10 @@ packages: /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + /js-tokens@8.0.3: + resolution: {integrity: sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==} + dev: true + /js-yaml@3.14.1: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} hasBin: true @@ -19313,6 +19896,11 @@ packages: engines: {node: '>= 8'} dev: true + /ky@0.33.3: + resolution: {integrity: sha512-CasD9OCEQSFIam2U8efFK81Yeg8vNMTBUqtMOHlrcWQHqUX3HeCl9Dr31u4toV7emlH8Mymk5+9p0lL6mKb/Xw==} + engines: {node: '>=14.16'} + dev: true + /language-subtag-registry@0.3.22: resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==} dev: true @@ -19340,6 +19928,13 @@ packages: picocolors: 1.0.0 shell-quote: 1.8.1 + /lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + dependencies: + readable-stream: 2.3.8 + dev: true + /less-loader@11.1.0(less@4.2.0)(webpack@5.89.0): resolution: {integrity: sha512-C+uDBV7kS7W5fJlUjq5mPBeBVhYpTIm5gB09APT9o3n/ILeaXVsiSFTbZpTJCJwQ/Crczfn3DmfQFwxYusWFug==} engines: {node: '>= 14.15.0'} @@ -19542,6 +20137,10 @@ packages: - supports-color dev: true + /listenercount@1.0.1: + resolution: {integrity: sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==} + dev: true + /listr2@8.0.1: resolution: {integrity: sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==} engines: {node: '>=18.0.0'} @@ -19580,6 +20179,22 @@ packages: resolution: {integrity: sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==} engines: {node: '>= 12.13.0'} + /local-pkg@0.5.0: + resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} + engines: {node: '>=14'} + dependencies: + mlly: 1.5.0 + pkg-types: 1.0.3 + dev: true + + /locate-app@2.2.19: + resolution: {integrity: sha512-mjhvrYRHnLAVwreShl8NTwq9EUyfRoCqB0UsOlMKXo2KBmtb4dhlHbZH4mcfDsoNoLkHZ1Rq4TsWP/59Ix62Ww==} + dependencies: + n12: 1.8.22 + type-fest: 2.13.0 + userhome: 1.0.0 + dev: true + /locate-path@3.0.0: resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} engines: {node: '>=6'} @@ -19645,6 +20260,10 @@ packages: /lodash.uniq@4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + /lodash.zip@4.2.0: + resolution: {integrity: sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg==} + dev: true + /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -19681,6 +20300,15 @@ packages: dayjs: 1.11.10 yargs: 15.4.1 + /loglevel-plugin-prefix@0.8.4: + resolution: {integrity: sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==} + dev: true + + /loglevel@1.9.1: + resolution: {integrity: sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==} + engines: {node: '>= 0.6.0'} + dev: true + /longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -19690,6 +20318,12 @@ packages: dependencies: js-tokens: 4.0.0 + /loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + dependencies: + get-func-name: 2.0.2 + dev: true + /lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: @@ -19730,6 +20364,11 @@ packages: dependencies: yallist: 4.0.0 + /lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + dev: true + /lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} dev: true @@ -21485,11 +22124,19 @@ packages: minipass: 3.3.6 yallist: 4.0.0 + /mitt@3.0.0: + resolution: {integrity: sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==} + dev: true + /mixme@0.5.10: resolution: {integrity: sha512-5H76ANWinB1H3twpJ6JY8uvAtpmFvHNArpilJAjXRKXSDDLPIMoZArw5SH0q9z+lLs8IrMw7Q2VWpWimFKFT1Q==} engines: {node: '>= 8.0.0'} dev: true + /mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + dev: true + /mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -21501,6 +22148,15 @@ packages: engines: {node: '>=10'} hasBin: true + /mlly@1.5.0: + resolution: {integrity: sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==} + dependencies: + acorn: 8.11.3 + pathe: 1.1.2 + pkg-types: 1.0.3 + ufo: 1.4.0 + dev: true + /moment@2.30.1: resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} requiresBuild: true @@ -21570,6 +22226,10 @@ packages: object-assign: 4.1.1 thenify-all: 1.6.0 + /n12@1.8.22: + resolution: {integrity: sha512-nzPCOuLOIoUuninAMRXfrbkB7O9XkWS7iv7fzDW1pRUaQhMpatj8iX55evwcNRWnm0UF29uuoHpwubYbsV7OGw==} + dev: true + /nan@2.18.0: resolution: {integrity: sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==} requiresBuild: true @@ -21623,6 +22283,11 @@ packages: resolution: {integrity: sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==} dev: false + /netmask@2.0.2: + resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} + engines: {node: '>= 0.4.0'} + dev: true + /next-images@1.8.5(webpack@5.90.1): resolution: {integrity: sha512-YLBERp92v+Nu2EVxI9+wa32KRuxyxTC8ItbiHUWVPlatUoTl0yRqsNtP39c2vYv27VRvY4LlYcUGjNRBSMUIZA==} peerDependencies: @@ -21735,6 +22400,7 @@ packages: optional: true dependencies: whatwg-url: 5.0.0 + dev: true /node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} @@ -22238,6 +22904,13 @@ packages: dependencies: yocto-queue: 1.0.0 + /p-limit@5.0.0: + resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} + engines: {node: '>=18'} + dependencies: + yocto-queue: 1.0.0 + dev: true + /p-locate@3.0.0: resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} engines: {node: '>=6'} @@ -22283,6 +22956,30 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + /pac-proxy-agent@7.0.1: + resolution: {integrity: sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A==} + engines: {node: '>= 14'} + dependencies: + '@tootallnate/quickjs-emscripten': 0.23.0 + agent-base: 7.1.0 + debug: 4.3.4(supports-color@8.1.1) + get-uri: 6.0.3 + http-proxy-agent: 7.0.1 + https-proxy-agent: 7.0.3 + pac-resolver: 7.0.1 + socks-proxy-agent: 8.0.2 + transitivePeerDependencies: + - supports-color + dev: true + + /pac-resolver@7.0.1: + resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} + engines: {node: '>= 14'} + dependencies: + degenerator: 5.0.1 + netmask: 2.0.2 + dev: true + /package-json@8.1.1: resolution: {integrity: sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==} engines: {node: '>=14.16'} @@ -22477,6 +23174,18 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + /pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + dev: true + + /pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + dev: true + + /pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + dev: true + /performant-array-to-tree@1.11.0: resolution: {integrity: sha512-YwCqIDvnaebXaKuKQhI5yJD6ryDc3FxvoeX/5ougXTKDUWb7s5S2BuBgIyftCa4sBe1+ZU5Kmi4RJy+pjjjrpw==} dev: false @@ -22556,6 +23265,14 @@ packages: dependencies: find-up: 6.3.0 + /pkg-types@1.0.3: + resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} + dependencies: + jsonc-parser: 3.2.1 + mlly: 1.5.0 + pathe: 1.1.2 + dev: true + /pkg-up@3.1.0: resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} engines: {node: '>=8'} @@ -23141,7 +23858,6 @@ packages: /progress@2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} - dev: false /promise-inflight@1.0.1: resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} @@ -23344,6 +24060,42 @@ packages: forwarded: 0.2.0 ipaddr.js: 1.9.1 + /proxy-agent@6.3.0: + resolution: {integrity: sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.0 + debug: 4.3.4(supports-color@8.1.1) + http-proxy-agent: 7.0.1 + https-proxy-agent: 7.0.3 + lru-cache: 7.18.3 + pac-proxy-agent: 7.0.1 + proxy-from-env: 1.1.0 + socks-proxy-agent: 8.0.2 + transitivePeerDependencies: + - supports-color + dev: true + + /proxy-agent@6.3.1: + resolution: {integrity: sha512-Rb5RVBy1iyqOtNl15Cw/llpeLH8bsb37gM1FUfKQ+Wck6xHlbAhWGUFiTRHtkjqGTA5pSHz6+0hrPW/oECihPQ==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.0 + debug: 4.3.4(supports-color@8.1.1) + http-proxy-agent: 7.0.1 + https-proxy-agent: 7.0.3 + lru-cache: 7.18.3 + pac-proxy-agent: 7.0.1 + proxy-from-env: 1.1.0 + socks-proxy-agent: 8.0.2 + transitivePeerDependencies: + - supports-color + dev: true + + /proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: true + /prr@1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} requiresBuild: true @@ -23359,7 +24111,6 @@ packages: dependencies: end-of-stream: 1.4.4 once: 1.4.0 - dev: false /punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} @@ -23379,6 +24130,29 @@ packages: dependencies: escape-goat: 4.0.0 + /puppeteer-core@20.9.0(typescript@5.3.3): + resolution: {integrity: sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==} + engines: {node: '>=16.3.0'} + peerDependencies: + typescript: '>= 4.7.4' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@puppeteer/browsers': 1.4.6(typescript@5.3.3) + chromium-bidi: 0.4.16(devtools-protocol@0.0.1147663) + cross-fetch: 4.0.0 + debug: 4.3.4(supports-color@8.1.1) + devtools-protocol: 0.0.1147663 + typescript: 5.3.3 + ws: 8.13.0 + transitivePeerDependencies: + - bufferutil + - encoding + - supports-color + - utf-8-validate + dev: true + /qrcode-terminal@0.11.0: resolution: {integrity: sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==} hasBin: true @@ -23402,6 +24176,10 @@ packages: side-channel: 1.0.5 dev: true + /query-selector-shadow-dom@1.0.1: + resolution: {integrity: sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==} + dev: true + /query-string@6.14.1: resolution: {integrity: sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==} engines: {node: '>=6'} @@ -23425,6 +24203,10 @@ packages: /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + /queue-tick@1.0.1: + resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} + dev: true + /queue@6.0.2: resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} dependencies: @@ -24414,6 +25196,12 @@ packages: string_decoder: 1.3.0 util-deprecate: 1.0.2 + /readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + dependencies: + minimatch: 5.1.2 + dev: true + /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -24796,6 +25584,12 @@ packages: dependencies: lowercase-keys: 3.0.0 + /resq@1.11.0: + resolution: {integrity: sha512-G10EBz+zAAy3zUd/CDoBbXRL6ia9kOo3xRHrMDsHljI0GDkhYlyjwoCx5+3eCC4swi1uCoZQhskuJkj7Gp57Bw==} + dependencies: + fast-deep-equal: 2.0.1 + dev: true + /restore-cursor@2.0.0: resolution: {integrity: sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==} engines: {node: '>=4'} @@ -24836,6 +25630,10 @@ packages: resolution: {integrity: sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==} dev: true + /rgb2hex@0.2.5: + resolution: {integrity: sha512-22MOP1Rh7sAo1BZpDG6R5RFYzR2lYEgwq7HEmyW2qcsOqR2lQKmn+O//xV3YG/0rrhMC6KVX2hU+ZXuaw9a5bw==} + dev: true + /rimraf@2.4.5: resolution: {integrity: sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==} hasBin: true @@ -24855,7 +25653,6 @@ packages: hasBin: true dependencies: glob: 7.2.3 - dev: false /rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} @@ -24947,6 +25744,10 @@ packages: dependencies: tslib: 2.6.2 + /safaridriver@0.1.2: + resolution: {integrity: sha512-4R309+gWflJktzPXBQCobbWEHlzC4aK3a+Ov3tz2Ib2aBxiwd11phkdIBH1l0EO22x24CJMUQkpKFumRriCSRg==} + dev: true + /safe-array-concat@1.1.0: resolution: {integrity: sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==} engines: {node: '>=0.4'} @@ -25176,6 +25977,13 @@ packages: transitivePeerDependencies: - supports-color + /serialize-error@11.0.3: + resolution: {integrity: sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==} + engines: {node: '>=14.16'} + dependencies: + type-fest: 2.19.0 + dev: true + /serialize-error@2.1.0: resolution: {integrity: sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==} engines: {node: '>=0.10.0'} @@ -25276,7 +26084,6 @@ packages: /setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} - dev: false /setprototypeof@1.1.0: resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} @@ -25351,6 +26158,10 @@ packages: get-intrinsic: 1.2.4 object-inspect: 1.13.1 + /siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + dev: true + /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -25621,6 +26432,11 @@ packages: engines: {node: '>=6'} dev: false + /split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + dev: true + /split@1.0.1: resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} dependencies: @@ -25661,6 +26477,10 @@ packages: dependencies: escape-string-regexp: 2.0.0 + /stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + dev: true + /stackframe@1.3.4: resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} @@ -25699,6 +26519,15 @@ packages: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} + /streamx@2.16.1: + resolution: {integrity: sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==} + dependencies: + fast-fifo: 1.3.2 + queue-tick: 1.0.1 + optionalDependencies: + bare-events: 2.2.0 + dev: true + /strict-uri-encode@2.0.0: resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} engines: {node: '>=4'} @@ -25854,6 +26683,12 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + /strip-literal@2.0.0: + resolution: {integrity: sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==} + dependencies: + js-tokens: 8.0.3 + dev: true + /strnum@1.0.5: resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} @@ -26140,6 +26975,32 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} + /tar-fs@3.0.4: + resolution: {integrity: sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==} + dependencies: + mkdirp-classic: 0.5.3 + pump: 3.0.0 + tar-stream: 3.1.7 + dev: true + + /tar-fs@3.0.5: + resolution: {integrity: sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg==} + dependencies: + pump: 3.0.0 + tar-stream: 3.1.7 + optionalDependencies: + bare-fs: 2.1.5 + bare-path: 2.1.0 + dev: true + + /tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + dependencies: + b4a: 1.6.6 + fast-fifo: 1.3.2 + streamx: 2.16.1 + dev: true + /tar@6.2.0: resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==} engines: {node: '>=10'} @@ -26383,6 +27244,20 @@ packages: /tiny-warning@1.0.3: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + /tinybench@2.6.0: + resolution: {integrity: sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==} + dev: true + + /tinypool@0.8.2: + resolution: {integrity: sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==} + engines: {node: '>=14.0.0'} + dev: true + + /tinyspy@2.2.1: + resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} + engines: {node: '>=14.0.0'} + dev: true + /tippy.js@6.3.7: resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==} dependencies: @@ -26425,6 +27300,10 @@ packages: punycode: 2.3.1 dev: false + /traverse@0.3.9: + resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==} + dev: true + /traverse@0.6.8: resolution: {integrity: sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==} engines: {node: '>= 0.4'} @@ -26651,6 +27530,11 @@ packages: resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} engines: {node: '>=10'} + /type-fest@2.13.0: + resolution: {integrity: sha512-lPfAm42MxE4/456+QyIaaVBAwgpJb6xZ8PRu09utnhPdWwcyj9vgy6Sq0Z5yNbJ21EdxB5dRU/Qg8bsyAMtlcw==} + engines: {node: '>=12.20'} + dev: true + /type-fest@2.19.0: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} @@ -26758,6 +27642,10 @@ packages: resolution: {integrity: sha512-DffL94LsNOccVn4hyfRe5rdKa273swqeA5DJpMOeFmEn1wCDc7nAbbB0gXlgBCL7TNzeTv6G7XVWzan7iJtfig==} dev: false + /ufo@1.4.0: + resolution: {integrity: sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==} + dev: true + /uglify-es@3.3.9: resolution: {integrity: sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==} engines: {node: '>=0.8.0'} @@ -26783,6 +27671,13 @@ packages: has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 + /unbzip2-stream@1.4.3: + resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} + dependencies: + buffer: 5.7.1 + through: 2.3.8 + dev: true + /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} @@ -26939,6 +27834,21 @@ packages: engines: {node: '>=8'} dev: true + /unzipper@0.10.14: + resolution: {integrity: sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==} + dependencies: + big-integer: 1.6.52 + binary: 0.3.0 + bluebird: 3.4.7 + buffer-indexof-polyfill: 1.0.2 + duplexer2: 0.1.4 + fstream: 1.0.12 + graceful-fs: 4.2.11 + listenercount: 1.0.1 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + dev: true + /upath@1.2.0: resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==} engines: {node: '>=4'} @@ -27055,6 +27965,11 @@ packages: dependencies: react: 18.2.0 + /userhome@1.0.0: + resolution: {integrity: sha512-ayFKY3H+Pwfy4W98yPdtH1VqH4psDeyW8lYYFzfecR9d6hqLpqhecktvYR3SEEXt7vG0S1JEpciI3g94pMErig==} + engines: {node: '>= 0.8.0'} + dev: true + /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -27149,6 +28064,27 @@ packages: unist-util-stringify-position: 4.0.0 vfile-message: 4.0.2 + /vite-node@1.3.1: + resolution: {integrity: sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4(supports-color@8.1.1) + pathe: 1.1.2 + picocolors: 1.0.0 + vite: 5.1.1 + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /vite-plugin-top-level-await@1.4.1(vite@5.1.1): resolution: {integrity: sha512-hogbZ6yT7+AqBaV6lK9JRNvJDn4/IJvHLu6ET06arNfo0t2IsyCaon7el9Xa8OumH+ESuq//SDf8xscZFE0rWw==} peerDependencies: @@ -27200,7 +28136,7 @@ packages: optional: true dependencies: '@types/node': 20.11.17 - esbuild: 0.19.11 + esbuild: 0.19.12 less: 4.2.0 postcss: 8.4.35 rollup: 4.10.0 @@ -27245,6 +28181,62 @@ packages: fsevents: 2.3.3 dev: true + /vitest@1.3.1(@vitest/browser@1.3.1): + resolution: {integrity: sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==} + 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.3.1 + '@vitest/ui': 1.3.1 + 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 + dependencies: + '@vitest/browser': 1.3.1(vitest@1.3.1)(webdriverio@8.32.3) + '@vitest/expect': 1.3.1 + '@vitest/runner': 1.3.1 + '@vitest/snapshot': 1.3.1 + '@vitest/spy': 1.3.1 + '@vitest/utils': 1.3.1 + acorn-walk: 8.3.2 + chai: 4.4.1 + debug: 4.3.4(supports-color@8.1.1) + execa: 8.0.1 + local-pkg: 0.5.0 + magic-string: 0.30.5 + pathe: 1.1.2 + picocolors: 1.0.0 + std-env: 3.7.0 + strip-literal: 2.0.0 + tinybench: 2.6.0 + tinypool: 0.8.2 + vite: 5.1.1 + vite-node: 1.3.1 + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /vlq@1.0.1: resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} @@ -27263,6 +28255,18 @@ packages: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} dev: false + /wait-port@1.1.0: + resolution: {integrity: sha512-3e04qkoN3LxTMLakdqeWth8nih8usyg+sf1Bgdf9wwUkp05iuK1eSY/QpLvscT/+F/gA89+LpUmmgBtesbqI2Q==} + engines: {node: '>=10'} + hasBin: true + dependencies: + chalk: 4.1.2 + commander: 9.5.0 + debug: 4.3.4(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + dev: true + /walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} dependencies: @@ -27304,6 +28308,68 @@ packages: resolution: {integrity: sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ==} engines: {node: '>= 8'} + /webdriver@8.32.3: + resolution: {integrity: sha512-1/kpZvuftt59oikHs+6FvWXNfOM5tgMMMAk3LnMe7D938dVOoNGAe46fq0oL/xsxxPicbMRTRgIy1OifLiglaA==} + engines: {node: ^16.13 || >=18} + dependencies: + '@types/node': 20.11.17 + '@types/ws': 8.5.10 + '@wdio/config': 8.32.3 + '@wdio/logger': 8.28.0 + '@wdio/protocols': 8.32.0 + '@wdio/types': 8.32.2 + '@wdio/utils': 8.32.3 + deepmerge-ts: 5.1.0 + got: 12.6.1 + ky: 0.33.3 + ws: 8.16.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + + /webdriverio@8.32.3(typescript@5.3.3): + resolution: {integrity: sha512-SupbQKMtUZHSH7lmF5xaJPgxsn8sIBNAjs1CyPI33u30eY9VcVQ4CJQ818ZS3FLxR0q2XdWk9lsQNyhZwlN3RA==} + engines: {node: ^16.13 || >=18} + peerDependencies: + devtools: ^8.14.0 + peerDependenciesMeta: + devtools: + optional: true + dependencies: + '@types/node': 20.11.17 + '@wdio/config': 8.32.3 + '@wdio/logger': 8.28.0 + '@wdio/protocols': 8.32.0 + '@wdio/repl': 8.24.12 + '@wdio/types': 8.32.2 + '@wdio/utils': 8.32.3 + archiver: 6.0.1 + aria-query: 5.3.0 + css-shorthand-properties: 1.1.1 + css-value: 0.0.1 + devtools-protocol: 0.0.1262051 + grapheme-splitter: 1.0.4 + import-meta-resolve: 4.0.0 + is-plain-obj: 4.1.0 + lodash.clonedeep: 4.5.0 + lodash.zip: 4.2.0 + minimatch: 9.0.3 + puppeteer-core: 20.9.0(typescript@5.3.3) + query-selector-shadow-dom: 1.0.1 + resq: 1.11.0 + rgb2hex: 0.2.5 + serialize-error: 11.0.3 + webdriver: 8.32.3 + transitivePeerDependencies: + - bufferutil + - encoding + - supports-color + - typescript + - utf-8-validate + dev: true + /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -27803,6 +28869,15 @@ packages: isexe: 3.1.1 dev: true + /why-is-node-running@2.2.2: + resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + engines: {node: '>=8'} + hasBin: true + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + dev: true + /widest-line@3.1.0: resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} engines: {node: '>=8'} @@ -28078,6 +29153,19 @@ packages: utf-8-validate: optional: true + /ws@8.13.0: + resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + /ws@8.16.0: resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==} engines: {node: '>=10.0.0'} @@ -28227,6 +29315,19 @@ packages: yargs-parser: 20.2.9 dev: false + /yargs@17.7.1: + resolution: {integrity: sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==} + engines: {node: '>=12'} + dependencies: + cliui: 8.0.1 + escalade: 3.1.2 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + dev: true + /yargs@17.7.2: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} @@ -28239,6 +29340,13 @@ packages: y18n: 5.0.8 yargs-parser: 21.1.1 + /yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + dev: true + /yjs@13.6.12: resolution: {integrity: sha512-KOT8ILoyVH2f/PxPadeu5kVVS055D1r3x1iFfJVJzFdnN98pVGM8H07NcKsO+fG3F7/0tf30Vnokf5YIqhU/iw==} engines: {node: '>=16.0.0', npm: '>=8.0.0'} @@ -28259,6 +29367,15 @@ packages: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} + /zip-stream@5.0.1: + resolution: {integrity: sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==} + engines: {node: '>= 12.0.0'} + dependencies: + archiver-utils: 4.0.1 + compress-commons: 5.0.1 + readable-stream: 3.6.2 + dev: true + /zone.js@0.14.3: resolution: {integrity: sha512-jYoNqF046Q+JfcZSItRSt+oXFcpXL88yq7XAZjb/NKTS7w2hHpKjRJ3VlFD1k75wMaRRXNUt5vrZVlygiMyHbA==} dependencies: