Compare commits

..

2 Commits

Author SHA1 Message Date
Florian THIERRY
5c6d54c459 Add documentation. 2025-10-09 17:54:36 +02:00
Florian THIERRY
13d811faae Add load tests. 2025-10-09 14:32:00 +02:00
30 changed files with 124 additions and 594 deletions

77
README.md Normal file
View File

@@ -0,0 +1,77 @@
# Virtual threads experiences
## General information
This project aims to show what is the developer experience when the virtual threads are used, in comparison of the framework Reactor.
Therefore, there is one spring boot application which uses "standard java code" with virtual threads, and another spring boot application which uses Reactor.
## Spring boot applications
### Common concepts
Both applications are developed to serve same endpoints with same models.
### Architecture
Both applications are architectured with a "light" version of the hexagonal architecture.
Java packages represent the hexagonal architecture modules:
- domain (Business code)
- exposition (Rest API)
- infrastructure (JPA layer)
#### Models
[<img src="./doc/images/models.png" witdh="1600px" />](./doc/images/models.png)
#### Exposed endpoints
- Catalogs
- GET /api/catalogs/{catalogId}
- Items
- GET /api/items
- POST /api/items
- Marketplace
- GET /api/marketplace
### Virtual threads application
The application runs on port `51001`.
```bash
./gradlew :virtual-threads-app:bootRun
```
### Reactor application
The application runs on port `52001`.
To start the application, run this command:
```bash
./gradlew :reactor-app:bootRun
```
## Database
The database is a PostgreSQL database which runs inside a container.
To start the database, just run this command inside the project root folder:
```bash
docker compose up --detach --file ./docker-compose.yml
```
Then, you will have to initialise the tables by executing scripts located [there](./src/main/sql/init_database.sql) into the container.
You can connect to it with this "unix" command:
```bash
docker exec --interactive --tty virtual-threads-test-db /bin/bash
psql --host localhost --port 5432 --username virtual_threads_test_user virtual_threads_test_db
```
### Database files
There is a volumes for the database container, which points to this location: [local/postgresql](./local/postgresql).
## Bruno http client collection
There is a collection of queries, usable with [bruno](https://www.usebruno.com/). It is located inside the [rest-client-collection](./rest-client-collection).
## Load testing
Load tests use K6 framework.
### Targeting the application to test
By default, tests are configured to load the `virtual-threads-app`.
If you want to run them on the `reactor-app`, you have to edit the variable `TARGET_URL` inside the file [config.ts](./k6-load-tests/src/tests/config.ts).

View File

@@ -11,16 +11,9 @@ allprojects {
tasks.withType<JavaCompile> {
options.compilerArgs.add("--release")
options.compilerArgs.add("21")
// options.compilerArgs.add("--enable-preview")
}
repositories {
mavenCentral()
}
// java {
// toolchain {
// languageVersion = JavaLanguageVersion.of(21)
// }
// }
}

BIN
doc/images/models.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,37 @@
<diagram program="umletino" version="15.1"><zoom_level>10</zoom_level><element><id>UMLClass</id><coordinates><x>730</x><y>500</y><w>90</w><h>60</h></coordinates><panel_attributes>Catalog
--
id: UUID
name: String</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>730</x><y>580</y><w>180</w><h>80</h></coordinates><panel_attributes>Item
--
id: UUID
name: String
sharedDate: ZonedDateTime</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>590</x><y>410</y><w>90</w><h>50</h></coordinates><panel_attributes>Marketplace
--
</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>620</x><y>450</y><w>130</w><h>100</h></coordinates><panel_attributes>lt=-
m1=0..*
m2=0..*</panel_attributes><additional_attributes>10;10;10;70;110;70</additional_attributes></element><element><id>Relation</id><coordinates><x>590</x><y>450</y><w>160</w><h>180</h></coordinates><panel_attributes>lt=-
m1=0..*
m2=0..*</panel_attributes><additional_attributes>10;10;10;150;140;150</additional_attributes></element><element><id>UMLNote</id><coordinates><x>550</x><y>340</y><w>400</w><h>30</h></coordinates><panel_attributes>Domain
bg=blue</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLNote</id><coordinates><x>20</x><y>340</y><w>400</w><h>30</h></coordinates><panel_attributes>Exposition
bg=red</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLNote</id><coordinates><x>1080</x><y>340</y><w>400</w><h>30</h></coordinates><panel_attributes>Infrastructure
bg=green</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>90</x><y>450</y><w>130</w><h>100</h></coordinates><panel_attributes>lt=-
m1=0..*
m2=0..*</panel_attributes><additional_attributes>10;10;10;70;110;70</additional_attributes></element><element><id>Relation</id><coordinates><x>60</x><y>450</y><w>160</w><h>180</h></coordinates><panel_attributes>lt=-
m1=0..*
m2=0..*</panel_attributes><additional_attributes>10;10;10;150;140;150</additional_attributes></element><element><id>UMLClass</id><coordinates><x>200</x><y>500</y><w>90</w><h>60</h></coordinates><panel_attributes>CatalogDto
--
id: UUID
name: String</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>200</x><y>580</y><w>180</w><h>80</h></coordinates><panel_attributes>ItemDto
--
id: UUID
name: String
isShared: Boolean</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>50</x><y>410</y><w>110</w><h>50</h></coordinates><panel_attributes>MarketplaceDto
--
</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>1120</x><y>470</y><w>130</w><h>60</h></coordinates><panel_attributes>CatalogJpaEntity
--
id: UUID
name: String</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>1280</x><y>460</y><w>180</w><h>80</h></coordinates><panel_attributes>ItemJpaEntity
--
id: UUID
name: String
sharedDate: ZonedDateTime</panel_attributes><additional_attributes></additional_attributes></element></diagram>

View File

@@ -1,3 +0,0 @@
target:
url: http://localhost
port: 51001

View File

@@ -1,113 +0,0 @@
import { check } from "k6";
import http, { StructuredRequestBody } from "k6/http";
import { Trend } from "k6/metrics";
import { logWaitingTime } from "../utils/logger";
import { Response } from '../data/common';
export type User = {
id: number;
email: string;
first_name: string;
last_name: string;
avatar: string;
};
// Metrics that we want to track
const metrics = {
getUserResponseTime: new Trend("get_user_response_time", true),
updateUserResponseTime: new Trend("update_user_response_time", true),
deleteUserResponseTime: new Trend("delete_user_response_time", true),
};
// Endpoint that we test against
const reqResUrl = "https://reqres.in/api";
/**
* getUser makes a GET request to the /users/:id endpoint and asserts
* that the response is 200 and that the user id and email match the
* user object that was passed in.
* @param user
* @returns Response<User>
*/
export const getUser = (user: User): Response<User> => {
const url = reqResUrl;
const params = {
headers: {},
};
const res = http.get(`${url}/users/${user.id}`, params);
const jsonRes = res.json() as { data: User };
logWaitingTime({
metric: metrics.getUserResponseTime,
response: res,
messageType: `Get User`,
});
check(res, {
"Get User By Id: is 200": (r) => r.status === 200,
"Get User By Id: has correct id": (_) => jsonRes.data.id === user.id,
"Get User By Id: has correct email": (_) =>
jsonRes.data.email === user.email,
});
return {
data: jsonRes.data,
statusCode: res.status,
};
};
type UpdateUserResponse = {
updatedAt: string;
job: string;
}
export const updateUser = (user: User): Response<UpdateUserResponse> => {
const url = reqResUrl;
const params = {
headers: {},
};
const jobTitle = "QA Engineer";
const payload: StructuredRequestBody = {
job: jobTitle,
};
const res = http.patch(`${url}/users/${user.id}`, payload, params);
const jsonRes = res.json() as UpdateUserResponse
logWaitingTime({
metric: metrics.updateUserResponseTime,
response: res,
messageType: `Update User`,
});
check(res, {
"Update User By Id: is 200": (r) => r.status === 200,
"Update User By Id: has correct updatedAt": (_) => jsonRes.updatedAt !== "",
"Update User By Id: has correct job title": (_) => jsonRes.job === jobTitle,
});
return {
data: jsonRes,
statusCode: res.status,
};
};
export const deleteUser = (userId: number): Response<{}> => {
const url = reqResUrl;
const res = http.del(`${url}/users/${userId}`);
logWaitingTime({
metric: metrics.deleteUserResponseTime,
response: res,
messageType: `Delete User`,
});
check(res, {
"Delete User By Id: is 204": (r) => r.status === 204,
});
return {
data: {},
statusCode: res.status,
};
};

View File

@@ -1,86 +0,0 @@
export const users = [
{
id: 1,
email: "george.bluth@reqres.in",
first_name: "George",
last_name: "Bluth",
avatar: "https://reqres.in/img/faces/1-image.jpg",
},
{
id: 2,
email: "janet.weaver@reqres.in",
first_name: "Janet",
last_name: "Weaver",
avatar: "https://reqres.in/img/faces/2-image.jpg",
},
{
id: 3,
email: "emma.wong@reqres.in",
first_name: "Emma",
last_name: "Wong",
avatar: "https://reqres.in/img/faces/3-image.jpg",
},
{
id: 4,
email: "eve.holt@reqres.in",
first_name: "Eve",
last_name: "Holt",
avatar: "https://reqres.in/img/faces/4-image.jpg",
},
{
id: 5,
email: "charles.morris@reqres.in",
first_name: "Charles",
last_name: "Morris",
avatar: "https://reqres.in/img/faces/5-image.jpg",
},
{
id: 6,
email: "tracey.ramos@reqres.in",
first_name: "Tracey",
last_name: "Ramos",
avatar: "https://reqres.in/img/faces/6-image.jpg",
},
{
id: 7,
email: "michael.lawson@reqres.in",
first_name: "Michael",
last_name: "Lawson",
avatar: "https://reqres.in/img/faces/7-image.jpg",
},
{
id: 8,
email: "lindsay.ferguson@reqres.in",
first_name: "Lindsay",
last_name: "Ferguson",
avatar: "https://reqres.in/img/faces/8-image.jpg",
},
{
id: 9,
email: "tobias.funke@reqres.in",
first_name: "Tobias",
last_name: "Funke",
avatar: "https://reqres.in/img/faces/9-image.jpg",
},
{
id: 10,
email: "byron.fields@reqres.in",
first_name: "Byron",
last_name: "Fields",
avatar: "https://reqres.in/img/faces/10-image.jpg",
},
{
id: 11,
email: "george.edwards@reqres.in",
first_name: "George",
last_name: "Edwards",
avatar: "https://reqres.in/img/faces/11-image.jpg",
},
{
id: 12,
email: "rachel.howell@reqres.in",
first_name: "Rachel",
last_name: "Howell",
avatar: "https://reqres.in/img/faces/12-image.jpg",
},
];

View File

@@ -1,3 +0,0 @@
export const VUs = 1

View File

@@ -1,34 +0,0 @@
import { SharedArray } from "k6/data";
import { vu } from "k6/execution";
import { Options } from "k6/options";
import { deleteUser, getUser, updateUser } from "../apis/reqres";
import { users } from "../data/users";
import { logger } from "../utils/logger";
const data = new SharedArray("users", function () {
return users;
});
export const options: Options = {
stages: [
// Ramp up to 12 users over 30 seconds
{ duration: "1m", target: 12 },
// Maintain steady state of 12 users over the next two minutes
{ duration: "2m", target: 12 },
// Ramp down to 0 users over the next 30 seconds
{ duration: "30s", target: 0 },
],
};
export default function test() {
// Get a random user from data that isn't currently being tested
const user = data[vu.idInTest - 1];
logger.info(
`Running iteration ${vu.iterationInInstance} for user id ${user.id} with name ${user.first_name} ${user.last_name}`
);
getUser(user);
updateUser(user);
deleteUser(user.id);
}

View File

@@ -1,35 +0,0 @@
import { SharedArray } from "k6/data";
// @ts-ignore
import { vu } from "k6/execution";
import { Options } from "k6/options";
import { deleteUser, getUser, updateUser } from "../apis/reqres";
import { users } from "../data/users";
import { logger } from "../utils/logger";
const data = new SharedArray("users", function () {
return users;
});
export const options: Options = {
scenarios: {
login: {
executor: 'per-vu-iterations',
vus: 5,
iterations: 10,
maxDuration: '1h30m',
},
},
};
export default function test () {
// Get a random user from data that isn't currently being tested
const user = data[vu.idInTest - 1];
logger.info(
`Running iteration ${vu.iterationInInstance} for user id ${user.id} with name ${user.first_name} ${user.last_name}`
);
getUser(user);
updateUser(user);
deleteUser(user.id);
}

View File

@@ -1,39 +0,0 @@
import * as fs from 'fs';
import * as yaml from 'js-yaml';
import * as path from 'path';
const FILE_ENCODING = 'utf-8';
const YAML_FILE_PATH = '../configuration.yml';
export interface Configuration {
target: TargetConfiguration;
}
export interface TargetConfiguration {
url: string;
port: string;
}
export function loadConfiguration(): Configuration {
try {
const fileContent = fs.readFileSync(path.resolve(__dirname, YAML_FILE_PATH), FILE_ENCODING);
const data = yaml.load(fileContent);
return {
target: {
// @ts-ignore
url: data['target']['url'],
// @ts-ignore
port: data['target']['port']
}
};
} catch (e) {
let error = {
message: 'Error while loading the configuration.',
cause: e
};
console.error(error);
throw error;
}
}

View File

@@ -6,10 +6,7 @@
"type": "module",
"scripts": {
"build": "vite build",
"test:demo": "yarn build && k6 run dist/tests/load-tests.cjs",
"test:demo-stages": "yarn build && k6 run dist/tests/reqres-stages.cjs",
"test-with-monitoring:demo": "yarn build && docker run --platform linux/amd64 -it -p 5665:5665 -v $(pwd)/dist/:/src ghcr.io/grafana/xk6-dashboard:0.6.1 run --out 'dashboard=period=2s' /src/tests/reqres.cjs",
"test-with-monitoring:demo-stages": "yarn build && docker run --platform linux/amd64 -it -p 5665:5665 -v $(pwd)/dist/:/src ghcr.io/grafana/xk6-dashboard:0.6.1 run --out 'dashboard=period=2s' /src/tests/reqres-stages.cjs"
"start": "yarn build && k6 run dist/tests/load-tests.cjs"
},
"devDependencies": {
"@babel/core": "7.23.3",

View File

@@ -4,8 +4,7 @@ import {logWaitingTime} from "../utils/logger";
import {Trend} from "k6/metrics";
import {check} from "k6";
import {Response} from "../data/common";
const marketplaceUrl = 'http://localhost:51001'
import {TARGET_URL} from "../tests/config";
// Metrics that we want to track
const metrics = {
@@ -13,11 +12,8 @@ const metrics = {
};
export const getMarketplace = (): Response<Marketplace> => {
const url = `${marketplaceUrl}/api/marketplace`;
const response = http.get(url);
// console.log('response:', response);
const urlToTest = `${TARGET_URL}/api/marketplace`;
const response = http.get(urlToTest);
logWaitingTime({
metric: metrics.getMarketplace,

View File

@@ -0,0 +1,6 @@
export const VUs = 1;
// @ts-ignore - if it is unused
const VIRTUAL_THREADS_APP_PORT = 51001;
// @ts-ignore - if it is unused
const REACTOR_APP_PORT = 52001;
export const TARGET_URL = `http://localhost:${VIRTUAL_THREADS_APP_PORT}`;

View File

@@ -1,18 +0,0 @@
{
"scripts": {
"build": "tsc",
"start": "yarn build && node dist/api-test.js"
},
"dependencies": {
"js-yaml": "^4.1.0",
"k6": "^0.0.0"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
"devDependencies": {
"@types/js-yaml": "^4.0.9",
"@types/k6": "^1.2.0",
"@types/node": "^24.5.2",
"tsx": "^4.20.5",
"typescript": "^5.9.2"
}
}

View File

@@ -1,9 +0,0 @@
import http from 'k6/http';
import * as fs from 'fs';
import * as yaml from 'js-yaml';
const
export default function () {
}

View File

@@ -1,236 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@esbuild/aix-ppc64@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz#ee6b7163a13528e099ecf562b972f2bcebe0aa97"
integrity sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==
"@esbuild/android-arm64@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz#115fc76631e82dd06811bfaf2db0d4979c16e2cb"
integrity sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==
"@esbuild/android-arm@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.10.tgz#8d5811912da77f615398611e5bbc1333fe321aa9"
integrity sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==
"@esbuild/android-x64@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.10.tgz#e3e96516b2d50d74105bb92594c473e30ddc16b1"
integrity sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==
"@esbuild/darwin-arm64@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz#6af6bb1d05887dac515de1b162b59dc71212ed76"
integrity sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==
"@esbuild/darwin-x64@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz#99ae82347fbd336fc2d28ffd4f05694e6e5b723d"
integrity sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==
"@esbuild/freebsd-arm64@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz#0c6d5558a6322b0bdb17f7025c19bd7d2359437d"
integrity sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==
"@esbuild/freebsd-x64@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz#8c35873fab8c0857a75300a3dcce4324ca0b9844"
integrity sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==
"@esbuild/linux-arm64@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz#3edc2f87b889a15b4cedaf65f498c2bed7b16b90"
integrity sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==
"@esbuild/linux-arm@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz#86501cfdfb3d110176d80c41b27ed4611471cde7"
integrity sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==
"@esbuild/linux-ia32@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz#e6589877876142537c6864680cd5d26a622b9d97"
integrity sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==
"@esbuild/linux-loong64@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz#11119e18781f136d8083ea10eb6be73db7532de8"
integrity sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==
"@esbuild/linux-mips64el@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz#3052f5436b0c0c67a25658d5fc87f045e7def9e6"
integrity sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==
"@esbuild/linux-ppc64@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz#2f098920ee5be2ce799f35e367b28709925a8744"
integrity sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==
"@esbuild/linux-riscv64@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz#fa51d7fd0a22a62b51b4b94b405a3198cf7405dd"
integrity sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==
"@esbuild/linux-s390x@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz#a27642e36fc282748fdb38954bd3ef4f85791e8a"
integrity sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==
"@esbuild/linux-x64@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz#9d9b09c0033d17529570ced6d813f98315dfe4e9"
integrity sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==
"@esbuild/netbsd-arm64@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz#25c09a659c97e8af19e3f2afd1c9190435802151"
integrity sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==
"@esbuild/netbsd-x64@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz#7fa5f6ffc19be3a0f6f5fd32c90df3dc2506937a"
integrity sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==
"@esbuild/openbsd-arm64@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz#8faa6aa1afca0c6d024398321d6cb1c18e72a1c3"
integrity sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==
"@esbuild/openbsd-x64@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz#a42979b016f29559a8453d32440d3c8cd420af5e"
integrity sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==
"@esbuild/openharmony-arm64@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz#fd87bfeadd7eeb3aa384bbba907459ffa3197cb1"
integrity sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==
"@esbuild/sunos-x64@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz#3a18f590e36cb78ae7397976b760b2b8c74407f4"
integrity sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==
"@esbuild/win32-arm64@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz#e71741a251e3fd971408827a529d2325551f530c"
integrity sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==
"@esbuild/win32-ia32@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz#c6f010b5d3b943d8901a0c87ea55f93b8b54bf94"
integrity sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==
"@esbuild/win32-x64@0.25.10":
version "0.25.10"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz#e4b3e255a1b4aea84f6e1d2ae0b73f826c3785bd"
integrity sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==
"@types/js-yaml@^4.0.9":
version "4.0.9"
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.9.tgz#cd82382c4f902fed9691a2ed79ec68c5898af4c2"
integrity sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==
"@types/k6@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@types/k6/-/k6-1.2.0.tgz#c27231f6311b48cb4c553bcf3d8d4bc036d00ab8"
integrity sha512-7F/vjekOUCzFi5AY+yYBOL38iEtN9EXufdKOAMFkJsdLryJ3/bB/W9jppED1FO5o7zeaPf8M46ardYPD8xgy/w==
"@types/node@^24.5.2":
version "24.5.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-24.5.2.tgz#52ceb83f50fe0fcfdfbd2a9fab6db2e9e7ef6446"
integrity sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==
dependencies:
undici-types "~7.12.0"
argparse@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
esbuild@~0.25.0:
version "0.25.10"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.10.tgz#37f5aa5cd14500f141be121c01b096ca83ac34a9"
integrity sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==
optionalDependencies:
"@esbuild/aix-ppc64" "0.25.10"
"@esbuild/android-arm" "0.25.10"
"@esbuild/android-arm64" "0.25.10"
"@esbuild/android-x64" "0.25.10"
"@esbuild/darwin-arm64" "0.25.10"
"@esbuild/darwin-x64" "0.25.10"
"@esbuild/freebsd-arm64" "0.25.10"
"@esbuild/freebsd-x64" "0.25.10"
"@esbuild/linux-arm" "0.25.10"
"@esbuild/linux-arm64" "0.25.10"
"@esbuild/linux-ia32" "0.25.10"
"@esbuild/linux-loong64" "0.25.10"
"@esbuild/linux-mips64el" "0.25.10"
"@esbuild/linux-ppc64" "0.25.10"
"@esbuild/linux-riscv64" "0.25.10"
"@esbuild/linux-s390x" "0.25.10"
"@esbuild/linux-x64" "0.25.10"
"@esbuild/netbsd-arm64" "0.25.10"
"@esbuild/netbsd-x64" "0.25.10"
"@esbuild/openbsd-arm64" "0.25.10"
"@esbuild/openbsd-x64" "0.25.10"
"@esbuild/openharmony-arm64" "0.25.10"
"@esbuild/sunos-x64" "0.25.10"
"@esbuild/win32-arm64" "0.25.10"
"@esbuild/win32-ia32" "0.25.10"
"@esbuild/win32-x64" "0.25.10"
fsevents@~2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
get-tsconfig@^4.7.5:
version "4.10.1"
resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.10.1.tgz#d34c1c01f47d65a606c37aa7a177bc3e56ab4b2e"
integrity sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==
dependencies:
resolve-pkg-maps "^1.0.0"
js-yaml@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
dependencies:
argparse "^2.0.1"
k6@^0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/k6/-/k6-0.0.0.tgz#8c923200be0a68c578e8f5a32be96b1d8065cc3b"
integrity sha512-GAQSWayS2+LjbH5bkRi+pMPYyP1JSp7o+4j58ANZ762N/RH/SdlAT3CHHztnn8s/xgg8kYNM24Gd2IPo9b5W+g==
resolve-pkg-maps@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f"
integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==
tsx@^4.20.5:
version "4.20.5"
resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.20.5.tgz#856c8b2f114c50a9f4ae108126967a167f240dc7"
integrity sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==
dependencies:
esbuild "~0.25.0"
get-tsconfig "^4.7.5"
optionalDependencies:
fsevents "~2.3.3"
typescript@^5.9.2:
version "5.9.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6"
integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==
undici-types@~7.12.0:
version "7.12.0"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.12.0.tgz#15c5c7475c2a3ba30659529f5cdb4674b622fafb"
integrity sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==