commit d926a2acd13db6324b4ab5477745406731561c81 Author: jessy-david-dev Date: Sun Jan 18 23:33:05 2026 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..86ba49a --- /dev/null +++ b/.gitignore @@ -0,0 +1,44 @@ +# Dependencies +node_modules +.pnpm-store + +# Build outputs +dist +.next +out +build + +# Cache +.turbo +.cache +*.tsbuildinfo + +# Environment +.env +.env.local +.env.*.local + +# IDE +.vscode/* +!.vscode/extensions.json +!.vscode/settings.json +.idea + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Test +coverage +.nyc_output + +# Misc +*.swp +*.swo diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..be013d5 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +node_modules +.next +dist +build +coverage +pnpm-lock.yaml diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..fe7e2a6 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5", + "printWidth": 100, + "bracketSpacing": true +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..4349cf4 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "prisma.prisma", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "bradlc.vscode-tailwindcss", + "vitest.explorer" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..bd67cf7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,13 @@ +{ + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "typescript.preferences.importModuleSpecifier": "relative", + "typescript.tsdk": "node_modules/typescript/lib", + "tailwindCSS.experimental.classRegex": [["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]], + "files.associations": { + "*.css": "tailwindcss" + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..5dcbaef --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# Prisma UML Viewer + +![Next.js](https://img.shields.io/badge/Next.js-16-black?logo=next.js) +![React](https://img.shields.io/badge/React-19-61DAFB?logo=react) +![Tailwind CSS](https://img.shields.io/badge/Tailwind-4-38B2AC?logo=tailwindcss) +![TypeScript](https://img.shields.io/badge/TypeScript-5-3178C6?logo=typescript) +![License](https://img.shields.io/badge/License-MIT-green) + +Visualize your Prisma schemas as interactive ERD diagrams. + +## Features + +- Real-time schema parsing +- Interactive drag & drop diagrams with zoom and minimap +- Compatible with Prisma 5, 6 and 7 +- Export to Mermaid, PlantUML, DBML +- Auto-layout with dagre + +## Tech Stack + +- **Framework:** Next.js 16 (App Router) +- **UI:** React 19, Tailwind CSS 4 +- **Diagrams:** ReactFlow +- **Monorepo:** pnpm + Turborepo + +## Packages + +- [`@prisma-uml/parser`](./packages/prisma-parser/) - Schema parsing and export + +## Getting Started + +```bash +pnpm install +pnpm dev +``` + +Open http://localhost:3000 + +## Build + +```bash +pnpm build +pnpm start +``` + +## Author + +[Jessy DAVID](https://jessy-david.dev) + +## Acknowledgements + +- [prisma-generate-uml](https://github.com/AbianS/prisma-generate-uml) - Inspiration for this project +- [Prisma](https://www.prisma.io/) - Next-generation ORM +- [ReactFlow](https://reactflow.dev/) - Interactive node-based diagrams +- [Next.js](https://nextjs.org/) - React framework +- [Tailwind CSS](https://tailwindcss.com/) - Utility-first CSS +- [dagre](https://github.com/dagrejs/dagre) - Graph layout algorithm +- [Monaco Editor](https://microsoft.github.io/monaco-editor/) - Code editor + +## License + +MIT diff --git a/apps/web/eslint.config.mjs b/apps/web/eslint.config.mjs new file mode 100644 index 0000000..5b67bd0 --- /dev/null +++ b/apps/web/eslint.config.mjs @@ -0,0 +1,14 @@ +import { dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { FlatCompat } from '@eslint/eslintrc'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +const eslintConfig = [...compat.extends('next/core-web-vitals', 'next/typescript')]; + +export default eslintConfig; diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts new file mode 100644 index 0000000..9edff1c --- /dev/null +++ b/apps/web/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +import "./.next/types/routes.d.ts"; + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts new file mode 100644 index 0000000..72c9e0a --- /dev/null +++ b/apps/web/next.config.ts @@ -0,0 +1,11 @@ +import type { NextConfig } from 'next'; + +const nextConfig: NextConfig = { + transpilePackages: ['@prisma-uml/parser'], + output: 'standalone', + experimental: { + optimizePackageImports: ['@heroicons/react'], + }, +}; + +export default nextConfig; diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..2766efd --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,39 @@ +{ + "name": "@prisma-uml/web", + "version": "1.0.0", + "author": "Jessy DAVID - https://jessy-david.dev", + "private": true, + "scripts": { + "dev": "next dev --turbopack", + "build": "next build", + "start": "next start", + "lint": "next lint", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@dagrejs/dagre": "^1.1.8", + "@heroicons/react": "^2.2.0", + "@icons-pack/react-simple-icons": "^13.8.0", + "@monaco-editor/react": "^4.7.0", + "@prisma-uml/parser": "workspace:*", + "@xyflow/react": "^12.4.0", + "clsx": "^2.1.0", + "html-to-image": "^1.11.0", + "next": "^16.1.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "tailwind-merge": "^2.6.0", + "use-debounce": "^10.0.4", + "zod": "^3.24.0" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4.1.18", + "@types/dagre": "^0.7.53", + "@types/react": "^19.1.0", + "@types/react-dom": "^19.1.0", + "autoprefixer": "^10.4.20", + "postcss": "^8.5.0", + "tailwindcss": "^4.1.18", + "typescript": "^5.7.0" + } +} diff --git a/apps/web/postcss.config.mjs b/apps/web/postcss.config.mjs new file mode 100644 index 0000000..51a6e4e --- /dev/null +++ b/apps/web/postcss.config.mjs @@ -0,0 +1,6 @@ +export default { + plugins: { + '@tailwindcss/postcss': {}, + autoprefixer: {}, + }, +}; diff --git a/apps/web/public/android-chrome-192x192.png b/apps/web/public/android-chrome-192x192.png new file mode 100644 index 0000000..75886b2 Binary files /dev/null and b/apps/web/public/android-chrome-192x192.png differ diff --git a/apps/web/public/android-chrome-512x512.png b/apps/web/public/android-chrome-512x512.png new file mode 100644 index 0000000..51884d4 Binary files /dev/null and b/apps/web/public/android-chrome-512x512.png differ diff --git a/apps/web/public/apple-touch-icon.png b/apps/web/public/apple-touch-icon.png new file mode 100644 index 0000000..c004361 Binary files /dev/null and b/apps/web/public/apple-touch-icon.png differ diff --git a/apps/web/public/favicon-16x16.png b/apps/web/public/favicon-16x16.png new file mode 100644 index 0000000..ab76f6e Binary files /dev/null and b/apps/web/public/favicon-16x16.png differ diff --git a/apps/web/public/favicon-32x32.png b/apps/web/public/favicon-32x32.png new file mode 100644 index 0000000..b50a180 Binary files /dev/null and b/apps/web/public/favicon-32x32.png differ diff --git a/apps/web/public/favicon.ico b/apps/web/public/favicon.ico new file mode 100644 index 0000000..02f6b22 Binary files /dev/null and b/apps/web/public/favicon.ico differ diff --git a/apps/web/public/manifest.json b/apps/web/public/manifest.json new file mode 100644 index 0000000..e97f85e --- /dev/null +++ b/apps/web/public/manifest.json @@ -0,0 +1,21 @@ +{ + "name": "Prisma UML Viewer", + "short_name": "Prisma UML", + "description": "A online tool to visualize and explore your Prisma schema as an interactive ERD diagram.", + "theme_color": "#2563eb", + "background_color": "#09090b", + "display": "standalone", + "start_url": "/", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/apps/web/public/robots.txt b/apps/web/public/robots.txt new file mode 100644 index 0000000..3fb6894 --- /dev/null +++ b/apps/web/public/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://prisma.jessy-david.dev/sitemap.xml diff --git a/apps/web/src/app/api/export/route.ts b/apps/web/src/app/api/export/route.ts new file mode 100644 index 0000000..de0cb1f --- /dev/null +++ b/apps/web/src/app/api/export/route.ts @@ -0,0 +1,46 @@ +// Export endpoint - converts Prisma schema to various diagram formats + +import { NextRequest, NextResponse } from 'next/server'; +import { parsePrismaSchema, toMermaid, toPlantUML, toDBML } from '@prisma-uml/parser'; +import { z } from 'zod'; + +const requestSchema = z.object({ + schema: z.string().min(1, 'Schema is required'), + format: z.enum(['mermaid', 'plantuml', 'dbml']), +}); + +// map format names to renderer functions +const renderers = { + mermaid: toMermaid, + plantuml: toPlantUML, + dbml: toDBML, +} as const; + +type ExportFormat = keyof typeof renderers; + +export async function POST(req: NextRequest) { + try { + const body = await req.json(); + const { schema, format } = requestSchema.parse(body); + + // first parse the schema, then render to requested format + const parsed = await parsePrismaSchema(schema); + const render = renderers[format as ExportFormat]; + const output = render(parsed); + + return NextResponse.json({ success: true, output, format }); + } catch (error) { + if (error instanceof z.ZodError) { + return NextResponse.json( + { success: false, message: 'Validation error', errors: error.errors }, + { status: 400 } + ); + } + + if (error instanceof Error) { + return NextResponse.json({ success: false, message: error.message }, { status: 500 }); + } + + return NextResponse.json({ success: false, message: 'Internal server error' }, { status: 500 }); + } +} diff --git a/apps/web/src/app/api/parse/route.ts b/apps/web/src/app/api/parse/route.ts new file mode 100644 index 0000000..a1c3a05 --- /dev/null +++ b/apps/web/src/app/api/parse/route.ts @@ -0,0 +1,53 @@ +/** + * API endpoint to parse Prisma schemas + * Returns a structured representation of the schema for visualization + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { parsePrismaSchema, validatePrismaSchema } from '@prisma-uml/parser'; +import { z } from 'zod'; + +// validate incoming request body +const requestSchema = z.object({ + schema: z.string().min(1, 'Schema is required'), + version: z.enum(['5', '6', '7']).optional(), +}); + +export async function POST(req: NextRequest) { + try { + const body = await req.json(); + const { schema, version } = requestSchema.parse(body); + + // quick validation before parsing + const validation = validatePrismaSchema(schema); + if (!validation.valid) { + return NextResponse.json( + { success: false, message: 'Invalid schema', errors: validation.errors }, + { status: 422 } + ); + } + + // parse the schema - version is auto-detected if not specified + const parsedSchema = await parsePrismaSchema(schema, { + version: version ? (parseInt(version) as 5 | 6 | 7) : undefined, + }); + + return NextResponse.json({ success: true, data: parsedSchema }); + } catch (error) { + // handle validation errors from zod + if (error instanceof z.ZodError) { + return NextResponse.json( + { success: false, message: 'Validation error', errors: error.errors }, + { status: 400 } + ); + } + + // handle parsing errors + if (error instanceof Error) { + return NextResponse.json({ success: false, message: error.message }, { status: 422 }); + } + + // something unexpected happened + return NextResponse.json({ success: false, message: 'Internal server error' }, { status: 500 }); + } +} diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css new file mode 100644 index 0000000..872886d --- /dev/null +++ b/apps/web/src/app/globals.css @@ -0,0 +1,42 @@ +@import 'tailwindcss'; + +@theme { + --font-sans: var(--font-ibm-plex-sans), ui-sans-serif, system-ui, sans-serif; +} + +/* React Flow Controls - Dark Theme */ +.react-flow__controls { + background-color: #27272a !important; + border: 1px solid #3f3f46 !important; + border-radius: 8px !important; + box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.3) !important; +} + +.react-flow__controls-button { + background-color: #27272a !important; + border: none !important; + border-bottom: 1px solid #3f3f46 !important; + fill: #a1a1aa !important; + color: #a1a1aa !important; +} + +.react-flow__controls-button:hover { + background-color: #3f3f46 !important; + fill: #ffffff !important; + color: #ffffff !important; +} + +.react-flow__controls-button:last-child { + border-bottom: none !important; +} + +.react-flow__controls-button svg { + fill: currentColor !important; +} + +/* MiniMap - Dark Theme */ +.react-flow__minimap { + background-color: #18181b !important; + border: 1px solid #3f3f46 !important; + border-radius: 8px !important; +} diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx new file mode 100644 index 0000000..4ec2ae5 --- /dev/null +++ b/apps/web/src/app/layout.tsx @@ -0,0 +1,89 @@ +import type { Metadata } from 'next'; +import { IBM_Plex_Sans } from 'next/font/google'; +import './globals.css'; + +const ibmPlexSans = IBM_Plex_Sans({ + subsets: ['latin'], + weight: ['300', '400', '500', '600', '700'], + variable: '--font-ibm-plex-sans', +}); + +export const metadata: Metadata = { + title: { + default: 'Prisma UML Viewer', + template: '%s | Prisma UML Viewer', + }, + description: + 'A online tool to visualize and explore your Prisma schema as an interactive ERD diagram.', + keywords: [ + 'prisma', + 'uml', + 'erd', + 'diagram', + 'database', + 'schema', + 'visualization', + 'prisma schema', + 'database diagram', + 'entity relationship diagram', + 'prisma viewer', + 'schema visualization', + 'prisma 7', + 'prisma 6', + 'prisma 5', + ], + authors: [{ name: 'Jessy DAVID', url: 'https://jessy-david.dev' }], + creator: 'Jessy DAVID', + publisher: 'Jessy DAVID', + metadataBase: new URL('https://prisma.jessy-david.dev'), + alternates: { + canonical: '/', + }, + icons: { + icon: [ + { url: '/favicon-16x16.png', sizes: '16x16', type: 'image/png' }, + { url: '/favicon-32x32.png', sizes: '32x32', type: 'image/png' }, + ], + apple: '/apple-touch-icon.png', + shortcut: '/favicon.ico', + }, + manifest: '/manifest.json', + openGraph: { + title: 'Prisma UML Viewer', + description: + ' A online tool to visualize and explore your Prisma schema as an interactive ERD diagram. ', + type: 'website', + locale: 'en_US', + url: 'https://prisma.jessy-david.dev', + siteName: 'Prisma UML Viewer', + }, + twitter: { + card: 'summary_large_image', + title: 'Prisma UML Viewer', + description: 'Visualize your Prisma schemas as interactive UML/ERD diagrams', + }, + robots: { + index: true, + follow: true, + googleBot: { + index: true, + follow: true, + 'max-video-preview': -1, + 'max-image-preview': 'large', + 'max-snippet': -1, + }, + }, + category: 'technology', +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + {children} + + ); +} diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx new file mode 100644 index 0000000..437b421 --- /dev/null +++ b/apps/web/src/app/page.tsx @@ -0,0 +1,115 @@ +'use client'; + +// Main page component +import { useState, useCallback } from 'react'; +import { SchemaEditor } from '@/components/schema-editor'; +import { UMLDiagram } from '@/components/uml-diagram'; +import { DiagramControls } from '@/components/diagram-controls'; +import { ExportButtons } from '@/components/export-buttons'; +import { Header } from '@/components/header'; +import { Footer } from '@/components/footer'; +import type { PrismaSchema } from '@prisma-uml/parser'; + +// I went with 40% for the editor panel, feels like a good balance +const DEFAULT_PANEL_SIZE = 40; +const MIN_PANEL = 20; +const MAX_PANEL = 80; + +export default function HomePage() { + const [schema, setSchema] = useState(null); + const [rawSchema, setRawSchema] = useState(''); + const [error, setError] = useState(null); + const [panelSize, setPanelSize] = useState(DEFAULT_PANEL_SIZE); + const [isResizing, setIsResizing] = useState(false); + + // when parsing succeeds, update both the parsed schema and raw text + const handleSchemaChange = useCallback((parsedSchema: PrismaSchema, raw: string) => { + setSchema(parsedSchema); + setRawSchema(raw); + setError(null); // clear any previous errors + }, []); + + // if there's an error, we still want to show it but clear the diagram + const handleError = useCallback((errorMessage: string | null) => { + setError(errorMessage); + if (errorMessage) setSchema(null); + }, []); + + const handleMouseDown = useCallback(() => { + setIsResizing(true); + }, []); + + const handleMouseUp = useCallback(() => { + setIsResizing(false); + }, []); + + // drag handler for the resizable divider + const handleMouseMove = useCallback( + (e: React.MouseEvent) => { + if (!isResizing) return; + + const container = e.currentTarget as HTMLElement; + const rect = container.getBoundingClientRect(); + const percentage = ((e.clientX - rect.left) / rect.width) * 100; + + // clamp to reasonable bounds so panels don't get too small + const clamped = Math.min(Math.max(percentage, MIN_PANEL), MAX_PANEL); + setPanelSize(clamped); + }, + [isResizing] + ); + + return ( +
+
+ +
+ +
+ {/* Editor Panel */} +
+
+

Prisma Schema

+
+
+ +
+
+ + {/* Resizer */} +
+ + {/* Diagram Panel */} +
+ + + {error ? ( +
+
+

Parsing Error

+

{error}

+
+
+ ) : ( + + )} +
+
+ +
+
+ ); +} diff --git a/apps/web/src/components/diagram-controls.tsx b/apps/web/src/components/diagram-controls.tsx new file mode 100644 index 0000000..35140a4 --- /dev/null +++ b/apps/web/src/components/diagram-controls.tsx @@ -0,0 +1,51 @@ +'use client'; + +// Toolbar above the diagram showing schema stats + +import type { PrismaSchema } from '@prisma-uml/parser'; +import { CubeIcon, RectangleStackIcon, CircleStackIcon } from '@heroicons/react/24/outline'; + +interface DiagramControlsProps { + schema: PrismaSchema | null; +} + +export function DiagramControls({ schema }: DiagramControlsProps) { + // show a minimal bar when no schema is loaded + if (!schema) { + return ( +
+
+ UML Diagram +
+
+ ); + } + + return ( +
+
+
+

UML Diagram

+
+ + + Prisma {schema.version} + +
+ +
+ + + {schema.models.length} models + + {schema.enums.length > 0 && ( + + + {schema.enums.length} enums + + )} +
+
+
+ ); +} diff --git a/apps/web/src/components/export-buttons.tsx b/apps/web/src/components/export-buttons.tsx new file mode 100644 index 0000000..af0324e --- /dev/null +++ b/apps/web/src/components/export-buttons.tsx @@ -0,0 +1,191 @@ +'use client'; + +import { useState, useCallback } from 'react'; +import type { PrismaSchema } from '@prisma-uml/parser'; +import { + ArrowDownTrayIcon, + DocumentDuplicateIcon, + CheckIcon, + PhotoIcon, +} from '@heroicons/react/24/outline'; +import { useExportImage, type ImageFormat } from '@/hooks/use-export-image'; + +type ExportFormat = 'mermaid' | 'plantuml' | 'dbml'; + +interface ExportButtonsProps { + schema: PrismaSchema | null; + rawSchema: string; +} + +export function ExportButtons({ schema, rawSchema }: ExportButtonsProps) { + const [isExporting, setIsExporting] = useState(false); + const [copiedFormat, setCopiedFormat] = useState(null); + const [showDropdown, setShowDropdown] = useState(false); + const { downloadImage } = useExportImage(); + + const exportAs = useCallback( + async (format: ExportFormat) => { + if (!rawSchema) return; + + setIsExporting(true); + try { + const response = await fetch('/api/export', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ schema: rawSchema, format }), + }); + + const result = await response.json(); + + if (result.success) { + await navigator.clipboard.writeText(result.output); + setCopiedFormat(format); + setTimeout(() => setCopiedFormat(null), 2000); + } + } catch (error) { + console.error('Export failed:', error); + } finally { + setIsExporting(false); + setShowDropdown(false); + } + }, + [rawSchema] + ); + + const downloadAs = useCallback( + async (format: ExportFormat) => { + if (!rawSchema) return; + + setIsExporting(true); + try { + const response = await fetch('/api/export', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ schema: rawSchema, format }), + }); + + const result = await response.json(); + + if (result.success) { + const extensions: Record = { + mermaid: 'mmd', + plantuml: 'puml', + dbml: 'dbml', + }; + + const blob = new Blob([result.output], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `schema.${extensions[format]}`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } + } catch (error) { + console.error('Download failed:', error); + } finally { + setIsExporting(false); + setShowDropdown(false); + } + }, + [rawSchema] + ); + + const downloadImageAs = useCallback( + async (format: ImageFormat) => { + setIsExporting(true); + try { + await downloadImage({ + format, + filename: 'prisma-diagram', + backgroundColor: '#18181b', + }); + } catch (error) { + console.error('Image export failed:', error); + } finally { + setIsExporting(false); + setShowDropdown(false); + } + }, + [downloadImage] + ); + + if (!schema) { + return null; + } + + return ( +
+ + + {showDropdown && ( + <> +
setShowDropdown(false)} /> +
+
+

Copy to clipboard

+ {(['mermaid', 'plantuml', 'dbml'] as ExportFormat[]).map((format) => ( + + ))} +
+ +
+

Download Code

+ {(['mermaid', 'plantuml', 'dbml'] as ExportFormat[]).map((format) => ( + + ))} +
+ +
+

Download Image

+ {(['png', 'svg', 'jpeg'] as ImageFormat[]).map((format) => ( + + ))} +
+
+ + )} +
+ ); +} diff --git a/apps/web/src/components/footer.tsx b/apps/web/src/components/footer.tsx new file mode 100644 index 0000000..80638aa --- /dev/null +++ b/apps/web/src/components/footer.tsx @@ -0,0 +1,40 @@ +// Simple footer with GitHub link and credits + +import { SiGithub } from '@icons-pack/react-simple-icons'; +import packageJson from '../../package.json'; + +export function Footer() { + return ( + + ); +} diff --git a/apps/web/src/components/header.tsx b/apps/web/src/components/header.tsx new file mode 100644 index 0000000..5c56768 --- /dev/null +++ b/apps/web/src/components/header.tsx @@ -0,0 +1,41 @@ +// Site header with branding and nav links + +import type { ReactNode } from 'react'; +import { SiPrisma } from '@icons-pack/react-simple-icons'; + +interface HeaderProps { + children?: ReactNode; +} + +export function Header({ children }: HeaderProps) { + return ( +
+ {/* logo + title */} +
+
+ +

Prisma UML Viewer

+
+
+ + v5/v6/v7 + + beta +
+
+ + {/* right side: docs link + export buttons */} + +
+ ); +} diff --git a/apps/web/src/components/index.ts b/apps/web/src/components/index.ts new file mode 100644 index 0000000..2d2784c --- /dev/null +++ b/apps/web/src/components/index.ts @@ -0,0 +1,6 @@ +export { SchemaEditor } from './schema-editor'; +export { UMLDiagram } from './uml-diagram'; +export { DiagramControls } from './diagram-controls'; +export { ExportButtons } from './export-buttons'; +export { Header } from './header'; +export { Footer } from './footer'; diff --git a/apps/web/src/components/nodes/enum-node.tsx b/apps/web/src/components/nodes/enum-node.tsx new file mode 100644 index 0000000..917298d --- /dev/null +++ b/apps/web/src/components/nodes/enum-node.tsx @@ -0,0 +1,67 @@ +'use client'; + +// ReactFlow node for Prisma enums +// Purple theme to distinguish from models (blue) + +import { memo } from 'react'; +import { Handle, Position } from '@xyflow/react'; +import type { NodeProps, Node } from '@xyflow/react'; +import type { PrismaEnum } from '@prisma-uml/parser'; + +type EnumNodeData = { enum: PrismaEnum }; +type EnumNodeType = Node; + +export const EnumNode = memo(function EnumNode({ data }: NodeProps) { + const enumDef = data.enum; + const values = enumDef.values; + + return ( +
+
+
+ {enumDef.name} + + enum + +
+ +
+ {enumDef.values.map((value, index) => ( +
+
+ {index} + {value.name} +
+ {value.dbName && ( + + @map("{value.dbName}") + + )} +
+ ))} +
+ + {enumDef.documentation && ( +
+ {enumDef.documentation} +
+ )} + + + +
+ ); +}); diff --git a/apps/web/src/components/nodes/index.ts b/apps/web/src/components/nodes/index.ts new file mode 100644 index 0000000..d4a0e1e --- /dev/null +++ b/apps/web/src/components/nodes/index.ts @@ -0,0 +1,2 @@ +export { ModelNode } from './model-node'; +export { EnumNode } from './enum-node'; diff --git a/apps/web/src/components/nodes/model-node.tsx b/apps/web/src/components/nodes/model-node.tsx new file mode 100644 index 0000000..9e73344 --- /dev/null +++ b/apps/web/src/components/nodes/model-node.tsx @@ -0,0 +1,113 @@ +'use client'; + +// Custom ReactFlow node for Prisma models +// Shows fields with their types and various indicators (PK, FK, unique, etc) + +import { memo } from 'react'; +import { Handle, Position } from '@xyflow/react'; +import type { NodeProps, Node } from '@xyflow/react'; +import type { PrismaModel, PrismaField } from '@prisma-uml/parser'; +import { + KeyIcon, + LinkIcon, + ListBulletIcon, + QuestionMarkCircleIcon, +} from '@heroicons/react/24/outline'; + +type ModelNodeData = { model: PrismaModel }; +type ModelNodeType = Node; + +export const ModelNode = memo(function ModelNode({ data }: NodeProps) { + const { model } = data; + + return ( +
+
+
+
+ {model.name} +
+ {model.dbName && ( + + {model.dbName} + + )} +
+ +
+ {model.fields.map((field) => ( + + ))} +
+ + {model.primaryKey && ( +
+ Composite PK: {model.primaryKey.fields.join(', ')} +
+ )} + + + + +
+ ); +}); + +// individual field row with icons and badges +function FieldRow({ field }: { field: PrismaField }) { + const isRelation = field.kind === 'object'; + + // build the type string (e.g., "User[]" or "String?") + let typeStr = field.type; + if (field.isList) typeStr += '[]'; + else if (!field.isRequired) typeStr += '?'; + + return ( +
+
+
+ {field.isId && } + {isRelation && } + {field.isList && } + {!field.isRequired && !field.isId && ( + + )} +
+ + + {field.name} + +
+ +
+ + {typeStr} + + + {field.isUnique && !field.isId && ( + + UK + + )} + + {field.isOmitted && ( + + omit + + )} +
+
+ ); +} diff --git a/apps/web/src/components/schema-editor.tsx b/apps/web/src/components/schema-editor.tsx new file mode 100644 index 0000000..ba54021 --- /dev/null +++ b/apps/web/src/components/schema-editor.tsx @@ -0,0 +1,174 @@ +'use client'; + +// Monaco-based editor for Prisma schemas +// Uses debouncing to avoid hammering the API on every keystroke + +import { useState, useCallback, useRef } from 'react'; +import Editor from '@monaco-editor/react'; +import type { OnMount } from '@monaco-editor/react'; +import { useDebouncedCallback } from 'use-debounce'; +import type { PrismaSchema } from '@prisma-uml/parser'; + +interface SchemaEditorProps { + initialValue?: string; + onSchemaChange: (schema: PrismaSchema, raw: string) => void; + onError: (error: string | null) => void; +} + +// sample schema to show when the page loads +// includes common patterns: relations, enums, optional fields +const DEFAULT_SCHEMA = `// Paste your Prisma schema here + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" +} + +model User { + id Int @id @default(autoincrement()) + email String @unique + name String? + role Role @default(USER) + posts Post[] + profile Profile? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model Profile { + id Int @id @default(autoincrement()) + bio String? + avatar String? + user User @relation(fields: [userId], references: [id]) + userId Int @unique +} + +model Post { + id Int @id @default(autoincrement()) + title String + content String? + published Boolean @default(false) + author User @relation(fields: [authorId], references: [id]) + authorId Int + tags Tag[] + comments Comment[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model Comment { + id Int @id @default(autoincrement()) + content String + post Post @relation(fields: [postId], references: [id]) + postId Int + createdAt DateTime @default(now()) +} + +model Tag { + id Int @id @default(autoincrement()) + name String @unique + posts Post[] +} + +enum Role { + ADMIN + USER + GUEST +} +`; + +export function SchemaEditor({ + initialValue = DEFAULT_SCHEMA, + onSchemaChange, + onError, +}: SchemaEditorProps) { + const [value, setValue] = useState(initialValue); + const [isLoading, setIsLoading] = useState(false); + const editorRef = useRef(null); + + // sends the schema to our API endpoint for parsing + async function parseSchema(schema: string) { + setIsLoading(true); + + try { + const res = await fetch('/api/parse', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ schema }), + }); + + const result = await res.json(); + + if (!res.ok || !result.success) { + // try to extract a meaningful error message + const msg = result.errors + ? result.errors.map((e: { message?: string }) => e.message ?? String(e)).join(', ') + : (result.message ?? 'Parsing error'); + onError(msg); + return; + } + + onError(null); + onSchemaChange(result.data, schema); + } catch (err) { + // network error or something unexpected + onError('Server connection error'); + } finally { + setIsLoading(false); + } + } + + // 500ms debounce seems like a good tradeoff between responsiveness and API load + const debouncedParse = useDebouncedCallback(parseSchema, 500); + + const handleChange = useCallback( + (newValue: string | undefined) => { + if (newValue === undefined) return; + setValue(newValue); + debouncedParse(newValue); + }, + [debouncedParse] + ); + + // parse the initial schema when editor mounts + const handleEditorMount: OnMount = (editor) => { + editorRef.current = editor; + parseSchema(initialValue); + }; + + return ( +
+ {isLoading && ( +
+
+
+ )} + +
+ ); +} diff --git a/apps/web/src/components/uml-diagram.tsx b/apps/web/src/components/uml-diagram.tsx new file mode 100644 index 0000000..e0e7128 --- /dev/null +++ b/apps/web/src/components/uml-diagram.tsx @@ -0,0 +1,234 @@ +'use client'; + +import { useMemo, useCallback, useEffect, useState } from 'react'; +import { + ReactFlow, + Background, + Controls, + MiniMap, + useNodesState, + useEdgesState, + useReactFlow, + ReactFlowProvider, + MarkerType, + BackgroundVariant, + Panel, +} from '@xyflow/react'; +import type { Node, Edge } from '@xyflow/react'; +import '@xyflow/react/dist/style.css'; +import { ArrowsPointingOutIcon } from '@heroicons/react/24/outline'; + +import { ModelNode } from './nodes/model-node'; +import { EnumNode } from './nodes/enum-node'; +import { useAutoLayout } from '../hooks'; +import type { PrismaSchema } from '@prisma-uml/parser'; + +// custom node types for models and enums +const nodeTypes = { + model: ModelNode, + enum: EnumNode, +} as const; + +interface UMLDiagramProps { + schema: PrismaSchema | null; +} + +// wrapper component to provide ReactFlow context +export function UMLDiagram({ schema }: UMLDiagramProps) { + return ( + + + + ); +} + +// the actual diagram logic lives here +function DiagramCanvas({ schema }: UMLDiagramProps) { + const [mounted, setMounted] = useState(false); + const { fitView } = useReactFlow(); + const { getLayoutedElements } = useAutoLayout(); + + useEffect(() => { + setMounted(true); + }, []); + + const { nodes: initialNodes, edges: initialEdges } = useMemo(() => { + if (!schema) return { nodes: [], edges: [] }; + return schemaToReactFlow(schema); + }, [schema]); + + const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); + const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); + + useEffect(() => { + if (schema) { + const { nodes: newNodes, edges: newEdges } = schemaToReactFlow(schema); + setNodes(newNodes); + setEdges(newEdges); + } + }, [schema, setNodes, setEdges]); + + // auto-layout using dagre - tweaked these values until it looked good + const onAutoLayout = useCallback(() => { + const layoutConfig = { + direction: 'LR' as const, + nodeWidth: 300, + baseNodeHeight: 150, + fieldHeight: 36, + rankSep: 80, // space between columns + nodeSep: 30, // space between nodes in same column + }; + + const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements( + nodes, + edges, + layoutConfig + ); + + setNodes(layoutedNodes); + setEdges(layoutedEdges); + + // small delay before fitting view, otherwise it doesn't work properly + setTimeout(() => fitView({ padding: 0.2, duration: 300 }), 50); + }, [nodes, edges, getLayoutedElements, setNodes, setEdges, fitView]); + + if (!mounted) { + return ( +
+
Loading diagram...
+
+ ); + } + + if (!schema) { + return ( +
+
+

No schema to display

+

Enter a valid Prisma schema in the editor

+
+
+ ); + } + + return ( +
+ + + + + + + + + {schema.models.length} models • {schema.enums.length} enums + + +
+ ); +} + +function schemaToReactFlow(schema: PrismaSchema): { nodes: Node[]; edges: Edge[] } { + const nodes: Node[] = []; + const edges: Edge[] = []; + + const GRID_COLS = Math.min(3, Math.ceil(Math.sqrt(schema.models.length + schema.enums.length))); + const NODE_WIDTH = 300; + const NODE_HEIGHT = 250; + const GAP_X = 120; + const GAP_Y = 100; + + schema.models.forEach((model, index) => { + const col = index % GRID_COLS; + const row = Math.floor(index / GRID_COLS); + + nodes.push({ + id: model.name, + type: 'model', + position: { + x: col * (NODE_WIDTH + GAP_X), + y: row * (NODE_HEIGHT + GAP_Y), + }, + data: { model }, + }); + + model.fields + .filter((field) => field.kind === 'object' && field.relationFromFields?.length) + .forEach((field) => { + const targetModel = field.type.replace('[]', '').replace('?', ''); + + edges.push({ + id: `${model.name}-${field.name}-${targetModel}`, + source: model.name, + target: targetModel, + sourceHandle: 'source', + targetHandle: 'target', + type: 'smoothstep', + animated: false, + markerEnd: { + type: MarkerType.ArrowClosed, + color: '#3b82f6', + width: 20, + height: 20, + }, + style: { + stroke: '#3b82f6', + strokeWidth: 2, + }, + label: field.relationName || undefined, + labelStyle: { + fill: '#a1a1aa', + fontSize: 11, + fontWeight: 500, + }, + labelBgStyle: { + fill: '#18181b', + fillOpacity: 0.9, + }, + labelBgPadding: [4, 4] as [number, number], + labelBgBorderRadius: 4, + }); + }); + }); + + schema.enums.forEach((enumDef, index) => { + const totalModels = schema.models.length; + const enumIndex = totalModels + index; + const col = enumIndex % GRID_COLS; + const row = Math.floor(enumIndex / GRID_COLS); + + nodes.push({ + id: enumDef.name, + type: 'enum', + position: { + x: col * (NODE_WIDTH + GAP_X), + y: row * (NODE_HEIGHT + GAP_Y), + }, + data: { enum: enumDef }, + }); + }); + + return { nodes, edges }; +} diff --git a/apps/web/src/hooks/index.ts b/apps/web/src/hooks/index.ts new file mode 100644 index 0000000..5ae9baa --- /dev/null +++ b/apps/web/src/hooks/index.ts @@ -0,0 +1,2 @@ +export { useExportImage, type ImageFormat } from './use-export-image'; +export { useAutoLayout } from './use-auto-layout'; diff --git a/apps/web/src/hooks/use-auto-layout.ts b/apps/web/src/hooks/use-auto-layout.ts new file mode 100644 index 0000000..c99fcb5 --- /dev/null +++ b/apps/web/src/hooks/use-auto-layout.ts @@ -0,0 +1,95 @@ +'use client'; + +import { useCallback } from 'react'; +import Dagre from '@dagrejs/dagre'; +import type { Node, Edge } from '@xyflow/react'; + +interface LayoutOptions { + direction?: 'TB' | 'LR' | 'BT' | 'RL'; + nodeWidth?: number; + baseNodeHeight?: number; + fieldHeight?: number; + rankSep?: number; + nodeSep?: number; +} + +function calculateNodeHeight(node: Node, baseHeight: number, fieldHeight: number): number { + const data = node.data as { model?: { fields?: unknown[] }; enum?: { values?: unknown[] } }; + + if (data.model?.fields) { + const fieldsCount = data.model.fields.length; + return Math.max(baseHeight, 52 + fieldsCount * 36 + 16); + } + + if (data.enum?.values) { + const valuesCount = data.enum.values.length; + return Math.max(baseHeight, 44 + valuesCount * 32 + 16); + } + + return baseHeight; +} + +export function useAutoLayout() { + const getLayoutedElements = useCallback( + ( + nodes: Node[], + edges: Edge[], + options: LayoutOptions = {} + ): { nodes: Node[]; edges: Edge[] } => { + const { + direction = 'LR', + nodeWidth = 300, + baseNodeHeight = 150, + fieldHeight = 36, + rankSep = 100, + nodeSep = 50, + } = options; + + const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({})); + + g.setGraph({ + rankdir: direction, + ranksep: rankSep, + nodesep: nodeSep, + marginx: 50, + marginy: 50, + }); + + const nodeHeights = new Map(); + + nodes.forEach((node) => { + const height = calculateNodeHeight(node, baseNodeHeight, fieldHeight); + nodeHeights.set(node.id, height); + + g.setNode(node.id, { + width: nodeWidth, + height: height, + }); + }); + + edges.forEach((edge) => { + g.setEdge(edge.source, edge.target); + }); + + Dagre.layout(g); + + const layoutedNodes = nodes.map((node) => { + const nodeWithPosition = g.node(node.id); + const height = nodeHeights.get(node.id) || baseNodeHeight; + + return { + ...node, + position: { + x: nodeWithPosition.x - nodeWidth / 2, + y: nodeWithPosition.y - height / 2, + }, + }; + }); + + return { nodes: layoutedNodes, edges }; + }, + [] + ); + + return { getLayoutedElements }; +} diff --git a/apps/web/src/hooks/use-export-image.ts b/apps/web/src/hooks/use-export-image.ts new file mode 100644 index 0000000..ea500c8 --- /dev/null +++ b/apps/web/src/hooks/use-export-image.ts @@ -0,0 +1,203 @@ +'use client'; + +import { useCallback } from 'react'; +import { toPng, toSvg, toJpeg } from 'html-to-image'; + +export type ImageFormat = 'png' | 'svg' | 'jpeg'; + +interface ExportImageOptions { + format: ImageFormat; + quality?: number; + backgroundColor?: string; + filename?: string; + includeGrid?: boolean; +} + +function drawGridBackground( + ctx: CanvasRenderingContext2D, + width: number, + height: number, + gridSize: number = 50, + backgroundColor: string = '#18181b', + gridColor: string = '#3f3f46' +) { + ctx.fillStyle = backgroundColor; + ctx.fillRect(0, 0, width, height); + + ctx.strokeStyle = gridColor; + ctx.lineWidth = 1; + + for (let x = 0; x <= width; x += gridSize) { + ctx.beginPath(); + ctx.moveTo(x, 0); + ctx.lineTo(x, height); + ctx.stroke(); + } + + for (let y = 0; y <= height; y += gridSize) { + ctx.beginPath(); + ctx.moveTo(0, y); + ctx.lineTo(width, y); + ctx.stroke(); + } +} + +export function useExportImage() { + const downloadImage = useCallback(async (options: ExportImageOptions) => { + const { + format, + quality = 0.92, + backgroundColor = '#18181b', + filename = 'prisma-schema', + includeGrid = true, + } = options; + + const reactFlowContainer = document.querySelector('.react-flow') as HTMLElement; + if (!reactFlowContainer) { + console.error('React Flow container not found'); + return false; + } + + const nodeElements = document.querySelectorAll('.react-flow__node') as NodeListOf; + if (nodeElements.length === 0) { + console.error('No nodes to export'); + return false; + } + + const viewport = document.querySelector('.react-flow__viewport') as HTMLElement; + if (!viewport) { + console.error('React Flow viewport not found'); + return false; + } + + let minX = Infinity; + let minY = Infinity; + let maxX = -Infinity; + let maxY = -Infinity; + + nodeElements.forEach((nodeEl) => { + const transform = nodeEl.style.transform; + const match = transform.match(/translate\((-?[\d.]+)px,\s*(-?[\d.]+)px\)/); + + if (match) { + const x = parseFloat(match[1]); + const y = parseFloat(match[2]); + const width = nodeEl.offsetWidth; + const height = nodeEl.offsetHeight; + + minX = Math.min(minX, x); + minY = Math.min(minY, y); + maxX = Math.max(maxX, x + width); + maxY = Math.max(maxY, y + height); + } + }); + + if (minX === Infinity) { + console.error('Could not calculate node bounds'); + return false; + } + + const padding = 80; + const contentWidth = maxX - minX; + const contentHeight = maxY - minY; + const imageWidth = Math.ceil(contentWidth + padding * 2); + const imageHeight = Math.ceil(contentHeight + padding * 2); + + const originalTransform = viewport.style.transform; + + viewport.style.transform = `translate(${-minX + padding}px, ${-minY + padding}px) scale(1)`; + + await new Promise((resolve) => setTimeout(resolve, 30)); + + const imageOptions = { + backgroundColor: includeGrid ? 'transparent' : backgroundColor, + width: imageWidth, + height: imageHeight, + quality, + pixelRatio: 2, + skipFonts: true, + cacheBust: true, + filter: (node: Element) => { + const classList = (node as HTMLElement).classList; + if (!classList) return true; + + if ( + classList.contains('react-flow__controls') || + classList.contains('react-flow__minimap') || + classList.contains('react-flow__panel') || + classList.contains('react-flow__background') + ) { + return false; + } + return true; + }, + }; + + try { + const diagramDataUrl = await toPng(reactFlowContainer, imageOptions); + + viewport.style.transform = originalTransform; + + if (includeGrid && format !== 'svg') { + const finalCanvas = document.createElement('canvas'); + const pixelRatio = 2; + finalCanvas.width = imageWidth * pixelRatio; + finalCanvas.height = imageHeight * pixelRatio; + const ctx = finalCanvas.getContext('2d')!; + + ctx.scale(pixelRatio, pixelRatio); + + drawGridBackground(ctx, imageWidth, imageHeight, 50, backgroundColor, '#3f3f46'); + + const img = new Image(); + await new Promise((resolve, reject) => { + img.onload = () => resolve(); + img.onerror = reject; + img.src = diagramDataUrl; + }); + + ctx.drawImage(img, 0, 0, imageWidth, imageHeight); + + let finalDataUrl: string; + if (format === 'jpeg') { + finalDataUrl = finalCanvas.toDataURL('image/jpeg', quality); + } else { + finalDataUrl = finalCanvas.toDataURL('image/png'); + } + + const link = document.createElement('a'); + link.download = `${filename}.${format}`; + link.href = finalDataUrl; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } else { + let dataUrl: string; + + if (format === 'svg') { + viewport.style.transform = `translate(${-minX + padding}px, ${-minY + padding}px) scale(1)`; + await new Promise((resolve) => setTimeout(resolve, 30)); + dataUrl = await toSvg(reactFlowContainer, { ...imageOptions, backgroundColor }); + viewport.style.transform = originalTransform; + } else { + dataUrl = diagramDataUrl; + } + + const link = document.createElement('a'); + link.download = `${filename}.${format}`; + link.href = dataUrl; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } + + return true; + } catch (error) { + console.error('Failed to export image:', error); + viewport.style.transform = originalTransform; + return false; + } + }, []); + + return { downloadImage }; +} diff --git a/apps/web/src/lib/index.ts b/apps/web/src/lib/index.ts new file mode 100644 index 0000000..491bf54 --- /dev/null +++ b/apps/web/src/lib/index.ts @@ -0,0 +1,2 @@ +export { parsePrismaSchema, validatePrismaSchema, detectPrismaVersion } from './prisma-parser'; +export type { PrismaSchema } from './prisma-parser'; diff --git a/apps/web/src/lib/prisma-parser.ts b/apps/web/src/lib/prisma-parser.ts new file mode 100644 index 0000000..f13246b --- /dev/null +++ b/apps/web/src/lib/prisma-parser.ts @@ -0,0 +1,17 @@ +import { + parsePrismaSchema as parse, + validatePrismaSchema, + detectPrismaVersion, + type PrismaSchema, + type ParserOptions, +} from '@prisma-uml/parser'; + +export async function parsePrismaSchema( + schemaContent: string, + options?: ParserOptions +): Promise { + return parse(schemaContent, options); +} + +export { validatePrismaSchema, detectPrismaVersion }; +export type { PrismaSchema }; diff --git a/apps/web/src/lib/utils.ts b/apps/web/src/lib/utils.ts new file mode 100644 index 0000000..a6c17be --- /dev/null +++ b/apps/web/src/lib/utils.ts @@ -0,0 +1,42 @@ +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +/** + * Merge Tailwind CSS classes with clsx + */ +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +export function encodeSchema(schema: string): string { + return btoa(encodeURIComponent(schema)); +} + +export function decodeSchema(encoded: string): string { + try { + return decodeURIComponent(atob(encoded)); + } catch { + return ''; + } +} + +export async function copyToClipboard(text: string): Promise { + try { + await navigator.clipboard.writeText(text); + return true; + } catch { + return false; + } +} + +export function downloadFile(content: string, filename: string, mimeType = 'text/plain'): void { + const blob = new Blob([content], { type: mimeType }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); +} diff --git a/apps/web/src/types/css.d.ts b/apps/web/src/types/css.d.ts new file mode 100644 index 0000000..444f793 --- /dev/null +++ b/apps/web/src/types/css.d.ts @@ -0,0 +1,4 @@ +declare module '*.css' { + const content: { [className: string]: string }; + export default content; +} diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..66237b5 --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"], + "@prisma-uml/parser": ["./../../packages/prisma-parser/src"] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": ["node_modules"] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..299d7a8 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "prisma-uml-viewer", + "private": true, + "scripts": { + "dev": "turbo dev", + "build": "turbo build", + "start": "pnpm --filter @prisma-uml/web start", + "lint": "turbo lint", + "test": "turbo test", + "typecheck": "turbo typecheck", + "format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"" + }, + "devDependencies": { + "@types/node": "^22.10.0", + "prettier": "^3.4.0", + "turbo": "^2.4.0", + "typescript": "^5.7.0" + }, + "packageManager": "pnpm@9.15.0", + "engines": { + "node": ">=20.0.0" + } +} diff --git a/packages/prisma-parser/README.md b/packages/prisma-parser/README.md new file mode 100644 index 0000000..1a0bd0f --- /dev/null +++ b/packages/prisma-parser/README.md @@ -0,0 +1,22 @@ +# @prisma-uml/parser + +Internal package for parsing Prisma schemas. + +## Features + +- Parse Prisma schemas (v5, v6, v7) +- Auto-detect Prisma version +- Export to Mermaid, PlantUML, DBML + +## Usage + +```typescript +import { parsePrismaSchema, toMermaid } from '@prisma-uml/parser'; + +const parsed = await parsePrismaSchema(schemaString); +const mermaid = toMermaid(parsed); +``` + +## License + +MIT diff --git a/packages/prisma-parser/package.json b/packages/prisma-parser/package.json new file mode 100644 index 0000000..5a7bce2 --- /dev/null +++ b/packages/prisma-parser/package.json @@ -0,0 +1,51 @@ +{ + "name": "@prisma-uml/parser", + "version": "0.1.0", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + }, + "./renderers/mermaid": { + "types": "./dist/renderers/mermaid.d.ts", + "import": "./dist/renderers/mermaid.mjs", + "require": "./dist/renderers/mermaid.js" + }, + "./renderers/plantuml": { + "types": "./dist/renderers/plantuml.d.ts", + "import": "./dist/renderers/plantuml.mjs", + "require": "./dist/renderers/plantuml.js" + }, + "./renderers/dbml": { + "types": "./dist/renderers/dbml.d.ts", + "import": "./dist/renderers/dbml.mjs", + "require": "./dist/renderers/dbml.js" + } + }, + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "test": "vitest", + "test:run": "vitest run", + "typecheck": "tsc --noEmit" + }, + "dependencies": {}, + "peerDependencies": { + "@prisma/internals": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "@prisma/internals": { + "optional": true + } + }, + "devDependencies": { + "@prisma/internals": "^6.3.0", + "tsup": "^8.4.0", + "typescript": "^5.7.0", + "vitest": "^4.0.0" + } +} diff --git a/packages/prisma-parser/src/__tests__/parser.test.ts b/packages/prisma-parser/src/__tests__/parser.test.ts new file mode 100644 index 0000000..2e4ce39 --- /dev/null +++ b/packages/prisma-parser/src/__tests__/parser.test.ts @@ -0,0 +1,192 @@ +import { describe, it, expect } from 'vitest'; +import { parsePrismaSchema, detectPrismaVersion, validatePrismaSchema } from '../index'; + +describe('Prisma Schema Parser', () => { + it('should parse a basic Prisma 5 schema', async () => { + const schema = ` + datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + } + + generator client { + provider = "prisma-client-js" + } + + model User { + id Int @id @default(autoincrement()) + email String @unique + name String? + } + `; + + const result = await parsePrismaSchema(schema); + + expect(result.version).toBe(5); + expect(result.models).toHaveLength(1); + expect(result.models[0].name).toBe('User'); + expect(result.models[0].fields).toHaveLength(3); + }); + + it('should detect Prisma 6 features', () => { + const schema = ` + datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + } + + model User { + id Int @id + password String @omit + } + `; + + expect(detectPrismaVersion(schema)).toBe(6); + }); + + it('should detect Prisma 7 features', () => { + const schema = ` + datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + } + + generator client { + provider = "prisma-client" + } + + model User { + id Int @id + } + `; + + expect(detectPrismaVersion(schema)).toBe(7); + }); + + it('should handle relations correctly', async () => { + const schema = ` + datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + } + + model User { + id Int @id + posts Post[] + } + + model Post { + id Int @id + author User @relation(fields: [authorId], references: [id]) + authorId Int + } + `; + + const result = await parsePrismaSchema(schema); + + const userModel = result.models.find((m) => m.name === 'User'); + const postModel = result.models.find((m) => m.name === 'Post'); + + expect(userModel?.fields.find((f) => f.name === 'posts')?.kind).toBe('object'); + expect(postModel?.fields.find((f) => f.name === 'author')?.relationFromFields).toEqual([ + 'authorId', + ]); + }); + + it('should parse enums correctly', async () => { + const schema = ` + datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + } + + enum Role { + ADMIN + USER + GUEST + } + + model User { + id Int @id + role Role @default(USER) + } + `; + + const result = await parsePrismaSchema(schema); + + expect(result.enums).toHaveLength(1); + expect(result.enums[0].name).toBe('Role'); + expect(result.enums[0].values).toHaveLength(3); + expect(result.enums[0].values.map((v) => v.name)).toEqual(['ADMIN', 'USER', 'GUEST']); + }); + + it('should validate schema correctly', () => { + const validSchema = ` + datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + } + + model User { + id Int @id + } + `; + + const invalidSchema = ` + model User { + id Int @id + `; + + expect(validatePrismaSchema(validSchema).valid).toBe(true); + expect(validatePrismaSchema(invalidSchema).valid).toBe(false); + }); + + it('should parse composite primary keys', async () => { + const schema = ` + datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + } + + model PostTag { + postId Int + tagId Int + + @@id([postId, tagId]) + } + `; + + const result = await parsePrismaSchema(schema); + const model = result.models[0]; + + expect(model.primaryKey).toBeDefined(); + expect(model.primaryKey?.fields).toEqual(['postId', 'tagId']); + }); + + it('should parse indexes and unique constraints', async () => { + const schema = ` + datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + } + + model User { + id Int @id + email String + firstName String + lastName String + + @@unique([email]) + @@index([firstName, lastName]) + } + `; + + const result = await parsePrismaSchema(schema); + const model = result.models[0]; + + expect(model.uniqueConstraints).toHaveLength(1); + expect(model.uniqueConstraints[0].fields).toEqual(['email']); + expect(model.indexes).toHaveLength(1); + expect(model.indexes[0].fields).toEqual(['firstName', 'lastName']); + }); +}); diff --git a/packages/prisma-parser/src/__tests__/renderers.test.ts b/packages/prisma-parser/src/__tests__/renderers.test.ts new file mode 100644 index 0000000..80c6924 --- /dev/null +++ b/packages/prisma-parser/src/__tests__/renderers.test.ts @@ -0,0 +1,107 @@ +import { describe, it, expect } from 'vitest'; +import { parsePrismaSchema, toMermaid, toPlantUML, toDBML } from '../index'; + +const testSchema = ` + datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + } + + generator client { + provider = "prisma-client-js" + } + + enum Role { + ADMIN + USER + } + + model User { + id Int @id @default(autoincrement()) + email String @unique + name String? + role Role @default(USER) + posts Post[] + createdAt DateTime @default(now()) + } + + model Post { + id Int @id @default(autoincrement()) + title String + content String? + published Boolean @default(false) + author User @relation(fields: [authorId], references: [id]) + authorId Int + } +`; + +describe('Renderers', () => { + describe('Mermaid Renderer', () => { + it('should generate valid Mermaid ER diagram', async () => { + const schema = await parsePrismaSchema(testSchema); + const mermaid = toMermaid(schema); + + expect(mermaid).toContain('erDiagram'); + expect(mermaid).toContain('User {'); + expect(mermaid).toContain('Post {'); + expect(mermaid).toContain('int id "PK"'); + expect(mermaid).toContain('string email "UK"'); + }); + + it('should include relations', async () => { + const schema = await parsePrismaSchema(testSchema); + const mermaid = toMermaid(schema); + + expect(mermaid).toMatch(/User.*Post/); + }); + }); + + describe('PlantUML Renderer', () => { + it('should generate valid PlantUML diagram', async () => { + const schema = await parsePrismaSchema(testSchema); + const plantuml = toPlantUML(schema); + + expect(plantuml).toContain('@startuml'); + expect(plantuml).toContain('@enduml'); + expect(plantuml).toContain('entity'); + expect(plantuml).toContain('User'); + expect(plantuml).toContain('Post'); + }); + + it('should include enums', async () => { + const schema = await parsePrismaSchema(testSchema); + const plantuml = toPlantUML(schema); + + expect(plantuml).toContain('enum Role'); + expect(plantuml).toContain('ADMIN'); + expect(plantuml).toContain('USER'); + }); + }); + + describe('DBML Renderer', () => { + it('should generate valid DBML', async () => { + const schema = await parsePrismaSchema(testSchema); + const dbml = toDBML(schema); + + expect(dbml).toContain('Project prisma_schema'); + expect(dbml).toContain('Table User'); + expect(dbml).toContain('Table Post'); + expect(dbml).toContain('Ref:'); + }); + + it('should include field constraints', async () => { + const schema = await parsePrismaSchema(testSchema); + const dbml = toDBML(schema); + + expect(dbml).toContain('pk'); + expect(dbml).toContain('unique'); + }); + + it('should include enums', async () => { + const schema = await parsePrismaSchema(testSchema); + const dbml = toDBML(schema); + + expect(dbml).toContain('Enum Role'); + }); + }); +}); diff --git a/packages/prisma-parser/src/adapters/index.ts b/packages/prisma-parser/src/adapters/index.ts new file mode 100644 index 0000000..555116e --- /dev/null +++ b/packages/prisma-parser/src/adapters/index.ts @@ -0,0 +1,3 @@ +export { Prisma5Adapter } from './prisma5'; +export { Prisma6Adapter } from './prisma6'; +export { Prisma7Adapter } from './prisma7'; diff --git a/packages/prisma-parser/src/adapters/prisma5.ts b/packages/prisma-parser/src/adapters/prisma5.ts new file mode 100644 index 0000000..81ca9f3 --- /dev/null +++ b/packages/prisma-parser/src/adapters/prisma5.ts @@ -0,0 +1,349 @@ +import type { + PrismaAdapter, + PrismaSchema, + PrismaModel, + PrismaField, + PrismaEnum, + EnumValue, + Datasource, + Generator, + UniqueConstraint, + Index, + CompositePrimaryKey, + DefaultValue, +} from '../types'; + +export class Prisma5Adapter implements PrismaAdapter { + async parse(schemaContent: string): Promise { + const models = this.parseModels(schemaContent); + const enums = this.parseEnums(schemaContent); + const datasource = this.parseDatasource(schemaContent); + const generators = this.parseGenerators(schemaContent); + + return { + version: 5, + models, + enums, + datasource, + generators, + }; + } + + private parseModels(schema: string): PrismaModel[] { + const models: PrismaModel[] = []; + const modelRegex = /(?:\/\/\/\s*(.+?)\n)?model\s+(\w+)\s*\{([^}]+)\}/gs; + + let match; + while ((match = modelRegex.exec(schema)) !== null) { + const documentation = match[1]?.trim(); + const name = match[2]; + const body = match[3]; + + const model: PrismaModel = { + name, + fields: this.parseFields(body), + uniqueConstraints: this.parseUniqueConstraints(body), + indexes: this.parseIndexes(body), + documentation, + }; + + // Parse @@map for dbName + const mapMatch = body.match(/@@map\("([^"]+)"\)/); + if (mapMatch) { + model.dbName = mapMatch[1]; + } + + // Parse @@id for composite primary key + const idMatch = body.match(/@@id\(\[([^\]]+)\]\)/); + if (idMatch) { + model.primaryKey = { + fields: idMatch[1].split(',').map((f) => f.trim()), + }; + } + + models.push(model); + } + + return models; + } + + private parseFields(body: string): PrismaField[] { + const fields: PrismaField[] = []; + const lines = body.split('\n'); + + let currentDoc = ''; + + for (const line of lines) { + const trimmedLine = line.trim(); + + // Capture documentation + if (trimmedLine.startsWith('///')) { + currentDoc = trimmedLine.replace('///', '').trim(); + continue; + } + + // Skip empty lines and block attributes + if (!trimmedLine || trimmedLine.startsWith('@@')) { + currentDoc = ''; + continue; + } + + // Parse field + const fieldMatch = trimmedLine.match(/^(\w+)\s+(\w+)(\[\])?\??(.*)$/); + if (fieldMatch) { + const [, fieldName, fieldType, isList, attributes] = fieldMatch; + + const field: PrismaField = { + name: fieldName, + type: fieldType, + kind: this.getFieldKind(fieldType, attributes), + isList: !!isList, + isRequired: !trimmedLine.includes('?') || trimmedLine.includes('@id'), + isUnique: attributes.includes('@unique'), + isId: attributes.includes('@id'), + isUpdatedAt: attributes.includes('@updatedAt'), + hasDefaultValue: attributes.includes('@default'), + documentation: currentDoc || undefined, + }; + + // Parse @default value + if (field.hasDefaultValue) { + field.default = this.parseDefaultValue(attributes); + } + + // Parse @relation + if (attributes.includes('@relation')) { + this.parseRelation(field, attributes); + } + + fields.push(field); + currentDoc = ''; + } + } + + return fields; + } + + private getFieldKind( + type: string, + attributes: string + ): 'scalar' | 'object' | 'enum' | 'unsupported' { + const scalarTypes = [ + 'String', + 'Int', + 'Float', + 'Boolean', + 'DateTime', + 'Json', + 'Bytes', + 'BigInt', + 'Decimal', + ]; + + if (attributes.includes('@relation')) { + return 'object'; + } + + if (scalarTypes.includes(type)) { + return 'scalar'; + } + + if (attributes.includes('Unsupported')) { + return 'unsupported'; + } + + // If it's not a scalar and not a relation, it's likely an enum + return 'enum'; + } + + private parseDefaultValue(attributes: string): DefaultValue | undefined { + const defaultMatch = attributes.match(/@default\(([^)]+)\)/); + if (!defaultMatch) return undefined; + + const value = defaultMatch[1].trim(); + + // Function call (e.g., autoincrement(), now(), uuid()) + if (value.includes('(')) { + const funcMatch = value.match(/(\w+)\(([^)]*)\)/); + if (funcMatch) { + return { + name: funcMatch[1], + args: funcMatch[2] ? [funcMatch[2]] : [], + }; + } + } + + // Boolean + if (value === 'true') return true; + if (value === 'false') return false; + + // Number + if (/^-?\d+(\.\d+)?$/.test(value)) { + return parseFloat(value); + } + + // String + if (value.startsWith('"') && value.endsWith('"')) { + return value.slice(1, -1); + } + + return value; + } + + private parseRelation(field: PrismaField, attributes: string): void { + // Parse relation name + const nameMatch = attributes.match(/@relation\("([^"]+)"/); + if (nameMatch) { + field.relationName = nameMatch[1]; + } + + // Parse fields + const fieldsMatch = attributes.match(/fields:\s*\[([^\]]+)\]/); + if (fieldsMatch) { + field.relationFromFields = fieldsMatch[1].split(',').map((f) => f.trim()); + } + + // Parse references + const refsMatch = attributes.match(/references:\s*\[([^\]]+)\]/); + if (refsMatch) { + field.relationToFields = refsMatch[1].split(',').map((f) => f.trim()); + } + } + + private parseUniqueConstraints(body: string): UniqueConstraint[] { + const constraints: UniqueConstraint[] = []; + const regex = /@@unique\(\[([^\]]+)\](?:,\s*name:\s*"([^"]+)")?\)/g; + + let match; + while ((match = regex.exec(body)) !== null) { + constraints.push({ + fields: match[1].split(',').map((f) => f.trim()), + name: match[2], + }); + } + + return constraints; + } + + private parseIndexes(body: string): Index[] { + const indexes: Index[] = []; + const regex = /@@index\(\[([^\]]+)\](?:,\s*name:\s*"([^"]+)")?\)/g; + + let match; + while ((match = regex.exec(body)) !== null) { + indexes.push({ + fields: match[1].split(',').map((f) => f.trim()), + name: match[2], + }); + } + + return indexes; + } + + private parseEnums(schema: string): PrismaEnum[] { + const enums: PrismaEnum[] = []; + const enumRegex = /(?:\/\/\/\s*(.+?)\n)?enum\s+(\w+)\s*\{([^}]+)\}/gs; + + let match; + while ((match = enumRegex.exec(schema)) !== null) { + const documentation = match[1]?.trim(); + const name = match[2]; + const body = match[3]; + + const values = this.parseEnumValues(body); + + enums.push({ + name, + values, + documentation, + }); + } + + return enums; + } + + private parseEnumValues(body: string): EnumValue[] { + const values: EnumValue[] = []; + const lines = body.split('\n'); + + let currentDoc = ''; + + for (const line of lines) { + const trimmedLine = line.trim(); + + if (trimmedLine.startsWith('///')) { + currentDoc = trimmedLine.replace('///', '').trim(); + continue; + } + + if (!trimmedLine) { + currentDoc = ''; + continue; + } + + const valueMatch = trimmedLine.match(/^(\w+)(?:\s+@map\("([^"]+)"\))?/); + if (valueMatch) { + values.push({ + name: valueMatch[1], + dbName: valueMatch[2], + documentation: currentDoc || undefined, + }); + currentDoc = ''; + } + } + + return values; + } + + private parseDatasource(schema: string): Datasource { + const dsMatch = schema.match(/datasource\s+(\w+)\s*\{([^}]+)\}/s); + + if (!dsMatch) { + return { + name: 'db', + provider: 'postgresql', + url: 'env("DATABASE_URL")', + }; + } + + const name = dsMatch[1]; + const body = dsMatch[2]; + + const providerMatch = body.match(/provider\s*=\s*"([^"]+)"/); + const urlMatch = body.match(/url\s*=\s*(?:env\("([^"]+)"\)|"([^"]+)")/); + const directUrlMatch = body.match(/directUrl\s*=\s*(?:env\("([^"]+)"\)|"([^"]+)")/); + + return { + name, + provider: providerMatch?.[1] || 'postgresql', + url: urlMatch?.[1] ? `env("${urlMatch[1]}")` : urlMatch?.[2] || '', + directUrl: directUrlMatch?.[1] ? `env("${directUrlMatch[1]}")` : directUrlMatch?.[2], + }; + } + + private parseGenerators(schema: string): Generator[] { + const generators: Generator[] = []; + const genRegex = /generator\s+(\w+)\s*\{([^}]+)\}/gs; + + let match; + while ((match = genRegex.exec(schema)) !== null) { + const name = match[1]; + const body = match[2]; + + const providerMatch = body.match(/provider\s*=\s*"([^"]+)"/); + const outputMatch = body.match(/output\s*=\s*"([^"]+)"/); + const previewMatch = body.match(/previewFeatures\s*=\s*\[([^\]]+)\]/); + const binaryMatch = body.match(/binaryTargets\s*=\s*\[([^\]]+)\]/); + + generators.push({ + name, + provider: providerMatch?.[1] || 'prisma-client-js', + output: outputMatch?.[1], + previewFeatures: previewMatch?.[1]?.split(',').map((f) => f.trim().replace(/"/g, '')), + binaryTargets: binaryMatch?.[1]?.split(',').map((t) => t.trim().replace(/"/g, '')), + }); + } + + return generators; + } +} diff --git a/packages/prisma-parser/src/adapters/prisma6.ts b/packages/prisma-parser/src/adapters/prisma6.ts new file mode 100644 index 0000000..3842830 --- /dev/null +++ b/packages/prisma-parser/src/adapters/prisma6.ts @@ -0,0 +1,25 @@ +import type { PrismaAdapter, PrismaSchema } from '../types'; +import { Prisma5Adapter } from './prisma5'; + +export class Prisma6Adapter extends Prisma5Adapter implements PrismaAdapter { + async parse(schemaContent: string): Promise { + const baseSchema = await super.parse(schemaContent); + + baseSchema.version = 6; + + this.parsePrisma6Features(baseSchema, schemaContent); + + return baseSchema; + } + + private parsePrisma6Features(schema: PrismaSchema, schemaContent: string): void { + for (const model of schema.models) { + for (const field of model.fields) { + const fieldPattern = new RegExp(`${field.name}\\s+${field.type}[^\\n]*@omit`, 'g'); + if (fieldPattern.test(schemaContent)) { + field.isOmitted = true; + } + } + } + } +} diff --git a/packages/prisma-parser/src/adapters/prisma7.ts b/packages/prisma-parser/src/adapters/prisma7.ts new file mode 100644 index 0000000..8572016 --- /dev/null +++ b/packages/prisma-parser/src/adapters/prisma7.ts @@ -0,0 +1,44 @@ +import type { PrismaAdapter, PrismaSchema } from '../types'; +import { Prisma6Adapter } from './prisma6'; + +export class Prisma7Adapter extends Prisma6Adapter implements PrismaAdapter { + async parse(schemaContent: string): Promise { + const baseSchema = await super.parse(schemaContent); + + baseSchema.version = 7; + + this.parsePrisma7Features(baseSchema, schemaContent); + + return baseSchema; + } + + private parsePrisma7Features(schema: PrismaSchema, schemaContent: string): void { + for (const model of schema.models) { + for (const field of model.fields) { + const fieldLine = this.findFieldLine(schemaContent, model.name, field.name); + if (fieldLine) { + const nativeTypeMatch = fieldLine.match(/@db\.(\w+)(?:\(([^)]*)\))?/); + if (nativeTypeMatch) { + field.nativeType = nativeTypeMatch[1]; + } + } + } + } + } + + private findFieldLine(schema: string, modelName: string, fieldName: string): string | null { + const modelRegex = new RegExp(`model\\s+${modelName}\\s*\\{([^}]+)\\}`, 's'); + const modelMatch = schema.match(modelRegex); + + if (!modelMatch) return null; + + const lines = modelMatch[1].split('\n'); + for (const line of lines) { + if (line.trim().startsWith(fieldName)) { + return line; + } + } + + return null; + } +} diff --git a/packages/prisma-parser/src/index.ts b/packages/prisma-parser/src/index.ts new file mode 100644 index 0000000..816bdab --- /dev/null +++ b/packages/prisma-parser/src/index.ts @@ -0,0 +1,29 @@ +// Main exports +export { parsePrismaSchema, validatePrismaSchema } from './parser'; +export { detectPrismaVersion, getVersionFeatures } from './version-detector'; + +// Adapters +export { Prisma5Adapter, Prisma6Adapter, Prisma7Adapter } from './adapters'; + +// Renderers +export { toMermaid } from './renderers/mermaid'; +export { toPlantUML } from './renderers/plantuml'; +export { toDBML } from './renderers/dbml'; + +// Types +export type { + PrismaSchema, + PrismaModel, + PrismaField, + PrismaEnum, + EnumValue, + Datasource, + Generator, + PrismaVersion, + ParserOptions, + PrismaAdapter, + DefaultValue, + CompositePrimaryKey, + UniqueConstraint, + Index, +} from './types'; diff --git a/packages/prisma-parser/src/parser.ts b/packages/prisma-parser/src/parser.ts new file mode 100644 index 0000000..5839854 --- /dev/null +++ b/packages/prisma-parser/src/parser.ts @@ -0,0 +1,60 @@ +/** + * Main parser module + * Detects Prisma version and delegates to the appropriate adapter + */ + +import type { PrismaSchema, ParserOptions, PrismaVersion } from './types'; +import { detectPrismaVersion } from './version-detector'; +import { Prisma5Adapter, Prisma6Adapter, Prisma7Adapter } from './adapters'; + +// each Prisma version has slightly different features +const adapters = { + 5: Prisma5Adapter, + 6: Prisma6Adapter, + 7: Prisma7Adapter, +} as const; + +/** + * Parse a Prisma schema string into a structured format + */ +export async function parsePrismaSchema( + schemaContent: string, + options?: ParserOptions +): Promise { + // auto-detect version if not specified + const version: PrismaVersion = options?.version ?? detectPrismaVersion(schemaContent); + + const AdapterClass = adapters[version]; + const adapter = new AdapterClass(); + + return adapter.parse(schemaContent); +} + +/** + * Basic validation - checks for common issues before parsing + */ +export function validatePrismaSchema(schemaContent: string): { + valid: boolean; + errors: string[]; +} { + const errors: string[] = []; + + // must have a datasource + if (!schemaContent.includes('datasource')) { + errors.push('Missing datasource block'); + } + + // should have at least one model + if (!schemaContent.includes('model ')) { + errors.push('No models found in schema'); + } + + // check for balanced braces (common mistake) + const openBraces = (schemaContent.match(/{/g) || []).length; + const closeBraces = (schemaContent.match(/}/g) || []).length; + if (openBraces !== closeBraces) { + errors.push('Mismatched braces in schema'); + } + + return { valid: errors.length === 0, errors }; +} diff --git a/packages/prisma-parser/src/renderers/dbml.ts b/packages/prisma-parser/src/renderers/dbml.ts new file mode 100644 index 0000000..15b9b75 --- /dev/null +++ b/packages/prisma-parser/src/renderers/dbml.ts @@ -0,0 +1,192 @@ +import type { PrismaSchema, PrismaField } from '../types'; + +export function toDBML(schema: PrismaSchema): string { + const lines: string[] = []; + + lines.push('// Generated from Prisma Schema'); + lines.push(`// Prisma Version: ${schema.version}`); + lines.push(''); + + lines.push('Project prisma_schema {'); + lines.push(` database_type: '${mapProviderToDBType(schema.datasource.provider)}'`); + lines.push('}'); + lines.push(''); + + for (const enumDef of schema.enums) { + if (enumDef.documentation) { + lines.push(`// ${enumDef.documentation}`); + } + lines.push(`Enum ${enumDef.name} {`); + for (const value of enumDef.values) { + const note = value.documentation ? ` [note: '${value.documentation}']` : ''; + lines.push(` ${value.name}${note}`); + } + lines.push('}'); + lines.push(''); + } + + for (const model of schema.models) { + if (model.documentation) { + lines.push(`// ${model.documentation}`); + } + + const tableName = model.dbName || model.name; + lines.push(`Table ${tableName} {`); + + for (const field of model.fields) { + // Skip relation fields (object kind) + if (field.kind === 'object') continue; + + const dbmlField = fieldToDBML(field); + lines.push(` ${dbmlField}`); + } + + // Add indexes + if (model.indexes.length > 0 || model.uniqueConstraints.length > 0) { + lines.push(''); + lines.push(' indexes {'); + + for (const idx of model.indexes) { + const idxName = idx.name ? ` [name: '${idx.name}']` : ''; + lines.push(` (${idx.fields.join(', ')})${idxName}`); + } + + for (const unique of model.uniqueConstraints) { + const uniqueName = unique.name ? ` [name: '${unique.name}']` : ''; + lines.push(` (${unique.fields.join(', ')}) [unique]${uniqueName}`); + } + + lines.push(' }'); + } + + lines.push('}'); + lines.push(''); + } + + for (const model of schema.models) { + for (const field of model.fields) { + if (field.kind === 'object' && field.relationFromFields?.length) { + const targetModel = field.type.replace('[]', ''); + const sourceFields = field.relationFromFields.join(', '); + const targetFields = field.relationToFields?.join(', ') || 'id'; + + const relationType = field.isList ? '<>' : field.isRequired ? '-' : '-'; + const sourceName = model.dbName || model.name; + const targetName = schema.models.find((m) => m.name === targetModel)?.dbName || targetModel; + + lines.push( + `Ref: ${sourceName}.${sourceFields} ${relationType} ${targetName}.${targetFields}` + ); + } + } + } + + return lines.join('\n'); +} + +function fieldToDBML(field: PrismaField): string { + const parts: string[] = []; + + parts.push(field.name); + + const dbmlType = mapPrismaTypeToDBML(field.type, field.isList); + parts.push(dbmlType); + + const settings: string[] = []; + + if (field.isId) { + settings.push('pk'); + } + + if (field.isUnique && !field.isId) { + settings.push('unique'); + } + + if (!field.isRequired && !field.isId) { + settings.push('null'); + } else if (!field.isId) { + settings.push('not null'); + } + + if (field.hasDefaultValue && field.default !== undefined) { + const defaultStr = formatDefault(field.default); + if (defaultStr) { + settings.push(`default: ${defaultStr}`); + } + } + + if (field.isUpdatedAt) { + settings.push("note: 'Updated automatically'"); + } + + if (field.documentation) { + settings.push(`note: '${field.documentation}'`); + } + + if (settings.length > 0) { + parts.push(`[${settings.join(', ')}]`); + } + + return parts.join(' '); +} + +function mapPrismaTypeToDBML(type: string, isList: boolean): string { + const mapping: Record = { + String: 'varchar', + Int: 'integer', + BigInt: 'bigint', + Float: 'float', + Decimal: 'decimal', + Boolean: 'boolean', + DateTime: 'timestamp', + Json: 'json', + Bytes: 'bytea', + }; + + const mappedType = mapping[type] || type.toLowerCase(); + return isList ? `${mappedType}[]` : mappedType; +} + +function mapProviderToDBType(provider: string): string { + const mapping: Record = { + postgresql: 'PostgreSQL', + mysql: 'MySQL', + sqlite: 'SQLite', + sqlserver: 'SQL Server', + mongodb: 'MongoDB', + cockroachdb: 'CockroachDB', + }; + return mapping[provider] || provider; +} + +function formatDefault(value: unknown): string | null { + if (typeof value === 'object' && value !== null && 'name' in value) { + const func = value as { name: string; args: unknown[] }; + switch (func.name) { + case 'autoincrement': + return null; // Handled by pk + case 'now': + return '`now()`'; + case 'uuid': + return '`uuid()`'; + case 'cuid': + return '`cuid()`'; + default: + return `\`${func.name}()\``; + } + } + + if (typeof value === 'string') { + return `'${value}'`; + } + + if (typeof value === 'boolean') { + return value ? 'true' : 'false'; + } + + if (typeof value === 'number') { + return String(value); + } + + return null; +} diff --git a/packages/prisma-parser/src/renderers/index.ts b/packages/prisma-parser/src/renderers/index.ts new file mode 100644 index 0000000..d81944d --- /dev/null +++ b/packages/prisma-parser/src/renderers/index.ts @@ -0,0 +1,3 @@ +export { toMermaid } from './mermaid'; +export { toPlantUML } from './plantuml'; +export { toDBML } from './dbml'; diff --git a/packages/prisma-parser/src/renderers/mermaid.ts b/packages/prisma-parser/src/renderers/mermaid.ts new file mode 100644 index 0000000..868d3cd --- /dev/null +++ b/packages/prisma-parser/src/renderers/mermaid.ts @@ -0,0 +1,106 @@ +import type { PrismaSchema, PrismaField } from '../types'; + +export function toMermaid(schema: PrismaSchema): string { + const lines: string[] = ['erDiagram']; + + for (const model of schema.models) { + lines.push(` ${model.name} {`); + + for (const field of model.fields) { + if (field.kind === 'scalar' || field.kind === 'enum') { + const type = mapPrismaTypeToMermaid(field.type); + const constraints: string[] = []; + + if (field.isId) constraints.push('PK'); + if (field.isUnique) constraints.push('UK'); + if (field.relationFromFields?.length) constraints.push('FK'); + + const constraintStr = constraints.length ? ` "${constraints.join(', ')}"` : ''; + lines.push(` ${type} ${field.name}${constraintStr}`); + } + } + + lines.push(' }'); + } + + lines.push(''); + + for (const enumDef of schema.enums) { + lines.push(` ${enumDef.name} {`); + for (const value of enumDef.values) { + lines.push(` string ${value.name}`); + } + lines.push(' }'); + } + + lines.push(''); + + const processedRelations = new Set(); + + for (const model of schema.models) { + for (const field of model.fields) { + if (field.kind === 'object') { + const targetModel = field.type.replace('[]', ''); + const relationKey = [model.name, targetModel].sort().join('-'); + + if (processedRelations.has(relationKey)) continue; + processedRelations.add(relationKey); + + const cardinality = getCardinality(field, schema, model.name, targetModel); + const relationLabel = field.relationName || field.name; + + lines.push(` ${model.name} ${cardinality} ${targetModel} : "${relationLabel}"`); + } + } + } + + return lines.join('\n'); +} + +function getCardinality( + field: PrismaField, + schema: PrismaSchema, + sourceModel: string, + targetModel: string +): string { + const target = schema.models.find((m) => m.name === targetModel); + const inverseField = target?.fields.find( + (f) => f.kind === 'object' && f.type.replace('[]', '') === sourceModel + ); + + const sourceIsList = field.isList; + const targetIsList = inverseField?.isList ?? false; + + // One-to-Many + if (!sourceIsList && targetIsList) { + return '||--o{'; + } + + // Many-to-One + if (sourceIsList && !targetIsList) { + return '}o--||'; + } + + // Many-to-Many + if (sourceIsList && targetIsList) { + return '}o--o{'; + } + + // One-to-One + return '||--||'; +} + +function mapPrismaTypeToMermaid(type: string): string { + const mapping: Record = { + String: 'string', + Int: 'int', + BigInt: 'bigint', + Float: 'float', + Decimal: 'decimal', + Boolean: 'boolean', + DateTime: 'datetime', + Json: 'json', + Bytes: 'bytes', + }; + return mapping[type] || type.toLowerCase(); +} diff --git a/packages/prisma-parser/src/renderers/plantuml.ts b/packages/prisma-parser/src/renderers/plantuml.ts new file mode 100644 index 0000000..2f15078 --- /dev/null +++ b/packages/prisma-parser/src/renderers/plantuml.ts @@ -0,0 +1,95 @@ +import type { PrismaSchema } from '../types'; + +export function toPlantUML(schema: PrismaSchema): string { + const lines: string[] = [ + '@startuml', + '!theme blueprint', + 'skinparam linetype ortho', + 'skinparam class {', + ' BackgroundColor #1e1e2e', + ' BorderColor #89b4fa', + ' ArrowColor #89b4fa', + ' FontColor #cdd6f4', + '}', + '', + ]; + + for (const model of schema.models) { + const displayName = model.dbName ? `"${model.name}\\n(${model.dbName})"` : `"${model.name}"`; + lines.push(`entity ${displayName} as ${model.name} {`); + + const pkFields = model.fields.filter((f) => f.isId); + const fkFields = model.fields.filter( + (f) => f.relationFromFields?.length && !f.isId && f.kind !== 'object' + ); + const regularFields = model.fields.filter( + (f) => !f.isId && f.kind !== 'object' && !f.relationFromFields?.length + ); + + // Primary keys + for (const field of pkFields) { + const nullable = field.isRequired ? '' : ' (nullable)'; + lines.push(` * ${field.name} : ${field.type}${nullable} <>`); + } + + if (pkFields.length && (fkFields.length || regularFields.length)) { + lines.push(' --'); + } + + // Foreign keys + for (const field of fkFields) { + const optional = field.isRequired ? '*' : 'o'; + const unique = field.isUnique ? ' <>' : ''; + lines.push(` ${optional} ${field.name} : ${field.type}${unique} <>`); + } + + if (fkFields.length && regularFields.length) { + lines.push(' ..'); + } + + // Regular fields + for (const field of regularFields) { + const optional = field.isRequired ? '*' : 'o'; + const unique = field.isUnique ? ' <>' : ''; + const updatedAt = field.isUpdatedAt ? ' <>' : ''; + lines.push(` ${optional} ${field.name} : ${field.type}${unique}${updatedAt}`); + } + + lines.push('}'); + lines.push(''); + } + + for (const enumDef of schema.enums) { + lines.push(`enum ${enumDef.name} {`); + for (const value of enumDef.values) { + const dbName = value.dbName ? ` [${value.dbName}]` : ''; + lines.push(` ${value.name}${dbName}`); + } + lines.push('}'); + lines.push(''); + } + + const processedRelations = new Set(); + + for (const model of schema.models) { + for (const field of model.fields) { + if (field.kind === 'object' && field.relationFromFields?.length) { + const target = field.type.replace('[]', ''); + const relationKey = `${model.name}-${target}-${field.relationName || field.name}`; + + if (processedRelations.has(relationKey)) continue; + processedRelations.add(relationKey); + + const sourceCard = field.isRequired ? '1' : '0..1'; + const targetCard = field.isList ? '*' : '1'; + + lines.push(`${model.name} "${sourceCard}" --> "${targetCard}" ${target} : ${field.name}`); + } + } + } + + lines.push(''); + lines.push('@enduml'); + + return lines.join('\n'); +} diff --git a/packages/prisma-parser/src/types.ts b/packages/prisma-parser/src/types.ts new file mode 100644 index 0000000..ec4880e --- /dev/null +++ b/packages/prisma-parser/src/types.ts @@ -0,0 +1,100 @@ +export type PrismaVersion = 5 | 6 | 7; + +export type ScalarDefaultValue = string | number | boolean; + +export interface FunctionDefaultValue { + name: string; + args: unknown[]; +} + +export type DefaultValue = ScalarDefaultValue | FunctionDefaultValue; + +export interface PrismaField { + name: string; + type: string; + kind: 'scalar' | 'object' | 'enum' | 'unsupported'; + isList: boolean; + isRequired: boolean; + isUnique: boolean; + isId: boolean; + isUpdatedAt: boolean; + hasDefaultValue: boolean; + default?: DefaultValue; + relationName?: string; + relationFromFields?: string[]; + relationToFields?: string[]; + documentation?: string; + isOmitted?: boolean; + nativeType?: string; +} + +export interface CompositePrimaryKey { + name?: string; + fields: string[]; +} + +export interface UniqueConstraint { + name?: string; + fields: string[]; +} + +export interface Index { + name?: string; + fields: string[]; + type?: 'btree' | 'hash' | 'gist' | 'gin' | 'spgist' | 'brin'; +} + +export interface PrismaModel { + name: string; + dbName?: string; + fields: PrismaField[]; + primaryKey?: CompositePrimaryKey; + uniqueConstraints: UniqueConstraint[]; + indexes: Index[]; + documentation?: string; +} + +export interface EnumValue { + name: string; + dbName?: string; + documentation?: string; +} + +export interface PrismaEnum { + name: string; + values: EnumValue[]; + documentation?: string; +} + +export interface Datasource { + name: string; + provider: string; + url: string; + directUrl?: string; + shadowDatabaseUrl?: string; +} + +export interface Generator { + name: string; + provider: string; + output?: string; + previewFeatures?: string[]; + binaryTargets?: string[]; +} + +export interface PrismaSchema { + version: PrismaVersion; + models: PrismaModel[]; + enums: PrismaEnum[]; + datasource: Datasource; + generators: Generator[]; +} + +export interface ParserOptions { + version?: PrismaVersion; + strict?: boolean; +} + +export interface PrismaAdapter { + parse(schemaContent: string): Promise; +} diff --git a/packages/prisma-parser/src/version-detector.ts b/packages/prisma-parser/src/version-detector.ts new file mode 100644 index 0000000..b8f66eb --- /dev/null +++ b/packages/prisma-parser/src/version-detector.ts @@ -0,0 +1,40 @@ +import type { PrismaVersion } from './types'; + +export function detectPrismaVersion(schema: string): PrismaVersion { + if ( + schema.includes('typedSql') || + schema.includes('@prisma/client/sql') || + /generator\s+\w+\s*\{[^}]*provider\s*=\s*"prisma-client"/s.test(schema) + ) { + return 7; + } + + if ( + schema.includes('@omit') || + schema.includes('strictUndefinedChecks') || + schema.includes('prismaSchemaFolder') || + schema.includes('omitApi') + ) { + return 6; + } + + return 5; +} + +export function getVersionFeatures(version: PrismaVersion): string[] { + const features: Record = { + 5: ['relations', 'enums', 'compositeTypes', 'views'], + 6: ['relations', 'enums', 'compositeTypes', 'views', 'omit', 'strictUndefinedChecks'], + 7: [ + 'relations', + 'enums', + 'compositeTypes', + 'views', + 'omit', + 'strictUndefinedChecks', + 'typedSql', + ], + }; + + return features[version]; +} diff --git a/packages/prisma-parser/tsconfig.json b/packages/prisma-parser/tsconfig.json new file mode 100644 index 0000000..484fb19 --- /dev/null +++ b/packages/prisma-parser/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.test.ts"] +} diff --git a/packages/prisma-parser/tsup.config.ts b/packages/prisma-parser/tsup.config.ts new file mode 100644 index 0000000..51b3afe --- /dev/null +++ b/packages/prisma-parser/tsup.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: { + index: 'src/index.ts', + 'renderers/mermaid': 'src/renderers/mermaid.ts', + 'renderers/plantuml': 'src/renderers/plantuml.ts', + 'renderers/dbml': 'src/renderers/dbml.ts', + }, + format: ['cjs', 'esm'], + dts: true, + sourcemap: true, + clean: true, + splitting: false, + treeshake: true, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..99a4eef --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2903 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@types/node': + specifier: ^22.10.0 + version: 22.19.7 + prettier: + specifier: ^3.4.0 + version: 3.8.0 + turbo: + specifier: ^2.4.0 + version: 2.7.5 + typescript: + specifier: ^5.7.0 + version: 5.9.3 + + apps/web: + dependencies: + '@dagrejs/dagre': + specifier: ^1.1.8 + version: 1.1.8 + '@heroicons/react': + specifier: ^2.2.0 + version: 2.2.0(react@19.2.3) + '@icons-pack/react-simple-icons': + specifier: ^13.8.0 + version: 13.8.0(react@19.2.3) + '@monaco-editor/react': + specifier: ^4.7.0 + version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@prisma-uml/parser': + specifier: workspace:* + version: link:../../packages/prisma-parser + '@xyflow/react': + specifier: ^12.4.0 + version: 12.10.0(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + clsx: + specifier: ^2.1.0 + version: 2.1.1 + html-to-image: + specifier: ^1.11.0 + version: 1.11.13 + next: + specifier: ^16.1.0 + version: 16.1.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react: + specifier: ^19.1.0 + version: 19.2.3 + react-dom: + specifier: ^19.1.0 + version: 19.2.3(react@19.2.3) + tailwind-merge: + specifier: ^2.6.0 + version: 2.6.0 + use-debounce: + specifier: ^10.0.4 + version: 10.1.0(react@19.2.3) + zod: + specifier: ^3.24.0 + version: 3.25.76 + devDependencies: + '@tailwindcss/postcss': + specifier: ^4.1.18 + version: 4.1.18 + '@types/dagre': + specifier: ^0.7.53 + version: 0.7.53 + '@types/react': + specifier: ^19.1.0 + version: 19.2.8 + '@types/react-dom': + specifier: ^19.1.0 + version: 19.2.3(@types/react@19.2.8) + autoprefixer: + specifier: ^10.4.20 + version: 10.4.23(postcss@8.5.6) + postcss: + specifier: ^8.5.0 + version: 8.5.6 + tailwindcss: + specifier: ^4.1.18 + version: 4.1.18 + typescript: + specifier: ^5.7.0 + version: 5.9.3 + + packages/prisma-parser: + devDependencies: + '@prisma/internals': + specifier: ^6.3.0 + version: 6.19.2(typescript@5.9.3) + tsup: + specifier: ^8.4.0 + version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.3) + typescript: + specifier: ^5.7.0 + version: 5.9.3 + vitest: + specifier: ^4.0.0 + version: 4.0.17(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.30.2) + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@dagrejs/dagre@1.1.8': + resolution: {integrity: sha512-5SEDlndt4W/LaVzPYJW+bSmSEZc9EzTf8rJ20WCKvjS5EAZAN0b+x0Yww7VMT4R3Wootkg+X9bUfUxazYw6Blw==} + + '@dagrejs/graphlib@2.2.4': + resolution: {integrity: sha512-mepCf/e9+SKYy1d02/UkvSy6+6MoyXhVxP8lLDfA7BPE1X1d4dR0sZznmbM8/XVJ1GPM+Svnx7Xj6ZweByWUkw==} + engines: {node: '>17.0.0'} + + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@heroicons/react@2.2.0': + resolution: {integrity: sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==} + peerDependencies: + react: '>= 16 || ^19.0.0-rc' + + '@icons-pack/react-simple-icons@13.8.0': + resolution: {integrity: sha512-iZrhL1fSklfCCVn68IYHaAoKfcby3RakUTn2tRPyHBkhr2tkYqeQbjJWf+NizIYBzKBn2IarDJXmTdXd6CuEfw==} + peerDependencies: + react: ^16.13 || ^17 || ^18 || ^19 + + '@img/colour@1.0.0': + resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@monaco-editor/loader@1.7.0': + resolution: {integrity: sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==} + + '@monaco-editor/react@4.7.0': + resolution: {integrity: sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==} + peerDependencies: + monaco-editor: '>= 0.25.0 < 1' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@next/env@16.1.3': + resolution: {integrity: sha512-BLP14oBOvZWXgfdJf9ao+VD8O30uE+x7PaV++QtACLX329WcRSJRO5YJ+Bcvu0Q+c/lei41TjSiFf6pXqnpbQA==} + + '@next/swc-darwin-arm64@16.1.3': + resolution: {integrity: sha512-CpOD3lmig6VflihVoGxiR/l5Jkjfi4uLaOR4ziriMv0YMDoF6cclI+p5t2nstM8TmaFiY6PCTBgRWB57/+LiBA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@next/swc-darwin-x64@16.1.3': + resolution: {integrity: sha512-aF4us2JXh0zn3hNxvL1Bx3BOuh8Lcw3p3Xnurlvca/iptrDH1BrpObwkw9WZra7L7/0qB9kjlREq3hN/4x4x+Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@next/swc-linux-arm64-gnu@16.1.3': + resolution: {integrity: sha512-8VRkcpcfBtYvhGgXAF7U3MBx6+G1lACM1XCo1JyaUr4KmAkTNP8Dv2wdMq7BI+jqRBw3zQE7c57+lmp7jCFfKA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-arm64-musl@16.1.3': + resolution: {integrity: sha512-UbFx69E2UP7MhzogJRMFvV9KdEn4sLGPicClwgqnLht2TEi204B71HuVfps3ymGAh0c44QRAF+ZmvZZhLLmhNg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-x64-gnu@16.1.3': + resolution: {integrity: sha512-SzGTfTjR5e9T+sZh5zXqG/oeRQufExxBF6MssXS7HPeZFE98JDhCRZXpSyCfWrWrYrzmnw/RVhlP2AxQm+wkRQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-linux-x64-musl@16.1.3': + resolution: {integrity: sha512-HlrDpj0v+JBIvQex1mXHq93Mht5qQmfyci+ZNwGClnAQldSfxI6h0Vupte1dSR4ueNv4q7qp5kTnmLOBIQnGow==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-win32-arm64-msvc@16.1.3': + resolution: {integrity: sha512-3gFCp83/LSduZMSIa+lBREP7+5e7FxpdBoc9QrCdmp+dapmTK9I+SLpY60Z39GDmTXSZA4huGg9WwmYbr6+WRw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-x64-msvc@16.1.3': + resolution: {integrity: sha512-1SZVfFT8zmMB+Oblrh5OKDvUo5mYQOkX2We6VGzpg7JUVZlqe4DYOFGKYZKTweSx1gbMixyO1jnFT4thU+nNHQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@prisma/config@6.19.2': + resolution: {integrity: sha512-kadBGDl+aUswv/zZMk9Mx0C8UZs1kjao8H9/JpI4Wh4SHZaM7zkTwiKn/iFLfRg+XtOAo/Z/c6pAYhijKl0nzQ==} + + '@prisma/debug@6.19.2': + resolution: {integrity: sha512-lFnEZsLdFLmEVCVNdskLDCL8Uup41GDfU0LUfquw+ercJC8ODTuL0WNKgOKmYxCJVvFwf0OuZBzW99DuWmoH2A==} + + '@prisma/dmmf@6.19.2': + resolution: {integrity: sha512-ZHGDQlnrWKwFPAOvJ8hVbmIqNPdE0XXptTeYqmUGhYMDySSts60O8g3KNY1uAqr3nHiyDcpaGIpO1MBYoDW6gQ==} + + '@prisma/driver-adapter-utils@6.19.2': + resolution: {integrity: sha512-tkHsL3jhx81eXg2oqtJH/1IEs8uEeUb1RpqHtwYqdNb176u9D0mnHRZM1/cKca/XhLpq49Nnd9XDxdMfWcKAYA==} + + '@prisma/engines-version@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7': + resolution: {integrity: sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA==} + + '@prisma/engines@6.19.2': + resolution: {integrity: sha512-TTkJ8r+uk/uqczX40wb+ODG0E0icVsMgwCTyTHXehaEfb0uo80M9g1aW1tEJrxmFHeOZFXdI2sTA1j1AgcHi4A==} + + '@prisma/fetch-engine@6.19.2': + resolution: {integrity: sha512-h4Ff4Pho+SR1S8XerMCC12X//oY2bG3Iug/fUnudfcXEUnIeRiBdXHFdGlGOgQ3HqKgosTEhkZMvGM9tWtYC+Q==} + + '@prisma/generator-helper@6.19.2': + resolution: {integrity: sha512-vSjDbHhtKjRDgywTDYyAJLEe+kMxq4DNLNiurk/tGca8W/H7duX4Z4UdvYXcv3kWmmgJJmdndh41dgIsrewa3w==} + + '@prisma/generator@6.19.2': + resolution: {integrity: sha512-2xDQQUJh2s6Ty9I9CIGRz7Z8pEGxKxjU/lkFyLI5f83fDoISUaGhWQZGW35xMqKVDoNRM93w1ghm+1ScRy0mAA==} + + '@prisma/get-platform@6.19.2': + resolution: {integrity: sha512-PGLr06JUSTqIvztJtAzIxOwtWKtJm5WwOG6xpsgD37Rc84FpfUBGLKz65YpJBGtkRQGXTYEFie7pYALocC3MtA==} + + '@prisma/internals@6.19.2': + resolution: {integrity: sha512-ciXNy15WKIg4rjuA0dNZ03CMJEneoEi47a3E6Yp59KI90M7r/nieaiqSRtWakl9nKj2YKfRX6nraYC3zaeSQXQ==} + peerDependencies: + typescript: '>=5.1.0' + peerDependenciesMeta: + typescript: + optional: true + + '@prisma/prisma-schema-wasm@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7': + resolution: {integrity: sha512-/5JrsJQAOIWSl+9WMM/0ugd737kT9zdMNr7EFaP7ogeMxAhxQ78uaOV6JYGEocD8LC0gZx7MXmVE/CTfYdJ2iA==} + + '@prisma/schema-engine-wasm@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7': + resolution: {integrity: sha512-mXtzeePkSdqpr/HAIclqzQEf+tBzLawy7NQ9AaRCfg8N/cruavpsocgbSwOOfV7JTk9JubyzKuR3tp2bNNuWbQ==} + + '@prisma/schema-files-loader@6.19.2': + resolution: {integrity: sha512-v4dHwmYn0rsn6E6Lo6EH4oT7rYVlbJTKWUL3j4gjX0AyQefm3NIITFdYuonDy1S4y2Z/3D1r8gCVjFLskKfK/A==} + + '@rollup/rollup-android-arm-eabi@4.55.1': + resolution: {integrity: sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.55.1': + resolution: {integrity: sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.55.1': + resolution: {integrity: sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.55.1': + resolution: {integrity: sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.55.1': + resolution: {integrity: sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.55.1': + resolution: {integrity: sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.55.1': + resolution: {integrity: sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.55.1': + resolution: {integrity: sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.55.1': + resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.55.1': + resolution: {integrity: sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.55.1': + resolution: {integrity: sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.55.1': + resolution: {integrity: sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.55.1': + resolution: {integrity: sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.55.1': + resolution: {integrity: sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.55.1': + resolution: {integrity: sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.55.1': + resolution: {integrity: sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.55.1': + resolution: {integrity: sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.55.1': + resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.55.1': + resolution: {integrity: sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.55.1': + resolution: {integrity: sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.55.1': + resolution: {integrity: sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.55.1': + resolution: {integrity: sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.55.1': + resolution: {integrity: sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.55.1': + resolution: {integrity: sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.55.1': + resolution: {integrity: sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==} + cpu: [x64] + os: [win32] + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + + '@tailwindcss/node@4.1.18': + resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} + + '@tailwindcss/oxide-android-arm64@4.1.18': + resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.18': + resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.18': + resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.18': + resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.18': + resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.18': + resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.18': + resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==} + engines: {node: '>= 10'} + + '@tailwindcss/postcss@4.1.18': + resolution: {integrity: sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/dagre@0.7.53': + resolution: {integrity: sha512-f4gkWqzPZvYmKhOsDnhq/R8mO4UMcKdxZo+i5SCkOU1wvGeHJeUXGIHeE9pnwGyPMDof1Vx5ZQo4nxpeg2TTVQ==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/node@22.19.7': + resolution: {integrity: sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.8': + resolution: {integrity: sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@vitest/expect@4.0.17': + resolution: {integrity: sha512-mEoqP3RqhKlbmUmntNDDCJeTDavDR+fVYkSOw8qRwJFaW/0/5zA9zFeTrHqNtcmwh6j26yMmwx2PqUDPzt5ZAQ==} + + '@vitest/mocker@4.0.17': + resolution: {integrity: sha512-+ZtQhLA3lDh1tI2wxe3yMsGzbp7uuJSWBM1iTIKCbppWTSBN09PUC+L+fyNlQApQoR+Ps8twt2pbSSXg2fQVEQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.0.17': + resolution: {integrity: sha512-Ah3VAYmjcEdHg6+MwFE17qyLqBHZ+ni2ScKCiW2XrlSBV4H3Z7vYfPfz7CWQ33gyu76oc0Ai36+kgLU3rfF4nw==} + + '@vitest/runner@4.0.17': + resolution: {integrity: sha512-JmuQyf8aMWoo/LmNFppdpkfRVHJcsgzkbCA+/Bk7VfNH7RE6Ut2qxegeyx2j3ojtJtKIbIGy3h+KxGfYfk28YQ==} + + '@vitest/snapshot@4.0.17': + resolution: {integrity: sha512-npPelD7oyL+YQM2gbIYvlavlMVWUfNNGZPcu0aEUQXt7FXTuqhmgiYupPnAanhKvyP6Srs2pIbWo30K0RbDtRQ==} + + '@vitest/spy@4.0.17': + resolution: {integrity: sha512-I1bQo8QaP6tZlTomQNWKJE6ym4SHf3oLS7ceNjozxxgzavRAgZDc06T7kD8gb9bXKEgcLNt00Z+kZO6KaJ62Ew==} + + '@vitest/utils@4.0.17': + resolution: {integrity: sha512-RG6iy+IzQpa9SB8HAFHJ9Y+pTzI+h8553MrciN9eC6TFBErqrQaTas4vG+MVj8S4uKk8uTT2p0vgZPnTdxd96w==} + + '@xyflow/react@12.10.0': + resolution: {integrity: sha512-eOtz3whDMWrB4KWVatIBrKuxECHqip6PfA8fTpaS2RUGVpiEAe+nqDKsLqkViVWxDGreq0lWX71Xth/SPAzXiw==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + + '@xyflow/system@0.0.74': + resolution: {integrity: sha512-7v7B/PkiVrkdZzSbL+inGAo6tkR/WQHHG0/jhSvLQToCsfa8YubOGmBYd1s08tpKpihdHDZFwzQZeR69QSBb4Q==} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + autoprefixer@10.4.23: + resolution: {integrity: sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + baseline-browser-mapping@2.9.15: + resolution: {integrity: sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg==} + hasBin: true + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + + c12@3.1.0: + resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==} + peerDependencies: + magicast: ^0.3.5 + peerDependenciesMeta: + magicast: + optional: true + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + caniuse-lite@1.0.30001765: + resolution: {integrity: sha512-LWcNtSyZrakjECqmpP4qdg0MMGdN368D7X8XvvAqOcqMv0RxnlqVKZl2V6/mBR68oYMxOZPLw/gO7DuisMHUvQ==} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + + classcat@5.0.5: + resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==} + + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deepmerge-ts@7.1.5: + resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} + engines: {node: '>=16.0.0'} + + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dompurify@3.2.7: + resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + effect@3.18.4: + resolution: {integrity: sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==} + + electron-to-chromium@1.5.267: + resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} + + empathic@2.0.0: + resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} + engines: {node: '>=14'} + + enhanced-resolve@5.18.4: + resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} + engines: {node: '>=10.13.0'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + exsolve@1.0.8: + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + + fast-check@3.23.2: + resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} + engines: {node: '>=8.0.0'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fix-dts-default-cjs-exports@1.0.1: + resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + fs-extra@11.3.0: + resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} + engines: {node: '>=14.14'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + giget@2.0.0: + resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} + hasBin: true + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + html-to-image@1.11.13: + resolution: {integrity: sha512-cuOPoI7WApyhBElTTb9oqsawRvZ0rHhaHwghRLlTuffoD1B2aDemlCruLeZrUIIdvG7gs9xeELEPm6PhuASqrg==} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + engines: {node: '>= 12.0.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + marked@14.0.0: + resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==} + engines: {node: '>= 18'} + hasBin: true + + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + + monaco-editor@0.55.1: + resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + next@16.1.3: + resolution: {integrity: sha512-gthG3TRD+E3/mA0uDQb9lqBmx1zVosq5kIwxNN6+MRNd085GzD+9VXMPUs+GGZCbZ+GDZdODUq4Pm7CTXK6ipw==} + engines: {node: '>=20.9.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.51.1 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + nypm@0.6.2: + resolution: {integrity: sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==} + engines: {node: ^14.16.0 || >=16.10.0} + hasBin: true + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prettier@3.8.0: + resolution: {integrity: sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==} + engines: {node: '>=14'} + hasBin: true + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + + rc9@2.1.2: + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + + react-dom@19.2.3: + resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} + peerDependencies: + react: ^19.2.3 + + react@19.2.3: + resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} + engines: {node: '>=0.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + rollup@4.55.1: + resolution: {integrity: sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + state-local@1.0.7: + resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + styled-jsx@5.1.6: + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + tailwind-merge@2.6.0: + resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==} + + tailwindcss@4.1.18: + resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} + engines: {node: '>=14.0.0'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsup@8.5.1: + resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + + turbo-darwin-64@2.7.5: + resolution: {integrity: sha512-nN3wfLLj4OES/7awYyyM7fkU8U8sAFxsXau2bYJwAWi6T09jd87DgHD8N31zXaJ7LcpyppHWPRI2Ov9MuZEwnQ==} + cpu: [x64] + os: [darwin] + + turbo-darwin-arm64@2.7.5: + resolution: {integrity: sha512-wCoDHMiTf3FgLAbZHDDx/unNNonSGhsF5AbbYODbxnpYyoKDpEYacUEPjZD895vDhNvYCH0Nnk24YsP4n/cD6g==} + cpu: [arm64] + os: [darwin] + + turbo-linux-64@2.7.5: + resolution: {integrity: sha512-KKPvhOmJMmzWj/yjeO4LywkQ85vOJyhru7AZk/+c4B6OUh/odQ++SiIJBSbTG2lm1CuV5gV5vXZnf/2AMlu3Zg==} + cpu: [x64] + os: [linux] + + turbo-linux-arm64@2.7.5: + resolution: {integrity: sha512-8PIva4L6BQhiPikUTds9lSFSHXVDAsEvV6QUlgwPsXrtXVQMVi6Sv9p+IxtlWQFvGkdYJUgX9GnK2rC030Xcmw==} + cpu: [arm64] + os: [linux] + + turbo-windows-64@2.7.5: + resolution: {integrity: sha512-rupskv/mkIUgQXzX/wUiK00mKMorQcK8yzhGFha/D5lm05FEnLx8dsip6rWzMcVpvh+4GUMA56PgtnOgpel2AA==} + cpu: [x64] + os: [win32] + + turbo-windows-arm64@2.7.5: + resolution: {integrity: sha512-G377Gxn6P42RnCzfMyDvsqQV7j69kVHKlhz9J4RhtJOB5+DyY4yYh/w0oTIxZQ4JRMmhjwLu3w9zncMoQ6nNDw==} + cpu: [arm64] + os: [win32] + + turbo@2.7.5: + resolution: {integrity: sha512-7Imdmg37joOloTnj+DPrab9hIaQcDdJ5RwSzcauo/wMOSAgO+A/I/8b3hsGGs6PWQz70m/jkPgdqWsfNKtwwDQ==} + hasBin: true + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + use-debounce@10.1.0: + resolution: {integrity: sha512-lu87Za35V3n/MyMoEpD5zJv0k7hCn0p+V/fK2kWD+3k2u3kOCwO593UArbczg1fhfs2rqPEnHpULJ3KmGdDzvg==} + engines: {node: '>= 16.0.0'} + peerDependencies: + react: '*' + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.0.17: + resolution: {integrity: sha512-FQMeF0DJdWY0iOnbv466n/0BudNdKj1l5jYgl5JVTwjSsZSlqyXFt/9+1sEyhR6CLowbZpV7O1sCHrzBhucKKg==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.17 + '@vitest/browser-preview': 4.0.17 + '@vitest/browser-webdriverio': 4.0.17 + '@vitest/ui': 4.0.17 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + + zustand@4.5.7: + resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@dagrejs/dagre@1.1.8': + dependencies: + '@dagrejs/graphlib': 2.2.4 + + '@dagrejs/graphlib@2.2.4': {} + + '@emnapi/runtime@1.8.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.27.2': + optional: true + + '@esbuild/android-arm64@0.27.2': + optional: true + + '@esbuild/android-arm@0.27.2': + optional: true + + '@esbuild/android-x64@0.27.2': + optional: true + + '@esbuild/darwin-arm64@0.27.2': + optional: true + + '@esbuild/darwin-x64@0.27.2': + optional: true + + '@esbuild/freebsd-arm64@0.27.2': + optional: true + + '@esbuild/freebsd-x64@0.27.2': + optional: true + + '@esbuild/linux-arm64@0.27.2': + optional: true + + '@esbuild/linux-arm@0.27.2': + optional: true + + '@esbuild/linux-ia32@0.27.2': + optional: true + + '@esbuild/linux-loong64@0.27.2': + optional: true + + '@esbuild/linux-mips64el@0.27.2': + optional: true + + '@esbuild/linux-ppc64@0.27.2': + optional: true + + '@esbuild/linux-riscv64@0.27.2': + optional: true + + '@esbuild/linux-s390x@0.27.2': + optional: true + + '@esbuild/linux-x64@0.27.2': + optional: true + + '@esbuild/netbsd-arm64@0.27.2': + optional: true + + '@esbuild/netbsd-x64@0.27.2': + optional: true + + '@esbuild/openbsd-arm64@0.27.2': + optional: true + + '@esbuild/openbsd-x64@0.27.2': + optional: true + + '@esbuild/openharmony-arm64@0.27.2': + optional: true + + '@esbuild/sunos-x64@0.27.2': + optional: true + + '@esbuild/win32-arm64@0.27.2': + optional: true + + '@esbuild/win32-ia32@0.27.2': + optional: true + + '@esbuild/win32-x64@0.27.2': + optional: true + + '@heroicons/react@2.2.0(react@19.2.3)': + dependencies: + react: 19.2.3 + + '@icons-pack/react-simple-icons@13.8.0(react@19.2.3)': + dependencies: + react: 19.2.3 + + '@img/colour@1.0.0': + optional: true + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.8.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@monaco-editor/loader@1.7.0': + dependencies: + state-local: 1.0.7 + + '@monaco-editor/react@4.7.0(monaco-editor@0.55.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@monaco-editor/loader': 1.7.0 + monaco-editor: 0.55.1 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + + '@next/env@16.1.3': {} + + '@next/swc-darwin-arm64@16.1.3': + optional: true + + '@next/swc-darwin-x64@16.1.3': + optional: true + + '@next/swc-linux-arm64-gnu@16.1.3': + optional: true + + '@next/swc-linux-arm64-musl@16.1.3': + optional: true + + '@next/swc-linux-x64-gnu@16.1.3': + optional: true + + '@next/swc-linux-x64-musl@16.1.3': + optional: true + + '@next/swc-win32-arm64-msvc@16.1.3': + optional: true + + '@next/swc-win32-x64-msvc@16.1.3': + optional: true + + '@prisma/config@6.19.2': + dependencies: + c12: 3.1.0 + deepmerge-ts: 7.1.5 + effect: 3.18.4 + empathic: 2.0.0 + transitivePeerDependencies: + - magicast + + '@prisma/debug@6.19.2': {} + + '@prisma/dmmf@6.19.2': {} + + '@prisma/driver-adapter-utils@6.19.2': + dependencies: + '@prisma/debug': 6.19.2 + + '@prisma/engines-version@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7': {} + + '@prisma/engines@6.19.2': + dependencies: + '@prisma/debug': 6.19.2 + '@prisma/engines-version': 7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7 + '@prisma/fetch-engine': 6.19.2 + '@prisma/get-platform': 6.19.2 + + '@prisma/fetch-engine@6.19.2': + dependencies: + '@prisma/debug': 6.19.2 + '@prisma/engines-version': 7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7 + '@prisma/get-platform': 6.19.2 + + '@prisma/generator-helper@6.19.2': + dependencies: + '@prisma/debug': 6.19.2 + '@prisma/dmmf': 6.19.2 + '@prisma/generator': 6.19.2 + + '@prisma/generator@6.19.2': {} + + '@prisma/get-platform@6.19.2': + dependencies: + '@prisma/debug': 6.19.2 + + '@prisma/internals@6.19.2(typescript@5.9.3)': + dependencies: + '@prisma/config': 6.19.2 + '@prisma/debug': 6.19.2 + '@prisma/dmmf': 6.19.2 + '@prisma/driver-adapter-utils': 6.19.2 + '@prisma/engines': 6.19.2 + '@prisma/fetch-engine': 6.19.2 + '@prisma/generator': 6.19.2 + '@prisma/generator-helper': 6.19.2 + '@prisma/get-platform': 6.19.2 + '@prisma/prisma-schema-wasm': 7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7 + '@prisma/schema-engine-wasm': 7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7 + '@prisma/schema-files-loader': 6.19.2 + arg: 5.0.2 + prompts: 2.4.2 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - magicast + + '@prisma/prisma-schema-wasm@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7': {} + + '@prisma/schema-engine-wasm@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7': {} + + '@prisma/schema-files-loader@6.19.2': + dependencies: + '@prisma/prisma-schema-wasm': 7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7 + fs-extra: 11.3.0 + + '@rollup/rollup-android-arm-eabi@4.55.1': + optional: true + + '@rollup/rollup-android-arm64@4.55.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.55.1': + optional: true + + '@rollup/rollup-darwin-x64@4.55.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.55.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.55.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.55.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.55.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.55.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.55.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.55.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.55.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.55.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.55.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.55.1': + optional: true + + '@standard-schema/spec@1.1.0': {} + + '@swc/helpers@0.5.15': + dependencies: + tslib: 2.8.1 + + '@tailwindcss/node@4.1.18': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.4 + jiti: 2.6.1 + lightningcss: 1.30.2 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.1.18 + + '@tailwindcss/oxide-android-arm64@4.1.18': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.18': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.18': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.18': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.18': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + optional: true + + '@tailwindcss/oxide@4.1.18': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-x64': 4.1.18 + '@tailwindcss/oxide-freebsd-x64': 4.1.18 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.18 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-x64-musl': 4.1.18 + '@tailwindcss/oxide-wasm32-wasi': 4.1.18 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 + + '@tailwindcss/postcss@4.1.18': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.1.18 + '@tailwindcss/oxide': 4.1.18 + postcss: 8.5.6 + tailwindcss: 4.1.18 + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/d3-color@3.1.3': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/dagre@0.7.53': {} + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/node@22.19.7': + dependencies: + undici-types: 6.21.0 + + '@types/react-dom@19.2.3(@types/react@19.2.8)': + dependencies: + '@types/react': 19.2.8 + + '@types/react@19.2.8': + dependencies: + csstype: 3.2.3 + + '@types/trusted-types@2.0.7': + optional: true + + '@vitest/expect@4.0.17': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.17 + '@vitest/utils': 4.0.17 + chai: 6.2.2 + tinyrainbow: 3.0.3 + + '@vitest/mocker@4.0.17(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.30.2))': + dependencies: + '@vitest/spy': 4.0.17 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.30.2) + + '@vitest/pretty-format@4.0.17': + dependencies: + tinyrainbow: 3.0.3 + + '@vitest/runner@4.0.17': + dependencies: + '@vitest/utils': 4.0.17 + pathe: 2.0.3 + + '@vitest/snapshot@4.0.17': + dependencies: + '@vitest/pretty-format': 4.0.17 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.0.17': {} + + '@vitest/utils@4.0.17': + dependencies: + '@vitest/pretty-format': 4.0.17 + tinyrainbow: 3.0.3 + + '@xyflow/react@12.10.0(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@xyflow/system': 0.0.74 + classcat: 5.0.5 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + zustand: 4.5.7(@types/react@19.2.8)(react@19.2.3) + transitivePeerDependencies: + - '@types/react' + - immer + + '@xyflow/system@0.0.74': + dependencies: + '@types/d3-drag': 3.0.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + + acorn@8.15.0: {} + + any-promise@1.3.0: {} + + arg@5.0.2: {} + + assertion-error@2.0.1: {} + + autoprefixer@10.4.23(postcss@8.5.6): + dependencies: + browserslist: 4.28.1 + caniuse-lite: 1.0.30001765 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + baseline-browser-mapping@2.9.15: {} + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.9.15 + caniuse-lite: 1.0.30001765 + electron-to-chromium: 1.5.267 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + bundle-require@5.1.0(esbuild@0.27.2): + dependencies: + esbuild: 0.27.2 + load-tsconfig: 0.2.5 + + c12@3.1.0: + dependencies: + chokidar: 4.0.3 + confbox: 0.2.2 + defu: 6.1.4 + dotenv: 16.6.1 + exsolve: 1.0.8 + giget: 2.0.0 + jiti: 2.6.1 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 1.0.0 + pkg-types: 2.3.0 + rc9: 2.1.2 + + cac@6.7.14: {} + + caniuse-lite@1.0.30001765: {} + + chai@6.2.2: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + citty@0.1.6: + dependencies: + consola: 3.4.2 + + classcat@5.0.5: {} + + client-only@0.0.1: {} + + clsx@2.1.1: {} + + commander@4.1.1: {} + + confbox@0.1.8: {} + + confbox@0.2.2: {} + + consola@3.4.2: {} + + csstype@3.2.3: {} + + d3-color@3.1.0: {} + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-ease@3.0.1: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-selection@3.0.0: {} + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deepmerge-ts@7.1.5: {} + + defu@6.1.4: {} + + destr@2.0.5: {} + + detect-libc@2.1.2: {} + + dompurify@3.2.7: + optionalDependencies: + '@types/trusted-types': 2.0.7 + + dotenv@16.6.1: {} + + effect@3.18.4: + dependencies: + '@standard-schema/spec': 1.1.0 + fast-check: 3.23.2 + + electron-to-chromium@1.5.267: {} + + empathic@2.0.0: {} + + enhanced-resolve@5.18.4: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + + es-module-lexer@1.7.0: {} + + esbuild@0.27.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 + + escalade@3.2.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + expect-type@1.3.0: {} + + exsolve@1.0.8: {} + + fast-check@3.23.2: + dependencies: + pure-rand: 6.1.0 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fix-dts-default-cjs-exports@1.0.1: + dependencies: + magic-string: 0.30.21 + mlly: 1.8.0 + rollup: 4.55.1 + + fraction.js@5.3.4: {} + + fs-extra@11.3.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fsevents@2.3.3: + optional: true + + giget@2.0.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + defu: 6.1.4 + node-fetch-native: 1.6.7 + nypm: 0.6.2 + pathe: 2.0.3 + + graceful-fs@4.2.11: {} + + html-to-image@1.11.13: {} + + jiti@2.6.1: {} + + joycon@3.1.1: {} + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + kleur@3.0.3: {} + + lightningcss-android-arm64@1.30.2: + optional: true + + lightningcss-darwin-arm64@1.30.2: + optional: true + + lightningcss-darwin-x64@1.30.2: + optional: true + + lightningcss-freebsd-x64@1.30.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.2: + optional: true + + lightningcss-linux-arm64-gnu@1.30.2: + optional: true + + lightningcss-linux-arm64-musl@1.30.2: + optional: true + + lightningcss-linux-x64-gnu@1.30.2: + optional: true + + lightningcss-linux-x64-musl@1.30.2: + optional: true + + lightningcss-win32-arm64-msvc@1.30.2: + optional: true + + lightningcss-win32-x64-msvc@1.30.2: + optional: true + + lightningcss@1.30.2: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.30.2 + lightningcss-darwin-arm64: 1.30.2 + lightningcss-darwin-x64: 1.30.2 + lightningcss-freebsd-x64: 1.30.2 + lightningcss-linux-arm-gnueabihf: 1.30.2 + lightningcss-linux-arm64-gnu: 1.30.2 + lightningcss-linux-arm64-musl: 1.30.2 + lightningcss-linux-x64-gnu: 1.30.2 + lightningcss-linux-x64-musl: 1.30.2 + lightningcss-win32-arm64-msvc: 1.30.2 + lightningcss-win32-x64-msvc: 1.30.2 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + load-tsconfig@0.2.5: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + marked@14.0.0: {} + + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.3 + + monaco-editor@0.55.1: + dependencies: + dompurify: 3.2.7 + marked: 14.0.0 + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.11: {} + + next@16.1.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + '@next/env': 16.1.3 + '@swc/helpers': 0.5.15 + baseline-browser-mapping: 2.9.15 + caniuse-lite: 1.0.30001765 + postcss: 8.4.31 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + styled-jsx: 5.1.6(react@19.2.3) + optionalDependencies: + '@next/swc-darwin-arm64': 16.1.3 + '@next/swc-darwin-x64': 16.1.3 + '@next/swc-linux-arm64-gnu': 16.1.3 + '@next/swc-linux-arm64-musl': 16.1.3 + '@next/swc-linux-x64-gnu': 16.1.3 + '@next/swc-linux-x64-musl': 16.1.3 + '@next/swc-win32-arm64-msvc': 16.1.3 + '@next/swc-win32-x64-msvc': 16.1.3 + sharp: 0.34.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + node-fetch-native@1.6.7: {} + + node-releases@2.0.27: {} + + nypm@0.6.2: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + pathe: 2.0.3 + pkg-types: 2.3.0 + tinyexec: 1.0.2 + + object-assign@4.1.1: {} + + obug@2.1.1: {} + + ohash@2.0.11: {} + + pathe@2.0.3: {} + + perfect-debounce@1.0.0: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + pirates@4.0.7: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.8 + pathe: 2.0.3 + + postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.6): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 2.6.1 + postcss: 8.5.6 + + postcss-value-parser@4.2.0: {} + + postcss@8.4.31: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prettier@3.8.0: {} + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + pure-rand@6.1.0: {} + + rc9@2.1.2: + dependencies: + defu: 6.1.4 + destr: 2.0.5 + + react-dom@19.2.3(react@19.2.3): + dependencies: + react: 19.2.3 + scheduler: 0.27.0 + + react@19.2.3: {} + + readdirp@4.1.2: {} + + resolve-from@5.0.0: {} + + rollup@4.55.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.55.1 + '@rollup/rollup-android-arm64': 4.55.1 + '@rollup/rollup-darwin-arm64': 4.55.1 + '@rollup/rollup-darwin-x64': 4.55.1 + '@rollup/rollup-freebsd-arm64': 4.55.1 + '@rollup/rollup-freebsd-x64': 4.55.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.55.1 + '@rollup/rollup-linux-arm-musleabihf': 4.55.1 + '@rollup/rollup-linux-arm64-gnu': 4.55.1 + '@rollup/rollup-linux-arm64-musl': 4.55.1 + '@rollup/rollup-linux-loong64-gnu': 4.55.1 + '@rollup/rollup-linux-loong64-musl': 4.55.1 + '@rollup/rollup-linux-ppc64-gnu': 4.55.1 + '@rollup/rollup-linux-ppc64-musl': 4.55.1 + '@rollup/rollup-linux-riscv64-gnu': 4.55.1 + '@rollup/rollup-linux-riscv64-musl': 4.55.1 + '@rollup/rollup-linux-s390x-gnu': 4.55.1 + '@rollup/rollup-linux-x64-gnu': 4.55.1 + '@rollup/rollup-linux-x64-musl': 4.55.1 + '@rollup/rollup-openbsd-x64': 4.55.1 + '@rollup/rollup-openharmony-arm64': 4.55.1 + '@rollup/rollup-win32-arm64-msvc': 4.55.1 + '@rollup/rollup-win32-ia32-msvc': 4.55.1 + '@rollup/rollup-win32-x64-gnu': 4.55.1 + '@rollup/rollup-win32-x64-msvc': 4.55.1 + fsevents: 2.3.3 + + scheduler@0.27.0: {} + + semver@7.7.3: + optional: true + + sharp@0.34.5: + dependencies: + '@img/colour': 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + optional: true + + siginfo@2.0.0: {} + + sisteransi@1.0.5: {} + + source-map-js@1.2.1: {} + + source-map@0.7.6: {} + + stackback@0.0.2: {} + + state-local@1.0.7: {} + + std-env@3.10.0: {} + + styled-jsx@5.1.6(react@19.2.3): + dependencies: + client-only: 0.0.1 + react: 19.2.3 + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.15 + ts-interface-checker: 0.1.13 + + tailwind-merge@2.6.0: {} + + tailwindcss@4.1.18: {} + + tapable@2.3.0: {} + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyexec@1.0.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinyrainbow@3.0.3: {} + + tree-kill@1.2.2: {} + + ts-interface-checker@0.1.13: {} + + tslib@2.8.1: {} + + tsup@8.5.1(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.3): + dependencies: + bundle-require: 5.1.0(esbuild@0.27.2) + cac: 6.7.14 + chokidar: 4.0.3 + consola: 3.4.2 + debug: 4.4.3 + esbuild: 0.27.2 + fix-dts-default-cjs-exports: 1.0.1 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.6) + resolve-from: 5.0.0 + rollup: 4.55.1 + source-map: 0.7.6 + sucrase: 3.35.1 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tree-kill: 1.2.2 + optionalDependencies: + postcss: 8.5.6 + typescript: 5.9.3 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + + turbo-darwin-64@2.7.5: + optional: true + + turbo-darwin-arm64@2.7.5: + optional: true + + turbo-linux-64@2.7.5: + optional: true + + turbo-linux-arm64@2.7.5: + optional: true + + turbo-windows-64@2.7.5: + optional: true + + turbo-windows-arm64@2.7.5: + optional: true + + turbo@2.7.5: + optionalDependencies: + turbo-darwin-64: 2.7.5 + turbo-darwin-arm64: 2.7.5 + turbo-linux-64: 2.7.5 + turbo-linux-arm64: 2.7.5 + turbo-windows-64: 2.7.5 + turbo-windows-arm64: 2.7.5 + + typescript@5.9.3: {} + + ufo@1.6.3: {} + + undici-types@6.21.0: {} + + universalify@2.0.1: {} + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + use-debounce@10.1.0(react@19.2.3): + dependencies: + react: 19.2.3 + + use-sync-external-store@1.6.0(react@19.2.3): + dependencies: + react: 19.2.3 + + vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.30.2): + dependencies: + esbuild: 0.27.2 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.55.1 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 22.19.7 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.30.2 + + vitest@4.0.17(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.30.2): + dependencies: + '@vitest/expect': 4.0.17 + '@vitest/mocker': 4.0.17(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.30.2)) + '@vitest/pretty-format': 4.0.17 + '@vitest/runner': 4.0.17 + '@vitest/snapshot': 4.0.17 + '@vitest/spy': 4.0.17 + '@vitest/utils': 4.0.17 + es-module-lexer: 1.7.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.30.2) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.19.7 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + zod@3.25.76: {} + + zustand@4.5.7(@types/react@19.2.8)(react@19.2.3): + dependencies: + use-sync-external-store: 1.6.0(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.8 + react: 19.2.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..e9b0dad --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - 'apps/*' + - 'packages/*' diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..5326016 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "allowJs": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true + }, + "exclude": ["node_modules"] +} diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..c499b68 --- /dev/null +++ b/turbo.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://turbo.build/schema.json", + "globalDependencies": ["**/.env.*local"], + "tasks": { + "build": { + "dependsOn": ["^build"], + "outputs": [".next/**", "!.next/cache/**", "dist/**"] + }, + "dev": { + "cache": false, + "persistent": true + }, + "lint": { + "dependsOn": ["^build"] + }, + "test": { + "dependsOn": ["^build"] + }, + "typecheck": { + "dependsOn": ["^build"] + } + } +}