diff --git a/.github/workflows/supplier-intake.yml b/.github/workflows/supplier-intake.yml new file mode 100644 index 00000000..d1e724b1 --- /dev/null +++ b/.github/workflows/supplier-intake.yml @@ -0,0 +1,25 @@ +name: Supplier intake +on: + pull_request: + paths: ['applications/supplier-intake/**', '.github/workflows/supplier-intake.yml'] + push: + paths: ['applications/supplier-intake/**', '.github/workflows/supplier-intake.yml'] +permissions: + contents: read +jobs: + native: + runs-on: ubuntu-latest + defaults: + run: + working-directory: applications/supplier-intake + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: applications/supplier-intake/.node-version + cache: npm + cache-dependency-path: applications/supplier-intake/package-lock.json + - run: npm ci --ignore-scripts + - run: npm test + - run: npm run check + - run: npm run build diff --git a/applications/supplier-intake/.env.example b/applications/supplier-intake/.env.example new file mode 100644 index 00000000..af06c6e9 --- /dev/null +++ b/applications/supplier-intake/.env.example @@ -0,0 +1,6 @@ +DATABASE_URL=postgresql://intake_app:YOUR_APP_PASSWORD@YOUR_DIRECT_HOST:5432/postgres +DATABASE_CA_PATH=.deployment/postgres-ca.pem +ORIGIN=http://127.0.0.1:3001 +HOST=127.0.0.1 +PORT=3001 +BODY_SIZE_LIMIT=600K diff --git a/applications/supplier-intake/.gitignore b/applications/supplier-intake/.gitignore new file mode 100644 index 00000000..909ead14 --- /dev/null +++ b/applications/supplier-intake/.gitignore @@ -0,0 +1,7 @@ +.env +.deployment/ +node_modules/ +build/ +.svelte-kit/ +test-results/ +playwright-report/ diff --git a/applications/supplier-intake/.node-version b/applications/supplier-intake/.node-version new file mode 100644 index 00000000..df6ae337 --- /dev/null +++ b/applications/supplier-intake/.node-version @@ -0,0 +1 @@ +24.21.0 diff --git a/applications/supplier-intake/.prettierignore b/applications/supplier-intake/.prettierignore new file mode 100644 index 00000000..0376a1e8 --- /dev/null +++ b/applications/supplier-intake/.prettierignore @@ -0,0 +1,7 @@ +node_modules/ +build/ +.svelte-kit/ +.deployment/ +package-lock.json +docs/ +static/ diff --git a/applications/supplier-intake/.prettierrc.json b/applications/supplier-intake/.prettierrc.json new file mode 100644 index 00000000..102b8a92 --- /dev/null +++ b/applications/supplier-intake/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "plugins": ["prettier-plugin-svelte"], + "singleQuote": true, + "printWidth": 90, + "tabWidth": 2 +} diff --git a/applications/supplier-intake/README.md b/applications/supplier-intake/README.md new file mode 100644 index 00000000..1bbbf589 --- /dev/null +++ b/applications/supplier-intake/README.md @@ -0,0 +1,293 @@ +# Supplier spreadsheet intake + +Review a supplier CSV or XLSX file before it reaches your product catalog. +**SvelteKit, Superforms, shadcn-svelte, Bits UI, Uppy, Papa Parse and SheetJS CE** +provide the upload and review flow; [ClickHouse Managed Postgres](https://clickhouse.com/cloud/postgres) +stores the batches, source values, validation results and approved products. + +**[Start a ClickHouse Cloud trial with $300 in credits](https://clickhouse.com/cloud).** + +## Receive, review, promote + +```mermaid +flowchart LR + Upload[Uppy Dashboard and XHR upload] --> Route[SvelteKit upload endpoint] + Route --> Parsers[Papa Parse CSV / SheetJS XLSX] + Parsers --> Staging[Postgres batches and staging rows] + Staging --> Review[Superforms mapping / shadcn and Bits UI review] + Review --> Promote[Locked batch transaction] + Promote --> Catalog[Approved product catalog] +``` + +The file's SHA-256 fingerprint identifies a batch. Uploading the same bytes again +opens that batch instead of creating more staging rows. Review maps the supplier's +headers to SKU, product, USD price and stock. The original values remain intact; +validated values and row errors are stored separately. + +Promotion locks the batch, checks its reviewed revision, and inserts all rows in +one transaction. A supplier/SKU conflict rolls back the complete import. A second +promotion request sees the completed batch and does not add duplicates. A stale +review cannot overwrite another tab's mapping. A read-only repeatable-read +snapshot keeps batch metadata and reviewed rows coherent during another review. + +| Integration | Actual role in the app | +| --- | --- | +| SvelteKit | Upload endpoint, server loaders and named review/promotion actions; Node adapter | +| Superforms | Mapping and supplier metadata, shared Zod 4 validation, enhanced form feedback | +| shadcn-svelte | Generated Nova Button, Badge, Input and Tabs source components | +| Bits UI | Accessible tabs underneath shadcn; direct accordion for row corrections | +| Uppy | Real file selection, restrictions, progress and multipart XHR upload | +| Papa Parse | CSV quoting and text-preserving parsing | +| SheetJS CE | Genuine XLSX worksheet input and included workbook fixture generation | + +[parsers.ts](src/lib/server/parsers.ts) normalizes both formats. +[intake.ts](src/lib/server/intake.ts) owns parameterized SQL and transactions. +[BatchReview.svelte](src/lib/components/BatchReview.svelte) shows the reviewed +mapping, unchanged source values, row errors and promotion result. + +## Prerequisites and native setup + +Build and run inside Linux or an isolated OrbStack VM. Do not install the app's +dependencies on macOS. You need Node **24.21.0**, npm, Git, `psql`, `jq`, OpenSSL +and a ClickHouse Cloud organization with Managed Postgres access. + +On an Ubuntu ARM64 VM, if the tools are not already present: + +```sh +sudo apt-get update +sudo apt-get install -y ca-certificates curl git xz-utils postgresql-client jq openssl +curl -fsSL https://nodejs.org/dist/v24.21.0/node-v24.21.0-linux-arm64.tar.xz -o /tmp/node.tar.xz +sudo tar -xJf /tmp/node.tar.xz -C /usr/local --strip-components=1 +node --version +git clone https://github.com/ClickHouse/examples.git +cd examples/applications/supplier-intake +npm ci --ignore-scripts +npm test +npm run check +npm run build +``` + +Use `linux-x64` on x86 Linux. Verify Node's archive against its +[release checksum file](https://nodejs.org/dist/v24.21.0/SHASUMS256.txt). +Native checks do not require a database. + +Pinned versions include SvelteKit **2.70.3**, Svelte **5.57.1**, adapter-node +**5.5.7**, Superforms **3.0.0**, Bits UI **2.19.5**, Uppy core **6.2.0**, +Papa Parse **5.7.0**, SheetJS CE **0.20.3** and TypeScript **6.0.3**. +The maintained Kit 2 release preserves the configuration and alias conventions +used by the shadcn-svelte 1.7.0 generator. We don't force incompatible peers to +use Kit 3. The lockfile pins transitive packages too. + +SheetJS uses the [official distribution](https://docs.sheetjs.com/docs/getting-started/installation/nodejs/), +not the older npm registry `xlsx` release. Its tarball URL and integrity are in the +lockfile. The generated shadcn components are checked in; installation does not +rerun its CLI. See [third-party notices](THIRD_PARTY_NOTICES.md) and the copied +[shadcn license](SHADCN-LICENSE.md). + +## Create Postgres with clickhousectl + +Use [clickhousectl](https://clickhouse.com/docs/interfaces/cli) on your provisioning +machine. Cloud management credentials are separate from application credentials. +Install the CLI if needed and authenticate interactively with an Admin API key: + +```sh +curl -fsSL https://clickhouse.com/cli | sh +export PATH="$HOME/.local/bin:$PATH" +clickhousectl --version +clickhousectl cloud auth login --interactive +clickhousectl cloud auth status +clickhousectl cloud org list +umask 077 +mkdir -p .deployment +``` + +Create a key using the [Cloud API guide](https://clickhouse.com/docs/cloud/manage/openapi). +In an editor, create `.deployment/resources.env` with an organization ID and an +available AWS region/size: + +```dotenv +CH_ORG_ID=YOUR_ORGANIZATION_UUID +DEPLOYMENT_NAME=supplier-intake-example +CLOUD_REGION=YOUR_AVAILABLE_REGION +PG_SIZE=YOUR_AVAILABLE_POSTGRES_SIZE +``` + +This example uses one Postgres 18 service with no HA. Check current +[pricing](https://clickhouse.com/pricing). Database charges continue until the +service is deleted; stopping Node or the VM does not stop them. + +```sh +source .deployment/resources.env +clickhousectl cloud postgres create \ + --org-id "$CH_ORG_ID" --name "$DEPLOYMENT_NAME" \ + --provider aws --region "$CLOUD_REGION" --size "$PG_SIZE" \ + --pg-version 18 --ha-type none --tag project=supplier-intake --json \ + > .deployment/postgres-create.json +PG_SERVICE_ID="$(jq -er '.id' .deployment/postgres-create.json)" +printf 'PG_SERVICE_ID=%s\n' "$PG_SERVICE_ID" >> .deployment/resources.env +``` + +The receipt contains the initial administrator password, returned once. Keep it +private. If creation is interrupted, inspect the service list before retrying. +A missing receipt does not prove creation failed. + +Repeat inspection until `state` is `running`, then download its CA: + +```sh +clickhousectl cloud postgres get "$PG_SERVICE_ID" \ + --org-id "$CH_ORG_ID" --json > .deployment/postgres-status.json +jq '{id,state,hostname,username,postgresVersion,size}' .deployment/postgres-status.json +clickhousectl cloud postgres certs get "$PG_SERVICE_ID" \ + --org-id "$CH_ORG_ID" --output .deployment/postgres-ca.pem +``` + +If provisioning runs outside the VM, transfer only the private database receipt +and CA into the VM's app `.deployment/` directory. Keep Cloud API keys outside +the application. Continue the following steps in that app directory in Linux. + +## Roles, schema and seed + +Generate separate migration and runtime passwords once and preserve them for +setup retries. Generated hex passwords are safe in the connection URL: + +```sh +umask 077 +cat > .deployment/passwords.env < .env < +Copyright (c) 2023 CokaKoala +Copyright (c) 2023 shadcn + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/applications/supplier-intake/THIRD_PARTY_NOTICES.md b/applications/supplier-intake/THIRD_PARTY_NOTICES.md new file mode 100644 index 00000000..c65fd7b7 --- /dev/null +++ b/applications/supplier-intake/THIRD_PARTY_NOTICES.md @@ -0,0 +1,31 @@ +# Third-party notices + +The checked-in Button, Badge, Input and Tabs components were generated with +shadcn-svelte **1.7.0**, Nova style. shadcn-svelte is MIT licensed; its complete +copyright and permission notice is preserved in [SHADCN-LICENSE.md](SHADCN-LICENSE.md). +These source components use Bits UI primitives and the local class-name utility. + +SvelteKit, Svelte, Superforms, Bits UI, Uppy and Papa Parse are MIT licensed. +Their installed packages preserve the upstream notices. These names describe +the software used here, not an endorsement of this example. + +SheetJS Community Edition **0.20.3** comes from its official CDN tarball and is +Apache-2.0 licensed. SheetJS Pro is not included or required. No SheetJS source +files are modified. Its installed package contains the complete Apache license. + +```text +SheetJS Community Edition -- https://sheetjs.com/ +Copyright (C) 2012-present SheetJS LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` + +See the official [SheetJS CE license](https://docs.sheetjs.com/docs/miscellany/license/) +and [installation instructions](https://docs.sheetjs.com/docs/getting-started/installation/nodejs/). diff --git a/applications/supplier-intake/components.json b/applications/supplier-intake/components.json new file mode 100644 index 00000000..365a3205 --- /dev/null +++ b/applications/supplier-intake/components.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://shadcn-svelte.com/schema.json", + "style": "nova", + "tailwind": { "css": "src/styles.css", "baseColor": "stone" }, + "aliases": { "components": "$lib/components", "utils": "$lib/utils", "ui": "$lib/components/ui", "hooks": "$lib/hooks", "lib": "$lib" }, + "typescript": true, + "registry": "https://shadcn-svelte.com/registry" +} diff --git a/applications/supplier-intake/docs/csv-promoted.png b/applications/supplier-intake/docs/csv-promoted.png new file mode 100644 index 00000000..713b3a6e Binary files /dev/null and b/applications/supplier-intake/docs/csv-promoted.png differ diff --git a/applications/supplier-intake/docs/receiving-desk.png b/applications/supplier-intake/docs/receiving-desk.png new file mode 100644 index 00000000..cd3a3b2f Binary files /dev/null and b/applications/supplier-intake/docs/receiving-desk.png differ diff --git a/applications/supplier-intake/docs/row-errors-mobile.png b/applications/supplier-intake/docs/row-errors-mobile.png new file mode 100644 index 00000000..b6bf4bde Binary files /dev/null and b/applications/supplier-intake/docs/row-errors-mobile.png differ diff --git a/applications/supplier-intake/docs/xlsx-promoted.png b/applications/supplier-intake/docs/xlsx-promoted.png new file mode 100644 index 00000000..d0e4f3f8 Binary files /dev/null and b/applications/supplier-intake/docs/xlsx-promoted.png differ diff --git a/applications/supplier-intake/package-lock.json b/applications/supplier-intake/package-lock.json new file mode 100644 index 00000000..60717eef --- /dev/null +++ b/applications/supplier-intake/package-lock.json @@ -0,0 +1,4465 @@ +{ + "name": "supplier-intake", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "supplier-intake", + "dependencies": { + "@lucide/svelte": "1.52.0", + "@sveltejs/kit": "2.70.3", + "@uppy/core": "6.2.0", + "@uppy/dashboard": "6.0.1", + "@uppy/xhr-upload": "6.0.1", + "bits-ui": "2.19.5", + "clsx": "2.1.1", + "papaparse": "5.7.0", + "pg": "8.23.1", + "svelte": "5.57.1", + "sveltekit-superforms": "3.0.0", + "tailwind-merge": "3.7.0", + "tailwind-variants": "3.3.1", + "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", + "zod": "4.6.5" + }, + "devDependencies": { + "@internationalized/date": "3.12.4", + "@playwright/test": "1.63.0", + "@sveltejs/adapter-node": "5.5.7", + "@sveltejs/vite-plugin-svelte": "7.3.1", + "@tailwindcss/vite": "4.3.3", + "@types/node": "26.6.4", + "@types/papaparse": "5.5.2", + "@types/pg": "8.23.1", + "prettier": "3.9.9", + "prettier-plugin-svelte": "4.1.1", + "svelte-check": "4.7.6", + "tailwindcss": "4.3.3", + "tsx": "4.23.15", + "typescript": "6.0.3", + "vite": "8.3.2" + }, + "engines": { + "node": ">=24.21.0 <25" + } + }, + "node_modules/@ark/schema": { + "version": "0.56.6", + "resolved": "https://registry.npmjs.org/@ark/schema/-/schema-0.56.6.tgz", + "integrity": "sha512-OmLv80BCF2ib+zjecrXDosip/CSnZSYBGjblhqWYZWFOzdLQka6QymHToE9u7Z/HZW2KA8Hwc8MCNXoI3uGSIA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@ark/util": "0.56.6" + } + }, + "node_modules/@ark/util": { + "version": "0.56.6", + "resolved": "https://registry.npmjs.org/@ark/util/-/util-0.56.6.tgz", + "integrity": "sha512-mHqq0f0HyDd9YadpFl25w0QECAP7d8HGIUpEC85duD+L+xr4i/CwfOid/Z7zJFFF/47oLt6+pZB9RbqWpBYTYg==", + "license": "MIT", + "optional": true + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@exodus/schemasafe": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz", + "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==", + "license": "MIT", + "optional": true + }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@internationalized/date": { + "version": "3.12.4", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.4.tgz", + "integrity": "sha512-M1dEn4c1U1HsSlaVR8upZtSqvXrTkHDfv18H01uCSJyjVLDxnBR38v/fMxecmlwXKR4i9HeZcmgQAPE6A+aGJQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@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==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "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==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lucide/svelte": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-1.52.0.tgz", + "integrity": "sha512-bQ8t3IcZxWqAdmdMZPyZEv8Qf+MDgBAf7n43eRQoXyBeLWr8HWToiMIO6vKlHxC/lnMiCDdTzm09hjy92ch46A==", + "license": "ISC", + "peerDependencies": { + "svelte": "^5" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.152.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.152.0.tgz", + "integrity": "sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@playwright/test": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz", + "integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/@poppinss/macroable": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@poppinss/macroable/-/macroable-1.1.2.tgz", + "integrity": "sha512-FAVBRzzWhYP5mA3lCwLH1A0fKBqq5anyjGet90Z81aRK5c/+LTGUE1zJhZrErjaenBSOOI9BVUs3WVmotneFQA==", + "license": "MIT", + "optional": true + }, + "node_modules/@poppinss/types": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@poppinss/types/-/types-1.2.1.tgz", + "integrity": "sha512-qUYnzl0m9HJTWsXtr8Xo7CwDx6wcjrvo14bOVbIMIlKJCzKrm3LX55dRTDr1/x4PpSvKVgmxvC6Ly2YiqXKOvQ==", + "license": "MIT", + "optional": true + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.12.tgz", + "integrity": "sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.12.tgz", + "integrity": "sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.12.tgz", + "integrity": "sha512-3YIhqHD96nA5SaYNRBR16HnGv4oavZvXfD/ayHM+oYZ0WD/8lBAtf6zQua4kEyAvpqrluKXl0lnOBoiNby7x9w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.12.tgz", + "integrity": "sha512-UuuJ35MFw4gmFOrE9pEqIV+K3syIKveph+Qc1/ljHZVdoDW4pz/JHR/eMVom+TZGl/5OOvGJOWaOCVt3ZfqhxA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.12.tgz", + "integrity": "sha512-uMvssit0a4W+/7D8CbHUvG719mH3R2jwXAlh/XcPvuHTE0g++LymF88DCGNX0HM2rBOn0xrzgXktIB6fLSJBTQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.12.tgz", + "integrity": "sha512-XcFu0R0xWnwzSf4IQgFH1rJIckPN1pLy2R+4r9IDB7Yfu/ys9cVqfa4pBrMHj7a3gl8mIR4nRNPg0e5IvEVs6g==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.12.tgz", + "integrity": "sha512-260UrKgn8tz39ak+SMDOirKzr7V04M9dWPw5llW00SwBivCZoWcRBKV1d8cXnRkUmSZA3BdiUmBHWk7734Ulpw==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.12.tgz", + "integrity": "sha512-5YK1I9SqDkbPgc1IA8BgDl34suqUS2q0KWnBrirm0E51YjOs6eo6dV6jbQfNE/argHRSvd0QUGgtpIoYx+WWpw==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.12.tgz", + "integrity": "sha512-Rkcrmp7eFRg74yL5fXEU91JEWbdEPLevWwGtXpmhbjlD1StScbWTmO94Bhly+Mo+ketKYkdmM1vNUKeWSlx8cQ==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.12.tgz", + "integrity": "sha512-qvK4DuAsQc2BSjlx+Xr+IzOIvvxbGZqxFwdWfG6F518Erj0GGISyQbJ6pIappnOxlNPzNHvo/L0BwB30GZ+zVw==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.12.tgz", + "integrity": "sha512-Q9uLBO53Xd4QIq1WOycVQyPP1O4HhraEV2qqb3uTrnVw6QZih9duY4vNXOivL1xoUS1/z+W8eF4NMfl2a8Sdjw==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.12.tgz", + "integrity": "sha512-3IBxWFMjbOZskDPKv8Lf9BCnahlKuHthWkYnyIxOH/QcJrFcS4EmcenthApkwr/5+nEqZlLzeYbxeMaX7A5u4g==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.12.tgz", + "integrity": "sha512-xtX61xg4LKPkPWilZU1ynKClz5Gj4bf74LML4r3eVLWumKnGjoEr1OSHQhMdbBDoYTi+yjrujvpZe2pUnqCrrA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.12.tgz", + "integrity": "sha512-At7fPB6PCaIjzgIhEZFxuT+BBFqiQibJDT4d3PhiR3f4E7bbMZF4aKblbFfEM3sETRDd1YiQx/+U/g/B/ou5Ew==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.12.tgz", + "integrity": "sha512-WIw2haVKwjuYdXkHaoC0mF8Le71TuCBxjrdKqLbJGctbBABj+ClfmNvtbOnzpq3RokNo5+V1qhtSzJyXorsklQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "license": "MIT" + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "29.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.3.tgz", + "integrity": "sha512-ZaOxZceP7SOUW7Lqw5IRVweSQYWaeIPnXIGLiB690EBA3FGJTO40EEr2L5yZplJWsgTCogILRSpcAe7+U0Otdg==", + "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-commonjs/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/@rollup/plugin-commonjs/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/@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/plugin-replace": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.3.tgz", + "integrity": "sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.30.3" + }, + "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-replace/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/@rollup/pluginutils": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", + "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", + "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.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.64.0.tgz", + "integrity": "sha512-FLLpX941CD/7aBvX3GqLGBM9OvY5PGXTryPJkG5r5/OuBpZpV5k45Uo3RxAvqd35tsyc3hH1bhtz921IKzoIzg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.64.0.tgz", + "integrity": "sha512-iI32XM/jnDYJsvx5ai/za/7gWO9XoSeFFtU06/ZCaRgyilfJOjaopD3nOpk2WrTCGOyeDt4hleUiGOAnd5tHNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.64.0.tgz", + "integrity": "sha512-dRBZlbt2sEtF8Mn4MODThze9fIPdDoOhVSlXMAKy5dQbOYK9pOOoPY4Fat3OTtr0+8zbJo5TTZwlsfxY/kDNJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.64.0.tgz", + "integrity": "sha512-Y3whrJOMucfkhgNkvcM93M6j8C53iEAMmPMYD99C6G1kUy4ELigLWk9ar2/4aCBSRgsql0zdlOemLgW2m5GZgQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.64.0.tgz", + "integrity": "sha512-S6S1GEzPjFF6IONYJX3AKt/799+8RgBgCcwRnUo+Ep1IvBxUbw5M51iU0TDdK2NWWVnSvCQ8yi6Em0fGASMOng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.64.0.tgz", + "integrity": "sha512-Ed1DPoFENkXj4IGzSB12SesV40iP6LxCCrbTIkSGIQBpOOxpBM+0kj62lmaeIHfMWTDoddqTOtqc7s8LYfn+Pw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.64.0.tgz", + "integrity": "sha512-J/6xhutJPtFyIkmHdf4olOdHHKH5kOJNumojdWn+Elv5ICEYTVOa362Vw27TxsK0BLzb1le3EDlO3UbvFK1lnQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.64.0.tgz", + "integrity": "sha512-G4L/Nnx59d2LvDoV/P4S5lZ7oVLT6hodOHMacqjiq2LL2gts86KbkEBTYL53+fBt0odhf7XPIbIATGoYiJctxg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.64.0.tgz", + "integrity": "sha512-zF16pvJDEUksGYRbfnXne7pdklAyj2XDmJeYKgP93oq0cUyX9Y8O7lUo1R+IY1GagP/YBvCEDCe7eEcAKpw//g==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.64.0.tgz", + "integrity": "sha512-nIggWn8n+w12+R5lsV+PYYoUY8vuObxPAQqJI7WES+9v0qspYuAHoZMfYM0yLxlipasT5BY9ht5W8t5c/rSCkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.64.0.tgz", + "integrity": "sha512-WLOTiHPzszP0dxIMsHpFeDSOSB5I49Mj8K4+yKE3H1X/D0JRG5XZ5jrctJLMjnUc/LUzaKiwq6EHWNJ8pup8PA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.64.0.tgz", + "integrity": "sha512-35RK9aRhh7g7Pmeu5gQ01aCvrhXzj9LoBE6xtUcEH1ns+nyTaSzxSpADmBQINu4QiTDGvmQYwo1j5hlg1rRXFw==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.64.0.tgz", + "integrity": "sha512-RqTfnpoTjuWfPAmqK5p2edt2AMCDJWm6oTn7sKfmyx+6tRga0ip05SJ977o6udTcIiDeF/CJMUfkaN4gtWt6Aw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.64.0.tgz", + "integrity": "sha512-RQjZPigjxFvFZDijG6rjlSXxhRaTf9zLYVpGK+lVcga64oMqP1GbgVqCb46eHTTceSIgp89Ja0y5aPe3GFBYHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.64.0.tgz", + "integrity": "sha512-tjL58mgQcTRnDRpeky+Sth1MKQeYi2Ct6r3Luq9E5aTSjnM/i/JbozqZkKqp9thM5+oDSZxo+05f9kdQedfTGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.64.0.tgz", + "integrity": "sha512-ictoRyVaoZ8C67myHef+8hhCa6IplXJJ/xUCkVCxziV4zxMZh+jvhkhGsPBLqj1S72fnqD0QH5zL4lcaa4X7DQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.64.0.tgz", + "integrity": "sha512-8aVvZ2hfr9RLFzBQkQNmYeHLDja8fLeQAmLt757zdKN/B+8L809KlLJKctQjS22wZotptV8G6o7fVPuAs8S1tQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.64.0.tgz", + "integrity": "sha512-2dEF8GAcDKwshUfydD+GhosppNyBzhVUHkdFF3CXd3FpOkzj/RZzF5aoEtzklujklT5qFVRU2GK/cFeLaTNKVg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.64.0.tgz", + "integrity": "sha512-3XHTvoo2hKkh0MiOfS7SkhHjMaaWF7P4F3drocZZaho/Z8w7h/jpnuBHm91BOQLfvrKHs1yR+zir4E8RI3XzTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.64.0.tgz", + "integrity": "sha512-HKtkzodL4a3KFWDXDoO61cRynAWTR3LnAE0oGtfG9lcNFpNuVYUFOByxDgNqEvic9rvaQbAj+SFwTEWDptRaIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.64.0.tgz", + "integrity": "sha512-JBsxqiY16uyyCujtoC3s7zL+SrYyNElptD7K4V+KUoJ2V8/XG3hPWpheK+SuPwNh1cikJvIZmIdsrYXvD6PLJw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.64.0.tgz", + "integrity": "sha512-S5Qrijh37qhnUYQ0ay8WN3LuWeKjow7wTUyCOPMZtnXiBXYXdsZETI6ZLfYnQAKovltckPLBFdNeD61P2rJc7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.64.0.tgz", + "integrity": "sha512-zbZ6iMmLFEsCtTdc2/y1F/cDFVNNCPF7Ae8PCT/wdPE//hGwYp6r+MyCLMFX+kOeIe09Pz6ASfnnfpSTN1FJQQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.64.0.tgz", + "integrity": "sha512-L6Hw69oNpuYaOr1zCG30v0/DqKYCSRwodPGwIcVZ/6f8SOzq9rxtL/xNVI5MO4wjli+Ko0ErK9uHJL3mNPt8wQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.64.0.tgz", + "integrity": "sha512-nl+CTWIgOGszQmUz/kanfgrfFZ4qiwxKIfT/ryl3N3Ewk5tqizmYtNXiqb7Bu/fDtNgu1kyxdFiAUJNur3iysg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "license": "BSD-3-Clause", + "optional": true + }, + "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==", + "license": "MIT" + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.13.tgz", + "integrity": "sha512-wgKggnhZVL9Bfx1OaKKTrYY9BFRk6C8UAkQNUcIv1+llzYrIqy+RZm5HPKzn0NpEBvTVhTqB4kQyllZywsRBRQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/adapter-node": { + "version": "5.5.7", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.5.7.tgz", + "integrity": "sha512-uOfc9eVlI3A37RRSaKcgrheBYPrfJwC9VMqDp8x/O6tlKdcLLvHThSWD0KNIbjQ/d+7bwLGx3vx6aowAcRfd2g==", + "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/plugin-replace": "^6.0.3", + "rollup": "^4.59.0" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.4.0" + } + }, + "node_modules/@sveltejs/kit": { + "version": "2.70.3", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.70.3.tgz", + "integrity": "sha512-UDvEYuZqAMbfB/oXIoqKvbKcb7YczK5zYrzmsGV1zRJk03jntwp8dXiYoIJotxAndsKvcPFtx9H1GRSKFdSHgg==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@sveltejs/acorn-typescript": "^1.0.9", + "@types/cookie": "^0.6.0", + "acorn": "^8.16.0", + "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/kit/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==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/@sveltejs/load-config": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@sveltejs/load-config/-/load-config-0.2.3.tgz", + "integrity": "sha512-VT3qmUb8pRV2QrZjd8iAmtg8lf4W0TIjZbvXtz5MKei/q96teWZgGJyyidJzOjzZzvdq616eSRVeMYIQChUTAQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-7.3.1.tgz", + "integrity": "sha512-ZPsLN8B1e/En+Ak5s4V7srFDT532oS0qieLsQwu63NGKsS+iAjoO2Js1BochlHlglcU+Pt7WAO3C5Ee+4f6gVA==", + "license": "MIT", + "dependencies": { + "deepmerge": "^4.3.1", + "magic-string": "^1.0.0", + "obug": "^2.1.0", + "vitefu": "^1.1.2" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "svelte": "^5.46.4", + "vite": "^8.0.0-beta.7 || ^8.0.0" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tailwindcss/node/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/@tailwindcss/oxide": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "inBundle": true, + "license": "0BSD", + "optional": true + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", + "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "tailwindcss": "4.3.3" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@transloadit/prettier-bytes": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@transloadit/prettier-bytes/-/prettier-bytes-2.0.0.tgz", + "integrity": "sha512-GYzxSL3y9XlhwamBLkHT1y2jFrRsHkZZ1jNctn8NeMNyTtIbMK1Qf0f1koHkDdW+MwOUowCmpm8zP6ZpQ36G5w==", + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "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==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.6.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.4.tgz", + "integrity": "sha512-ldVPDCzj7fsaGZrLB0NuHuTvJcsNasysBAqMolr/cgxrLd1xbqxIr3XJiPnHHJUCxj5sNF1vnRj9aWnrVh5Jcg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.9.0" + } + }, + "node_modules/@types/papaparse": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.2.tgz", + "integrity": "sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/pg": { + "version": "8.23.1", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.23.1.tgz", + "integrity": "sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "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/@types/validator": { + "version": "13.15.10", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", + "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", + "license": "MIT", + "optional": true + }, + "node_modules/@typeschema/class-validator": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@typeschema/class-validator/-/class-validator-0.3.0.tgz", + "integrity": "sha512-OJSFeZDIQ8EK1HTljKLT5CItM2wsbgczLN8tMEfz3I1Lmhc5TBfkZ0eikFzUC16tI3d1Nag7um6TfCgp2I2Bww==", + "license": "MIT", + "optional": true, + "dependencies": { + "@typeschema/core": "0.14.0" + }, + "peerDependencies": { + "class-validator": "^0.14.1" + }, + "peerDependenciesMeta": { + "class-validator": { + "optional": true + } + } + }, + "node_modules/@typeschema/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@typeschema/core/-/core-0.14.0.tgz", + "integrity": "sha512-Ia6PtZHcL3KqsAWXjMi5xIyZ7XMH4aSnOQes8mfMLx+wGFGtGRNlwe6Y7cYvX+WfNK67OL0/HSe9t8QDygV0/w==", + "license": "MIT", + "optional": true, + "peerDependencies": { + "@types/json-schema": "^7.0.15" + }, + "peerDependenciesMeta": { + "@types/json-schema": { + "optional": true + } + } + }, + "node_modules/@uppy/core": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@uppy/core/-/core-6.2.0.tgz", + "integrity": "sha512-GujQtHazyHM2jjoNZyZlB+R3mUf1bynx0D+sPRt4IfLEfV09LbqA1kzwabL+WEKFdfqls15T+a/4u3XGV4n/oQ==", + "license": "MIT", + "dependencies": { + "@transloadit/prettier-bytes": "^2.0.0", + "classnames": "^2.5.1", + "lodash": "^4.18.1", + "mime-match": "^1.0.2", + "namespace-emitter": "^2.0.1", + "nanoid": "^6.0.1", + "p-queue": "^9.3.3", + "p-retry": "^8.0.0", + "preact": "^10.29.2" + } + }, + "node_modules/@uppy/dashboard": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@uppy/dashboard/-/dashboard-6.0.1.tgz", + "integrity": "sha512-nss7xYzh6zXly03Tz/j9Lclm8rOkBOp0wPSQuAYkrtTzJrRsahemrOXwYRQTW2hyN+hDdjVtU9Ouo2NMcPzkVQ==", + "license": "MIT", + "dependencies": { + "@transloadit/prettier-bytes": "^2.0.0", + "@uppy/thumbnail-generator": "^6.0.1", + "classnames": "^2.5.1", + "lodash": "^4.18.1", + "nanoid": "^6.0.1", + "preact": "^10.29.2", + "shallow-equal": "^3.1.0" + }, + "peerDependencies": { + "@uppy/core": "^6.2.0" + } + }, + "node_modules/@uppy/thumbnail-generator": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@uppy/thumbnail-generator/-/thumbnail-generator-6.0.1.tgz", + "integrity": "sha512-tNMn3C6tepp0UDoN6L9L3OLNzwG999s1+crXGLpZI2xG2UZ2uyVFASy4rjJS7/2wsXFsE9+xf1t12Hob8ZYRlQ==", + "license": "MIT", + "dependencies": { + "exifr": "^7.1.3" + }, + "peerDependencies": { + "@uppy/core": "^6.2.0" + } + }, + "node_modules/@uppy/xhr-upload": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@uppy/xhr-upload/-/xhr-upload-6.0.1.tgz", + "integrity": "sha512-4G5COa9yu6LZkYBupbDm02Rn8WloHw8wor+RiPNpQ1+Vdgk4MD5iFurRE4cHRzXjzDibFKeJH1B4qK5hKqaQnw==", + "license": "MIT", + "peerDependencies": { + "@uppy/core": "^6.2.0" + } + }, + "node_modules/@valibot/to-json-schema": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@valibot/to-json-schema/-/to-json-schema-1.8.0.tgz", + "integrity": "sha512-a0M+uwCuQZEPAo65NYkFSJ14O5c213KoSZmPdoCfuFvUhfULi4T2Z6Tpjv6lTVMW9RoLm74RpRAjNEa43KcN+g==", + "license": "MIT", + "optional": true, + "peerDependencies": { + "valibot": "^1.5.0" + } + }, + "node_modules/@vinejs/compiler": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vinejs/compiler/-/compiler-4.1.3.tgz", + "integrity": "sha512-UyH7Zn8dkTMLeU+PF2WjCnWkFb2qYaOxAcvp/uXW0njtKNcJOnVJaPsnWYwqewkTcHN47yvOdzosj3kj3RAP5w==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@vinejs/vine": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@vinejs/vine/-/vine-4.4.0.tgz", + "integrity": "sha512-cfnNXjs9+f+22d3Eb8koyg2qlCbaT394XBlL4AeEr1WM+NH1omFcXwe1zrkW4hHwPDSfUd4lFUdNO6E+YnTeWQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@poppinss/macroable": "^1.1.0", + "@poppinss/types": "^1.2.1", + "@standard-schema/spec": "^1.1.0", + "@types/validator": "^13.15.10", + "@vinejs/compiler": "^4.1.3", + "camelcase": "^9.0.0", + "dayjs": "^1.11.19", + "dlv": "^1.1.3", + "normalize-url": "^8.1.1", + "validator": "^13.15.26" + }, + "engines": { + "node": ">=18.16.0" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aria-query": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arkregex": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/arkregex/-/arkregex-0.0.12.tgz", + "integrity": "sha512-sOxL0eX50pnRYO4OyajKZChGuuj/iHwC5tysiAaGOPBu+zJwJQckWn7/DRH2++ol5uaKpyRn7N0hKT7nDqGmgg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@ark/util": "0.56.6" + } + }, + "node_modules/arktype": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/arktype/-/arktype-2.2.7.tgz", + "integrity": "sha512-AbgM5SQvwCTURSR+ENfPtOG7cL5VZ0hqOVtoWyXKw2SviFddZhCBEUAjjVJYKCNFlPPR1VX5LGUKxsSqqc4Kdw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@ark/schema": "0.56.6", + "@ark/util": "0.56.6", + "arkregex": "0.0.12" + } + }, + "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==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/bits-ui": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.19.5.tgz", + "integrity": "sha512-TEwkLSYElE5SE0fIdcItiFPfyg9vrR67N1Mt6VvZCqZo24dwTA1MpGVV3WeyIg7rfVAaZ0So8a0/dcnPngCoew==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.1", + "@floating-ui/dom": "^1.7.1", + "esm-env": "^1.1.2", + "runed": "^0.35.1", + "svelte-toolbelt": "^0.10.6", + "tabbable": "^6.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/huntabyte" + }, + "peerDependencies": { + "@internationalized/date": "^3.8.1", + "svelte": "^5.33.0" + } + }, + "node_modules/bits-ui/node_modules/runed": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.35.1.tgz", + "integrity": "sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q==", + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "esm-env": "^1.0.0", + "lz-string": "^1.5.0" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.21.0", + "svelte": "^5.7.0" + }, + "peerDependenciesMeta": { + "@sveltejs/kit": { + "optional": true + } + } + }, + "node_modules/camelcase": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-9.0.0.tgz", + "integrity": "sha512-TO9xmyXTZ9HUHI8M1OnvExxYB0eYVS/1e5s7IDMTAoIcwUd+aNcFODs6Xk83mobk0velyHFQgA1yIrvYc6wclw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/class-validator": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.4.tgz", + "integrity": "sha512-AwNusCCam51q703dW82x95tOqQp6oC9HNUl724KxJJOfnKscI8dOloXFgyez7LbTTKWuRBA37FScqVbJEoq8Yw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/validator": "^13.15.3", + "libphonenumber-js": "^1.11.1", + "validator": "^13.15.22" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/dayjs": { + "version": "1.11.23", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz", + "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==", + "license": "MIT", + "optional": true + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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.9.4", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.9.4.tgz", + "integrity": "sha512-sPAT4pztbu6586/hrhOnMKS17IJrvg12mXiSPSS3W5qDeN2RGgvZ0diZCm31dBbnevfVmujNO3IM2wrS4Y2Rhg==", + "license": "MIT" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT", + "optional": true + }, + "node_modules/effect": { + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.22.2.tgz", + "integrity": "sha512-fm1CQXCs7GTMwpadZxwU5rnAb2bN6mbQpknq/pXBGCzSKkosNYb9k0wsoL0TfB10rSa5xOEZJjo77u/KLXSANg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.26.0.tgz", + "integrity": "sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.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.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "devOptional": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/esrap": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.4.0.tgz", + "integrity": "sha512-CQPcneEHw/czj32uwQC52nYnax8H4SXt25FCM+LIwGZ49cMIxpOj3bP8wmzcI+Z0HIy5KkClaPhRLfCySzwHPA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "peerDependencies": { + "@typescript-eslint/types": "^8.2.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/types": { + "optional": true + } + } + }, + "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/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/exifr": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/exifr/-/exifr-7.1.3.tgz", + "integrity": "sha512-g/aje2noHivrRSLbAUtBPWFbxKdKhgj/xr1vATDdUXPOFYJlQ62Ft0oy+72V6XLIpDJfHs6gXLbBLAolqOXYRw==", + "license": "MIT" + }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "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/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.17.0.tgz", + "integrity": "sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.4" + }, + "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-network-error": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.2.tgz", + "integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/joi": { + "version": "17.13.8", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.8.tgz", + "integrity": "sha512-iPKOGmiRw1jxf/JOPwxmCcUQAOdF359mdzYiP2DJ+TMX0YK2zjK3D+zYOaGjpumWxOFF/l2xVWjRVK5bGSLdEw==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/libphonenumber-js": { + "version": "1.13.14", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.13.14.tgz", + "integrity": "sha512-llihgCcx0BFLksecLP+x1J+6JDE1GsXS1RN/LoPF6qcwpeQcnjj0lcvZxY8AzbEpYwyZWPZW/nDuqkqzm3amiw==", + "license": "MIT", + "optional": true + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.3.tgz", + "integrity": "sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0" + } + }, + "node_modules/memoize-weak": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/memoize-weak/-/memoize-weak-1.0.2.tgz", + "integrity": "sha512-gj39xkrjEw7nCn4nJ1M5ms6+MyMlyiGmttzsqAUsAKn6bYKwuTHh/AO3cKPF8IBrTIYTxb0wWXFs3E//Y8VoWQ==", + "license": "ISC" + }, + "node_modules/mime-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mime-match/-/mime-match-1.0.2.tgz", + "integrity": "sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==", + "license": "ISC", + "dependencies": { + "wildcard": "^1.1.0" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/namespace-emitter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/namespace-emitter/-/namespace-emitter-2.0.1.tgz", + "integrity": "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-6.0.1.tgz", + "integrity": "sha512-3wVS3i51pE2pi1k5FFL/95BGfVS0kSsvDVuGXHOtxox/TywUmtgq+3qiTOTbs9J7KfHaXPiN171k/A6dBnaXFw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^22 || ^24 || >=26" + } + }, + "node_modules/normalize-url": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", + "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/obug": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/p-queue": { + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.3.tgz", + "integrity": "sha512-NXAOdnEe5FsZJfT4oK84lE1Y5cFFdWlRuOo5tww8DyNMxyRXwn39fIkUtNLKppcPC+UYU/bXujNCUGDv01y7CA==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.4", + "p-timeout": "^7.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-8.0.1.tgz", + "integrity": "sha512-NAigyu8hfvJe7MsS18mnc4is0MZtau3QMdBklaJKK23oBQb6occO3UPM3vHmTckW8KhfyjMZaUOqdTFEYliHgg==", + "license": "MIT", + "dependencies": { + "is-network-error": "^1.3.0" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.2.tgz", + "integrity": "sha512-prbX4Z3YszrFNgH+MW5Zoeq3baXrMtP/MQnFeET90UB/GtGcGDQ5Usg9OCy6ETjTTntOw1SL2z9fMPUppN3Guw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/papaparse": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.7.0.tgz", + "integrity": "sha512-qBGxg/7Q3Kl9Wfhrz2Z74UnvnHTXLNG6jmKJFeBvP2+y4lV7So+7SR62+Zd47JvdrCkX+nDcnr0ObPzek/+6RA==", + "license": "MIT" + }, + "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/pg": { + "version": "8.23.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.23.1.tgz", + "integrity": "sha512-aL96AHANtWjPLDOLqnhx+ngp9+UK7ETEU8VJrDCGvsSSi/mGLcWYsS6Herg7lmaBJe4uwrfqsa7gTEFaSizDoQ==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.14.1", + "pg-pool": "^3.14.0", + "pg-protocol": "^1.16.1", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.4.1" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.1.tgz", + "integrity": "sha512-6PQbsFWZcp9EmJEwy5cGQ2La+AMWpP46lgbb8X+U/XsHIUweYDNCpeuKck5RxL2MdVFi7krbbEi5nX4Zh7JhrQ==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.1.tgz", + "integrity": "sha512-qR3kGNPBLpCNtz0evbKA0Y/MRFXwSSdT+pTJvYp/bXTcReZbvX1kzF0IyTc1QnxqF7AZbOeBhNL8R5mYQZV/MA==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", + "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.16.1.tgz", + "integrity": "sha512-p9VOFMiHB/ZbJATetbg+99PxssTVSQRnyuPSQ67mN1+1KBOjZaZ83ZQzltnxPhJwSsC3nwVjJ10DVJlerbFzLg==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/playwright": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", + "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright-core": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz", + "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "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.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "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/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/preact": { + "version": "10.29.8", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.8.tgz", + "integrity": "sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + }, + "peerDependencies": { + "preact-render-to-string": ">=5" + }, + "peerDependenciesMeta": { + "preact-render-to-string": { + "optional": true + } + } + }, + "node_modules/prettier": { + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.9.tgz", + "integrity": "sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-svelte": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-4.1.1.tgz", + "integrity": "sha512-wXvbXMjSvb4C9ENWTHXyd+ihakKCsJ6rJhLP6/8HFNj4GkZr48jqL9PoKsl2sk7SyCZRTnJ7O2TTowUpOxP/KA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "prettier": "^3.0.0", + "svelte": "^5.0.0" + } + }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==", + "license": "MIT", + "optional": true + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "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/rolldown": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.12.tgz", + "integrity": "sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.152.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.12", + "@rolldown/binding-android-arm64": "1.2.12", + "@rolldown/binding-darwin-arm64": "1.2.12", + "@rolldown/binding-darwin-x64": "1.2.12", + "@rolldown/binding-freebsd-x64": "1.2.12", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.12", + "@rolldown/binding-linux-arm64-gnu": "1.2.12", + "@rolldown/binding-linux-arm64-musl": "1.2.12", + "@rolldown/binding-linux-ppc64-gnu": "1.2.12", + "@rolldown/binding-linux-s390x-gnu": "1.2.12", + "@rolldown/binding-linux-x64-gnu": "1.2.12", + "@rolldown/binding-linux-x64-musl": "1.2.12", + "@rolldown/binding-openharmony-arm64": "1.2.12", + "@rolldown/binding-win32-arm64-msvc": "1.2.12", + "@rolldown/binding-win32-x64-msvc": "1.2.12" + } + }, + "node_modules/rollup": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.64.0.tgz", + "integrity": "sha512-gjA/kFeDfffII8COoPsvLPvE16hWDTEc1+0QKQNKUkuV8iJjqsC4xXs8/FzuL0v9EV39kuxTbAgcFoTRyy2TGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.64.0", + "@rollup/rollup-android-arm64": "4.64.0", + "@rollup/rollup-darwin-arm64": "4.64.0", + "@rollup/rollup-darwin-x64": "4.64.0", + "@rollup/rollup-freebsd-arm64": "4.64.0", + "@rollup/rollup-freebsd-x64": "4.64.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.64.0", + "@rollup/rollup-linux-arm-musleabihf": "4.64.0", + "@rollup/rollup-linux-arm64-gnu": "4.64.0", + "@rollup/rollup-linux-arm64-musl": "4.64.0", + "@rollup/rollup-linux-loong64-gnu": "4.64.0", + "@rollup/rollup-linux-loong64-musl": "4.64.0", + "@rollup/rollup-linux-ppc64-gnu": "4.64.0", + "@rollup/rollup-linux-ppc64-musl": "4.64.0", + "@rollup/rollup-linux-riscv64-gnu": "4.64.0", + "@rollup/rollup-linux-riscv64-musl": "4.64.0", + "@rollup/rollup-linux-s390x-gnu": "4.64.0", + "@rollup/rollup-linux-x64-gnu": "4.64.0", + "@rollup/rollup-linux-x64-musl": "4.64.0", + "@rollup/rollup-openbsd-x64": "4.64.0", + "@rollup/rollup-openharmony-arm64": "4.64.0", + "@rollup/rollup-win32-arm64-msvc": "4.64.0", + "@rollup/rollup-win32-ia32-msvc": "4.64.0", + "@rollup/rollup-win32-x64-gnu": "4.64.0", + "@rollup/rollup-win32-x64-msvc": "4.64.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/set-cookie-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.2.tgz", + "integrity": "sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==", + "license": "MIT" + }, + "node_modules/shallow-equal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-3.1.0.tgz", + "integrity": "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==", + "license": "MIT" + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "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.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/superstruct": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz", + "integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14.0.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": "5.57.1", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.57.1.tgz", + "integrity": "sha512-Uqj49lWKB+iSSnneuwiYYJ7MZgkB+eXr0LXBhv4uDuAkXqnWmq65Sxflfvp0Lc6MdKjMUxGaeOKWJqz5SNiVIA==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "@jridgewell/sourcemap-codec": "^1.6.0", + "@sveltejs/acorn-typescript": "^1.0.13", + "@types/estree": "^1.0.9", + "acorn": "^8.18.0", + "aria-query": "5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "devalue": "^5.9.2", + "esm-env": "^1.2.1", + "esrap": "^2.3.6", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.7.6.tgz", + "integrity": "sha512-t2scM//ZuVbSY/T2w6FSBw1v9s2NEmh/g+sy1lqtosW5ylBV5AF4wFb1Ts9Kf3MbfPDUDJDZ9L436YT0SPTdvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "@sveltejs/load-config": "^0.2.3", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": "^5.0.0 || ^6.0.0" + } + }, + "node_modules/svelte-toolbelt": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.10.6.tgz", + "integrity": "sha512-YWuX+RE+CnWYx09yseAe4ZVMM7e7GRFZM6OYWpBKOb++s+SQ8RBIMMe+Bs/CznBMc0QPLjr+vDBxTAkozXsFXQ==", + "funding": [ + "https://github.com/sponsors/huntabyte" + ], + "dependencies": { + "clsx": "^2.1.1", + "runed": "^0.35.1", + "style-to-object": "^1.0.8" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "peerDependencies": { + "svelte": "^5.30.2" + } + }, + "node_modules/svelte-toolbelt/node_modules/runed": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.35.1.tgz", + "integrity": "sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q==", + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "esm-env": "^1.0.0", + "lz-string": "^1.5.0" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.21.0", + "svelte": "^5.7.0" + }, + "peerDependenciesMeta": { + "@sveltejs/kit": { + "optional": true + } + } + }, + "node_modules/svelte/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==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/sveltekit-superforms": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/sveltekit-superforms/-/sveltekit-superforms-3.0.0.tgz", + "integrity": "sha512-fnMV7pb96yYDD8jgoOZshob3c2jgcgtsNhYAYv9atBbkYQJRINOqptRGTK/iYdLaCnzvUqJDPSYHUVAtbmeXbA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ciscoheat" + }, + { + "type": "ko-fi", + "url": "https://ko-fi.com/ciscoheat" + }, + { + "type": "paypal", + "url": "https://www.paypal.com/donate/?hosted_button_id=NY7F5ALHHSVQS" + } + ], + "license": "MIT", + "dependencies": { + "devalue": "^5.9.4", + "esm-env": "^1.2.2", + "memoize-weak": "^1.0.2", + "ts-deepmerge": "^8.0.0" + }, + "optionalDependencies": { + "@exodus/schemasafe": "^1.3.0", + "@standard-schema/spec": "^1.1.0", + "@typeschema/class-validator": "^0.3.0", + "@valibot/to-json-schema": "^1.8.0", + "@vinejs/vine": "^4.0.0", + "arktype": "^2.2.7", + "class-validator": "^0.14.4", + "effect": "^3.22.2", + "joi": "^17.13.8", + "json-schema-to-ts": "^3.1.1", + "superstruct": "^2.0.2", + "typebox": "^1.3.34", + "valibot": "^1.5.0", + "yup": "^1.7.1", + "zod": "^4.6.5", + "zod-v3-to-json-schema": "^4.0.0" + }, + "peerDependencies": { + "@exodus/schemasafe": "^1.3.0", + "@sveltejs/kit": "^2.12.0 || ^3.0.0", + "@typeschema/class-validator": "^0.3.0", + "@vinejs/vine": "^1.8.0 || ^2.0.0 || ^3.0.0 || ^4.0.0", + "arktype": "^2.0.0", + "class-validator": "^0.14.1", + "effect": "^3.21.0", + "joi": "^17.13.1", + "superstruct": "^2.0.2", + "svelte": "^5.56.4", + "typebox": "^1.0.36", + "valibot": "^1.2.0", + "yup": "^1.4.0", + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "@exodus/schemasafe": { + "optional": true + }, + "@typeschema/class-validator": { + "optional": true + }, + "@vinejs/vine": { + "optional": true + }, + "arktype": { + "optional": true + }, + "class-validator": { + "optional": true + }, + "effect": { + "optional": true + }, + "joi": { + "optional": true + }, + "superstruct": { + "optional": true + }, + "typebox": { + "optional": true + }, + "valibot": { + "optional": true + }, + "yup": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/tabbable": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz", + "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", + "license": "MIT" + }, + "node_modules/tailwind-merge": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.7.0.tgz", + "integrity": "sha512-XPPUyAc+cvspz3lHTcR/QgPfW2A0lv/xQNIjX3HGhLR+Nq2lHaLq5MtTesHn8GUr3W3DguT2KT5x3NVgRtYwmA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwind-variants": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-3.3.1.tgz", + "integrity": "sha512-4pAvwUtM4HKBiRZftncAbpn6V9Hhwoa5Fl7O2u5zbp7Z5Cvu+/o/6+176WY3WCEES209543quG8zFIcXCsc5Jw==", + "license": "MIT", + "engines": { + "node": ">=16.9.x", + "pnpm": ">=7.x" + }, + "peerDependencies": { + "tailwind-merge": ">=3.0.0", + "tailwindcss": "*" + }, + "peerDependenciesMeta": { + "tailwind-merge": { + "optional": true + }, + "tailwindcss": { + "optional": true + } + } + }, + "node_modules/tailwindcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==", + "license": "MIT", + "optional": true + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", + "license": "MIT", + "optional": true + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "license": "MIT", + "optional": true + }, + "node_modules/ts-deepmerge": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-8.0.0.tgz", + "integrity": "sha512-133O+10nJmVI8w5xeVZPEv5PIrv7iaUae07wv1aH8XJH95Ur6YIhWAPhPyP1YPlbPS9fCVcNIZTu7m8urRVF0A==", + "license": "ISC", + "engines": { + "node": ">=14.13.1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.23.15", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.15.tgz", + "integrity": "sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typebox": { + "version": "1.3.34", + "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.3.34.tgz", + "integrity": "sha512-wbnzrXXDW8xEFHDZZs2jo1MkhaYlKAY4FRhpBc1+2LF1fZVBGCXGdLEhA/Z/NBbgzJMFfeM8m7elKPa/+KxaUQ==", + "license": "MIT", + "optional": true + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/valibot": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.5.0.tgz", + "integrity": "sha512-nil6AkP2TChWL43Z5uJ6GTxX01CUA+g8LWUM+N/rB9NBbkUMaUsi9PUNzlUPgoKASgmx9f7eGOYpJ04/fSa6FQ==", + "license": "MIT", + "optional": true, + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/validator": { + "version": "13.15.35", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz", + "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vite": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.2.tgz", + "integrity": "sha512-SQr1x6W5vVSbROg7vsyXIaxK9b0G7zsT68acdWWRmnBUsgDieLCRG+Rep9WdZgcposvv/GSnr4GUUBqB3vXq6w==", + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.11", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/vite/node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/wildcard": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-1.1.2.tgz", + "integrity": "sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==", + "license": "MIT" + }, + "node_modules/xlsx": { + "version": "0.20.3", + "resolved": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", + "integrity": "sha512-oLDq3jw7AcLqKWH2AhCpVTZl8mf6X2YReP+Neh0SJUzV/BdZYjth94tG5toiMB1PPrYtxOCfaoUCkvtuH+3AJA==", + "license": "Apache-2.0", + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yup": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.7.1.tgz", + "integrity": "sha512-GKHFX2nXul2/4Dtfxhozv701jLQHdf6J34YDh2cEkpqoo8le5Mg6/LrdseVLrFarmFygZTlfIhHx/QKfb/QWXw==", + "license": "MIT", + "optional": true, + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + }, + "node_modules/zimmerframe": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.5.tgz", + "integrity": "sha512-msJxIvYDYcoNL+PJsu+7qmpDWsYmAxTY+2TNYXXF0hzBzBk0BMecOqDOG/EckUoKCuKwObfbugIl8QpqHDXeFA==", + "license": "MIT" + }, + "node_modules/zod": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", + "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-v3-to-json-schema": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/zod-v3-to-json-schema/-/zod-v3-to-json-schema-4.0.0.tgz", + "integrity": "sha512-KixLrhX/uPmRFnDgsZrzrk4x5SSJA+PmaE5adbfID9+3KPJcdxqRobaHU397EfWBqfQircrjKqvEqZ/mW5QH6w==", + "license": "ISC", + "optional": true, + "peerDependencies": { + "zod": "^3.25 || ^4.0.14" + } + } + } +} diff --git a/applications/supplier-intake/package.json b/applications/supplier-intake/package.json new file mode 100644 index 00000000..ac245a7d --- /dev/null +++ b/applications/supplier-intake/package.json @@ -0,0 +1,53 @@ +{ + "name": "supplier-intake", + "private": true, + "type": "module", + "engines": { + "node": ">=24.21.0 <25" + }, + "scripts": { + "dev": "node --env-file=.env ./node_modules/vite/bin/vite.js --host 127.0.0.1 --port 3001", + "build": "vite build", + "start": "node --env-file=.env build/index.js", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "test": "tsx --test tests/parsers.test.ts", + "test:integration": "node --env-file=.env --import tsx --test tests/database.test.ts", + "test:browser": "node --env-file=.env ./node_modules/@playwright/test/cli.js test", + "fixtures": "tsx scripts/fixtures.ts", + "format": "prettier --write \"src/**/*.{ts,svelte}\" \"src/styles.css\" \"tests/**/*.ts\" \"scripts/*.ts\" \"*.{ts,js,json}\"" + }, + "dependencies": { + "@lucide/svelte": "1.52.0", + "@sveltejs/kit": "2.70.3", + "@uppy/core": "6.2.0", + "@uppy/dashboard": "6.0.1", + "@uppy/xhr-upload": "6.0.1", + "bits-ui": "2.19.5", + "clsx": "2.1.1", + "papaparse": "5.7.0", + "pg": "8.23.1", + "svelte": "5.57.1", + "sveltekit-superforms": "3.0.0", + "tailwind-merge": "3.7.0", + "tailwind-variants": "3.3.1", + "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", + "zod": "4.6.5" + }, + "devDependencies": { + "@internationalized/date": "3.12.4", + "@playwright/test": "1.63.0", + "@sveltejs/adapter-node": "5.5.7", + "@sveltejs/vite-plugin-svelte": "7.3.1", + "@tailwindcss/vite": "4.3.3", + "@types/node": "26.6.4", + "@types/papaparse": "5.5.2", + "@types/pg": "8.23.1", + "svelte-check": "4.7.6", + "tailwindcss": "4.3.3", + "tsx": "4.23.15", + "typescript": "6.0.3", + "vite": "8.3.2", + "prettier": "3.9.9", + "prettier-plugin-svelte": "4.1.1" + } +} diff --git a/applications/supplier-intake/playwright.config.ts b/applications/supplier-intake/playwright.config.ts new file mode 100644 index 00000000..d8a11b5d --- /dev/null +++ b/applications/supplier-intake/playwright.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from '@playwright/test'; +export default defineConfig({ + testDir: 'tests/browser', + workers: 1, + retries: 0, + timeout: 180_000, + use: { + baseURL: process.env.ORIGIN ?? 'http://127.0.0.1:3001', + browserName: 'chromium', + viewport: { width: 1440, height: 1050 }, + trace: 'retain-on-failure', + }, +}); diff --git a/applications/supplier-intake/scripts/fixtures.ts b/applications/supplier-intake/scripts/fixtures.ts new file mode 100644 index 00000000..de7316c5 --- /dev/null +++ b/applications/supplier-intake/scripts/fixtures.ts @@ -0,0 +1,29 @@ +import { mkdirSync, writeFileSync } from 'node:fs'; +import * as XLSX from 'xlsx'; +mkdirSync('static/fixtures', { recursive: true }); +writeFileSync( + 'static/fixtures/harbor-tools.csv', + 'Item Code,Description,Price,Available\nHBR101,"Copper watering can, 2L",24.50,18\nHBR102,Garden twine,6.75,42\n', +); +writeFileSync( + 'static/fixtures/needs-correction.csv', + 'Item Code,Description,Price,Available\nERR101,Hand rake,12.00,20\nERR102,Garden fork,ask,-2\n', +); +writeFileSync( + 'static/fixtures/conflicting-sku.csv', + 'Item Code,Description,Price,Available\nNEW999,New tote,9.50,15\nBASE001,Cotton market tote,12.50,40\n', +); +const workbook = XLSX.utils.book_new(); +XLSX.utils.book_append_sheet( + workbook, + XLSX.utils.aoa_to_sheet([ + ['Item Code', 'Description', 'Price', 'Available'], + ['FLW201', 'Woven table runner', '18.90', '12'], + ['FLW202', 'Cotton napkins, set of four', '11.25', '30'], + ]), + 'Catalog', +); +writeFileSync( + 'static/fixtures/meadow-textiles.xlsx', + XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }), +); diff --git a/applications/supplier-intake/sql/bootstrap.sql b/applications/supplier-intake/sql/bootstrap.sql new file mode 100644 index 00000000..59e1cac9 --- /dev/null +++ b/applications/supplier-intake/sql/bootstrap.sql @@ -0,0 +1,12 @@ +\set ON_ERROR_STOP on +\getenv migration_password PG_MIGRATION_PASSWORD +\getenv app_password PG_APP_PASSWORD +BEGIN; +CREATE ROLE intake_owner NOLOGIN; +CREATE ROLE intake_migrator LOGIN PASSWORD :'migration_password'; +GRANT intake_owner TO intake_migrator; +CREATE ROLE intake_app LOGIN PASSWORD :'app_password'; +CREATE SCHEMA intake AUTHORIZATION intake_owner; +REVOKE ALL ON SCHEMA intake FROM PUBLIC; +GRANT USAGE ON SCHEMA intake TO intake_app; +COMMIT; diff --git a/applications/supplier-intake/sql/grants.sql b/applications/supplier-intake/sql/grants.sql new file mode 100644 index 00000000..5a29cd8e --- /dev/null +++ b/applications/supplier-intake/sql/grants.sql @@ -0,0 +1,6 @@ +\set ON_ERROR_STOP on +BEGIN; +SET LOCAL ROLE intake_owner; +GRANT SELECT,INSERT,UPDATE ON intake.batches, intake.staging_rows TO intake_app; +GRANT SELECT,INSERT ON intake.products TO intake_app; +COMMIT; diff --git a/applications/supplier-intake/sql/migrations/001_initial.sql b/applications/supplier-intake/sql/migrations/001_initial.sql new file mode 100644 index 00000000..85ce0459 --- /dev/null +++ b/applications/supplier-intake/sql/migrations/001_initial.sql @@ -0,0 +1,26 @@ +\set ON_ERROR_STOP on +BEGIN; +SET LOCAL ROLE intake_owner; +CREATE TABLE intake.batches ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), fingerprint text NOT NULL UNIQUE CHECK (fingerprint ~ '^[a-f0-9]{64}$'), + filename text NOT NULL CHECK (length(filename) BETWEEN 1 AND 120), format text NOT NULL CHECK (format IN ('csv','xlsx')), + headers jsonb NOT NULL CHECK (jsonb_typeof(headers)='array'), row_count integer NOT NULL CHECK (row_count BETWEEN 1 AND 100), + supplier text NOT NULL DEFAULT '', mapping jsonb, revision integer NOT NULL DEFAULT 1 CHECK (revision>0), + status text NOT NULL DEFAULT 'uploaded' CHECK (status IN ('uploaded','reviewed','promoted')), + invalid_count integer NOT NULL DEFAULT 0 CHECK (invalid_count BETWEEN 0 AND 100), created_at timestamptz NOT NULL DEFAULT now() +); +CREATE TABLE intake.staging_rows ( + batch_id uuid NOT NULL REFERENCES intake.batches(id), row_number integer NOT NULL CHECK (row_number BETWEEN 2 AND 101), + raw_values jsonb NOT NULL, mapped_values jsonb, errors jsonb NOT NULL DEFAULT '[]', + PRIMARY KEY (batch_id,row_number), CHECK (jsonb_typeof(errors)='array') +); +CREATE TABLE intake.products ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), batch_id uuid NOT NULL, row_number integer NOT NULL, + supplier text NOT NULL CHECK (length(supplier) BETWEEN 2 AND 80), supplier_key text NOT NULL, + sku text NOT NULL CHECK (sku ~ '^[A-Z0-9_-]{1,32}$'), product text NOT NULL CHECK (length(product) BETWEEN 1 AND 120), + price_cents integer NOT NULL CHECK (price_cents BETWEEN 1 AND 99999999), stock integer NOT NULL CHECK (stock BETWEEN 0 AND 1000000), + FOREIGN KEY (batch_id,row_number) REFERENCES intake.staging_rows(batch_id,row_number), + UNIQUE (supplier_key,sku), UNIQUE (batch_id,row_number), CHECK (supplier_key=lower(supplier)) +); +CREATE INDEX batches_recent ON intake.batches(created_at DESC); +COMMIT; diff --git a/applications/supplier-intake/sql/seed.sql b/applications/supplier-intake/sql/seed.sql new file mode 100644 index 00000000..0146a61a --- /dev/null +++ b/applications/supplier-intake/sql/seed.sql @@ -0,0 +1,11 @@ +\set ON_ERROR_STOP on +BEGIN; +SET LOCAL ROLE intake_owner; +INSERT INTO intake.batches (id,fingerprint,filename,format,headers,row_count,supplier,status,revision) +VALUES ('10000000-0000-4000-8000-000000000001',repeat('0',64),'Built-in catalog.csv','csv','["SKU","Product","Price","Stock"]',2,'Willow Cooperative','promoted',3) ON CONFLICT DO NOTHING; +INSERT INTO intake.staging_rows (batch_id,row_number,raw_values,mapped_values) +VALUES ('10000000-0000-4000-8000-000000000001',2,'{"SKU":"BASE001"}','{"sku":"BASE001","product":"Cotton market tote","cents":1250,"stock":40}'), +('10000000-0000-4000-8000-000000000001',3,'{"SKU":"BASE002"}','{"sku":"BASE002","product":"Linen tea towel","cents":800,"stock":75}') ON CONFLICT DO NOTHING; +INSERT INTO intake.products (batch_id,row_number,supplier,supplier_key,sku,product,price_cents,stock) +SELECT batch_id,row_number,'Willow Cooperative','willow cooperative',mapped_values->>'sku',mapped_values->>'product',(mapped_values->>'cents')::integer,(mapped_values->>'stock')::integer FROM intake.staging_rows WHERE batch_id='10000000-0000-4000-8000-000000000001' ON CONFLICT DO NOTHING; +COMMIT; diff --git a/applications/supplier-intake/src/app.html b/applications/supplier-intake/src/app.html new file mode 100644 index 00000000..f0a402ea --- /dev/null +++ b/applications/supplier-intake/src/app.html @@ -0,0 +1,2 @@ + +%sveltekit.head%
%sveltekit.body%
diff --git a/applications/supplier-intake/src/hooks.server.ts b/applications/supplier-intake/src/hooks.server.ts new file mode 100644 index 00000000..75f8908b --- /dev/null +++ b/applications/supplier-intake/src/hooks.server.ts @@ -0,0 +1,21 @@ +import type { Handle } from '@sveltejs/kit'; +export const handle: Handle = async ({ event, resolve }) => { + const origin = process.env.ORIGIN; + if (!origin || !['127.0.0.1', 'localhost', '[::1]'].includes(new URL(origin).hostname)) + return new Response( + 'Configure a loopback ORIGIN for this trusted-operator example.', + { status: 503 }, + ); + if ( + event.request.headers.get('host') !== new URL(origin).host || + event.url.origin !== origin || + (!['GET', 'HEAD'].includes(event.request.method) && + event.request.headers.get('origin') !== origin) + ) + return new Response('Use the configured local supplier intake address.', { + status: 403, + }); + const response = await resolve(event); + response.headers.set('cache-control', 'no-store'); + return response; +}; diff --git a/applications/supplier-intake/src/lib/components/BatchReview.svelte b/applications/supplier-intake/src/lib/components/BatchReview.svelte new file mode 100644 index 00000000..68ba2636 --- /dev/null +++ b/applications/supplier-intake/src/lib/components/BatchReview.svelte @@ -0,0 +1,213 @@ + + +{data.batch.filename} | Supplier review +← Receiving desk +
+
+
02 / REVIEW THE SUPPLIER SHEET
+

{data.batch.filename}

+

+ {data.batch.format.toUpperCase()} · {data.batch.row_count} source rows · Revision {data + .batch.revision} +

+
+ {data.batch.status} +
+
+
+

Match the columns

+

+ The source values stay unchanged. These choices define how we validate the staged + rows. +

+
+ +
+ {#if $errors.supplier}

{$errors.supplier.join(' ')}

{/if} +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ {#if $message}
{$message}
{/if} +
+
+
03 / Promote together
+

One batch. One decision.

+

+ The entire batch must pass review. Promotion inserts every product in one + transaction; a conflicting supplier SKU leaves all its rows in staging. +

+
+ {data.batch.row_count - data.batch.invalid_count} / {data.batch.row_count} + {data.batch.status === 'uploaded' ? 'unreviewed' : 'valid rows'}{data.batch.invalid_count} errors +
+
+ +
+ {#if actionData && 'promotion' in actionData}
+ {actionData.promotion} +
{/if} + {#if data.batch.status === 'promoted'}

+ This batch is in the catalog. Replaying promotion does not add another copy. +

{/if} +
+
+
+ Reviewed rowsSource values +
+ + {#each data.rows as row} 0} + >{/each} +
Source rowSKU / productUSD priceStockReview
{row.row}{row.mapped?.sku ?? '—'} +
+ {row.mapped?.product ?? 'Awaiting valid review'} +
{row.mapped ? money(row.mapped.cents) : '—'}{row.mapped?.stock ?? '—'}{row.errors.length + ? `${row.errors.length} issues` + : data.batch.status === 'uploaded' + ? 'Not reviewed' + : 'Valid'}
+
+
+ + {#each data.batch.headers as header}{/each}{#each data.rows as row}{#each data.batch.headers as header}{/each}{/each} +
Row{header}
{row.row}{row.raw[header]}
+
+ Row corrections ({data.batch.invalid_count}){#if data.batch.invalid_count}

+ Correct these values in the supplier spreadsheet and upload the changed file, + or fix the column mapping above. +

+ {#each data.rows.filter((row) => row.errors.length) as row}

+ Row {row.row}: + {row.errors.join(' ')} +

{/each}{:else}

+ {data.batch.status === 'uploaded' + ? 'Validate the mapping to inspect row corrections.' + : 'No row corrections needed.'} +

{/if}
+
diff --git a/applications/supplier-intake/src/lib/components/Uploader.svelte b/applications/supplier-intake/src/lib/components/Uploader.svelte new file mode 100644 index 00000000..d725e32b --- /dev/null +++ b/applications/supplier-intake/src/lib/components/Uploader.svelte @@ -0,0 +1,62 @@ + + +
+{#if failure}{/if} diff --git a/applications/supplier-intake/src/lib/components/ui/badge/badge.svelte b/applications/supplier-intake/src/lib/components/ui/badge/badge.svelte new file mode 100644 index 00000000..ed853775 --- /dev/null +++ b/applications/supplier-intake/src/lib/components/ui/badge/badge.svelte @@ -0,0 +1,51 @@ + + + + + + {@render children?.()} + diff --git a/applications/supplier-intake/src/lib/components/ui/badge/index.ts b/applications/supplier-intake/src/lib/components/ui/badge/index.ts new file mode 100644 index 00000000..f05fb87f --- /dev/null +++ b/applications/supplier-intake/src/lib/components/ui/badge/index.ts @@ -0,0 +1,2 @@ +export { default as Badge } from './badge.svelte'; +export { badgeVariants, type BadgeVariant } from './badge.svelte'; diff --git a/applications/supplier-intake/src/lib/components/ui/button/button.svelte b/applications/supplier-intake/src/lib/components/ui/button/button.svelte new file mode 100644 index 00000000..fa113b41 --- /dev/null +++ b/applications/supplier-intake/src/lib/components/ui/button/button.svelte @@ -0,0 +1,89 @@ + + + + +{#if href} + + {@render children?.()} + +{:else} + +{/if} diff --git a/applications/supplier-intake/src/lib/components/ui/button/index.ts b/applications/supplier-intake/src/lib/components/ui/button/index.ts new file mode 100644 index 00000000..66c1087b --- /dev/null +++ b/applications/supplier-intake/src/lib/components/ui/button/index.ts @@ -0,0 +1,17 @@ +import Root, { + type ButtonProps, + type ButtonSize, + type ButtonVariant, + buttonVariants, +} from './button.svelte'; + +export { + Root, + type ButtonProps as Props, + // + Root as Button, + buttonVariants, + type ButtonProps, + type ButtonSize, + type ButtonVariant, +}; diff --git a/applications/supplier-intake/src/lib/components/ui/input/index.ts b/applications/supplier-intake/src/lib/components/ui/input/index.ts new file mode 100644 index 00000000..0888f0e0 --- /dev/null +++ b/applications/supplier-intake/src/lib/components/ui/input/index.ts @@ -0,0 +1,7 @@ +import Root from './input.svelte'; + +export { + Root, + // + Root as Input, +}; diff --git a/applications/supplier-intake/src/lib/components/ui/input/input.svelte b/applications/supplier-intake/src/lib/components/ui/input/input.svelte new file mode 100644 index 00000000..f2f5d3a7 --- /dev/null +++ b/applications/supplier-intake/src/lib/components/ui/input/input.svelte @@ -0,0 +1,48 @@ + + +{#if type === 'file'} + +{:else} + +{/if} diff --git a/applications/supplier-intake/src/lib/components/ui/tabs/index.ts b/applications/supplier-intake/src/lib/components/ui/tabs/index.ts new file mode 100644 index 00000000..ed2db460 --- /dev/null +++ b/applications/supplier-intake/src/lib/components/ui/tabs/index.ts @@ -0,0 +1,18 @@ +import Content from './tabs-content.svelte'; +import Trigger from './tabs-trigger.svelte'; +import Root from './tabs.svelte'; +import List, { tabsListVariants, type TabsListVariant } from './tabs-list.svelte'; + +export { + Root, + Content, + List, + Trigger, + tabsListVariants, + type TabsListVariant, + // + Root as Tabs, + Content as TabsContent, + List as TabsList, + Trigger as TabsTrigger, +}; diff --git a/applications/supplier-intake/src/lib/components/ui/tabs/tabs-content.svelte b/applications/supplier-intake/src/lib/components/ui/tabs/tabs-content.svelte new file mode 100644 index 00000000..8127b976 --- /dev/null +++ b/applications/supplier-intake/src/lib/components/ui/tabs/tabs-content.svelte @@ -0,0 +1,17 @@ + + + diff --git a/applications/supplier-intake/src/lib/components/ui/tabs/tabs-list.svelte b/applications/supplier-intake/src/lib/components/ui/tabs/tabs-list.svelte new file mode 100644 index 00000000..96cfee2b --- /dev/null +++ b/applications/supplier-intake/src/lib/components/ui/tabs/tabs-list.svelte @@ -0,0 +1,40 @@ + + + + + diff --git a/applications/supplier-intake/src/lib/components/ui/tabs/tabs-trigger.svelte b/applications/supplier-intake/src/lib/components/ui/tabs/tabs-trigger.svelte new file mode 100644 index 00000000..31837538 --- /dev/null +++ b/applications/supplier-intake/src/lib/components/ui/tabs/tabs-trigger.svelte @@ -0,0 +1,23 @@ + + + diff --git a/applications/supplier-intake/src/lib/components/ui/tabs/tabs.svelte b/applications/supplier-intake/src/lib/components/ui/tabs/tabs.svelte new file mode 100644 index 00000000..52803371 --- /dev/null +++ b/applications/supplier-intake/src/lib/components/ui/tabs/tabs.svelte @@ -0,0 +1,19 @@ + + + diff --git a/applications/supplier-intake/src/lib/mapping.ts b/applications/supplier-intake/src/lib/mapping.ts new file mode 100644 index 00000000..4c61a8df --- /dev/null +++ b/applications/supplier-intake/src/lib/mapping.ts @@ -0,0 +1,55 @@ +import { z } from 'zod'; +export const mappingSchema = z.object({ + batchId: z.uuid(), + revision: z.coerce.number().int().min(1), + supplier: z.string().trim().min(2).max(80), + sku: z.string().min(1).max(80), + product: z.string().min(1).max(80), + price: z.string().min(1).max(80), + stock: z.string().min(1).max(80), +}); +export type Mapping = z.infer; +export type Product = { sku: string; product: string; cents: number; stock: number }; +export type ReviewedRow = { + row: number; + raw: Record; + mapped: Product | null; + errors: string[]; +}; +export function mapRows(rows: Record[], mapping: Mapping): ReviewedRow[] { + const reviewed = rows.map((raw, index) => { + const errors: string[] = []; + const sku = (raw[mapping.sku] ?? '').trim().toUpperCase(); + const product = (raw[mapping.product] ?? '').trim(); + const price = (raw[mapping.price] ?? '').trim(); + const quantity = (raw[mapping.stock] ?? '').trim(); + if (!/^[A-Z0-9_-]{1,32}$/.test(sku)) + errors.push('SKU must contain 1–32 letters, digits, underscores or dashes.'); + if (!product || product.length > 120) + errors.push('Product name must contain 1–120 characters.'); + const [whole, decimal = ''] = price.split('.'); + const cents = Number(whole) * 100 + Number(decimal.padEnd(2, '0')); + if (!/^\d{1,6}(\.\d{1,2})?$/.test(price) || cents < 1 || cents > 99_999_999) + errors.push('Price must be a positive USD amount with at most two decimals.'); + const stock = Number(quantity); + if (!/^\d{1,7}$/.test(quantity) || stock > 1_000_000) + errors.push('Stock must be a whole number between 0 and 1,000,000.'); + return { + row: index + 2, + raw, + mapped: errors.length ? null : { sku, product, cents, stock }, + errors, + }; + }); + const counts = new Map(); + for (const row of reviewed) { + const sku = (row.raw[mapping.sku] ?? '').trim().toUpperCase(); + counts.set(sku, (counts.get(sku) ?? 0) + 1); + } + for (const row of reviewed) + if ((counts.get((row.raw[mapping.sku] ?? '').trim().toUpperCase()) ?? 0) > 1) { + row.errors.push('SKU occurs more than once in this file.'); + row.mapped = null; + } + return reviewed; +} diff --git a/applications/supplier-intake/src/lib/server/database.ts b/applications/supplier-intake/src/lib/server/database.ts new file mode 100644 index 00000000..6cf997c0 --- /dev/null +++ b/applications/supplier-intake/src/lib/server/database.ts @@ -0,0 +1,35 @@ +import { readFileSync } from 'node:fs'; +import { Pool } from 'pg'; +let pool: Pool | undefined; +export function databaseConfig() { + const connectionString = process.env.DATABASE_URL; + const caPath = process.env.DATABASE_CA_PATH; + if (!connectionString || !caPath) + throw new Error('DATABASE_URL and DATABASE_CA_PATH are required.'); + const url = new URL(connectionString); + if ( + !['postgresql:', 'postgres:'].includes(url.protocol) || + url.search || + url.hash || + process.env.NODE_TLS_REJECT_UNAUTHORIZED === '0' + ) + throw new Error('Use a direct Postgres URL without SSL query parameters.'); + return { + connectionString, + max: 5, + connectionTimeoutMillis: 10_000, + query_timeout: 15_000, + ssl: { + ca: readFileSync(caPath, 'utf8'), + rejectUnauthorized: true, + servername: url.hostname, + }, + }; +} +export function database() { + return (pool ??= new Pool(databaseConfig())); +} +export async function closeDatabase() { + await pool?.end(); + pool = undefined; +} diff --git a/applications/supplier-intake/src/lib/server/intake.ts b/applications/supplier-intake/src/lib/server/intake.ts new file mode 100644 index 00000000..420443ba --- /dev/null +++ b/applications/supplier-intake/src/lib/server/intake.ts @@ -0,0 +1,190 @@ +import { createHash } from 'node:crypto'; +import type { PoolClient } from 'pg'; +import { database } from './database'; +import { IntakeError, parseUpload } from './parsers'; +import { mapRows, mappingSchema, type Mapping, type ReviewedRow } from '../mapping'; +export class RevisionConflict extends IntakeError {} +export type Batch = { + id: string; + filename: string; + format: string; + headers: string[]; + supplier: string; + mapping: Mapping | null; + revision: number; + status: 'uploaded' | 'reviewed' | 'promoted'; + created_at: string; + row_count: number; + invalid_count: number; +}; +export async function transaction(work: (client: PoolClient) => Promise) { + const client = await database().connect(); + try { + await client.query('BEGIN'); + const result = await work(client); + await client.query('COMMIT'); + return result; + } catch (error) { + await client.query('ROLLBACK'); + throw error; + } finally { + client.release(); + } +} +export async function stageUpload(bytes: Buffer, filename: string) { + const parsed = parseUpload(bytes, filename); + const fingerprint = createHash('sha256').update(bytes).digest('hex'); + return transaction(async (client) => { + const inserted = await client.query( + 'INSERT INTO intake.batches (fingerprint,filename,format,headers,row_count) VALUES ($1,$2,$3,$4::jsonb,$5) ON CONFLICT (fingerprint) DO NOTHING RETURNING id', + [ + fingerprint, + filename.replace(/[\x00-\x1f\/\\]/g, '_').slice(0, 120), + parsed.format, + JSON.stringify(parsed.headers), + parsed.rows.length, + ], + ); + if (!inserted.rowCount) + return { + id: ( + await client.query('SELECT id FROM intake.batches WHERE fingerprint=$1', [ + fingerprint, + ]) + ).rows[0].id as string, + reused: true, + }; + const id = inserted.rows[0].id as string; + await client.query( + 'INSERT INTO intake.staging_rows (batch_id,row_number,raw_values) SELECT $1, ordinality + 1, value FROM jsonb_array_elements($2::jsonb) WITH ORDINALITY', + [id, JSON.stringify(parsed.rows)], + ); + return { id, reused: false }; + }); +} +export async function getBatch(id: string) { + return transaction(async (client) => { + await client.query('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY'); + const batch = ( + await client.query( + "SELECT *, to_char(created_at AT TIME ZONE 'UTC','YYYY-MM-DD HH24:MI') AS created_at FROM intake.batches WHERE id=$1", + [id], + ) + ).rows[0]; + if (!batch) throw new IntakeError('Batch not found.'); + const rows = ( + await client.query<{ + row: number; + raw: Record; + mapped: ReviewedRow['mapped']; + errors: string[]; + }>( + 'SELECT row_number AS row, raw_values AS raw, mapped_values AS mapped, errors FROM intake.staging_rows WHERE batch_id=$1 ORDER BY row_number', + [id], + ) + ).rows; + return { batch, rows }; + }); +} +export async function reviewBatch(input: Mapping) { + const mapping = mappingSchema.parse(input); + return transaction(async (client) => { + const batch = ( + await client.query('SELECT * FROM intake.batches WHERE id=$1 FOR UPDATE', [ + mapping.batchId, + ]) + ).rows[0]; + if (!batch) throw new IntakeError('Batch not found.'); + if (batch.status === 'promoted') + throw new RevisionConflict('This batch has already been promoted.'); + if (batch.revision !== mapping.revision) + throw new RevisionConflict( + 'This review changed in another tab. Reload before reviewing again.', + ); + const columns = [mapping.sku, mapping.product, mapping.price, mapping.stock]; + if (columns.some((v) => !batch.headers.includes(v)) || new Set(columns).size !== 4) + throw new IntakeError('Map each field to a different existing column.'); + const raw = ( + await client.query( + 'SELECT raw_values FROM intake.staging_rows WHERE batch_id=$1 ORDER BY row_number', + [mapping.batchId], + ) + ).rows.map((row) => row.raw_values as Record); + const rows = mapRows(raw, mapping); + await client.query( + 'UPDATE intake.staging_rows AS s SET mapped_values=v.mapped, errors=v.errors FROM jsonb_to_recordset($2::jsonb) AS v(row integer,mapped jsonb,errors jsonb) WHERE s.batch_id=$1 AND s.row_number=v.row', + [mapping.batchId, JSON.stringify(rows)], + ); + const invalidCount = rows.filter((row) => row.errors.length).length; + await client.query( + "UPDATE intake.batches SET supplier=$2,mapping=$3::jsonb,status='reviewed',invalid_count=$4,revision=revision+1 WHERE id=$1", + [mapping.batchId, mapping.supplier, JSON.stringify(mapping), invalidCount], + ); + return { revision: batch.revision + 1, invalidCount }; + }); +} +export async function promoteBatch(id: string, revision: number) { + try { + return await transaction(async (client) => { + const batch = ( + await client.query('SELECT * FROM intake.batches WHERE id=$1 FOR UPDATE', [ + id, + ]) + ).rows[0]; + if (!batch) throw new IntakeError('Batch not found.'); + if (batch.status === 'promoted') return { count: batch.row_count, reused: true }; + if (batch.revision !== revision) + throw new RevisionConflict('This review changed. Reload before promoting.'); + if (batch.status !== 'reviewed' || batch.invalid_count) + throw new IntakeError( + 'Review every row successfully before promoting this batch.', + ); + const invalid = await client.query( + 'SELECT 1 FROM intake.staging_rows WHERE batch_id=$1 AND (mapped_values IS NULL OR jsonb_array_length(errors)>0) LIMIT 1', + [id], + ); + if (invalid.rowCount) + throw new IntakeError('Every row needs a valid reviewed mapping.'); + await client.query( + "INSERT INTO intake.products (batch_id,row_number,supplier,supplier_key,sku,product,price_cents,stock) SELECT batch_id,row_number,$2,lower($2),mapped_values->>'sku',mapped_values->>'product',(mapped_values->>'cents')::integer,(mapped_values->>'stock')::integer FROM intake.staging_rows WHERE batch_id=$1 ORDER BY row_number", + [id, batch.supplier], + ); + await client.query( + "UPDATE intake.batches SET status='promoted',revision=revision+1 WHERE id=$1", + [id], + ); + return { count: batch.row_count, reused: false }; + }); + } catch (error) { + if ((error as { code?: string }).code === '23505') + throw new IntakeError( + 'A supplier SKU already exists in the catalog. No rows were imported.', + ); + throw error; + } +} +export async function dashboard() { + return transaction(async (client) => { + await client.query('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY'); + const batches = ( + await client.query( + "SELECT *, to_char(created_at AT TIME ZONE 'UTC','YYYY-MM-DD HH24:MI') AS created_at FROM intake.batches ORDER BY intake.batches.created_at DESC,id LIMIT 12", + ) + ).rows; + const products = ( + await client.query<{ + supplier: string; + sku: string; + product: string; + price_cents: number; + stock: number; + }>( + 'SELECT supplier,sku,product,price_cents,stock FROM intake.products ORDER BY supplier,sku LIMIT 200', + ) + ).rows; + const count = ( + await client.query('SELECT count(*)::integer AS total FROM intake.products') + ).rows[0].total as number; + return { batches, products, count }; + }); +} diff --git a/applications/supplier-intake/src/lib/server/parsers.ts b/applications/supplier-intake/src/lib/server/parsers.ts new file mode 100644 index 00000000..3b25a5dd --- /dev/null +++ b/applications/supplier-intake/src/lib/server/parsers.ts @@ -0,0 +1,91 @@ +import Papa from 'papaparse'; +import * as XLSX from 'xlsx'; +export const MAX_BYTES = 512 * 1024; +export const MAX_ROWS = 100; +export class IntakeError extends Error {} +export type Parsed = { + format: 'csv' | 'xlsx'; + headers: string[]; + rows: Record[]; +}; +export function parseUpload(bytes: Buffer, filename: string): Parsed { + if (!bytes.length || bytes.length > MAX_BYTES) + throw new IntakeError('Choose a non-empty file no larger than 512 KiB.'); + let matrix: string[][]; + let format: Parsed['format']; + if (/\.csv$/i.test(filename)) { + format = 'csv'; + let text: string; + try { + text = new TextDecoder('utf-8', { fatal: true }).decode(bytes); + } catch { + throw new IntakeError('CSV must use UTF-8 encoding.'); + } + const parsed = Papa.parse(text, { + header: false, + delimiter: ',', + dynamicTyping: false, + skipEmptyLines: 'greedy', + preview: MAX_ROWS + 2, + }); + if (parsed.errors.length) + throw new IntakeError('CSV contains invalid quoting or inconsistent fields.'); + matrix = parsed.data; + } else if (/\.xlsx$/i.test(filename)) { + format = 'xlsx'; + if (bytes[0] !== 0x50 || bytes[1] !== 0x4b) + throw new IntakeError('Choose a genuine XLSX workbook.'); + let workbook: XLSX.WorkBook; + try { + workbook = XLSX.read(bytes, { + type: 'buffer', + sheetRows: MAX_ROWS + 2, + cellFormula: true, + }); + } catch { + throw new IntakeError('The XLSX workbook could not be read.'); + } + if (workbook.SheetNames.length !== 1) + throw new IntakeError('Use a workbook with exactly one worksheet.'); + const sheet = workbook.Sheets[workbook.SheetNames[0]]; + const range = XLSX.utils.decode_range(sheet['!fullref'] ?? sheet['!ref'] ?? 'A1'); + if (range.e.c >= 16 || range.e.r > MAX_ROWS) + throw new IntakeError('Use at most 100 data rows and 16 columns.'); + if ( + Object.entries(sheet).some(([address, cell]) => !address.startsWith('!') && cell.f) + ) + throw new IntakeError('Formula cells are not accepted; paste their values first.'); + matrix = XLSX.utils.sheet_to_json(sheet, { + header: 1, + raw: false, + defval: '', + blankrows: false, + }); + } else throw new IntakeError('Choose a CSV or XLSX file.'); + if (matrix.length < 2 || matrix.length > MAX_ROWS + 1) + throw new IntakeError('Use between 1 and 100 data rows.'); + const headers = matrix[0].map((v) => + String(v) + .trim() + .replace(/^\uFEFF/, ''), + ); + if ( + !headers.length || + headers.length > 16 || + headers.some((v) => !v || v.length > 80) || + new Set(headers).size !== headers.length + ) + throw new IntakeError( + 'Use 1–16 distinct, non-empty column headers of at most 80 characters.', + ); + const rows = matrix.slice(1).map((row) => { + if (row.length !== headers.length) + throw new IntakeError( + 'Every row must have the same number of fields as the header.', + ); + if (row.some((v) => String(v).length > 500)) + throw new IntakeError('Each cell may contain at most 500 characters.'); + return Object.fromEntries(headers.map((header, i) => [header, String(row[i] ?? '')])); + }); + return { format, headers, rows }; +} diff --git a/applications/supplier-intake/src/lib/utils.ts b/applications/supplier-intake/src/lib/utils.ts new file mode 100644 index 00000000..51be06c7 --- /dev/null +++ b/applications/supplier-intake/src/lib/utils.ts @@ -0,0 +1,13 @@ +import { clsx, type ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} +export type WithoutChild = T extends { child?: unknown } ? Omit : T; +export type WithoutChildren = T extends { children?: unknown } + ? Omit + : T; +export type WithoutChildrenOrChild = WithoutChildren>; +export type WithElementRef = T & { + ref?: U | null; +}; diff --git a/applications/supplier-intake/src/routes/+layout.svelte b/applications/supplier-intake/src/routes/+layout.svelte new file mode 100644 index 00000000..9d54db7c --- /dev/null +++ b/applications/supplier-intake/src/routes/+layout.svelte @@ -0,0 +1,11 @@ + + +
+ {@render children()} +
+ Supplier intake · SvelteKit, Superforms and Postgres · One trusted operator +
+
diff --git a/applications/supplier-intake/src/routes/+page.server.ts b/applications/supplier-intake/src/routes/+page.server.ts new file mode 100644 index 00000000..d0aedade --- /dev/null +++ b/applications/supplier-intake/src/routes/+page.server.ts @@ -0,0 +1,2 @@ +import { dashboard } from '$lib/server/intake'; +export const load = async () => dashboard(); diff --git a/applications/supplier-intake/src/routes/+page.svelte b/applications/supplier-intake/src/routes/+page.svelte new file mode 100644 index 00000000..1fcb7694 --- /dev/null +++ b/applications/supplier-intake/src/routes/+page.svelte @@ -0,0 +1,124 @@ + + +Supplier intake | Receiving desk +
+
+
RECEIVING DESK / SUPPLIER INTAKE
+

A spreadsheet isn’t a catalog. Yet.

+

+ Bring in a supplier file, match its columns, and inspect the rows before anything + reaches the catalog. +

+
+ {data.count} catalog products +
+
+
+
01 / Receive a file
+

Start with the supplier sheet

+

+ Files stay in Postgres staging until you approve a valid review. Uploading the same + bytes opens the existing batch. +

+ + +
+
+
The route to the catalog
+

Receive → Review → Promote

+

+ Match SKU, product, USD price and available stock to the supplier’s headers. Review + checks every row and keeps mistakes visible. +

+
+ Integer centsNo partial importsReplay-safe batches +
+

+ A duplicate supplier SKU stops the entire promotion. Correct the source spreadsheet + and upload it again; the existing catalog remains intact. +

+
+
+Intake batchesLive catalog +
+

On the receiving desk

+

Latest 12 batches. Reopen any batch to resume its review.

+
+ + {#each data.batches as batch}{/each} +
FileSupplierRowsStatus
{batch.filename} +
+ {batch.format.toUpperCase()} · {batch.created_at} UTC +
{batch.supplier || 'Not reviewed'}{batch.row_count}{batch.status}
+
+
+
+

Ready to order

+

+ {data.count > 200 + ? 'First 200 products, sorted by supplier and SKU.' + : `${data.count} products in the catalog.`} +

+
+ + {#each data.products as product}{/each} +
Supplier / SKUProductUSD priceAvailable
{product.supplier} +
{product.sku}
{product.product}{money(product.price_cents)}{product.stock}
+
+
+
diff --git a/applications/supplier-intake/src/routes/api/upload/+server.ts b/applications/supplier-intake/src/routes/api/upload/+server.ts new file mode 100644 index 00000000..224f4348 --- /dev/null +++ b/applications/supplier-intake/src/routes/api/upload/+server.ts @@ -0,0 +1,22 @@ +import { json } from '@sveltejs/kit'; +import { stageUpload } from '$lib/server/intake'; +import { IntakeError } from '$lib/server/parsers'; +import type { RequestHandler } from './$types'; +export const POST: RequestHandler = async ({ request }) => { + try { + const data = await request.formData(); + const file = data.get('file'); + if (!(file instanceof File) || data.getAll('file').length !== 1) + return json({ error: 'Choose one CSV or XLSX file.' }, { status: 400 }); + const staged = await stageUpload(Buffer.from(await file.arrayBuffer()), file.name); + return json(staged); + } catch (error) { + if (error instanceof IntakeError) + return json({ error: error.message }, { status: 400 }); + console.error('Supplier upload failed.'); + return json( + { error: 'The upload could not be staged. Check the database connection.' }, + { status: 503 }, + ); + } +}; diff --git a/applications/supplier-intake/src/routes/batches/[id]/+page.server.ts b/applications/supplier-intake/src/routes/batches/[id]/+page.server.ts new file mode 100644 index 00000000..13a0b73e --- /dev/null +++ b/applications/supplier-intake/src/routes/batches/[id]/+page.server.ts @@ -0,0 +1,87 @@ +import { error, fail } from '@sveltejs/kit'; +import { superValidate, message } from 'sveltekit-superforms'; +import { zod4 } from 'sveltekit-superforms/adapters'; +import { z } from 'zod'; +import { mappingSchema } from '$lib/mapping'; +import { + getBatch, + reviewBatch, + promoteBatch, + RevisionConflict, +} from '$lib/server/intake'; +import { IntakeError } from '$lib/server/parsers'; +import type { Actions, PageServerLoad } from './$types'; +export const load: PageServerLoad = async ({ params }) => { + if (!z.uuid().safeParse(params.id).success) error(404, 'Batch not found.'); + const data = await getBatch(params.id).catch((err) => { + if (err instanceof IntakeError) error(404, 'Batch not found.'); + throw err; + }); + const { batch } = data; + const headers = batch.headers; + const values = { + batchId: batch.id, + revision: batch.revision, + supplier: batch.supplier || 'Harbor Supply', + sku: headers[0], + product: headers[1], + price: headers[2], + stock: headers[3], + ...batch.mapping, + }; + values.revision = batch.revision; + return { + ...data, + form: await superValidate(values, zod4(mappingSchema), { errors: false }), + }; +}; +export const actions: Actions = { + review: async ({ request, params }) => { + const form = await superValidate(request, zod4(mappingSchema)); + if (!form.valid) return fail(400, { form }); + if (form.data.batchId !== params.id) + return message(form, 'Review the batch at this address.', { status: 400 }); + try { + const result = await reviewBatch(form.data); + form.data.revision = result.revision; + return message( + form, + result.invalidCount + ? `${result.invalidCount} ${result.invalidCount === 1 ? 'row needs' : 'rows need'} correction. Nothing has been promoted.` + : 'Every row is valid. The batch is ready for promotion.', + ); + } catch (err) { + if (err instanceof IntakeError) + return message(form, err.message, { + status: err instanceof RevisionConflict ? 409 : 400, + }); + console.error('Supplier review failed.'); + return message(form, 'Review could not be saved. Check the database connection.', { + status: 503, + }); + } + }, + promote: async ({ request, params }) => { + const form = await request.formData(); + const parsed = z + .object({ revision: z.coerce.number().int().positive() }) + .safeParse(Object.fromEntries(form)); + if (!parsed.success || !z.uuid().safeParse(params.id).success) + return fail(400, { promotion: 'Reload the batch before promoting.' }); + try { + const result = await promoteBatch(params.id, parsed.data.revision); + return { + promotion: result.reused + ? 'This batch was already imported; no duplicates were added.' + : `${result.count} products promoted together.`, + }; + } catch (err) { + if (err instanceof IntakeError) + return fail(err instanceof RevisionConflict ? 409 : 400, { + promotion: err.message, + }); + console.error('Supplier promotion failed.'); + return fail(503, { promotion: 'Promotion failed. No rows were imported.' }); + } + }, +}; diff --git a/applications/supplier-intake/src/routes/batches/[id]/+page.svelte b/applications/supplier-intake/src/routes/batches/[id]/+page.svelte new file mode 100644 index 00000000..719c36f2 --- /dev/null +++ b/applications/supplier-intake/src/routes/batches/[id]/+page.svelte @@ -0,0 +1,7 @@ + + +{#key data.batch.id}{/key} diff --git a/applications/supplier-intake/src/styles.css b/applications/supplier-intake/src/styles.css new file mode 100644 index 00000000..70c8c5b9 --- /dev/null +++ b/applications/supplier-intake/src/styles.css @@ -0,0 +1,236 @@ +@import 'tailwindcss'; +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--foreground); + --color-popover: var(--card); + --color-popover-foreground: var(--foreground); + --color-primary: var(--primary); + --color-primary-foreground: #fff; + --color-secondary: #ede7dc; + --color-secondary-foreground: var(--foreground); + --color-muted: #f0ece4; + --color-muted-foreground: #726e64; + --color-accent: #f3e8d1; + --color-accent-foreground: var(--foreground); + --color-destructive: #b2382c; + --color-border: #dfd9cc; + --color-input: #d5cebf; + --color-ring: #9f6327; + --radius-sm: 0.35rem; + --radius-md: 0.5rem; + --radius-lg: 0.7rem; + --radius-xl: 1rem; +} +:root { + --background: #f7f4ed; + --foreground: #283c39; + --card: #fffefa; + --primary: #866024; + font-family: Arial, sans-serif; + color: var(--foreground); + background: var(--background); +} +body { + margin: 0; +} +h1, +h2, +h3 { + font-family: Georgia, serif; + letter-spacing: -0.02em; +} +button, +a, +input, +select { + touch-action: manipulation; +} +button { + cursor: pointer; +} +.shell { + max-width: 1160px; + margin: 0 auto; + padding: 36px 24px; +} +.eyebrow { + font-size: 11px; + letter-spacing: 0.16em; + color: #837248; + font-weight: 700; +} +.masthead { + display: flex; + justify-content: space-between; + align-items: center; + gap: 24px; + padding-bottom: 30px; + border-bottom: 1px solid #ded6c6; + margin-bottom: 28px; +} +.masthead h1 { + font-size: 46px; + line-height: 1.05; + margin: 12px 0; +} +.masthead p { + max-width: 650px; + color: #726e64; + line-height: 1.6; +} +.step { + font-size: 12px; + color: #837248; + letter-spacing: 0.12em; + text-transform: uppercase; + margin-bottom: 10px; +} +.layout { + display: grid; + grid-template-columns: 1fr 1.15fr; + gap: 24px; +} +.panel { + background: var(--card); + border: 1px solid #dfd9cc; + border-radius: 12px; + padding: 24px; + margin-bottom: 24px; +} +.panel h2 { + font-size: 26px; + margin: 0 0 12px; +} +.muted { + color: #726e64; + font-size: 14px; + line-height: 1.6; +} +.help-links { + display: flex; + flex-wrap: wrap; + gap: 16px; + margin: 18px 0 0; + font-size: 13px; + text-decoration: underline; +} +.mapping-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; +} +label { + display: block; + font-size: 13px; + font-weight: 600; + margin-bottom: 7px; +} +select { + width: 100%; + padding: 10px; + border: 1px solid #d5cebf; + border-radius: 6px; + background: #fff; + font-size: 14px; +} +.message { + padding: 13px 16px; + background: #edf3eb; + border: 1px solid #c9d9c3; + border-radius: 8px; + margin: 16px 0; + font-size: 14px; +} +.error { + color: #a13526; + font-size: 13px; +} +.scroll { + overflow: auto; +} +table { + width: 100%; + border-collapse: collapse; + font-size: 13px; +} +th { + text-align: left; + font-weight: 600; + color: #726e64; +} +th, +td { + padding: 13px 12px; + border-bottom: 1px solid #e9e3d7; + vertical-align: top; +} +td { + max-width: 280px; + overflow-wrap: anywhere; +} +.catalog-table { + min-width: 640px; +} +.row-error { + background: #fff6ee; +} +.row-error p { + max-width: 250px; +} +.summary { + display: flex; + gap: 14px; + flex-wrap: wrap; + margin: 18px 0; +} +.pill { + background: #efe8d6; + border-radius: 100px; + padding: 8px 14px; + font-size: 12px; +} +.accordion-trigger { + width: 100%; + text-align: left; + padding: 16px 0; + font-weight: 600; + display: flex; + justify-content: space-between; +} +.accordion-trigger::after { + content: '+'; +} +.accordion-trigger[data-state='open']::after { + content: '−'; +} +footer { + padding: 28px 0 8px; + color: #7b766a; + font-size: 12px; +} +.uppy-Dashboard-inner { + border-radius: 10px !important; + border-color: #dfd9cc !important; +} +@media (max-width: 760px) { + .shell { + padding: 24px 16px; + } + .layout { + grid-template-columns: 1fr; + } + .masthead { + display: block; + } + .masthead h1 { + font-size: 36px; + } + .mapping-grid { + grid-template-columns: 1fr; + } + .panel { + padding: 18px; + } +} diff --git a/applications/supplier-intake/static/fixtures/conflicting-sku.csv b/applications/supplier-intake/static/fixtures/conflicting-sku.csv new file mode 100644 index 00000000..7dfba21a --- /dev/null +++ b/applications/supplier-intake/static/fixtures/conflicting-sku.csv @@ -0,0 +1,3 @@ +Item Code,Description,Price,Available +NEW999,New tote,9.50,15 +BASE001,Cotton market tote,12.50,40 diff --git a/applications/supplier-intake/static/fixtures/harbor-tools.csv b/applications/supplier-intake/static/fixtures/harbor-tools.csv new file mode 100644 index 00000000..d924b8cc --- /dev/null +++ b/applications/supplier-intake/static/fixtures/harbor-tools.csv @@ -0,0 +1,3 @@ +Item Code,Description,Price,Available +HBR101,"Copper watering can, 2L",24.50,18 +HBR102,Garden twine,6.75,42 diff --git a/applications/supplier-intake/static/fixtures/meadow-textiles.xlsx b/applications/supplier-intake/static/fixtures/meadow-textiles.xlsx new file mode 100644 index 00000000..9e72704b Binary files /dev/null and b/applications/supplier-intake/static/fixtures/meadow-textiles.xlsx differ diff --git a/applications/supplier-intake/static/fixtures/needs-correction.csv b/applications/supplier-intake/static/fixtures/needs-correction.csv new file mode 100644 index 00000000..4e6a761c --- /dev/null +++ b/applications/supplier-intake/static/fixtures/needs-correction.csv @@ -0,0 +1,3 @@ +Item Code,Description,Price,Available +ERR101,Hand rake,12.00,20 +ERR102,Garden fork,ask,-2 diff --git a/applications/supplier-intake/svelte.config.js b/applications/supplier-intake/svelte.config.js new file mode 100644 index 00000000..e971c38e --- /dev/null +++ b/applications/supplier-intake/svelte.config.js @@ -0,0 +1,3 @@ +import adapter from '@sveltejs/adapter-node'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; +export default { preprocess: vitePreprocess(), kit: { adapter: adapter() } }; diff --git a/applications/supplier-intake/tests/browser/intake.spec.ts b/applications/supplier-intake/tests/browser/intake.spec.ts new file mode 100644 index 00000000..357b5b0e --- /dev/null +++ b/applications/supplier-intake/tests/browser/intake.spec.ts @@ -0,0 +1,229 @@ +import { test, expect, type Page } from '@playwright/test'; +import { spawn, type ChildProcess } from 'node:child_process'; +import { once } from 'node:events'; +import { mkdirSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { database, closeDatabase } from '../../src/lib/server/database'; +const base = process.env.ORIGIN ?? 'http://127.0.0.1:3001'; +const evidence = process.env.EVIDENCE_DIR ?? 'test-results'; +let server: ChildProcess; +async function startServer() { + server = spawn(process.execPath, ['--env-file=.env', 'build/index.js'], { + stdio: 'ignore', + env: { ...process.env, SHUTDOWN_TIMEOUT: '2' }, + }); + for (let i = 0; i < 80; i++) { + if (server.exitCode !== null) throw new Error('Application exited during startup.'); + try { + if ((await fetch(base)).ok) return; + } catch {} + await new Promise((resolve) => setTimeout(resolve, 250)); + } + throw new Error('Application did not become ready.'); +} +async function stopServer() { + if (server && server.exitCode === null) { + const exited = once(server, 'exit'); + server.kill('SIGTERM'); + await exited; + } +} +test.beforeAll(async () => { + mkdirSync(evidence, { recursive: true }); + await startServer(); +}); +test.afterAll(async () => { + await stopServer(); + await closeDatabase(); +}); +async function upload(page: Page, filename: string) { + await page.goto('/'); + await page + .locator('input[type="file"]') + .first() + .setInputFiles(join('static/fixtures', filename)); + await page.getByRole('button', { name: /Upload 1 file/ }).click(); + await expect(page).toHaveURL(/\/batches\/[a-f0-9-]+/); + await expect(page.getByRole('heading', { name: filename, exact: true })).toBeVisible(); + return new URL(page.url()).pathname.split('/').at(-1)!; +} +test('CSV and XLSX upload, mapped review, atomic promotion, row errors and real process restart', async ({ + page, + request, +}) => { + await page.goto('/'); + await expect( + page.getByRole('heading', { name: 'A spreadsheet isn’t a catalog. Yet.' }), + ).toBeVisible(); + await expect(page.locator('.uppy-Dashboard-inner')).toBeVisible(); + await page.screenshot({ path: join(evidence, 'receiving-desk.png'), fullPage: true }); + const csv = await upload(page, 'harbor-tools.csv'); + await page.getByLabel('Supplier name', { exact: true }).fill('Harbor Supply'); + await page.getByLabel('USD price column', { exact: true }).selectOption('Description'); + await page.getByRole('button', { name: 'Validate review', exact: true }).click(); + await expect( + page + .getByRole('status') + .filter({ hasText: 'Map each field to a different existing column.' }), + ).toBeVisible(); + await page.getByLabel('USD price column', { exact: true }).selectOption('Price'); + await page.getByRole('button', { name: 'Validate review', exact: true }).click(); + await expect( + page.getByRole('status').filter({ hasText: 'Every row is valid.' }), + ).toBeVisible(); + await expect(page.getByRole('cell', { name: /Copper watering can, 2L/ })).toBeVisible(); + await page.getByRole('tab', { name: 'Source values', exact: true }).click(); + await expect(page.getByRole('cell', { name: '24.50', exact: true })).toBeVisible(); + await page.getByRole('tab', { name: 'Reviewed rows', exact: true }).click(); + await page.getByRole('button', { name: 'Promote batch', exact: true }).click(); + await expect( + page.getByRole('status').filter({ hasText: '2 products promoted together.' }), + ).toBeVisible(); + await page.screenshot({ path: join(evidence, 'csv-promoted.png'), fullPage: true }); + await page.getByRole('button', { name: 'Check imported batch', exact: true }).click(); + await expect( + page.getByRole('status').filter({ hasText: 'already imported' }), + ).toBeVisible(); + expect( + ( + await database().query( + 'SELECT count(*)::integer AS count FROM intake.products WHERE batch_id=$1', + [csv], + ) + ).rows[0].count, + ).toBe(2); + expect(await upload(page, 'harbor-tools.csv')).toBe(csv); + await expect(page.getByText('promoted', { exact: true })).toBeVisible(); + + const xlsx = await upload(page, 'meadow-textiles.xlsx'); + await page.getByLabel('Supplier name', { exact: true }).fill('Meadow Textiles'); + await page.getByRole('button', { name: 'Validate review', exact: true }).click(); + await expect( + page.getByRole('status').filter({ hasText: 'Every row is valid.' }), + ).toBeVisible(); + await expect(page.getByRole('cell', { name: /Woven table runner/ })).toBeVisible(); + await page.getByRole('button', { name: 'Promote batch', exact: true }).click(); + await expect( + page.getByRole('status').filter({ hasText: '2 products promoted together.' }), + ).toBeVisible(); + await page.screenshot({ path: join(evidence, 'xlsx-promoted.png'), fullPage: true }); + expect( + ( + await database().query( + 'SELECT count(*)::integer AS count FROM intake.products WHERE batch_id=$1', + [xlsx], + ) + ).rows[0].count, + ).toBe(2); + + const bad = await upload(page, 'needs-correction.csv'); + await page.getByRole('button', { name: 'Validate review', exact: true }).click(); + await expect( + page.getByRole('status').filter({ hasText: '1 row needs correction.' }), + ).toBeVisible(); + await page.getByRole('button', { name: /^Row corrections \(1\)/ }).click(); + await expect(page.getByText(/Price must be a positive USD amount/)).toBeVisible(); + await expect( + page.getByRole('button', { name: 'Promote batch', exact: true }), + ).toBeDisabled(); + expect( + ( + await request.post(`/batches/${bad}?/promote`, { + form: { revision: '2' }, + headers: { Origin: base, Accept: 'text/html' }, + }) + ).status(), + ).toBe(400); + expect( + ( + await database().query( + 'SELECT count(*)::integer AS count FROM intake.products WHERE batch_id=$1', + [bad], + ) + ).rows[0].count, + ).toBe(0); + await page.setViewportSize({ width: 390, height: 844 }); + await page.screenshot({ + path: join(evidence, 'row-errors-mobile.png'), + fullPage: true, + }); + await page.setViewportSize({ width: 1440, height: 1050 }); + + const conflict = await upload(page, 'conflicting-sku.csv'); + await page.getByLabel('Supplier name', { exact: true }).fill('Willow Cooperative'); + await page.getByRole('button', { name: 'Validate review', exact: true }).click(); + await expect( + page.getByRole('status').filter({ hasText: 'Every row is valid.' }), + ).toBeVisible(); + await page.getByRole('button', { name: 'Promote batch', exact: true }).click(); + await expect( + page.getByRole('status').filter({ hasText: 'No rows were imported.' }), + ).toBeVisible(); + expect( + ( + await database().query( + 'SELECT count(*)::integer AS count FROM intake.products WHERE batch_id=$1', + [conflict], + ) + ).rows[0].count, + ).toBe(0); + expect( + ( + await database().query( + "SELECT count(*)::integer AS count FROM intake.products WHERE sku='NEW999'", + ) + ).rows[0].count, + ).toBe(0); + + const previousPid = server.pid; + await stopServer(); + await startServer(); + expect(server.pid).not.toBe(previousPid); + writeFileSync( + join(evidence, 'restart.json'), + JSON.stringify( + { previousPid, replacementPid: server.pid, csvBatch: csv, xlsxBatch: xlsx }, + null, + 2, + ) + '\n', + ); + await page.goto(`/batches/${csv}`); + await expect(page.getByText('promoted', { exact: true })).toBeVisible(); + await page.goto(`/batches/${xlsx}`); + await expect(page.getByText('promoted', { exact: true })).toBeVisible(); + await page.goto('/'); + await page.getByRole('tab', { name: 'Live catalog', exact: true }).click(); + await expect(page.getByRole('cell', { name: /Copper watering can, 2L/ })).toBeVisible(); + await expect(page.getByRole('cell', { name: /Woven table runner/ })).toBeVisible(); + expect( + ( + await request.post('/api/upload', { + multipart: { + file: { + name: 'ambiguous.csv', + mimeType: 'text/csv', + buffer: Buffer.from('A,A\nx,y\n'), + }, + }, + headers: { Origin: base, Accept: 'text/html' }, + }) + ).status(), + ).toBe(400); + expect( + ( + await request.post('/api/upload', { + form: {}, + headers: { Origin: 'https://foreign.example' }, + }) + ).status(), + ).toBe(403); + expect((await request.post('/api/upload', { form: {} })).status()).toBe(403); + expect( + ( + await request.post('/api/upload', { + form: {}, + headers: { Origin: base, Host: 'foreign.example' }, + }) + ).status(), + ).toBe(403); +}); diff --git a/applications/supplier-intake/tests/database.test.ts b/applications/supplier-intake/tests/database.test.ts new file mode 100644 index 00000000..7e6da986 --- /dev/null +++ b/applications/supplier-intake/tests/database.test.ts @@ -0,0 +1,255 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { randomUUID } from 'node:crypto'; +import { lookup } from 'node:dns/promises'; +import { mkdtempSync, readFileSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { execFileSync } from 'node:child_process'; +import { Pool } from 'pg'; +import { database, databaseConfig, closeDatabase } from '../src/lib/server/database'; +import { + stageUpload, + reviewBatch, + promoteBatch, + getBatch, + RevisionConflict, +} from '../src/lib/server/intake'; +import { IntakeError } from '../src/lib/server/parsers'; +import type { Mapping } from '../src/lib/mapping'; +const mapping = (id: string, supplier: string): Mapping => ({ + batchId: id, + revision: 1, + supplier, + sku: 'Code', + product: 'Name', + price: 'USD', + stock: 'Qty', +}); +test('managed intake: duplicate requests, competing reviews, atomic rollback, grants and verified TLS', async (t) => { + try { + const identity = (await database().query('SELECT current_user,version() AS version')) + .rows[0]; + assert.equal(identity.current_user, 'intake_app'); + console.log(JSON.stringify(identity)); + const key = randomUUID().slice(0, 8).toUpperCase(); + const csv = Buffer.from( + `Code,Name,USD,Qty\nA_${key},Tea towel,8.00,4\nB_${key},Market tote,12.50,9\n`, + ); + const staged = await Promise.all([ + stageUpload(csv, 'concurrent.csv'), + stageUpload(csv, 'concurrent.csv'), + ]); + assert.equal(staged[0].id, staged[1].id); + assert.equal(staged.filter((value) => !value.reused).length, 1); + const id = staged[0].id; + const supplier = `Integration ${key}`; + assert.equal((await getBatch(id)).rows.length, 2); + await assert.rejects( + reviewBatch({ ...mapping(id, supplier), price: 'Unknown' }), + IntakeError, + ); + const reviews = await Promise.allSettled([ + reviewBatch(mapping(id, supplier)), + reviewBatch(mapping(id, supplier)), + ]); + assert.equal(reviews.filter((value) => value.status === 'fulfilled').length, 1); + const rejected = reviews.find( + (value) => value.status === 'rejected', + ) as PromiseRejectedResult; + assert.ok(rejected.reason instanceof RevisionConflict); + const promotions = await Promise.all([promoteBatch(id, 2), promoteBatch(id, 2)]); + assert.equal(promotions.filter((value) => !value.reused).length, 1); + assert.equal( + ( + await database().query( + 'SELECT count(*)::integer AS count FROM intake.products WHERE batch_id=$1', + [id], + ) + ).rows[0].count, + 2, + ); + assert.equal((await promoteBatch(id, 2)).reused, true); + + const snapshot = await stageUpload( + Buffer.from(`Code,Name,USD,Qty,Alternate\nSNAP_${key},Snapshot row,8.00,4,9.00\n`), + 'snapshot.csv', + ); + await reviewBatch(mapping(snapshot.id, supplier)); + // Pause the real reader after its header query. The actual review service + // commits another mapping before that reader queries staging rows, forcing + // the interleaving that a pair of read-committed queries would mishandle. + let headerRead!: () => void; + let continueRead!: () => void; + const ready = new Promise((resolve) => { + headerRead = resolve; + }); + const release = new Promise((resolve) => { + continueRead = resolve; + }); + const pool = database(); + const originalConnect = pool.connect.bind(pool); + let intercepted = false; + const mock = t.mock.method(pool, 'connect', async () => { + const client = await originalConnect(); + if (intercepted) return client; + intercepted = true; + return new Proxy(client, { + get(target, property) { + if (property === 'query') + return async (sql: string, ...params: any[]) => { + const result = await ( + target.query.bind(target) as (...values: any[]) => Promise + )(sql, ...params); + if (sql.includes('to_char') && sql.includes('WHERE id=$1')) { + headerRead(); + await release; + } + return result; + }; + const value = Reflect.get(target, property); + return typeof value === 'function' ? value.bind(target) : value; + }, + }); + }); + try { + const reading = getBatch(snapshot.id); + await ready; + await reviewBatch({ + ...mapping(snapshot.id, supplier), + revision: 2, + price: 'Alternate', + }); + continueRead(); + const coherent = await reading; + assert.equal(coherent.batch.revision, 2); + assert.equal(coherent.batch.mapping!.price, 'USD'); + assert.equal(coherent.rows[0].mapped!.cents, 800); + } finally { + continueRead(); + mock.mock.restore(); + } + const current = await getBatch(snapshot.id); + assert.equal(current.batch.revision, 3); + assert.equal(current.rows[0].mapped!.cents, 900); + + const invalid = await stageUpload( + Buffer.from( + `Code,Name,USD,Qty\nOK_${key},Valid row,5.00,3\nBAD_${key},Invalid row,ask,-2\n`, + ), + 'invalid.csv', + ); + assert.equal((await reviewBatch(mapping(invalid.id, supplier))).invalidCount, 1); + await assert.rejects(promoteBatch(invalid.id, 2), IntakeError); + assert.equal( + ( + await database().query( + 'SELECT count(*)::integer AS count FROM intake.products WHERE batch_id=$1', + [invalid.id], + ) + ).rows[0].count, + 0, + ); + + const conflicting = await stageUpload( + Buffer.from( + `Code,Name,USD,Qty\nROLL_${key},Otherwise valid,1.00,1\nBASE001,Existing SKU,25.00,20\n`, + ), + 'rollback.csv', + ); + await reviewBatch(mapping(conflicting.id, 'Willow Cooperative')); + await assert.rejects(promoteBatch(conflicting.id, 2), /No rows were imported/); + assert.equal( + ( + await database().query( + 'SELECT count(*)::integer AS count FROM intake.products WHERE batch_id=$1', + [conflicting.id], + ) + ).rows[0].count, + 0, + ); + assert.equal((await getBatch(conflicting.id)).batch.status, 'reviewed'); + assert.equal( + ( + await database().query( + "SELECT price_cents FROM intake.products WHERE supplier_key='willow cooperative' AND sku='BASE001'", + ) + ).rows[0].price_cents, + 1250, + ); + for (const sql of [ + 'CREATE TABLE intake.forbidden (id integer)', + 'ALTER TABLE intake.products ADD COLUMN forbidden text', + 'UPDATE intake.products SET stock=0', + 'DELETE FROM intake.products', + ]) + await assert.rejects(database().query(sql), (err: any) => err.code === '42501'); + + const config = databaseConfig(); + const ip = new URL(process.env.DATABASE_URL!); + ip.hostname = (await lookup(ip.hostname, { family: 4 })).address; + const wrongName = new Pool({ + ...config, + connectionString: ip.toString(), + max: 1, + ssl: { ...(config.ssl as object), servername: 'wrong-host.invalid' }, + }); + try { + await assert.rejects( + wrongName.query('SELECT 1'), + (err: any) => err.code === 'ERR_TLS_CERT_ALTNAME_INVALID', + ); + } finally { + await wrongName.end(); + } + const directory = mkdtempSync(join(tmpdir(), 'intake-ca-')); + try { + execFileSync( + 'openssl', + [ + 'req', + '-x509', + '-newkey', + 'rsa:2048', + '-nodes', + '-keyout', + join(directory, 'key.pem'), + '-out', + join(directory, 'ca.pem'), + '-days', + '1', + '-subj', + '/CN=unrelated-intake-root', + ], + { stdio: 'ignore' }, + ); + const wrongCa = new Pool({ + ...config, + max: 1, + ssl: { + ...(config.ssl as object), + ca: readFileSync(join(directory, 'ca.pem'), 'utf8'), + }, + }); + try { + await assert.rejects(wrongCa.query('SELECT 1'), (err: any) => + [ + 'UNABLE_TO_VERIFY_LEAF_SIGNATURE', + 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY', + 'SELF_SIGNED_CERT_IN_CHAIN', + 'DEPTH_ZERO_SELF_SIGNED_CERT', + ].includes(err.code), + ); + } finally { + await wrongCa.end(); + } + } finally { + rmSync(directory, { recursive: true, force: true }); + } + console.log( + 'Verified upload replay, competing review, concurrent read/review snapshot, concurrent promotion replay, invalid-batch rejection, supplier-SKU rollback, runtime grants, wrong CA and hostname.', + ); + } finally { + await closeDatabase(); + } +}); diff --git a/applications/supplier-intake/tests/parsers.test.ts b/applications/supplier-intake/tests/parsers.test.ts new file mode 100644 index 00000000..2eb7f9ee --- /dev/null +++ b/applications/supplier-intake/tests/parsers.test.ts @@ -0,0 +1,75 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import * as XLSX from 'xlsx'; +import { parseUpload, IntakeError } from '../src/lib/server/parsers'; +import { mapRows } from '../src/lib/mapping'; +const csv = 'Code,Name,USD,Qty\n001,"Tote, cotton",0.01,0\n'; +test('CSV preserves quoted values and leading-zero identifiers; money stays exact', () => { + const parsed = parseUpload(Buffer.from(csv), 'catalog.csv'); + assert.equal(parsed.rows[0].Code, '001'); + assert.equal(parsed.rows[0].Name, 'Tote, cotton'); + const reviewed = mapRows(parsed.rows, { + batchId: '10000000-0000-4000-8000-000000000001', + revision: 1, + supplier: 'Cotton Co', + sku: 'Code', + product: 'Name', + price: 'USD', + stock: 'Qty', + }); + assert.deepEqual(reviewed[0].mapped, { + sku: '001', + product: 'Tote, cotton', + cents: 1, + stock: 0, + }); +}); +test('ambiguous headers, malformed rows and row limits are rejected', () => { + for (const value of [ + 'Code,Code\nA,B\n', + 'Code,Name\nA,B,C\n', + 'Code,Name\n' + Array(101).fill('A,B').join('\n'), + ]) + assert.throws(() => parseUpload(Buffer.from(value), 'input.csv'), IntakeError); +}); +test('XLSX parsing reads a genuine workbook and rejects formulas', () => { + const workbook = XLSX.utils.book_new(); + const sheet = XLSX.utils.aoa_to_sheet([ + ['Code', 'Name', 'USD', 'Qty'], + ['001', 'Tea towel', '8.00', '4'], + ]); + XLSX.utils.book_append_sheet(workbook, sheet, 'Catalog'); + assert.equal( + parseUpload( + XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }), + 'catalog.xlsx', + ).rows[0].Name, + 'Tea towel', + ); + sheet.C2 = { t: 'n', v: 8, f: '4+4' }; + assert.throws( + () => + parseUpload( + XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }), + 'catalog.xlsx', + ), + /Formula/, + ); +}); +test('invalid and duplicated SKU rows remain unpromotable', () => { + const rows = [ + { Code: 'A', Name: 'Rake', USD: '9.00', Qty: '1' }, + { Code: 'a', Name: 'Fork', USD: 'ask', Qty: '-2' }, + ]; + const reviewed = mapRows(rows, { + batchId: '10000000-0000-4000-8000-000000000001', + revision: 1, + supplier: 'Tools Co', + sku: 'Code', + product: 'Name', + price: 'USD', + stock: 'Qty', + }); + assert.ok(reviewed.every((row) => row.mapped === null)); + assert.ok(reviewed[1].errors.length >= 3); +}); diff --git a/applications/supplier-intake/tsconfig.json b/applications/supplier-intake/tsconfig.json new file mode 100644 index 00000000..ad16746e --- /dev/null +++ b/applications/supplier-intake/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { "allowJs": true, "checkJs": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "skipLibCheck": true, "sourceMap": true, "strict": true, "moduleResolution": "bundler" } +} diff --git a/applications/supplier-intake/vite.config.ts b/applications/supplier-intake/vite.config.ts new file mode 100644 index 00000000..854920ef --- /dev/null +++ b/applications/supplier-intake/vite.config.ts @@ -0,0 +1,7 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import tailwindcss from '@tailwindcss/vite'; +import { defineConfig } from 'vite'; +export default defineConfig({ + plugins: [tailwindcss(), sveltekit()], + server: { host: '127.0.0.1', port: 3001, strictPort: true }, +});