commit 05be5b03aac66f4142777850689d70bfb01976d2 Author: David Beccue Date: Sat May 16 07:05:24 2026 +0500 Initial scaffold for AvtoAmbor parts inventory SvelteKit 2 + Svelte 4 + adapter-node, SQLite via better-sqlite3 (WAL, foreign keys on). Bilingual EN/Тоҷикӣ throughout, locale persisted in localStorage. Pages: dashboard (totals, low stock, recent movements), parts list with search and sort, part create/edit, record movement (in/out/adjust with smart unit-price and adjust-quantity prefill), suppliers list with inline add. Schema: categories, suppliers, parts (with _en/_tg name+description columns, dirams for money), stock_movements with check on movement_type. On-hand updates are done in JS inside a transaction with the movement insert. Dockerized dev: docker compose, named project, bind-mounted data/ for DB persistence. Seed contains 6 categories, 4 suppliers, 31 realistic parts (Lada / Nexia / Opel / Toyota bias). Co-Authored-By: Claude Opus 4.7 diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..efd9de0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +node_modules +.svelte-kit +build +data +.git +*.log +.env +.env.* +*.swp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..58970c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +node_modules/ +.svelte-kit/ +build/ +data/ +*.log +.env +.env.* +!.env.example +.DS_Store +*.sw? diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8371d66 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM node:20-bookworm-slim + +# Tools needed to compile better-sqlite3 +RUN apt-get update \ + && apt-get install -y --no-install-recommends python3 make g++ ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Run as the node user (uid 1000) — already exists in node images +USER node +WORKDIR /app + +EXPOSE 5173 3000 + +CMD ["npm", "run", "dev"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1bda848 --- /dev/null +++ b/Makefile @@ -0,0 +1,56 @@ +.PHONY: help install run build db-init db-reset docker-build docker-shell clean clean-all + +DC := docker compose + +help: + @echo "" + @echo " ╔════════════════════════════════════════════════╗" + @echo " ║ AvtoAmbor — auto parts inventory (dev tasks) ║" + @echo " ╚════════════════════════════════════════════════╝" + @echo "" + @echo " make install Install npm dependencies inside the container" + @echo " make run Start the dev server (http://localhost:5173)" + @echo " make build Production build into ./build (adapter-node)" + @echo " make db-init Create data/avtoambor.db from schema + seed (skip if exists)" + @echo " make db-reset DELETE and recreate data/avtoambor.db (asks first)" + @echo " make docker-build Rebuild the Docker image" + @echo " make docker-shell Open an interactive bash shell in the container" + @echo " make clean Remove node_modules and build/ (keeps data/)" + @echo " make clean-all Also wipe data/ (destroys the DB)" + @echo "" + +install: + @$(DC) run --rm app npm install + +run: + @$(DC) up + +build: + @$(DC) run --rm app npm run build + +db-init: + @if [ -f data/avtoambor.db ]; then \ + echo "data/avtoambor.db already exists — skipping. Use 'make db-reset' to recreate."; \ + else \ + mkdir -p data && $(DC) run --rm app node scripts/init-db.js; \ + fi + +db-reset: + @printf "This will DELETE data/avtoambor.db. Continue? [y/N] " && read ans && [ "$$ans" = "y" ] || (echo "aborted." && exit 1) + @rm -f data/avtoambor.db data/avtoambor.db-shm data/avtoambor.db-wal + @mkdir -p data + @$(DC) run --rm app node scripts/init-db.js + +docker-build: + @$(DC) build + +docker-shell: + @$(DC) run --rm app bash + +clean: + @rm -rf node_modules build .svelte-kit + @echo "removed node_modules, build/, .svelte-kit/ (data/ kept)" + +clean-all: clean + @rm -rf data + @echo "wiped data/ as well." diff --git a/README.md b/README.md new file mode 100644 index 0000000..2c57596 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# AvtoAmbor + +Simple auto-parts inventory for a single shop. SvelteKit + SQLite. UI is +bilingual (English / Тоҷикӣ). + +## Prerequisites + +- Docker + Docker Compose + +Everything else (Node, npm, native build tools for `better-sqlite3`) runs +inside the container. + +## Quickstart + +```sh +make install # install dependencies inside the container +make db-init # create data/avtoambor.db with schema + seed +make run # dev server at http://localhost:5173 +``` + +`make help` lists every target. + +## Production + +Build with `make build`, copy the `build/` directory plus `node_modules` and +`data/avtoambor.db` to the Windows host, then run: + +```sh +node build/index.js +``` + +The server listens on port 3000 by default. Open `http://localhost:3000` in +the browser on that machine. + +## Data + +The SQLite database lives at `data/avtoambor.db`. The `data/` directory is +gitignored and bind-mounted into the container, so the DB file persists on +the host. + +- `make db-init` — create the DB if it doesn't exist (does nothing otherwise). +- `make db-reset` — delete and recreate the DB (asks for confirmation). diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..88410ec --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +name: avtoambor + +services: + app: + build: . + image: avtoambor:dev + container_name: avtoambor + working_dir: /app + user: node + ports: + - "5173:5173" + - "3000:3000" + volumes: + - .:/app + - ./data:/app/data + environment: + - NODE_ENV=development + command: npm run dev diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..6e36cc3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2201 @@ +{ + "name": "avtoambor", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "avtoambor", + "version": "0.1.0", + "dependencies": { + "better-sqlite3": "^11.3.0" + }, + "devDependencies": { + "@sveltejs/adapter-node": "^5.2.0", + "@sveltejs/kit": "^2.5.0", + "@sveltejs/vite-plugin-svelte": "^3.1.0", + "svelte": "^4.2.0", + "vite": "^5.2.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.2.tgz", + "integrity": "sha512-S/ggWH1LU7jTyi9DxZOKyxpVd4hF/OZ0JrEbeLjXk/DFXwRny0tjD2c992zOUYQobLrVkRVMDdmHP16HKP7GRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "fdir": "^6.2.0", + "is-reference": "1.2.1", + "magic-string": "^0.30.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0 || 14 >= 14.17" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", + "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", + "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", + "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz", + "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", + "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz", + "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz", + "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", + "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz", + "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz", + "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz", + "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", + "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz", + "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz", + "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz", + "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz", + "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz", + "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz", + "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz", + "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz", + "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", + "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", + "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", + "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", + "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz", + "integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/adapter-node": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.5.4.tgz", + "integrity": "sha512-45X92CXW+2J8ZUzPv3eLlKWEzINKiiGeFWTjyER4ZN4sGgNoaoeSkCY/QYNxHpPXy71QPsctwccBo9jJs0ySPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/plugin-commonjs": "^29.0.0", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.0", + "rollup": "^4.59.0" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.4.0" + } + }, + "node_modules/@sveltejs/kit": { + "version": "2.60.1", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.60.1.tgz", + "integrity": "sha512-mQjlkNo+rJvpln7V2IGY2j99BqhcFbS4UN0AQNKNYfhBAFZTuCDAdW3a1sgf330mvtNvsBXn3HpAhcmvdJTcIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/cookie": "^0.6.0", + "acorn": "^8.14.1", + "cookie": "^0.6.0", + "devalue": "^5.8.1", + "esm-env": "^1.2.2", + "kleur": "^4.1.5", + "magic-string": "^0.30.5", + "mrmime": "^2.0.0", + "set-cookie-parser": "^3.0.0", + "sirv": "^3.0.0" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": ">=18.13" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": "^5.3.3 || ^6.0.0", + "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.1.2.tgz", + "integrity": "sha512-Txsm1tJvtiYeLUVRNqxZGKR/mI+CzuIQuc2gn+YCs9rMTowpNZ2Nqt53JdL8KF9bLhAf2ruR/dr9eZCwdTriRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^2.1.0", + "debug": "^4.3.4", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.10", + "svelte-hmr": "^0.16.0", + "vitefu": "^0.2.5" + }, + "engines": { + "node": "^18.0.0 || >=20" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "vite": "^5.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-2.1.0.tgz", + "integrity": "sha512-9QX28IymvBlSCqsCll5t0kQVxipsfhFFL+L2t3nTWfXnddYwxBuAEtTtlaVQpRz9c37BhJjltSeY4AJSC03SSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.0.0 || >=20" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^3.0.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "vite": "^5.0.0" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/better-sqlite3": { + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz", + "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/code-red": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", + "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "@types/estree": "^1.0.1", + "acorn": "^8.10.0", + "estree-walker": "^3.0.3", + "periscopic": "^3.1.0" + } + }, + "node_modules/code-red/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz", + "integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/node-abi": { + "version": "3.92.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", + "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/periscopic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", + "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^3.0.0", + "is-reference": "^3.0.0" + } + }, + "node_modules/periscopic/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/periscopic/node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rollup": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", + "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.4", + "@rollup/rollup-android-arm64": "4.60.4", + "@rollup/rollup-darwin-arm64": "4.60.4", + "@rollup/rollup-darwin-x64": "4.60.4", + "@rollup/rollup-freebsd-arm64": "4.60.4", + "@rollup/rollup-freebsd-x64": "4.60.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", + "@rollup/rollup-linux-arm-musleabihf": "4.60.4", + "@rollup/rollup-linux-arm64-gnu": "4.60.4", + "@rollup/rollup-linux-arm64-musl": "4.60.4", + "@rollup/rollup-linux-loong64-gnu": "4.60.4", + "@rollup/rollup-linux-loong64-musl": "4.60.4", + "@rollup/rollup-linux-ppc64-gnu": "4.60.4", + "@rollup/rollup-linux-ppc64-musl": "4.60.4", + "@rollup/rollup-linux-riscv64-gnu": "4.60.4", + "@rollup/rollup-linux-riscv64-musl": "4.60.4", + "@rollup/rollup-linux-s390x-gnu": "4.60.4", + "@rollup/rollup-linux-x64-gnu": "4.60.4", + "@rollup/rollup-linux-x64-musl": "4.60.4", + "@rollup/rollup-openbsd-x64": "4.60.4", + "@rollup/rollup-openharmony-arm64": "4.60.4", + "@rollup/rollup-win32-arm64-msvc": "4.60.4", + "@rollup/rollup-win32-ia32-msvc": "4.60.4", + "@rollup/rollup-win32-x64-gnu": "4.60.4", + "@rollup/rollup-win32-x64-msvc": "4.60.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz", + "integrity": "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svelte": { + "version": "4.2.20", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.20.tgz", + "integrity": "sha512-eeEgGc2DtiUil5ANdtd8vPwt9AgaMdnuUFnPft9F5oMvU/FHu5IHFic+p1dR/UOB7XU2mX2yHW+NcTch4DCh5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@jridgewell/sourcemap-codec": "^1.4.15", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/estree": "^1.0.1", + "acorn": "^8.9.0", + "aria-query": "^5.3.0", + "axobject-query": "^4.0.0", + "code-red": "^1.0.3", + "css-tree": "^2.3.1", + "estree-walker": "^3.0.3", + "is-reference": "^3.0.1", + "locate-character": "^3.0.0", + "magic-string": "^0.30.4", + "periscopic": "^3.1.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/svelte-hmr": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.16.0.tgz", + "integrity": "sha512-Gyc7cOS3VJzLlfj7wKS0ZnzDVdv3Pn2IuVeJPk9m2skfhcu5bq3wtIZyQGggr7/Iim5rH5cncyQft/kRLupcnA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^12.20 || ^14.13.1 || >= 16" + }, + "peerDependencies": { + "svelte": "^3.19.0 || ^4.0.0" + } + }, + "node_modules/svelte/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/svelte/node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz", + "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..5b6c1cd --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "avtoambor", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite dev --host 0.0.0.0 --port 5173", + "build": "vite build", + "preview": "node build/index.js", + "start": "node build/index.js", + "db:init": "node scripts/init-db.js" + }, + "devDependencies": { + "@sveltejs/adapter-node": "^5.2.0", + "@sveltejs/kit": "^2.5.0", + "svelte": "^4.2.0", + "vite": "^5.2.0" + }, + "dependencies": { + "better-sqlite3": "^11.3.0" + } +} diff --git a/prompts.txt b/prompts.txt new file mode 100644 index 0000000..8e802e0 --- /dev/null +++ b/prompts.txt @@ -0,0 +1,167 @@ + +# AvtoAmbor — Auto Parts Inventory System (v1 scaffold) + +## Context +Build a simple, single-user inventory system named "AvtoAmbor" for an auto parts +store in Tajikistan. Development is on Linux inside Docker; the production target +is a Windows machine where the owner will access it via browser on localhost. +v1 is PARTS ONLY — no service jobs, customers, or invoicing. + +Do not write tests, lint config, or CI in this pass. Keep the code idiomatic, +lightly commented, and not over-engineered. + +## Tech stack (strict) +- Node.js 20 LTS, run via Docker +- SQLite via `better-sqlite3` +- SvelteKit 2.x pinned to **Svelte 4** — `"svelte": "^4.2.0"` in package.json. + Use Svelte 4 syntax only: no runes, no `$state`/`$derived`/`$effect`. +- `@sveltejs/adapter-node` for production (so deployment to Windows is just + `node build/index.js`) +- Plain CSS. No Tailwind, no component library. + +## Repo layout +avtoambor/ + Makefile + Dockerfile + docker-compose.yml + .dockerignore + .gitignore + README.md + package.json + svelte.config.js + vite.config.js + src/ + app.html + hooks.server.js # opens db on startup + lib/ + server/ + db.js # better-sqlite3, WAL mode, foreign keys on + schema.sql + seed.sql + parts.js # CRUD helpers + movements.js + suppliers.js + i18n/ + en.json + tg.json + store.js # locale store + t(key) helper + components/ + Header.svelte # "AvtoAmbor" wordmark + EN/Тоҷ toggle + routes/ + +layout.svelte # renders
+ +page.svelte # dashboard + parts/ + +page.svelte # list, search, sort + +page.server.js + new/ + +page.svelte + +page.server.js + [id]/ + +page.svelte # edit + recent movements + +page.server.js + movements/ + new/ + +page.svelte + +page.server.js + suppliers/ + +page.svelte + +page.server.js + data/ # gitignored; holds avtoambor.db + scripts/ + init-db.js # reads schema.sql + seed.sql, writes data/avtoambor.db + +## Database schema (initial guess — we will iterate) +- All translated fields use `_en` and `_tg` suffixes. +- Money stored as INTEGER dirams (1 TJS = 100 dirams). +- Timestamps as ISO 8601 TEXT (`datetime('now')`). + +Tables: +- categories(id PK, name_en, name_tg, sort_order) +- suppliers(id PK, name, phone, address, notes, created_at) +- parts(id PK, sku UNIQUE NOT NULL, name_en, name_tg, + description_en, description_tg, category_id FK, + unit TEXT, cost_price INT, sale_price INT, + quantity_on_hand INT DEFAULT 0, reorder_level INT DEFAULT 0, + location TEXT, barcode TEXT, active INT DEFAULT 1, + created_at, updated_at) +- stock_movements(id PK, part_id FK, movement_type + CHECK(movement_type IN ('in','out','adjust')), + quantity INT, unit_price INT, + supplier_id FK NULL, reference TEXT, notes TEXT, + created_at) + +Indexes on parts.sku, parts.barcode, parts.category_id, stock_movements.part_id. + +Update `parts.quantity_on_hand` in application code (in a transaction with +the movement insert), not via trigger — clearer for future-me. + +## Seed data +- 5–6 categories: Filters, Brakes, Engine, Electrical, Fluids, Belts & Hoses +- 3–4 suppliers with realistic names +- 25–30 realistic auto parts with EN and Tajik (Cyrillic) names. + Bias toward parts common for Lada, Daewoo Nexia, Opel, and Toyota, + which are common in Tajikistan. Use realistic somoni prices. + +## UI +- `Header.svelte` (in `+layout.svelte`, every page): + - Left: "AvtoAmbor" wordmark + - Right: language toggle showing the *other* language (click EN → switches to + Tajik). Persist choice to `localStorage` under key `avtoambor.locale`. +- Pages for v1: + - `/` dashboard: total SKUs, count of parts at/below reorder level, + total inventory value at cost + - `/parts` searchable + sortable list + - `/parts/new` create + - `/parts/[id]` edit, with recent movements panel + - `/movements/new` record in/out/adjust + - `/suppliers` list + add inline +- Every visible string goes through the i18n helper. Missing keys fall back to + English and log a `console.warn` once per missing key. + GIve it a Tajik look & feel, if that's even possible for such a simple app + +## i18n +- `en.json` and `tg.json` with nested keys (e.g. `nav.parts`, `parts.sku`) +- `store.js` exports a writable `locale` store and a derived `t` function: + `$t('parts.sku')` in templates +- Default locale is `'tg'`. On mount in the layout, read `localStorage` and + hydrate if present. + +## Makefile +Targets (use `docker compose`). First target = `help`. +- `help` — print a friendly banner + list of targets with descriptions +- `install` — `docker compose run --rm app npm install` +- `run` — `docker compose up` (dev server on 5173) +- `build` — production build via adapter-node into `build/` +- `db-init` — run `scripts/init-db.js`, skip if `data/avtoambor.db` exists +- `db-reset` — confirm prompt, then delete and recreate the db +- `docker-build` — build the image +- `docker-shell` — interactive bash in the container +- `clean` — remove `node_modules`, `build/`, but keep `data/` +- `clean-all` — also wipe `data/` + +Use `@` to suppress command echo where it would be noise. + +## Dockerfile +- `node:20-bookworm-slim` +- Install `python3 make g++` for the `better-sqlite3` native build +- Non-root user +- WORKDIR `/app` +- EXPOSE 5173 and 3000 + +## docker-compose.yml +- One service `app` +- Bind-mount the repo to `/app` +- Named volume for `node_modules` so it doesn't shadow the host +- Bind-mount `./data` so the SQLite file persists on the host +- Map 5173:5173 and 3000:3000 + +## README.md +Short: what it is, prerequisites (Docker), quickstart +(`make install && make db-init && make run`), and a one-liner on production: +`make build` then `node build/index.js` on the Windows host. + +## Deliverables for this pass +1. Generate every file above, working out of the box. +2. Print the resulting file tree. +3. Print the exact command sequence to bring it up from a fresh clone. +4. Call out anything you guessed at that I should review before we move on. diff --git a/scripts/init-db.js b/scripts/init-db.js new file mode 100644 index 0000000..3265f9d --- /dev/null +++ b/scripts/init-db.js @@ -0,0 +1,35 @@ +#!/usr/bin/env node +// Build data/avtoambor.db from schema.sql + seed.sql. +// Safe to run from anywhere — paths are resolved relative to this file. + +import Database from 'better-sqlite3'; +import { readFileSync, mkdirSync, existsSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, resolve } from 'node:path'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const ROOT = resolve(__dirname, '..'); +const DATA_DIR = resolve(ROOT, 'data'); +const DB_PATH = resolve(DATA_DIR, 'avtoambor.db'); +const SCHEMA = resolve(ROOT, 'src/lib/server/schema.sql'); +const SEED = resolve(ROOT, 'src/lib/server/seed.sql'); + +if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true }); + +const db = new Database(DB_PATH); +db.pragma('journal_mode = WAL'); +db.pragma('foreign_keys = ON'); + +console.log(`[init-db] applying schema → ${DB_PATH}`); +db.exec(readFileSync(SCHEMA, 'utf8')); + +const partsCount = db.prepare(`SELECT COUNT(*) AS n FROM parts`).get().n; +if (partsCount === 0) { + console.log('[init-db] inserting seed data…'); + db.exec(readFileSync(SEED, 'utf8')); +} else { + console.log(`[init-db] parts table already has ${partsCount} rows — skipping seed.`); +} + +db.close(); +console.log('[init-db] done.'); diff --git a/src/app.html b/src/app.html new file mode 100644 index 0000000..c5c07fd --- /dev/null +++ b/src/app.html @@ -0,0 +1,13 @@ + + + + + + + AvtoAmbor + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/src/hooks.server.js b/src/hooks.server.js new file mode 100644 index 0000000..b3a79c8 --- /dev/null +++ b/src/hooks.server.js @@ -0,0 +1,10 @@ +import { getDb } from '$lib/server/db.js'; + +// Open (and warm) the database on server startup so the first request +// doesn't pay the cost. +getDb(); + +/** @type {import('@sveltejs/kit').Handle} */ +export async function handle({ event, resolve }) { + return resolve(event); +} diff --git a/src/lib/components/Header.svelte b/src/lib/components/Header.svelte new file mode 100644 index 0000000..9caf0e2 --- /dev/null +++ b/src/lib/components/Header.svelte @@ -0,0 +1,98 @@ + + +
+ + + {#if lang === 'tg'}АвтоАмбор{:else}AvtoAmbor{/if} + + {$t('app.tagline')} + + + + + +
+ + diff --git a/src/lib/i18n/en.json b/src/lib/i18n/en.json new file mode 100644 index 0000000..47725a8 --- /dev/null +++ b/src/lib/i18n/en.json @@ -0,0 +1,113 @@ +{ + "app": { + "name": "AvtoAmbor", + "tagline": "Auto parts inventory" + }, + "nav": { + "dashboard": "Dashboard", + "parts": "Parts", + "movements": "Movements", + "suppliers": "Suppliers", + "new_part": "New part", + "new_movement": "Record movement" + }, + "lang": { + "switch_to_tg": "Тоҷикӣ", + "switch_to_en": "English" + }, + "common": { + "save": "Save", + "cancel": "Cancel", + "delete": "Delete", + "search": "Search", + "clear": "Clear", + "actions": "Actions", + "back": "Back", + "yes": "Yes", + "no": "No", + "loading": "Loading…", + "none": "—", + "edit": "Edit", + "add": "Add", + "submit": "Submit", + "created": "Created", + "updated": "Updated", + "value": "Value", + "total": "Total", + "currency_short": "TJS", + "missing_translation": "(missing translation)" + }, + "dashboard": { + "title": "Dashboard", + "total_skus": "Total SKUs", + "low_stock": "At or below reorder level", + "inventory_value": "Inventory value (at cost)", + "low_stock_list": "Low stock", + "recent_movements": "Recent movements", + "quick_actions": "Quick actions" + }, + "parts": { + "title": "Parts", + "new": "New part", + "edit": "Edit part", + "sku": "SKU", + "name": "Name", + "name_en": "Name (English)", + "name_tg": "Name (Tajik)", + "description": "Description", + "description_en": "Description (English)", + "description_tg": "Description (Tajik)", + "category": "Category", + "unit": "Unit", + "cost_price": "Cost price", + "sale_price": "Sale price", + "quantity_on_hand": "On hand", + "reorder_level": "Reorder level", + "location": "Location", + "barcode": "Barcode", + "active": "Active", + "search_placeholder": "Search by SKU, name, or barcode…", + "no_results": "No parts match your search.", + "recent_movements": "Recent movements", + "initial_quantity": "Initial quantity", + "errors": { + "sku_required": "SKU is required.", + "name_required": "At least one name (English or Tajik) is required.", + "sku_taken": "That SKU is already used." + } + }, + "movements": { + "title": "Stock movements", + "new": "Record movement", + "type": "Type", + "type_in": "In (receive)", + "type_out": "Out (sale / use)", + "type_adjust": "Adjust (set on-hand)", + "part": "Part", + "quantity": "Quantity", + "unit_price": "Unit price", + "supplier": "Supplier", + "reference": "Reference", + "notes": "Notes", + "created_at": "When", + "no_movements": "No movements recorded yet.", + "errors": { + "part_required": "Pick a part.", + "quantity_required": "Quantity must be a positive whole number.", + "not_enough_stock": "Not enough stock on hand." + } + }, + "suppliers": { + "title": "Suppliers", + "name": "Name", + "phone": "Phone", + "address": "Address", + "notes": "Notes", + "add": "Add supplier", + "no_suppliers": "No suppliers yet.", + "delete_confirm": "Delete this supplier?", + "errors": { + "name_required": "Supplier name is required." + } + } +} diff --git a/src/lib/i18n/store.js b/src/lib/i18n/store.js new file mode 100644 index 0000000..28b2d40 --- /dev/null +++ b/src/lib/i18n/store.js @@ -0,0 +1,87 @@ +import { writable, derived } from 'svelte/store'; +import { browser } from '$app/environment'; +import en from './en.json'; +import tg from './tg.json'; + +const DICTS = { en, tg }; +const STORAGE_KEY = 'avtoambor.locale'; +const DEFAULT_LOCALE = 'tg'; + +// Warn at most once per missing key, so the console doesn't flood. +const _warned = new Set(); + +function lookup(dict, key) { + const parts = key.split('.'); + let v = dict; + for (const p of parts) { + if (v == null) return undefined; + v = v[p]; + } + return v; +} + +export const locale = writable( + (browser && localStorage.getItem(STORAGE_KEY)) || DEFAULT_LOCALE +); + +if (browser) { + locale.subscribe((value) => { + try { localStorage.setItem(STORAGE_KEY, value); } catch { /* ignore */ } + document.documentElement.setAttribute('lang', value); + }); +} + +export const t = derived(locale, ($locale) => { + return (key) => { + const primary = lookup(DICTS[$locale], key); + if (primary != null) return primary; + const fallback = lookup(DICTS.en, key); + if (fallback != null) { + if (!_warned.has(key)) { + _warned.add(key); + console.warn(`[i18n] missing "${key}" for locale "${$locale}"; using English.`); + } + return fallback; + } + if (!_warned.has(key)) { + _warned.add(key); + console.warn(`[i18n] missing key "${key}"`); + } + return key; + }; +}); + +export function toggleLocale() { + locale.update((v) => (v === 'en' ? 'tg' : 'en')); +} + +// Pick the right column from a record that has both _en and _tg fields, +// e.g. localized(part, 'name', $locale) → part.name_tg or part.name_en. +// Falls back to whichever language has content (not just English) so a +// TG-only entry still renders for an EN viewer. +export function localized(record, baseField, lang) { + if (!record) return ''; + return ( + record[`${baseField}_${lang}`] || + record[`${baseField}_en`] || + record[`${baseField}_tg`] || + '' + ); +} + +// True if the record has a non-empty value in the requested language. +// Used to flag "(missing translation)" when we had to fall back. +export function hasTranslation(record, baseField, lang) { + if (!record) return false; + const v = record[`${baseField}_${lang}`]; + return v != null && String(v).trim() !== ''; +} + +// Money helpers: dirams ↔ display string. +export function formatMoney(dirams, lang = 'en') { + if (dirams == null) return ''; + const n = Number(dirams) / 100; + // Tajik uses comma decimal separator in everyday use; English uses period. + const s = n.toFixed(2); + return lang === 'tg' ? s.replace('.', ',') : s; +} diff --git a/src/lib/i18n/tg.json b/src/lib/i18n/tg.json new file mode 100644 index 0000000..a1f2fce --- /dev/null +++ b/src/lib/i18n/tg.json @@ -0,0 +1,113 @@ +{ + "app": { + "name": "AvtoAmbor", + "tagline": "Захираи қисмҳои эҳтиётии мошин" + }, + "nav": { + "dashboard": "Лавҳаи асосӣ", + "parts": "Қисмҳо", + "movements": "Ҳаракатҳо", + "suppliers": "Таъминкунандагон", + "new_part": "Қисми нав", + "new_movement": "Сабти ҳаракат" + }, + "lang": { + "switch_to_tg": "Тоҷикӣ", + "switch_to_en": "English" + }, + "common": { + "save": "Захира", + "cancel": "Бекор", + "delete": "Нест кардан", + "search": "Ҷустуҷӯ", + "clear": "Пок кардан", + "actions": "Амалҳо", + "back": "Бозгашт", + "yes": "Ҳа", + "no": "Не", + "loading": "Боркунӣ…", + "none": "—", + "edit": "Тағйир додан", + "add": "Илова", + "submit": "Тасдиқ", + "created": "Сохта шуд", + "updated": "Нав карда шуд", + "value": "Арзиш", + "total": "Ҳамагӣ", + "currency_short": "сом.", + "missing_translation": "(тарҷума нест)" + }, + "dashboard": { + "title": "Лавҳаи асосӣ", + "total_skus": "Ҳамаи SKU-ҳо", + "low_stock": "Дар сатҳи фармоиш ё камтар", + "inventory_value": "Арзиши захира (бо нархи харид)", + "low_stock_list": "Захираи кам", + "recent_movements": "Ҳаракатҳои охирин", + "quick_actions": "Амалҳои тез" + }, + "parts": { + "title": "Қисмҳо", + "new": "Қисми нав", + "edit": "Тағйири қисм", + "sku": "SKU", + "name": "Ном", + "name_en": "Ном (англисӣ)", + "name_tg": "Ном (тоҷикӣ)", + "description": "Тавсиф", + "description_en": "Тавсиф (англисӣ)", + "description_tg": "Тавсиф (тоҷикӣ)", + "category": "Категория", + "unit": "Воҳид", + "cost_price": "Нархи харид", + "sale_price": "Нархи фурӯш", + "quantity_on_hand": "Дар анбор", + "reorder_level": "Сатҳи фармоиш", + "location": "Ҷой", + "barcode": "Штрих-код", + "active": "Фаъол", + "search_placeholder": "Ҷустуҷӯ аз рӯи SKU, ном ё штрих-код…", + "no_results": "Ҳеҷ қисм мувофиқат намекунад.", + "recent_movements": "Ҳаракатҳои охирин", + "initial_quantity": "Шумораи аввала", + "errors": { + "sku_required": "SKU зарур аст.", + "name_required": "Ҳадди ақалл як ном (англисӣ ё тоҷикӣ) зарур аст.", + "sku_taken": "Ин SKU аллакай истифода шудааст." + } + }, + "movements": { + "title": "Ҳаракатҳои захира", + "new": "Сабти ҳаракат", + "type": "Намуд", + "type_in": "Воридот (қабул)", + "type_out": "Содирот (фурӯш / истеъмол)", + "type_adjust": "Танзим (миқдор гузоштан)", + "part": "Қисм", + "quantity": "Миқдор", + "unit_price": "Нархи воҳид", + "supplier": "Таъминкунанда", + "reference": "Рамзи ҳуҷҷат", + "notes": "Эзоҳ", + "created_at": "Сана", + "no_movements": "Ҳоло ҳаракате сабт нашудааст.", + "errors": { + "part_required": "Қисмро интихоб кунед.", + "quantity_required": "Миқдор бояд бутун ва мусбат бошад.", + "not_enough_stock": "Дар анбор миқдори кофӣ нест." + } + }, + "suppliers": { + "title": "Таъминкунандагон", + "name": "Ном", + "phone": "Телефон", + "address": "Суроға", + "notes": "Эзоҳ", + "add": "Илова кардани таъминкунанда", + "no_suppliers": "Ҳоло таъминкунандае нест.", + "delete_confirm": "Ин таъминкунандаро нест мекунед?", + "errors": { + "name_required": "Номи таъминкунанда зарур аст." + } + } +} diff --git a/src/lib/server/db.js b/src/lib/server/db.js new file mode 100644 index 0000000..04d718e --- /dev/null +++ b/src/lib/server/db.js @@ -0,0 +1,23 @@ +import Database from 'better-sqlite3'; +import { fileURLToPath } from 'node:url'; +import { dirname, resolve } from 'node:path'; +import { mkdirSync, existsSync } from 'node:fs'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +// data/ lives at the repo root regardless of where the server is launched from. +const DB_DIR = resolve(__dirname, '../../../data'); +const DB_PATH = resolve(DB_DIR, 'avtoambor.db'); + +let _db; + +export function getDb() { + if (_db) return _db; + if (!existsSync(DB_DIR)) mkdirSync(DB_DIR, { recursive: true }); + _db = new Database(DB_PATH); + _db.pragma('journal_mode = WAL'); + _db.pragma('foreign_keys = ON'); + return _db; +} + +export const DB_FILE = DB_PATH; diff --git a/src/lib/server/movements.js b/src/lib/server/movements.js new file mode 100644 index 0000000..158d387 --- /dev/null +++ b/src/lib/server/movements.js @@ -0,0 +1,90 @@ +import { getDb } from './db.js'; + +/** + * Record a stock movement and update parts.quantity_on_hand atomically. + * Returns the new on-hand quantity. + * + * type: 'in' | 'out' | 'adjust' + * quantity: positive integer + * - 'in': adds to on-hand + * - 'out': subtracts from on-hand (stored as negative) + * - 'adjust': sets on-hand to exactly this number (delta stored) + */ +export function recordMovement(input) { + const db = getDb(); + const partId = Number(input.part_id); + const type = input.movement_type; + const qty = Math.abs(Number(input.quantity || 0)); + const unitPrice = toDirams(input.unit_price); + const supplierId = input.supplier_id ? Number(input.supplier_id) : null; + const reference = input.reference?.trim() || null; + const notes = input.notes?.trim() || null; + + if (!partId) throw new Error('part_id required'); + if (!['in','out','adjust'].includes(type)) throw new Error('invalid movement_type'); + if (!Number.isInteger(qty) || qty < 0) throw new Error('quantity must be a non-negative integer'); + + const tx = db.transaction(() => { + const part = db.prepare(`SELECT id, quantity_on_hand FROM parts WHERE id = ?`).get(partId); + if (!part) throw new Error(`part ${partId} not found`); + + let storedQty; // what we save in stock_movements.quantity + let newOnHand; + if (type === 'in') { + storedQty = qty; + newOnHand = part.quantity_on_hand + qty; + } else if (type === 'out') { + if (qty > part.quantity_on_hand) { + throw new Error(`not enough stock (have ${part.quantity_on_hand}, need ${qty})`); + } + storedQty = -qty; + newOnHand = part.quantity_on_hand - qty; + } else { // adjust → qty is the new total + storedQty = qty - part.quantity_on_hand; + newOnHand = qty; + } + + db.prepare(` + INSERT INTO stock_movements + (part_id, movement_type, quantity, unit_price, supplier_id, reference, notes) + VALUES (?, ?, ?, ?, ?, ?, ?) + `).run(partId, type, storedQty, unitPrice, supplierId, reference, notes); + + db.prepare(` + UPDATE parts SET quantity_on_hand = ?, updated_at = datetime('now') WHERE id = ? + `).run(newOnHand, partId); + + return newOnHand; + }); + + return tx(); +} + +export function recentMovementsForPart(partId, limit = 20) { + return getDb().prepare(` + SELECT m.*, s.name AS supplier_name + FROM stock_movements m + LEFT JOIN suppliers s ON s.id = m.supplier_id + WHERE m.part_id = ? + ORDER BY m.created_at DESC, m.id DESC + LIMIT ? + `).all(partId, limit); +} + +export function recentMovements(limit = 25) { + return getDb().prepare(` + SELECT m.*, p.sku, p.name_en, p.name_tg, s.name AS supplier_name + FROM stock_movements m + JOIN parts p ON p.id = m.part_id + LEFT JOIN suppliers s ON s.id = m.supplier_id + ORDER BY m.created_at DESC, m.id DESC + LIMIT ? + `).all(limit); +} + +function toDirams(value) { + if (value === '' || value == null) return null; + const num = typeof value === 'number' ? value : Number(String(value).replace(',', '.')); + if (!Number.isFinite(num)) return null; + return Math.round(num * 100); +} diff --git a/src/lib/server/parts.js b/src/lib/server/parts.js new file mode 100644 index 0000000..f7797e4 --- /dev/null +++ b/src/lib/server/parts.js @@ -0,0 +1,139 @@ +import { getDb } from './db.js'; + +// Columns the user can sort the parts list by. Anything else is ignored. +const SORTABLE = new Set([ + 'sku', 'name_en', 'name_tg', 'quantity_on_hand', + 'sale_price', 'cost_price', 'reorder_level', 'updated_at' +]); + +export function listParts({ q = '', sort = 'sku', dir = 'asc' } = {}) { + const db = getDb(); + const col = SORTABLE.has(sort) ? sort : 'sku'; + const order = dir === 'desc' ? 'DESC' : 'ASC'; + + const where = []; + const params = {}; + if (q && q.trim()) { + where.push(`(p.sku LIKE @q OR p.name_en LIKE @q OR p.name_tg LIKE @q OR p.barcode LIKE @q)`); + params.q = `%${q.trim()}%`; + } + const whereSql = where.length ? `WHERE ${where.join(' AND ')}` : ''; + + const sql = ` + SELECT p.*, c.name_en AS category_name_en, c.name_tg AS category_name_tg + FROM parts p + LEFT JOIN categories c ON c.id = p.category_id + ${whereSql} + ORDER BY ${col} ${order} + `; + return db.prepare(sql).all(params); +} + +export function getPart(id) { + return getDb().prepare(` + SELECT p.*, c.name_en AS category_name_en, c.name_tg AS category_name_tg + FROM parts p + LEFT JOIN categories c ON c.id = p.category_id + WHERE p.id = ? + `).get(id); +} + +export function getPartBySku(sku) { + return getDb().prepare(`SELECT * FROM parts WHERE sku = ?`).get(sku); +} + +export function listCategories() { + return getDb() + .prepare(`SELECT * FROM categories ORDER BY sort_order, name_en`) + .all(); +} + +export function createPart(input) { + const db = getDb(); + const stmt = db.prepare(` + INSERT INTO parts + (sku, name_en, name_tg, description_en, description_tg, + category_id, unit, cost_price, sale_price, + quantity_on_hand, reorder_level, location, barcode, active) + VALUES + (@sku, @name_en, @name_tg, @description_en, @description_tg, + @category_id, @unit, @cost_price, @sale_price, + @quantity_on_hand, @reorder_level, @location, @barcode, @active) + `); + const result = stmt.run(normalizePart(input)); + return result.lastInsertRowid; +} + +export function updatePart(id, input) { + const db = getDb(); + const stmt = db.prepare(` + UPDATE parts SET + sku = @sku, + name_en = @name_en, + name_tg = @name_tg, + description_en = @description_en, + description_tg = @description_tg, + category_id = @category_id, + unit = @unit, + cost_price = @cost_price, + sale_price = @sale_price, + reorder_level = @reorder_level, + location = @location, + barcode = @barcode, + active = @active, + updated_at = datetime('now') + WHERE id = @id + `); + // Note: quantity_on_hand is intentionally NOT editable here — it changes + // only through stock_movements. + stmt.run({ ...normalizePart(input), id }); +} + +function normalizePart(p) { + return { + sku: (p.sku || '').trim(), + name_en: (p.name_en || '').trim(), + name_tg: (p.name_tg || '').trim(), + description_en: p.description_en?.trim() || null, + description_tg: p.description_tg?.trim() || null, + category_id: p.category_id ? Number(p.category_id) : null, + unit: (p.unit || 'pcs').trim(), + cost_price: toDirams(p.cost_price), + sale_price: toDirams(p.sale_price), + quantity_on_hand: Number.isFinite(Number(p.quantity_on_hand)) ? Number(p.quantity_on_hand) : 0, + reorder_level: Number.isFinite(Number(p.reorder_level)) ? Number(p.reorder_level) : 0, + location: p.location?.trim() || null, + barcode: p.barcode?.trim() || null, + active: p.active === false || p.active === '0' || p.active === 'false' ? 0 : 1 + }; +} + +// Accepts somoni-as-string (e.g. "12.50") and returns INTEGER dirams. +function toDirams(value) { + if (value === '' || value == null) return 0; + const num = typeof value === 'number' ? value : Number(String(value).replace(',', '.')); + if (!Number.isFinite(num)) return 0; + return Math.round(num * 100); +} + +export function dashboardStats() { + const db = getDb(); + const total = db.prepare(`SELECT COUNT(*) AS n FROM parts WHERE active = 1`).get().n; + const lowStock = db.prepare(` + SELECT COUNT(*) AS n FROM parts + WHERE active = 1 AND quantity_on_hand <= reorder_level + `).get().n; + const value = db.prepare(` + SELECT COALESCE(SUM(quantity_on_hand * cost_price), 0) AS v FROM parts WHERE active = 1 + `).get().v; + return { total, lowStock, inventoryValueDirams: value }; +} + +export function lowStockParts(limit = 10) { + return getDb().prepare(` + SELECT * FROM parts + WHERE active = 1 AND quantity_on_hand <= reorder_level + ORDER BY (quantity_on_hand - reorder_level) ASC, sku ASC + LIMIT ? + `).all(limit); +} diff --git a/src/lib/server/schema.sql b/src/lib/server/schema.sql new file mode 100644 index 0000000..9ab50df --- /dev/null +++ b/src/lib/server/schema.sql @@ -0,0 +1,56 @@ +-- AvtoAmbor schema. Money is stored as INTEGER dirams (1 TJS = 100 dirams). +-- Translated fields use _en / _tg suffixes. + +CREATE TABLE IF NOT EXISTS categories ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name_en TEXT NOT NULL, + name_tg TEXT NOT NULL, + sort_order INTEGER NOT NULL DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS suppliers ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + phone TEXT, + address TEXT, + notes TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE TABLE IF NOT EXISTS parts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + sku TEXT NOT NULL UNIQUE, + name_en TEXT NOT NULL, + name_tg TEXT NOT NULL, + description_en TEXT, + description_tg TEXT, + category_id INTEGER REFERENCES categories(id) ON DELETE SET NULL, + unit TEXT NOT NULL DEFAULT 'pcs', + cost_price INTEGER NOT NULL DEFAULT 0, -- dirams + sale_price INTEGER NOT NULL DEFAULT 0, -- dirams + quantity_on_hand INTEGER NOT NULL DEFAULT 0, + reorder_level INTEGER NOT NULL DEFAULT 0, + location TEXT, + barcode TEXT, + active INTEGER NOT NULL DEFAULT 1, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE TABLE IF NOT EXISTS stock_movements ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + part_id INTEGER NOT NULL REFERENCES parts(id) ON DELETE CASCADE, + movement_type TEXT NOT NULL CHECK(movement_type IN ('in','out','adjust')), + quantity INTEGER NOT NULL, -- positive for in/adjust-up, negative for out/adjust-down + unit_price INTEGER, -- dirams; nullable for adjustments + supplier_id INTEGER REFERENCES suppliers(id) ON DELETE SET NULL, + reference TEXT, + notes TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE INDEX IF NOT EXISTS idx_parts_sku ON parts(sku); +CREATE INDEX IF NOT EXISTS idx_parts_barcode ON parts(barcode); +CREATE INDEX IF NOT EXISTS idx_parts_category ON parts(category_id); +CREATE INDEX IF NOT EXISTS idx_movements_part ON stock_movements(part_id); +CREATE INDEX IF NOT EXISTS idx_movements_created ON stock_movements(created_at); diff --git a/src/lib/server/seed.sql b/src/lib/server/seed.sql new file mode 100644 index 0000000..59395c0 --- /dev/null +++ b/src/lib/server/seed.sql @@ -0,0 +1,190 @@ +-- Seed data for AvtoAmbor. Prices are in dirams (1 TJS = 100 dirams). +-- Names are biased toward Lada / Daewoo Nexia / Opel / Toyota, which are +-- common in Tajikistan. + +INSERT INTO categories (id, name_en, name_tg, sort_order) VALUES + (1, 'Filters', 'Филтрҳо', 10), + (2, 'Brakes', 'Тормоз', 20), + (3, 'Engine', 'Муҳаррик', 30), + (4, 'Electrical', 'Барқӣ', 40), + (5, 'Fluids', 'Моеъҳо', 50), + (6, 'Belts & Hoses', 'Тасма ва шланг', 60); + +INSERT INTO suppliers (name, phone, address, notes) VALUES + ('Avtomir Dushanbe', '+992 37 221 33 44', 'Dushanbe, Rudaki ave. 112', 'General auto parts wholesale.'), + ('Nexia Parts TJ', '+992 92 700 12 34', 'Dushanbe, Korvon market row 8', 'Daewoo / Chevrolet specialist.'), + ('Vostok Auto', '+992 93 555 77 11', 'Khujand, Lenin st. 45', 'VAZ / Lada / Niva parts.'), + ('Korea Motors TJ', '+992 90 411 22 33', 'Dushanbe, Sino district', 'Korean and Japanese imports.'); + +-- Parts --------------------------------------------------------------------- + +INSERT INTO parts + (sku, name_en, name_tg, description_en, description_tg, + category_id, unit, cost_price, sale_price, + quantity_on_hand, reorder_level, location, barcode) +VALUES + -- Filters + ('FLT-LDA-OIL-01', 'Oil filter Lada 2107', 'Филтри равған Lada 2107', + 'Standard oil filter for VAZ 2101–2107 engines.', + 'Филтри равғани муқаррарӣ барои ВАЗ 2101–2107.', + 1, 'pcs', 2500, 4000, 24, 6, 'A1-01', '4607000000017'), + + ('FLT-DWO-OIL-01', 'Oil filter Daewoo Nexia', 'Филтри равған Daewoo Nexia', + 'Oil filter for Daewoo Nexia 1.5 SOHC/DOHC.', + 'Филтри равған барои Daewoo Nexia 1.5 SOHC/DOHC.', + 1, 'pcs', 2800, 4500, 18, 6, 'A1-02', '4607000000024'), + + ('FLT-LDA-AIR-01', 'Air filter Lada Niva', 'Филтри ҳаво Lada Niva', + 'Round-style air filter for Lada Niva 1.7i.', + 'Филтри ҳавои гирд барои Lada Niva 1.7i.', + 1, 'pcs', 3200, 5000, 12, 4, 'A1-03', '4607000000031'), + + ('FLT-OPL-AIR-01', 'Air filter Opel Astra H', 'Филтри ҳаво Opel Astra H', + 'Panel air filter for Opel Astra H 1.6 / 1.8.', + 'Филтри ҳавои панелӣ барои Opel Astra H 1.6 / 1.8.', + 1, 'pcs', 4500, 7000, 9, 3, 'A1-04', '4607000000048'), + + ('FLT-TYT-FUL-01', 'Fuel filter Toyota Camry', 'Филтри сӯзишворӣ Toyota Camry', + 'In-line fuel filter for Toyota Camry V30/V40.', + 'Филтри сӯзишвории трубачагӣ барои Toyota Camry V30/V40.', + 1, 'pcs', 6000, 9500, 7, 3, 'A1-05', '4607000000055'), + + ('FLT-DWO-CAB-01', 'Cabin filter Daewoo Nexia', 'Филтри салон Daewoo Nexia', + 'Cabin / pollen filter for Daewoo Nexia.', + 'Филтри салон / гардолуд барои Daewoo Nexia.', + 1, 'pcs', 3500, 5500, 14, 4, 'A1-06', '4607000000062'), + + -- Brakes + ('BRK-LDA-PAD-F', 'Front brake pads Lada 2110', 'Колодкаҳои пеши тормоз Lada 2110', + 'Front brake pad set for Lada 2110/2111/2112.', + 'Маҷмӯи колодкаҳои пеши тормоз барои Lada 2110/2111/2112.', + 2, 'set', 11000, 17000, 10, 3, 'B2-01', '4607000000079'), + + ('BRK-LDA-SHO-R', 'Rear brake shoes Lada Niva', 'Колодкаҳои қафои тормоз Lada Niva', + 'Rear drum brake shoe set for Lada Niva.', + 'Маҷмӯи колодкаҳои қафои барабаниро Lada Niva.', + 2, 'set', 9500, 15000, 8, 3, 'B2-02', '4607000000086'), + + ('BRK-DWO-DSC-F', 'Front brake disc Daewoo Nexia', 'Диски тормози пеш Daewoo Nexia', + 'Ventilated front brake disc, Daewoo Nexia.', + 'Диски тормози пеши вентилятсияшаванда, Daewoo Nexia.', + 2, 'pcs', 18000, 28000, 6, 2, 'B2-03', '4607000000093'), + + ('BRK-OPL-PAD-F', 'Front brake pads Opel Vectra B', 'Колодкаҳои пеши тормоз Opel Vectra B', + 'Front brake pad set, Opel Vectra B 1.6 / 1.8.', + 'Маҷмӯи колодкаҳои пеши тормоз, Opel Vectra B 1.6 / 1.8.', + 2, 'set', 16000, 24000, 5, 2, 'B2-04', '4607000000109'), + + ('BRK-TYT-PAD-F', 'Front brake pads Toyota Corolla','Колодкаҳои пеши тормоз Toyota Corolla', + 'Front brake pad set, Toyota Corolla E120/E150.', + 'Маҷмӯи колодкаҳои пеши тормоз, Toyota Corolla E120/E150.', + 2, 'set', 19000, 29500, 4, 2, 'B2-05', '4607000000116'), + + -- Engine + ('ENG-SPK-NGK-01', 'Spark plug NGK BPR6E', 'Шамъи оташфурӯзӣ NGK BPR6E', + 'NGK BPR6E spark plug — common for VAZ.', + 'Шамъи оташфурӯзии NGK BPR6E — барои ВАЗ.', + 3, 'pcs', 1800, 3000, 60, 12, 'C3-01', '4607000000123'), + + ('ENG-SPK-DEN-01', 'Spark plug Denso K20TT', 'Шамъи оташфурӯзӣ Denso K20TT', + 'Denso K20TT twin-tip plug, Toyota / Daewoo.', + 'Шамъи Denso K20TT, Toyota / Daewoo.', + 3, 'pcs', 3200, 5000, 36, 8, 'C3-02', '4607000000130'), + + ('ENG-LDA-PR-01', 'Piston ring set Lada 2106 STD', 'Маҷмӯи ҳалқаҳои поршен Lada 2106 STD', + 'Standard-bore piston ring set for VAZ 2106.', + 'Маҷмӯи ҳалқаҳои поршени андозаи стандартӣ барои ВАЗ 2106.', + 3, 'set', 22000, 33000, 3, 1, 'C3-03', '4607000000147'), + + ('ENG-LDA-VCG-01', 'Valve cover gasket Lada 2110', 'Прокладкаи сарпӯши клапанҳо Lada 2110', + 'Valve cover gasket for Lada 2110 16V.', + 'Прокладкаи сарпӯши клапанҳо барои Lada 2110 16V.', + 3, 'pcs', 3000, 4800, 11, 3, 'C3-04', '4607000000154'), + + ('ENG-DWO-MNT-01', 'Engine mount Daewoo Nexia', 'Подушкаи муҳаррик Daewoo Nexia', + 'Right-side engine mount for Daewoo Nexia.', + 'Подушкаи рости муҳаррик барои Daewoo Nexia.', + 3, 'pcs', 14000, 22000, 4, 2, 'C3-05', '4607000000161'), + + ('ENG-OPL-THM-01', 'Thermostat Opel Astra H', 'Термостат Opel Astra H', + 'Thermostat with housing, Opel Astra H 1.6.', + 'Термостат бо корпус, Opel Astra H 1.6.', + 3, 'pcs', 17000, 26000, 3, 1, 'C3-06', '4607000000178'), + + ('ENG-DWO-WPM-01', 'Water pump Daewoo Nexia', 'Насоси об Daewoo Nexia', + 'Coolant water pump, Daewoo Nexia 1.5.', + 'Насоси хунуккунӣ, Daewoo Nexia 1.5.', + 3, 'pcs', 25000, 38000, 3, 1, 'C3-07', '4607000000185'), + + -- Electrical + ('ELC-LDA-STR-01', 'Starter motor Lada 2107', 'Стартери Lada 2107', + 'Reduction-gear starter for Lada 2107 carb / inj.', + 'Стартери редукторӣ барои Lada 2107.', + 4, 'pcs', 65000, 95000, 2, 1, 'D4-01', '4607000000192'), + + ('ELC-DWO-ALT-01', 'Alternator 14V 80A Daewoo Nexia','Генератори 14V 80A Daewoo Nexia', + '14V 80A alternator, Daewoo Nexia 1.5.', + 'Генератори 14V 80A, Daewoo Nexia 1.5.', + 4, 'pcs', 95000,140000, 2, 1, 'D4-02', '4607000000208'), + + ('ELC-BAT-60AH-01','Car battery 60Ah 12V', 'Батареяи мошин 60Ач 12В', + 'Maintenance-free 60Ah 12V battery, 500A CCA.', + 'Батареяи бе нигоҳдории 60Ач 12В, 500А CCA.', + 4, 'pcs', 55000, 78000, 6, 2, 'D4-03', '4607000000215'), + + ('ELC-H4-12V-01', 'Headlamp bulb H4 12V 60/55W', 'Лампаи фара H4 12V 60/55Вт', + 'Halogen H4 headlamp bulb 12V 60/55W.', + 'Лампаи галогении H4 барои фара 12V 60/55Вт.', + 4, 'pcs', 1200, 2200, 40, 10, 'D4-04', '4607000000222'), + + ('ELC-TYT-COL-01', 'Ignition coil Toyota Corolla', 'Ғалтаки оташфурӯзӣ Toyota Corolla', + 'Pencil-type ignition coil, Toyota Corolla 1.6 VVT-i.', + 'Ғалтаки оташфурӯзии қаламшакл, Toyota Corolla 1.6 VVT-i.', + 4, 'pcs', 28000, 42000, 4, 2, 'D4-05', '4607000000239'), + + -- Fluids + ('FLD-OIL-5W40-4L','Engine oil 5W-40 synthetic 4L', 'Равғани муҳаррик 5W-40 синтетикӣ 4Л', + 'Fully synthetic 5W-40 motor oil, 4L jug.', + 'Равғани муҳаррики пурра синтетикии 5W-40, 4 литр.', + 5, 'btl', 14000, 21000, 15, 4, 'E5-01', '4607000000246'), + + ('FLD-OIL-10W40-4L','Engine oil 10W-40 semi-syn 4L', 'Равғани муҳаррик 10W-40 нимсинтетикӣ 4Л', + 'Semi-synthetic 10W-40 motor oil, 4L jug.', + 'Равғани муҳаррики нимсинтетикии 10W-40, 4 литр.', + 5, 'btl', 9500, 15000, 22, 6, 'E5-02', '4607000000253'), + + ('FLD-BRK-DOT4-1L','Brake fluid DOT-4 1L', 'Моеъи тормоз DOT-4 1Л', + 'DOT-4 brake fluid, 1L bottle.', + 'Моеъи тормози DOT-4, шишаи 1 литр.', + 5, 'btl', 2200, 3800, 20, 6, 'E5-03', '4607000000260'), + + ('FLD-AFR-G11-5L', 'Antifreeze G11 green 5L', 'Антифриз G11 сабз 5Л', + 'G11 (green) antifreeze concentrate, 5L.', + 'Антифризи G11 (сабз), консентрат, 5 литр.', + 5, 'btl', 6800, 10500, 10, 3, 'E5-04', '4607000000277'), + + -- Belts & Hoses + ('BLT-DWO-TIM-01', 'Timing belt Daewoo Nexia 8V', 'Тасмаи газораспределение Daewoo Nexia 8V', + 'Timing belt for Daewoo Nexia 1.5 SOHC (8V).', + 'Тасмаи газораспределение барои Daewoo Nexia 1.5 SOHC (8V).', + 6, 'pcs', 5500, 9000, 8, 3, 'F6-01', '4607000000284'), + + ('BLT-LDA-VBL-01', 'V-belt Lada 2107 alternator', 'Тасмаи V Lada 2107 (генератор)', + 'V-belt for alternator, Lada 2107 carb engine.', + 'Тасмаи V барои генератор, муҳаррики карбюратории Lada 2107.', + 6, 'pcs', 1800, 3000, 20, 5, 'F6-02', '4607000000291'), + + ('HOS-LDA-RUP-01', 'Radiator upper hose Lada 2107', 'Шланги болоии радиатор Lada 2107', + 'Upper radiator hose, Lada 2107.', + 'Шланги болоии радиатор, Lada 2107.', + 6, 'pcs', 2400, 3900, 12, 4, 'F6-03', '4607000000307'), + + ('HOS-DWO-RLW-01', 'Radiator lower hose Daewoo Nexia','Шланги поёнии радиатор Daewoo Nexia', + 'Lower radiator hose, Daewoo Nexia.', + 'Шланги поёнии радиатор, Daewoo Nexia.', + 6, 'pcs', 3000, 4800, 9, 3, 'F6-04', '4607000000314'); + +-- A couple of opening "in" stock movements so the dashboard isn't empty. +INSERT INTO stock_movements (part_id, movement_type, quantity, unit_price, supplier_id, reference, notes) +SELECT id, 'in', quantity_on_hand, cost_price, 1, 'OPENING', 'Initial seeded stock' +FROM parts WHERE quantity_on_hand > 0; diff --git a/src/lib/server/suppliers.js b/src/lib/server/suppliers.js new file mode 100644 index 0000000..29faabd --- /dev/null +++ b/src/lib/server/suppliers.js @@ -0,0 +1,27 @@ +import { getDb } from './db.js'; + +export function listSuppliers() { + return getDb().prepare(`SELECT * FROM suppliers ORDER BY name`).all(); +} + +export function getSupplier(id) { + return getDb().prepare(`SELECT * FROM suppliers WHERE id = ?`).get(id); +} + +export function createSupplier(input) { + const stmt = getDb().prepare(` + INSERT INTO suppliers (name, phone, address, notes) + VALUES (@name, @phone, @address, @notes) + `); + const result = stmt.run({ + name: (input.name || '').trim(), + phone: input.phone?.trim() || null, + address: input.address?.trim() || null, + notes: input.notes?.trim() || null + }); + return result.lastInsertRowid; +} + +export function deleteSupplier(id) { + getDb().prepare(`DELETE FROM suppliers WHERE id = ?`).run(id); +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 0000000..2f9fdd7 --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,108 @@ + + +
+ +
+ +
+ + diff --git a/src/routes/+page.server.js b/src/routes/+page.server.js new file mode 100644 index 0000000..801db64 --- /dev/null +++ b/src/routes/+page.server.js @@ -0,0 +1,10 @@ +import { dashboardStats, lowStockParts } from '$lib/server/parts.js'; +import { recentMovements } from '$lib/server/movements.js'; + +export function load() { + return { + stats: dashboardStats(), + lowStock: lowStockParts(10), + movements: recentMovements(10) + }; +} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte new file mode 100644 index 0000000..bc1888d --- /dev/null +++ b/src/routes/+page.svelte @@ -0,0 +1,109 @@ + + +

{$t('dashboard.title')}

+ +
+
+
{$t('dashboard.total_skus')}
+
{stats.total}
+
+
+
{$t('dashboard.low_stock')}
+
0}>{stats.lowStock}
+
+
+
{$t('dashboard.inventory_value')}
+
+ {formatMoney(stats.inventoryValueDirams, lang)} + {$t('common.currency_short')} +
+
+
+ +
+ {$t('dashboard.quick_actions')} + {$t('nav.new_part')} + {$t('nav.new_movement')} + {$t('nav.parts')} +
+ +

{$t('dashboard.low_stock_list')}

+{#if lowStock.length === 0} +

{$t('common.none')}

+{:else} + + + + + + + + + + + {#each lowStock as p} + + + + + + + {/each} + +
{$t('parts.sku')}{$t('parts.name')}{$t('parts.quantity_on_hand')}{$t('parts.reorder_level')}
{p.sku}{localized(p, 'name', lang)}{p.quantity_on_hand}{p.reorder_level}
+{/if} + +

{$t('dashboard.recent_movements')}

+{#if movements.length === 0} +

{$t('movements.no_movements')}

+{:else} + + + + + + + + + + + + {#each movements as m} + + + + + + + + {/each} + +
{$t('movements.created_at')}{$t('movements.type')}{$t('parts.sku')}{$t('parts.name')}{$t('movements.quantity')}
{m.created_at}{$t('movements.type_' + m.movement_type)}{m.sku}{localized(m, 'name', lang)}{m.quantity}
+{/if} + + diff --git a/src/routes/movements/new/+page.server.js b/src/routes/movements/new/+page.server.js new file mode 100644 index 0000000..81892a4 --- /dev/null +++ b/src/routes/movements/new/+page.server.js @@ -0,0 +1,39 @@ +import { fail, redirect } from '@sveltejs/kit'; +import { listParts } from '$lib/server/parts.js'; +import { listSuppliers } from '$lib/server/suppliers.js'; +import { recordMovement } from '$lib/server/movements.js'; + +export function load({ url }) { + return { + parts: listParts(), + suppliers: listSuppliers(), + presetPartId: url.searchParams.get('part_id') || '' + }; +} + +export const actions = { + default: async ({ request }) => { + const form = await request.formData(); + const data = Object.fromEntries(form); + + const errors = {}; + if (!data.part_id) errors.part_id = 'movements.errors.part_required'; + const qty = Number(data.quantity); + if (!Number.isInteger(qty) || qty < 0) errors.quantity = 'movements.errors.quantity_required'; + // 'in' or 'adjust' may legitimately use 0 (e.g. adjust to zero), so we + // only block negative or non-integer; 'out' requires > 0. + if (data.movement_type === 'out' && qty <= 0) errors.quantity = 'movements.errors.quantity_required'; + if (Object.keys(errors).length) return fail(400, { errors, values: data }); + + try { + recordMovement(data); + } catch (err) { + if (String(err.message).includes('not enough stock')) { + return fail(400, { errors: { quantity: 'movements.errors.not_enough_stock' }, values: data }); + } + throw err; + } + + throw redirect(303, `/parts/${data.part_id}`); + } +}; diff --git a/src/routes/movements/new/+page.svelte b/src/routes/movements/new/+page.svelte new file mode 100644 index 0000000..43e1da1 --- /dev/null +++ b/src/routes/movements/new/+page.svelte @@ -0,0 +1,168 @@ + + +

{$t('movements.new')}

+ +
+ + + + + + + {#if movementType !== 'adjust'} + + {/if} + + {#if movementType === 'in'} + + {/if} + +
+ + +
+ +
+ + {$t('common.cancel')} +
+
+ + diff --git a/src/routes/parts/+page.server.js b/src/routes/parts/+page.server.js new file mode 100644 index 0000000..7217237 --- /dev/null +++ b/src/routes/parts/+page.server.js @@ -0,0 +1,8 @@ +import { listParts } from '$lib/server/parts.js'; + +export function load({ url }) { + const q = url.searchParams.get('q') ?? ''; + const sort = url.searchParams.get('sort') ?? 'sku'; + const dir = url.searchParams.get('dir') ?? 'asc'; + return { parts: listParts({ q, sort, dir }), q, sort, dir }; +} diff --git a/src/routes/parts/+page.svelte b/src/routes/parts/+page.svelte new file mode 100644 index 0000000..0c93d80 --- /dev/null +++ b/src/routes/parts/+page.svelte @@ -0,0 +1,126 @@ + + +
+

{$t('parts.title')}

+ + {$t('nav.new_part')} +
+ + + +{#if parts.length === 0} +

{$t('parts.no_results')}

+{:else} + + + + + + + + + + + + + {#each parts as p} + + + + + + + + + {/each} + +
{$t('parts.category')}
{p.sku} + {localized(p, 'name', lang)} + {#if !hasTranslation(p, 'name', lang)} + {$t('common.missing_translation')} + {/if} + {localized({name_en: p.category_name_en, name_tg: p.category_name_tg}, 'name', lang) || $t('common.none')} + {#if p.quantity_on_hand <= p.reorder_level} + {p.quantity_on_hand} + {:else} + {p.quantity_on_hand} + {/if} + {p.reorder_level}{formatMoney(p.sale_price, lang)}
+{/if} + + diff --git a/src/routes/parts/[id]/+page.server.js b/src/routes/parts/[id]/+page.server.js new file mode 100644 index 0000000..904aa46 --- /dev/null +++ b/src/routes/parts/[id]/+page.server.js @@ -0,0 +1,34 @@ +import { error, fail, redirect } from '@sveltejs/kit'; +import { getPart, getPartBySku, listCategories, updatePart } from '$lib/server/parts.js'; +import { recentMovementsForPart } from '$lib/server/movements.js'; + +export function load({ params }) { + const id = Number(params.id); + const part = getPart(id); + if (!part) throw error(404, 'Part not found'); + return { + part, + categories: listCategories(), + movements: recentMovementsForPart(id, 25) + }; +} + +export const actions = { + default: async ({ request, params }) => { + const id = Number(params.id); + const form = await request.formData(); + const data = Object.fromEntries(form); + + const errors = {}; + if (!data.sku || !data.sku.trim()) errors.sku = 'parts.errors.sku_required'; + if ((!data.name_en || !data.name_en.trim()) && (!data.name_tg || !data.name_tg.trim())) { + errors.name = 'parts.errors.name_required'; + } + const existing = getPartBySku(data.sku.trim()); + if (existing && existing.id !== id) errors.sku = 'parts.errors.sku_taken'; + if (Object.keys(errors).length) return fail(400, { errors, values: data }); + + updatePart(id, data); + throw redirect(303, `/parts/${id}`); + } +}; diff --git a/src/routes/parts/[id]/+page.svelte b/src/routes/parts/[id]/+page.svelte new file mode 100644 index 0000000..60c1c8c --- /dev/null +++ b/src/routes/parts/[id]/+page.svelte @@ -0,0 +1,195 @@ + + +
+

{$t('parts.edit')}: {part.sku}

+ ← {$t('common.back')} +
+ +{#if errors.name} +
{$t(errors.name)}
+{/if} + +
+
+
+ + +
+ + +
+ + + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + + +
+ + + {$t('nav.new_movement')} +
+
+
+ + +
+ + diff --git a/src/routes/parts/new/+page.server.js b/src/routes/parts/new/+page.server.js new file mode 100644 index 0000000..1ea43b8 --- /dev/null +++ b/src/routes/parts/new/+page.server.js @@ -0,0 +1,46 @@ +import { fail, redirect } from '@sveltejs/kit'; +import { createPart, getPartBySku, listCategories } from '$lib/server/parts.js'; +import { recordMovement } from '$lib/server/movements.js'; + +export function load() { + return { categories: listCategories() }; +} + +export const actions = { + default: async ({ request }) => { + const form = await request.formData(); + const data = Object.fromEntries(form); + const errors = validate(data); + if (errors) return fail(400, { errors, values: data }); + + if (getPartBySku(data.sku.trim())) { + return fail(400, { errors: { sku: 'parts.errors.sku_taken' }, values: data }); + } + + // Save the part with quantity 0, then record an opening "in" movement + // if the user supplied an initial quantity. This keeps quantity changes + // funneled exclusively through stock_movements. + const initialQty = Number(data.quantity_on_hand || 0); + const id = createPart({ ...data, quantity_on_hand: 0 }); + if (initialQty > 0) { + recordMovement({ + part_id: id, + movement_type: 'in', + quantity: initialQty, + unit_price: data.cost_price, + reference: 'OPENING' + }); + } + + throw redirect(303, `/parts/${id}`); + } +}; + +function validate(d) { + const errors = {}; + if (!d.sku || !d.sku.trim()) errors.sku = 'parts.errors.sku_required'; + if ((!d.name_en || !d.name_en.trim()) && (!d.name_tg || !d.name_tg.trim())) { + errors.name = 'parts.errors.name_required'; + } + return Object.keys(errors).length ? errors : null; +} diff --git a/src/routes/parts/new/+page.svelte b/src/routes/parts/new/+page.svelte new file mode 100644 index 0000000..8680459 --- /dev/null +++ b/src/routes/parts/new/+page.svelte @@ -0,0 +1,108 @@ + + +

{$t('parts.new')}

+ +{#if errors.name} +
{$t(errors.name)}
+{/if} + +
+ + +
+ + +
+ + + +
+ + +
+ +
+ + +
+ +
+ + +
+ + + +
+ + +
+ +
+ + {$t('common.cancel')} +
+
+ + diff --git a/src/routes/suppliers/+page.server.js b/src/routes/suppliers/+page.server.js new file mode 100644 index 0000000..445b0af --- /dev/null +++ b/src/routes/suppliers/+page.server.js @@ -0,0 +1,24 @@ +import { fail } from '@sveltejs/kit'; +import { listSuppliers, createSupplier, deleteSupplier } from '$lib/server/suppliers.js'; + +export function load() { + return { suppliers: listSuppliers() }; +} + +export const actions = { + create: async ({ request }) => { + const form = await request.formData(); + const data = Object.fromEntries(form); + if (!data.name || !data.name.trim()) { + return fail(400, { errors: { name: 'suppliers.errors.name_required' }, values: data }); + } + createSupplier(data); + return { ok: true }; + }, + delete: async ({ request }) => { + const form = await request.formData(); + const id = Number(form.get('id')); + if (id) deleteSupplier(id); + return { ok: true }; + } +}; diff --git a/src/routes/suppliers/+page.svelte b/src/routes/suppliers/+page.svelte new file mode 100644 index 0000000..b61b129 --- /dev/null +++ b/src/routes/suppliers/+page.svelte @@ -0,0 +1,75 @@ + + +

{$t('suppliers.title')}

+ +{#if suppliers.length === 0} +

{$t('suppliers.no_suppliers')}

+{:else} + + + + + + + + + + + + {#each suppliers as s} + + + + + + + + {/each} + +
{$t('suppliers.name')}{$t('suppliers.phone')}{$t('suppliers.address')}{$t('suppliers.notes')}
{s.name}{s.phone || $t('common.none')}{s.address || $t('common.none')}{s.notes || $t('common.none')} +
{ if (!confirm($t('suppliers.delete_confirm'))) e.preventDefault(); }}> + + +
+
+{/if} + +

{$t('suppliers.add')}

+
+ +
+ + +
+ +
+ +
+
+ + diff --git a/svelte.config.js b/svelte.config.js new file mode 100644 index 0000000..780a781 --- /dev/null +++ b/svelte.config.js @@ -0,0 +1,10 @@ +import adapter from '@sveltejs/adapter-node'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: { + adapter: adapter({ out: 'build' }) + } +}; + +export default config; diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..fcd2d8c --- /dev/null +++ b/vite.config.js @@ -0,0 +1,24 @@ +import { sveltekit } from '@sveltejs/kit/vite'; + +/** @type {import('vite').UserConfig} */ +const config = { + plugins: [sveltekit()], + server: { + host: '0.0.0.0', + port: 5173, + strictPort: true, + watch: { + usePolling: true + } + }, + // better-sqlite3 is native — keep it out of Vite's optimizer + ssr: { + noExternal: [], + external: ['better-sqlite3'] + }, + optimizeDeps: { + exclude: ['better-sqlite3'] + } +}; + +export default config;