aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/build.yml5
-rw-r--r--basetypes/src/main/kotlin/moe/nea/ledger/TransactionType.kt1
-rw-r--r--build-src/src/main/kotlin/helpers.kt7
-rw-r--r--build-src/src/main/kotlin/ledger-globals.gradle.kts2
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/Ledger.kt2
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/PestRepellentDetection.kt47
-rw-r--r--server/aio/build.gradle.kts19
-rw-r--r--server/aio/src/main/kotlin/moe/nea/ledger/server/aio/AIO.kt20
-rw-r--r--server/core/build.gradle.kts23
-rw-r--r--server/core/src/main/kotlin/moe/nea/ledger/server/core/Application.kt53
-rw-r--r--server/core/src/main/kotlin/moe/nea/ledger/server/core/api/BaseApi.kt50
-rw-r--r--server/frontend/.gitignore2
-rw-r--r--server/frontend/build.gradle.kts17
-rw-r--r--server/frontend/index.html16
-rw-r--r--server/frontend/package.json34
-rw-r--r--server/frontend/pnpm-lock.yaml1338
-rw-r--r--server/frontend/src/App.module.css0
-rw-r--r--server/frontend/src/App.tsx13
-rw-r--r--server/frontend/src/Test.tsx31
-rw-r--r--server/frontend/src/api-schema.d.ts98
-rw-r--r--server/frontend/src/api.ts6
-rw-r--r--server/frontend/src/index.css13
-rw-r--r--server/frontend/src/index.tsx21
-rw-r--r--server/frontend/tsconfig.json19
-rw-r--r--server/frontend/vite.config.ts12
-rw-r--r--server/swagger/build.gradle.kts17
-rw-r--r--server/swagger/src/main/kotlin/moe/nea/ledger/server/core/api/OpenApiModel.kt116
-rw-r--r--server/swagger/src/main/kotlin/moe/nea/ledger/server/core/api/docs.kt323
-rw-r--r--settings.gradle.kts3
29 files changed, 2290 insertions, 18 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a9eee2c..fd9ec1e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -15,7 +15,10 @@ jobs:
with:
distribution: temurin
java-version: 17
-
+ - name: Setup PNPM
+ uses: pnpm/action-setup@v4
+ with:
+ package_json_file: 'server/frontend/package.json'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
diff --git a/basetypes/src/main/kotlin/moe/nea/ledger/TransactionType.kt b/basetypes/src/main/kotlin/moe/nea/ledger/TransactionType.kt
index 8424893..a3910a0 100644
--- a/basetypes/src/main/kotlin/moe/nea/ledger/TransactionType.kt
+++ b/basetypes/src/main/kotlin/moe/nea/ledger/TransactionType.kt
@@ -31,6 +31,7 @@ enum class TransactionType {
KUUDRA_CHEST_OPEN,
NPC_BUY,
NPC_SELL,
+ PEST_REPELLENT_USED,
VISITOR_BARGAIN,
WYRM_EVOKED,
} \ No newline at end of file
diff --git a/build-src/src/main/kotlin/helpers.kt b/build-src/src/main/kotlin/helpers.kt
index 5afef4f..48c230e 100644
--- a/build-src/src/main/kotlin/helpers.kt
+++ b/build-src/src/main/kotlin/helpers.kt
@@ -1,4 +1,5 @@
import org.gradle.api.plugins.ExtensionAware
+import org.gradle.kotlin.dsl.DependencyHandlerScope
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.findByType
@@ -8,3 +9,9 @@ inline fun <reified T : Any> ExtensionAware.configureIf(crossinline block: T.()
extensions.configure<T> { block() }
}
}
+
+val ktor_version = "3.0.3"
+
+fun DependencyHandlerScope.declareKtorVersion() {
+ "implementation"(platform("io.ktor:ktor-bom:$ktor_version"))
+}
diff --git a/build-src/src/main/kotlin/ledger-globals.gradle.kts b/build-src/src/main/kotlin/ledger-globals.gradle.kts
index 8d49379..4238322 100644
--- a/build-src/src/main/kotlin/ledger-globals.gradle.kts
+++ b/build-src/src/main/kotlin/ledger-globals.gradle.kts
@@ -1,3 +1,5 @@
+apply(plugin = "org.gradle.base")
+
repositories {
mavenCentral()
maven("https://repo.nea.moe/releases/")
diff --git a/mod/src/main/kotlin/moe/nea/ledger/Ledger.kt b/mod/src/main/kotlin/moe/nea/ledger/Ledger.kt
index e0382a9..5db4346 100644
--- a/mod/src/main/kotlin/moe/nea/ledger/Ledger.kt
+++ b/mod/src/main/kotlin/moe/nea/ledger/Ledger.kt
@@ -35,6 +35,7 @@ import moe.nea.ledger.modules.KuudraChestDetection
import moe.nea.ledger.modules.MineshaftCorpseDetection
import moe.nea.ledger.modules.MinionDetection
import moe.nea.ledger.modules.NpcDetection
+import moe.nea.ledger.modules.PestRepellentDetection
import moe.nea.ledger.modules.UpdateChecker
import moe.nea.ledger.modules.VisitorDetection
import moe.nea.ledger.utils.ErrorUtil
@@ -152,6 +153,7 @@ class Ledger {
MineshaftCorpseDetection::class.java,
MinionDetection::class.java,
NpcDetection::class.java,
+ PestRepellentDetection::class.java,
QueryCommand::class.java,
RequestUtil::class.java,
TriggerCommand::class.java,
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/PestRepellentDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/PestRepellentDetection.kt
new file mode 100644
index 0000000..f627393
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/PestRepellentDetection.kt
@@ -0,0 +1,47 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.ItemChange
+import moe.nea.ledger.LedgerEntry
+import moe.nea.ledger.LedgerLogger
+import moe.nea.ledger.TransactionType
+import moe.nea.ledger.events.ChatReceived
+import moe.nea.ledger.gen.ItemIds
+import moe.nea.ledger.useMatcher
+import moe.nea.ledger.utils.di.Inject
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+class PestRepellentDetection {
+
+ val pestRepellent = "YUM! Pests will now spawn (?<reduction>[2-4])x less while you break crops for the next 60m!".toPattern()
+
+ @Inject
+ lateinit var logger: LedgerLogger
+
+ @SubscribeEvent
+ fun onChat(event: ChatReceived) {
+ pestRepellent.useMatcher(event.message) {
+ val reductionAmount = group("reduction")
+ if (reductionAmount == "2") {
+ logger.logEntry(
+ LedgerEntry(
+ TransactionType.PEST_REPELLENT_USED,
+ event.timestamp,
+ listOf(
+ ItemChange.lose(ItemIds.PEST_REPELLENT, 1),
+ )
+ )
+ )
+ } else if (reductionAmount == "4"){
+ logger.logEntry(
+ LedgerEntry(
+ TransactionType.PEST_REPELLENT_USED,
+ event.timestamp,
+ listOf(
+ ItemChange.lose(ItemIds.PEST_REPELLENT_MAX, 1),
+ )
+ )
+ )
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/server/aio/build.gradle.kts b/server/aio/build.gradle.kts
new file mode 100644
index 0000000..22819f0
--- /dev/null
+++ b/server/aio/build.gradle.kts
@@ -0,0 +1,19 @@
+plugins {
+ kotlin("jvm")
+ application
+}
+
+dependencies {
+ declareKtorVersion()
+ implementation(project(":server:core"))
+ implementation(project(":server:frontend"))
+}
+
+java {
+ toolchain.languageVersion.set(JavaLanguageVersion.of(21))
+}
+
+application {
+ mainClass.set("moe.nea.ledger.server.core.ApplicationKt")
+}
+
diff --git a/server/aio/src/main/kotlin/moe/nea/ledger/server/aio/AIO.kt b/server/aio/src/main/kotlin/moe/nea/ledger/server/aio/AIO.kt
new file mode 100644
index 0000000..e59301f
--- /dev/null
+++ b/server/aio/src/main/kotlin/moe/nea/ledger/server/aio/AIO.kt
@@ -0,0 +1,20 @@
+package moe.nea.ledger.server.aio
+
+import io.ktor.server.application.Application
+import io.ktor.server.http.content.singlePageApplication
+import io.ktor.server.routing.Routing
+import moe.nea.ledger.server.core.AIOProvider
+
+
+class AIO : AIOProvider {
+ override fun Routing.installExtraRouting() {
+ singlePageApplication {
+ useResources = true
+ filesPath = "ledger-web-dist"
+ defaultPage = "index.html"
+ }
+ }
+
+ override fun Application.module() {
+ }
+} \ No newline at end of file
diff --git a/server/core/build.gradle.kts b/server/core/build.gradle.kts
index 87f613a..6300a4b 100644
--- a/server/core/build.gradle.kts
+++ b/server/core/build.gradle.kts
@@ -2,19 +2,21 @@ plugins {
kotlin("jvm")
kotlin("plugin.serialization")
application
+ id("com.github.gmazzo.buildconfig")
}
-val ktor_version = "3.0.3"
dependencies {
- implementation(platform("io.ktor:ktor-bom:$ktor_version"))
- implementation("io.ktor:ktor-server-netty")
- implementation("io.ktor:ktor-server-status-pages")
- implementation("io.ktor:ktor-server-content-negotiation")
- implementation("io.ktor:ktor-server-openapi")
- implementation("io.ktor:ktor-serialization-kotlinx-json")
- implementation("io.ktor:ktor-server-compression")
- implementation(project(":database:impl"))
+ declareKtorVersion()
+ api("io.ktor:ktor-server-netty")
+ api("io.ktor:ktor-server-status-pages")
+ api("io.ktor:ktor-server-content-negotiation")
+ api("io.ktor:ktor-serialization-kotlinx-json")
+ api("io.ktor:ktor-server-compression")
+ api("io.ktor:ktor-server-cors")
+ api("sh.ondr:kotlin-json-schema:0.1.1")
+ api(project(":database:impl"))
+ api(project(":server:swagger"))
runtimeOnly("ch.qos.logback:logback-classic:1.5.16")
runtimeOnly("org.xerial:sqlite-jdbc:3.45.3.0")
@@ -29,3 +31,6 @@ application {
"-Dledger.databasefolder=${project(":mod").file("run/money-ledger").absoluteFile}")
mainClass.set("moe.nea.ledger.server.core.ApplicationKt")
}
+buildConfig {
+ packageName("moe.nea.ledger.gen")
+}
diff --git a/server/core/src/main/kotlin/moe/nea/ledger/server/core/Application.kt b/server/core/src/main/kotlin/moe/nea/ledger/server/core/Application.kt
index 0ea6ed3..23b2a6a 100644
--- a/server/core/src/main/kotlin/moe/nea/ledger/server/core/Application.kt
+++ b/server/core/src/main/kotlin/moe/nea/ledger/server/core/Application.kt
@@ -6,29 +6,76 @@ import io.ktor.server.application.install
import io.ktor.server.netty.EngineMain
import io.ktor.server.plugins.compression.Compression
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
+import io.ktor.server.plugins.cors.routing.CORS
+import io.ktor.server.response.respondRedirect
+import io.ktor.server.routing.Routing
+import io.ktor.server.routing.get
import io.ktor.server.routing.route
import io.ktor.server.routing.routing
+import kotlinx.serialization.json.Json
import moe.nea.ledger.database.Database
+import moe.nea.ledger.gen.BuildConfig
+import moe.nea.ledger.server.core.api.Documentation
+import moe.nea.ledger.server.core.api.Info
+import moe.nea.ledger.server.core.api.Server
import moe.nea.ledger.server.core.api.apiRouting
+import moe.nea.ledger.server.core.api.openApiDocsJson
+import moe.nea.ledger.server.core.api.openApiUi
+import moe.nea.ledger.server.core.api.setApiRoot
import java.io.File
fun main(args: Array<String>) {
EngineMain.main(args)
}
+interface AIOProvider {
+ fun Routing.installExtraRouting()
+ fun Application.module()
+}
fun Application.module() {
+ val aio = runCatching {
+ Class.forName("moe.nea.ledger.server.aio.AIO")
+ .newInstance() as AIOProvider
+ }.getOrNull()
+ aio?.run { module() }
install(Compression)
+ install(Documentation) {
+ info = Info(
+ "Ledger Analysis Server",
+ "Your local API for loading ledger data",
+ BuildConfig.VERSION
+ )
+ servers.add(
+ Server("http://localhost:8080/api", "Your Local Server")
+ )
+ }
install(ContentNegotiation) {
- json()
+ json(Json {
+ this.explicitNulls = false
+ this.encodeDefaults = true
+ })
// cbor()
}
- val database = Database(File(System.getProperty("ledger.databasefolder")))
+ install(CORS) {
+ anyHost()
+ }
+ val database = Database(File(System.getProperty("ledger.databasefolder",
+ "/home/nea/.local/share/PrismLauncher/instances/Skyblock/.minecraft/money-ledger")))
database.loadAndUpgrade()
routing {
route("/api") {
- this.apiRouting(database)
+ setApiRoot()
+ get { call.respondRedirect("/openapi/") }
+ apiRouting(database)
+ }
+ route("/api.json") {
+ openApiDocsJson()
+ }
+ route("/openapi") {
+ openApiUi("/api.json")
}
+ aio?.run { installExtraRouting() }
}
}
diff --git a/server/core/src/main/kotlin/moe/nea/ledger/server/core/api/BaseApi.kt b/server/core/src/main/kotlin/moe/nea/ledger/server/core/api/BaseApi.kt
index 264f74b..c58763f 100644
--- a/server/core/src/main/kotlin/moe/nea/ledger/server/core/api/BaseApi.kt
+++ b/server/core/src/main/kotlin/moe/nea/ledger/server/core/api/BaseApi.kt
@@ -1,18 +1,21 @@
package moe.nea.ledger.server.core.api
+import io.ktor.http.Url
+import io.ktor.http.toURI
import io.ktor.server.response.respond
-import io.ktor.server.response.respondText
import io.ktor.server.routing.Route
-import io.ktor.server.routing.Routing
import io.ktor.server.routing.get
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.async
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.decodeFromStream
import moe.nea.ledger.database.DBLogEntry
import moe.nea.ledger.database.Database
import moe.nea.ledger.server.core.Profile
+import sh.ondr.jsonschema.jsonSchema
fun Route.apiRouting(database: Database) {
- get("/") {
- call.respondText("K")
- }
get("/profiles") {
val profiles = DBLogEntry.from(database.connection)
.select(DBLogEntry.playerId, DBLogEntry.profileId)
@@ -21,5 +24,42 @@ fun Route.apiRouting(database: Database) {
Profile(it[DBLogEntry.playerId], it[DBLogEntry.profileId])
}
call.respond(profiles)
+ }.docs {
+ summary = "List all profiles and players known to ledger"
+ operationId = "listProfiles"
+ tag(Tags.PROFILE)
+ respondsOk {
+ schema<List<Profile>>()
+ }
+ }
+ @OptIn(DelicateCoroutinesApi::class)
+ val itemNames = GlobalScope.async {
+ val itemNamesUrl =
+ Url("https://github.com/nea89o/ledger-auxiliary-data/raw/refs/heads/master/data/item_names.json")
+ Json.decodeFromStream<Map<String, String>>(itemNamesUrl.toURI().toURL().openStream())
+ }
+ get("/item") {
+ val itemIds = call.queryParameters.getAll("itemId")?.toSet() ?: emptySet()
+ val itemNameMap = itemNames.await()
+ call.respond(itemIds.associateWith { itemNameMap[it] })
+ }.docs {
+ summary = "Get item names for item ids"
+ operationId = "getItemNames"
+ tag(Tags.HYPIXEL)
+ queryParameter<List<String>>("itemId")
+ respondsOk {
+ schema<Map<String, String?>>()
+ }
}
}
+
+enum class Tags : IntoTag {
+ PROFILE,
+ HYPIXEL,
+ MANAGEMENT,
+ ;
+
+ override fun intoTag(): String {
+ return name
+ }
+} \ No newline at end of file
diff --git a/server/frontend/.gitignore b/server/frontend/.gitignore
new file mode 100644
index 0000000..76add87
--- /dev/null
+++ b/server/frontend/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+dist \ No newline at end of file
diff --git a/server/frontend/build.gradle.kts b/server/frontend/build.gradle.kts
new file mode 100644
index 0000000..72fe6ea
--- /dev/null
+++ b/server/frontend/build.gradle.kts
@@ -0,0 +1,17 @@
+import com.github.gradle.node.pnpm.task.PnpmTask
+
+plugins {
+ id("com.github.node-gradle.node") version "7.1.0"
+ `java-library`
+}
+
+val webDist by tasks.register("webDist", PnpmTask::class) {
+ dependsOn(tasks.pnpmInstall)
+ args.addAll("build")
+ outputs.dir("dist")
+}
+tasks.jar {
+ from(webDist) {
+ into("ledger-web-dist/")
+ }
+}
diff --git a/server/frontend/index.html b/server/frontend/index.html
new file mode 100644
index 0000000..48c59fc
--- /dev/null
+++ b/server/frontend/index.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta name="theme-color" content="#000000" />
+ <link rel="shortcut icon" type="image/ico" href="/src/assets/favicon.ico" />
+ <title>Solid App</title>
+ </head>
+ <body>
+ <noscript>You need to enable JavaScript to run this app.</noscript>
+ <div id="root"></div>
+
+ <script src="/src/index.tsx" type="module"></script>
+ </body>
+</html>
diff --git a/server/frontend/package.json b/server/frontend/package.json
new file mode 100644
index 0000000..f8a89f1
--- /dev/null
+++ b/server/frontend/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "ledger-frontend",
+ "version": "0.0.0",
+ "description": "",
+ "type": "module",
+ "scripts": {
+ "start": "vite",
+ "dev": "vite",
+ "build": "vite build",
+ "serve": "vite preview",
+ "test:ts": "tsc --noEmit",
+ "genApi": "pnpx openapi-typescript http://localhost:8080/api.json -o src/api-schema.d.ts"
+ },
+ "license": "MIT",
+ "devDependencies": {
+ "openapi-typescript": "^7.5.2",
+ "typescript": "^5.7.2",
+ "vite": "^6.0.0",
+ "vite-plugin-solid": "^2.11.0"
+ },
+ "dependencies": {
+ "@solidjs/router": "^0.15.3",
+ "openapi-fetch": "^0.13.4",
+ "solid-js": "^1.9.3"
+ },
+
+"devEngines": {
+ "packageManager": {
+ "name": "pnpm",
+ "onFail": "error"
+ }
+ },
+ "packageManager": "pnpm@^9.3.0"
+}
diff --git a/server/frontend/pnpm-lock.yaml b/server/frontend/pnpm-lock.yaml
new file mode 100644
index 0000000..e7a2e34
--- /dev/null
+++ b/server/frontend/pnpm-lock.yaml
@@ -0,0 +1,1338 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@solidjs/router':
+ specifier: ^0.15.3
+ version: 0.15.3(solid-js@1.9.4)
+ openapi-fetch:
+ specifier: ^0.13.4
+ version: 0.13.4
+ solid-js:
+ specifier: ^1.9.3
+ version: 1.9.4
+ devDependencies:
+ openapi-typescript:
+ specifier: ^7.5.2
+ version: 7.5.2(typescript@5.7.3)
+ typescript:
+ specifier: ^5.7.2
+ version: 5.7.3
+ vite:
+ specifier: ^6.0.0
+ version: 6.0.7
+ vite-plugin-solid:
+ specifier: ^2.11.0
+ version: 2.11.0(solid-js@1.9.4)(vite@6.0.7)
+
+packages:
+
+ '@ampproject/remapping@2.3.0':
+ resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
+ engines: {node: '>=6.0.0'}
+
+ '@babel/code-frame@7.26.2':
+ resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/compat-data@7.26.5':
+ resolution: {integrity: sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/core@7.26.0':
+ resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.26.5':
+ resolution: {integrity: sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-compilation-targets@7.26.5':
+ resolution: {integrity: sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-imports@7.18.6':
+ resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-imports@7.25.9':
+ resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-transforms@7.26.0':
+ resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-plugin-utils@7.26.5':
+ resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-string-parser@7.25.9':
+ resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-identifier@7.25.9':
+ resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-option@7.25.9':
+ resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helpers@7.26.0':
+ resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/parser@7.26.5':
+ resolution: {integrity: sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ '@babel/plugin-syntax-jsx@7.25.9':
+ resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/template@7.25.9':
+ resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.26.5':
+ resolution: {integrity: sha512-rkOSPOw+AXbgtwUga3U4u8RpoK9FEFWBNAlTpcnkLFjL5CT+oyHNuUUC/xx6XefEJ16r38r8Bc/lfp6rYuHeJQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/types@7.26.5':
+ resolution: {integrity: sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==}
+ engines: {node: '>=6.9.0'}
+
+ '@esbuild/aix-ppc64@0.24.2':
+ resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.24.2':
+ resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.24.2':
+ resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.24.2':
+ resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.24.2':
+ resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.24.2':
+ resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.24.2':
+ resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.24.2':
+ resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.24.2':
+ resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.24.2':
+ resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.24.2':
+ resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.24.2':
+ resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.24.2':
+ resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.24.2':
+ resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.24.2':
+ resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.24.2':
+ resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.24.2':
+ resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-arm64@0.24.2':
+ resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.24.2':
+ resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.24.2':
+ resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.24.2':
+ resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/sunos-x64@0.24.2':
+ resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.24.2':
+ resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.24.2':
+ resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.24.2':
+ resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@jridgewell/gen-mapping@0.3.8':
+ resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/set-array@1.2.1':
+ resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.5.0':
+ resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
+
+ '@jridgewell/trace-mapping@0.3.25':
+ resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
+
+ '@redocly/ajv@8.11.2':
+ resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==}
+
+ '@redocly/config@0.20.1':
+ resolution: {integrity: sha512-TYiTDtuItiv95YMsrRxyCs1HKLrDPtTvpaD3+kDKXBnFDeJuYKZ+eHXpCr6YeN4inxfVBs7DLhHsQcs9srddyQ==}
+
+ '@redocly/openapi-core@1.27.2':
+ resolution: {integrity: sha512-qVrDc27DHpeO2NRCMeRdb4299nijKQE3BY0wrA+WUHlOLScorIi/y7JzammLk22IaTvjR9Mv9aTAdjE1aUwJnA==}
+ engines: {node: '>=14.19.0', npm: '>=7.0.0'}
+
+ '@rollup/rollup-android-arm-eabi@4.30.1':
+ resolution: {integrity: sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.30.1':
+ resolution: {integrity: sha512-/NA2qXxE3D/BRjOJM8wQblmArQq1YoBVJjrjoTSBS09jgUisq7bqxNHJ8kjCHeV21W/9WDGwJEWSN0KQ2mtD/w==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-darwin-arm64@4.30.1':
+ resolution: {integrity: sha512-r7FQIXD7gB0WJ5mokTUgUWPl0eYIH0wnxqeSAhuIwvnnpjdVB8cRRClyKLQr7lgzjctkbp5KmswWszlwYln03Q==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.30.1':
+ resolution: {integrity: sha512-x78BavIwSH6sqfP2xeI1hd1GpHL8J4W2BXcVM/5KYKoAD3nNsfitQhvWSw+TFtQTLZ9OmlF+FEInEHyubut2OA==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-freebsd-arm64@4.30.1':
+ resolution: {integrity: sha512-HYTlUAjbO1z8ywxsDFWADfTRfTIIy/oUlfIDmlHYmjUP2QRDTzBuWXc9O4CXM+bo9qfiCclmHk1x4ogBjOUpUQ==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.30.1':
+ resolution: {integrity: sha512-1MEdGqogQLccphhX5myCJqeGNYTNcmTyaic9S7CG3JhwuIByJ7J05vGbZxsizQthP1xpVx7kd3o31eOogfEirw==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.30.1':
+ resolution: {integrity: sha512-PaMRNBSqCx7K3Wc9QZkFx5+CX27WFpAMxJNiYGAXfmMIKC7jstlr32UhTgK6T07OtqR+wYlWm9IxzennjnvdJg==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.30.1':
+ resolution: {integrity: sha512-B8Rcyj9AV7ZlEFqvB5BubG5iO6ANDsRKlhIxySXcF1axXYUyqwBok+XZPgIYGBgs7LDXfWfifxhw0Ik57T0Yug==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-gnu@4.30.1':
+ resolution: {integrity: sha512-hqVyueGxAj3cBKrAI4aFHLV+h0Lv5VgWZs9CUGqr1z0fZtlADVV1YPOij6AhcK5An33EXaxnDLmJdQikcn5NEw==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-musl@4.30.1':
+ resolution: {integrity: sha512-i4Ab2vnvS1AE1PyOIGp2kXni69gU2DAUVt6FSXeIqUCPIR3ZlheMW3oP2JkukDfu3PsexYRbOiJrY+yVNSk9oA==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loongarch64-gnu@4.30.1':
+ resolution: {integrity: sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-powerpc64le-gnu@4.30.1':
+ resolution: {integrity: sha512-GLrZraoO3wVT4uFXh67ElpwQY0DIygxdv0BNW9Hkm3X34wu+BkqrDrkcsIapAY+N2ATEbvak0XQ9gxZtCIA5Rw==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.30.1':
+ resolution: {integrity: sha512-0WKLaAUUHKBtll0wvOmh6yh3S0wSU9+yas923JIChfxOaaBarmb/lBKPF0w/+jTVozFnOXJeRGZ8NvOxvk/jcw==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-s390x-gnu@4.30.1':
+ resolution: {integrity: sha512-GWFs97Ruxo5Bt+cvVTQkOJ6TIx0xJDD/bMAOXWJg8TCSTEK8RnFeOeiFTxKniTc4vMIaWvCplMAFBt9miGxgkA==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-gnu@4.30.1':
+ resolution: {integrity: sha512-UtgGb7QGgXDIO+tqqJ5oZRGHsDLO8SlpE4MhqpY9Llpzi5rJMvrK6ZGhsRCST2abZdBqIBeXW6WPD5fGK5SDwg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-musl@4.30.1':
+ resolution: {integrity: sha512-V9U8Ey2UqmQsBT+xTOeMzPzwDzyXmnAoO4edZhL7INkwQcaW1Ckv3WJX3qrrp/VHaDkEWIBWhRwP47r8cdrOow==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-win32-arm64-msvc@4.30.1':
+ resolution: {integrity: sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.30.1':
+ resolution: {integrity: sha512-pxHAU+Zv39hLUTdQQHUVHf4P+0C47y/ZloorHpzs2SXMRqeAWmGghzAhfOlzFHHwjvgokdFAhC4V+6kC1lRRfw==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.30.1':
+ resolution: {integrity: sha512-D6qjsXGcvhTjv0kI4fU8tUuBDF/Ueee4SVX79VfNDXZa64TfCW1Slkb6Z7O1p7vflqZjcmOVdZlqf8gvJxc6og==}
+ cpu: [x64]
+ os: [win32]
+
+ '@solidjs/router@0.15.3':
+ resolution: {integrity: sha512-iEbW8UKok2Oio7o6Y4VTzLj+KFCmQPGEpm1fS3xixwFBdclFVBvaQVeibl1jys4cujfAK5Kn6+uG2uBm3lxOMw==}
+ peerDependencies:
+ solid-js: ^1.8.6
+
+ '@types/babel__core@7.20.5':
+ resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+
+ '@types/babel__generator@7.6.8':
+ resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==}
+
+ '@types/babel__template@7.4.4':
+ resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+
+ '@types/babel__traverse@7.20.6':
+ resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==}
+
+ '@types/estree@1.0.6':
+ resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
+
+ agent-base@7.1.3:
+ resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==}
+ engines: {node: '>= 14'}
+
+ ansi-colors@4.1.3:
+ resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
+ engines: {node: '>=6'}
+
+ argparse@2.0.1:
+ resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+ babel-plugin-jsx-dom-expressions@0.39.5:
+ resolution: {integrity: sha512-dwyVkszHRsZCXfFusu3xq1DJS7twhgLrjEpMC1gtTfJG1xSrMMKWWhdl1SFFFNXrvYDsoHiRxSbku/TzLxHNxg==}
+ peerDependencies:
+ '@babel/core': ^7.20.12
+
+ babel-preset-solid@1.9.3:
+ resolution: {integrity: sha512-jvlx5wDp8s+bEF9sGFw/84SInXOA51ttkUEroQziKMbxplXThVKt83qB6bDTa1HuLNatdU9FHpFOiQWs1tLQIg==}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ brace-expansion@2.0.1:
+ resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
+
+ browserslist@4.24.4:
+ resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
+ caniuse-lite@1.0.30001692:
+ resolution: {integrity: sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==}
+
+ change-case@5.4.4:
+ resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==}
+
+ colorette@1.4.0:
+ resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==}
+
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+ csstype@3.1.3:
+ resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
+
+ debug@4.4.0:
+ resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ electron-to-chromium@1.5.83:
+ resolution: {integrity: sha512-LcUDPqSt+V0QmI47XLzZrz5OqILSMGsPFkDYus22rIbgorSvBYEFqq854ltTmUdHkY92FSdAAvsh4jWEULMdfQ==}
+
+ entities@4.5.0:
+ resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
+ engines: {node: '>=0.12'}
+
+ esbuild@0.24.2:
+ resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
+ fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+
+ globals@11.12.0:
+ resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
+ engines: {node: '>=4'}
+
+ html-entities@2.3.3:
+ resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==}
+
+ https-proxy-agent@7.0.6:
+ resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
+ engines: {node: '>= 14'}
+
+ index-to-position@0.1.2:
+ resolution: {integrity: sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g==}
+ engines: {node: '>=18'}
+
+ is-what@4.1.16:
+ resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
+ engines: {node: '>=12.13'}
+
+ js-levenshtein@1.1.6:
+ resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==}
+ engines: {node: '>=0.10.0'}
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ js-yaml@4.1.0:
+ resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
+ hasBin: true
+
+ jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ json-schema-traverse@1.0.0:
+ resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+
+ json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+ merge-anything@5.1.7:
+ resolution: {integrity: sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==}
+ engines: {node: '>=12.13'}
+
+ minimatch@5.1.6:
+ resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
+ engines: {node: '>=10'}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ nanoid@3.3.8:
+ resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ node-fetch@2.7.0:
+ resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
+ engines: {node: 4.x || >=6.0.0}
+ peerDependencies:
+ encoding: ^0.1.0
+ peerDependenciesMeta:
+ encoding:
+ optional: true
+
+ node-releases@2.0.19:
+ resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
+
+ openapi-fetch@0.13.4:
+ resolution: {integrity: sha512-JHX7UYjLEiHuQGCPxa3CCCIqe/nc4bTIF9c4UYVC8BegAbWoS3g4gJxKX5XcG7UtYQs2060kY6DH64KkvNZahg==}
+
+ openapi-typescript-helpers@0.0.15:
+ resolution: {integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==}
+
+ openapi-typescript@7.5.2:
+ resolution: {integrity: sha512-W/QXuQz0Fa3bGY6LKoqTCgrSX+xI/ST+E5RXo2WBmp3WwgXCWKDJPHv5GZmElF4yLCccnqYsakBDOJikHZYGRw==}
+ hasBin: true
+ peerDependencies:
+ typescript: ^5.x
+
+ parse-json@8.1.0:
+ resolution: {integrity: sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==}
+ engines: {node: '>=18'}
+
+ parse5@7.2.1:
+ resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ pluralize@8.0.0:
+ resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
+ engines: {node: '>=4'}
+
+ postcss@8.5.1:
+ resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ require-from-string@2.0.2:
+ resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
+ engines: {node: '>=0.10.0'}
+
+ rollup@4.30.1:
+ resolution: {integrity: sha512-mlJ4glW020fPuLi7DkM/lN97mYEZGWeqBnrljzN0gs7GLctqX3lNWxKQ7Gl712UAX+6fog/L3jh4gb7R6aVi3w==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
+ seroval-plugins@1.2.0:
+ resolution: {integrity: sha512-hULTbfzSe81jGWLH8TAJjkEvw6JWMqOo9Uq+4V4vg+HNq53hyHldM9ZOfjdzokcFysiTp9aFdV2vJpZFqKeDjQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ seroval: ^1.0
+
+ seroval@1.2.0:
+ resolution: {integrity: sha512-GURoU99ko2UiAgUC3qDCk59Jb3Ss4Po8VIMGkG8j5PFo2Q7y0YSMP8QG9NuL/fJCoTz9V1XZUbpNIMXPOfaGpA==}
+ engines: {node: '>=10'}
+
+ solid-js@1.9.4:
+ resolution: {integrity: sha512-ipQl8FJ31bFUoBNScDQTG3BjN6+9Rg+Q+f10bUbnO6EOTTf5NGerJeHc7wyu5I4RMHEl/WwZwUmy/PTRgxxZ8g==}
+
+ solid-refresh@0.6.3:
+ resolution: {integrity: sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==}
+ peerDependencies:
+ solid-js: ^1.3
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ supports-color@9.4.0:
+ resolution: {integrity: sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==}
+ engines: {node: '>=12'}
+
+ tr46@0.0.3:
+ resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
+
+ type-fest@4.32.0:
+ resolution: {integrity: sha512-rfgpoi08xagF3JSdtJlCwMq9DGNDE0IMh3Mkpc1wUypg9vPi786AiqeBBKcqvIkq42azsBM85N490fyZjeUftw==}
+ engines: {node: '>=16'}
+
+ typescript@5.7.3:
+ resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ update-browserslist-db@1.1.2:
+ resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
+ uri-js-replace@1.0.1:
+ resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==}
+
+ validate-html-nesting@1.2.2:
+ resolution: {integrity: sha512-hGdgQozCsQJMyfK5urgFcWEqsSSrK63Awe0t/IMR0bZ0QMtnuaiHzThW81guu3qx9abLi99NEuiaN6P9gVYsNg==}
+
+ vite-plugin-solid@2.11.0:
+ resolution: {integrity: sha512-G+NiwDj4EAeUE0wt3Ur9f+Lt9oMUuLd0FIxYuqwJSqRacKQRteCwUFzNy8zMEt88xWokngQhiFjfJMhjc1fDXw==}
+ peerDependencies:
+ '@testing-library/jest-dom': ^5.16.6 || ^5.17.0 || ^6.*
+ solid-js: ^1.7.2
+ vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0
+ peerDependenciesMeta:
+ '@testing-library/jest-dom':
+ optional: true
+
+ vite@6.0.7:
+ resolution: {integrity: sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+ jiti: '>=1.21.0'
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ sass-embedded: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ vitefu@1.0.5:
+ resolution: {integrity: sha512-h4Vflt9gxODPFNGPwp4zAMZRpZR7eslzwH2c5hn5kNZ5rhnKyRJ50U+yGCdc2IRaBs8O4haIgLNGrV5CrpMsCA==}
+ peerDependencies:
+ vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0
+ peerDependenciesMeta:
+ vite:
+ optional: true
+
+ webidl-conversions@3.0.1:
+ resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
+
+ whatwg-url@5.0.0:
+ resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
+
+ yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+ yaml-ast-parser@0.0.43:
+ resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==}
+
+ yargs-parser@21.1.1:
+ resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
+ engines: {node: '>=12'}
+
+snapshots:
+
+ '@ampproject/remapping@2.3.0':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.8
+ '@jridgewell/trace-mapping': 0.3.25
+
+ '@babel/code-frame@7.26.2':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.25.9
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/compat-data@7.26.5': {}
+
+ '@babel/core@7.26.0':
+ dependencies:
+ '@ampproject/remapping': 2.3.0
+ '@babel/code-frame': 7.26.2
+ '@babel/generator': 7.26.5
+ '@babel/helper-compilation-targets': 7.26.5
+ '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0)
+ '@babel/helpers': 7.26.0
+ '@babel/parser': 7.26.5
+ '@babel/template': 7.25.9
+ '@babel/traverse': 7.26.5
+ '@babel/types': 7.26.5
+ convert-source-map: 2.0.0
+ debug: 4.4.0(supports-color@9.4.0)
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/generator@7.26.5':
+ dependencies:
+ '@babel/parser': 7.26.5
+ '@babel/types': 7.26.5
+ '@jridgewell/gen-mapping': 0.3.8
+ '@jridgewell/trace-mapping': 0.3.25
+ jsesc: 3.1.0
+
+ '@babel/helper-compilation-targets@7.26.5':
+ dependencies:
+ '@babel/compat-data': 7.26.5
+ '@babel/helper-validator-option': 7.25.9
+ browserslist: 4.24.4
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-module-imports@7.18.6':
+ dependencies:
+ '@babel/types': 7.26.5
+
+ '@babel/helper-module-imports@7.25.9':
+ dependencies:
+ '@babel/traverse': 7.26.5
+ '@babel/types': 7.26.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)':
+ dependencies:
+ '@babel/core': 7.26.0
+ '@babel/helper-module-imports': 7.25.9
+ '@babel/helper-validator-identifier': 7.25.9
+ '@babel/traverse': 7.26.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-plugin-utils@7.26.5': {}
+
+ '@babel/helper-string-parser@7.25.9': {}
+
+ '@babel/helper-validator-identifier@7.25.9': {}
+
+ '@babel/helper-validator-option@7.25.9': {}
+
+ '@babel/helpers@7.26.0':
+ dependencies:
+ '@babel/template': 7.25.9
+ '@babel/types': 7.26.5
+
+ '@babel/parser@7.26.5':
+ dependencies:
+ '@babel/types': 7.26.5
+
+ '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)':
+ dependencies:
+ '@babel/core': 7.26.0
+ '@babel/helper-plugin-utils': 7.26.5
+
+ '@babel/template@7.25.9':
+ dependencies:
+ '@babel/code-frame': 7.26.2
+ '@babel/parser': 7.26.5
+ '@babel/types': 7.26.5
+
+ '@babel/traverse@7.26.5':
+ dependencies:
+ '@babel/code-frame': 7.26.2
+ '@babel/generator': 7.26.5
+ '@babel/parser': 7.26.5
+ '@babel/template': 7.25.9
+ '@babel/types': 7.26.5
+ debug: 4.4.0(supports-color@9.4.0)
+ globals: 11.12.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/types@7.26.5':
+ dependencies:
+ '@babel/helper-string-parser': 7.25.9
+ '@babel/helper-validator-identifier': 7.25.9
+
+ '@esbuild/aix-ppc64@0.24.2':
+ optional: true
+
+ '@esbuild/android-arm64@0.24.2':
+ optional: true
+
+ '@esbuild/android-arm@0.24.2':
+ optional: true
+
+ '@esbuild/android-x64@0.24.2':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.24.2':
+ optional: true
+
+ '@esbuild/darwin-x64@0.24.2':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.24.2':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.24.2':
+ optional: true
+
+ '@esbuild/linux-arm64@0.24.2':
+ optional: true
+
+ '@esbuild/linux-arm@0.24.2':
+ optional: true
+
+ '@esbuild/linux-ia32@0.24.2':
+ optional: true
+
+ '@esbuild/linux-loong64@0.24.2':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.24.2':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.24.2':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.24.2':
+ optional: true
+
+ '@esbuild/linux-s390x@0.24.2':
+ optional: true
+
+ '@esbuild/linux-x64@0.24.2':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.24.2':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.24.2':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.24.2':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.24.2':
+ optional: true
+
+ '@esbuild/sunos-x64@0.24.2':
+ optional: true
+
+ '@esbuild/win32-arm64@0.24.2':
+ optional: true
+
+ '@esbuild/win32-ia32@0.24.2':
+ optional: true
+
+ '@esbuild/win32-x64@0.24.2':
+ optional: true
+
+ '@jridgewell/gen-mapping@0.3.8':
+ dependencies:
+ '@jridgewell/set-array': 1.2.1
+ '@jridgewell/sourcemap-codec': 1.5.0
+ '@jridgewell/trace-mapping': 0.3.25
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/set-array@1.2.1': {}
+
+ '@jridgewell/sourcemap-codec@1.5.0': {}
+
+ '@jridgewell/trace-mapping@0.3.25':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.0
+
+ '@redocly/ajv@8.11.2':
+ dependencies:
+ fast-deep-equal: 3.1.3
+ json-schema-traverse: 1.0.0
+ require-from-string: 2.0.2
+ uri-js-replace: 1.0.1
+
+ '@redocly/config@0.20.1': {}
+
+ '@redocly/openapi-core@1.27.2(supports-color@9.4.0)':
+ dependencies:
+ '@redocly/ajv': 8.11.2
+ '@redocly/config': 0.20.1
+ colorette: 1.4.0
+ https-proxy-agent: 7.0.6(supports-color@9.4.0)
+ js-levenshtein: 1.1.6
+ js-yaml: 4.1.0
+ minimatch: 5.1.6
+ node-fetch: 2.7.0
+ pluralize: 8.0.0
+ yaml-ast-parser: 0.0.43
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+
+ '@rollup/rollup-android-arm-eabi@4.30.1':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.30.1':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.30.1':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.30.1':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.30.1':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.30.1':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.30.1':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.30.1':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.30.1':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.30.1':
+ optional: true
+
+ '@rollup/rollup-linux-loongarch64-gnu@4.30.1':
+ optional: true
+
+ '@rollup/rollup-linux-powerpc64le-gnu@4.30.1':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.30.1':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.30.1':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.30.1':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.30.1':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.30.1':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.30.1':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.30.1':
+ optional: true
+
+ '@solidjs/router@0.15.3(solid-js@1.9.4)':
+ dependencies:
+ solid-js: 1.9.4
+
+ '@types/babel__core@7.20.5':
+ dependencies:
+ '@babel/parser': 7.26.5
+ '@babel/types': 7.26.5
+ '@types/babel__generator': 7.6.8
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.20.6
+
+ '@types/babel__generator@7.6.8':
+ dependencies:
+ '@babel/types': 7.26.5
+
+ '@types/babel__template@7.4.4':
+ dependencies:
+ '@babel/parser': 7.26.5
+ '@babel/types': 7.26.5
+
+ '@types/babel__traverse@7.20.6':
+ dependencies:
+ '@babel/types': 7.26.5
+
+ '@types/estree@1.0.6': {}
+
+ agent-base@7.1.3: {}
+
+ ansi-colors@4.1.3: {}
+
+ argparse@2.0.1: {}
+
+ babel-plugin-jsx-dom-expressions@0.39.5(@babel/core@7.26.0):
+ dependencies:
+ '@babel/core': 7.26.0
+ '@babel/helper-module-imports': 7.18.6
+ '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0)
+ '@babel/types': 7.26.5
+ html-entities: 2.3.3
+ parse5: 7.2.1
+ validate-html-nesting: 1.2.2
+
+ babel-preset-solid@1.9.3(@babel/core@7.26.0):
+ dependencies:
+ '@babel/core': 7.26.0
+ babel-plugin-jsx-dom-expressions: 0.39.5(@babel/core@7.26.0)
+
+ balanced-match@1.0.2: {}
+
+ brace-expansion@2.0.1:
+ dependencies:
+ balanced-match: 1.0.2
+
+ browserslist@4.24.4:
+ dependencies:
+ caniuse-lite: 1.0.30001692
+ electron-to-chromium: 1.5.83
+ node-releases: 2.0.19
+ update-browserslist-db: 1.1.2(browserslist@4.24.4)
+
+ caniuse-lite@1.0.30001692: {}
+
+ change-case@5.4.4: {}
+
+ colorette@1.4.0: {}
+
+ convert-source-map@2.0.0: {}
+
+ csstype@3.1.3: {}
+
+ debug@4.4.0(supports-color@9.4.0):
+ dependencies:
+ ms: 2.1.3
+ optionalDependencies:
+ supports-color: 9.4.0
+
+ electron-to-chromium@1.5.83: {}
+
+ entities@4.5.0: {}
+
+ esbuild@0.24.2:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.24.2
+ '@esbuild/android-arm': 0.24.2
+ '@esbuild/android-arm64': 0.24.2
+ '@esbuild/android-x64': 0.24.2
+ '@esbuild/darwin-arm64': 0.24.2
+ '@esbuild/darwin-x64': 0.24.2
+ '@esbuild/freebsd-arm64': 0.24.2
+ '@esbuild/freebsd-x64': 0.24.2
+ '@esbuild/linux-arm': 0.24.2
+ '@esbuild/linux-arm64': 0.24.2
+ '@esbuild/linux-ia32': 0.24.2
+ '@esbuild/linux-loong64': 0.24.2
+ '@esbuild/linux-mips64el': 0.24.2
+ '@esbuild/linux-ppc64': 0.24.2
+ '@esbuild/linux-riscv64': 0.24.2
+ '@esbuild/linux-s390x': 0.24.2
+ '@esbuild/linux-x64': 0.24.2
+ '@esbuild/netbsd-arm64': 0.24.2
+ '@esbuild/netbsd-x64': 0.24.2
+ '@esbuild/openbsd-arm64': 0.24.2
+ '@esbuild/openbsd-x64': 0.24.2
+ '@esbuild/sunos-x64': 0.24.2
+ '@esbuild/win32-arm64': 0.24.2
+ '@esbuild/win32-ia32': 0.24.2
+ '@esbuild/win32-x64': 0.24.2
+
+ escalade@3.2.0: {}
+
+ fast-deep-equal@3.1.3: {}
+
+ fsevents@2.3.3:
+ optional: true
+
+ gensync@1.0.0-beta.2: {}
+
+ globals@11.12.0: {}
+
+ html-entities@2.3.3: {}
+
+ https-proxy-agent@7.0.6(supports-color@9.4.0):
+ dependencies:
+ agent-base: 7.1.3
+ debug: 4.4.0(supports-color@9.4.0)
+ transitivePeerDependencies:
+ - supports-color
+
+ index-to-position@0.1.2: {}
+
+ is-what@4.1.16: {}
+
+ js-levenshtein@1.1.6: {}
+
+ js-tokens@4.0.0: {}
+
+ js-yaml@4.1.0:
+ dependencies:
+ argparse: 2.0.1
+
+ jsesc@3.1.0: {}
+
+ json-schema-traverse@1.0.0: {}
+
+ json5@2.2.3: {}
+
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
+ merge-anything@5.1.7:
+ dependencies:
+ is-what: 4.1.16
+
+ minimatch@5.1.6:
+ dependencies:
+ brace-expansion: 2.0.1
+
+ ms@2.1.3: {}
+
+ nanoid@3.3.8: {}
+
+ node-fetch@2.7.0:
+ dependencies:
+ whatwg-url: 5.0.0
+
+ node-releases@2.0.19: {}
+
+ openapi-fetch@0.13.4:
+ dependencies:
+ openapi-typescript-helpers: 0.0.15
+
+ openapi-typescript-helpers@0.0.15: {}
+
+ openapi-typescript@7.5.2(typescript@5.7.3):
+ dependencies:
+ '@redocly/openapi-core': 1.27.2(supports-color@9.4.0)
+ ansi-colors: 4.1.3
+ change-case: 5.4.4
+ parse-json: 8.1.0
+ supports-color: 9.4.0
+ typescript: 5.7.3
+ yargs-parser: 21.1.1
+ transitivePeerDependencies:
+ - encoding
+
+ parse-json@8.1.0:
+ dependencies:
+ '@babel/code-frame': 7.26.2
+ index-to-position: 0.1.2
+ type-fest: 4.32.0
+
+ parse5@7.2.1:
+ dependencies:
+ entities: 4.5.0
+
+ picocolors@1.1.1: {}
+
+ pluralize@8.0.0: {}
+
+ postcss@8.5.1:
+ dependencies:
+ nanoid: 3.3.8
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ require-from-string@2.0.2: {}
+
+ rollup@4.30.1:
+ dependencies:
+ '@types/estree': 1.0.6
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.30.1
+ '@rollup/rollup-android-arm64': 4.30.1
+ '@rollup/rollup-darwin-arm64': 4.30.1
+ '@rollup/rollup-darwin-x64': 4.30.1
+ '@rollup/rollup-freebsd-arm64': 4.30.1
+ '@rollup/rollup-freebsd-x64': 4.30.1
+ '@rollup/rollup-linux-arm-gnueabihf': 4.30.1
+ '@rollup/rollup-linux-arm-musleabihf': 4.30.1
+ '@rollup/rollup-linux-arm64-gnu': 4.30.1
+ '@rollup/rollup-linux-arm64-musl': 4.30.1
+ '@rollup/rollup-linux-loongarch64-gnu': 4.30.1
+ '@rollup/rollup-linux-powerpc64le-gnu': 4.30.1
+ '@rollup/rollup-linux-riscv64-gnu': 4.30.1
+ '@rollup/rollup-linux-s390x-gnu': 4.30.1
+ '@rollup/rollup-linux-x64-gnu': 4.30.1
+ '@rollup/rollup-linux-x64-musl': 4.30.1
+ '@rollup/rollup-win32-arm64-msvc': 4.30.1
+ '@rollup/rollup-win32-ia32-msvc': 4.30.1
+ '@rollup/rollup-win32-x64-msvc': 4.30.1
+ fsevents: 2.3.3
+
+ semver@6.3.1: {}
+
+ seroval-plugins@1.2.0(seroval@1.2.0):
+ dependencies:
+ seroval: 1.2.0
+
+ seroval@1.2.0: {}
+
+ solid-js@1.9.4:
+ dependencies:
+ csstype: 3.1.3
+ seroval: 1.2.0
+ seroval-plugins: 1.2.0(seroval@1.2.0)
+
+ solid-refresh@0.6.3(solid-js@1.9.4):
+ dependencies:
+ '@babel/generator': 7.26.5
+ '@babel/helper-module-imports': 7.25.9
+ '@babel/types': 7.26.5
+ solid-js: 1.9.4
+ transitivePeerDependencies:
+ - supports-color
+
+ source-map-js@1.2.1: {}
+
+ supports-color@9.4.0: {}
+
+ tr46@0.0.3: {}
+
+ type-fest@4.32.0: {}
+
+ typescript@5.7.3: {}
+
+ update-browserslist-db@1.1.2(browserslist@4.24.4):
+ dependencies:
+ browserslist: 4.24.4
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ uri-js-replace@1.0.1: {}
+
+ validate-html-nesting@1.2.2: {}
+
+ vite-plugin-solid@2.11.0(solid-js@1.9.4)(vite@6.0.7):
+ dependencies:
+ '@babel/core': 7.26.0
+ '@types/babel__core': 7.20.5
+ babel-preset-solid: 1.9.3(@babel/core@7.26.0)
+ merge-anything: 5.1.7
+ solid-js: 1.9.4
+ solid-refresh: 0.6.3(solid-js@1.9.4)
+ vite: 6.0.7
+ vitefu: 1.0.5(vite@6.0.7)
+ transitivePeerDependencies:
+ - supports-color
+
+ vite@6.0.7:
+ dependencies:
+ esbuild: 0.24.2
+ postcss: 8.5.1
+ rollup: 4.30.1
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ vitefu@1.0.5(vite@6.0.7):
+ optionalDependencies:
+ vite: 6.0.7
+
+ webidl-conversions@3.0.1: {}
+
+ whatwg-url@5.0.0:
+ dependencies:
+ tr46: 0.0.3
+ webidl-conversions: 3.0.1
+
+ yallist@3.1.1: {}
+
+ yaml-ast-parser@0.0.43: {}
+
+ yargs-parser@21.1.1: {}
diff --git a/server/frontend/src/App.module.css b/server/frontend/src/App.module.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/server/frontend/src/App.module.css
diff --git a/server/frontend/src/App.tsx b/server/frontend/src/App.tsx
new file mode 100644
index 0000000..e35bb42
--- /dev/null
+++ b/server/frontend/src/App.tsx
@@ -0,0 +1,13 @@
+import type { Component } from "solid-js";
+import { A } from "@solidjs/router";
+
+const App: Component = () => {
+ return (
+ <>
+ Hello World
+ <A href="/test">Test Page</A>
+ </>
+ );
+};
+
+export default App;
diff --git a/server/frontend/src/Test.tsx b/server/frontend/src/Test.tsx
new file mode 100644
index 0000000..15d2f73
--- /dev/null
+++ b/server/frontend/src/Test.tsx
@@ -0,0 +1,31 @@
+import { A, createAsync } from "@solidjs/router";
+import { client } from "./api.js";
+import { For, Suspense } from "solid-js";
+
+export default function Test() {
+ let items = createAsync(() =>
+ client.GET("/item", {
+ params: {
+ query: {
+ itemId: ["HYPERION", "BAT_WAND"],
+ },
+ },
+ })
+ );
+ return (
+ <>
+ Test page <A href={"/"}>Back to main</A>
+ <hr />
+ <Suspense fallback={"Loading items..."}>
+ <p>Here are all Items:</p>
+ <For each={Object.entries(items()?.data || {})}>
+ {([id, name]) => (
+ <li>
+ <code>{id}</code>: {name}
+ </li>
+ )}
+ </For>
+ </Suspense>
+ </>
+ );
+}
diff --git a/server/frontend/src/api-schema.d.ts b/server/frontend/src/api-schema.d.ts
new file mode 100644
index 0000000..93bedc9
--- /dev/null
+++ b/server/frontend/src/api-schema.d.ts
@@ -0,0 +1,98 @@
+/**
+ * This file was auto-generated by openapi-typescript.
+ * Do not make direct changes to the file.
+ */
+
+export interface paths {
+ "/profiles": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** List all profiles and players known to ledger */
+ get: operations["listProfiles"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/item": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Get item names for item ids */
+ get: operations["getItemNames"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+}
+export type webhooks = Record<string, never>;
+export interface components {
+ schemas: never;
+ responses: never;
+ parameters: never;
+ requestBodies: never;
+ headers: never;
+ pathItems: never;
+}
+export type $defs = Record<string, never>;
+export interface operations {
+ listProfiles: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ playerId: string;
+ profileId: string;
+ }[];
+ };
+ };
+ };
+ };
+ getItemNames: {
+ parameters: {
+ query?: {
+ itemId?: string[];
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ [key: string]: string;
+ };
+ };
+ };
+ };
+ };
+}
diff --git a/server/frontend/src/api.ts b/server/frontend/src/api.ts
new file mode 100644
index 0000000..22cf6ca
--- /dev/null
+++ b/server/frontend/src/api.ts
@@ -0,0 +1,6 @@
+import createClient from "openapi-fetch";
+import type { paths } from "./api-schema.js";
+
+const apiRoot = import.meta.env.DEV ? "//localhost:8080/api" : "/api";
+
+export const client = createClient<paths>({ baseUrl: apiRoot });
diff --git a/server/frontend/src/index.css b/server/frontend/src/index.css
new file mode 100644
index 0000000..4a1df4d
--- /dev/null
+++ b/server/frontend/src/index.css
@@ -0,0 +1,13 @@
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
+ "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+code {
+ font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
+ monospace;
+}
diff --git a/server/frontend/src/index.tsx b/server/frontend/src/index.tsx
new file mode 100644
index 0000000..6ab7d4e
--- /dev/null
+++ b/server/frontend/src/index.tsx
@@ -0,0 +1,21 @@
+/* @refresh reload */
+import { render } from "solid-js/web";
+
+import "./index.css";
+import type { RouteDefinition } from "@solidjs/router";
+import { Router } from "@solidjs/router";
+import { lazy } from "solid-js";
+
+const root = document.getElementById("root");
+
+if (!(root instanceof HTMLElement)) {
+ throw new Error(
+ "Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?"
+ );
+}
+const routes: Array<RouteDefinition> = [
+ { path: "/", component: lazy(() => import("./App.tsx")) },
+ { path: "/test/", component: lazy(() => import("./Test.tsx")) },
+];
+
+render(() => <Router>{routes}</Router>, root!);
diff --git a/server/frontend/tsconfig.json b/server/frontend/tsconfig.json
new file mode 100644
index 0000000..73eb483
--- /dev/null
+++ b/server/frontend/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "jsxImportSource": "solid-js",
+ "module": "ESNext",
+ "moduleResolution": "NodeNext",
+ "noEmit": true,
+ "noUncheckedIndexedAccess": true,
+ "strict": true,
+ "target": "ESNext",
+ "types": [
+ "vite/client"
+ ]
+ }
+}
diff --git a/server/frontend/vite.config.ts b/server/frontend/vite.config.ts
new file mode 100644
index 0000000..9ff59a1
--- /dev/null
+++ b/server/frontend/vite.config.ts
@@ -0,0 +1,12 @@
+import { defineConfig } from 'vite';
+import solidPlugin from 'vite-plugin-solid';
+
+export default defineConfig({
+ plugins: [solidPlugin()],
+ server: {
+ port: 3000,
+ },
+ build: {
+ target: 'esnext',
+ },
+});
diff --git a/server/swagger/build.gradle.kts b/server/swagger/build.gradle.kts
new file mode 100644
index 0000000..76e5f78
--- /dev/null
+++ b/server/swagger/build.gradle.kts
@@ -0,0 +1,17 @@
+plugins {
+ `java-library`
+ kotlin("jvm")
+ kotlin("plugin.serialization")
+}
+
+
+dependencies {
+ declareKtorVersion()
+ api("io.ktor:ktor-server-core")
+ api("sh.ondr:kotlin-json-schema:0.1.1")
+ implementation("org.webjars:swagger-ui:5.18.2")
+}
+
+java {
+ toolchain.languageVersion.set(JavaLanguageVersion.of(21))
+}
diff --git a/server/swagger/src/main/kotlin/moe/nea/ledger/server/core/api/OpenApiModel.kt b/server/swagger/src/main/kotlin/moe/nea/ledger/server/core/api/OpenApiModel.kt
new file mode 100644
index 0000000..8392c5c
--- /dev/null
+++ b/server/swagger/src/main/kotlin/moe/nea/ledger/server/core/api/OpenApiModel.kt
@@ -0,0 +1,116 @@
+package moe.nea.ledger.server.core.api
+
+import io.ktor.http.ContentType
+import io.ktor.http.HttpStatusCode
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import sh.ondr.jsonschema.JsonSchema
+
+@Serializable
+data class OpenApiModel(
+ val openapi: String = "3.0.0",
+ val info: Info,
+ val servers: List<Server>,
+ val paths: Map<OpenApiPath, OpenApiRoute>,
+)
+
+@Serializable // TODO: custom serializer
+@JvmInline
+value class OpenApiPath(val name: String)
+
+@Serializable
+data class OpenApiRoute(
+ val summary: String,
+ val description: String,
+ val get: OpenApiOperation?,
+ val put: OpenApiOperation?,
+ val patch: OpenApiOperation?,
+ val post: OpenApiOperation?,
+ val delete: OpenApiOperation?,
+)
+
+@Serializable
+data class OpenApiOperation(
+ val tags: List<Tag>,
+ val summary: String,
+ val description: String,
+ val operationId: String,
+ val deprecated: Boolean,
+ val parameters: List<OpenApiParameter>,
+ val responses: Map<@Serializable(HttpStatusCodeIntAsString::class) HttpStatusCode, OpenApiResponse>
+)
+
+@Serializable
+data class OpenApiParameter(
+ @SerialName("in") val location: ParameterLocation,
+ val name: String,
+ val description: String,
+ val schema: JsonSchema?,
+)
+
+@Serializable
+enum class ParameterLocation {
+ @SerialName("query")
+ QUERY,
+ @SerialName("path")
+ PATH,
+}
+
+object HttpStatusCodeIntAsString : KSerializer<HttpStatusCode> {
+ override val descriptor: SerialDescriptor =
+ PrimitiveSerialDescriptor("HttpStatusCodeIntAsString", PrimitiveKind.STRING)
+
+ override fun deserialize(decoder: Decoder): HttpStatusCode {
+ return HttpStatusCode.fromValue(decoder.decodeString().toInt())
+ }
+
+ override fun serialize(encoder: Encoder, value: HttpStatusCode) {
+ encoder.encodeString(value.value.toString())
+ }
+}
+
+object ContentTypeSerializer : KSerializer<ContentType> {
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ContentTypeSerializer", PrimitiveKind.STRING)
+
+ override fun deserialize(decoder: Decoder): ContentType {
+ return ContentType.parse(decoder.decodeString())
+ }
+
+ override fun serialize(encoder: Encoder, value: ContentType) {
+ encoder.encodeString(value.contentType + "/" + value.contentSubtype)
+ }
+}
+
+@Serializable
+data class OpenApiResponse(
+ val description: String,
+ val content: Map<@Serializable(ContentTypeSerializer::class) ContentType, OpenApiResponseContentType>
+)
+
+@Serializable
+data class OpenApiResponseContentType(
+ val schema: JsonSchema?
+)
+
+@Serializable
+@JvmInline
+value class Tag(val name: String)
+
+@Serializable
+data class Info(
+ val title: String,
+ val description: String,
+ val version: String,
+)
+
+@Serializable
+data class Server(
+ val url: String,
+ val description: String,
+)
diff --git a/server/swagger/src/main/kotlin/moe/nea/ledger/server/core/api/docs.kt b/server/swagger/src/main/kotlin/moe/nea/ledger/server/core/api/docs.kt
new file mode 100644
index 0000000..c1b550d
--- /dev/null
+++ b/server/swagger/src/main/kotlin/moe/nea/ledger/server/core/api/docs.kt
@@ -0,0 +1,323 @@
+package moe.nea.ledger.server.core.api
+
+import io.ktor.http.ContentType
+import io.ktor.http.HttpMethod
+import io.ktor.http.HttpStatusCode
+import io.ktor.http.content.OutgoingContent
+import io.ktor.http.defaultForFilePath
+import io.ktor.server.application.ApplicationCallPipeline
+import io.ktor.server.application.BaseApplicationPlugin
+import io.ktor.server.application.host
+import io.ktor.server.application.port
+import io.ktor.server.response.respond
+import io.ktor.server.response.respondText
+import io.ktor.server.routing.HttpMethodRouteSelector
+import io.ktor.server.routing.PathSegmentConstantRouteSelector
+import io.ktor.server.routing.RootRouteSelector
+import io.ktor.server.routing.Route
+import io.ktor.server.routing.RoutingNode
+import io.ktor.server.routing.TrailingSlashRouteSelector
+import io.ktor.server.routing.get
+import io.ktor.server.routing.route
+import io.ktor.util.AttributeKey
+import io.ktor.util.cio.KtorDefaultPool
+import io.ktor.utils.io.ByteReadChannel
+import io.ktor.utils.io.jvm.javaio.toByteReadChannel
+import kotlinx.serialization.json.JsonPrimitive
+import sh.ondr.jsonschema.JsonSchema
+import sh.ondr.jsonschema.jsonSchema
+import java.io.File
+import java.io.InputStream
+
+
+fun Route.openApiDocsJson() {
+ get {
+ val docs = plugin(Documentation)
+ val model = docs.finalizeJson()
+ call.respond(model)
+ }
+}
+
+fun Route.openApiUi(apiJsonUrl: String) {
+ get("swagger-initializer.js") {
+ call.respondText(
+ //language=JavaScript
+ """
+ window.onload = function() {
+ //<editor-fold desc="Changeable Configuration Block">
+
+ // the following lines will be replaced by docker/configurator, when it runs in a docker-container
+ window.ui = SwaggerUIBundle({
+ url: ${JsonPrimitive(apiJsonUrl)},
+ dom_id: '#swagger-ui',
+ deepLinking: true,
+ presets: [
+ SwaggerUIBundle.presets.apis,
+ SwaggerUIStandalonePreset
+ ],
+ plugins: [
+ SwaggerUIBundle.plugins.DownloadUrl
+ ],
+ layout: "StandaloneLayout"
+ });
+
+ //</editor-fold>
+ };
+ """.trimIndent())
+ }
+// val swaggerUiProperties =
+// environment.classLoader.getResource("/META-INF/maven/org.webjars/swagger-ui/pom.properties")
+// ?: error("Could not find swagger webjar")
+// val swaggerUiZip = swaggerUiProperties.toString().substringBefore("!")
+ val pathParameterName = "static-content-path-parameter"
+ route("{$pathParameterName...}") {
+ get {
+ var requestedPath = call.parameters.getAll(pathParameterName)?.joinToString(File.separator) ?: ""
+ requestedPath = requestedPath.replace("\\", "/")
+ if (requestedPath.isEmpty()) requestedPath = "index.html"
+ if (requestedPath.contains("..")) {
+ call.respondText("Forbidden", status = HttpStatusCode.Forbidden)
+ return@get
+ }
+ //TODO: I mean i should read out the version properties but idc
+ val version = "5.18.2"
+ val resource =
+ environment.classLoader.getResourceAsStream("META-INF/resources/webjars/swagger-ui/$version/$requestedPath")
+
+ if (resource == null) {
+ call.respondText("Not Found", status = HttpStatusCode.NotFound)
+ return@get
+ }
+
+ call.respond(InputStreamContent(resource, ContentType.defaultForFilePath(requestedPath)))
+ }
+ }
+}
+
+internal class InputStreamContent(
+ private val input: InputStream,
+ override val contentType: ContentType
+) : OutgoingContent.ReadChannelContent() {
+
+ override fun readFrom(): ByteReadChannel = input.toByteReadChannel(pool = KtorDefaultPool)
+}
+
+class DocumentationPath(val path: String)
+
+class DocumentationEndpoint private constructor() {
+ var method: HttpMethod = HttpMethod.Get
+ private set
+ lateinit var path: DocumentationPath
+ private set
+
+ private fun initFromPath(
+ baseRoute: Route?,
+ route: RoutingNode,
+ ) {
+ path = DocumentationPath(createRoutePath(baseRoute, route))
+ }
+
+ private fun createRoutePath(
+ baseRoute: Route?,
+ route: RoutingNode,
+ ): String {
+ if (baseRoute == route)
+ return "/"
+ val parent = route.parent
+ if (parent == null) {
+ if (baseRoute != null)
+ error("Could not find $route in $baseRoute")
+ return "/"
+ }
+ val parentPath = createRoutePath(baseRoute, parent)
+ var parentPathAppendable = parentPath
+ if (!parentPathAppendable.endsWith("/"))
+ parentPathAppendable += "/"
+ return when (val selector = route.selector) {
+ is TrailingSlashRouteSelector -> parentPathAppendable
+ is RootRouteSelector -> parentPath
+ is PathSegmentConstantRouteSelector -> parentPathAppendable + selector.value
+ is HttpMethodRouteSelector -> {
+ method = selector.method
+ parentPath
+ }
+
+ else -> error("Could not comprehend $selector (${selector.javaClass})")
+ }
+ }
+
+ companion object {
+ fun createDocumentationPath(baseRoute: Route?, route: Route): DocumentationEndpoint {
+ val path = DocumentationEndpoint()
+ path.initFromPath(baseRoute, route as RoutingNode)
+ return path
+ }
+ }
+}
+
+class Response {
+ var schema: JsonSchema? = null
+ inline fun <reified T : Any> schema() {
+ schema = jsonSchema<T>()
+ }
+
+ fun intoJson(): OpenApiResponse {
+ return OpenApiResponse(
+ "",
+ mapOf(
+ ContentType.Application.Json to OpenApiResponseContentType(schema)
+ )
+ )
+ }
+}
+
+interface IntoTag {
+ fun intoTag(): String
+}
+
+class DocumentationOperationContext(val route: DocumentationContext) {
+ val responses = mutableMapOf<HttpStatusCode, Response>()
+ fun responds(statusCode: HttpStatusCode, block: Response.() -> Unit) {
+ responses.getOrPut(statusCode) { Response() }.also(block)
+ }
+
+ fun respondsOk(block: Response.() -> Unit) {
+ responds(HttpStatusCode.OK, block)
+ }
+
+ var summary: String = ""
+ var description: String = ""
+ var deprecated: Boolean = false
+ var operationId: String = ""
+ val tags: MutableList<String> = mutableListOf()
+ val parameters: MutableList<OpenApiParameter> = mutableListOf()
+ fun tag(vararg tag: String) {
+ tags.addAll(tag)
+ }
+
+ fun tag(vararg tag: IntoTag) {
+ tag.mapTo(tags) { it.intoTag() }
+ }
+
+ inline fun <reified T : Any> queryParameter(name: String, description: String = "") {
+ parameter(ParameterLocation.QUERY, name, description, jsonSchema<T>())
+ }
+
+ fun parameter(
+ location: ParameterLocation, name: String,
+ description: String = "", schema: JsonSchema? = null
+ ) {
+ parameters.add(OpenApiParameter(
+ location, name, description,
+ schema
+ ))
+ }
+
+ fun intoJson(): OpenApiOperation {
+ return OpenApiOperation(
+ tags = tags.map { Tag(it) },
+ summary = summary,
+ description = description,
+ operationId = operationId,
+ deprecated = deprecated,
+ parameters = parameters,
+ responses = responses.mapValues {
+ it.value.intoJson()
+ }
+ )
+
+ }
+}
+
+class DocumentationContext(val path: DocumentationPath) {
+ val ops: MutableMap<HttpMethod, DocumentationOperationContext> = mutableMapOf()
+ var summary: String = ""
+ var description = ""
+ fun intoJson(): OpenApiRoute {
+ return OpenApiRoute(
+ summary,
+ description,
+ get = ops[HttpMethod.Get]?.intoJson(),
+ put = ops[HttpMethod.Put]?.intoJson(),
+ post = ops[HttpMethod.Post]?.intoJson(),
+ patch = ops[HttpMethod.Patch]?.intoJson(),
+ delete = ops[HttpMethod.Delete]?.intoJson(),
+ )
+ }
+
+ fun createOperationNode(method: HttpMethod): DocumentationOperationContext {
+ return ops.getOrPut(method) { DocumentationOperationContext(this) }
+ }
+}
+
+
+class Documentation(config: Configuration) {
+ companion object Plugin : BaseApplicationPlugin<ApplicationCallPipeline, Configuration, Documentation> {
+ override val key: AttributeKey<Documentation> = AttributeKey("LedgerDocumentation")
+
+ override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): Documentation {
+ val config = Configuration().also(configure)
+ if (config.servers.isEmpty()) {
+ config.servers.add(Server(
+ "http://${pipeline.environment.config.host}:${pipeline.environment.config.port}",
+ "Server",
+ ))
+ }
+ val plugin = Documentation(config)
+ return plugin
+ }
+ }
+
+ val info = config.info
+ var root: RoutingNode? = null
+ private set
+ val servers: List<Server> = config.servers
+
+ private val documentationNodes = mutableMapOf<DocumentationPath, DocumentationContext>()
+ fun createDocumentationNode(endpoint: DocumentationEndpoint) =
+ documentationNodes.getOrPut(endpoint.path) { DocumentationContext(endpoint.path) }
+ .createOperationNode(endpoint.method)
+
+ private val openApiJson by lazy {
+ OpenApiModel(
+ info = info,
+ servers = servers,
+ paths = documentationNodes.map {
+ OpenApiPath(it.key.path) to it.value.intoJson()
+ }.toMap()
+ )
+ }
+
+ fun finalizeJson(): OpenApiModel {
+ return openApiJson
+ }
+
+ fun setRootNode(routingNode: RoutingNode) {
+ require(documentationNodes.isEmpty()) { "Cannot set API root node after routes have been documented: ${documentationNodes.keys}" }
+ this.root = routingNode
+ }
+
+ class Configuration {
+ var info: Info = Info(
+ title = "Example API Docs",
+ description = "Missing description",
+ version = "0.0.0"
+ )
+ val servers: MutableList<Server> = mutableListOf()
+ }
+}
+
+fun Route.docs(block: DocumentationOperationContext.() -> Unit) {
+ val documentation = plugin(Documentation)
+ val documentationPath = DocumentationEndpoint.createDocumentationPath(documentation.root, this)
+ val node = documentation.createDocumentationNode(documentationPath)
+ block(node)
+}
+
+/**
+ * Mark this current routing node as API route. Note that this will not apply retroactively and all api requests must be declared relative to this one.
+ */
+fun Route.setApiRoot() {
+ plugin(Documentation).setRootNode(this as RoutingNode)
+}
+
diff --git a/settings.gradle.kts b/settings.gradle.kts
index d34446f..949cbe4 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -31,5 +31,8 @@ include("database:core")
include("database:impl")
include("basetypes")
include("mod")
+include("server:swagger")
include("server:core")
+include("server:frontend")
+include("server:aio")
includeBuild("build-src")