diff --git a/.bruno/bruno.json b/.bruno/bruno.json
new file mode 100644
index 0000000..b2c9a3a
--- /dev/null
+++ b/.bruno/bruno.json
@@ -0,0 +1,9 @@
+{
+ "version": "1",
+ "name": "Valkyr",
+ "type": "collection",
+ "ignore": [
+ "node_modules",
+ ".git"
+ ]
+}
\ No newline at end of file
diff --git a/.bruno/environments/localhost.bru b/.bruno/environments/localhost.bru
new file mode 100644
index 0000000..1f2c42d
--- /dev/null
+++ b/.bruno/environments/localhost.bru
@@ -0,0 +1,3 @@
+vars {
+ url: http://localhost:8370/api/v1
+}
diff --git a/.bruno/identity/Get.bru b/.bruno/identity/Get.bru
new file mode 100644
index 0000000..1637c3d
--- /dev/null
+++ b/.bruno/identity/Get.bru
@@ -0,0 +1,19 @@
+meta {
+ name: Get
+ type: http
+ seq: 2
+}
+
+get {
+ url: {{url}}/identity/:id
+ body: none
+ auth: inherit
+}
+
+params:path {
+ id:
+}
+
+settings {
+ encodeUrl: true
+}
diff --git a/.bruno/identity/Roles.bru b/.bruno/identity/Roles.bru
new file mode 100644
index 0000000..d658976
--- /dev/null
+++ b/.bruno/identity/Roles.bru
@@ -0,0 +1,32 @@
+meta {
+ name: Roles
+ type: http
+ seq: 4
+}
+
+put {
+ url: {{url}}/identity/:id/roles
+ body: json
+ auth: inherit
+}
+
+params:path {
+ id:
+}
+
+body:json {
+ [
+ {
+ "type": "add",
+ "roles": []
+ },
+ {
+ "type": "remove",
+ "roles": []
+ }
+ ]
+}
+
+settings {
+ encodeUrl: true
+}
diff --git a/.bruno/identity/Update.bru b/.bruno/identity/Update.bru
new file mode 100644
index 0000000..552cde0
--- /dev/null
+++ b/.bruno/identity/Update.bru
@@ -0,0 +1,43 @@
+meta {
+ name: Update
+ type: http
+ seq: 4
+}
+
+put {
+ url: {{url}}/identity/:id
+ body: json
+ auth: inherit
+}
+
+params:path {
+ id:
+}
+
+body:json {
+ [
+ {
+ "type": "add",
+ "key": "",
+ "value": ""
+ },
+ {
+ "type": "push",
+ "key": "",
+ "values": ""
+ },
+ {
+ "type": "pop",
+ "key": "",
+ "values": ""
+ },
+ {
+ "type": "remove",
+ "key": ""
+ }
+ ]
+}
+
+settings {
+ encodeUrl: true
+}
diff --git a/.bruno/identity/folder.bru b/.bruno/identity/folder.bru
new file mode 100644
index 0000000..a89bdf5
--- /dev/null
+++ b/.bruno/identity/folder.bru
@@ -0,0 +1,8 @@
+meta {
+ name: Identity
+ seq: 1
+}
+
+auth {
+ mode: inherit
+}
diff --git a/.bruno/identity/login/code.bru b/.bruno/identity/login/code.bru
new file mode 100644
index 0000000..e96d53b
--- /dev/null
+++ b/.bruno/identity/login/code.bru
@@ -0,0 +1,29 @@
+meta {
+ name: Code
+ type: http
+ seq: 3
+}
+
+post {
+ url: {{url}}/identity/login/code
+ body: json
+ auth: inherit
+}
+
+body:json {
+ {
+ "email": "john.doe@fixture.none",
+ "otp": ""
+ }
+}
+
+script:post-response {
+ const cookies = res.getHeader('set-cookie');
+ if (cookies) {
+ bru.setVar("cookie", cookies.join('; '));
+ }
+}
+
+settings {
+ encodeUrl: true
+}
diff --git a/.bruno/identity/login/email.bru b/.bruno/identity/login/email.bru
new file mode 100644
index 0000000..7025c71
--- /dev/null
+++ b/.bruno/identity/login/email.bru
@@ -0,0 +1,21 @@
+meta {
+ name: Email
+ type: http
+ seq: 2
+}
+
+post {
+ url: {{url}}/identity/login/email
+ body: json
+ auth: inherit
+}
+
+body:json {
+ {
+ "email": "john.doe@fixture.none"
+ }
+}
+
+settings {
+ encodeUrl: true
+}
diff --git a/.bruno/identity/login/folder.bru b/.bruno/identity/login/folder.bru
new file mode 100644
index 0000000..9b12963
--- /dev/null
+++ b/.bruno/identity/login/folder.bru
@@ -0,0 +1,8 @@
+meta {
+ name: Login
+ seq: 3
+}
+
+auth {
+ mode: inherit
+}
diff --git a/.bruno/identity/login/sudo.bru b/.bruno/identity/login/sudo.bru
new file mode 100644
index 0000000..21f8712
--- /dev/null
+++ b/.bruno/identity/login/sudo.bru
@@ -0,0 +1,21 @@
+meta {
+ name: Sudo
+ type: http
+ seq: 1
+}
+
+post {
+ url: {{url}}/identities/login/sudo
+ body: json
+ auth: inherit
+}
+
+body:json {
+ {
+ "email": "john.doe@fixture.none"
+ }
+}
+
+settings {
+ encodeUrl: true
+}
diff --git a/.bruno/identity/me.bru b/.bruno/identity/me.bru
new file mode 100644
index 0000000..58fdd69
--- /dev/null
+++ b/.bruno/identity/me.bru
@@ -0,0 +1,15 @@
+meta {
+ name: Me
+ type: http
+ seq: 1
+}
+
+get {
+ url: {{url}}/identity/me
+ body: none
+ auth: inherit
+}
+
+settings {
+ encodeUrl: true
+}
diff --git a/.bruno/workspace/create.bru b/.bruno/workspace/create.bru
new file mode 100644
index 0000000..10b7829
--- /dev/null
+++ b/.bruno/workspace/create.bru
@@ -0,0 +1,21 @@
+meta {
+ name: Create
+ type: http
+ seq: 1
+}
+
+post {
+ url: {{url}}/workspace
+ body: json
+ auth: inherit
+}
+
+body:json {
+ {
+ "name": ""
+ }
+}
+
+settings {
+ encodeUrl: true
+}
diff --git a/.bruno/workspace/folder.bru b/.bruno/workspace/folder.bru
new file mode 100644
index 0000000..46b85fa
--- /dev/null
+++ b/.bruno/workspace/folder.bru
@@ -0,0 +1,8 @@
+meta {
+ name: Workspace
+ seq: 2
+}
+
+auth {
+ mode: inherit
+}
diff --git a/.gitignore b/.gitignore
index b512c09..7625c09 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
+.volumes
node_modules
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index b2bb4f6..a6ffc5d 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,10 +1,19 @@
{
+ "biome.enabled": true,
"deno.enable": true,
+ "deno.lint": false,
+ "editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
- "source.fixAll.eslint": "explicit"
+ "source.organizeImports.biome": "explicit",
+ "source.fixAll.biome": "explicit"
+ },
+ "files.exclude": {
+ "**/.git": true,
+ "**/.svn": true,
+ "**/.hg": true,
+ "**/CVS": true,
+ "**/.DS_Store": true,
+ "**/Thumbs.db": true
},
- "[typescript]": {
- "editor.defaultFormatter": "esbenp.prettier-vscode"
- }
}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..398d47c
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,17 @@
+FROM denoland/deno:2.5.1
+
+ENV TZ=UTC
+ENV PORT=8370
+
+EXPOSE 8370
+
+WORKDIR /app
+
+COPY api/ ./api/
+COPY relay/ ./relay/
+COPY .npmrc .
+COPY deno-docker.json ./deno.json
+
+RUN deno install --allow-scripts
+
+CMD ["sh", "-c", "deno run --allow-all ./api/.tasks/migrate.ts && deno run --allow-all ./api/server.ts"]
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index ed5c97f..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,16 +0,0 @@
-MIT License
-
-Copyright 2025 Christoffer Rødvik.
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the
-Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
-Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-
-Commercial use is permitted, provided the Software is not sold, relicensed, or distributed as a stand-alone solution, whether in original or minimally modified form.
-Use as part of a larger work, integrated product, or service is allowed.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
-PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
index a9c7246..bd2198e 100644
--- a/README.md
+++ b/README.md
@@ -1,87 +1 @@
-
-
-
-
-# Relay
-
-Relay is a full stack protocol for communicating between client and server. It is also built around the major HTTP methods allowing for creating public API endpoints.
-
-## Quick Start
-
-For this quick start guide we assume the following project setup:
-
-```
- api/
- relay/
- web/
-```
-
-### Relay
-
-First we want to set up our relay space, from the structure above lets start by defining our route.
-
-```ts
-import { route } from "@valkyr/relay";
-import z from "zod";
-
-export default route
- .post("/users")
- .body(
- z.object({
- name: z.string(),
- email: z.string().check(z.email()),
- })
- )
- .response(z.string());
-```
-
-After creating our first route we mount it onto our relay instance.
-
-```ts
-import { Relay } from "@valkyr/relay";
-
-import route from "./path/to/route.ts";
-
-export const relay = new Relay([
- route
-]);
-```
-
-We have now finished defining our initial relay setup which we can now utilize in our `api` and `web` spaces.
-
-### API
-
-To be able to successfully execute our user create route we need to attach a handler in our `api`. Lets start off by defining our handler.
-
-```ts
-import { UnprocessableContentError } from "@valkyr/relay";
-
-import { relay } from "~project/relay/mod.ts";
-
-relay
- .route("POST", "/users")
- .handle(async ({ name, email }) => {
- const user = await db.users.insert({ name, email });
- if (user === undefined) {
- return new UnprocessableContentError();
- }
- return user.id;
- });
-```
-
-We now have a `POST` handler for the `/users` path.
-
-### Web
-
-Now that we have both our relay and api ready to recieve requests we can trigger a user creation request in our web application.
-
-```ts
-import { relay } from "~project/relay/mod.ts"
-
-const userId = await relay.post("/users", {
- name: "John Doe",
- email: "john.doe@fixture.none"
-});
-
-console.log(userId); // => string
-```
+# Boilerplate
\ No newline at end of file
diff --git a/adapters/http.ts b/adapters/http.ts
deleted file mode 100644
index 17a7a6c..0000000
--- a/adapters/http.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { RequestInput } from "../libraries/relay.ts";
-import { RelayAdapter } from "../mod.ts";
-
-export const http: RelayAdapter = {
- async fetch({ method, url, search, body }: RequestInput) {
- const res = await fetch(`${url}${search}`, { method, body });
- const data = await res.text();
- if (res.status >= 400) {
- throw new Error(data);
- }
- if (res.headers.get("content-type")?.includes("json")) {
- return JSON.parse(data);
- }
- return data;
- },
-};
diff --git a/api/config.ts b/api/config.ts
new file mode 100644
index 0000000..7cfec2a
--- /dev/null
+++ b/api/config.ts
@@ -0,0 +1,12 @@
+import { getEnvironmentVariable } from "@platform/config";
+import z from "zod";
+
+export const config = {
+ name: "@valkyr/boilerplate",
+ host: getEnvironmentVariable({ key: "API_HOST", type: z.ipv4(), fallback: "0.0.0.0" }),
+ port: getEnvironmentVariable({
+ key: "API_PORT",
+ type: z.coerce.number(),
+ fallback: "8370",
+ }),
+};
diff --git a/api/package.json b/api/package.json
new file mode 100644
index 0000000..9cd602a
--- /dev/null
+++ b/api/package.json
@@ -0,0 +1,12 @@
+{
+ "private": true,
+ "scripts": {
+ "start": "deno --allow-all --watch-hmr=routes/ server.ts"
+ },
+ "dependencies": {
+ "@modules/identity": "workspace:*",
+ "@module/workspace": "workspace:*",
+ "@platform/config": "workspace:*",
+ "zod": "4.1.12"
+ }
+}
diff --git a/api/server.ts b/api/server.ts
new file mode 100644
index 0000000..060ded3
--- /dev/null
+++ b/api/server.ts
@@ -0,0 +1,73 @@
+import { logger } from "@platform/logger";
+import { context } from "@platform/relay";
+import { Api } from "@platform/server/api.ts";
+import server from "@platform/server/server.ts";
+import socket from "@platform/socket/server.ts";
+import { storage } from "@platform/storage";
+
+import { config } from "./config.ts";
+import session from "./session.ts";
+
+const log = logger.prefix("Server");
+
+/*
+ |--------------------------------------------------------------------------------
+ | Bootstrap
+ |--------------------------------------------------------------------------------
+ */
+
+// ### Platform
+
+await server.bootstrap();
+await socket.bootstrap();
+await session.bootstrap();
+
+// ### Modules
+
+// await workspace.bootstrap();
+
+/*
+ |--------------------------------------------------------------------------------
+ | Service
+ |--------------------------------------------------------------------------------
+ */
+
+const api = new Api([
+ /*...identity.routes, ...workspace.routes*/
+]);
+
+/*
+ |--------------------------------------------------------------------------------
+ | Server
+ |--------------------------------------------------------------------------------
+ */
+
+Deno.serve(
+ {
+ port: config.port,
+ hostname: config.host,
+ onListen({ port, hostname }) {
+ logger.prefix("Server").info(`Listening at http://${hostname}:${port}`);
+ },
+ },
+ async (request) =>
+ storage.run({}, async () => {
+ const url = new URL(request.url);
+
+ // ### Storage Context
+ // Resolve storage context for all dependent modules.
+
+ await server.resolve(request);
+ await socket.resolve();
+ await session.resolve(request);
+
+ // ### Fetch
+ // Execute fetch against the api instance.
+
+ return api.fetch(request).finally(() => {
+ log.info(
+ `${request.method} ${url.pathname} [${((Date.now() - context.info.start) / 1000).toLocaleString()} seconds]`,
+ );
+ });
+ }),
+);
diff --git a/api/session.ts b/api/session.ts
new file mode 100644
index 0000000..bba990d
--- /dev/null
+++ b/api/session.ts
@@ -0,0 +1,111 @@
+import { context, UnauthorizedError } from "@platform/relay";
+import { storage } from "@platform/storage";
+
+const IDENTITY_RESOLVE_HEADER = "x-identity-resolver";
+
+export default {
+ bootstrap: async () => {
+ bootstrapSessionContext();
+ },
+
+ resolve: async (request: Request) => {
+ await resolvePrincipalSession(request);
+ },
+};
+
+function bootstrapSessionContext() {
+ Object.defineProperties(context, {
+ /**
+ * TODO ...
+ */
+ isAuthenticated: {
+ get() {
+ return storage.getStore()?.principal !== undefined;
+ },
+ },
+
+ /**
+ * TODO ...
+ */
+ session: {
+ get() {
+ const session = storage.getStore()?.session;
+ if (session === undefined) {
+ throw new UnauthorizedError();
+ }
+ return session;
+ },
+ },
+
+ /**
+ * TODO ...
+ */
+ principal: {
+ get() {
+ const principal = storage.getStore()?.principal;
+ if (principal === undefined) {
+ throw new UnauthorizedError();
+ }
+ return principal;
+ },
+ },
+
+ /**
+ * TODO ...
+ */
+ access: {
+ get() {
+ const access = storage.getStore()?.access;
+ if (access === undefined) {
+ throw new UnauthorizedError();
+ }
+ return access;
+ },
+ },
+ });
+}
+
+async function resolvePrincipalSession(request: Request) {
+ // ### Resolver
+ // Check if the incoming request is tagged as a resolver check.
+ // If it is a resolver we break out of the session resolution
+ // to avoid an infinite resolution loop.
+
+ const isResolver = request.headers.get(IDENTITY_RESOLVE_HEADER) !== null;
+ if (isResolver) {
+ return;
+ }
+
+ // ### Cookie
+ // Check for the existence of cookie to pass onto the session
+ // resolver.
+
+ const cookie = request.headers.get("cookie");
+ if (cookie === null) {
+ return;
+ }
+
+ // ### Session
+ // Fetch session from identity module and tag it as a resolution
+ // call so it can break out of a resolution loop.
+
+ const session = await getPrincipalSession({
+ headers: new Headers({
+ cookie,
+ [IDENTITY_RESOLVE_HEADER]: "true",
+ }),
+ });
+
+ // ### Populate Context
+ // On successfull resolution we build the request identity context.
+
+ if (session !== undefined) {
+ const context = storage.getStore();
+ if (context === undefined) {
+ return;
+ }
+ context.session = session.session;
+ context.principal = session.principal;
+ context.access = identity.access;
+ }
+}
diff --git a/apps/README.md b/apps/README.md
new file mode 100644
index 0000000..c7782ba
--- /dev/null
+++ b/apps/README.md
@@ -0,0 +1 @@
+# Apps
\ No newline at end of file
diff --git a/apps/react/.gitignore b/apps/react/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/apps/react/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/apps/react/.npmrc b/apps/react/.npmrc
new file mode 100644
index 0000000..41583e3
--- /dev/null
+++ b/apps/react/.npmrc
@@ -0,0 +1 @@
+@jsr:registry=https://npm.jsr.io
diff --git a/apps/react/README.md b/apps/react/README.md
new file mode 100644
index 0000000..7959ce4
--- /dev/null
+++ b/apps/react/README.md
@@ -0,0 +1,69 @@
+# React + TypeScript + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
+
+```js
+export default tseslint.config([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ // Other configs...
+
+ // Remove tseslint.configs.recommended and replace with this
+ ...tseslint.configs.recommendedTypeChecked,
+ // Alternatively, use this for stricter rules
+ ...tseslint.configs.strictTypeChecked,
+ // Optionally, add this for stylistic rules
+ ...tseslint.configs.stylisticTypeChecked,
+
+ // Other configs...
+ ],
+ languageOptions: {
+ parserOptions: {
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ // other options...
+ },
+ },
+])
+```
+
+You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
+
+```js
+// eslint.config.js
+import reactX from 'eslint-plugin-react-x'
+import reactDom from 'eslint-plugin-react-dom'
+
+export default tseslint.config([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ // Other configs...
+ // Enable lint rules for React
+ reactX.configs['recommended-typescript'],
+ // Enable lint rules for React DOM
+ reactDom.configs.recommended,
+ ],
+ languageOptions: {
+ parserOptions: {
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ // other options...
+ },
+ },
+])
+```
diff --git a/apps/react/components.json b/apps/react/components.json
new file mode 100644
index 0000000..c3a0a52
--- /dev/null
+++ b/apps/react/components.json
@@ -0,0 +1,22 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": false,
+ "tsx": true,
+ "tailwind": {
+ "config": "tailwind.config.js",
+ "css": "src/index.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "iconLibrary": "lucide",
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/libraries/utils",
+ "ui": "@/components/ui",
+ "lib": "@/libraries",
+ "hooks": "@/hooks"
+ },
+ "registries": {}
+}
diff --git a/apps/react/index.html b/apps/react/index.html
new file mode 100644
index 0000000..f9f08cc
--- /dev/null
+++ b/apps/react/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+ Vite + React + TS
+
+
+
+
+
+
diff --git a/apps/react/package.json b/apps/react/package.json
new file mode 100644
index 0000000..396ec5d
--- /dev/null
+++ b/apps/react/package.json
@@ -0,0 +1,56 @@
+{
+ "name": "react",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@module/account": "workspace:*",
+ "@platform/relay": "workspace:*",
+ "@platform/spec": "workspace:*",
+ "@radix-ui/react-avatar": "^1.1.11",
+ "@radix-ui/react-dialog": "^1.1.15",
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
+ "@radix-ui/react-scroll-area": "^1.2.10",
+ "@radix-ui/react-separator": "^1.1.8",
+ "@radix-ui/react-slot": "^1.2.4",
+ "@radix-ui/react-tooltip": "^1.2.8",
+ "@tabler/icons-react": "3.35.0",
+ "@tanstack/react-query": "5.89.0",
+ "@tanstack/react-router": "1.131.47",
+ "@valkyr/db": "npm:@jsr/valkyr__db@2.0.0",
+ "@valkyr/event-emitter": "npm:@jsr/valkyr__event-emitter@1.0.1",
+ "@zitadel/react": "1.1.0",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "fast-equals": "5.2.2",
+ "lucide-react": "^0.554.0",
+ "react": "19.1.1",
+ "react-dom": "19.1.1",
+ "tailwind-merge": "^3.4.0",
+ "tailwindcss": "4.1.13",
+ "tailwindcss-animate": "^1.0.7",
+ "tw-animate-css": "1.4.0",
+ "zod": "4.1.12"
+ },
+ "devDependencies": {
+ "@eslint/js": "9.35.0",
+ "@tailwindcss/vite": "4.1.13",
+ "@tanstack/react-router-devtools": "1.131.47",
+ "@types/react": "19.1.13",
+ "@types/react-dom": "19.1.9",
+ "@vitejs/plugin-react": "4.7.0",
+ "eslint": "9.35.0",
+ "eslint-plugin-react-hooks": "5.2.0",
+ "eslint-plugin-react-refresh": "0.4.20",
+ "globals": "16.4.0",
+ "typescript": "5.9.2",
+ "typescript-eslint": "8.44.0",
+ "vite": "7.1.6"
+ }
+}
diff --git a/apps/react/public/vite.svg b/apps/react/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/apps/react/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/react/src/adapters/http.ts b/apps/react/src/adapters/http.ts
new file mode 100644
index 0000000..33fa288
--- /dev/null
+++ b/apps/react/src/adapters/http.ts
@@ -0,0 +1,273 @@
+import {
+ assertServerErrorResponse,
+ type RelayAdapter,
+ type RelayInput,
+ type RelayResponse,
+ ServerError,
+ type ServerErrorResponse,
+ type ServerErrorType,
+} from "@platform/relay";
+
+/**
+ * HttpAdapter provides a unified transport layer for Relay.
+ *
+ * It supports sending JSON objects, nested structures, arrays, and file uploads
+ * via FormData. The adapter automatically detects the payload type and formats
+ * the request accordingly. Responses are normalized into `RelayResponse`.
+ *
+ * @example
+ * ```ts
+ * const adapter = new HttpAdapter({ url: "https://api.example.com" });
+ *
+ * // Sending JSON data
+ * const jsonResponse = await adapter.send({
+ * method: "POST",
+ * endpoint: "/users",
+ * body: { name: "Alice", age: 30 },
+ * });
+ *
+ * // Sending files and nested objects
+ * const formResponse = await adapter.send({
+ * method: "POST",
+ * endpoint: "/upload",
+ * body: {
+ * user: { name: "Bob", avatar: fileInput.files[0] },
+ * documents: [fileInput.files[1], fileInput.files[2]],
+ * },
+ * });
+ * ```
+ */
+export class HttpAdapter implements RelayAdapter {
+ /**
+ * Instantiate a new HttpAdapter instance.
+ *
+ * @param options - Adapter options.
+ */
+ constructor(readonly options: HttpAdapterOptions) {}
+
+ /**
+ * Override the initial url value set by instantiator.
+ */
+ set url(value: string) {
+ this.options.url = value;
+ }
+
+ /**
+ * Retrieve the URL value from options object.
+ */
+ get url() {
+ return this.options.url;
+ }
+
+ /**
+ * Return the full URL from given endpoint.
+ *
+ * @param endpoint - Endpoint to get url for.
+ */
+ getUrl(endpoint: string): string {
+ return `${this.url}${endpoint}`;
+ }
+
+ async send({ method, endpoint, query, body, headers = new Headers() }: RelayInput): Promise {
+ const init: RequestInit = { method, headers };
+
+ // ### Before Request
+ // If any before request hooks has been defined, we run them here passing in the
+ // request headers for further modification.
+
+ await this.#beforeRequest(headers);
+
+ // ### Body
+
+ if (body !== undefined) {
+ const type = this.#getRequestFormat(body);
+ if (type === "form-data") {
+ headers.delete("content-type");
+ init.body = this.#getFormData(body);
+ }
+ if (type === "json") {
+ headers.set("content-type", "application/json");
+ init.body = JSON.stringify(body);
+ }
+ }
+
+ // ### Response
+
+ return this.request(`${endpoint}${query}`, init);
+ }
+
+ /**
+ * Send a fetch request using the given fetch options and returns
+ * a relay formatted response.
+ *
+ * @param endpoint - Which endpoint to submit request to.
+ * @param init - Request init details to submit with the request.
+ */
+ async request(endpoint: string, init?: RequestInit): Promise {
+ return this.#toResponse(await fetch(this.getUrl(endpoint), init));
+ }
+
+ /**
+ * Run before request operations.
+ *
+ * @param headers - Headers to pass to hooks.
+ */
+ async #beforeRequest(headers: Headers) {
+ if (this.options.hooks?.beforeRequest !== undefined) {
+ for (const hook of this.options.hooks.beforeRequest) {
+ await hook(headers);
+ }
+ }
+ }
+
+ /**
+ * Determine the parser method required for the request.
+ *
+ * @param body - Request body.
+ */
+ #getRequestFormat(body: unknown): "form-data" | "json" {
+ if (containsFile(body) === true) {
+ return "form-data";
+ }
+ return "json";
+ }
+
+ /**
+ * Get FormData instance for the given body.
+ *
+ * @param body - Request body.
+ */
+ #getFormData(data: Record, formData = new FormData(), parentKey?: string): FormData {
+ for (const key in data) {
+ const value = data[key];
+ if (value === undefined || value === null) continue;
+
+ const formKey = parentKey ? `${parentKey}[${key}]` : key;
+
+ if (value instanceof File) {
+ formData.append(formKey, value, value.name);
+ } else if (Array.isArray(value)) {
+ value.forEach((item, index) => {
+ if (item instanceof File) {
+ formData.append(`${formKey}[${index}]`, item, item.name);
+ } else if (typeof item === "object") {
+ this.#getFormData(item as Record, formData, `${formKey}[${index}]`);
+ } else {
+ formData.append(`${formKey}[${index}]`, String(item));
+ }
+ });
+ } else if (typeof value === "object") {
+ this.#getFormData(value as Record, formData, formKey);
+ } else {
+ formData.append(formKey, String(value));
+ }
+ }
+
+ return formData;
+ }
+
+ /**
+ * Convert a fetch response to a compliant relay response.
+ *
+ * @param response - Fetch response to convert.
+ */
+ async #toResponse(response: Response): Promise {
+ const type = response.headers.get("content-type");
+
+ // ### Content Type
+ // Ensure that the server responds with a 'content-type' definition. We should
+ // always expect the server to respond with a type.
+
+ if (type === null) {
+ return {
+ result: "error",
+ error: {
+ status: response.status,
+ message: "Missing 'content-type' in header returned from server.",
+ },
+ };
+ }
+
+ // ### Empty Response
+ // If the response comes back with empty response status 204 we simply return a
+ // empty success.
+
+ if (response.status === 204) {
+ return {
+ result: "success",
+ data: null,
+ };
+ }
+
+ // ### JSON
+ // If the 'content-type' contains 'json' we treat it as a 'json' compliant response
+ // and attempt to resolve it as such.
+
+ if (type.includes("json") === true) {
+ const parsed = await response.json();
+ if ("data" in parsed) {
+ return {
+ result: "success",
+ data: parsed.data,
+ };
+ }
+ if ("error" in parsed) {
+ return {
+ result: "error",
+ error: this.#toError(parsed),
+ };
+ }
+ return {
+ result: "error",
+ error: {
+ status: response.status,
+ message: "Unsupported 'json' body returned from server, missing 'data' or 'error' key.",
+ },
+ };
+ }
+
+ return {
+ result: "error",
+ error: {
+ status: response.status,
+ message: "Unsupported 'content-type' in header returned from server.",
+ },
+ };
+ }
+
+ #toError(candidate: unknown, status: number = 500): ServerErrorType | ServerErrorResponse["error"] {
+ if (assertServerErrorResponse(candidate)) {
+ return ServerError.fromJSON({ type: "relay", ...candidate.error });
+ }
+ if (typeof candidate === "string") {
+ return {
+ status,
+ message: candidate,
+ };
+ }
+ return {
+ status,
+ message: "Unsupported 'error' returned from server.",
+ };
+ }
+}
+
+function containsFile(value: unknown): boolean {
+ if (value instanceof File) {
+ return true;
+ }
+ if (Array.isArray(value)) {
+ return value.some(containsFile);
+ }
+ if (typeof value === "object" && value !== null) {
+ return Object.values(value).some(containsFile);
+ }
+ return false;
+}
+
+export type HttpAdapterOptions = {
+ url: string;
+ hooks?: {
+ beforeRequest?: ((headers: Headers) => Promise)[];
+ };
+};
diff --git a/apps/react/src/assets/react.svg b/apps/react/src/assets/react.svg
new file mode 100644
index 0000000..6c87de9
--- /dev/null
+++ b/apps/react/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/react/src/components/app-sidebar.tsx b/apps/react/src/components/app-sidebar.tsx
new file mode 100644
index 0000000..3952ec0
--- /dev/null
+++ b/apps/react/src/components/app-sidebar.tsx
@@ -0,0 +1,178 @@
+"use client";
+
+import {
+ IconCamera,
+ IconChartBar,
+ IconDashboard,
+ IconDatabase,
+ IconFileAi,
+ IconFileDescription,
+ IconFileWord,
+ IconFolder,
+ IconHelp,
+ IconInnerShadowTop,
+ IconListDetails,
+ IconReport,
+ IconSearch,
+ IconSettings,
+ IconUsers,
+} from "@tabler/icons-react";
+import type * as React from "react";
+
+import { NavDocuments } from "@/components/nav-documents";
+import { NavMain } from "@/components/nav-main";
+import { NavSecondary } from "@/components/nav-secondary";
+import { NavUser } from "@/components/nav-user";
+import {
+ Sidebar,
+ SidebarContent,
+ SidebarFooter,
+ SidebarHeader,
+ SidebarMenu,
+ SidebarMenuButton,
+ SidebarMenuItem,
+} from "@/components/ui/sidebar";
+
+const data = {
+ user: {
+ name: "shadcn",
+ email: "m@example.com",
+ avatar: "/avatars/shadcn.jpg",
+ },
+ navMain: [
+ {
+ title: "Dashboard",
+ url: "#",
+ icon: IconDashboard,
+ },
+ {
+ title: "Lifecycle",
+ url: "#",
+ icon: IconListDetails,
+ },
+ {
+ title: "Analytics",
+ url: "#",
+ icon: IconChartBar,
+ },
+ {
+ title: "Projects",
+ url: "#",
+ icon: IconFolder,
+ },
+ {
+ title: "Team",
+ url: "#",
+ icon: IconUsers,
+ },
+ ],
+ navClouds: [
+ {
+ title: "Capture",
+ icon: IconCamera,
+ isActive: true,
+ url: "#",
+ items: [
+ {
+ title: "Active Proposals",
+ url: "#",
+ },
+ {
+ title: "Archived",
+ url: "#",
+ },
+ ],
+ },
+ {
+ title: "Proposal",
+ icon: IconFileDescription,
+ url: "#",
+ items: [
+ {
+ title: "Active Proposals",
+ url: "#",
+ },
+ {
+ title: "Archived",
+ url: "#",
+ },
+ ],
+ },
+ {
+ title: "Prompts",
+ icon: IconFileAi,
+ url: "#",
+ items: [
+ {
+ title: "Active Proposals",
+ url: "#",
+ },
+ {
+ title: "Archived",
+ url: "#",
+ },
+ ],
+ },
+ ],
+ navSecondary: [
+ {
+ title: "Settings",
+ url: "#",
+ icon: IconSettings,
+ },
+ {
+ title: "Get Help",
+ url: "#",
+ icon: IconHelp,
+ },
+ {
+ title: "Search",
+ url: "#",
+ icon: IconSearch,
+ },
+ ],
+ documents: [
+ {
+ name: "Data Library",
+ url: "#",
+ icon: IconDatabase,
+ },
+ {
+ name: "Reports",
+ url: "#",
+ icon: IconReport,
+ },
+ {
+ name: "Word Assistant",
+ url: "#",
+ icon: IconFileWord,
+ },
+ ],
+};
+
+export function AppSidebar({ ...props }: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+
+ Acme Inc.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/react/src/components/nav-documents.tsx b/apps/react/src/components/nav-documents.tsx
new file mode 100644
index 0000000..972a543
--- /dev/null
+++ b/apps/react/src/components/nav-documents.tsx
@@ -0,0 +1,83 @@
+"use client";
+
+import { type Icon, IconDots, IconFolder, IconShare3, IconTrash } from "@tabler/icons-react";
+
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import {
+ SidebarGroup,
+ SidebarGroupLabel,
+ SidebarMenu,
+ SidebarMenuAction,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ useSidebar,
+} from "@/components/ui/sidebar";
+
+export function NavDocuments({
+ items,
+}: {
+ items: {
+ name: string;
+ url: string;
+ icon: Icon;
+ }[];
+}) {
+ const { isMobile } = useSidebar();
+
+ return (
+
+ Documents
+
+ {items.map((item) => (
+
+
+
+
+ {item.name}
+
+
+
+
+
+
+ More
+
+
+
+
+
+ Open
+
+
+
+ Share
+
+
+
+
+ Delete
+
+
+
+
+ ))}
+
+
+
+ More
+
+
+
+
+ );
+}
diff --git a/apps/react/src/components/nav-main.tsx b/apps/react/src/components/nav-main.tsx
new file mode 100644
index 0000000..ac0332f
--- /dev/null
+++ b/apps/react/src/components/nav-main.tsx
@@ -0,0 +1,54 @@
+"use client";
+
+import { type Icon, IconCirclePlusFilled, IconMail } from "@tabler/icons-react";
+
+import { Button } from "@/components/ui/button";
+import {
+ SidebarGroup,
+ SidebarGroupContent,
+ SidebarMenu,
+ SidebarMenuButton,
+ SidebarMenuItem,
+} from "@/components/ui/sidebar";
+
+export function NavMain({
+ items,
+}: {
+ items: {
+ title: string;
+ url: string;
+ icon?: Icon;
+ }[];
+}) {
+ return (
+
+
+
+
+
+
+ Quick Create
+
+
+
+
+
+ {items.map((item) => (
+
+
+ {item.icon && }
+ {item.title}
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/apps/react/src/components/nav-secondary.tsx b/apps/react/src/components/nav-secondary.tsx
new file mode 100644
index 0000000..7690c2d
--- /dev/null
+++ b/apps/react/src/components/nav-secondary.tsx
@@ -0,0 +1,42 @@
+"use client";
+
+import type { Icon } from "@tabler/icons-react";
+import type * as React from "react";
+
+import {
+ SidebarGroup,
+ SidebarGroupContent,
+ SidebarMenu,
+ SidebarMenuButton,
+ SidebarMenuItem,
+} from "@/components/ui/sidebar";
+
+export function NavSecondary({
+ items,
+ ...props
+}: {
+ items: {
+ title: string;
+ url: string;
+ icon: Icon;
+ }[];
+} & React.ComponentPropsWithoutRef) {
+ return (
+
+
+
+ {items.map((item) => (
+
+
+
+
+ {item.title}
+
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/apps/react/src/components/nav-user.controller.ts b/apps/react/src/components/nav-user.controller.ts
new file mode 100644
index 0000000..9735a71
--- /dev/null
+++ b/apps/react/src/components/nav-user.controller.ts
@@ -0,0 +1,51 @@
+import { Controller } from "../libraries/controller.ts";
+import { type User as ZitadelUser, zitadel } from "../services/zitadel.ts";
+
+export class NavUserController extends Controller<{
+ user?: User;
+}> {
+ async onInit() {
+ return {
+ user: await this.#getAuthenticatedUser(),
+ };
+ }
+
+ async #getAuthenticatedUser(): Promise {
+ const user = await zitadel.userManager.getUser();
+ if (user !== null) {
+ return getUserProfile(user);
+ }
+ }
+
+ authorize() {
+ zitadel.authorize();
+ }
+
+ signout() {
+ zitadel.signout();
+ }
+}
+
+function getUserProfile({ profile }: ZitadelUser): User {
+ const user: User = { name: "Unknown", email: "unknown@acme.none", avatar: "" };
+ if (profile.name) {
+ user.name = profile.name;
+ } else if (profile.given_name && profile.family_name) {
+ user.name = `${profile.given_name} ${profile.family_name}`;
+ } else if (profile.given_name) {
+ user.name = profile.given_name;
+ }
+ if (profile.email) {
+ user.email = profile.email;
+ }
+ if (profile.picture !== undefined) {
+ user.avatar = profile.picture;
+ }
+ return user;
+}
+
+type User = {
+ name: string;
+ email: string;
+ avatar: string;
+};
diff --git a/apps/react/src/components/nav-user.tsx b/apps/react/src/components/nav-user.tsx
new file mode 100644
index 0000000..80fefc8
--- /dev/null
+++ b/apps/react/src/components/nav-user.tsx
@@ -0,0 +1,140 @@
+"use client";
+
+import { IconCreditCard, IconDotsVertical, IconLogout, IconNotification, IconUserCircle } from "@tabler/icons-react";
+
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar } from "@/components/ui/sidebar";
+import { useController } from "@/libraries/controller.ts";
+
+import { NavUserController } from "./nav-user.controller.ts";
+
+export function NavUser() {
+ const [{ user }, loading, { authorize, signout }] = useController(NavUserController);
+ const { isMobile } = useSidebar();
+
+ console.log({authorize})
+
+ if (loading === true || user === undefined) {
+ return (
+
+
+
+
+
+
+
+ CN
+
+
+ Guest
+ guest@fixture.none
+
+
+
+
+
+
+
+
+
+ CN
+
+
+ Guest
+ guest@fixture.none
+
+
+
+
+ authorize()}>
+
+ Sign in
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+ CN
+
+
+ {user.name}
+ {user.email}
+
+
+
+
+
+
+
+
+
+ CN
+
+
+ {user.name}
+ {user.email}
+
+
+
+
+
+
+
+ Account
+
+
+
+ Billing
+
+
+
+ Notifications
+
+
+
+ signout()}>
+
+ Log out
+
+
+
+
+
+ );
+}
diff --git a/apps/react/src/components/site-header.tsx b/apps/react/src/components/site-header.tsx
new file mode 100644
index 0000000..c21931f
--- /dev/null
+++ b/apps/react/src/components/site-header.tsx
@@ -0,0 +1,27 @@
+import { Button } from "@/components/ui/button";
+import { Separator } from "@/components/ui/separator";
+import { SidebarTrigger } from "@/components/ui/sidebar";
+
+export function SiteHeader() {
+ return (
+
+ );
+}
diff --git a/apps/react/src/components/theme-provider.tsx b/apps/react/src/components/theme-provider.tsx
new file mode 100644
index 0000000..4d676bd
--- /dev/null
+++ b/apps/react/src/components/theme-provider.tsx
@@ -0,0 +1,67 @@
+import { createContext, useContext, useEffect, useState } from "react";
+
+type Theme = "dark" | "light" | "system";
+
+type ThemeProviderProps = {
+ children: React.ReactNode;
+ defaultTheme?: Theme;
+ storageKey?: string;
+};
+
+type ThemeProviderState = {
+ theme: Theme;
+ setTheme: (theme: Theme) => void;
+};
+
+const initialState: ThemeProviderState = {
+ theme: "system",
+ setTheme: () => null,
+};
+
+const ThemeProviderContext = createContext(initialState);
+
+export function ThemeProvider({
+ children,
+ defaultTheme = "system",
+ storageKey = "vite-ui-theme",
+ ...props
+}: ThemeProviderProps) {
+ const [theme, setTheme] = useState(() => (localStorage.getItem(storageKey) as Theme) || defaultTheme);
+
+ useEffect(() => {
+ const root = window.document.documentElement;
+
+ root.classList.remove("light", "dark");
+
+ if (theme === "system") {
+ const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
+
+ root.classList.add(systemTheme);
+ return;
+ }
+
+ root.classList.add(theme);
+ }, [theme]);
+
+ const value = {
+ theme,
+ setTheme: (theme: Theme) => {
+ localStorage.setItem(storageKey, theme);
+ setTheme(theme);
+ },
+ };
+
+ return (
+
+ {children}
+
+ );
+}
+
+export const useTheme = () => {
+ const context = useContext(ThemeProviderContext);
+
+ if (context === undefined) throw new Error("useTheme must be used within a ThemeProvider");
+
+ return context;
+};
diff --git a/apps/react/src/components/ui/avatar.tsx b/apps/react/src/components/ui/avatar.tsx
new file mode 100644
index 0000000..35db151
--- /dev/null
+++ b/apps/react/src/components/ui/avatar.tsx
@@ -0,0 +1,38 @@
+import * as AvatarPrimitive from "@radix-ui/react-avatar";
+import * as React from "react";
+
+import { cn } from "../../libraries/utils.ts";
+
+const Avatar = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+Avatar.displayName = AvatarPrimitive.Root.displayName;
+
+const AvatarImage = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AvatarImage.displayName = AvatarPrimitive.Image.displayName;
+
+const AvatarFallback = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
+
+export { Avatar, AvatarImage, AvatarFallback };
diff --git a/apps/react/src/components/ui/button.tsx b/apps/react/src/components/ui/button.tsx
new file mode 100644
index 0000000..f4f67f1
--- /dev/null
+++ b/apps/react/src/components/ui/button.tsx
@@ -0,0 +1,47 @@
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
+import * as React from "react";
+
+import { cn } from "../../libraries/utils.ts";
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
+ destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
+ outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
+ secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-9 px-4 py-2",
+ sm: "h-8 rounded-md px-3 text-xs",
+ lg: "h-10 rounded-md px-8",
+ icon: "h-9 w-9",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ },
+);
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes,
+ VariantProps {
+ asChild?: boolean;
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button";
+ return ;
+ },
+);
+Button.displayName = "Button";
+
+export { Button, buttonVariants };
diff --git a/apps/react/src/components/ui/dropdown-menu.tsx b/apps/react/src/components/ui/dropdown-menu.tsx
new file mode 100644
index 0000000..696a682
--- /dev/null
+++ b/apps/react/src/components/ui/dropdown-menu.tsx
@@ -0,0 +1,180 @@
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
+import { Check, ChevronRight, Circle } from "lucide-react";
+import * as React from "react";
+
+import { cn } from "../../libraries/utils.ts";
+
+const DropdownMenu = DropdownMenuPrimitive.Root;
+
+const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
+
+const DropdownMenuGroup = DropdownMenuPrimitive.Group;
+
+const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
+
+const DropdownMenuSub = DropdownMenuPrimitive.Sub;
+
+const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
+
+const DropdownMenuSubTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean;
+ }
+>(({ className, inset, children, ...props }, ref) => (
+
+ {children}
+
+
+));
+DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
+
+const DropdownMenuSubContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
+
+const DropdownMenuContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, sideOffset = 4, ...props }, ref) => (
+
+
+
+));
+DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
+
+const DropdownMenuItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean;
+ }
+>(({ className, inset, ...props }, ref) => (
+ svg]:size-4 [&>svg]:shrink-0",
+ inset && "pl-8",
+ className,
+ )}
+ {...props}
+ />
+));
+DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
+
+const DropdownMenuCheckboxItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, checked, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+));
+DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
+
+const DropdownMenuRadioItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+));
+DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
+
+const DropdownMenuLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean;
+ }
+>(({ className, inset, ...props }, ref) => (
+
+));
+DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
+
+const DropdownMenuSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
+
+const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => {
+ return ;
+};
+DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
+
+export {
+ DropdownMenu,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuGroup,
+ DropdownMenuPortal,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuRadioGroup,
+};
diff --git a/apps/react/src/components/ui/input.tsx b/apps/react/src/components/ui/input.tsx
new file mode 100644
index 0000000..22e9562
--- /dev/null
+++ b/apps/react/src/components/ui/input.tsx
@@ -0,0 +1,21 @@
+import type * as React from "react";
+
+import { cn } from "@/libraries/utils";
+
+function Input({ className, type, ...props }: React.ComponentProps<"input">) {
+ return (
+
+ );
+}
+
+export { Input };
diff --git a/apps/react/src/components/ui/scroll-area.tsx b/apps/react/src/components/ui/scroll-area.tsx
new file mode 100644
index 0000000..cade2ee
--- /dev/null
+++ b/apps/react/src/components/ui/scroll-area.tsx
@@ -0,0 +1,38 @@
+import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
+import * as React from "react";
+
+import { cn } from "@/libraries/utils";
+
+const ScrollArea = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ {children}
+
+
+
+));
+ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
+
+const ScrollBar = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, orientation = "vertical", ...props }, ref) => (
+
+
+
+));
+ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
+
+export { ScrollArea, ScrollBar };
diff --git a/apps/react/src/components/ui/separator.tsx b/apps/react/src/components/ui/separator.tsx
new file mode 100644
index 0000000..c1c3644
--- /dev/null
+++ b/apps/react/src/components/ui/separator.tsx
@@ -0,0 +1,26 @@
+import * as SeparatorPrimitive from "@radix-ui/react-separator";
+import type * as React from "react";
+
+import { cn } from "@/libraries/utils";
+
+function Separator({
+ className,
+ orientation = "horizontal",
+ decorative = true,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export { Separator };
diff --git a/apps/react/src/components/ui/sheet.tsx b/apps/react/src/components/ui/sheet.tsx
new file mode 100644
index 0000000..f377113
--- /dev/null
+++ b/apps/react/src/components/ui/sheet.tsx
@@ -0,0 +1,103 @@
+"use client";
+
+import * as SheetPrimitive from "@radix-ui/react-dialog";
+import { XIcon } from "lucide-react";
+import type * as React from "react";
+
+import { cn } from "@/libraries/utils";
+
+function Sheet({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function SheetTrigger({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function SheetClose({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function SheetPortal({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function SheetOverlay({ className, ...props }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function SheetContent({
+ className,
+ children,
+ side = "right",
+ ...props
+}: React.ComponentProps & {
+ side?: "top" | "right" | "bottom" | "left";
+}) {
+ return (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+ );
+}
+
+function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return ;
+}
+
+function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return ;
+}
+
+function SheetTitle({ className, ...props }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function SheetDescription({ className, ...props }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export { Sheet, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription };
diff --git a/apps/react/src/components/ui/sidebar.tsx b/apps/react/src/components/ui/sidebar.tsx
new file mode 100644
index 0000000..ef84fe8
--- /dev/null
+++ b/apps/react/src/components/ui/sidebar.tsx
@@ -0,0 +1,643 @@
+"use client";
+
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
+import { PanelLeft } from "lucide-react";
+import * as React from "react";
+
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Separator } from "@/components/ui/separator";
+import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet";
+import { Skeleton } from "@/components/ui/skeleton";
+import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
+import { useIsMobile } from "@/hooks/use-mobile";
+import { cn } from "@/libraries/utils";
+
+const SIDEBAR_COOKIE_NAME = "sidebar_state";
+const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
+const SIDEBAR_WIDTH = "16rem";
+const SIDEBAR_WIDTH_MOBILE = "18rem";
+const SIDEBAR_WIDTH_ICON = "3rem";
+const SIDEBAR_KEYBOARD_SHORTCUT = "b";
+
+type SidebarContextProps = {
+ state: "expanded" | "collapsed";
+ open: boolean;
+ setOpen: (open: boolean) => void;
+ openMobile: boolean;
+ setOpenMobile: (open: boolean) => void;
+ isMobile: boolean;
+ toggleSidebar: () => void;
+};
+
+const SidebarContext = React.createContext(null);
+
+function useSidebar() {
+ const context = React.useContext(SidebarContext);
+ if (!context) {
+ throw new Error("useSidebar must be used within a SidebarProvider.");
+ }
+
+ return context;
+}
+
+const SidebarProvider = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div"> & {
+ defaultOpen?: boolean;
+ open?: boolean;
+ onOpenChange?: (open: boolean) => void;
+ }
+>(({ defaultOpen = true, open: openProp, onOpenChange: setOpenProp, className, style, children, ...props }, ref) => {
+ const isMobile = useIsMobile();
+ const [openMobile, setOpenMobile] = React.useState(false);
+
+ // This is the internal state of the sidebar.
+ // We use openProp and setOpenProp for control from outside the component.
+ const [_open, _setOpen] = React.useState(defaultOpen);
+ const open = openProp ?? _open;
+ const setOpen = React.useCallback(
+ (value: boolean | ((value: boolean) => boolean)) => {
+ const openState = typeof value === "function" ? value(open) : value;
+ if (setOpenProp) {
+ setOpenProp(openState);
+ } else {
+ _setOpen(openState);
+ }
+
+ // This sets the cookie to keep the sidebar state.
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
+ },
+ [setOpenProp, open],
+ );
+
+ // Helper to toggle the sidebar.
+ const toggleSidebar = React.useCallback(() => {
+ return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
+ }, [isMobile, setOpen, setOpenMobile]);
+
+ // Adds a keyboard shortcut to toggle the sidebar.
+ React.useEffect(() => {
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
+ event.preventDefault();
+ toggleSidebar();
+ }
+ };
+
+ window.addEventListener("keydown", handleKeyDown);
+ return () => window.removeEventListener("keydown", handleKeyDown);
+ }, [toggleSidebar]);
+
+ // We add a state so that we can do data-state="expanded" or "collapsed".
+ // This makes it easier to style the sidebar with Tailwind classes.
+ const state = open ? "expanded" : "collapsed";
+
+ const contextValue = React.useMemo(
+ () => ({
+ state,
+ open,
+ setOpen,
+ isMobile,
+ openMobile,
+ setOpenMobile,
+ toggleSidebar,
+ }),
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],
+ );
+
+ return (
+
+
+
+ {children}
+
+
+
+ );
+});
+SidebarProvider.displayName = "SidebarProvider";
+
+const Sidebar = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div"> & {
+ side?: "left" | "right";
+ variant?: "sidebar" | "floating" | "inset";
+ collapsible?: "offcanvas" | "icon" | "none";
+ }
+>(({ side = "left", variant = "sidebar", collapsible = "offcanvas", className, children, ...props }, ref) => {
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
+
+ if (collapsible === "none") {
+ return (
+
+ {children}
+
+ );
+ }
+
+ if (isMobile) {
+ return (
+
+
+
+ Sidebar
+ Displays the mobile sidebar.
+
+ {children}
+
+
+ );
+ }
+
+ return (
+
+ {/* This is what handles the sidebar gap on desktop */}
+
+
+
+ );
+});
+Sidebar.displayName = "Sidebar";
+
+const SidebarTrigger = React.forwardRef, React.ComponentProps>(
+ ({ className, onClick, ...props }, ref) => {
+ const { toggleSidebar } = useSidebar();
+
+ return (
+
+ );
+ },
+);
+SidebarTrigger.displayName = "SidebarTrigger";
+
+const SidebarRail = React.forwardRef>(
+ ({ className, ...props }, ref) => {
+ const { toggleSidebar } = useSidebar();
+
+ return (
+
+ );
+ },
+);
+SidebarRail.displayName = "SidebarRail";
+
+const SidebarInset = React.forwardRef>(({ className, ...props }, ref) => {
+ return (
+
+ );
+});
+SidebarInset.displayName = "SidebarInset";
+
+const SidebarInput = React.forwardRef, React.ComponentProps>(
+ ({ className, ...props }, ref) => {
+ return (
+
+ );
+ },
+);
+SidebarInput.displayName = "SidebarInput";
+
+const SidebarHeader = React.forwardRef>(({ className, ...props }, ref) => {
+ return ;
+});
+SidebarHeader.displayName = "SidebarHeader";
+
+const SidebarFooter = React.forwardRef>(({ className, ...props }, ref) => {
+ return ;
+});
+SidebarFooter.displayName = "SidebarFooter";
+
+const SidebarSeparator = React.forwardRef, React.ComponentProps>(
+ ({ className, ...props }, ref) => {
+ return (
+
+ );
+ },
+);
+SidebarSeparator.displayName = "SidebarSeparator";
+
+const SidebarContent = React.forwardRef>(({ className, ...props }, ref) => {
+ return (
+
+ );
+});
+SidebarContent.displayName = "SidebarContent";
+
+const SidebarGroup = React.forwardRef>(({ className, ...props }, ref) => {
+ return (
+
+ );
+});
+SidebarGroup.displayName = "SidebarGroup";
+
+const SidebarGroupLabel = React.forwardRef & { asChild?: boolean }>(
+ ({ className, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "div";
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0",
+ "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
+ className,
+ )}
+ {...props}
+ />
+ );
+ },
+);
+SidebarGroupLabel.displayName = "SidebarGroupLabel";
+
+const SidebarGroupAction = React.forwardRef & { asChild?: boolean }>(
+ ({ className, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button";
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0",
+ // Increases the hit area of the button on mobile.
+ "after:absolute after:-inset-2 after:md:hidden",
+ "group-data-[collapsible=icon]:hidden",
+ className,
+ )}
+ {...props}
+ />
+ );
+ },
+);
+SidebarGroupAction.displayName = "SidebarGroupAction";
+
+const SidebarGroupContent = React.forwardRef>(
+ ({ className, ...props }, ref) => (
+
+ ),
+);
+SidebarGroupContent.displayName = "SidebarGroupContent";
+
+const SidebarMenu = React.forwardRef>(({ className, ...props }, ref) => (
+
+));
+SidebarMenu.displayName = "SidebarMenu";
+
+const SidebarMenuItem = React.forwardRef>(({ className, ...props }, ref) => (
+
+));
+SidebarMenuItem.displayName = "SidebarMenuItem";
+
+const sidebarMenuButtonVariants = cva(
+ "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
+ outline:
+ "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
+ },
+ size: {
+ default: "h-8 text-sm",
+ sm: "h-7 text-xs",
+ lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ },
+);
+
+const SidebarMenuButton = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps<"button"> & {
+ asChild?: boolean;
+ isActive?: boolean;
+ tooltip?: string | React.ComponentProps;
+ } & VariantProps
+>(({ asChild = false, isActive = false, variant = "default", size = "default", tooltip, className, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button";
+ const { isMobile, state } = useSidebar();
+
+ const button = (
+
+ );
+
+ if (!tooltip) {
+ return button;
+ }
+
+ if (typeof tooltip === "string") {
+ tooltip = {
+ children: tooltip,
+ };
+ }
+
+ return (
+
+ {button}
+
+
+ );
+});
+SidebarMenuButton.displayName = "SidebarMenuButton";
+
+const SidebarMenuAction = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps<"button"> & {
+ asChild?: boolean;
+ showOnHover?: boolean;
+ }
+>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button";
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0",
+ // Increases the hit area of the button on mobile.
+ "after:absolute after:-inset-2 after:md:hidden",
+ "peer-data-[size=sm]/menu-button:top-1",
+ "peer-data-[size=default]/menu-button:top-1.5",
+ "peer-data-[size=lg]/menu-button:top-2.5",
+ "group-data-[collapsible=icon]:hidden",
+ showOnHover &&
+ "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
+ className,
+ )}
+ {...props}
+ />
+ );
+});
+SidebarMenuAction.displayName = "SidebarMenuAction";
+
+const SidebarMenuBadge = React.forwardRef>(
+ ({ className, ...props }, ref) => (
+
+ ),
+);
+SidebarMenuBadge.displayName = "SidebarMenuBadge";
+
+const SidebarMenuSkeleton = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div"> & {
+ showIcon?: boolean;
+ }
+>(({ className, showIcon = false, ...props }, ref) => {
+ // Random width between 50 to 90%.
+ const width = React.useMemo(() => {
+ return `${Math.floor(Math.random() * 40) + 50}%`;
+ }, []);
+
+ return (
+
+ {showIcon && }
+
+
+ );
+});
+SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton";
+
+const SidebarMenuSub = React.forwardRef>(
+ ({ className, ...props }, ref) => (
+
+ ),
+);
+SidebarMenuSub.displayName = "SidebarMenuSub";
+
+const SidebarMenuSubItem = React.forwardRef>(({ ...props }, ref) => (
+
+));
+SidebarMenuSubItem.displayName = "SidebarMenuSubItem";
+
+const SidebarMenuSubButton = React.forwardRef<
+ HTMLAnchorElement,
+ React.ComponentProps<"a"> & {
+ asChild?: boolean;
+ size?: "sm" | "md";
+ isActive?: boolean;
+ }
+>(({ asChild = false, size = "md", isActive, className, ...props }, ref) => {
+ const Comp = asChild ? Slot : "a";
+
+ return (
+ span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
+ "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
+ size === "sm" && "text-xs",
+ size === "md" && "text-sm",
+ "group-data-[collapsible=icon]:hidden",
+ className,
+ )}
+ {...props}
+ />
+ );
+});
+SidebarMenuSubButton.displayName = "SidebarMenuSubButton";
+
+export {
+ Sidebar,
+ SidebarContent,
+ SidebarFooter,
+ SidebarGroup,
+ SidebarGroupAction,
+ SidebarGroupContent,
+ SidebarGroupLabel,
+ SidebarHeader,
+ SidebarInput,
+ SidebarInset,
+ SidebarMenu,
+ SidebarMenuAction,
+ SidebarMenuBadge,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ SidebarMenuSkeleton,
+ SidebarMenuSub,
+ SidebarMenuSubButton,
+ SidebarMenuSubItem,
+ SidebarProvider,
+ SidebarRail,
+ SidebarSeparator,
+ SidebarTrigger,
+ useSidebar,
+};
diff --git a/apps/react/src/components/ui/skeleton.tsx b/apps/react/src/components/ui/skeleton.tsx
new file mode 100644
index 0000000..bb99295
--- /dev/null
+++ b/apps/react/src/components/ui/skeleton.tsx
@@ -0,0 +1,7 @@
+import { cn } from "@/libraries/utils";
+
+function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
+ return ;
+}
+
+export { Skeleton };
diff --git a/apps/react/src/components/ui/tooltip.tsx b/apps/react/src/components/ui/tooltip.tsx
new file mode 100644
index 0000000..a00a370
--- /dev/null
+++ b/apps/react/src/components/ui/tooltip.tsx
@@ -0,0 +1,46 @@
+import * as TooltipPrimitive from "@radix-ui/react-tooltip";
+import type * as React from "react";
+
+import { cn } from "@/libraries/utils";
+
+function TooltipProvider({ delayDuration = 0, ...props }: React.ComponentProps) {
+ return ;
+}
+
+function Tooltip({ ...props }: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+function TooltipTrigger({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function TooltipContent({
+ className,
+ sideOffset = 0,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+ {children}
+
+
+
+ );
+}
+
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
diff --git a/apps/react/src/hooks/use-mobile.ts b/apps/react/src/hooks/use-mobile.ts
new file mode 100644
index 0000000..502fd32
--- /dev/null
+++ b/apps/react/src/hooks/use-mobile.ts
@@ -0,0 +1,19 @@
+import * as React from "react";
+
+const MOBILE_BREAKPOINT = 768;
+
+export function useIsMobile() {
+ const [isMobile, setIsMobile] = React.useState(undefined);
+
+ React.useEffect(() => {
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
+ const onChange = () => {
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
+ };
+ mql.addEventListener("change", onChange);
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
+ return () => mql.removeEventListener("change", onChange);
+ }, []);
+
+ return !!isMobile;
+}
diff --git a/apps/react/src/index.css b/apps/react/src/index.css
new file mode 100644
index 0000000..9702450
--- /dev/null
+++ b/apps/react/src/index.css
@@ -0,0 +1,122 @@
+@import "tailwindcss";
+
+@plugin "tailwindcss-animate";
+@import "tw-animate-css";
+
+@custom-variant dark (&:is(.dark *));
+
+@theme inline {
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --color-chart-1: var(--chart-1);
+ --color-chart-2: var(--chart-2);
+ --color-chart-3: var(--chart-3);
+ --color-chart-4: var(--chart-4);
+ --color-chart-5: var(--chart-5);
+ --color-sidebar: var(--sidebar);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-ring: var(--sidebar-ring);
+}
+
+:root {
+ --radius: 0.625rem;
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.145 0 0);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.145 0 0);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.145 0 0);
+ --primary: oklch(0.205 0 0);
+ --primary-foreground: oklch(0.985 0 0);
+ --secondary: oklch(0.97 0 0);
+ --secondary-foreground: oklch(0.205 0 0);
+ --muted: oklch(0.97 0 0);
+ --muted-foreground: oklch(0.556 0 0);
+ --accent: oklch(0.97 0 0);
+ --accent-foreground: oklch(0.205 0 0);
+ --destructive: oklch(0.577 0.245 27.325);
+ --border: oklch(0.922 0 0);
+ --input: oklch(0.922 0 0);
+ --ring: oklch(0.708 0 0);
+ --chart-1: oklch(0.646 0.222 41.116);
+ --chart-2: oklch(0.6 0.118 184.704);
+ --chart-3: oklch(0.398 0.07 227.392);
+ --chart-4: oklch(0.828 0.189 84.429);
+ --chart-5: oklch(0.769 0.188 70.08);
+ --sidebar: oklch(0.985 0 0);
+ --sidebar-foreground: oklch(0.145 0 0);
+ --sidebar-primary: oklch(0.205 0 0);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.97 0 0);
+ --sidebar-accent-foreground: oklch(0.205 0 0);
+ --sidebar-border: oklch(0.922 0 0);
+ --sidebar-ring: oklch(0.708 0 0);
+}
+
+.dark {
+ --background: oklch(0.145 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.205 0 0);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.205 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.922 0 0);
+ --primary-foreground: oklch(0.205 0 0);
+ --secondary: oklch(0.269 0 0);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.269 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.269 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.704 0.191 22.216);
+ --border: oklch(1 0 0 / 10%);
+ --input: oklch(1 0 0 / 15%);
+ --ring: oklch(0.556 0 0);
+ --chart-1: oklch(0.488 0.243 264.376);
+ --chart-2: oklch(0.696 0.17 162.48);
+ --chart-3: oklch(0.769 0.188 70.08);
+ --chart-4: oklch(0.627 0.265 303.9);
+ --chart-5: oklch(0.645 0.246 16.439);
+ --sidebar: oklch(0.205 0 0);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.269 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(1 0 0 / 10%);
+ --sidebar-ring: oklch(0.556 0 0);
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
diff --git a/apps/react/src/libraries/controller.ts b/apps/react/src/libraries/controller.ts
new file mode 100644
index 0000000..762505b
--- /dev/null
+++ b/apps/react/src/libraries/controller.ts
@@ -0,0 +1,234 @@
+import { useEffect, useMemo, useRef, useState } from "react";
+
+/**
+ * Minimal Controller for managing component state and lifecycle.
+ */
+export class Controller = {}, TProps extends Record = {}> {
+ state: TState = {} as TState;
+ props: TProps = {} as TProps;
+
+ #resolved = false;
+ #destroyed = false;
+
+ #setState: (state: Partial) => void;
+ #setLoading: (state: boolean) => void;
+
+ constructor(setState: (state: Partial) => void, setLoading: (state: boolean) => void) {
+ this.#setState = setState;
+ this.#setLoading = setLoading;
+ }
+
+ /**
+ * Factory method to create a new controller instance.
+ */
+ static make(
+ this: TController,
+ setState: any,
+ setLoading: any,
+ ): InstanceType {
+ return new this(setState, setLoading) as InstanceType;
+ }
+
+ /*
+ |--------------------------------------------------------------------------------
+ | Lifecycle
+ |--------------------------------------------------------------------------------
+ */
+
+ /**
+ * Resolves the controller with given props.
+ * - First time: Runs onInit() then onResolve()
+ * - Subsequent times: Runs only onResolve()
+ */
+ async $resolve(props: TProps): Promise {
+ if (this.#destroyed === true) {
+ return;
+ }
+
+ this.props = props;
+ let state: Partial = {};
+
+ try {
+ if (this.#resolved === false) {
+ const initState = await this.onInit();
+ if (initState) {
+ state = { ...state, ...initState };
+ }
+ }
+ const resolveState = await this.onResolve();
+ if (resolveState) {
+ state = { ...state, ...resolveState };
+ }
+ } catch (error) {
+ console.error("Controller resolve error:", error);
+ throw error;
+ }
+
+ this.#resolved = true;
+
+ if (this.#destroyed === false) {
+ this.setState(state);
+ }
+ }
+
+ /**
+ * Destroys the controller and cleans up resources.
+ */
+ async $destroy(): Promise {
+ this.#destroyed = true;
+ await this.onDestroy();
+ }
+
+ /*
+ |--------------------------------------------------------------------------------
+ | Lifecycle Hooks
+ |--------------------------------------------------------------------------------
+ */
+
+ /**
+ * Called once when the controller is first initialized.
+ */
+ async onInit(): Promise | void> {
+ return {};
+ }
+
+ /**
+ * Called every time props change (including first mount).
+ */
+ async onResolve(): Promise | void> {
+ return {};
+ }
+
+ /**
+ * Called when the controller is destroyed.
+ */
+ async onDestroy(): Promise {}
+
+ /*
+ |--------------------------------------------------------------------------------
+ | State Management
+ |--------------------------------------------------------------------------------
+ */
+
+ /**
+ * Updates the controller state.
+ */
+ setState(state: Partial): void;
+ setState(key: K, value: TState[K]): void;
+ setState(...args: [K | Partial, TState[K]?]): void {
+ if (this.#destroyed) {
+ return;
+ }
+
+ const [target, value] = args;
+
+ if (typeof target === "string") {
+ this.state = { ...this.state, [target]: value };
+ } else {
+ this.state = { ...this.state, ...(target as Partial) };
+ }
+
+ this.#setState(this.state);
+ this.#setLoading(false);
+ }
+
+ /*
+ |--------------------------------------------------------------------------------
+ | Actions
+ |--------------------------------------------------------------------------------
+ */
+
+ /**
+ * Returns all public methods as bound actions.
+ */
+ toActions(): ControllerActions {
+ const actions: any = {};
+ const prototype = Object.getPrototypeOf(this);
+
+ for (const name of Object.getOwnPropertyNames(prototype)) {
+ if (this.#isAction(name)) {
+ const method = (this as any)[name];
+ if (typeof method === "function") {
+ actions[name] = method.bind(this);
+ }
+ }
+ }
+
+ return actions;
+ }
+
+ #isAction(name: string): boolean {
+ return name !== "constructor" && !name.startsWith("$") && !name.startsWith("_") && !name.startsWith("#");
+ }
+}
+
+/*
+ |--------------------------------------------------------------------------------
+ | Hook
+ |--------------------------------------------------------------------------------
+ */
+
+/**
+ * React hook for using a controller.
+ *
+ * @example
+ * ```tsx
+ * function LoginView() {
+ * const [state, actions] = useController(LoginController);
+ *
+ * return (
+ *
+ * );
+ * }
+ * ```
+ */
+export function useController Controller>(
+ ControllerClass: TController,
+ props?: InstanceType["props"],
+): [InstanceType["state"], boolean, ControllerActions>] {
+ const [state, setState] = useState({});
+ const [loading, setLoading] = useState(true);
+
+ const controllerRef = useRef | null>(null);
+ const actionsRef = useRef> | null>(null);
+ const propsRef = useRef(props);
+
+ // Resolve only once after creation
+ useMemo(() => {
+ const instance = (ControllerClass as any).make(setState, setLoading);
+ controllerRef.current = instance;
+ actionsRef.current = instance.toActions();
+
+ instance.$resolve(props || {});
+
+ return () => {
+ instance.$destroy();
+ };
+ }, [controllerRef]);
+
+ // Resolve on props change
+ useEffect(() => {
+ if (propsRef.current !== props) {
+ propsRef.current = props;
+ controllerRef.current?.$resolve(props || {});
+ }
+ }, [props]);
+
+ return [state, loading, actionsRef.current!];
+}
+
+/*
+ |--------------------------------------------------------------------------------
+ | Types
+ |--------------------------------------------------------------------------------
+ */
+
+type ControllerActions = {
+ [K in keyof T]: T[K] extends (...args: any[]) => any
+ ? K extends `$${string}` | `_${string}` | `#${string}` | "constructor"
+ ? never
+ : T[K]
+ : never;
+};
diff --git a/apps/react/src/libraries/form.ts b/apps/react/src/libraries/form.ts
new file mode 100644
index 0000000..665f595
--- /dev/null
+++ b/apps/react/src/libraries/form.ts
@@ -0,0 +1,269 @@
+import z, { type ZodObject, type ZodRawShape } from "zod";
+
+export class Form>> {
+ readonly schema: ZodObject;
+
+ readonly inputs: Partial = {};
+
+ #debounce: FormDebounce = {
+ validate: {},
+ };
+
+ #defaults: Partial;
+ #errors: FormErrors = {};
+ #elements: Record = {};
+
+ #onChange?: OnChangeCallback;
+ #onProcessing?: OnProcessingCallback;
+ #onError?: OnErrorCallback;
+ #onSubmit?: OnSubmitCallback;
+ #onResponse?: OnResponseCallback;
+
+ /*
+ |--------------------------------------------------------------------------------
+ | Constructor
+ |--------------------------------------------------------------------------------
+ */
+
+ constructor(schema: TSchema, defaults: Partial = {}) {
+ this.schema = z.object(schema);
+ this.#defaults = defaults;
+ this.#bindMethods();
+ this.#setDefaults();
+ this.#setSubmit();
+ }
+
+ #bindMethods() {
+ this.register = this.register.bind(this);
+ this.set = this.set.bind(this);
+ this.get = this.get.bind(this);
+ this.validate = this.validate.bind(this);
+ this.submit = this.submit.bind(this);
+ }
+
+ #setDefaults() {
+ for (const key in this.#defaults) {
+ this.inputs[key] = this.#defaults[key] ?? ("" as any);
+ }
+ }
+
+ #setSubmit() {
+ if ((this.constructor as any).submit !== undefined) {
+ this.onSubmit((this.constructor as any).submit);
+ }
+ }
+
+ /*
+ |--------------------------------------------------------------------------------
+ | Accessors
+ |--------------------------------------------------------------------------------
+ */
+
+ get isValid(): boolean {
+ return Object.keys(this.#getFormErrors()).length === 0;
+ }
+
+ get hasError() {
+ return Object.keys(this.errors).length !== 0;
+ }
+
+ get errors(): FormErrors {
+ return this.#errors;
+ }
+
+ set errors(value: FormErrors) {
+ this.#errors = value;
+ this.#onError?.(value);
+ }
+
+ /**
+ * Register a input element with the form. This registers form related methods and a
+ * reference to the element itself that can be utilized by the form.
+ *
+ * @param name - Name of the input field.
+ */
+ register(name: TKey) {
+ return {
+ name,
+ ref: (element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | null) => {
+ if (element !== null) {
+ this.#elements[name as string] = element;
+ }
+ },
+ defaultValue: this.get(name),
+ onChange: ({ target: { value } }: any) => {
+ this.set(name, value);
+ },
+ };
+ }
+
+ /*
+ |--------------------------------------------------------------------------------
+ | Registrars
+ |--------------------------------------------------------------------------------
+ */
+
+ onChange(callback: OnChangeCallback): this {
+ this.#onChange = callback;
+ return this;
+ }
+
+ onProcessing(callback: OnProcessingCallback): this {
+ this.#onProcessing = callback;
+ return this;
+ }
+
+ onError(callback: OnErrorCallback): this {
+ this.#onError = callback;
+ return this;
+ }
+
+ onSubmit(callback: OnSubmitCallback): this {
+ this.#onSubmit = callback;
+ return this;
+ }
+
+ onResponse(callback: OnResponseCallback): this {
+ this.#onResponse = callback;
+ return this;
+ }
+
+ /*
+ |--------------------------------------------------------------------------------
+ | Data
+ |--------------------------------------------------------------------------------
+ */
+
+ /**
+ * Set the value of an input field.
+ *
+ * @param name - Name of the input field.
+ * @param value - Value to set.
+ */
+ set(name: TKey, value: TInputs[TKey]): void {
+ this.inputs[name] = value;
+ this.#onChange?.(name, value);
+ clearTimeout(this.#debounce.validate[name]);
+ this.#debounce.validate[name] = setTimeout(() => {
+ this.validate(name);
+ }, 200);
+ }
+
+ /**
+ * Get the current input values or a specific input value.
+ *
+ * @param name - Name of the input field. _(Optional)_
+ */
+ get(): Partial;
+ get(name: TKey): TInputs[TKey] | undefined;
+ get(name?: TKey): Partial | TInputs[TKey] | undefined {
+ if (name === undefined) {
+ return { ...this.inputs };
+ }
+ return this.inputs[name];
+ }
+
+ /**
+ * Reset form back to its default values.
+ */
+ reset() {
+ for (const key in this.inputs) {
+ const value = this.#defaults[key] ?? "";
+ (this.inputs as any)[key] = value;
+ if (this.#elements[key] !== undefined) {
+ (this.#elements as any)[key].value = value;
+ }
+ }
+ }
+
+ /*
+ |--------------------------------------------------------------------------------
+ | Submission
+ |--------------------------------------------------------------------------------
+ */
+
+ async submit(event: any) {
+ event.preventDefault?.();
+ this.#onProcessing?.(true);
+ this.validate();
+ if (this.hasError === false) {
+ try {
+ const response = await this.#onSubmit?.(this.schema.parse(this.inputs) as TInputs);
+ this.#onResponse?.(undefined, response);
+ } catch (error) {
+ this.#onResponse?.(error, undefined as any);
+ }
+ }
+ this.#onProcessing?.(false);
+ this.reset();
+ }
+
+ validate(name?: keyof TInputs) {
+ if (name !== undefined) {
+ this.#validateInput(name);
+ } else {
+ this.#validateForm();
+ }
+ }
+
+ #validateForm(): void {
+ this.errors = this.#getFormErrors();
+ }
+
+ #validateInput(name: keyof TInputs): void {
+ const errors = this.#getFormErrors();
+ let hasChanges = false;
+ if (errors[name] === undefined && this.errors[name] !== undefined) {
+ delete this.errors[name];
+ hasChanges = true;
+ }
+ if (errors[name] !== undefined && this.errors[name] !== errors[name]) {
+ this.errors[name] = errors[name];
+ hasChanges = true;
+ }
+ if (hasChanges === true) {
+ this.#onError?.({ ...this.errors });
+ }
+ }
+
+ #getFormErrors(): FormErrors {
+ const result = this.schema.safeParse(this.inputs);
+ if (result.success === false) {
+ throw result.error.flatten;
+ // return result.error.details.reduce>(
+ // (error, next) => ({
+ // ...error,
+ // [next.path[0]]: next.message,
+ // }),
+ // {},
+ // );
+ }
+ return {};
+ }
+}
+
+/*
+ |--------------------------------------------------------------------------------
+ | Types
+ |--------------------------------------------------------------------------------
+ */
+
+type OnChangeCallback = (name: TKey, value: TInputs[TKey]) => void;
+
+type OnProcessingCallback = (value: boolean) => void;
+
+type OnErrorCallback = (errors: FormErrors) => void;
+
+type OnSubmitCallback = (inputs: TInputs) => Promise;
+
+type OnResponseCallback = (err: Error, res: Response) => void;
+
+type FormDebounce = {
+ validate: {
+ [TKey in keyof TInputs]?: any;
+ };
+};
+
+type FormErrors = {
+ [TKey in keyof TInputs]?: string;
+};
diff --git a/apps/react/src/libraries/utils.ts b/apps/react/src/libraries/utils.ts
new file mode 100644
index 0000000..365058c
--- /dev/null
+++ b/apps/react/src/libraries/utils.ts
@@ -0,0 +1,6 @@
+import { type ClassValue, clsx } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
diff --git a/apps/react/src/main.tsx b/apps/react/src/main.tsx
new file mode 100644
index 0000000..9f22d46
--- /dev/null
+++ b/apps/react/src/main.tsx
@@ -0,0 +1,29 @@
+import "./index.css";
+
+import { createRouter, RouterProvider } from "@tanstack/react-router";
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+
+import { ThemeProvider } from "./components/theme-provider.tsx";
+import { routeTree } from "./routes.tsx";
+
+const router = createRouter({ routeTree });
+
+declare module "@tanstack/react-router" {
+ interface Register {
+ router: typeof router;
+ }
+}
+
+const rootElement = document.getElementById("root");
+if (rootElement === null) {
+ throw new Error("Failed to retrieve root element");
+}
+
+createRoot(rootElement).render(
+
+
+
+
+ ,
+);
diff --git a/apps/react/src/routes.tsx b/apps/react/src/routes.tsx
new file mode 100644
index 0000000..394bb73
--- /dev/null
+++ b/apps/react/src/routes.tsx
@@ -0,0 +1,30 @@
+import { createRootRoute, createRoute } from "@tanstack/react-router";
+
+import { AppView } from "./views/app.view.tsx";
+import { CallbackView } from "./views/auth/callback.view.tsx";
+import { DashboardView } from "./views/dashboard/dashboard.view.tsx";
+
+const root = createRootRoute();
+
+const callback = createRoute({
+ getParentRoute: () => root,
+ path: "/callback",
+ component: CallbackView,
+});
+
+const app = createRoute({
+ id: "app",
+ getParentRoute: () => root,
+ component: AppView,
+});
+
+const dashboard = createRoute({
+ getParentRoute: () => app,
+ path: "/",
+ component: DashboardView,
+});
+
+root.addChildren([app, callback]);
+app.addChildren([dashboard]);
+
+export const routeTree = root;
diff --git a/apps/react/src/services/api.ts b/apps/react/src/services/api.ts
new file mode 100644
index 0000000..29060e7
--- /dev/null
+++ b/apps/react/src/services/api.ts
@@ -0,0 +1,15 @@
+import { account } from "@module/account/client";
+import { makeClient } from "@platform/relay";
+
+import { HttpAdapter } from "../adapters/http.ts";
+
+export const api = makeClient(
+ {
+ adapter: new HttpAdapter({
+ url: window.location.origin,
+ }),
+ },
+ {
+ account,
+ },
+);
diff --git a/apps/react/src/services/zitadel.ts b/apps/react/src/services/zitadel.ts
new file mode 100644
index 0000000..50abd02
--- /dev/null
+++ b/apps/react/src/services/zitadel.ts
@@ -0,0 +1,14 @@
+import { createZitadelAuth, type ZitadelConfig } from "@zitadel/react";
+
+const config: ZitadelConfig = {
+ authority: "https://auth.valkyrjs.com",
+ client_id: "347982179092987909",
+ redirect_uri: "http://localhost:5173/callback",
+ post_logout_redirect_uri: "http://localhost:5173",
+ response_type: "code",
+ scope: "openid profile email",
+};
+
+export const zitadel = createZitadelAuth(config);
+
+export type User = NonNullable>>;
diff --git a/apps/react/src/stores/database.ts b/apps/react/src/stores/database.ts
new file mode 100644
index 0000000..15e9561
--- /dev/null
+++ b/apps/react/src/stores/database.ts
@@ -0,0 +1,14 @@
+import { IndexedDatabase } from "@valkyr/db";
+
+import type { Todo } from "./todo.ts";
+import type { TodoItem } from "./todo-item.ts";
+import type { User } from "./user.ts";
+
+export const db = new IndexedDatabase<{
+ todos: Todo;
+ todoItems: TodoItem;
+ users: User;
+}>({
+ name: "app:valkyr",
+ registrars: [{ name: "todos", indexes: [["name", { unique: true }]] }, { name: "todoItems" }, { name: "users" }],
+});
diff --git a/apps/react/src/stores/todo-item.ts b/apps/react/src/stores/todo-item.ts
new file mode 100644
index 0000000..8c1719a
--- /dev/null
+++ b/apps/react/src/stores/todo-item.ts
@@ -0,0 +1,9 @@
+import z from "zod";
+
+export const TodoItemSchema = z.object({
+ id: z.string(),
+ task: z.string(),
+ completedAt: z.string(),
+});
+
+export type TodoItem = z.infer;
diff --git a/apps/react/src/stores/todo.ts b/apps/react/src/stores/todo.ts
new file mode 100644
index 0000000..2fad59d
--- /dev/null
+++ b/apps/react/src/stores/todo.ts
@@ -0,0 +1,11 @@
+import z from "zod";
+
+import { TodoItemSchema } from "./todo-item.ts";
+
+export const TodoSchema = z.object({
+ id: z.string(),
+ name: z.string(),
+ items: z.array(TodoItemSchema).default([]),
+});
+
+export type Todo = z.infer;
diff --git a/apps/react/src/stores/user.ts b/apps/react/src/stores/user.ts
new file mode 100644
index 0000000..071f4db
--- /dev/null
+++ b/apps/react/src/stores/user.ts
@@ -0,0 +1,11 @@
+import { ContactSchema } from "@spec/schemas/contact.ts";
+import { NameSchema } from "@spec/schemas/name.ts";
+import z from "zod";
+
+export const UserSchema = z.object({
+ id: z.string(),
+ name: NameSchema,
+ contact: ContactSchema,
+});
+
+export type User = z.infer;
diff --git a/apps/react/src/theme.css b/apps/react/src/theme.css
new file mode 100644
index 0000000..fd549d6
--- /dev/null
+++ b/apps/react/src/theme.css
@@ -0,0 +1,105 @@
+body {
+ @apply overscroll-none bg-transparent;
+}
+
+:root {
+ --font-sans: var(--font-inter);
+ --header-height: calc(var(--spacing) * 12 + 1px);
+}
+
+.theme-scaled {
+ @media (min-width: 1024px) {
+ --radius: 0.6rem;
+ --text-lg: 1.05rem;
+ --text-base: 0.85rem;
+ --text-sm: 0.8rem;
+ --spacing: 0.222222rem;
+ }
+
+ [data-slot="card"] {
+ --spacing: 0.16rem;
+ }
+
+ [data-slot="select-trigger"],
+ [data-slot="toggle-group-item"] {
+ --spacing: 0.222222rem;
+ }
+}
+
+.theme-default,
+.theme-default-scaled {
+ --primary: var(--color-neutral-600);
+ --primary-foreground: var(--color-neutral-50);
+
+ @variant dark {
+ --primary: var(--color-neutral-500);
+ --primary-foreground: var(--color-neutral-50);
+ }
+}
+
+.theme-blue,
+.theme-blue-scaled {
+ --primary: var(--color-blue-600);
+ --primary-foreground: var(--color-blue-50);
+
+ @variant dark {
+ --primary: var(--color-blue-500);
+ --primary-foreground: var(--color-blue-50);
+ }
+}
+
+.theme-green,
+.theme-green-scaled {
+ --primary: var(--color-lime-600);
+ --primary-foreground: var(--color-lime-50);
+
+ @variant dark {
+ --primary: var(--color-lime-600);
+ --primary-foreground: var(--color-lime-50);
+ }
+}
+
+.theme-amber,
+.theme-amber-scaled {
+ --primary: var(--color-amber-600);
+ --primary-foreground: var(--color-amber-50);
+
+ @variant dark {
+ --primary: var(--color-amber-500);
+ --primary-foreground: var(--color-amber-50);
+ }
+}
+
+.theme-mono,
+.theme-mono-scaled {
+ --font-sans: var(--font-mono);
+ --primary: var(--color-neutral-600);
+ --primary-foreground: var(--color-neutral-50);
+
+ @variant dark {
+ --primary: var(--color-neutral-500);
+ --primary-foreground: var(--color-neutral-50);
+ }
+
+ .rounded-xs,
+ .rounded-sm,
+ .rounded-md,
+ .rounded-lg,
+ .rounded-xl {
+ @apply !rounded-none;
+ border-radius: 0;
+ }
+
+ .shadow-xs,
+ .shadow-sm,
+ .shadow-md,
+ .shadow-lg,
+ .shadow-xl {
+ @apply !shadow-none;
+ }
+
+ [data-slot="toggle-group"],
+ [data-slot="toggle-group-item"] {
+ @apply !rounded-none !shadow-none;
+ }
+}
diff --git a/apps/react/src/views/app.controller.ts b/apps/react/src/views/app.controller.ts
new file mode 100644
index 0000000..b5c1fc7
--- /dev/null
+++ b/apps/react/src/views/app.controller.ts
@@ -0,0 +1,25 @@
+import { Controller } from "../libraries/controller.ts";
+import { zitadel } from "../services/zitadel.ts";
+
+export class AppController extends Controller<{
+ authenticated: boolean;
+}> {
+ async onInit() {
+ return {
+ authenticated: await this.#getAuthenticatedState(),
+ };
+ }
+
+ async #getAuthenticatedState(): Promise {
+ const user = await zitadel.userManager.getUser();
+ if (user === null) {
+ zitadel.authorize();
+ return false;
+ }
+ return true;
+ }
+
+ signout() {
+ zitadel.signout();
+ }
+}
diff --git a/apps/react/src/views/app.view.tsx b/apps/react/src/views/app.view.tsx
new file mode 100644
index 0000000..8b83ee8
--- /dev/null
+++ b/apps/react/src/views/app.view.tsx
@@ -0,0 +1,40 @@
+import { Outlet } from "@tanstack/react-router";
+
+import { AppSidebar } from "@/components/app-sidebar.tsx";
+import { SiteHeader } from "@/components/site-header.tsx";
+import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar.tsx";
+import { useController } from "@/libraries/controller.ts";
+
+import { AppController } from "./app.controller.ts";
+
+export function AppView() {
+ const [{ authenticated }, loading] = useController(AppController);
+ if (loading === true) {
+ return Loading ...
;
+ }
+ if (authenticated === false) {
+ return Unauthenticated
;
+ }
+ return (
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/react/src/views/auth/callback.view.tsx b/apps/react/src/views/auth/callback.view.tsx
new file mode 100644
index 0000000..2f1bec9
--- /dev/null
+++ b/apps/react/src/views/auth/callback.view.tsx
@@ -0,0 +1,26 @@
+import { useNavigate } from "@tanstack/react-router";
+import { useEffect } from "react";
+
+import { zitadel } from "../../services/zitadel.ts";
+
+export function CallbackView() {
+ const navigate = useNavigate();
+ useEffect(() => {
+ async function handleCallback() {
+ try {
+ const user = await zitadel.userManager.signinRedirectCallback();
+ if (user) {
+ navigate({ to: "/", replace: true });
+ } else {
+ navigate({ to: "/", replace: true });
+ }
+ } catch (error) {
+ console.error("Callback error", error);
+ navigate({ to: "/", replace: true });
+ }
+ }
+ handleCallback();
+ }, [navigate]);
+
+ return null;
+}
diff --git a/apps/react/src/views/auth/login.controller.ts b/apps/react/src/views/auth/login.controller.ts
new file mode 100644
index 0000000..b1d0a55
--- /dev/null
+++ b/apps/react/src/views/auth/login.controller.ts
@@ -0,0 +1,29 @@
+import { Controller } from "../../libraries/controller.ts";
+import { type User, zitadel } from "../../services/zitadel.ts";
+
+export class LoginController extends Controller<{
+ user?: User;
+}> {
+ async onInit() {
+ return {
+ user: await this.#getAuthenticationState(),
+ };
+ }
+
+ async #getAuthenticationState(): Promise {
+ return zitadel.userManager.getUser().then((user) => {
+ if (user === null) {
+ return undefined;
+ }
+ return user;
+ });
+ }
+
+ login() {
+ zitadel.authorize();
+ }
+
+ logout() {
+ zitadel.signout();
+ }
+}
diff --git a/apps/react/src/views/auth/login.view.tsx b/apps/react/src/views/auth/login.view.tsx
new file mode 100644
index 0000000..10c3d21
--- /dev/null
+++ b/apps/react/src/views/auth/login.view.tsx
@@ -0,0 +1,14 @@
+import { useController } from "../../libraries/controller.ts";
+import { LoginController } from "./login.controller.ts";
+
+export function LoginView() {
+ const [{ user }, { login, logout }] = useController(LoginController);
+ return (
+
+
+ {user !== undefined ?
{JSON.stringify(user, null, 2)} : null}
+
+ );
+}
diff --git a/apps/react/src/views/dashboard/dashboard.view.tsx b/apps/react/src/views/dashboard/dashboard.view.tsx
new file mode 100644
index 0000000..6d0cf26
--- /dev/null
+++ b/apps/react/src/views/dashboard/dashboard.view.tsx
@@ -0,0 +1,3 @@
+export function DashboardView() {
+ return Dashboard
;
+}
diff --git a/apps/react/src/vite-env.d.ts b/apps/react/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/apps/react/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/apps/react/tailwind.config.js b/apps/react/tailwind.config.js
new file mode 100644
index 0000000..5e76857
--- /dev/null
+++ b/apps/react/tailwind.config.js
@@ -0,0 +1,9 @@
+module.exports = {
+ theme: {
+ extend: {
+ fontFamily: {
+ sans: ["Inter", "sans-serif"],
+ },
+ },
+ },
+};
diff --git a/apps/react/tsconfig.app.json b/apps/react/tsconfig.app.json
new file mode 100644
index 0000000..b02fee3
--- /dev/null
+++ b/apps/react/tsconfig.app.json
@@ -0,0 +1,34 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ // "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true,
+
+ "baseUrl": ".",
+ "paths": {
+ "@/*": [
+ "./src/*"
+ ]
+ }
+ },
+ "include": ["src"]
+}
diff --git a/apps/react/tsconfig.json b/apps/react/tsconfig.json
new file mode 100644
index 0000000..2b78387
--- /dev/null
+++ b/apps/react/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "files": [],
+ "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }],
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ }
+}
diff --git a/apps/react/tsconfig.node.json b/apps/react/tsconfig.node.json
new file mode 100644
index 0000000..99f7cb0
--- /dev/null
+++ b/apps/react/tsconfig.node.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2023",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ // "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/apps/react/vite.config.ts b/apps/react/vite.config.ts
new file mode 100644
index 0000000..366f799
--- /dev/null
+++ b/apps/react/vite.config.ts
@@ -0,0 +1,20 @@
+import path from "node:path"
+import tailwindcss from "@tailwindcss/vite";
+import react from "@vitejs/plugin-react";
+import { defineConfig } from "vite";
+
+export default defineConfig({
+ plugins: [react(), tailwindcss()],
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "./src"),
+ },
+ },
+ server: {
+ proxy: {
+ "/api/v1": {
+ target: "http://localhost:8370",
+ },
+ },
+ },
+});
diff --git a/biome.json b/biome.json
new file mode 100644
index 0000000..e01dce7
--- /dev/null
+++ b/biome.json
@@ -0,0 +1,45 @@
+{
+ "formatter": {
+ "enabled": true,
+ "formatWithErrors": false,
+ "indentStyle": "space",
+ "indentWidth": 2,
+ "lineEnding": "lf",
+ "lineWidth": 120,
+ "attributePosition": "auto"
+ },
+ "linter": {
+ "enabled": true,
+ "rules": {
+ "recommended": true,
+ "suspicious": {
+ "noConfusingVoidType": "off",
+ "noExplicitAny": "off"
+ },
+ "complexity": {
+ "noBannedTypes": "off"
+ }
+ }
+ },
+ "assist": {
+ "enabled": true,
+ "actions": {
+ "source": {
+ "organizeImports": {
+ "level": "on",
+ "options": {
+ "groups": [
+ [":BUN:", ":NODE:"],
+ ":BLANK_LINE:",
+ ":PACKAGE:",
+ ":BLANK_LINE:",
+ [":ALIAS:"],
+ ":BLANK_LINE:",
+ ":PATH:"
+ ]
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/cerbos.yaml b/cerbos.yaml
new file mode 100644
index 0000000..9df1d0f
--- /dev/null
+++ b/cerbos.yaml
@@ -0,0 +1,13 @@
+server:
+ adminAPI:
+ enabled: true
+ adminCredentials:
+ username: cerbos
+ passwordHash: JDJ5JDEwJDc5VzBkQ0NUWHFTT3N1OW9xZkx5ZC43M0tuM0JBSTU0dVRsMVBkOEtuYVBCaWFzVXk5d0phCgo=
+ httpListenAddr: ":3592"
+ grpcListenAddr: ":3593"
+
+storage:
+ driver: "sqlite3"
+ sqlite3:
+ dsn: "file:/tmp/cerbos.sqlite?mode=rwc&cache=shared&_fk=true"
diff --git a/deno.json b/deno.json
index 9a19b9a..5d147e6 100644
--- a/deno.json
+++ b/deno.json
@@ -1,23 +1,43 @@
{
- "name": "@valkyr/relay",
- "version": "0.1.0",
- "exports": {
- ".": "./mod.ts"
- },
- "publish": {
- "exclude": [
- ".github",
- ".vscode",
- ".gitignore",
- "tests"
- ]
- },
+ "unstable": ["fmt-component"],
+ "nodeModulesDir": "auto",
+ "workspace": [
+ "api",
+ "apps/react",
+ "modules/account",
+ "modules/tenant",
+ "platform/cerbos",
+ "platform/config",
+ "platform/database",
+ "platform/logger",
+ "platform/relay",
+ "platform/routes",
+ "platform/server",
+ "platform/socket",
+ "platform/spec",
+ "platform/storage",
+ "platform/vault"
+ ],
"tasks": {
- "check": "deno check ./mod.ts",
- "lint": "npx eslint -c eslint.config.mjs .",
- "test": "deno test --allow-all",
- "test:publish": "deno publish --dry-run",
- "ncu": "npx ncu -u -p npm"
- },
- "nodeModulesDir": "auto"
+ "api": {
+ "command": "cd ./api && deno run start",
+ "description": "Start api server instance."
+ },
+ "react": {
+ "command": "cd ./apps/react && deno run dev",
+ "description": "Start react application instance."
+ },
+ "check": {
+ "command": "deno run -A npm:@biomejs/biome check --write ./api ./apps/react/src ./modules ./platform",
+ "description": "Format, lint, and organize imports of the entire project."
+ },
+ "test": {
+ "command": "deno test --allow-all",
+ "description": "Runs all defined tests across the entire project."
+ },
+ "ncu": {
+ "command": "npx ncu -u -p npm",
+ "description": "Updates all the dependencies in package.json to their latest versions."
+ }
+ }
}
diff --git a/deno.lock b/deno.lock
index a54e3c5..769b4a3 100644
--- a/deno.lock
+++ b/deno.lock
@@ -1,17 +1,408 @@
{
- "version": "4",
+ "version": "5",
"specifiers": {
- "npm:@jsr/std__assert@1.0.12": "1.0.12",
- "npm:@jsr/std__testing@1.0.11": "1.0.11",
- "npm:eslint-plugin-simple-import-sort@12.1.1": "12.1.1_eslint@9.24.0",
- "npm:eslint@9.24.0": "9.24.0",
- "npm:prettier@3.5.3": "3.5.3",
- "npm:typescript-eslint@8.30.1": "8.30.1_eslint@9.24.0_typescript@5.8.3_@typescript-eslint+parser@8.30.1__eslint@9.24.0__typescript@5.8.3",
- "npm:zod@next": "4.0.0-beta.20250417T043022"
+ "npm:@biomejs/biome@*": "2.2.4",
+ "npm:@biomejs/biome@2.2.4": "2.2.4",
+ "npm:@cerbos/core@0.25.1": "0.25.1_@bufbuild+protobuf@2.10.1",
+ "npm:@cerbos/http@0.23.3": "0.23.3_@bufbuild+protobuf@2.10.1_@cerbos+api@0.2.0",
+ "npm:@eslint/js@9.35.0": "9.35.0",
+ "npm:@jsr/std__assert@1.0.14": "1.0.14",
+ "npm:@jsr/std__dotenv@0.225.5": "0.225.5",
+ "npm:@jsr/std__testing@1.0.15": "1.0.15",
+ "npm:@jsr/valkyr__db@2.0.0": "2.0.0",
+ "npm:@jsr/valkyr__event-emitter@1.0.1": "1.0.1",
+ "npm:@jsr/valkyr__event-store@2.0.1": "2.0.1",
+ "npm:@jsr/valkyr__json-rpc@1.1.0": "1.1.0",
+ "npm:@radix-ui/react-avatar@^1.1.11": "1.1.11_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
+ "npm:@radix-ui/react-dialog@^1.1.15": "1.1.15_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
+ "npm:@radix-ui/react-dropdown-menu@^2.1.16": "2.1.16_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
+ "npm:@radix-ui/react-scroll-area@^1.2.10": "1.2.10_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
+ "npm:@radix-ui/react-separator@^1.1.8": "1.1.8_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
+ "npm:@radix-ui/react-slot@^1.2.4": "1.2.4_@types+react@19.1.13_react@19.1.1",
+ "npm:@radix-ui/react-tooltip@^1.2.8": "1.2.8_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
+ "npm:@tabler/icons-react@3.35.0": "3.35.0_react@19.1.1",
+ "npm:@tailwindcss/vite@4.1.13": "4.1.13_vite@7.1.6__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0",
+ "npm:@tanstack/react-query@5.89.0": "5.89.0_react@19.1.1",
+ "npm:@tanstack/react-router-devtools@1.131.47": "1.131.47_@tanstack+react-router@1.131.47__react@19.1.1__react-dom@19.1.1___react@19.1.1_react@19.1.1_react-dom@19.1.1__react@19.1.1",
+ "npm:@tanstack/react-router@1.131.47": "1.131.47_react@19.1.1_react-dom@19.1.1__react@19.1.1",
+ "npm:@types/node@*": "24.2.0",
+ "npm:@types/react-dom@19.1.9": "19.1.9_@types+react@19.1.13",
+ "npm:@types/react@19.1.13": "19.1.13",
+ "npm:@vitejs/plugin-react@4.7.0": "4.7.0_vite@7.1.6__@types+node@24.2.0__picomatch@4.0.3_@babel+core@7.28.4_@types+node@24.2.0",
+ "npm:@zitadel/react@1.1.0": "1.1.0",
+ "npm:class-variance-authority@~0.7.1": "0.7.1",
+ "npm:clsx@^2.1.1": "2.1.1",
+ "npm:eslint-plugin-react-hooks@5.2.0": "5.2.0_eslint@9.35.0",
+ "npm:eslint-plugin-react-refresh@0.4.20": "0.4.20_eslint@9.35.0",
+ "npm:eslint@9.35.0": "9.35.0",
+ "npm:fast-equals@5.2.2": "5.2.2",
+ "npm:globals@16.4.0": "16.4.0",
+ "npm:jose@6.1.0": "6.1.0",
+ "npm:lucide-react@0.554": "0.554.0_react@19.1.1",
+ "npm:nanoid@5.1.5": "5.1.5",
+ "npm:path-to-regexp@8": "8.3.0",
+ "npm:postgres@3.4.7": "3.4.7",
+ "npm:react-dom@19.1.1": "19.1.1_react@19.1.1",
+ "npm:react@19.1.1": "19.1.1",
+ "npm:tailwind-merge@^3.4.0": "3.4.0",
+ "npm:tailwindcss-animate@^1.0.7": "1.0.7_tailwindcss@4.1.13",
+ "npm:tailwindcss@4.1.13": "4.1.13",
+ "npm:tw-animate-css@1.4.0": "1.4.0",
+ "npm:typescript-eslint@8.44.0": "8.44.0_eslint@9.35.0_typescript@5.9.2_@typescript-eslint+parser@8.44.0__eslint@9.35.0__typescript@5.9.2",
+ "npm:typescript@5.9.2": "5.9.2",
+ "npm:vite@7.1.6": "7.1.6_@types+node@24.2.0_picomatch@4.0.3",
+ "npm:zod@4.1.12": "4.1.12"
},
"npm": {
- "@eslint-community/eslint-utils@4.6.1_eslint@9.24.0": {
- "integrity": "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==",
+ "@babel/code-frame@7.27.1": {
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "dependencies": [
+ "@babel/helper-validator-identifier",
+ "js-tokens",
+ "picocolors"
+ ]
+ },
+ "@babel/compat-data@7.28.4": {
+ "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw=="
+ },
+ "@babel/core@7.28.4": {
+ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
+ "dependencies": [
+ "@babel/code-frame",
+ "@babel/generator",
+ "@babel/helper-compilation-targets",
+ "@babel/helper-module-transforms",
+ "@babel/helpers",
+ "@babel/parser",
+ "@babel/template",
+ "@babel/traverse",
+ "@babel/types",
+ "@jridgewell/remapping",
+ "convert-source-map",
+ "debug",
+ "gensync",
+ "json5",
+ "semver@6.3.1"
+ ]
+ },
+ "@babel/generator@7.28.3": {
+ "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
+ "dependencies": [
+ "@babel/parser",
+ "@babel/types",
+ "@jridgewell/gen-mapping",
+ "@jridgewell/trace-mapping",
+ "jsesc"
+ ]
+ },
+ "@babel/helper-compilation-targets@7.27.2": {
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "dependencies": [
+ "@babel/compat-data",
+ "@babel/helper-validator-option",
+ "browserslist",
+ "lru-cache",
+ "semver@6.3.1"
+ ]
+ },
+ "@babel/helper-globals@7.28.0": {
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="
+ },
+ "@babel/helper-module-imports@7.27.1": {
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "dependencies": [
+ "@babel/traverse",
+ "@babel/types"
+ ]
+ },
+ "@babel/helper-module-transforms@7.28.3_@babel+core@7.28.4": {
+ "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
+ "dependencies": [
+ "@babel/core",
+ "@babel/helper-module-imports",
+ "@babel/helper-validator-identifier",
+ "@babel/traverse"
+ ]
+ },
+ "@babel/helper-plugin-utils@7.27.1": {
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="
+ },
+ "@babel/helper-string-parser@7.27.1": {
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="
+ },
+ "@babel/helper-validator-identifier@7.27.1": {
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="
+ },
+ "@babel/helper-validator-option@7.27.1": {
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="
+ },
+ "@babel/helpers@7.28.4": {
+ "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
+ "dependencies": [
+ "@babel/template",
+ "@babel/types"
+ ]
+ },
+ "@babel/parser@7.28.4": {
+ "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
+ "dependencies": [
+ "@babel/types"
+ ],
+ "bin": true
+ },
+ "@babel/plugin-transform-react-jsx-self@7.27.1_@babel+core@7.28.4": {
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dependencies": [
+ "@babel/core",
+ "@babel/helper-plugin-utils"
+ ]
+ },
+ "@babel/plugin-transform-react-jsx-source@7.27.1_@babel+core@7.28.4": {
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dependencies": [
+ "@babel/core",
+ "@babel/helper-plugin-utils"
+ ]
+ },
+ "@babel/template@7.27.2": {
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "dependencies": [
+ "@babel/code-frame",
+ "@babel/parser",
+ "@babel/types"
+ ]
+ },
+ "@babel/traverse@7.28.4": {
+ "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==",
+ "dependencies": [
+ "@babel/code-frame",
+ "@babel/generator",
+ "@babel/helper-globals",
+ "@babel/parser",
+ "@babel/template",
+ "@babel/types",
+ "debug"
+ ]
+ },
+ "@babel/types@7.28.4": {
+ "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
+ "dependencies": [
+ "@babel/helper-string-parser",
+ "@babel/helper-validator-identifier"
+ ]
+ },
+ "@biomejs/biome@2.2.4": {
+ "integrity": "sha512-TBHU5bUy/Ok6m8c0y3pZiuO/BZoY/OcGxoLlrfQof5s8ISVwbVBdFINPQZyFfKwil8XibYWb7JMwnT8wT4WVPg==",
+ "optionalDependencies": [
+ "@biomejs/cli-darwin-arm64",
+ "@biomejs/cli-darwin-x64",
+ "@biomejs/cli-linux-arm64",
+ "@biomejs/cli-linux-arm64-musl",
+ "@biomejs/cli-linux-x64",
+ "@biomejs/cli-linux-x64-musl",
+ "@biomejs/cli-win32-arm64",
+ "@biomejs/cli-win32-x64"
+ ],
+ "bin": true
+ },
+ "@biomejs/cli-darwin-arm64@2.2.4": {
+ "integrity": "sha512-RJe2uiyaloN4hne4d2+qVj3d3gFJFbmrr5PYtkkjei1O9c+BjGXgpUPVbi8Pl8syumhzJjFsSIYkcLt2VlVLMA==",
+ "os": ["darwin"],
+ "cpu": ["arm64"]
+ },
+ "@biomejs/cli-darwin-x64@2.2.4": {
+ "integrity": "sha512-cFsdB4ePanVWfTnPVaUX+yr8qV8ifxjBKMkZwN7gKb20qXPxd/PmwqUH8mY5wnM9+U0QwM76CxFyBRJhC9tQwg==",
+ "os": ["darwin"],
+ "cpu": ["x64"]
+ },
+ "@biomejs/cli-linux-arm64-musl@2.2.4": {
+ "integrity": "sha512-7TNPkMQEWfjvJDaZRSkDCPT/2r5ESFPKx+TEev+I2BXDGIjfCZk2+b88FOhnJNHtksbOZv8ZWnxrA5gyTYhSsQ==",
+ "os": ["linux"],
+ "cpu": ["arm64"]
+ },
+ "@biomejs/cli-linux-arm64@2.2.4": {
+ "integrity": "sha512-M/Iz48p4NAzMXOuH+tsn5BvG/Jb07KOMTdSVwJpicmhN309BeEyRyQX+n1XDF0JVSlu28+hiTQ2L4rZPvu7nMw==",
+ "os": ["linux"],
+ "cpu": ["arm64"]
+ },
+ "@biomejs/cli-linux-x64-musl@2.2.4": {
+ "integrity": "sha512-m41nFDS0ksXK2gwXL6W6yZTYPMH0LughqbsxInSKetoH6morVj43szqKx79Iudkp8WRT5SxSh7qVb8KCUiewGg==",
+ "os": ["linux"],
+ "cpu": ["x64"]
+ },
+ "@biomejs/cli-linux-x64@2.2.4": {
+ "integrity": "sha512-orr3nnf2Dpb2ssl6aihQtvcKtLySLta4E2UcXdp7+RTa7mfJjBgIsbS0B9GC8gVu0hjOu021aU8b3/I1tn+pVQ==",
+ "os": ["linux"],
+ "cpu": ["x64"]
+ },
+ "@biomejs/cli-win32-arm64@2.2.4": {
+ "integrity": "sha512-NXnfTeKHDFUWfxAefa57DiGmu9VyKi0cDqFpdI+1hJWQjGJhJutHPX0b5m+eXvTKOaf+brU+P0JrQAZMb5yYaQ==",
+ "os": ["win32"],
+ "cpu": ["arm64"]
+ },
+ "@biomejs/cli-win32-x64@2.2.4": {
+ "integrity": "sha512-3Y4V4zVRarVh/B/eSHczR4LYoSVyv3Dfuvm3cWs5w/HScccS0+Wt/lHOcDTRYeHjQmMYVC3rIRWqyN2EI52+zg==",
+ "os": ["win32"],
+ "cpu": ["x64"]
+ },
+ "@bufbuild/protobuf@2.10.1": {
+ "integrity": "sha512-ckS3+vyJb5qGpEYv/s1OebUHDi/xSNtfgw1wqKZo7MR9F2z+qXr0q5XagafAG/9O0QPVIUfST0smluYSTpYFkg=="
+ },
+ "@cerbos/api@0.2.0": {
+ "integrity": "sha512-p3kAfmgz0WwxXBJ8Dt1vugV/QjQoPtF5b1R+h16YnUPZ6O4YL8D9gjz5WQRg/0FllodyaEtJlrMPxGEvJetkIw==",
+ "dependencies": [
+ "@bufbuild/protobuf"
+ ]
+ },
+ "@cerbos/core@0.25.1_@bufbuild+protobuf@2.10.1": {
+ "integrity": "sha512-aPi8IqqgGHq9xyoBk6dYAKQ1U1athW0YZfI+7lzxxwpLlNFdZ9EwJLhaRSUFgYpUS2TBWDtX094Yn1kgB1esCQ==",
+ "dependencies": [
+ "@bufbuild/protobuf",
+ "@cerbos/api",
+ "uuid"
+ ]
+ },
+ "@cerbos/http@0.23.3_@bufbuild+protobuf@2.10.1_@cerbos+api@0.2.0": {
+ "integrity": "sha512-yf8s4v+T4sf/ZiLorHpXhdStOr+q5XEjF2m/yvpcR7E/7e5eGCr5yEov9NIgfRQg1HGW8h+B6CIFBl9amSsaGw==",
+ "dependencies": [
+ "@bufbuild/protobuf",
+ "@cerbos/api",
+ "@cerbos/core",
+ "qs"
+ ]
+ },
+ "@esbuild/aix-ppc64@0.25.10": {
+ "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==",
+ "os": ["aix"],
+ "cpu": ["ppc64"]
+ },
+ "@esbuild/android-arm64@0.25.10": {
+ "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==",
+ "os": ["android"],
+ "cpu": ["arm64"]
+ },
+ "@esbuild/android-arm@0.25.10": {
+ "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==",
+ "os": ["android"],
+ "cpu": ["arm"]
+ },
+ "@esbuild/android-x64@0.25.10": {
+ "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==",
+ "os": ["android"],
+ "cpu": ["x64"]
+ },
+ "@esbuild/darwin-arm64@0.25.10": {
+ "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==",
+ "os": ["darwin"],
+ "cpu": ["arm64"]
+ },
+ "@esbuild/darwin-x64@0.25.10": {
+ "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==",
+ "os": ["darwin"],
+ "cpu": ["x64"]
+ },
+ "@esbuild/freebsd-arm64@0.25.10": {
+ "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==",
+ "os": ["freebsd"],
+ "cpu": ["arm64"]
+ },
+ "@esbuild/freebsd-x64@0.25.10": {
+ "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==",
+ "os": ["freebsd"],
+ "cpu": ["x64"]
+ },
+ "@esbuild/linux-arm64@0.25.10": {
+ "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==",
+ "os": ["linux"],
+ "cpu": ["arm64"]
+ },
+ "@esbuild/linux-arm@0.25.10": {
+ "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==",
+ "os": ["linux"],
+ "cpu": ["arm"]
+ },
+ "@esbuild/linux-ia32@0.25.10": {
+ "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==",
+ "os": ["linux"],
+ "cpu": ["ia32"]
+ },
+ "@esbuild/linux-loong64@0.25.10": {
+ "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==",
+ "os": ["linux"],
+ "cpu": ["loong64"]
+ },
+ "@esbuild/linux-mips64el@0.25.10": {
+ "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==",
+ "os": ["linux"],
+ "cpu": ["mips64el"]
+ },
+ "@esbuild/linux-ppc64@0.25.10": {
+ "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==",
+ "os": ["linux"],
+ "cpu": ["ppc64"]
+ },
+ "@esbuild/linux-riscv64@0.25.10": {
+ "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==",
+ "os": ["linux"],
+ "cpu": ["riscv64"]
+ },
+ "@esbuild/linux-s390x@0.25.10": {
+ "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==",
+ "os": ["linux"],
+ "cpu": ["s390x"]
+ },
+ "@esbuild/linux-x64@0.25.10": {
+ "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==",
+ "os": ["linux"],
+ "cpu": ["x64"]
+ },
+ "@esbuild/netbsd-arm64@0.25.10": {
+ "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==",
+ "os": ["netbsd"],
+ "cpu": ["arm64"]
+ },
+ "@esbuild/netbsd-x64@0.25.10": {
+ "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==",
+ "os": ["netbsd"],
+ "cpu": ["x64"]
+ },
+ "@esbuild/openbsd-arm64@0.25.10": {
+ "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==",
+ "os": ["openbsd"],
+ "cpu": ["arm64"]
+ },
+ "@esbuild/openbsd-x64@0.25.10": {
+ "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==",
+ "os": ["openbsd"],
+ "cpu": ["x64"]
+ },
+ "@esbuild/openharmony-arm64@0.25.10": {
+ "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==",
+ "os": ["openharmony"],
+ "cpu": ["arm64"]
+ },
+ "@esbuild/sunos-x64@0.25.10": {
+ "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==",
+ "os": ["sunos"],
+ "cpu": ["x64"]
+ },
+ "@esbuild/win32-arm64@0.25.10": {
+ "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==",
+ "os": ["win32"],
+ "cpu": ["arm64"]
+ },
+ "@esbuild/win32-ia32@0.25.10": {
+ "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==",
+ "os": ["win32"],
+ "cpu": ["ia32"]
+ },
+ "@esbuild/win32-x64@0.25.10": {
+ "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==",
+ "os": ["win32"],
+ "cpu": ["x64"]
+ },
+ "@eslint-community/eslint-utils@4.9.0_eslint@9.35.0": {
+ "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
"dependencies": [
"eslint",
"eslint-visitor-keys@3.4.3"
@@ -20,25 +411,19 @@
"@eslint-community/regexpp@4.12.1": {
"integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="
},
- "@eslint/config-array@0.20.0": {
- "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==",
+ "@eslint/config-array@0.21.0": {
+ "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
"dependencies": [
"@eslint/object-schema",
"debug",
"minimatch@3.1.2"
]
},
- "@eslint/config-helpers@0.2.1": {
- "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw=="
+ "@eslint/config-helpers@0.3.1": {
+ "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA=="
},
- "@eslint/core@0.12.0": {
- "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==",
- "dependencies": [
- "@types/json-schema"
- ]
- },
- "@eslint/core@0.13.0": {
- "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==",
+ "@eslint/core@0.15.2": {
+ "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==",
"dependencies": [
"@types/json-schema"
]
@@ -49,72 +434,147 @@
"ajv",
"debug",
"espree",
- "globals",
- "ignore",
+ "globals@14.0.0",
+ "ignore@5.3.2",
"import-fresh",
"js-yaml",
"minimatch@3.1.2",
"strip-json-comments"
]
},
- "@eslint/js@9.24.0": {
- "integrity": "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA=="
+ "@eslint/js@9.35.0": {
+ "integrity": "sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw=="
},
"@eslint/object-schema@2.1.6": {
"integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="
},
- "@eslint/plugin-kit@0.2.8": {
- "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==",
+ "@eslint/plugin-kit@0.3.5": {
+ "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==",
"dependencies": [
- "@eslint/core@0.13.0",
+ "@eslint/core",
"levn"
]
},
+ "@floating-ui/core@1.7.3": {
+ "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
+ "dependencies": [
+ "@floating-ui/utils"
+ ]
+ },
+ "@floating-ui/dom@1.7.4": {
+ "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
+ "dependencies": [
+ "@floating-ui/core",
+ "@floating-ui/utils"
+ ]
+ },
+ "@floating-ui/react-dom@2.1.6_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
+ "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==",
+ "dependencies": [
+ "@floating-ui/dom",
+ "react",
+ "react-dom"
+ ]
+ },
+ "@floating-ui/utils@0.2.10": {
+ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="
+ },
"@humanfs/core@0.19.1": {
"integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="
},
- "@humanfs/node@0.16.6": {
- "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
+ "@humanfs/node@0.16.7": {
+ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
"dependencies": [
"@humanfs/core",
- "@humanwhocodes/retry@0.3.1"
+ "@humanwhocodes/retry"
]
},
"@humanwhocodes/module-importer@1.0.1": {
"integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="
},
- "@humanwhocodes/retry@0.3.1": {
- "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="
+ "@humanwhocodes/retry@0.4.3": {
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="
},
- "@humanwhocodes/retry@0.4.2": {
- "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ=="
+ "@isaacs/fs-minipass@4.0.1": {
+ "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
+ "dependencies": [
+ "minipass"
+ ]
},
- "@jsr/std__assert@1.0.12": {
- "integrity": "sha512-9pmgjJhuljZCmLlbvsRV6aLT5+YCmhX/yIjaWYav7R7Vup2DOLAgpUOs4JkzRbwn7fdKYrwHT8+DjqPr7Ti8mg==",
+ "@jridgewell/gen-mapping@0.3.13": {
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dependencies": [
+ "@jridgewell/sourcemap-codec",
+ "@jridgewell/trace-mapping"
+ ]
+ },
+ "@jridgewell/remapping@2.3.5": {
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dependencies": [
+ "@jridgewell/gen-mapping",
+ "@jridgewell/trace-mapping"
+ ]
+ },
+ "@jridgewell/resolve-uri@3.1.2": {
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="
+ },
+ "@jridgewell/sourcemap-codec@1.5.5": {
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="
+ },
+ "@jridgewell/trace-mapping@0.3.31": {
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dependencies": [
+ "@jridgewell/resolve-uri",
+ "@jridgewell/sourcemap-codec"
+ ]
+ },
+ "@jsr/std__assert@1.0.14": {
+ "integrity": "sha512-BcjBimpxuy7mXjWo7sZ3TtPitx91w3UqssyY92RmJIuoMGYywZRGxaxqK9/oybljbZbZpPOSrkgQI9wKpgZ9vQ==",
"dependencies": [
"@jsr/std__internal"
- ]
+ ],
+ "tarball": "https://npm.jsr.io/~/11/@jsr/std__assert/1.0.14.tgz"
},
- "@jsr/std__async@1.0.12": {
- "integrity": "sha512-NUaSOcwMetVeVkIqet2Ammy2A5YxG8ViFxryBbTaC4h7l/cgAkU59U3zF58ek4Y8HZ0Nx5De7qBptPfp62kcgw=="
+ "@jsr/std__async@1.0.14": {
+ "integrity": "sha512-aIG8W3TOmW+lKdAJA5w56qASu9EiUmBXbhW6eAlSEUBid+KVESGqQygFFg+awt/c8K+qobVM6M/u3SbIy0NyUQ==",
+ "tarball": "https://npm.jsr.io/~/11/@jsr/std__async/1.0.14.tgz"
},
- "@jsr/std__data-structures@1.0.6": {
- "integrity": "sha512-Ejc8mHLuoYxXLu2zPquvqijdgQ19OV+1DdVDrLc/Cg+tiuGh4Dq2FSnLiPINh4lO1AJ3XcZcYPx38RxdsZcCOg=="
- },
- "@jsr/std__fs@1.0.16": {
- "integrity": "sha512-xnqp8XqEFN+ttkERg9GG+AxyipSd+rfCquLPviF5ZSwN6oCV1TM0ZNoKHXNk/EJAsz28YjF4sfgdJt8XwTV2UQ==",
+ "@jsr/std__data-structures@1.0.9": {
+ "integrity": "sha512-+mT4Nll6fx+CPNqrlC+huhIOYNSMS+KUdJ4B8NujiQrh/bq++ds5PXpEsfV5EPR+YuWcuDGG0P1DE+Rednd7Wg==",
"dependencies": [
+ "@jsr/std__assert"
+ ],
+ "tarball": "https://npm.jsr.io/~/11/@jsr/std__data-structures/1.0.9.tgz"
+ },
+ "@jsr/std__dotenv@0.225.5": {
+ "integrity": "sha512-qrBt3wfQgvXbjo+Up6lyzBGxk0IPhDqW9Jx7CJQUQpsxqhoqnBmD8gn0Mt8i+RHHI9uZFCO+FP122ClAC8yljg==",
+ "tarball": "https://npm.jsr.io/~/11/@jsr/std__dotenv/0.225.5.tgz"
+ },
+ "@jsr/std__fs@1.0.19": {
+ "integrity": "sha512-TEjyE8g+46jPlu7dJHLrwc8NMGl8zfG+JjWxyNQyDbxP0RtqZ4JmYZfR9vy4RWYWJQbLpw6Kbt2n+K/2zAO/JA==",
+ "dependencies": [
+ "@jsr/std__internal",
"@jsr/std__path"
- ]
+ ],
+ "tarball": "https://npm.jsr.io/~/11/@jsr/std__fs/1.0.19.tgz"
},
- "@jsr/std__internal@1.0.6": {
- "integrity": "sha512-1NLtCx9XAL44nt56gzmRSCgXjIthHVzK62fTkJdq8/XsP7eN9a21AZDpc0EGJ/cgvmmOB52UGh46OuKrrY7eVg=="
+ "@jsr/std__internal@1.0.10": {
+ "integrity": "sha512-fmD6yKep/sMnB2yPQU/REZG7Z4N9SZwcUBNnceo4QkXk67l3JEfxHoROQ/YHeVSOmq6x55Ra6nuMjz2ib3nj3g==",
+ "tarball": "https://npm.jsr.io/~/11/@jsr/std__internal/1.0.10.tgz"
},
- "@jsr/std__path@1.0.8": {
- "integrity": "sha512-eNBGlh/8ZVkMxtFH4bwIzlAeKoHYk5in4wrBZhi20zMdOiuX4QozP4+19mIXBT2lzHDjhuVLyECbhFeR304iDg=="
+ "@jsr/std__net@1.0.6": {
+ "integrity": "sha512-mh27Fw4UMCjGSIMoOhjia5cS5fNP9M9DZYhGB7EYSZNnzf/eguFiarii/W4oDwYMmnxCMouUzhc6Y7jFuwTzcg==",
+ "tarball": "https://npm.jsr.io/~/11/@jsr/std__net/1.0.6.tgz"
},
- "@jsr/std__testing@1.0.11": {
- "integrity": "sha512-pqQDYtIsaDf+x4NHQ+WiixRJ8DfhgFQRdlHWWssFAzIYwleR+VHLTNlgsgg+AH3mIIR+gTkBmKk21hTkM/WbMQ==",
+ "@jsr/std__path@1.1.2": {
+ "integrity": "sha512-5hkOR1s5M7am02Bn9KS+SNMNwUSivz7t7/w2HBhFIfO7Eh8+mWilaZ+1tdanV9aaSHr4c99Zo4Da+cCSuzUOdA==",
+ "dependencies": [
+ "@jsr/std__internal"
+ ],
+ "tarball": "https://npm.jsr.io/~/11/@jsr/std__path/1.1.2.tgz"
+ },
+ "@jsr/std__testing@1.0.15": {
+ "integrity": "sha512-NgQuXxTEG4ecbh2fzYbkJWJoBgPXwbv6bdsrAYSOeLpX2d+TROEzpErbWQXHi/yxZy/FNn9IF548ZDAqMZxi/g==",
"dependencies": [
"@jsr/std__assert",
"@jsr/std__async",
@@ -122,6 +582,59 @@
"@jsr/std__fs",
"@jsr/std__internal",
"@jsr/std__path"
+ ],
+ "tarball": "https://npm.jsr.io/~/11/@jsr/std__testing/1.0.15.tgz"
+ },
+ "@jsr/valkyr__db@2.0.0": {
+ "integrity": "sha512-0gIauba+vQW6ssqMACLO1Z/METlhzoX+y4t9Sawh/IafQ986Rgvp6gCI+WArp7vbsO5hpItixrqjkxnnNC+h5g==",
+ "dependencies": [
+ "bson",
+ "idb",
+ "mingo",
+ "rxjs"
+ ],
+ "tarball": "https://npm.jsr.io/~/11/@jsr/valkyr__db/2.0.0.tgz"
+ },
+ "@jsr/valkyr__event-emitter@1.0.1": {
+ "integrity": "sha512-mre5tWJddz8LylSQWuLOw3zgIxd2JmhGRV46jKXNPCGzY2NKJwGGT9H7SBw36RV4dW7jnnH2U1aCJkh8IS/pzA==",
+ "dependencies": [
+ "eventemitter3"
+ ],
+ "tarball": "https://npm.jsr.io/~/11/@jsr/valkyr__event-emitter/1.0.1.tgz"
+ },
+ "@jsr/valkyr__event-store@2.0.1": {
+ "integrity": "sha512-OvSPX0XH5+oS4zQh1O8J7JvsCoH5pBFNuJ1PdNA5B0OascrSWUqpxNEmytOtJhZuhfYzdvyOU1yNEvSI84D5wg==",
+ "dependencies": [
+ "@jsr/valkyr__db",
+ "@jsr/valkyr__testcontainers",
+ "mongodb",
+ "postgres",
+ "zod"
+ ],
+ "tarball": "https://npm.jsr.io/~/11/@jsr/valkyr__event-store/2.0.1.tgz"
+ },
+ "@jsr/valkyr__json-rpc@1.1.0": {
+ "integrity": "sha512-i1dwWLI29i5mqRvS2NbI3jUyw8uZuO71hJRvT5+sGAexG8RmQJP4N+ETJkxq0RNwNAGGG1bocuzdqnawa2ahIA==",
+ "dependencies": [
+ "@jsr/valkyr__event-emitter"
+ ],
+ "tarball": "https://npm.jsr.io/~/11/@jsr/valkyr__json-rpc/1.1.0.tgz"
+ },
+ "@jsr/valkyr__testcontainers@2.0.2": {
+ "integrity": "sha512-YnmfraYFr3msoUGrIFeElm03nbQqXOaPu0QUT6JI3w6/mIYpVfzPxghkB7gn2RIc81QgrqjwKJE/AL3dltlR1w==",
+ "dependencies": [
+ "@jsr/std__async",
+ "@jsr/std__fs",
+ "@jsr/std__net",
+ "mongodb",
+ "postgres"
+ ],
+ "tarball": "https://npm.jsr.io/~/11/@jsr/valkyr__testcontainers/2.0.2.tgz"
+ },
+ "@mongodb-js/saslprep@1.3.0": {
+ "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==",
+ "dependencies": [
+ "sparse-bitfield"
]
},
"@nodelib/fs.scandir@2.1.5": {
@@ -141,14 +654,882 @@
"fastq"
]
},
- "@types/estree@1.0.7": {
- "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="
+ "@radix-ui/number@1.1.1": {
+ "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="
+ },
+ "@radix-ui/primitive@1.1.3": {
+ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="
+ },
+ "@radix-ui/react-arrow@1.1.7_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
+ "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==",
+ "dependencies": [
+ "@radix-ui/react-primitive@2.1.3_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
+ "@types/react",
+ "@types/react-dom",
+ "react",
+ "react-dom"
+ ],
+ "optionalPeers": [
+ "@types/react",
+ "@types/react-dom"
+ ]
+ },
+ "@radix-ui/react-avatar@1.1.11_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
+ "integrity": "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==",
+ "dependencies": [
+ "@radix-ui/react-context@1.1.3_@types+react@19.1.13_react@19.1.1",
+ "@radix-ui/react-primitive@2.1.4_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
+ "@radix-ui/react-use-callback-ref",
+ "@radix-ui/react-use-is-hydrated",
+ "@radix-ui/react-use-layout-effect",
+ "@types/react",
+ "@types/react-dom",
+ "react",
+ "react-dom"
+ ],
+ "optionalPeers": [
+ "@types/react",
+ "@types/react-dom"
+ ]
+ },
+ "@radix-ui/react-collection@1.1.7_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
+ "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
+ "dependencies": [
+ "@radix-ui/react-compose-refs",
+ "@radix-ui/react-context@1.1.2_@types+react@19.1.13_react@19.1.1",
+ "@radix-ui/react-primitive@2.1.3_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
+ "@radix-ui/react-slot@1.2.3_@types+react@19.1.13_react@19.1.1",
+ "@types/react",
+ "@types/react-dom",
+ "react",
+ "react-dom"
+ ],
+ "optionalPeers": [
+ "@types/react",
+ "@types/react-dom"
+ ]
+ },
+ "@radix-ui/react-compose-refs@1.1.2_@types+react@19.1.13_react@19.1.1": {
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "dependencies": [
+ "@types/react",
+ "react"
+ ],
+ "optionalPeers": [
+ "@types/react"
+ ]
+ },
+ "@radix-ui/react-context@1.1.2_@types+react@19.1.13_react@19.1.1": {
+ "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
+ "dependencies": [
+ "@types/react",
+ "react"
+ ],
+ "optionalPeers": [
+ "@types/react"
+ ]
+ },
+ "@radix-ui/react-context@1.1.3_@types+react@19.1.13_react@19.1.1": {
+ "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==",
+ "dependencies": [
+ "@types/react",
+ "react"
+ ],
+ "optionalPeers": [
+ "@types/react"
+ ]
+ },
+ "@radix-ui/react-dialog@1.1.15_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
+ "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==",
+ "dependencies": [
+ "@radix-ui/primitive",
+ "@radix-ui/react-compose-refs",
+ "@radix-ui/react-context@1.1.2_@types+react@19.1.13_react@19.1.1",
+ "@radix-ui/react-dismissable-layer",
+ "@radix-ui/react-focus-guards",
+ "@radix-ui/react-focus-scope",
+ "@radix-ui/react-id",
+ "@radix-ui/react-portal",
+ "@radix-ui/react-presence",
+ "@radix-ui/react-primitive@2.1.3_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
+ "@radix-ui/react-slot@1.2.3_@types+react@19.1.13_react@19.1.1",
+ "@radix-ui/react-use-controllable-state",
+ "@types/react",
+ "@types/react-dom",
+ "aria-hidden",
+ "react",
+ "react-dom",
+ "react-remove-scroll"
+ ],
+ "optionalPeers": [
+ "@types/react",
+ "@types/react-dom"
+ ]
+ },
+ "@radix-ui/react-direction@1.1.1_@types+react@19.1.13_react@19.1.1": {
+ "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==",
+ "dependencies": [
+ "@types/react",
+ "react"
+ ],
+ "optionalPeers": [
+ "@types/react"
+ ]
+ },
+ "@radix-ui/react-dismissable-layer@1.1.11_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
+ "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==",
+ "dependencies": [
+ "@radix-ui/primitive",
+ "@radix-ui/react-compose-refs",
+ "@radix-ui/react-primitive@2.1.3_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
+ "@radix-ui/react-use-callback-ref",
+ "@radix-ui/react-use-escape-keydown",
+ "@types/react",
+ "@types/react-dom",
+ "react",
+ "react-dom"
+ ],
+ "optionalPeers": [
+ "@types/react",
+ "@types/react-dom"
+ ]
+ },
+ "@radix-ui/react-dropdown-menu@2.1.16_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
+ "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==",
+ "dependencies": [
+ "@radix-ui/primitive",
+ "@radix-ui/react-compose-refs",
+ "@radix-ui/react-context@1.1.2_@types+react@19.1.13_react@19.1.1",
+ "@radix-ui/react-id",
+ "@radix-ui/react-menu",
+ "@radix-ui/react-primitive@2.1.3_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
+ "@radix-ui/react-use-controllable-state",
+ "@types/react",
+ "@types/react-dom",
+ "react",
+ "react-dom"
+ ],
+ "optionalPeers": [
+ "@types/react",
+ "@types/react-dom"
+ ]
+ },
+ "@radix-ui/react-focus-guards@1.1.3_@types+react@19.1.13_react@19.1.1": {
+ "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==",
+ "dependencies": [
+ "@types/react",
+ "react"
+ ],
+ "optionalPeers": [
+ "@types/react"
+ ]
+ },
+ "@radix-ui/react-focus-scope@1.1.7_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
+ "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==",
+ "dependencies": [
+ "@radix-ui/react-compose-refs",
+ "@radix-ui/react-primitive@2.1.3_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
+ "@radix-ui/react-use-callback-ref",
+ "@types/react",
+ "@types/react-dom",
+ "react",
+ "react-dom"
+ ],
+ "optionalPeers": [
+ "@types/react",
+ "@types/react-dom"
+ ]
+ },
+ "@radix-ui/react-id@1.1.1_@types+react@19.1.13_react@19.1.1": {
+ "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
+ "dependencies": [
+ "@radix-ui/react-use-layout-effect",
+ "@types/react",
+ "react"
+ ],
+ "optionalPeers": [
+ "@types/react"
+ ]
+ },
+ "@radix-ui/react-menu@2.1.16_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
+ "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==",
+ "dependencies": [
+ "@radix-ui/primitive",
+ "@radix-ui/react-collection",
+ "@radix-ui/react-compose-refs",
+ "@radix-ui/react-context@1.1.2_@types+react@19.1.13_react@19.1.1",
+ "@radix-ui/react-direction",
+ "@radix-ui/react-dismissable-layer",
+ "@radix-ui/react-focus-guards",
+ "@radix-ui/react-focus-scope",
+ "@radix-ui/react-id",
+ "@radix-ui/react-popper",
+ "@radix-ui/react-portal",
+ "@radix-ui/react-presence",
+ "@radix-ui/react-primitive@2.1.3_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
+ "@radix-ui/react-roving-focus",
+ "@radix-ui/react-slot@1.2.3_@types+react@19.1.13_react@19.1.1",
+ "@radix-ui/react-use-callback-ref",
+ "@types/react",
+ "@types/react-dom",
+ "aria-hidden",
+ "react",
+ "react-dom",
+ "react-remove-scroll"
+ ],
+ "optionalPeers": [
+ "@types/react",
+ "@types/react-dom"
+ ]
+ },
+ "@radix-ui/react-popper@1.2.8_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
+ "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==",
+ "dependencies": [
+ "@floating-ui/react-dom",
+ "@radix-ui/react-arrow",
+ "@radix-ui/react-compose-refs",
+ "@radix-ui/react-context@1.1.2_@types+react@19.1.13_react@19.1.1",
+ "@radix-ui/react-primitive@2.1.3_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
+ "@radix-ui/react-use-callback-ref",
+ "@radix-ui/react-use-layout-effect",
+ "@radix-ui/react-use-rect",
+ "@radix-ui/react-use-size",
+ "@radix-ui/rect",
+ "@types/react",
+ "@types/react-dom",
+ "react",
+ "react-dom"
+ ],
+ "optionalPeers": [
+ "@types/react",
+ "@types/react-dom"
+ ]
+ },
+ "@radix-ui/react-portal@1.1.9_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
+ "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
+ "dependencies": [
+ "@radix-ui/react-primitive@2.1.3_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
+ "@radix-ui/react-use-layout-effect",
+ "@types/react",
+ "@types/react-dom",
+ "react",
+ "react-dom"
+ ],
+ "optionalPeers": [
+ "@types/react",
+ "@types/react-dom"
+ ]
+ },
+ "@radix-ui/react-presence@1.1.5_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
+ "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
+ "dependencies": [
+ "@radix-ui/react-compose-refs",
+ "@radix-ui/react-use-layout-effect",
+ "@types/react",
+ "@types/react-dom",
+ "react",
+ "react-dom"
+ ],
+ "optionalPeers": [
+ "@types/react",
+ "@types/react-dom"
+ ]
+ },
+ "@radix-ui/react-primitive@2.1.3_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "dependencies": [
+ "@radix-ui/react-slot@1.2.3_@types+react@19.1.13_react@19.1.1",
+ "@types/react",
+ "@types/react-dom",
+ "react",
+ "react-dom"
+ ],
+ "optionalPeers": [
+ "@types/react",
+ "@types/react-dom"
+ ]
+ },
+ "@radix-ui/react-primitive@2.1.4_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
+ "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
+ "dependencies": [
+ "@radix-ui/react-slot@1.2.4_@types+react@19.1.13_react@19.1.1",
+ "@types/react",
+ "@types/react-dom",
+ "react",
+ "react-dom"
+ ],
+ "optionalPeers": [
+ "@types/react",
+ "@types/react-dom"
+ ]
+ },
+ "@radix-ui/react-roving-focus@1.1.11_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
+ "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==",
+ "dependencies": [
+ "@radix-ui/primitive",
+ "@radix-ui/react-collection",
+ "@radix-ui/react-compose-refs",
+ "@radix-ui/react-context@1.1.2_@types+react@19.1.13_react@19.1.1",
+ "@radix-ui/react-direction",
+ "@radix-ui/react-id",
+ "@radix-ui/react-primitive@2.1.3_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
+ "@radix-ui/react-use-callback-ref",
+ "@radix-ui/react-use-controllable-state",
+ "@types/react",
+ "@types/react-dom",
+ "react",
+ "react-dom"
+ ],
+ "optionalPeers": [
+ "@types/react",
+ "@types/react-dom"
+ ]
+ },
+ "@radix-ui/react-scroll-area@1.2.10_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
+ "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==",
+ "dependencies": [
+ "@radix-ui/number",
+ "@radix-ui/primitive",
+ "@radix-ui/react-compose-refs",
+ "@radix-ui/react-context@1.1.2_@types+react@19.1.13_react@19.1.1",
+ "@radix-ui/react-direction",
+ "@radix-ui/react-presence",
+ "@radix-ui/react-primitive@2.1.3_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
+ "@radix-ui/react-use-callback-ref",
+ "@radix-ui/react-use-layout-effect",
+ "@types/react",
+ "@types/react-dom",
+ "react",
+ "react-dom"
+ ],
+ "optionalPeers": [
+ "@types/react",
+ "@types/react-dom"
+ ]
+ },
+ "@radix-ui/react-separator@1.1.8_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
+ "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==",
+ "dependencies": [
+ "@radix-ui/react-primitive@2.1.4_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
+ "@types/react",
+ "@types/react-dom",
+ "react",
+ "react-dom"
+ ],
+ "optionalPeers": [
+ "@types/react",
+ "@types/react-dom"
+ ]
+ },
+ "@radix-ui/react-slot@1.2.3_@types+react@19.1.13_react@19.1.1": {
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "dependencies": [
+ "@radix-ui/react-compose-refs",
+ "@types/react",
+ "react"
+ ],
+ "optionalPeers": [
+ "@types/react"
+ ]
+ },
+ "@radix-ui/react-slot@1.2.4_@types+react@19.1.13_react@19.1.1": {
+ "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==",
+ "dependencies": [
+ "@radix-ui/react-compose-refs",
+ "@types/react",
+ "react"
+ ],
+ "optionalPeers": [
+ "@types/react"
+ ]
+ },
+ "@radix-ui/react-tooltip@1.2.8_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
+ "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==",
+ "dependencies": [
+ "@radix-ui/primitive",
+ "@radix-ui/react-compose-refs",
+ "@radix-ui/react-context@1.1.2_@types+react@19.1.13_react@19.1.1",
+ "@radix-ui/react-dismissable-layer",
+ "@radix-ui/react-id",
+ "@radix-ui/react-popper",
+ "@radix-ui/react-portal",
+ "@radix-ui/react-presence",
+ "@radix-ui/react-primitive@2.1.3_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
+ "@radix-ui/react-slot@1.2.3_@types+react@19.1.13_react@19.1.1",
+ "@radix-ui/react-use-controllable-state",
+ "@radix-ui/react-visually-hidden",
+ "@types/react",
+ "@types/react-dom",
+ "react",
+ "react-dom"
+ ],
+ "optionalPeers": [
+ "@types/react",
+ "@types/react-dom"
+ ]
+ },
+ "@radix-ui/react-use-callback-ref@1.1.1_@types+react@19.1.13_react@19.1.1": {
+ "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
+ "dependencies": [
+ "@types/react",
+ "react"
+ ],
+ "optionalPeers": [
+ "@types/react"
+ ]
+ },
+ "@radix-ui/react-use-controllable-state@1.2.2_@types+react@19.1.13_react@19.1.1": {
+ "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
+ "dependencies": [
+ "@radix-ui/react-use-effect-event",
+ "@radix-ui/react-use-layout-effect",
+ "@types/react",
+ "react"
+ ],
+ "optionalPeers": [
+ "@types/react"
+ ]
+ },
+ "@radix-ui/react-use-effect-event@0.0.2_@types+react@19.1.13_react@19.1.1": {
+ "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
+ "dependencies": [
+ "@radix-ui/react-use-layout-effect",
+ "@types/react",
+ "react"
+ ],
+ "optionalPeers": [
+ "@types/react"
+ ]
+ },
+ "@radix-ui/react-use-escape-keydown@1.1.1_@types+react@19.1.13_react@19.1.1": {
+ "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==",
+ "dependencies": [
+ "@radix-ui/react-use-callback-ref",
+ "@types/react",
+ "react"
+ ],
+ "optionalPeers": [
+ "@types/react"
+ ]
+ },
+ "@radix-ui/react-use-is-hydrated@0.1.0_@types+react@19.1.13_react@19.1.1": {
+ "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==",
+ "dependencies": [
+ "@types/react",
+ "react",
+ "use-sync-external-store"
+ ],
+ "optionalPeers": [
+ "@types/react"
+ ]
+ },
+ "@radix-ui/react-use-layout-effect@1.1.1_@types+react@19.1.13_react@19.1.1": {
+ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+ "dependencies": [
+ "@types/react",
+ "react"
+ ],
+ "optionalPeers": [
+ "@types/react"
+ ]
+ },
+ "@radix-ui/react-use-rect@1.1.1_@types+react@19.1.13_react@19.1.1": {
+ "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==",
+ "dependencies": [
+ "@radix-ui/rect",
+ "@types/react",
+ "react"
+ ],
+ "optionalPeers": [
+ "@types/react"
+ ]
+ },
+ "@radix-ui/react-use-size@1.1.1_@types+react@19.1.13_react@19.1.1": {
+ "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
+ "dependencies": [
+ "@radix-ui/react-use-layout-effect",
+ "@types/react",
+ "react"
+ ],
+ "optionalPeers": [
+ "@types/react"
+ ]
+ },
+ "@radix-ui/react-visually-hidden@1.2.3_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
+ "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==",
+ "dependencies": [
+ "@radix-ui/react-primitive@2.1.3_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
+ "@types/react",
+ "@types/react-dom",
+ "react",
+ "react-dom"
+ ],
+ "optionalPeers": [
+ "@types/react",
+ "@types/react-dom"
+ ]
+ },
+ "@radix-ui/rect@1.1.1": {
+ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="
+ },
+ "@rolldown/pluginutils@1.0.0-beta.27": {
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="
+ },
+ "@rollup/rollup-android-arm-eabi@4.52.0": {
+ "integrity": "sha512-VxDYCDqOaR7NXzAtvRx7G1u54d2kEHopb28YH/pKzY6y0qmogP3gG7CSiWsq9WvDFxOQMpNEyjVAHZFXfH3o/A==",
+ "os": ["android"],
+ "cpu": ["arm"]
+ },
+ "@rollup/rollup-android-arm64@4.52.0": {
+ "integrity": "sha512-pqDirm8koABIKvzL59YI9W9DWbRlTX7RWhN+auR8HXJxo89m4mjqbah7nJZjeKNTNYopqL+yGg+0mhCpf3xZtQ==",
+ "os": ["android"],
+ "cpu": ["arm64"]
+ },
+ "@rollup/rollup-darwin-arm64@4.52.0": {
+ "integrity": "sha512-YCdWlY/8ltN6H78HnMsRHYlPiKvqKagBP1r+D7SSylxX+HnsgXGCmLiV3Y4nSyY9hW8qr8U9LDUx/Lo7M6MfmQ==",
+ "os": ["darwin"],
+ "cpu": ["arm64"]
+ },
+ "@rollup/rollup-darwin-x64@4.52.0": {
+ "integrity": "sha512-z4nw6y1j+OOSGzuVbSWdIp1IUks9qNw4dc7z7lWuWDKojY38VMWBlEN7F9jk5UXOkUcp97vA1N213DF+Lz8BRg==",
+ "os": ["darwin"],
+ "cpu": ["x64"]
+ },
+ "@rollup/rollup-freebsd-arm64@4.52.0": {
+ "integrity": "sha512-Q/dv9Yvyr5rKlK8WQJZVrp5g2SOYeZUs9u/t2f9cQ2E0gJjYB/BWoedXfUT0EcDJefi2zzVfhcOj8drWCzTviw==",
+ "os": ["freebsd"],
+ "cpu": ["arm64"]
+ },
+ "@rollup/rollup-freebsd-x64@4.52.0": {
+ "integrity": "sha512-kdBsLs4Uile/fbjZVvCRcKB4q64R+1mUq0Yd7oU1CMm1Av336ajIFqNFovByipciuUQjBCPMxwJhCgfG2re3rg==",
+ "os": ["freebsd"],
+ "cpu": ["x64"]
+ },
+ "@rollup/rollup-linux-arm-gnueabihf@4.52.0": {
+ "integrity": "sha512-aL6hRwu0k7MTUESgkg7QHY6CoqPgr6gdQXRJI1/VbFlUMwsSzPGSR7sG5d+MCbYnJmJwThc2ol3nixj1fvI/zQ==",
+ "os": ["linux"],
+ "cpu": ["arm"]
+ },
+ "@rollup/rollup-linux-arm-musleabihf@4.52.0": {
+ "integrity": "sha512-BTs0M5s1EJejgIBJhCeiFo7GZZ2IXWkFGcyZhxX4+8usnIo5Mti57108vjXFIQmmJaRyDwmV59Tw64Ap1dkwMw==",
+ "os": ["linux"],
+ "cpu": ["arm"]
+ },
+ "@rollup/rollup-linux-arm64-gnu@4.52.0": {
+ "integrity": "sha512-uj672IVOU9m08DBGvoPKPi/J8jlVgjh12C9GmjjBxCTQc3XtVmRkRKyeHSmIKQpvJ7fIm1EJieBUcnGSzDVFyw==",
+ "os": ["linux"],
+ "cpu": ["arm64"]
+ },
+ "@rollup/rollup-linux-arm64-musl@4.52.0": {
+ "integrity": "sha512-/+IVbeDMDCtB/HP/wiWsSzduD10SEGzIZX2945KSgZRNi4TSkjHqRJtNTVtVb8IRwhJ65ssI56krlLik+zFWkw==",
+ "os": ["linux"],
+ "cpu": ["arm64"]
+ },
+ "@rollup/rollup-linux-loong64-gnu@4.52.0": {
+ "integrity": "sha512-U1vVzvSWtSMWKKrGoROPBXMh3Vwn93TA9V35PldokHGqiUbF6erSzox/5qrSMKp6SzakvyjcPiVF8yB1xKr9Pg==",
+ "os": ["linux"],
+ "cpu": ["loong64"]
+ },
+ "@rollup/rollup-linux-ppc64-gnu@4.52.0": {
+ "integrity": "sha512-X/4WfuBAdQRH8cK3DYl8zC00XEE6aM472W+QCycpQJeLWVnHfkv7RyBFVaTqNUMsTgIX8ihMjCvFF9OUgeABzw==",
+ "os": ["linux"],
+ "cpu": ["ppc64"]
+ },
+ "@rollup/rollup-linux-riscv64-gnu@4.52.0": {
+ "integrity": "sha512-xIRYc58HfWDBZoLmWfWXg2Sq8VCa2iJ32B7mqfWnkx5mekekl0tMe7FHpY8I72RXEcUkaWawRvl3qA55og+cwQ==",
+ "os": ["linux"],
+ "cpu": ["riscv64"]
+ },
+ "@rollup/rollup-linux-riscv64-musl@4.52.0": {
+ "integrity": "sha512-mbsoUey05WJIOz8U1WzNdf+6UMYGwE3fZZnQqsM22FZ3wh1N887HT6jAOjXs6CNEK3Ntu2OBsyQDXfIjouI4dw==",
+ "os": ["linux"],
+ "cpu": ["riscv64"]
+ },
+ "@rollup/rollup-linux-s390x-gnu@4.52.0": {
+ "integrity": "sha512-qP6aP970bucEi5KKKR4AuPFd8aTx9EF6BvutvYxmZuWLJHmnq4LvBfp0U+yFDMGwJ+AIJEH5sIP+SNypauMWzg==",
+ "os": ["linux"],
+ "cpu": ["s390x"]
+ },
+ "@rollup/rollup-linux-x64-gnu@4.52.0": {
+ "integrity": "sha512-nmSVN+F2i1yKZ7rJNKO3G7ZzmxJgoQBQZ/6c4MuS553Grmr7WqR7LLDcYG53Z2m9409z3JLt4sCOhLdbKQ3HmA==",
+ "os": ["linux"],
+ "cpu": ["x64"]
+ },
+ "@rollup/rollup-linux-x64-musl@4.52.0": {
+ "integrity": "sha512-2d0qRo33G6TfQVjaMR71P+yJVGODrt5V6+T0BDYH4EMfGgdC/2HWDVjSSFw888GSzAZUwuska3+zxNUCDco6rQ==",
+ "os": ["linux"],
+ "cpu": ["x64"]
+ },
+ "@rollup/rollup-openharmony-arm64@4.52.0": {
+ "integrity": "sha512-A1JalX4MOaFAAyGgpO7XP5khquv/7xKzLIyLmhNrbiCxWpMlnsTYr8dnsWM7sEeotNmxvSOEL7F65j0HXFcFsw==",
+ "os": ["openharmony"],
+ "cpu": ["arm64"]
+ },
+ "@rollup/rollup-win32-arm64-msvc@4.52.0": {
+ "integrity": "sha512-YQugafP/rH0eOOHGjmNgDURrpYHrIX0yuojOI8bwCyXwxC9ZdTd3vYkmddPX0oHONLXu9Rb1dDmT0VNpjkzGGw==",
+ "os": ["win32"],
+ "cpu": ["arm64"]
+ },
+ "@rollup/rollup-win32-ia32-msvc@4.52.0": {
+ "integrity": "sha512-zYdUYhi3Qe2fndujBqL5FjAFzvNeLxtIqfzNEVKD1I7C37/chv1VxhscWSQHTNfjPCrBFQMnynwA3kpZpZ8w4A==",
+ "os": ["win32"],
+ "cpu": ["ia32"]
+ },
+ "@rollup/rollup-win32-x64-gnu@4.52.0": {
+ "integrity": "sha512-fGk03kQylNaCOQ96HDMeT7E2n91EqvCDd3RwvT5k+xNdFCeMGnj5b5hEgTGrQuyidqSsD3zJDQ21QIaxXqTBJw==",
+ "os": ["win32"],
+ "cpu": ["x64"]
+ },
+ "@rollup/rollup-win32-x64-msvc@4.52.0": {
+ "integrity": "sha512-6iKDCVSIUQ8jPMoIV0OytRKniaYyy5EbY/RRydmLW8ZR3cEBhxbWl5ro0rkUNe0ef6sScvhbY79HrjRm8i3vDQ==",
+ "os": ["win32"],
+ "cpu": ["x64"]
+ },
+ "@tabler/icons-react@3.35.0_react@19.1.1": {
+ "integrity": "sha512-XG7t2DYf3DyHT5jxFNp5xyLVbL4hMJYJhiSdHADzAjLRYfL7AnjlRfiHDHeXxkb2N103rEIvTsBRazxXtAUz2g==",
+ "dependencies": [
+ "@tabler/icons",
+ "react"
+ ]
+ },
+ "@tabler/icons@3.35.0": {
+ "integrity": "sha512-yYXe+gJ56xlZFiXwV9zVoe3FWCGuZ/D7/G4ZIlDtGxSx5CGQK110wrnT29gUj52kEZoxqF7oURTk97GQxELOFQ=="
+ },
+ "@tailwindcss/node@4.1.13": {
+ "integrity": "sha512-eq3ouolC1oEFOAvOMOBAmfCIqZBJuvWvvYWh5h5iOYfe1HFC6+GZ6EIL0JdM3/niGRJmnrOc+8gl9/HGUaaptw==",
+ "dependencies": [
+ "@jridgewell/remapping",
+ "enhanced-resolve",
+ "jiti",
+ "lightningcss",
+ "magic-string",
+ "source-map-js",
+ "tailwindcss"
+ ]
+ },
+ "@tailwindcss/oxide-android-arm64@4.1.13": {
+ "integrity": "sha512-BrpTrVYyejbgGo57yc8ieE+D6VT9GOgnNdmh5Sac6+t0m+v+sKQevpFVpwX3pBrM2qKrQwJ0c5eDbtjouY/+ew==",
+ "os": ["android"],
+ "cpu": ["arm64"]
+ },
+ "@tailwindcss/oxide-darwin-arm64@4.1.13": {
+ "integrity": "sha512-YP+Jksc4U0KHcu76UhRDHq9bx4qtBftp9ShK/7UGfq0wpaP96YVnnjFnj3ZFrUAjc5iECzODl/Ts0AN7ZPOANQ==",
+ "os": ["darwin"],
+ "cpu": ["arm64"]
+ },
+ "@tailwindcss/oxide-darwin-x64@4.1.13": {
+ "integrity": "sha512-aAJ3bbwrn/PQHDxCto9sxwQfT30PzyYJFG0u/BWZGeVXi5Hx6uuUOQEI2Fa43qvmUjTRQNZnGqe9t0Zntexeuw==",
+ "os": ["darwin"],
+ "cpu": ["x64"]
+ },
+ "@tailwindcss/oxide-freebsd-x64@4.1.13": {
+ "integrity": "sha512-Wt8KvASHwSXhKE/dJLCCWcTSVmBj3xhVhp/aF3RpAhGeZ3sVo7+NTfgiN8Vey/Fi8prRClDs6/f0KXPDTZE6nQ==",
+ "os": ["freebsd"],
+ "cpu": ["x64"]
+ },
+ "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.13": {
+ "integrity": "sha512-mbVbcAsW3Gkm2MGwA93eLtWrwajz91aXZCNSkGTx/R5eb6KpKD5q8Ueckkh9YNboU8RH7jiv+ol/I7ZyQ9H7Bw==",
+ "os": ["linux"],
+ "cpu": ["arm"]
+ },
+ "@tailwindcss/oxide-linux-arm64-gnu@4.1.13": {
+ "integrity": "sha512-wdtfkmpXiwej/yoAkrCP2DNzRXCALq9NVLgLELgLim1QpSfhQM5+ZxQQF8fkOiEpuNoKLp4nKZ6RC4kmeFH0HQ==",
+ "os": ["linux"],
+ "cpu": ["arm64"]
+ },
+ "@tailwindcss/oxide-linux-arm64-musl@4.1.13": {
+ "integrity": "sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg==",
+ "os": ["linux"],
+ "cpu": ["arm64"]
+ },
+ "@tailwindcss/oxide-linux-x64-gnu@4.1.13": {
+ "integrity": "sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ==",
+ "os": ["linux"],
+ "cpu": ["x64"]
+ },
+ "@tailwindcss/oxide-linux-x64-musl@4.1.13": {
+ "integrity": "sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ==",
+ "os": ["linux"],
+ "cpu": ["x64"]
+ },
+ "@tailwindcss/oxide-wasm32-wasi@4.1.13": {
+ "integrity": "sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA==",
+ "cpu": ["wasm32"]
+ },
+ "@tailwindcss/oxide-win32-arm64-msvc@4.1.13": {
+ "integrity": "sha512-dziTNeQXtoQ2KBXmrjCxsuPk3F3CQ/yb7ZNZNA+UkNTeiTGgfeh+gH5Pi7mRncVgcPD2xgHvkFCh/MhZWSgyQg==",
+ "os": ["win32"],
+ "cpu": ["arm64"]
+ },
+ "@tailwindcss/oxide-win32-x64-msvc@4.1.13": {
+ "integrity": "sha512-3+LKesjXydTkHk5zXX01b5KMzLV1xl2mcktBJkje7rhFUpUlYJy7IMOLqjIRQncLTa1WZZiFY/foAeB5nmaiTw==",
+ "os": ["win32"],
+ "cpu": ["x64"]
+ },
+ "@tailwindcss/oxide@4.1.13": {
+ "integrity": "sha512-CPgsM1IpGRa880sMbYmG1s4xhAy3xEt1QULgTJGQmZUeNgXFR7s1YxYygmJyBGtou4SyEosGAGEeYqY7R53bIA==",
+ "dependencies": [
+ "detect-libc",
+ "tar"
+ ],
+ "optionalDependencies": [
+ "@tailwindcss/oxide-android-arm64",
+ "@tailwindcss/oxide-darwin-arm64",
+ "@tailwindcss/oxide-darwin-x64",
+ "@tailwindcss/oxide-freebsd-x64",
+ "@tailwindcss/oxide-linux-arm-gnueabihf",
+ "@tailwindcss/oxide-linux-arm64-gnu",
+ "@tailwindcss/oxide-linux-arm64-musl",
+ "@tailwindcss/oxide-linux-x64-gnu",
+ "@tailwindcss/oxide-linux-x64-musl",
+ "@tailwindcss/oxide-wasm32-wasi",
+ "@tailwindcss/oxide-win32-arm64-msvc",
+ "@tailwindcss/oxide-win32-x64-msvc"
+ ],
+ "scripts": true
+ },
+ "@tailwindcss/vite@4.1.13_vite@7.1.6__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0": {
+ "integrity": "sha512-0PmqLQ010N58SbMTJ7BVJ4I2xopiQn/5i6nlb4JmxzQf8zcS5+m2Cv6tqh+sfDwtIdjoEnOvwsGQ1hkUi8QEHQ==",
+ "dependencies": [
+ "@tailwindcss/node",
+ "@tailwindcss/oxide",
+ "tailwindcss",
+ "vite"
+ ]
+ },
+ "@tanstack/history@1.131.2": {
+ "integrity": "sha512-cs1WKawpXIe+vSTeiZUuSBy8JFjEuDgdMKZFRLKwQysKo8y2q6Q1HvS74Yw+m5IhOW1nTZooa6rlgdfXcgFAaw=="
+ },
+ "@tanstack/query-core@5.89.0": {
+ "integrity": "sha512-joFV1MuPhSLsKfTzwjmPDrp8ENfZ9N23ymFu07nLfn3JCkSHy0CFgsyhHTJOmWaumC/WiNIKM0EJyduCF/Ih/Q=="
+ },
+ "@tanstack/react-query@5.89.0_react@19.1.1": {
+ "integrity": "sha512-SXbtWSTSRXyBOe80mszPxpEbaN4XPRUp/i0EfQK1uyj3KCk/c8FuPJNIRwzOVe/OU3rzxrYtiNabsAmk1l714A==",
+ "dependencies": [
+ "@tanstack/query-core",
+ "react"
+ ]
+ },
+ "@tanstack/react-router-devtools@1.131.47_@tanstack+react-router@1.131.47__react@19.1.1__react-dom@19.1.1___react@19.1.1_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
+ "integrity": "sha512-lbDUXLvShxC0cjIjzAAUtd+VzLPDJfiEAACykvGnW5dywBD/w20z7Hd8Jx8l/LrYOCI+EY8C6+0UxlnVqF5bdQ==",
+ "dependencies": [
+ "@tanstack/react-router",
+ "@tanstack/router-devtools-core",
+ "react",
+ "react-dom"
+ ]
+ },
+ "@tanstack/react-router@1.131.47_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
+ "integrity": "sha512-yS5rUPjCvWRg+CZRnY7irKiNZEhLeIsNlwuhIdnIX2K7jU9gOc7pOIT8JI2Vo6IAhh/Mr+7aILKLPYxwTRnS5A==",
+ "dependencies": [
+ "@tanstack/history",
+ "@tanstack/react-store",
+ "@tanstack/router-core",
+ "isbot",
+ "react",
+ "react-dom",
+ "tiny-invariant",
+ "tiny-warning"
+ ]
+ },
+ "@tanstack/react-store@0.7.7_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
+ "integrity": "sha512-qqT0ufegFRDGSof9D/VqaZgjNgp4tRPHZIJq2+QIHkMUtHjaJ0lYrrXjeIUJvjnTbgPfSD1XgOMEt0lmANn6Zg==",
+ "dependencies": [
+ "@tanstack/store",
+ "react",
+ "react-dom",
+ "use-sync-external-store"
+ ]
+ },
+ "@tanstack/router-core@1.131.47_seroval@1.3.2": {
+ "integrity": "sha512-ixwowt//SLvnuMoInSxSNCJ41J3S53FLgw8tu5MyXftZ9d7cVOnHoAuSOhKNJNyBDTC2JODC3w/4EH3KDMj6ew==",
+ "dependencies": [
+ "@tanstack/history",
+ "@tanstack/store",
+ "cookie-es",
+ "seroval",
+ "seroval-plugins",
+ "tiny-invariant",
+ "tiny-warning"
+ ]
+ },
+ "@tanstack/router-devtools-core@1.131.47_@tanstack+router-core@1.131.47__seroval@1.3.2_solid-js@1.9.9__seroval@1.3.2_tiny-invariant@1.3.3": {
+ "integrity": "sha512-XKeTfZcy5RmlPUUYkidIeK/KIfjSWo1cFp0P9L+LleclbVa6pkIfjocSHqUiHM5wGlxkbC5EzZfLBqs2xTinuA==",
+ "dependencies": [
+ "@tanstack/router-core",
+ "clsx",
+ "goober",
+ "solid-js",
+ "tiny-invariant"
+ ]
+ },
+ "@tanstack/store@0.7.7": {
+ "integrity": "sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ=="
+ },
+ "@types/babel__core@7.20.5": {
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dependencies": [
+ "@babel/parser",
+ "@babel/types",
+ "@types/babel__generator",
+ "@types/babel__template",
+ "@types/babel__traverse"
+ ]
+ },
+ "@types/babel__generator@7.27.0": {
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dependencies": [
+ "@babel/types"
+ ]
+ },
+ "@types/babel__template@7.4.4": {
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dependencies": [
+ "@babel/parser",
+ "@babel/types"
+ ]
+ },
+ "@types/babel__traverse@7.28.0": {
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dependencies": [
+ "@babel/types"
+ ]
+ },
+ "@types/estree@1.0.8": {
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="
},
"@types/json-schema@7.0.15": {
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
},
- "@typescript-eslint/eslint-plugin@8.30.1_@typescript-eslint+parser@8.30.1__eslint@9.24.0__typescript@5.8.3_eslint@9.24.0_typescript@5.8.3": {
- "integrity": "sha512-v+VWphxMjn+1t48/jO4t950D6KR8JaJuNXzi33Ve6P8sEmPr5k6CEXjdGwT6+LodVnEa91EQCtwjWNUCPweo+Q==",
+ "@types/node@24.2.0": {
+ "integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==",
+ "dependencies": [
+ "undici-types"
+ ]
+ },
+ "@types/react-dom@19.1.9_@types+react@19.1.13": {
+ "integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==",
+ "dependencies": [
+ "@types/react"
+ ]
+ },
+ "@types/react@19.1.13": {
+ "integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==",
+ "dependencies": [
+ "csstype"
+ ]
+ },
+ "@types/webidl-conversions@7.0.3": {
+ "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="
+ },
+ "@types/whatwg-url@11.0.5": {
+ "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
+ "dependencies": [
+ "@types/webidl-conversions"
+ ]
+ },
+ "@typescript-eslint/eslint-plugin@8.44.0_@typescript-eslint+parser@8.44.0__eslint@9.35.0__typescript@5.9.2_eslint@9.35.0_typescript@5.9.2": {
+ "integrity": "sha512-EGDAOGX+uwwekcS0iyxVDmRV9HX6FLSM5kzrAToLTsr9OWCIKG/y3lQheCq18yZ5Xh78rRKJiEpP0ZaCs4ryOQ==",
"dependencies": [
"@eslint-community/regexpp",
"@typescript-eslint/parser",
@@ -158,14 +1539,14 @@
"@typescript-eslint/visitor-keys",
"eslint",
"graphemer",
- "ignore",
+ "ignore@7.0.5",
"natural-compare",
"ts-api-utils",
"typescript"
]
},
- "@typescript-eslint/parser@8.30.1_eslint@9.24.0_typescript@5.8.3": {
- "integrity": "sha512-H+vqmWwT5xoNrXqWs/fesmssOW70gxFlgcMlYcBaWNPIEWDgLa4W9nkSPmhuOgLnXq9QYgkZ31fhDyLhleCsAg==",
+ "@typescript-eslint/parser@8.44.0_eslint@9.35.0_typescript@5.9.2": {
+ "integrity": "sha512-VGMpFQGUQWYT9LfnPcX8ouFojyrZ/2w3K5BucvxL/spdNehccKhB4jUyB1yBCXpr2XFm0jkECxgrpXBW2ipoAw==",
"dependencies": [
"@typescript-eslint/scope-manager",
"@typescript-eslint/types",
@@ -176,16 +1557,32 @@
"typescript"
]
},
- "@typescript-eslint/scope-manager@8.30.1": {
- "integrity": "sha512-+C0B6ChFXZkuaNDl73FJxRYT0G7ufVPOSQkqkpM/U198wUwUFOtgo1k/QzFh1KjpBitaK7R1tgjVz6o9HmsRPg==",
+ "@typescript-eslint/project-service@8.44.0_typescript@5.9.2": {
+ "integrity": "sha512-ZeaGNraRsq10GuEohKTo4295Z/SuGcSq2LzfGlqiuEvfArzo/VRrT0ZaJsVPuKZ55lVbNk8U6FcL+ZMH8CoyVA==",
+ "dependencies": [
+ "@typescript-eslint/tsconfig-utils",
+ "@typescript-eslint/types",
+ "debug",
+ "typescript"
+ ]
+ },
+ "@typescript-eslint/scope-manager@8.44.0": {
+ "integrity": "sha512-87Jv3E+al8wpD+rIdVJm/ItDBe/Im09zXIjFoipOjr5gHUhJmTzfFLuTJ/nPTMc2Srsroy4IBXwcTCHyRR7KzA==",
"dependencies": [
"@typescript-eslint/types",
"@typescript-eslint/visitor-keys"
]
},
- "@typescript-eslint/type-utils@8.30.1_eslint@9.24.0_typescript@5.8.3": {
- "integrity": "sha512-64uBF76bfQiJyHgZISC7vcNz3adqQKIccVoKubyQcOnNcdJBvYOILV1v22Qhsw3tw3VQu5ll8ND6hycgAR5fEA==",
+ "@typescript-eslint/tsconfig-utils@8.44.0_typescript@5.9.2": {
+ "integrity": "sha512-x5Y0+AuEPqAInc6yd0n5DAcvtoQ/vyaGwuX5HE9n6qAefk1GaedqrLQF8kQGylLUb9pnZyLf+iEiL9fr8APDtQ==",
"dependencies": [
+ "typescript"
+ ]
+ },
+ "@typescript-eslint/type-utils@8.44.0_eslint@9.35.0_typescript@5.9.2": {
+ "integrity": "sha512-9cwsoSxJ8Sak67Be/hD2RNt/fsqmWnNE1iHohG8lxqLSNY8xNfyY7wloo5zpW3Nu9hxVgURevqfcH6vvKCt6yg==",
+ "dependencies": [
+ "@typescript-eslint/types",
"@typescript-eslint/typescript-estree",
"@typescript-eslint/utils",
"debug",
@@ -194,25 +1591,27 @@
"typescript"
]
},
- "@typescript-eslint/types@8.30.1": {
- "integrity": "sha512-81KawPfkuulyWo5QdyG/LOKbspyyiW+p4vpn4bYO7DM/hZImlVnFwrpCTnmNMOt8CvLRr5ojI9nU1Ekpw4RcEw=="
+ "@typescript-eslint/types@8.44.0": {
+ "integrity": "sha512-ZSl2efn44VsYM0MfDQe68RKzBz75NPgLQXuGypmym6QVOWL5kegTZuZ02xRAT9T+onqvM6T8CdQk0OwYMB6ZvA=="
},
- "@typescript-eslint/typescript-estree@8.30.1_typescript@5.8.3": {
- "integrity": "sha512-kQQnxymiUy9tTb1F2uep9W6aBiYODgq5EMSk6Nxh4Z+BDUoYUSa029ISs5zTzKBFnexQEh71KqwjKnRz58lusQ==",
+ "@typescript-eslint/typescript-estree@8.44.0_typescript@5.9.2": {
+ "integrity": "sha512-lqNj6SgnGcQZwL4/SBJ3xdPEfcBuhCG8zdcwCPgYcmiPLgokiNDKlbPzCwEwu7m279J/lBYWtDYL+87OEfn8Jw==",
"dependencies": [
+ "@typescript-eslint/project-service",
+ "@typescript-eslint/tsconfig-utils",
"@typescript-eslint/types",
"@typescript-eslint/visitor-keys",
"debug",
"fast-glob",
"is-glob",
"minimatch@9.0.5",
- "semver",
+ "semver@7.7.2",
"ts-api-utils",
"typescript"
]
},
- "@typescript-eslint/utils@8.30.1_eslint@9.24.0_typescript@5.8.3": {
- "integrity": "sha512-T/8q4R9En2tcEsWPQgB5BQ0XJVOtfARcUvOa8yJP3fh9M/mXraLxZrkCfGb6ChrO/V3W+Xbd04RacUEqk1CFEQ==",
+ "@typescript-eslint/utils@8.44.0_eslint@9.35.0_typescript@5.9.2": {
+ "integrity": "sha512-nktOlVcg3ALo0mYlV+L7sWUD58KG4CMj1rb2HUVOO4aL3K/6wcD+NERqd0rrA5Vg06b42YhF6cFxeixsp9Riqg==",
"dependencies": [
"@eslint-community/eslint-utils",
"@typescript-eslint/scope-manager",
@@ -222,24 +1621,40 @@
"typescript"
]
},
- "@typescript-eslint/visitor-keys@8.30.1": {
- "integrity": "sha512-aEhgas7aJ6vZnNFC7K4/vMGDGyOiqWcYZPpIWrTKuTAlsvDNKy2GFDqh9smL+iq069ZvR0YzEeq0B8NJlLzjFA==",
+ "@typescript-eslint/visitor-keys@8.44.0": {
+ "integrity": "sha512-zaz9u8EJ4GBmnehlrpoKvj/E3dNbuQ7q0ucyZImm3cLqJ8INTc970B1qEqDX/Rzq65r3TvVTN7kHWPBoyW7DWw==",
"dependencies": [
"@typescript-eslint/types",
- "eslint-visitor-keys@4.2.0"
+ "eslint-visitor-keys@4.2.1"
]
},
- "@zod/core@0.6.2": {
- "integrity": "sha512-KdH7bT0BRG1CvJ1LWH8oyNnkvLpjVZ5qVGpRu7Vq8WsFTKRDWfdr3rFfBYh8atZJSWDgD0ibhOyff1AyRvG1DA=="
+ "@vitejs/plugin-react@4.7.0_vite@7.1.6__@types+node@24.2.0__picomatch@4.0.3_@babel+core@7.28.4_@types+node@24.2.0": {
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dependencies": [
+ "@babel/core",
+ "@babel/plugin-transform-react-jsx-self",
+ "@babel/plugin-transform-react-jsx-source",
+ "@rolldown/pluginutils",
+ "@types/babel__core",
+ "react-refresh",
+ "vite"
+ ]
},
- "acorn-jsx@5.3.2_acorn@8.14.1": {
+ "@zitadel/react@1.1.0": {
+ "integrity": "sha512-aMad1iZNpsZgEtUvSIyjCt1TdCzg++OMg3GwdPbFhnHTHwQMoSRB9IrYuD0grHK0TqU7yx283iJO5te2ToRWtA==",
+ "dependencies": [
+ "oidc-client-ts"
+ ]
+ },
+ "acorn-jsx@5.3.2_acorn@8.15.0": {
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
"dependencies": [
"acorn"
]
},
- "acorn@8.14.1": {
- "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="
+ "acorn@8.15.0": {
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "bin": true
},
"ajv@6.12.6": {
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
@@ -259,18 +1674,28 @@
"argparse@2.0.1": {
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
+ "aria-hidden@1.2.6": {
+ "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
+ "dependencies": [
+ "tslib"
+ ]
+ },
"balanced-match@1.0.2": {
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
- "brace-expansion@1.1.11": {
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "baseline-browser-mapping@2.8.6": {
+ "integrity": "sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw==",
+ "bin": true
+ },
+ "brace-expansion@1.1.12": {
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dependencies": [
"balanced-match",
"concat-map"
]
},
- "brace-expansion@2.0.1": {
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "brace-expansion@2.0.2": {
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dependencies": [
"balanced-match"
]
@@ -281,9 +1706,40 @@
"fill-range"
]
},
+ "browserslist@4.26.2": {
+ "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==",
+ "dependencies": [
+ "baseline-browser-mapping",
+ "caniuse-lite",
+ "electron-to-chromium",
+ "node-releases",
+ "update-browserslist-db"
+ ],
+ "bin": true
+ },
+ "bson@6.10.4": {
+ "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng=="
+ },
+ "call-bind-apply-helpers@1.0.2": {
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "dependencies": [
+ "es-errors",
+ "function-bind"
+ ]
+ },
+ "call-bound@1.0.4": {
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "dependencies": [
+ "call-bind-apply-helpers",
+ "get-intrinsic"
+ ]
+ },
"callsites@3.1.0": {
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="
},
+ "caniuse-lite@1.0.30001743": {
+ "integrity": "sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw=="
+ },
"chalk@4.1.2": {
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dependencies": [
@@ -291,6 +1747,18 @@
"supports-color"
]
},
+ "chownr@3.0.0": {
+ "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="
+ },
+ "class-variance-authority@0.7.1": {
+ "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
+ "dependencies": [
+ "clsx"
+ ]
+ },
+ "clsx@2.1.1": {
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="
+ },
"color-convert@2.0.1": {
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": [
@@ -303,6 +1771,12 @@
"concat-map@0.0.1": {
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
+ "convert-source-map@2.0.0": {
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
+ },
+ "cookie-es@1.2.2": {
+ "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg=="
+ },
"cross-spawn@7.0.6": {
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dependencies": [
@@ -311,8 +1785,14 @@
"which"
]
},
- "debug@4.4.0": {
- "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "crypto-js@4.2.0": {
+ "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
+ },
+ "csstype@3.1.3": {
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+ },
+ "debug@4.4.3": {
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dependencies": [
"ms"
]
@@ -320,17 +1800,95 @@
"deep-is@0.1.4": {
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="
},
+ "detect-libc@2.1.0": {
+ "integrity": "sha512-vEtk+OcP7VBRtQZ1EJ3bdgzSfBjgnEalLTp5zjJrS+2Z1w2KZly4SBdac/WDU3hhsNAZ9E8SC96ME4Ey8MZ7cg=="
+ },
+ "detect-node-es@1.1.0": {
+ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
+ },
+ "dunder-proto@1.0.1": {
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "dependencies": [
+ "call-bind-apply-helpers",
+ "es-errors",
+ "gopd"
+ ]
+ },
+ "electron-to-chromium@1.5.222": {
+ "integrity": "sha512-gA7psSwSwQRE60CEoLz6JBCQPIxNeuzB2nL8vE03GK/OHxlvykbLyeiumQy1iH5C2f3YbRAZpGCMT12a/9ih9w=="
+ },
+ "enhanced-resolve@5.18.3": {
+ "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
+ "dependencies": [
+ "graceful-fs",
+ "tapable"
+ ]
+ },
+ "es-define-property@1.0.1": {
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
+ },
+ "es-errors@1.3.0": {
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
+ },
+ "es-object-atoms@1.1.1": {
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "dependencies": [
+ "es-errors"
+ ]
+ },
+ "esbuild@0.25.10": {
+ "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==",
+ "optionalDependencies": [
+ "@esbuild/aix-ppc64",
+ "@esbuild/android-arm",
+ "@esbuild/android-arm64",
+ "@esbuild/android-x64",
+ "@esbuild/darwin-arm64",
+ "@esbuild/darwin-x64",
+ "@esbuild/freebsd-arm64",
+ "@esbuild/freebsd-x64",
+ "@esbuild/linux-arm",
+ "@esbuild/linux-arm64",
+ "@esbuild/linux-ia32",
+ "@esbuild/linux-loong64",
+ "@esbuild/linux-mips64el",
+ "@esbuild/linux-ppc64",
+ "@esbuild/linux-riscv64",
+ "@esbuild/linux-s390x",
+ "@esbuild/linux-x64",
+ "@esbuild/netbsd-arm64",
+ "@esbuild/netbsd-x64",
+ "@esbuild/openbsd-arm64",
+ "@esbuild/openbsd-x64",
+ "@esbuild/openharmony-arm64",
+ "@esbuild/sunos-x64",
+ "@esbuild/win32-arm64",
+ "@esbuild/win32-ia32",
+ "@esbuild/win32-x64"
+ ],
+ "scripts": true,
+ "bin": true
+ },
+ "escalade@3.2.0": {
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="
+ },
"escape-string-regexp@4.0.0": {
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
},
- "eslint-plugin-simple-import-sort@12.1.1_eslint@9.24.0": {
- "integrity": "sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==",
+ "eslint-plugin-react-hooks@5.2.0_eslint@9.35.0": {
+ "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==",
"dependencies": [
"eslint"
]
},
- "eslint-scope@8.3.0": {
- "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
+ "eslint-plugin-react-refresh@0.4.20_eslint@9.35.0": {
+ "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==",
+ "dependencies": [
+ "eslint"
+ ]
+ },
+ "eslint-scope@8.4.0": {
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
"dependencies": [
"esrecurse",
"estraverse"
@@ -339,23 +1897,23 @@
"eslint-visitor-keys@3.4.3": {
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="
},
- "eslint-visitor-keys@4.2.0": {
- "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="
+ "eslint-visitor-keys@4.2.1": {
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="
},
- "eslint@9.24.0": {
- "integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==",
+ "eslint@9.35.0": {
+ "integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==",
"dependencies": [
"@eslint-community/eslint-utils",
"@eslint-community/regexpp",
"@eslint/config-array",
"@eslint/config-helpers",
- "@eslint/core@0.12.0",
+ "@eslint/core",
"@eslint/eslintrc",
"@eslint/js",
"@eslint/plugin-kit",
"@humanfs/node",
"@humanwhocodes/module-importer",
- "@humanwhocodes/retry@0.4.2",
+ "@humanwhocodes/retry",
"@types/estree",
"@types/json-schema",
"ajv",
@@ -364,7 +1922,7 @@
"debug",
"escape-string-regexp",
"eslint-scope",
- "eslint-visitor-keys@4.2.0",
+ "eslint-visitor-keys@4.2.1",
"espree",
"esquery",
"esutils",
@@ -372,7 +1930,7 @@
"file-entry-cache",
"find-up",
"glob-parent@6.0.2",
- "ignore",
+ "ignore@5.3.2",
"imurmurhash",
"is-glob",
"json-stable-stringify-without-jsonify",
@@ -380,14 +1938,15 @@
"minimatch@3.1.2",
"natural-compare",
"optionator"
- ]
+ ],
+ "bin": true
},
- "espree@10.3.0_acorn@8.14.1": {
- "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
+ "espree@10.4.0_acorn@8.15.0": {
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
"dependencies": [
"acorn",
"acorn-jsx",
- "eslint-visitor-keys@4.2.0"
+ "eslint-visitor-keys@4.2.1"
]
},
"esquery@1.6.0": {
@@ -408,9 +1967,15 @@
"esutils@2.0.3": {
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
},
+ "eventemitter3@5.0.1": {
+ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
+ },
"fast-deep-equal@3.1.3": {
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
+ "fast-equals@5.2.2": {
+ "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw=="
+ },
"fast-glob@3.3.3": {
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"dependencies": [
@@ -433,6 +1998,15 @@
"reusify"
]
},
+ "fdir@6.5.0_picomatch@4.0.3": {
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dependencies": [
+ "picomatch@4.0.3"
+ ],
+ "optionalPeers": [
+ "picomatch@4.0.3"
+ ]
+ },
"file-entry-cache@8.0.0": {
"integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
"dependencies": [
@@ -462,6 +2036,42 @@
"flatted@3.3.3": {
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="
},
+ "fsevents@2.3.3": {
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "os": ["darwin"],
+ "scripts": true
+ },
+ "function-bind@1.1.2": {
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
+ },
+ "gensync@1.0.0-beta.2": {
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="
+ },
+ "get-intrinsic@1.3.0": {
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "dependencies": [
+ "call-bind-apply-helpers",
+ "es-define-property",
+ "es-errors",
+ "es-object-atoms",
+ "function-bind",
+ "get-proto",
+ "gopd",
+ "has-symbols",
+ "hasown",
+ "math-intrinsics"
+ ]
+ },
+ "get-nonce@1.0.1": {
+ "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="
+ },
+ "get-proto@1.0.1": {
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "dependencies": [
+ "dunder-proto",
+ "es-object-atoms"
+ ]
+ },
"glob-parent@5.1.2": {
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dependencies": [
@@ -477,15 +2087,45 @@
"globals@14.0.0": {
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="
},
+ "globals@16.4.0": {
+ "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw=="
+ },
+ "goober@2.1.16_csstype@3.1.3": {
+ "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==",
+ "dependencies": [
+ "csstype"
+ ]
+ },
+ "gopd@1.2.0": {
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
+ },
+ "graceful-fs@4.2.11": {
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
+ },
"graphemer@1.4.0": {
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="
},
"has-flag@4.0.0": {
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
+ "has-symbols@1.1.0": {
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
+ },
+ "hasown@2.0.2": {
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dependencies": [
+ "function-bind"
+ ]
+ },
+ "idb@8.0.3": {
+ "integrity": "sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg=="
+ },
"ignore@5.3.2": {
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="
},
+ "ignore@7.0.5": {
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="
+ },
"import-fresh@3.3.1": {
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
"dependencies": [
@@ -508,14 +2148,32 @@
"is-number@7.0.0": {
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
+ "isbot@5.1.30": {
+ "integrity": "sha512-3wVJEonAns1OETX83uWsk5IAne2S5zfDcntD2hbtU23LelSqNXzXs9zKjMPOLMzroCgIjCfjYAEHrd2D6FOkiA=="
+ },
"isexe@2.0.0": {
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
},
+ "jiti@2.5.1": {
+ "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==",
+ "bin": true
+ },
+ "jose@6.1.0": {
+ "integrity": "sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA=="
+ },
+ "js-tokens@4.0.0": {
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
"js-yaml@4.1.0": {
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dependencies": [
"argparse"
- ]
+ ],
+ "bin": true
+ },
+ "jsesc@3.1.0": {
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "bin": true
},
"json-buffer@3.0.1": {
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
@@ -526,6 +2184,13 @@
"json-stable-stringify-without-jsonify@1.0.1": {
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="
},
+ "json5@2.2.3": {
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "bin": true
+ },
+ "jwt-decode@3.1.2": {
+ "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
+ },
"keyv@4.5.4": {
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
"dependencies": [
@@ -539,6 +2204,74 @@
"type-check"
]
},
+ "lightningcss-darwin-arm64@1.30.1": {
+ "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==",
+ "os": ["darwin"],
+ "cpu": ["arm64"]
+ },
+ "lightningcss-darwin-x64@1.30.1": {
+ "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==",
+ "os": ["darwin"],
+ "cpu": ["x64"]
+ },
+ "lightningcss-freebsd-x64@1.30.1": {
+ "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==",
+ "os": ["freebsd"],
+ "cpu": ["x64"]
+ },
+ "lightningcss-linux-arm-gnueabihf@1.30.1": {
+ "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==",
+ "os": ["linux"],
+ "cpu": ["arm"]
+ },
+ "lightningcss-linux-arm64-gnu@1.30.1": {
+ "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==",
+ "os": ["linux"],
+ "cpu": ["arm64"]
+ },
+ "lightningcss-linux-arm64-musl@1.30.1": {
+ "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==",
+ "os": ["linux"],
+ "cpu": ["arm64"]
+ },
+ "lightningcss-linux-x64-gnu@1.30.1": {
+ "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==",
+ "os": ["linux"],
+ "cpu": ["x64"]
+ },
+ "lightningcss-linux-x64-musl@1.30.1": {
+ "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==",
+ "os": ["linux"],
+ "cpu": ["x64"]
+ },
+ "lightningcss-win32-arm64-msvc@1.30.1": {
+ "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==",
+ "os": ["win32"],
+ "cpu": ["arm64"]
+ },
+ "lightningcss-win32-x64-msvc@1.30.1": {
+ "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==",
+ "os": ["win32"],
+ "cpu": ["x64"]
+ },
+ "lightningcss@1.30.1": {
+ "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
+ "dependencies": [
+ "detect-libc"
+ ],
+ "optionalDependencies": [
+ "lightningcss-darwin-arm64",
+ "lightningcss-darwin-x64",
+ "lightningcss-freebsd-x64",
+ "lightningcss-linux-arm-gnueabihf",
+ "lightningcss-linux-arm64-gnu",
+ "lightningcss-linux-arm64-musl",
+ "lightningcss-linux-x64-gnu",
+ "lightningcss-linux-x64-musl",
+ "lightningcss-win32-arm64-msvc",
+ "lightningcss-win32-x64-msvc"
+ ]
+ },
"locate-path@6.0.0": {
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"dependencies": [
@@ -548,6 +2281,30 @@
"lodash.merge@4.6.2": {
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
},
+ "lru-cache@5.1.1": {
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dependencies": [
+ "yallist@3.1.1"
+ ]
+ },
+ "lucide-react@0.554.0_react@19.1.1": {
+ "integrity": "sha512-St+z29uthEJVx0Is7ellNkgTEhaeSoA42I7JjOCBCrc5X6LYMGSv0P/2uS5HDLTExP5tpiqRD2PyUEOS6s9UXA==",
+ "dependencies": [
+ "react"
+ ]
+ },
+ "magic-string@0.30.19": {
+ "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==",
+ "dependencies": [
+ "@jridgewell/sourcemap-codec"
+ ]
+ },
+ "math-intrinsics@1.1.0": {
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
+ },
+ "memory-pager@1.5.0": {
+ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="
+ },
"merge2@1.4.1": {
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
},
@@ -555,27 +2312,75 @@
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dependencies": [
"braces",
- "picomatch"
+ "picomatch@2.3.1"
]
},
+ "mingo@6.6.1": {
+ "integrity": "sha512-KC6b1ODYoSdYu5fBm+SzQb7fa4ARmGwfa3Cf9F7U+2mnfD4Zhf89qQgO1cPTtaJ68w3ntIT5dVujgF52HvN7+g=="
+ },
"minimatch@3.1.2": {
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dependencies": [
- "brace-expansion@1.1.11"
+ "brace-expansion@1.1.12"
]
},
"minimatch@9.0.5": {
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dependencies": [
- "brace-expansion@2.0.1"
+ "brace-expansion@2.0.2"
+ ]
+ },
+ "minipass@7.1.2": {
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="
+ },
+ "minizlib@3.1.0": {
+ "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
+ "dependencies": [
+ "minipass"
+ ]
+ },
+ "mongodb-connection-string-url@3.0.2": {
+ "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==",
+ "dependencies": [
+ "@types/whatwg-url",
+ "whatwg-url"
+ ]
+ },
+ "mongodb@6.20.0": {
+ "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==",
+ "dependencies": [
+ "@mongodb-js/saslprep",
+ "bson",
+ "mongodb-connection-string-url"
]
},
"ms@2.1.3": {
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
+ "nanoid@3.3.11": {
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "bin": true
+ },
+ "nanoid@5.1.5": {
+ "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==",
+ "bin": true
+ },
"natural-compare@1.4.0": {
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="
},
+ "node-releases@2.0.21": {
+ "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw=="
+ },
+ "object-inspect@1.13.4": {
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="
+ },
+ "oidc-client-ts@2.4.1": {
+ "integrity": "sha512-IxlGMsbkZPsHJGCliWT3LxjUcYzmiN21656n/Zt2jDncZlBFc//cd8WqFF0Lt681UT3AImM57E6d4N53ziTCYA==",
+ "dependencies": [
+ "crypto-js",
+ "jwt-decode"
+ ]
+ },
"optionator@0.9.4": {
"integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
"dependencies": [
@@ -611,35 +2416,165 @@
"path-key@3.1.1": {
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
},
+ "path-to-regexp@8.3.0": {
+ "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="
+ },
+ "picocolors@1.1.1": {
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+ },
"picomatch@2.3.1": {
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
},
+ "picomatch@4.0.3": {
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="
+ },
+ "postcss@8.5.6": {
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dependencies": [
+ "nanoid@3.3.11",
+ "picocolors",
+ "source-map-js"
+ ]
+ },
+ "postgres@3.4.7": {
+ "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="
+ },
"prelude-ls@1.2.1": {
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="
},
- "prettier@3.5.3": {
- "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="
- },
"punycode@2.3.1": {
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
},
+ "qs@6.14.0": {
+ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
+ "dependencies": [
+ "side-channel"
+ ]
+ },
"queue-microtask@1.2.3": {
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
},
+ "react-dom@19.1.1_react@19.1.1": {
+ "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
+ "dependencies": [
+ "react",
+ "scheduler"
+ ]
+ },
+ "react-refresh@0.17.0": {
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="
+ },
+ "react-remove-scroll-bar@2.3.8_@types+react@19.1.13_react@19.1.1": {
+ "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
+ "dependencies": [
+ "@types/react",
+ "react",
+ "react-style-singleton",
+ "tslib"
+ ],
+ "optionalPeers": [
+ "@types/react"
+ ]
+ },
+ "react-remove-scroll@2.7.1_@types+react@19.1.13_react@19.1.1": {
+ "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==",
+ "dependencies": [
+ "@types/react",
+ "react",
+ "react-remove-scroll-bar",
+ "react-style-singleton",
+ "tslib",
+ "use-callback-ref",
+ "use-sidecar"
+ ],
+ "optionalPeers": [
+ "@types/react"
+ ]
+ },
+ "react-style-singleton@2.2.3_@types+react@19.1.13_react@19.1.1": {
+ "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
+ "dependencies": [
+ "@types/react",
+ "get-nonce",
+ "react",
+ "tslib"
+ ],
+ "optionalPeers": [
+ "@types/react"
+ ]
+ },
+ "react@19.1.1": {
+ "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ=="
+ },
"resolve-from@4.0.0": {
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
},
"reusify@1.1.0": {
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="
},
+ "rollup@4.52.0": {
+ "integrity": "sha512-+IuescNkTJQgX7AkIDtITipZdIGcWF0pnVvZTWStiazUmcGA2ag8dfg0urest2XlXUi9kuhfQ+qmdc5Stc3z7g==",
+ "dependencies": [
+ "@types/estree"
+ ],
+ "optionalDependencies": [
+ "@rollup/rollup-android-arm-eabi",
+ "@rollup/rollup-android-arm64",
+ "@rollup/rollup-darwin-arm64",
+ "@rollup/rollup-darwin-x64",
+ "@rollup/rollup-freebsd-arm64",
+ "@rollup/rollup-freebsd-x64",
+ "@rollup/rollup-linux-arm-gnueabihf",
+ "@rollup/rollup-linux-arm-musleabihf",
+ "@rollup/rollup-linux-arm64-gnu",
+ "@rollup/rollup-linux-arm64-musl",
+ "@rollup/rollup-linux-loong64-gnu",
+ "@rollup/rollup-linux-ppc64-gnu",
+ "@rollup/rollup-linux-riscv64-gnu",
+ "@rollup/rollup-linux-riscv64-musl",
+ "@rollup/rollup-linux-s390x-gnu",
+ "@rollup/rollup-linux-x64-gnu",
+ "@rollup/rollup-linux-x64-musl",
+ "@rollup/rollup-openharmony-arm64",
+ "@rollup/rollup-win32-arm64-msvc",
+ "@rollup/rollup-win32-ia32-msvc",
+ "@rollup/rollup-win32-x64-gnu",
+ "@rollup/rollup-win32-x64-msvc",
+ "fsevents"
+ ],
+ "bin": true
+ },
"run-parallel@1.2.0": {
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
"dependencies": [
"queue-microtask"
]
},
- "semver@7.7.1": {
- "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="
+ "rxjs@7.8.2": {
+ "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
+ "dependencies": [
+ "tslib"
+ ]
+ },
+ "scheduler@0.26.0": {
+ "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="
+ },
+ "semver@6.3.1": {
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "bin": true
+ },
+ "semver@7.7.2": {
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "bin": true
+ },
+ "seroval-plugins@1.3.3_seroval@1.3.2": {
+ "integrity": "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==",
+ "dependencies": [
+ "seroval"
+ ]
+ },
+ "seroval@1.3.2": {
+ "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ=="
},
"shebang-command@2.0.0": {
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
@@ -650,6 +2585,59 @@
"shebang-regex@3.0.0": {
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
},
+ "side-channel-list@1.0.0": {
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "dependencies": [
+ "es-errors",
+ "object-inspect"
+ ]
+ },
+ "side-channel-map@1.0.1": {
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "dependencies": [
+ "call-bound",
+ "es-errors",
+ "get-intrinsic",
+ "object-inspect"
+ ]
+ },
+ "side-channel-weakmap@1.0.2": {
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "dependencies": [
+ "call-bound",
+ "es-errors",
+ "get-intrinsic",
+ "object-inspect",
+ "side-channel-map"
+ ]
+ },
+ "side-channel@1.1.0": {
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "dependencies": [
+ "es-errors",
+ "object-inspect",
+ "side-channel-list",
+ "side-channel-map",
+ "side-channel-weakmap"
+ ]
+ },
+ "solid-js@1.9.9_seroval@1.3.2": {
+ "integrity": "sha512-A0ZBPJQldAeGCTW0YRYJmt7RCeh5rbFfPZ2aOttgYnctHE7HgKeHCBB/PVc2P7eOfmNXqMFFFoYYdm3S4dcbkA==",
+ "dependencies": [
+ "csstype",
+ "seroval",
+ "seroval-plugins"
+ ]
+ },
+ "source-map-js@1.2.1": {
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
+ },
+ "sparse-bitfield@3.0.3": {
+ "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
+ "dependencies": [
+ "memory-pager"
+ ]
+ },
"strip-json-comments@3.1.1": {
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="
},
@@ -659,36 +2647,100 @@
"has-flag"
]
},
+ "tailwind-merge@3.4.0": {
+ "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="
+ },
+ "tailwindcss-animate@1.0.7_tailwindcss@4.1.13": {
+ "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==",
+ "dependencies": [
+ "tailwindcss"
+ ]
+ },
+ "tailwindcss@4.1.13": {
+ "integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w=="
+ },
+ "tapable@2.2.3": {
+ "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg=="
+ },
+ "tar@7.4.4": {
+ "integrity": "sha512-O1z7ajPkjTgEgmTGz0v9X4eqeEXTDREPTO77pVC1Nbs86feBU1Zhdg+edzavPmYW1olxkwsqA2v4uOw6E8LeDg==",
+ "dependencies": [
+ "@isaacs/fs-minipass",
+ "chownr",
+ "minipass",
+ "minizlib",
+ "yallist@5.0.0"
+ ]
+ },
+ "tiny-invariant@1.3.3": {
+ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
+ },
+ "tiny-warning@1.0.3": {
+ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
+ },
+ "tinyglobby@0.2.15_picomatch@4.0.3": {
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dependencies": [
+ "fdir",
+ "picomatch@4.0.3"
+ ]
+ },
"to-regex-range@5.0.1": {
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dependencies": [
"is-number"
]
},
- "ts-api-utils@2.1.0_typescript@5.8.3": {
+ "tr46@5.1.1": {
+ "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
+ "dependencies": [
+ "punycode"
+ ]
+ },
+ "ts-api-utils@2.1.0_typescript@5.9.2": {
"integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
"dependencies": [
"typescript"
]
},
+ "tslib@2.8.1": {
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
+ },
+ "tw-animate-css@1.4.0": {
+ "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="
+ },
"type-check@0.4.0": {
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
"dependencies": [
"prelude-ls"
]
},
- "typescript-eslint@8.30.1_eslint@9.24.0_typescript@5.8.3_@typescript-eslint+parser@8.30.1__eslint@9.24.0__typescript@5.8.3": {
- "integrity": "sha512-D7lC0kcehVH7Mb26MRQi64LMyRJsj3dToJxM1+JVTl53DQSV5/7oUGWQLcKl1C1KnoVHxMMU2FNQMffr7F3Row==",
+ "typescript-eslint@8.44.0_eslint@9.35.0_typescript@5.9.2_@typescript-eslint+parser@8.44.0__eslint@9.35.0__typescript@5.9.2": {
+ "integrity": "sha512-ib7mCkYuIzYonCq9XWF5XNw+fkj2zg629PSa9KNIQ47RXFF763S5BIX4wqz1+FLPogTZoiw8KmCiRPRa8bL3qw==",
"dependencies": [
"@typescript-eslint/eslint-plugin",
"@typescript-eslint/parser",
+ "@typescript-eslint/typescript-estree",
"@typescript-eslint/utils",
"eslint",
"typescript"
]
},
- "typescript@5.8.3": {
- "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="
+ "typescript@5.9.2": {
+ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
+ "bin": true
+ },
+ "undici-types@7.10.0": {
+ "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="
+ },
+ "update-browserslist-db@1.1.3_browserslist@4.26.2": {
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+ "dependencies": [
+ "browserslist",
+ "escalade",
+ "picocolors"
+ ],
+ "bin": true
},
"uri-js@4.4.1": {
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
@@ -696,36 +2748,234 @@
"punycode"
]
},
+ "use-callback-ref@1.3.3_@types+react@19.1.13_react@19.1.1": {
+ "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
+ "dependencies": [
+ "@types/react",
+ "react",
+ "tslib"
+ ],
+ "optionalPeers": [
+ "@types/react"
+ ]
+ },
+ "use-sidecar@1.1.3_@types+react@19.1.13_react@19.1.1": {
+ "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
+ "dependencies": [
+ "@types/react",
+ "detect-node-es",
+ "react",
+ "tslib"
+ ],
+ "optionalPeers": [
+ "@types/react"
+ ]
+ },
+ "use-sync-external-store@1.5.0_react@19.1.1": {
+ "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
+ "dependencies": [
+ "react"
+ ]
+ },
+ "uuid@13.0.0": {
+ "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
+ "bin": true
+ },
+ "vite@7.1.6_@types+node@24.2.0_picomatch@4.0.3": {
+ "integrity": "sha512-SRYIB8t/isTwNn8vMB3MR6E+EQZM/WG1aKmmIUCfDXfVvKfc20ZpamngWHKzAmmu9ppsgxsg4b2I7c90JZudIQ==",
+ "dependencies": [
+ "@types/node",
+ "esbuild",
+ "fdir",
+ "picomatch@4.0.3",
+ "postcss",
+ "rollup",
+ "tinyglobby"
+ ],
+ "optionalDependencies": [
+ "fsevents"
+ ],
+ "optionalPeers": [
+ "@types/node"
+ ],
+ "bin": true
+ },
+ "webidl-conversions@7.0.0": {
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="
+ },
+ "whatwg-url@14.2.0": {
+ "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
+ "dependencies": [
+ "tr46",
+ "webidl-conversions"
+ ]
+ },
"which@2.0.2": {
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dependencies": [
"isexe"
- ]
+ ],
+ "bin": true
},
"word-wrap@1.2.5": {
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="
},
+ "yallist@3.1.1": {
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
+ },
+ "yallist@5.0.0": {
+ "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="
+ },
"yocto-queue@0.1.0": {
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
},
- "zod@4.0.0-beta.20250417T043022": {
- "integrity": "sha512-zjfYudLXPgHvRdCWzy/iJqhB6suE8tBqnGubbFHSkMvcknI4iexEP53QCO13FoC/EIALseuZReVykCY8yd/skA==",
- "dependencies": [
- "@zod/core"
- ]
+ "zod@4.1.12": {
+ "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ=="
}
},
"workspace": {
"packageJson": {
"dependencies": [
- "npm:@jsr/std__assert@1.0.12",
- "npm:@jsr/std__testing@1.0.11",
- "npm:eslint-plugin-simple-import-sort@12.1.1",
- "npm:eslint@9.24.0",
- "npm:prettier@3.5.3",
- "npm:typescript-eslint@8.30.1",
- "npm:zod@next"
+ "npm:@biomejs/biome@2.2.4",
+ "npm:@jsr/std__assert@1.0.14",
+ "npm:@jsr/std__testing@1.0.15"
]
+ },
+ "members": {
+ "api": {
+ "packageJson": {
+ "dependencies": [
+ "npm:zod@4.1.12"
+ ]
+ }
+ },
+ "apps/react": {
+ "packageJson": {
+ "dependencies": [
+ "npm:@eslint/js@9.35.0",
+ "npm:@jsr/valkyr__db@2.0.0",
+ "npm:@jsr/valkyr__event-emitter@1.0.1",
+ "npm:@radix-ui/react-avatar@^1.1.11",
+ "npm:@radix-ui/react-dialog@^1.1.15",
+ "npm:@radix-ui/react-dropdown-menu@^2.1.16",
+ "npm:@radix-ui/react-scroll-area@^1.2.10",
+ "npm:@radix-ui/react-separator@^1.1.8",
+ "npm:@radix-ui/react-slot@^1.2.4",
+ "npm:@radix-ui/react-tooltip@^1.2.8",
+ "npm:@tabler/icons-react@3.35.0",
+ "npm:@tailwindcss/vite@4.1.13",
+ "npm:@tanstack/react-query@5.89.0",
+ "npm:@tanstack/react-router-devtools@1.131.47",
+ "npm:@tanstack/react-router@1.131.47",
+ "npm:@types/react-dom@19.1.9",
+ "npm:@types/react@19.1.13",
+ "npm:@vitejs/plugin-react@4.7.0",
+ "npm:@zitadel/react@1.1.0",
+ "npm:class-variance-authority@~0.7.1",
+ "npm:clsx@^2.1.1",
+ "npm:eslint-plugin-react-hooks@5.2.0",
+ "npm:eslint-plugin-react-refresh@0.4.20",
+ "npm:eslint@9.35.0",
+ "npm:fast-equals@5.2.2",
+ "npm:globals@16.4.0",
+ "npm:lucide-react@0.554",
+ "npm:react-dom@19.1.1",
+ "npm:react@19.1.1",
+ "npm:tailwind-merge@^3.4.0",
+ "npm:tailwindcss-animate@^1.0.7",
+ "npm:tailwindcss@4.1.13",
+ "npm:tw-animate-css@1.4.0",
+ "npm:typescript-eslint@8.44.0",
+ "npm:typescript@5.9.2",
+ "npm:vite@7.1.6",
+ "npm:zod@4.1.12"
+ ]
+ }
+ },
+ "modules/account": {
+ "packageJson": {
+ "dependencies": [
+ "npm:zod@4.1.12"
+ ]
+ }
+ },
+ "platform/cerbos": {
+ "packageJson": {
+ "dependencies": [
+ "npm:@cerbos/core@0.25.1",
+ "npm:@cerbos/http@0.23.3"
+ ]
+ }
+ },
+ "platform/config": {
+ "packageJson": {
+ "dependencies": [
+ "npm:@jsr/std__dotenv@0.225.5",
+ "npm:zod@4.1.12"
+ ]
+ }
+ },
+ "platform/database": {
+ "packageJson": {
+ "dependencies": [
+ "npm:postgres@3.4.7",
+ "npm:zod@4.1.12"
+ ]
+ }
+ },
+ "platform/logger": {
+ "packageJson": {
+ "dependencies": [
+ "npm:@jsr/valkyr__event-store@2.0.1",
+ "npm:zod@4.1.12"
+ ]
+ }
+ },
+ "platform/relay": {
+ "packageJson": {
+ "dependencies": [
+ "npm:path-to-regexp@8",
+ "npm:zod@4.1.12"
+ ]
+ }
+ },
+ "platform/routes": {
+ "packageJson": {
+ "dependencies": [
+ "npm:zod@4.1.12"
+ ]
+ }
+ },
+ "platform/server": {
+ "packageJson": {
+ "dependencies": [
+ "npm:@jsr/valkyr__json-rpc@1.1.0",
+ "npm:zod@4.1.12"
+ ]
+ }
+ },
+ "platform/socket": {
+ "packageJson": {
+ "dependencies": [
+ "npm:@jsr/valkyr__json-rpc@1.1.0"
+ ]
+ }
+ },
+ "platform/spec": {
+ "packageJson": {
+ "dependencies": [
+ "npm:zod@4.1.12"
+ ]
+ }
+ },
+ "platform/vault": {
+ "packageJson": {
+ "dependencies": [
+ "npm:jose@6.1.0",
+ "npm:nanoid@5.1.5"
+ ]
+ }
+ }
}
}
}
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..fb5a455
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,45 @@
+networks:
+ server:
+ name: server
+
+volumes:
+ mongo:
+ driver: local
+
+services:
+
+ # MongoDB
+ # --------------------------------------------------------------------------------
+ # Used by event store and read store for managing and reading application data.
+
+ mongo:
+ restart: unless-stopped
+ image: mongo:8
+ container_name: boilerplate_mongo
+ ports:
+ - 6017:27017
+ environment:
+ MONGO_INITDB_ROOT_USERNAME: root
+ MONGO_INITDB_ROOT_PASSWORD: password
+ volumes:
+ - mongo:/data/db
+ networks:
+ - server
+
+ # Cerbos
+ # --------------------------------------------------------------------------------
+ # Policy engine for application access control.
+
+ cerbos:
+ restart: unless-stopped
+ image: ghcr.io/cerbos/cerbos:latest
+ container_name: boilerplate_cerbos
+ command: ["server", "--config=/config.yaml"]
+ ports:
+ - 6592:3592
+ - 6593:3593
+ - 6594:3594
+ volumes:
+ - ./cerbos.yaml:/config.yaml
+ networks:
+ - server
diff --git a/eslint.config.mjs b/eslint.config.mjs
deleted file mode 100644
index ff5356c..0000000
--- a/eslint.config.mjs
+++ /dev/null
@@ -1,30 +0,0 @@
-import simpleImportSort from "eslint-plugin-simple-import-sort";
-import tseslint from "typescript-eslint";
-
-export default [
- ...tseslint.configs.recommended,
- {
- plugins: {
- "simple-import-sort": simpleImportSort,
- },
- rules: {
- "simple-import-sort/imports": "error",
- "simple-import-sort/exports": "error",
- },
- },
- {
- files: ["**/*.ts"],
- rules: {
- "@typescript-eslint/ban-ts-comment": ["error", {
- "ts-expect-error": "allow-with-description",
- minimumDescriptionLength: 10,
- }],
- "@typescript-eslint/ban-types": "off",
- "@typescript-eslint/no-explicit-any": "off",
- "@typescript-eslint/no-unused-vars": ["error", {
- argsIgnorePattern: "^_",
- varsIgnorePattern: "^_",
- }],
- },
- },
-];
diff --git a/libraries/action.ts b/libraries/action.ts
deleted file mode 100644
index 5a69064..0000000
--- a/libraries/action.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import z, { ZodObject, ZodRawShape } from "zod";
-
-export class Action {
- constructor(readonly state: TActionState) {}
-
- /**
- * Input object required by the action to fulfill its function.
- *
- * @param input - Schema defining the input requirements of the action.
- */
- input(input: TInput): Action & { input: ZodObject }> {
- return new Action({ ...this.state, input: z.object(input) as any });
- }
-
- /**
- * Output object defining the result shape of the action.
- *
- * @param output - Schema defining the result shape.
- */
- output(output: TOutput): Action & { output: ZodObject }> {
- return new Action({ ...this.state, output: z.object(output) as any });
- }
-
- /**
- * Add handler method to the action.
- *
- * @param handle - Handler method.
- */
- handle>(
- handle: THandleFn,
- ): Action & { handle: THandleFn }> {
- return new Action({ ...this.state, handle });
- }
-}
-
-/*
- |--------------------------------------------------------------------------------
- | Factory
- |--------------------------------------------------------------------------------
- */
-
-export const action = {
- make(name: string) {
- return new Action({ name });
- },
-};
-
-/*
- |--------------------------------------------------------------------------------
- | Types
- |--------------------------------------------------------------------------------
- */
-
-type ActionState = {
- name: string;
- input?: ZodObject;
- output?: ZodObject;
- handle?: ActionHandlerFn;
-};
-
-type ActionHandlerFn = TInput extends ZodObject
- ? (input: z.infer) => TOutput extends ZodObject ? Promise> : Promise
- : () => TOutput extends ZodObject ? Promise> : Promise;
diff --git a/libraries/errors.ts b/libraries/errors.ts
deleted file mode 100644
index 9fd6683..0000000
--- a/libraries/errors.ts
+++ /dev/null
@@ -1,227 +0,0 @@
-export abstract class RelayError extends Error {
- constructor(
- message: string,
- readonly status: number,
- readonly data?: D,
- ) {
- super(message);
- }
-
- toJSON() {
- return {
- status: this.status,
- message: this.message,
- data: this.data,
- };
- }
-}
-
-export class BadRequestError extends RelayError {
- /**
- * Instantiate a new BadRequestError.
- *
- * The **HTTP 400 Bad Request** response status code indicates that the server
- * cannot or will not process the request due to something that is perceived to
- * be a client error.
- *
- * @param data - Optional data to send with the error.
- */
- constructor(message = "Bad Request", data?: D) {
- super(message, 400, data);
- }
-}
-
-export class UnauthorizedError extends RelayError {
- /**
- * Instantiate a new UnauthorizedError.
- *
- * The **HTTP 401 Unauthorized** response status code indicates that the client
- * request has not been completed because it lacks valid authentication
- * credentials for the requested resource.
- *
- * This status code is sent with an HTTP WWW-Authenticate response header that
- * contains information on how the client can request for the resource again after
- * prompting the user for authentication credentials.
- *
- * This status code is similar to the **403 Forbidden** status code, except that
- * in situations resulting in this status code, user authentication can allow
- * access to the resource.
- *
- * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401
- *
- * @param message - Optional message to send with the error. Default: "Unauthorized".
- * @param data - Optional data to send with the error.
- */
- constructor(message = "Unauthorized", data?: D) {
- super(message, 401, data);
- }
-}
-
-export class ForbiddenError extends RelayError {
- /**
- * Instantiate a new ForbiddenError.
- *
- * The **HTTP 403 Forbidden** response status code indicates that the server
- * understands the request but refuses to authorize it.
- *
- * This status is similar to **401**, but for the **403 Forbidden** status code
- * re-authenticating makes no difference. The access is permanently forbidden and
- * tied to the application logic, such as insufficient rights to a resource.
- *
- * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403
- *
- * @param message - Optional message to send with the error. Default: "Forbidden".
- * @param data - Optional data to send with the error.
- */
- constructor(message = "Forbidden", data?: D) {
- super(message, 403, data);
- }
-}
-
-export class NotFoundError extends RelayError {
- /**
- * Instantiate a new NotFoundError.
- *
- * The **HTTP 404 Not Found** response status code indicates that the server
- * cannot find the requested resource. Links that lead to a 404 page are often
- * called broken or dead links and can be subject to link rot.
- *
- * A 404 status code only indicates that the resource is missing: not whether the
- * absence is temporary or permanent. If a resource is permanently removed,
- * use the **410 _(Gone)_** status instead.
- *
- * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404
- *
- * @param message - Optional message to send with the error. Default: "Not Found".
- * @param data - Optional data to send with the error.
- */
- constructor(message = "Not Found", data?: D) {
- super(message, 404, data);
- }
-}
-
-export class NotAcceptableError extends RelayError {
- /**
- * Instantiate a new NotAcceptableError.
- *
- * The **HTTP 406 Not Acceptable** client error response code indicates that the
- * server cannot produce a response matching the list of acceptable values
- * defined in the request, and that the server is unwilling to supply a default
- * representation.
- *
- * @param message - Optional message to send with the error. Default: "Not Acceptable".
- * @param data - Optional data to send with the error.
- */
- constructor(message = "Not Acceptable", data?: D) {
- super(message, 406, data);
- }
-}
-
-export class ConflictError extends RelayError {
- /**
- * Instantiate a new ConflictError.
- *
- * The **HTTP 409 Conflict** response status code indicates a request conflict
- * with the current state of the target resource.
- *
- * Conflicts are most likely to occur in response to a PUT request. For example,
- * you may get a 409 response when uploading a file that is older than the
- * existing one on the server, resulting in a version control conflict.
- *
- * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409
- *
- * @param message - Optional message to send with the error. Default: "Conflict".
- * @param data - Optional data to send with the error.
- */
- constructor(message = "Conflict", data?: D) {
- super(message, 409, data);
- }
-}
-
-export class GoneError extends RelayError {
- /**
- * Instantiate a new GoneError.
- *
- * The **HTTP 410 Gone** indicates that the target resource is no longer
- * available at the origin server and that this condition is likely to be
- * permanent. A 410 response is cacheable by default.
- *
- * Clients should not repeat requests for resources that return a 410 response,
- * and website owners should remove or replace links that return this code. If
- * server owners don't know whether this condition is temporary or permanent,
- * a 404 status code should be used instead.
- *
- * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/410
- *
- * @param message - Optional message to send with the error. Default: "Gone".
- * @param data - Optional data to send with the error.
- */
- constructor(message = "Gone", data?: D) {
- super(message, 410, data);
- }
-}
-
-export class UnprocessableContentError extends RelayError {
- /**
- * Instantiate a new UnprocessableContentError.
- *
- * The **HTTP 422 Unprocessable Content** client error response status code
- * indicates that the server understood the content type of the request entity,
- * and the syntax of the request entity was correct, but it was unable to
- * process the contained instructions.
- *
- * Clients that receive a 422 response should expect that repeating the request
- * without modification will fail with the same error.
- *
- * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422
- *
- * @param message - Optional message to send with the error. Default: "Unprocessable Content".
- * @param data - Optional data to send with the error.
- */
- constructor(message = "Unprocessable Content", data?: D) {
- super(message, 422, data);
- }
-}
-
-export class InternalServerError extends RelayError {
- /**
- * Instantiate a new InternalServerError.
- *
- * The **HTTP 500 Internal Server Error** server error response code indicates that
- * the server encountered an unexpected condition that prevented it from fulfilling
- * the request.
- *
- * This error response is a generic "catch-all" response. Usually, this indicates
- * the server cannot find a better 5xx error code to response. Sometimes, server
- * administrators log error responses like the 500 status code with more details
- * about the request to prevent the error from happening again in the future.
- *
- * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500
- *
- * @param message - Optional message to send with the error. Default: "Internal Server Error".
- * @param data - Optional data to send with the error.
- */
- constructor(message = "Internal Server Error", data?: D) {
- super(message, 500, data);
- }
-}
-
-export class ServiceUnavailableError extends RelayError {
- /**
- * Instantiate a new ServiceUnavailableError.
- *
- * The **HTTP 503 Service Unavailable** server error response status code indicates
- * that the server is not ready to handle the request.
- *
- * This response should be used for temporary conditions and the Retry-After HTTP header
- * should contain the estimated time for the recovery of the service, if possible.
- *
- * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503
- *
- * @param message - Optional message to send with the error. Default: "Service Unavailable".
- * @param data - Optional data to send with the error.
- */
- constructor(message = "Service Unavailable", data?: D) {
- super(message, 503, data);
- }
-}
diff --git a/libraries/relay.ts b/libraries/relay.ts
deleted file mode 100644
index 75b0526..0000000
--- a/libraries/relay.ts
+++ /dev/null
@@ -1,449 +0,0 @@
-import z, { ZodType } from "zod";
-
-import { BadRequestError, NotFoundError, RelayError } from "./errors.ts";
-import { Route, RouteMethod } from "./route.ts";
-
-const SUPPORTED_MEHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
-
-export class Relay {
- /**
- * Route maps funneling registered routes to the specific methods supported by
- * the relay instance.
- */
- readonly routes: Routes = {
- POST: [],
- GET: [],
- PUT: [],
- PATCH: [],
- DELETE: [],
- };
-
- /**
- * List of paths in the '${method} ${path}' format allowing us to quickly throw
- * errors if a duplicate route path is being added.
- */
- readonly #paths = new Set();
-
- /**
- * Route index in the '${method} ${path}' format allowing for quick access to
- * a specific route.
- */
- readonly #index = new Map();
-
- /**
- * Instantiate a new Relay instance.
- *
- * @param config - Relay configuration to apply to the instance.
- * @param routes - Routes to register with the instance.
- */
- constructor(
- readonly config: RelayConfig,
- routes: TRoutes,
- ) {
- const methods: (keyof typeof this.routes)[] = [];
- for (const route of routes) {
- this.#validateRoutePath(route);
- this.routes[route.method].push(route);
- methods.push(route.method);
- this.#index.set(`${route.method} ${route.path}`, route);
- }
- for (const method of methods) {
- this.routes[method].sort(byStaticPriority);
- }
- }
-
- /*
- |--------------------------------------------------------------------------------
- | Agnostic
- |--------------------------------------------------------------------------------
- */
-
- /**
- * Retrieve a route for the given method/path combination which can be further extended
- * for serving incoming third party requests.
- *
- * @param method - Method the route is registered for.
- * @param path - Path the route is registered under.
- *
- * @examples
- *
- * ```ts
- * const relay = new Relay([
- * route
- * .post("/users")
- * .body(
- * z.object({
- * name: z.object({ family: z.string(), given: z.string() }),
- * email: z.string().check(z.email()),
- * })
- * )
- * ]);
- *
- * relay
- * .route("POST", "/users")
- * .actions([hasSessionUser, hasAccess("users", "create")])
- * .handle(async ({ name, email, sessionUserId }) => {
- * // await db.users.insert({ name, email, createdBy: sessionUserId });
- * })
- * ```
- */
- route<
- TMethod extends RouteMethod,
- TPath extends Extract["state"]["path"],
- TRoute extends Extract,
- >(method: TMethod, path: TPath): TRoute {
- const route = this.#index.get(`${method} ${path}`);
- if (route === undefined) {
- throw new Error(`Relay > Route not found at '${method} ${path}' index`);
- }
- return route as TRoute;
- }
-
- /*
- |--------------------------------------------------------------------------------
- | Client
- |--------------------------------------------------------------------------------
- */
-
- /**
- * Send a "POST" request through the relay `fetch` adapter.
- *
- * @param path - Path to send request to.
- * @param args - List of request arguments.
- */
- async post<
- TPath extends Extract["state"]["path"],
- TRoute extends Extract,
- >(path: TPath, ...args: TRoute["args"]): Promise> {
- return this.#send("POST", path, args) as RelayResponse;
- }
-
- /**
- * Send a "GET" request through the relay `fetch` adapter.
- *
- * @param path - Path to send request to.
- * @param args - List of request arguments.
- */
- async get<
- TPath extends Extract["state"]["path"],
- TRoute extends Extract,
- >(path: TPath, ...args: TRoute["args"]): Promise> {
- return this.#send("GET", path, args) as RelayResponse;
- }
-
- /**
- * Send a "PUT" request through the relay `fetch` adapter.
- *
- * @param path - Path to send request to.
- * @param args - List of request arguments.
- */
- async put<
- TPath extends Extract["state"]["path"],
- TRoute extends Extract,
- >(path: TPath, ...args: TRoute["args"]): Promise> {
- return this.#send("PUT", path, args) as RelayResponse;
- }
-
- /**
- * Send a "PATCH" request through the relay `fetch` adapter.
- *
- * @param path - Path to send request to.
- * @param args - List of request arguments.
- */
- async patch<
- TPath extends Extract["state"]["path"],
- TRoute extends Extract,
- >(path: TPath, ...args: TRoute["args"]): Promise> {
- return this.#send("PATCH", path, args) as RelayResponse;
- }
-
- /**
- * Send a "DELETE" request through the relay `fetch` adapter.
- *
- * @param path - Path to send request to.
- * @param args - List of request arguments.
- */
- async delete<
- TPath extends Extract["state"]["path"],
- TRoute extends Extract,
- >(path: TPath, ...args: TRoute["args"]): Promise> {
- return this.#send("DELETE", path, args) as RelayResponse;
- }
-
- /*
- |--------------------------------------------------------------------------------
- | Server
- |--------------------------------------------------------------------------------
- */
-
- /**
- * Handle a incoming fetch request.
- *
- * @param request - Fetch request to pass to a route handler.
- */
- async handle(request: Request) {
- const url = new URL(request.url);
-
- const matched = this.#resolve(request.method, request.url);
- if (matched === undefined) {
- return toResponse(
- new NotFoundError(`Invalid routing path provided for ${request.url}`, {
- method: request.method,
- url: request.url,
- }),
- );
- }
-
- const { route, params } = matched;
-
- // ### Context
- // Context is passed to every route handler and provides a suite of functionality
- // and request data.
-
- const context = {
- ...params,
- ...toSearch(url.searchParams),
- };
-
- // ### Params
- // If the route has params we want to coerce the values to the expected types.
-
- if (route.state.params !== undefined) {
- const result = await route.state.params.safeParseAsync(context.params);
- if (result.success === false) {
- return toResponse(new BadRequestError("Invalid request params", z.prettifyError(result.error)));
- }
- context.params = result.data;
- }
-
- // ### Query
- // If the route has a query schema we need to validate and parse the query.
-
- if (route.state.search !== undefined) {
- const result = await route.state.search.safeParseAsync(context.query ?? {});
- if (result.success === false) {
- return toResponse(new BadRequestError("Invalid request query", z.prettifyError(result.error)));
- }
- context.query = result.data;
- }
-
- // ### Body
- // If the route has a body schema we need to validate and parse the body.
-
- const body: Record = {};
-
- if (route.state.body !== undefined) {
- const result = await route.state.body.safeParseAsync(body);
- if (result.success === false) {
- return toResponse(new BadRequestError("Invalid request body", z.prettifyError(result.error)));
- }
- context.body = result.data;
- }
-
- // ### Actions
- // Run through all assigned actions for the route.
-
- if (route.state.actions !== undefined) {
- for (const action of route.state.actions) {
- const result = (await action.state.input?.safeParseAsync(context)) ?? { success: true, data: {} };
- if (result.success === false) {
- return toResponse(new BadRequestError("Invalid action input", z.prettifyError(result.error)));
- }
- const output = (await action.state.handle?.(result.data)) ?? {};
- for (const key in output) {
- context[key] = output[key];
- }
- }
- }
-
- // ### Handler
- // Execute the route handler and apply the result.
-
- return toResponse(await route.state.handle?.(context).catch((error) => error));
- }
-
- /**
- * Attempt to resolve a route based on the given method and pathname.
- *
- * @param method - HTTP method.
- * @param url - HTTP request url.
- */
- #resolve(method: string, url: string): ResolvedRoute | undefined {
- this.#assertMethod(method);
- for (const route of this.routes[method]) {
- if (route.match(url) === true) {
- return { route, params: route.getParsedParams(url) };
- }
- }
- }
-
- #validateRoutePath(route: Route): void {
- const path = `${route.method} ${route.path}`;
- if (this.#paths.has(path)) {
- throw new Error(`Router > Path ${path} already exists`);
- }
- this.#paths.add(path);
- }
-
- async #send(method: RouteMethod, url: string, args: any[]) {
- const route = this.route(method, url);
-
- // ### Input
-
- const input: RequestInput = { method, url, search: "" };
-
- let index = 0; // argument incrementor
-
- if (route.state.params !== undefined) {
- const params = args[index++] as { [key: string]: string };
- for (const key in params) {
- input.url = input.url.replace(`:${key}`, params[key]);
- }
- }
-
- if (route.state.search !== undefined) {
- const search = args[index++] as { [key: string]: string };
- const pieces: string[] = [];
- for (const key in search) {
- pieces.push(`${key}=${search[key]}`);
- }
- if (pieces.length > 0) {
- input.search = `?${pieces.join("&")}`;
- }
- }
-
- if (route.state.body !== undefined) {
- input.body = JSON.stringify(args[index++]);
- }
-
- // ### Fetch
-
- const data = await this.config.adapter.fetch(input);
- if (route.state.output !== undefined) {
- return route.state.output.parse(data);
- }
- return data;
- }
-
- #assertMethod(method: string): asserts method is RouteMethod {
- if (!SUPPORTED_MEHODS.includes(method)) {
- throw new Error(`Router > Unsupported method '${method}'`);
- }
- }
-}
-
-/*
- |--------------------------------------------------------------------------------
- | Helpers
- |--------------------------------------------------------------------------------
- */
-
-/**
- * Sorting method for routes to ensure that static properties takes precedence
- * for when a route is matched against incoming requests.
- *
- * @param a - Route A
- * @param b - Route B
- */
-function byStaticPriority(a: Route, b: Route) {
- const aSegments = a.path.split("/");
- const bSegments = b.path.split("/");
-
- const maxLength = Math.max(aSegments.length, bSegments.length);
-
- for (let i = 0; i < maxLength; i++) {
- const aSegment = aSegments[i] || "";
- const bSegment = bSegments[i] || "";
-
- const isADynamic = aSegment.startsWith(":");
- const isBDynamic = bSegment.startsWith(":");
-
- if (isADynamic !== isBDynamic) {
- return isADynamic ? 1 : -1;
- }
-
- if (isADynamic === false && aSegment !== bSegment) {
- return aSegment.localeCompare(bSegment);
- }
- }
-
- return a.path.localeCompare(b.path);
-}
-
-/**
- * Resolve and return query object from the provided search parameters, or undefined
- * if the search parameters does not have any entries.
- *
- * @param searchParams - Search params to create a query object from.
- */
-function toSearch(searchParams: URLSearchParams): object | undefined {
- if (searchParams.size === 0) {
- return undefined;
- }
- const result: Record = {};
- for (const [key, value] of searchParams.entries()) {
- result[key] = value;
- }
- return result;
-}
-
-/**
- * Takes a server side request result and returns a fetch Response.
- *
- * @param result - Result to send back as a Response.
- */
-function toResponse(result: object | RelayError | Response | void): Response {
- if (result instanceof Response) {
- return result;
- }
- if (result instanceof RelayError) {
- return new Response(result.message, {
- status: result.status,
- });
- }
- if (result === undefined) {
- return new Response(null, { status: 204 });
- }
- return new Response(JSON.stringify(result), {
- status: 200,
- headers: {
- "content-type": "application/json",
- },
- });
-}
-
-/*
- |--------------------------------------------------------------------------------
- | Types
- |--------------------------------------------------------------------------------
- */
-
-type Routes = {
- POST: Route[];
- GET: Route[];
- PUT: Route[];
- PATCH: Route[];
- DELETE: Route[];
-};
-
-type ResolvedRoute = {
- route: Route;
- params: any;
-};
-
-type RelayResponse = TRoute["state"]["output"] extends ZodType ? z.infer : void;
-
-type RelayConfig = {
- adapter: RelayAdapter;
-};
-
-export type RelayAdapter = {
- fetch(input: RequestInput): Promise;
-};
-
-export type RequestInput = {
- method: RouteMethod;
- url: string;
- search: string;
- body?: string;
-};
diff --git a/libraries/route.ts b/libraries/route.ts
deleted file mode 100644
index dbbc0e7..0000000
--- a/libraries/route.ts
+++ /dev/null
@@ -1,332 +0,0 @@
-import z, { ZodObject, ZodRawShape, ZodType } from "zod";
-
-import { Action } from "./action.ts";
-
-export class Route {
- #pattern?: URLPattern;
-
- declare readonly args: RouteArgs;
- declare readonly context: RouteContext;
-
- constructor(readonly state: TRouteState) {}
-
- /**
- * HTTP Method
- */
- get method(): RouteMethod {
- return this.state.method;
- }
-
- /**
- * URL pattern of the route.
- */
- get pattern(): URLPattern {
- if (this.#pattern === undefined) {
- this.#pattern = new URLPattern({ pathname: this.path });
- }
- return this.#pattern;
- }
-
- /**
- * URL path
- */
- get path(): string {
- return this.state.path;
- }
-
- /**
- * Check if the provided URL matches the route pattern.
- *
- * @param url - HTTP request.url
- */
- match(url: string): boolean {
- return this.pattern.test(url);
- }
-
- /**
- * Extract parameters from the provided URL based on the route pattern.
- *
- * @param url - HTTP request.url
- */
- getParsedParams(url: string): TRouteState["params"] extends ZodObject ? z.infer : object {
- const params = this.pattern.exec(url)?.pathname.groups;
- if (params === undefined) {
- return {};
- }
- return this.state.params?.parse(params) ?? params;
- }
-
- /**
- * Params allows for custom casting of URL parameters. If a parameter does not
- * have a corresponding zod schema the default param type is "string".
- *
- * @param params - URL params.
- *
- * @examples
- *
- * ```ts
- * route
- * .post("/foo/:bar")
- * .params({
- * bar: z.number({ coerce: true })
- * })
- * .handle(async ({ params: { bar } }) => {
- * console.log(typeof bar); // => number
- * });
- * ```
- */
- params(params: TParams): Route & { params: ZodObject }> {
- return new Route({ ...this.state, params }) as any;
- }
-
- /**
- * Search allows for custom casting of URL search parameters. If a parameter does
- * not have a corresponding zod schema the default param type is "string".
- *
- * @param search - URL search arguments.
- *
- * @examples
- *
- * ```ts
- * route
- * .post("/foo")
- * .search({
- * bar: z.number({ coerce: true })
- * })
- * .handle(async ({ search: { bar } }) => {
- * console.log(typeof bar); // => number
- * });
- * ```
- */
- search(search: TSearch): Route & { search: ZodObject }> {
- return new Route({ ...this.state, search }) as any;
- }
-
- /**
- * Shape of the body this route expects to receive. This is used by all
- * mutator routes and has no effect when defined on "GET" methods.
- *
- * @param body - Body the route expects.
- *
- * @examples
- *
- * ```ts
- * route
- * .post("/foo")
- * .body(
- * z.object({
- * bar: z.number()
- * })
- * )
- * .handle(async ({ bar }) => {
- * console.log(typeof bar); // => number
- * });
- * ```
- */
- body(body: TBody): Route & { body: TBody }> {
- return new Route({ ...this.state, body });
- }
-
- /**
- * List of route level middleware action to execute before running the
- * route handler.
- *
- * @param actions - Actions to execute on this route.
- *
- * @examples
- *
- * ```ts
- * const hasFooBar = action
- * .make("hasFooBar")
- * .response(z.object({ foobar: z.number() }))
- * .handle(async () => {
- * return {
- * foobar: 1,
- * };
- * });
- *
- * route
- * .post("/foo")
- * .actions([hasFooBar])
- * .handle(async ({ foobar }) => {
- * console.log(typeof foobar); // => number
- * });
- * ```
- */
- actions(actions: TAction[]): Route & { actions: TAction[] }> {
- return new Route({ ...this.state, actions });
- }
-
- /**
- * Shape of the response this route produces. This is used by the transform
- * tools to ensure the client receives parsed data.
- *
- * @param response - Response shape of the route.
- *
- * @examples
- *
- * ```ts
- * route
- * .post("/foo")
- * .response(
- * z.object({
- * bar: z.number()
- * })
- * )
- * .handle(async () => {
- * return {
- * bar: 1
- * }
- * });
- * ```
- */
- response(output: TResponse): Route & { output: TResponse }> {
- return new Route({ ...this.state, output });
- }
-
- /**
- * Server handler callback method.
- *
- * @param handle - Handle function to trigger when the route is executed.
- */
- handle>(handle: THandleFn): Route & { handle: THandleFn }> {
- return new Route({ ...this.state, handle });
- }
-}
-
-/*
- |--------------------------------------------------------------------------------
- | Factories
- |--------------------------------------------------------------------------------
- */
-
-/**
- * Route factories allowing for easy generation of relay compliant routes.
- */
-export const route = {
- /**
- * Create a new "POST" route for the given path.
- *
- * @param path - Path to generate route for.
- *
- * @examples
- *
- * ```ts
- * route
- * .post("/foo")
- * .body(
- * z.object({ bar: z.string() })
- * );
- * ```
- */
- post(path: TPath) {
- return new Route({ method: "POST", path });
- },
-
- /**
- * Create a new "GET" route for the given path.
- *
- * @param path - Path to generate route for.
- *
- * @examples
- *
- * ```ts
- * route.get("/foo");
- * ```
- */
- get(path: TPath) {
- return new Route({ method: "GET", path });
- },
-
- /**
- * Create a new "PUT" route for the given path.
- *
- * @param path - Path to generate route for.
- *
- * @examples
- *
- * ```ts
- * route
- * .put("/foo")
- * .body(
- * z.object({ bar: z.string() })
- * );
- * ```
- */
- put(path: TPath) {
- return new Route({ method: "PUT", path });
- },
-
- /**
- * Create a new "PATCH" route for the given path.
- *
- * @param path - Path to generate route for.
- *
- * @examples
- *
- * ```ts
- * route
- * .patch("/foo")
- * .body(
- * z.object({ bar: z.string() })
- * );
- * ```
- */
- patch(path: TPath) {
- return new Route({ method: "PATCH", path });
- },
-
- /**
- * Create a new "DELETE" route for the given path.
- *
- * @param path - Path to generate route for.
- *
- * @examples
- *
- * ```ts
- * route.delete("/foo");
- * ```
- */
- delete(path: TPath) {
- return new Route({ method: "DELETE", path });
- },
-};
-
-/*
- |--------------------------------------------------------------------------------
- | Types
- |--------------------------------------------------------------------------------
- */
-
-type RouteState = {
- method: RouteMethod;
- path: string;
- params?: ZodObject;
- search?: ZodObject;
- body?: ZodObject;
- actions?: Array;
- output?: ZodType;
- handle?: HandleFn;
-};
-
-export type RouteMethod = "POST" | "GET" | "PUT" | "PATCH" | "DELETE";
-
-export type HandleFn = (context: TContext) => TResponse extends ZodType ? Promise> : Promise;
-
-type RouteContext = (TRouteState["params"] extends ZodObject ? z.infer : object) &
- (TRouteState["search"] extends ZodObject ? z.infer : object) &
- (TRouteState["body"] extends ZodObject ? z.infer : object) &
- (TRouteState["actions"] extends Array ? UnionToIntersection