diff --git a/.gitignore b/.gitignore index d07fdcc..b415284 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,7 @@ **/dist server-rs/target .task -frontend/public/assets +frontend/public/assets/* +!frontend/public/assets/poi-icons/ +!frontend/public/assets/poi-icons/** +server-rs/logs diff --git a/Makefile.data b/Makefile.data index ed6b339..bfcc286 100644 --- a/Makefile.data +++ b/Makefile.data @@ -189,8 +189,8 @@ $(GREENSPACE): $(PBF) $(OS_GREENSPACE): uv run python -m pipeline.download.os_greenspace --output $@ -$(PLACES): $(PBF) $(ENGLAND_BOUNDARY) - uv run python -m pipeline.download.places --output $@ --pbf $(PBF) --boundary $(ENGLAND_BOUNDARY) +$(PLACES): $(PBF) $(ENGLAND_BOUNDARY) $(NAPTAN) + uv run python -m pipeline.download.places --output $@ --pbf $(PBF) --boundary $(ENGLAND_BOUNDARY) --naptan $(NAPTAN) $(LSOA_POP): uv run python -m pipeline.download.lsoa_population --output $@ diff --git a/README.md b/README.md index 13a2531..10f45ce 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,35 @@ a React/deck.gl map. The public product is branded as Perfect Postcodes, while this repository is still named `property-map`. +## Public SEO Pages + +The indexable public pages are listed in `frontend/public/sitemap.xml` and +prerendered by `frontend/scripts/prerender.mjs`: + +- [Home](https://perfect-postcode.co.uk/) - `/` +- [Learn](https://perfect-postcode.co.uk/learn) - `/learn` +- [Pricing](https://perfect-postcode.co.uk/pricing) - `/pricing` +- [Property price map](https://perfect-postcode.co.uk/property-price-map) - + `/property-price-map` +- [Postcode property search](https://perfect-postcode.co.uk/postcode-property-search) - + `/postcode-property-search` +- [Commute property search](https://perfect-postcode.co.uk/commute-property-search) - + `/commute-property-search` +- [School property search](https://perfect-postcode.co.uk/school-property-search) - + `/school-property-search` +- [Postcode checker](https://perfect-postcode.co.uk/postcode-checker) - + `/postcode-checker` +- [Birmingham property search](https://perfect-postcode.co.uk/property-search/birmingham) - + `/property-search/birmingham` +- [Manchester property search](https://perfect-postcode.co.uk/property-search/manchester) - + `/property-search/manchester` +- [Bristol property search](https://perfect-postcode.co.uk/property-search/bristol) - + `/property-search/bristol` +- [Data sources](https://perfect-postcode.co.uk/data-sources) - `/data-sources` +- [Methodology](https://perfect-postcode.co.uk/methodology) - `/methodology` +- [Privacy and security](https://perfect-postcode.co.uk/privacy-security) - + `/privacy-security` + ## What Is In Here - `frontend/` - React 18, TypeScript, Tailwind, MapLibre, and deck.gl. The app diff --git a/check.sh b/check.sh index 1643930..34a7968 100755 --- a/check.sh +++ b/check.sh @@ -23,6 +23,7 @@ step "Python unit tests" uv run pytest \ step "Frontend lint: ESLint" npm run lint step "Frontend format check: Prettier" npm run format:check step "Frontend typecheck: TypeScript" npm run typecheck + step "Frontend i18n completeness" npm run check:i18n step "Frontend unit tests: Vitest" npm run test ) diff --git a/docker-compose.yml b/docker-compose.yml index 53ac132..390d769 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -54,8 +54,12 @@ services: init: true build: /volumes/syncthing/Projects/property-map/screenshot environment: + PORT: "8002" APP_URL: http://frontend:3001 CACHE_DIR: /cache + SCREENSHOT_CONCURRENCY: "3" + SCREENSHOT_RATE_WINDOW_MS: "60000" + SCREENSHOT_RATE_LIMIT: "30" volumes: - screenshot-cache:/cache networks: diff --git a/frontend/eslint.config.cjs b/frontend/eslint.config.cjs new file mode 100644 index 0000000..c1c512b --- /dev/null +++ b/frontend/eslint.config.cjs @@ -0,0 +1,54 @@ +const js = require('@eslint/js'); +const tsParser = require('@typescript-eslint/parser'); +const tsPlugin = require('@typescript-eslint/eslint-plugin'); +const globals = require('globals'); +const reactPlugin = require('eslint-plugin-react'); +const reactHooksPlugin = require('eslint-plugin-react-hooks'); + +module.exports = [ + { + ignores: ['dist/**', 'node_modules/**'], + linterOptions: { + reportUnusedDisableDirectives: false, + }, + }, + js.configs.recommended, + { + files: ['src/**/*.{ts,tsx}'], + languageOptions: { + parser: tsParser, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + ecmaVersion: 'latest', + sourceType: 'module', + }, + globals: { + ...globals.browser, + ...globals.es2021, + }, + }, + plugins: { + '@typescript-eslint': tsPlugin, + react: reactPlugin, + 'react-hooks': reactHooksPlugin, + }, + settings: { + react: { + version: 'detect', + }, + }, + rules: { + ...reactPlugin.configs.recommended.rules, + ...tsPlugin.configs.recommended.rules, + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', + 'react/react-in-jsx-scope': 'off', + 'react/prop-types': 'off', + 'no-undef': 'off', + '@typescript-eslint/no-require-imports': 'off', + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + }, + }, +]; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 46a3ddf..838c657 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,63 +8,69 @@ "name": "property-map-frontend", "version": "1.0.0", "dependencies": { - "@deck.gl/core": "^9.0.0", - "@deck.gl/geo-layers": "^9.0.0", - "@deck.gl/layers": "^9.0.0", - "@deck.gl/mapbox": "^9.2.6", - "@deck.gl/react": "^9.0.0", - "@plausible-analytics/tracker": "^0.4.4", - "@protomaps/basemaps": "^5.7.0", - "@radix-ui/react-select": "^2.0.0", - "@radix-ui/react-slider": "^1.1.0", + "@deck.gl/core": "^9.3.2", + "@deck.gl/extensions": "^9.3.2", + "@deck.gl/geo-layers": "^9.3.2", + "@deck.gl/layers": "^9.3.2", + "@deck.gl/mapbox": "^9.3.2", + "@deck.gl/mesh-layers": "^9.3.2", + "@deck.gl/react": "^9.3.2", + "@deck.gl/widgets": "^9.3.2", + "@plausible-analytics/tracker": "^0.4.5", + "@protomaps/basemaps": "^5.7.2", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-slider": "^1.3.6", "@types/supercluster": "^7.1.3", - "i18next": "^26.0.3", - "maplibre-gl": "^4.0.0", + "i18next": "^26.0.10", + "maplibre-gl": "^5.24.0", "pocketbase": "^0.26.8", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-i18next": "^17.0.2", - "react-joyride": "^2.9.3", - "react-map-gl": "^7.1.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "react-i18next": "^17.0.7", + "react-joyride": "^3.1.0", + "react-map-gl": "^8.1.1", "supercluster": "^8.0.1" }, "devDependencies": { "@babel/core": "^7.29.0", - "@babel/preset-env": "^7.29.0", + "@babel/preset-env": "^7.29.5", "@babel/preset-react": "^7.28.5", "@babel/preset-typescript": "^7.28.5", + "@eslint/js": "^9.39.4", "@pmmmwh/react-refresh-webpack-plugin": "^0.6.2", + "@tailwindcss/postcss": "^4.2.4", "@testing-library/react": "^16.3.2", - "@types/react": "^18.2.0", - "@types/react-dom": "^18.2.0", - "@typescript-eslint/eslint-plugin": "^7.0.0", - "@typescript-eslint/parser": "^7.0.0", - "autoprefixer": "^10.4.0", - "babel-loader": "^10.0.0", - "copy-webpack-plugin": "^13.0.1", - "css-loader": "^7.0.0", - "eslint": "^8.57.0", - "eslint-plugin-react": "^7.34.0", - "eslint-plugin-react-hooks": "^4.6.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@typescript-eslint/eslint-plugin": "^8.59.2", + "@typescript-eslint/parser": "^8.59.2", + "autoprefixer": "^10.5.0", + "babel-loader": "^10.1.1", + "copy-webpack-plugin": "^14.0.0", + "css-loader": "^7.1.4", + "eslint": "^9.39.4", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.1.1", "favicons": "^7.2.0", "favicons-webpack-plugin": "^6.0.1", - "html-webpack-plugin": "^5.6.0", + "globals": "^17.6.0", + "html-webpack-plugin": "^5.6.7", "jsdom": "^29.1.1", - "mini-css-extract-plugin": "^2.9.0", - "postcss": "^8.4.0", - "postcss-loader": "^8.0.0", - "prettier": "^3.2.0", - "puppeteer": "^24.0.0", + "mini-css-extract-plugin": "^2.10.2", + "postcss": "^8.5.14", + "postcss-loader": "^8.2.1", + "prettier": "^3.8.3", + "puppeteer": "^24.43.0", "react-refresh": "^0.18.0", "sharp": "^0.34.5", "style-loader": "^4.0.0", - "tailwindcss": "^3.4.0", - "ts-loader": "^9.5.0", - "typescript": "^5.4.0", + "tailwindcss": "^4.2.4", + "ts-loader": "^9.5.7", + "typescript": "^6.0.3", "vitest": "^4.1.5", - "webpack": "^5.90.0", - "webpack-cli": "^5.1.0", - "webpack-dev-server": "^5.0.0" + "webpack": "^5.106.2", + "webpack-cli": "^7.0.2", + "webpack-dev-server": "^5.2.3" } }, "node_modules/@alloc/quick-lru": { @@ -136,6 +142,7 @@ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", @@ -146,10 +153,11 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -159,6 +167,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -184,20 +193,12 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/generator": { "version": "7.29.1", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", @@ -214,6 +215,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.27.3" }, @@ -226,6 +228,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", @@ -237,36 +240,19 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", - "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.3.tgz", + "integrity": "sha512-RpLYy2sb51oNLjuu1iD3bwBqCBWUzjO0ocp+iaCP/lJtb2CPLcnC2Fftw+4sAzaMELGeWTgExSKADbdo0GFVzA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.6", + "@babel/traverse": "^7.29.0", "semver": "^6.3.1" }, "engines": { @@ -276,20 +262,12 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-create-regexp-features-plugin": { "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", @@ -302,20 +280,12 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz", - "integrity": "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==", + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", @@ -332,6 +302,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -341,6 +312,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" @@ -354,6 +326,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" @@ -367,6 +340,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", @@ -384,6 +358,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.27.1" }, @@ -396,6 +371,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -405,6 +381,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-wrap-function": "^7.27.1", @@ -422,6 +399,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", @@ -439,6 +417,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" @@ -452,6 +431,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -471,6 +451,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -480,6 +461,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", @@ -490,23 +472,25 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.29.0" }, @@ -522,6 +506,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" @@ -538,6 +523,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -553,6 +539,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -563,11 +550,29 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.3.tgz", + "integrity": "sha512-SRS46DFR4HqzUzCVgi90/xMoL+zeBDBvWdKYXSEzh79kXswNFEglUpMKxR04//dPqwYXWUBJ3mpUd933ru9Kmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", @@ -585,6 +590,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/traverse": "^7.28.6" @@ -601,6 +607,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" }, @@ -613,6 +620,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -628,6 +636,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -643,6 +652,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -658,6 +668,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -673,6 +684,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -689,6 +701,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -704,6 +717,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1", @@ -721,6 +735,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", @@ -738,6 +753,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -753,6 +769,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -768,6 +785,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" @@ -784,6 +802,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" @@ -800,6 +819,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-compilation-targets": "^7.28.6", @@ -820,6 +840,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/template": "^7.28.6" @@ -836,6 +857,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" @@ -852,6 +874,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" @@ -868,6 +891,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -883,6 +907,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" @@ -899,6 +924,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -914,6 +940,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5" @@ -930,6 +957,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -945,6 +973,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -960,6 +989,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" @@ -976,6 +1006,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", @@ -993,6 +1024,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -1008,6 +1040,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1023,6 +1056,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -1038,6 +1072,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1053,6 +1088,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1069,6 +1105,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" @@ -1081,10 +1118,11 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", - "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", + "version": "7.29.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz", + "integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", @@ -1103,6 +1141,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1119,6 +1158,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" @@ -1135,6 +1175,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1150,6 +1191,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -1165,6 +1207,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -1180,6 +1223,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", @@ -1199,6 +1243,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1" @@ -1215,6 +1260,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -1230,6 +1276,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" @@ -1246,6 +1293,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1261,6 +1309,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" @@ -1277,6 +1326,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.6", @@ -1294,6 +1344,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1309,6 +1360,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1324,6 +1376,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz", "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-module-imports": "^7.28.6", @@ -1343,6 +1396,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", "dev": true, + "license": "MIT", "dependencies": { "@babel/plugin-transform-react-jsx": "^7.27.1" }, @@ -1358,6 +1412,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1374,6 +1429,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -1389,6 +1445,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" @@ -1405,6 +1462,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1420,6 +1478,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1435,6 +1494,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" @@ -1451,6 +1511,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1466,6 +1527,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1481,6 +1543,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1496,6 +1559,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.6", @@ -1515,6 +1579,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1530,6 +1595,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" @@ -1546,6 +1612,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1562,6 +1629,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" @@ -1574,18 +1642,20 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.0.tgz", - "integrity": "sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==", + "version": "7.29.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.5.tgz", + "integrity": "sha512-/69t2aEzGKHD76DyLbHysF/QH2LJOB8iFnYO37unDTKBTubzcMRv0f3H5EiN1Q6ajOd/eB7dAInF0qdFVS06kA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.29.0", + "@babel/compat-data": "^7.29.3", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.3", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", @@ -1617,7 +1687,7 @@ "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.28.6", - "@babel/plugin-transform-modules-systemjs": "^7.29.0", + "@babel/plugin-transform-modules-systemjs": "^7.29.4", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-new-target": "^7.27.1", @@ -1657,20 +1727,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/preset-modules": { "version": "0.1.6-no-external-plugins", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", @@ -1685,6 +1747,7 @@ "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.28.5.tgz", "integrity": "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", @@ -1705,6 +1768,7 @@ "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", @@ -1733,6 +1797,7 @@ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", @@ -1747,6 +1812,7 @@ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1765,6 +1831,7 @@ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" @@ -1927,90 +1994,88 @@ } }, "node_modules/@deck.gl/core": { - "version": "9.2.6", - "resolved": "https://registry.npmjs.org/@deck.gl/core/-/core-9.2.6.tgz", - "integrity": "sha512-bBFfwfythPPpXS/OKUMvziQ8td84mRGMnYZfqdUvfUVltzjFtQCBQUJTzgo3LubvOzSnzo8GTWskxHaZzkqdKQ==", + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/@deck.gl/core/-/core-9.3.2.tgz", + "integrity": "sha512-32Va3np0Zdlz/LBNtDWCs4EkKqdHmXcbGmVp4+7i1Cpdza8y8CFmJs2VPOmSX1fwHvNCGkAZV/SFZOfDb2INsg==", "license": "MIT", "dependencies": { - "@loaders.gl/core": "^4.2.0", - "@loaders.gl/images": "^4.2.0", - "@luma.gl/constants": "^9.2.6", - "@luma.gl/core": "^9.2.6", - "@luma.gl/engine": "^9.2.6", - "@luma.gl/shadertools": "^9.2.6", - "@luma.gl/webgl": "^9.2.6", + "@loaders.gl/core": "^4.4.1", + "@loaders.gl/images": "^4.4.1", + "@luma.gl/core": "^9.3.3", + "@luma.gl/engine": "^9.3.3", + "@luma.gl/shadertools": "^9.3.3", + "@luma.gl/webgl": "^9.3.3", "@math.gl/core": "^4.1.0", "@math.gl/sun": "^4.1.0", "@math.gl/types": "^4.1.0", "@math.gl/web-mercator": "^4.1.0", - "@probe.gl/env": "^4.1.0", - "@probe.gl/log": "^4.1.0", - "@probe.gl/stats": "^4.1.0", + "@probe.gl/env": "^4.1.1", + "@probe.gl/log": "^4.1.1", + "@probe.gl/stats": "^4.1.1", "@types/offscreencanvas": "^2019.6.4", "gl-matrix": "^3.0.0", "mjolnir.js": "^3.0.0" } }, "node_modules/@deck.gl/extensions": { - "version": "9.2.6", - "resolved": "https://registry.npmjs.org/@deck.gl/extensions/-/extensions-9.2.6.tgz", - "integrity": "sha512-HNuzo76mD6Ykc/xMEyCMH+to6/Xi+7ehG3VYToSm+R3196Ki5p58pyRHzvq9CrBDvFd3SLMe9QqRm2GTg3wn/w==", + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/@deck.gl/extensions/-/extensions-9.3.2.tgz", + "integrity": "sha512-P2gPCCmGC5R6HRB3Mv3JraDnsSpvStjFFUGKxW810SXmo2eTft/5xpvliiyJeFGDjqttwo8V4Qk6oD3BNVGvRw==", "license": "MIT", - "peer": true, "dependencies": { - "@luma.gl/constants": "^9.2.6", - "@luma.gl/shadertools": "^9.2.6", + "@luma.gl/shadertools": "^9.3.3", + "@luma.gl/webgl": "^9.3.3", "@math.gl/core": "^4.1.0" }, "peerDependencies": { - "@deck.gl/core": "~9.2.0", - "@luma.gl/core": "~9.2.6", - "@luma.gl/engine": "~9.2.6" + "@deck.gl/core": "~9.3.0", + "@luma.gl/core": "~9.3.3", + "@luma.gl/engine": "~9.3.3" } }, "node_modules/@deck.gl/geo-layers": { - "version": "9.2.6", - "resolved": "https://registry.npmjs.org/@deck.gl/geo-layers/-/geo-layers-9.2.6.tgz", - "integrity": "sha512-Js42GcAlzH5vHWHdg/eKSmFvx1TWlhW+d6p8Y+67/iHpcCXmx/CBmpsr1ZsQ8XYc+GY8NDAmkHe5KECDJsJiDg==", + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/@deck.gl/geo-layers/-/geo-layers-9.3.2.tgz", + "integrity": "sha512-3sndOyq5A3b2DMCBWFCeX9/QkBSp5MD8EUD3eu4hHfCDV4IrbJHtxE/pv60J848Yz8D5u7ftUqXf9gLWnEeBeg==", "license": "MIT", "dependencies": { - "@loaders.gl/3d-tiles": "^4.2.0", - "@loaders.gl/gis": "^4.2.0", - "@loaders.gl/loader-utils": "^4.2.0", - "@loaders.gl/mvt": "^4.2.0", - "@loaders.gl/schema": "^4.2.0", - "@loaders.gl/terrain": "^4.2.0", - "@loaders.gl/tiles": "^4.2.0", - "@loaders.gl/wms": "^4.2.0", - "@luma.gl/gltf": "^9.2.6", - "@luma.gl/shadertools": "^9.2.6", + "@loaders.gl/3d-tiles": "^4.4.1", + "@loaders.gl/gis": "^4.4.1", + "@loaders.gl/loader-utils": "^4.4.1", + "@loaders.gl/mvt": "^4.4.1", + "@loaders.gl/schema": "^4.4.1", + "@loaders.gl/terrain": "^4.4.1", + "@loaders.gl/tiles": "^4.4.1", + "@loaders.gl/wms": "^4.4.1", + "@luma.gl/gltf": "^9.3.3", + "@luma.gl/shadertools": "^9.3.3", "@math.gl/core": "^4.1.0", "@math.gl/culling": "^4.1.0", "@math.gl/web-mercator": "^4.1.0", "@types/geojson": "^7946.0.8", - "a5-js": "^0.5.0", - "h3-js": "^4.1.0", + "a5-js": "^0.7.2", + "h3-js": "^4.4.0", "long": "^3.2.0" }, "peerDependencies": { - "@deck.gl/core": "~9.2.0", - "@deck.gl/extensions": "~9.2.0", - "@deck.gl/layers": "~9.2.0", - "@deck.gl/mesh-layers": "~9.2.0", - "@loaders.gl/core": "^4.2.0", - "@luma.gl/core": "~9.2.6", - "@luma.gl/engine": "~9.2.6" + "@deck.gl/core": "~9.3.0", + "@deck.gl/extensions": "~9.3.0", + "@deck.gl/layers": "~9.3.0", + "@deck.gl/mesh-layers": "~9.3.0", + "@loaders.gl/core": "^4.4.1", + "@luma.gl/core": "~9.3.3", + "@luma.gl/engine": "~9.3.3" } }, "node_modules/@deck.gl/layers": { - "version": "9.2.6", - "resolved": "https://registry.npmjs.org/@deck.gl/layers/-/layers-9.2.6.tgz", - "integrity": "sha512-ASwL5CHm/QX+fVW+MejmtA/84RKO0BaL2/Fv9N+l+WcSII2M5s730rrTw3JgyQ66AUGFPe1SL3JDkqsUlVJ0yg==", + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/@deck.gl/layers/-/layers-9.3.2.tgz", + "integrity": "sha512-TeVfhQ/cQU1oTlTn16mCp7268d1uBJ6dwfgmKXThe2TzW9hql3iJaxbYTKg2phDg5YSiGmeEOpXbeBh59jyUcA==", "license": "MIT", "dependencies": { - "@loaders.gl/images": "^4.2.0", - "@loaders.gl/schema": "^4.2.0", - "@luma.gl/shadertools": "^9.2.6", + "@loaders.gl/images": "^4.4.1", + "@loaders.gl/schema": "^4.4.1", + "@luma.gl/shadertools": "^9.3.3", "@mapbox/tiny-sdf": "^2.0.5", "@math.gl/core": "^4.1.0", "@math.gl/polygon": "^4.1.0", @@ -2018,82 +2083,79 @@ "earcut": "^2.2.4" }, "peerDependencies": { - "@deck.gl/core": "~9.2.0", - "@loaders.gl/core": "^4.2.0", - "@luma.gl/core": "~9.2.6", - "@luma.gl/engine": "~9.2.6" + "@deck.gl/core": "~9.3.0", + "@loaders.gl/core": "^4.4.1", + "@luma.gl/core": "~9.3.3", + "@luma.gl/engine": "~9.3.3" } }, "node_modules/@deck.gl/mapbox": { - "version": "9.2.6", - "resolved": "https://registry.npmjs.org/@deck.gl/mapbox/-/mapbox-9.2.6.tgz", - "integrity": "sha512-gyqCHZwiZS8LOYY6LILQQp5YCCf++VFk/wRoGskZvhb/kdEPX2Onv8iV8pXe0h9UyMLO6Mj0wl3HlJWg2ILkrg==", + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/@deck.gl/mapbox/-/mapbox-9.3.2.tgz", + "integrity": "sha512-+T9pJwsOXwjUxyGN6oiBMfIs28VtDIG1V1Rqz4qqn4TjjNEFFw+xO0olJIg8FO5IAqw2OtePdsrMj0tX8tHdGQ==", "license": "MIT", "dependencies": { - "@luma.gl/constants": "^9.2.6", "@math.gl/web-mercator": "^4.1.0" }, "peerDependencies": { - "@deck.gl/core": "~9.2.0", - "@luma.gl/constants": "~9.2.6", - "@luma.gl/core": "~9.2.6", + "@deck.gl/core": "~9.3.0", + "@luma.gl/core": "~9.3.3", "@math.gl/web-mercator": "^4.1.0" } }, "node_modules/@deck.gl/mesh-layers": { - "version": "9.2.6", - "resolved": "https://registry.npmjs.org/@deck.gl/mesh-layers/-/mesh-layers-9.2.6.tgz", - "integrity": "sha512-/KjhjoQJRb9lUcDE6pZlHvcto9H5iBCJtUb1/uCb8fahzEAcZBDubAn4RUWjfRyOSmzJfQHrWdNAjflNkL87Yg==", + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/@deck.gl/mesh-layers/-/mesh-layers-9.3.2.tgz", + "integrity": "sha512-9KeEnEx8PYalFPq/jSb1983QtjwZeuz64OfHH8I2VOB3eOhtRDxaTAaelbkVkD3E9HqlU2dD6f9huYsuv1WZfw==", "license": "MIT", - "peer": true, "dependencies": { - "@loaders.gl/gltf": "^4.2.0", - "@loaders.gl/schema": "^4.2.0", - "@luma.gl/gltf": "^9.2.6", - "@luma.gl/shadertools": "^9.2.6" + "@loaders.gl/gltf": "^4.4.1", + "@loaders.gl/schema": "^4.4.1", + "@luma.gl/gltf": "^9.3.3", + "@luma.gl/shadertools": "^9.3.3" }, "peerDependencies": { - "@deck.gl/core": "~9.2.0", - "@luma.gl/core": "~9.2.6", - "@luma.gl/engine": "~9.2.6", - "@luma.gl/gltf": "~9.2.6", - "@luma.gl/shadertools": "~9.2.6" + "@deck.gl/core": "~9.3.0", + "@luma.gl/core": "~9.3.3", + "@luma.gl/engine": "~9.3.3", + "@luma.gl/gltf": "~9.3.3", + "@luma.gl/shadertools": "~9.3.3" } }, "node_modules/@deck.gl/react": { - "version": "9.2.6", - "resolved": "https://registry.npmjs.org/@deck.gl/react/-/react-9.2.6.tgz", - "integrity": "sha512-wYjfX52EAeThZposplTT/vkP0dk2qOv5AryLOq/Y/DIrtA1FGe91GlL28DvDJ2YZrl6K7cFAvoXpuFZe2zYULA==", + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/@deck.gl/react/-/react-9.3.2.tgz", + "integrity": "sha512-XGxoyTQiQWvBHt+q5bATOHlv9INdl0xwt/IxP4eMbbE9XhLreeGTTb0CDGbk+SwDHVwQuLFp5JHZNibUt0J3fA==", "license": "MIT", "peerDependencies": { - "@deck.gl/core": "~9.2.0", - "@deck.gl/widgets": "~9.2.0", + "@deck.gl/core": "~9.3.0", + "@deck.gl/widgets": "~9.3.0", "react": ">=16.3.0", "react-dom": ">=16.3.0" } }, "node_modules/@deck.gl/widgets": { - "version": "9.2.6", - "resolved": "https://registry.npmjs.org/@deck.gl/widgets/-/widgets-9.2.6.tgz", - "integrity": "sha512-WkKP+HB90x1qwOxs5l6Dg0d1iAvf999jJGDdGUbDVsRF7+hJDv03GZY6XKpoiEW7VfcZ1y1iU2vRwV/GHuQ57g==", + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/@deck.gl/widgets/-/widgets-9.3.2.tgz", + "integrity": "sha512-EdNxecpUlZHhfCSD5qpMVva7fjd48u6lDSXMxQRnT2uCGCMHBViZadkCmyK3lPwac4nylM9vRWek/NPSOqPKcQ==", "license": "MIT", - "peer": true, "dependencies": { + "@floating-ui/dom": "^1.7.5", "preact": "^10.17.0" }, "peerDependencies": { - "@deck.gl/core": "~9.2.0", - "@luma.gl/core": "~9.2.6" + "@deck.gl/core": "~9.3.0", + "@luma.gl/core": "~9.3.3" } }, "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-1.1.0.tgz", + "integrity": "sha512-Xc3VhU02wqZ1HvHRJUwL09HkZSTvidqY5Ya0NXBSYOxAp+Ln9dcJr9fySI+CkONzP3PekQo9WdzCv0PGER/mOA==", "dev": true, "license": "MIT", "engines": { - "node": ">=10.0.0" + "node": ">=14.17.0" } }, "node_modules/@emnapi/core": { @@ -2159,51 +2221,32 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } + "license": "MIT" }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -2211,17 +2254,114 @@ "concat-map": "0.0.1" } }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -2232,41 +2372,102 @@ } }, "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", "dev": true, "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@fastify/deepmerge": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-3.2.1.tgz", + "integrity": "sha512-N5Oqvltoa2r9z1tbx4xjky0oRR60v+T47Ic4J1ukoVQcptLOrIdRnCSdTGmOmajZuHVKlTnfcmrjyqsGEW1ztA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/@floating-ui/core": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", - "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.10" + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", - "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.3", - "@floating-ui/utils": "^0.2.10" + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", - "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.7.4" + "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", @@ -2274,54 +2475,74 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", - "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", "license": "MIT" }, "node_modules/@gilbarbara/deep-equal": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.3.1.tgz", - "integrity": "sha512-I7xWjLs2YSVMc5gGx1Z3ZG1lgFpITPndpi8Ku55GeEIKpACCPQNS/OTqQbxgTCfq0Ncvcc+CrFov96itVh6Qvw==" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.4.1.tgz", + "integrity": "sha512-QF2BGeQjsa59T59XvFdR3is5jrl28Eg0J6giXAC5919bcqvR8XP4B+07tpbs6Y6/IQd4FBncaL2WVXIBgSxt4w==", + "license": "MIT" }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@gilbarbara/hooks": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@gilbarbara/hooks/-/hooks-0.11.0.tgz", + "integrity": "sha512-CIVazdxqFRplUfm9wZL3/0X1TURJekhPMWGFdWzEmyJrGPiotX2yxA1KiB8N7VnhawIaMtb2Apnda4Y6DRwi2Q==", + "license": "MIT", + "dependencies": { + "@gilbarbara/deep-equal": "^0.4.1" + }, + "peerDependencies": { + "react": "16.8 - 19" + } + }, + "node_modules/@gilbarbara/types": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@gilbarbara/types/-/types-0.2.2.tgz", + "integrity": "sha512-QuQDBRRcm1Q8AbSac2W1YElurOhprj3Iko/o+P1fJxUWS4rOGKMVli98OXS7uo4z+cKAif6a+L9bcZFSyauQpQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^4.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@humanfs/types": "^0.15.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=18.18.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { - "node": "*" + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -2338,13 +2559,19 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, - "license": "BSD-3-Clause" + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@img/colour": { "version": "1.1.0", @@ -2852,6 +3079,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" @@ -2914,9 +3142,9 @@ } }, "node_modules/@jsonjoy.com/buffers": { - "version": "17.65.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.65.0.tgz", - "integrity": "sha512-eBrIXd0/Ld3p9lpDDlMaMn6IEfWqtHMD+z61u0JrIiPzsV1r7m6xDZFRxJyvIFTEO+SWdYF9EiQbXZGd8BzPfA==", + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz", + "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2948,14 +3176,14 @@ } }, "node_modules/@jsonjoy.com/fs-core": { - "version": "4.56.10", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.56.10.tgz", - "integrity": "sha512-PyAEA/3cnHhsGcdY+AmIU+ZPqTuZkDhCXQ2wkXypdLitSpd6d5Ivxhnq4wa2ETRWFVJGabYynBWxIijOswSmOw==", + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.57.2.tgz", + "integrity": "sha512-SVjwklkpIV5wrynpYtuYnfYH1QF4/nDuLBX7VXdb+3miglcAgBVZb/5y0cOsehRV/9Vb+3UqhkMq3/NR3ztdkQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-node-builtins": "4.56.10", - "@jsonjoy.com/fs-node-utils": "4.56.10", + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2", "thingies": "^2.5.0" }, "engines": { @@ -2970,15 +3198,15 @@ } }, "node_modules/@jsonjoy.com/fs-fsa": { - "version": "4.56.10", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.56.10.tgz", - "integrity": "sha512-/FVK63ysNzTPOnCCcPoPHt77TOmachdMS422txM4KhxddLdbW1fIbFMYH0AM0ow/YchCyS5gqEjKLNyv71j/5Q==", + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.57.2.tgz", + "integrity": "sha512-fhO8+iR2I+OCw668ISDJdn1aArc9zx033sWejIyzQ8RBeXa9bDSaUeA3ix0poYOfrj1KdOzytmYNv2/uLDfV6g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-core": "4.56.10", - "@jsonjoy.com/fs-node-builtins": "4.56.10", - "@jsonjoy.com/fs-node-utils": "4.56.10", + "@jsonjoy.com/fs-core": "4.57.2", + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2", "thingies": "^2.5.0" }, "engines": { @@ -2993,17 +3221,17 @@ } }, "node_modules/@jsonjoy.com/fs-node": { - "version": "4.56.10", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.56.10.tgz", - "integrity": "sha512-7R4Gv3tkUdW3dXfXiOkqxkElxKNVdd8BDOWC0/dbERd0pXpPY+s2s1Mino+aTvkGrFPiY+mmVxA7zhskm4Ue4Q==", + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.57.2.tgz", + "integrity": "sha512-nX2AdL6cOFwLdju9G4/nbRnYevmCJbh7N7hvR3gGm97Cs60uEjyd0rpR+YBS7cTg175zzl22pGKXR5USaQMvKg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-core": "4.56.10", - "@jsonjoy.com/fs-node-builtins": "4.56.10", - "@jsonjoy.com/fs-node-utils": "4.56.10", - "@jsonjoy.com/fs-print": "4.56.10", - "@jsonjoy.com/fs-snapshot": "4.56.10", + "@jsonjoy.com/fs-core": "4.57.2", + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2", + "@jsonjoy.com/fs-print": "4.57.2", + "@jsonjoy.com/fs-snapshot": "4.57.2", "glob-to-regex.js": "^1.0.0", "thingies": "^2.5.0" }, @@ -3019,9 +3247,9 @@ } }, "node_modules/@jsonjoy.com/fs-node-builtins": { - "version": "4.56.10", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.56.10.tgz", - "integrity": "sha512-uUnKz8R0YJyKq5jXpZtkGV9U0pJDt8hmYcLRrPjROheIfjMXsz82kXMgAA/qNg0wrZ1Kv+hrg7azqEZx6XZCVw==", + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.57.2.tgz", + "integrity": "sha512-xhiegylRmhw43Ki2HO1ZBL7DQ5ja/qpRsL29VtQ2xuUHiuDGbgf2uD4p9Qd8hJI5P6RCtGYD50IXHXVq/Ocjcg==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3036,15 +3264,15 @@ } }, "node_modules/@jsonjoy.com/fs-node-to-fsa": { - "version": "4.56.10", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.56.10.tgz", - "integrity": "sha512-oH+O6Y4lhn9NyG6aEoFwIBNKZeYy66toP5LJcDOMBgL99BKQMUf/zWJspdRhMdn/3hbzQsZ8EHHsuekbFLGUWw==", + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.57.2.tgz", + "integrity": "sha512-18LmWTSONhoAPW+IWRuf8w/+zRolPFGPeGwMxlAhhfY11EKzX+5XHDBPAw67dBF5dxDErHJbl40U+3IXSDRXSQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-fsa": "4.56.10", - "@jsonjoy.com/fs-node-builtins": "4.56.10", - "@jsonjoy.com/fs-node-utils": "4.56.10" + "@jsonjoy.com/fs-fsa": "4.57.2", + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2" }, "engines": { "node": ">=10.0" @@ -3058,13 +3286,13 @@ } }, "node_modules/@jsonjoy.com/fs-node-utils": { - "version": "4.56.10", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.56.10.tgz", - "integrity": "sha512-8EuPBgVI2aDPwFdaNQeNpHsyqPi3rr+85tMNG/lHvQLiVjzoZsvxA//Xd8aB567LUhy4QS03ptT+unkD/DIsNg==", + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.57.2.tgz", + "integrity": "sha512-rsPSJgekz43IlNbLyAM/Ab+ouYLWGp5DDBfYBNNEqDaSpsbXfthBn29Q4muFA9L0F+Z3mKo+CWlgSCXrf+mOyQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-node-builtins": "4.56.10" + "@jsonjoy.com/fs-node-builtins": "4.57.2" }, "engines": { "node": ">=10.0" @@ -3078,13 +3306,13 @@ } }, "node_modules/@jsonjoy.com/fs-print": { - "version": "4.56.10", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.56.10.tgz", - "integrity": "sha512-JW4fp5mAYepzFsSGrQ48ep8FXxpg4niFWHdF78wDrFGof7F3tKDJln72QFDEn/27M1yHd4v7sKHHVPh78aWcEw==", + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.57.2.tgz", + "integrity": "sha512-wK9NSow48i4DbDl9F1CQE5TqnyZOJ04elU3WFG5aJ76p+YxO/ulyBBQvKsessPxdo381Bc2pcEoyPujMOhcRqQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-node-utils": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.57.2", "tree-dump": "^1.1.0" }, "engines": { @@ -3099,14 +3327,14 @@ } }, "node_modules/@jsonjoy.com/fs-snapshot": { - "version": "4.56.10", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.56.10.tgz", - "integrity": "sha512-DkR6l5fj7+qj0+fVKm/OOXMGfDFCGXLfyHkORH3DF8hxkpDgIHbhf/DwncBMs2igu/ST7OEkexn1gIqoU6Y+9g==", + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.57.2.tgz", + "integrity": "sha512-GdduDZuoP5V/QCgJkx9+BZ6SC0EZ/smXAdTS7PfMqgMTGXLlt/bH/FqMYaqB9JmLf05sJPtO0XRbAwwkEEPbVw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/buffers": "^17.65.0", - "@jsonjoy.com/fs-node-utils": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.57.2", "@jsonjoy.com/json-pack": "^17.65.0", "@jsonjoy.com/util": "^17.65.0" }, @@ -3122,9 +3350,9 @@ } }, "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": { - "version": "17.65.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.65.0.tgz", - "integrity": "sha512-Xrh7Fm/M0QAYpekSgmskdZYnFdSGnsxJ/tHaolA4bNwWdG9i65S8m83Meh7FOxyJyQAdo4d4J97NOomBLEfkDQ==", + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz", + "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3139,9 +3367,9 @@ } }, "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": { - "version": "17.65.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.65.0.tgz", - "integrity": "sha512-7MXcRYe7n3BG+fo3jicvjB0+6ypl2Y/bQp79Sp7KeSiiCgLqw4Oled6chVv07/xLVTdo3qa1CD0VCCnPaw+RGA==", + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz", + "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3156,17 +3384,17 @@ } }, "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": { - "version": "17.65.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.65.0.tgz", - "integrity": "sha512-e0SG/6qUCnVhHa0rjDJHgnXnbsacooHVqQHxspjvlYQSkHm+66wkHw6Gql+3u/WxI/b1VsOdUi0M+fOtkgKGdQ==", + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz", + "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/base64": "17.65.0", - "@jsonjoy.com/buffers": "17.65.0", - "@jsonjoy.com/codegen": "17.65.0", - "@jsonjoy.com/json-pointer": "17.65.0", - "@jsonjoy.com/util": "17.65.0", + "@jsonjoy.com/base64": "17.67.0", + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0", + "@jsonjoy.com/json-pointer": "17.67.0", + "@jsonjoy.com/util": "17.67.0", "hyperdyperid": "^1.2.0", "thingies": "^2.5.0", "tree-dump": "^1.1.0" @@ -3183,13 +3411,13 @@ } }, "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": { - "version": "17.65.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.65.0.tgz", - "integrity": "sha512-uhTe+XhlIZpWOxgPcnO+iSCDgKKBpwkDVTyYiXX9VayGV8HSFVJM67M6pUE71zdnXF1W0Da21AvnhlmdwYPpow==", + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz", + "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/util": "17.65.0" + "@jsonjoy.com/util": "17.67.0" }, "engines": { "node": ">=10.0" @@ -3203,14 +3431,14 @@ } }, "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": { - "version": "17.65.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.65.0.tgz", - "integrity": "sha512-cWiEHZccQORf96q2y6zU3wDeIVPeidmGqd9cNKJRYoVHTV0S1eHPy5JTbHpMnGfDvtvujQwQozOqgO9ABu6h0w==", + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz", + "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/buffers": "17.65.0", - "@jsonjoy.com/codegen": "17.65.0" + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0" }, "engines": { "node": ">=10.0" @@ -3334,28 +3562,28 @@ "license": "MIT" }, "node_modules/@loaders.gl/3d-tiles": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/3d-tiles/-/3d-tiles-4.3.4.tgz", - "integrity": "sha512-JQ3y3p/KlZP7lfobwON5t7H9WinXEYTvuo3SRQM8TBKhM+koEYZhvI2GwzoXx54MbBbY+s3fm1dq5UAAmaTsZw==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@loaders.gl/3d-tiles/-/3d-tiles-4.4.1.tgz", + "integrity": "sha512-837MynN5/lqVbuZcqdxFb0CMfT8v0yRlX7TUFKIBdmkS7AeRRrgcrB+XKblrkdZINUcxOs2N/YLVkwC9wLH1Uw==", "license": "MIT", "dependencies": { - "@loaders.gl/compression": "4.3.4", - "@loaders.gl/crypto": "4.3.4", - "@loaders.gl/draco": "4.3.4", - "@loaders.gl/gltf": "4.3.4", - "@loaders.gl/images": "4.3.4", - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/math": "4.3.4", - "@loaders.gl/tiles": "4.3.4", - "@loaders.gl/zip": "4.3.4", + "@loaders.gl/compression": "4.4.1", + "@loaders.gl/crypto": "4.4.1", + "@loaders.gl/draco": "4.4.1", + "@loaders.gl/gltf": "4.4.1", + "@loaders.gl/images": "4.4.1", + "@loaders.gl/loader-utils": "4.4.1", + "@loaders.gl/math": "4.4.1", + "@loaders.gl/tiles": "4.4.1", + "@loaders.gl/zip": "4.4.1", "@math.gl/core": "^4.1.0", "@math.gl/culling": "^4.1.0", "@math.gl/geospatial": "^4.1.0", - "@probe.gl/log": "^4.0.4", + "@probe.gl/log": "^4.1.1", "long": "^5.2.1" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "~4.4.0" } }, "node_modules/@loaders.gl/3d-tiles/node_modules/long": { @@ -3365,373 +3593,374 @@ "license": "Apache-2.0" }, "node_modules/@loaders.gl/compression": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/compression/-/compression-4.3.4.tgz", - "integrity": "sha512-+o+5JqL9Sx8UCwdc2MTtjQiUHYQGJALHbYY/3CT+b9g/Emzwzez2Ggk9U9waRfdHiBCzEgRBivpWZEOAtkimXQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@loaders.gl/compression/-/compression-4.4.1.tgz", + "integrity": "sha512-MKtGbqHBH7xRVFKyB3E9xRqRMwNW8H72OKpUBDdFwP+hQ0mjHZuud0GeYm5pP50+7o3J2PrES06kHTwT4fg7oQ==", "license": "MIT", "dependencies": { - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", - "@types/brotli": "^1.3.0", + "@loaders.gl/loader-utils": "4.4.1", + "@loaders.gl/worker-utils": "4.4.1", "@types/pako": "^1.0.1", "fflate": "0.7.4", - "lzo-wasm": "^0.0.4", "pako": "1.0.11", "snappyjs": "^0.6.1" }, "optionalDependencies": { + "@types/brotli": "^1.3.0", "brotli": "^1.3.2", "lz4js": "^0.2.0", "zstd-codec": "^0.1" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "~4.4.0" } }, "node_modules/@loaders.gl/core": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/core/-/core-4.3.4.tgz", - "integrity": "sha512-cG0C5fMZ1jyW6WCsf4LoHGvaIAJCEVA/ioqKoYRwoSfXkOf+17KupK1OUQyUCw5XoRn+oWA1FulJQOYlXnb9Gw==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@loaders.gl/core/-/core-4.4.1.tgz", + "integrity": "sha512-/s4IuvCCQUepvhjLnmePwQppGko2d1pxRS+sp7lyExU0uiqo5dVsAKaCZ2VnddBkFWgDVb/wvcZUBmv/dWcj0Q==", "license": "MIT", "dependencies": { - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", - "@probe.gl/log": "^4.0.2" + "@loaders.gl/loader-utils": "4.4.1", + "@loaders.gl/schema": "4.4.1", + "@loaders.gl/schema-utils": "4.4.1", + "@loaders.gl/worker-utils": "4.4.1", + "@probe.gl/log": "^4.1.1" } }, "node_modules/@loaders.gl/crypto": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/crypto/-/crypto-4.3.4.tgz", - "integrity": "sha512-3VS5FgB44nLOlAB9Q82VOQnT1IltwfRa1miE0mpHCe1prYu1M/dMnEyynusbrsp+eDs3EKbxpguIS9HUsFu5dQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@loaders.gl/crypto/-/crypto-4.4.1.tgz", + "integrity": "sha512-ORhS9GSYr9uVTU4I2Taa46XBgPPG+nKErKcyDGIXov3gs0EtgMqs8nU4epuLbsJN3+du6FkQaILyGSZlTxbA7Q==", "license": "MIT", "dependencies": { - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", + "@loaders.gl/loader-utils": "4.4.1", + "@loaders.gl/worker-utils": "4.4.1", "@types/crypto-js": "^4.0.2" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "~4.4.0" } }, "node_modules/@loaders.gl/draco": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/draco/-/draco-4.3.4.tgz", - "integrity": "sha512-4Lx0rKmYENGspvcgV5XDpFD9o+NamXoazSSl9Oa3pjVVjo+HJuzCgrxTQYD/3JvRrolW/QRehZeWD/L/cEC6mw==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@loaders.gl/draco/-/draco-4.4.1.tgz", + "integrity": "sha512-EcapVlkP8Pz53VKg9pYRQUzqm9jH+A+7vGE1kV8nkv63lN8/qtFzBSWMiC6IX1CwxjKJDEINU9Sh8YB1AfMwbQ==", "license": "MIT", "dependencies": { - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", + "@loaders.gl/loader-utils": "4.4.1", + "@loaders.gl/schema": "4.4.1", + "@loaders.gl/schema-utils": "4.4.1", + "@loaders.gl/worker-utils": "4.4.1", "draco3d": "1.5.7" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "~4.4.0" + } + }, + "node_modules/@loaders.gl/geoarrow": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@loaders.gl/geoarrow/-/geoarrow-4.4.1.tgz", + "integrity": "sha512-d9+AxsNpdJzilgHTFnyycoIocp4b+iEX3bbCCAEdUm/7eZbOdM7sFcgLLiGVTehtGnOUOICskjrzT27gqmzDqg==", + "license": "MIT", + "dependencies": { + "@math.gl/polygon": "^4.1.0", + "apache-arrow": ">= 17.0.0" + }, + "peerDependencies": { + "@loaders.gl/core": "~4.4.0" } }, "node_modules/@loaders.gl/gis": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/gis/-/gis-4.3.4.tgz", - "integrity": "sha512-8xub38lSWW7+ZXWuUcggk7agRHJUy6RdipLNKZ90eE0ZzLNGDstGD1qiBwkvqH0AkG+uz4B7Kkiptyl7w2Oa6g==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@loaders.gl/gis/-/gis-4.4.1.tgz", + "integrity": "sha512-M9Z9jXwye4SjlD1hAFJwE3+eZiN1lprwlSkWIo7R642kN5r3R60M9fqBD1mvCTBj96FPmbsyOm1eYKS0XCpKxQ==", "license": "MIT", "dependencies": { - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/schema": "4.3.4", + "@loaders.gl/geoarrow": "4.4.1", + "@loaders.gl/loader-utils": "4.4.1", + "@loaders.gl/schema": "4.4.1", + "@loaders.gl/schema-utils": "4.4.1", "@mapbox/vector-tile": "^1.3.1", "@math.gl/polygon": "^4.1.0", "pbf": "^3.2.1" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "~4.4.0" } }, "node_modules/@loaders.gl/gltf": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/gltf/-/gltf-4.3.4.tgz", - "integrity": "sha512-EiUTiLGMfukLd9W98wMpKmw+hVRhQ0dJ37wdlXK98XPeGGB+zTQxCcQY+/BaMhsSpYt/OOJleHhTfwNr8RgzRg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@loaders.gl/gltf/-/gltf-4.4.1.tgz", + "integrity": "sha512-9ESHEm3YoMgsQh8QS1N99uwA+cij6p6xhCmZnHX4rQnqHm0jvE5RAHlGV1D/Xjvr4PR8IiXaBn/QDl/qdGIxkw==", "license": "MIT", "dependencies": { - "@loaders.gl/draco": "4.3.4", - "@loaders.gl/images": "4.3.4", - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/textures": "4.3.4", + "@loaders.gl/draco": "4.4.1", + "@loaders.gl/images": "4.4.1", + "@loaders.gl/loader-utils": "4.4.1", + "@loaders.gl/schema": "4.4.1", + "@loaders.gl/textures": "4.4.1", "@math.gl/core": "^4.1.0" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "~4.4.0" } }, "node_modules/@loaders.gl/images": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/images/-/images-4.3.4.tgz", - "integrity": "sha512-qgc33BaNsqN9cWa/xvcGvQ50wGDONgQQdzHCKDDKhV2w/uptZoR5iofJfuG8UUV2vUMMd82Uk9zbopRx2rS4Ag==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@loaders.gl/images/-/images-4.4.1.tgz", + "integrity": "sha512-v9A4BliEKGxhLuEbh0Ke8ElUlp04KxpKIknUtXXWoEaszAMTSrHI3YhaL/JdRlHraC1VUF/sjzbSBFkKh7nxJg==", "license": "MIT", "dependencies": { - "@loaders.gl/loader-utils": "4.3.4" + "@loaders.gl/loader-utils": "4.4.1" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "~4.4.0" } }, "node_modules/@loaders.gl/loader-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-4.3.4.tgz", - "integrity": "sha512-tjMZvlKQSaMl2qmYTAxg+ySR6zd6hQn5n3XaU8+Ehp90TD3WzxvDKOMNDqOa72fFmIV+KgPhcmIJTpq4lAdC4Q==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-4.4.1.tgz", + "integrity": "sha512-waosL7VtVRfXsNOXtAM3rOjZyNQD0lQBlhuB5/oY+E+lNzYNFlzgiGXiDOwBpcs7dK7kW2Vv8+KcxyIGIyXOtg==", "license": "MIT", "dependencies": { - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", - "@probe.gl/log": "^4.0.2", - "@probe.gl/stats": "^4.0.2" - }, - "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/schema": "4.4.1", + "@loaders.gl/worker-utils": "4.4.1", + "@probe.gl/log": "^4.1.1", + "@probe.gl/stats": "^4.1.1" } }, "node_modules/@loaders.gl/math": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/math/-/math-4.3.4.tgz", - "integrity": "sha512-UJrlHys1fp9EUO4UMnqTCqvKvUjJVCbYZ2qAKD7tdGzHJYT8w/nsP7f/ZOYFc//JlfC3nq+5ogvmdpq2pyu3TA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@loaders.gl/math/-/math-4.4.1.tgz", + "integrity": "sha512-xenAPOAUd7HDlus5V/g4LKVh1l7FpyVRSYXa+g7tBj91xzhRYgLEXSxdrGfRNAFMDOSGC1ITwCGQwlwSX4Mpxw==", "license": "MIT", "dependencies": { - "@loaders.gl/images": "4.3.4", - "@loaders.gl/loader-utils": "4.3.4", "@math.gl/core": "^4.1.0" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "~4.4.0" } }, "node_modules/@loaders.gl/mvt": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/mvt/-/mvt-4.3.4.tgz", - "integrity": "sha512-9DrJX8RQf14htNtxsPIYvTso5dUce9WaJCWCIY/79KYE80Be6dhcEYMknxBS4w3+PAuImaAe66S5xo9B7Erm5A==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@loaders.gl/mvt/-/mvt-4.4.1.tgz", + "integrity": "sha512-ou1Oyec7hcpCQ2onF1FefNXVv1MwPjwUkII6IFrrRZ/f0/ei0b8yc5IVwO4gkhta/Ve/Y+mFcs/GaeQZMOEBOg==", "license": "MIT", "dependencies": { - "@loaders.gl/gis": "4.3.4", - "@loaders.gl/images": "4.3.4", - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/schema": "4.3.4", + "@loaders.gl/gis": "4.4.1", + "@loaders.gl/images": "4.4.1", + "@loaders.gl/loader-utils": "4.4.1", + "@loaders.gl/schema": "4.4.1", "@math.gl/polygon": "^4.1.0", - "@probe.gl/stats": "^4.0.0", + "@probe.gl/stats": "^4.1.1", "pbf": "^3.2.1" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "~4.4.0" } }, "node_modules/@loaders.gl/schema": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-4.3.4.tgz", - "integrity": "sha512-1YTYoatgzr/6JTxqBLwDiD3AVGwQZheYiQwAimWdRBVB0JAzych7s1yBuE0CVEzj4JDPKOzVAz8KnU1TiBvJGw==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-4.4.1.tgz", + "integrity": "sha512-s7NjEnyK6jZvJJSWj/mHq+S9mHRHVzIYtFP+C7sMf1gVCQbdkt6OSAMUWRzwPr9+whQNVWjZ9pbLsI/IPW3zvw==", "license": "MIT", "dependencies": { - "@types/geojson": "^7946.0.7" + "@types/geojson": "^7946.0.7", + "apache-arrow": ">= 17.0.0" + } + }, + "node_modules/@loaders.gl/schema-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@loaders.gl/schema-utils/-/schema-utils-4.4.1.tgz", + "integrity": "sha512-4upip2O6MFaWzk68/lnna7P2uRj9NQ8MIk/ff3CLbciP5/9lKl1qyuzObz5JrJRYzfGB6I81vpOn6FSVQ6m6KQ==", + "license": "MIT", + "dependencies": { + "@loaders.gl/schema": "4.4.1", + "@types/geojson": "^7946.0.7", + "apache-arrow": ">= 17.0.0" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "~4.4.0" } }, "node_modules/@loaders.gl/terrain": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/terrain/-/terrain-4.3.4.tgz", - "integrity": "sha512-JszbRJGnxL5Fh82uA2U8HgjlsIpzYoCNNjy3cFsgCaxi4/dvjz3BkLlBilR7JlbX8Ka+zlb4GAbDDChiXLMJ/g==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@loaders.gl/terrain/-/terrain-4.4.1.tgz", + "integrity": "sha512-cBLT+G0HefySTppxqqkMKcN5kfOfIRRx0WDPHa0VHFJw9rbnxoEDhrXvfsXfOATNFFNtcpgQUDqDqhEBp0XvZw==", "license": "MIT", "dependencies": { - "@loaders.gl/images": "4.3.4", - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/schema": "4.3.4", + "@loaders.gl/images": "4.4.1", + "@loaders.gl/loader-utils": "4.4.1", + "@loaders.gl/schema": "4.4.1", "@mapbox/martini": "^0.2.0" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "~4.4.0" } }, "node_modules/@loaders.gl/textures": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/textures/-/textures-4.3.4.tgz", - "integrity": "sha512-arWIDjlE7JaDS6v9by7juLfxPGGnjT9JjleaXx3wq/PTp+psLOpGUywHXm38BNECos3MFEQK3/GFShWI+/dWPw==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@loaders.gl/textures/-/textures-4.4.1.tgz", + "integrity": "sha512-r1//6sO29GOHso+IvXQ3GrvXZ4cl03VWc34XcnXPn3sAV7O96uRGd5xkyx60lMYAl7Jv7qK/smT3z4Mdxdd4aA==", "license": "MIT", "dependencies": { - "@loaders.gl/images": "4.3.4", - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/worker-utils": "4.3.4", + "@loaders.gl/images": "4.4.1", + "@loaders.gl/loader-utils": "4.4.1", + "@loaders.gl/schema": "4.4.1", + "@loaders.gl/worker-utils": "4.4.1", "@math.gl/types": "^4.1.0", "ktx-parse": "^0.7.0", "texture-compressor": "^1.0.2" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "~4.4.0" } }, "node_modules/@loaders.gl/tiles": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/tiles/-/tiles-4.3.4.tgz", - "integrity": "sha512-oC0zJfyvGox6Ag9ABF8fxOkx9yEFVyzTa9ryHXl2BqLiQoR1v3p+0tIJcEbh5cnzHfoTZzUis1TEAZluPRsHBQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@loaders.gl/tiles/-/tiles-4.4.1.tgz", + "integrity": "sha512-EbF81/c1oXJocVAKR0rx+vWSOnmBBWWhM7pZpYk6oNUQAJfA99APhiRNstAJiJomAgqAxr7vfnhXHjPZg6osZw==", "license": "MIT", "dependencies": { - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/math": "4.3.4", + "@loaders.gl/loader-utils": "4.4.1", + "@loaders.gl/math": "4.4.1", "@math.gl/core": "^4.1.0", "@math.gl/culling": "^4.1.0", "@math.gl/geospatial": "^4.1.0", "@math.gl/web-mercator": "^4.1.0", - "@probe.gl/stats": "^4.0.2" + "@probe.gl/stats": "^4.1.1" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "~4.4.0" } }, "node_modules/@loaders.gl/wms": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/wms/-/wms-4.3.4.tgz", - "integrity": "sha512-yXF0wuYzJUdzAJQrhLIua6DnjOiBJusaY1j8gpvuH1VYs3mzvWlIRuZKeUd9mduQZKK88H2IzHZbj2RGOauq4w==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@loaders.gl/wms/-/wms-4.4.1.tgz", + "integrity": "sha512-sIaqyHXPuLQnkN2eebvczZYVvapkjA8EZaI8feaPxj4jZk/Hk5EuZzIbxJ4eftLotZwDHd3XzEVIs6YlFOSJ+Q==", "license": "MIT", "dependencies": { - "@loaders.gl/images": "4.3.4", - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/schema": "4.3.4", - "@loaders.gl/xml": "4.3.4", + "@loaders.gl/images": "4.4.1", + "@loaders.gl/loader-utils": "4.4.1", + "@loaders.gl/schema": "4.4.1", + "@loaders.gl/xml": "4.4.1", "@turf/rewind": "^5.1.5", "deep-strict-equal": "^0.2.0" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "~4.4.0" } }, "node_modules/@loaders.gl/worker-utils": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-4.3.4.tgz", - "integrity": "sha512-EbsszrASgT85GH3B7jkx7YXfQyIYo/rlobwMx6V3ewETapPUwdSAInv+89flnk5n2eu2Lpdeh+2zS6PvqbL2RA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-4.4.1.tgz", + "integrity": "sha512-ovMyIyj9dlChuHuD64Bel7Mir2UYlmLqlZ9MMzVxzTTLvaudJoNAXi6Disp0ooxwF62ZqjNXXutaSbS6UDeuIg==", "license": "MIT", "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "~4.4.0" } }, "node_modules/@loaders.gl/xml": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/xml/-/xml-4.3.4.tgz", - "integrity": "sha512-p+y/KskajsvyM3a01BwUgjons/j/dUhniqd5y1p6keLOuwoHlY/TfTKd+XluqfyP14vFrdAHCZTnFCWLblN10w==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@loaders.gl/xml/-/xml-4.4.1.tgz", + "integrity": "sha512-+8Dtxp0BZZj1CVUkiIlKGDLmhwsPILK9yJvc1P7tuJO9KsaQ5cywJk/b8A7lmqb2SfPkEg0xlQOK2FWIo1ATMA==", "license": "MIT", "dependencies": { - "@loaders.gl/loader-utils": "4.3.4", - "@loaders.gl/schema": "4.3.4", - "fast-xml-parser": "^4.2.5" + "@loaders.gl/loader-utils": "4.4.1", + "@loaders.gl/schema": "4.4.1", + "fast-xml-parser": "^5.3.6" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "~4.4.0" } }, "node_modules/@loaders.gl/zip": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@loaders.gl/zip/-/zip-4.3.4.tgz", - "integrity": "sha512-bHY4XdKYJm3vl9087GMoxnUqSURwTxPPh6DlAGOmz6X9Mp3JyWuA2gk3tQ1UIuInfjXKph3WAUfGe6XRIs1sfw==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@loaders.gl/zip/-/zip-4.4.1.tgz", + "integrity": "sha512-fV7oqREEzzqYl2/b4tiM+J4qeSq6pB4gw1hHngpCtVyjVwWVtsNH2r1ly9kkv4XssIdXJxPcrX/GR0mDIwmp6w==", "license": "MIT", "dependencies": { - "@loaders.gl/compression": "4.3.4", - "@loaders.gl/crypto": "4.3.4", - "@loaders.gl/loader-utils": "4.3.4", + "@loaders.gl/compression": "4.4.1", + "@loaders.gl/crypto": "4.4.1", + "@loaders.gl/loader-utils": "4.4.1", "jszip": "^3.1.5", "md5": "^2.3.0" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "~4.4.0" } }, - "node_modules/@luma.gl/constants": { - "version": "9.2.6", - "resolved": "https://registry.npmjs.org/@luma.gl/constants/-/constants-9.2.6.tgz", - "integrity": "sha512-rvFFrJuSm5JIWbDHFuR4Q2s4eudO3Ggsv0TsGKn9eqvO7bBiPm/ANugHredvh3KviEyYuMZZxtfJvBdr3kzldg==", - "license": "MIT" - }, "node_modules/@luma.gl/core": { - "version": "9.2.6", - "resolved": "https://registry.npmjs.org/@luma.gl/core/-/core-9.2.6.tgz", - "integrity": "sha512-d8KcH8ZZcjDAodSN/G2nueA9YE2X8kMz7Q0OxDGpCww6to1MZXM3Ydate/Jqsb5DDKVgUF6yD6RL8P5jOki9Yw==", + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/@luma.gl/core/-/core-9.3.3.tgz", + "integrity": "sha512-jCFm2htvrVpcXIy85TBTF1ROgMfknKnfw2OH+Vydr41hiCFd6nqr79gM3f2uhaNkal0BghFNqF3qDioKiUWtew==", "license": "MIT", "dependencies": { "@math.gl/types": "^4.1.0", - "@probe.gl/env": "^4.0.8", - "@probe.gl/log": "^4.0.8", - "@probe.gl/stats": "^4.0.8", - "@types/offscreencanvas": "^2019.6.4" + "@probe.gl/env": "^4.1.1", + "@probe.gl/log": "^4.1.1", + "@probe.gl/stats": "^4.1.1", + "@types/offscreencanvas": "^2019.7.3" } }, "node_modules/@luma.gl/engine": { - "version": "9.2.6", - "resolved": "https://registry.npmjs.org/@luma.gl/engine/-/engine-9.2.6.tgz", - "integrity": "sha512-1AEDs2AUqOWh7Wl4onOhXmQF+Rz1zNdPXF+Kxm4aWl92RQ42Sh2CmTvRt2BJku83VQ91KFIEm/v3qd3Urzf+Uw==", + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/@luma.gl/engine/-/engine-9.3.3.tgz", + "integrity": "sha512-StmMTzUcUlpKMU3wvWU48A6OQyphptD9zVGBsSkK6iHIBdtBKlOcmqRkyfvRouo8JHtlrnoJDHLVKhxorwhGAg==", "license": "MIT", "dependencies": { "@math.gl/core": "^4.1.0", "@math.gl/types": "^4.1.0", - "@probe.gl/log": "^4.0.8", - "@probe.gl/stats": "^4.0.8" + "@probe.gl/log": "^4.1.1", + "@probe.gl/stats": "^4.1.1" }, "peerDependencies": { - "@luma.gl/core": "~9.2.0", - "@luma.gl/shadertools": "~9.2.0" + "@luma.gl/core": "~9.3.0", + "@luma.gl/shadertools": "~9.3.0" } }, "node_modules/@luma.gl/gltf": { - "version": "9.2.6", - "resolved": "https://registry.npmjs.org/@luma.gl/gltf/-/gltf-9.2.6.tgz", - "integrity": "sha512-is3YkiGsWqWTmwldMz6PRaIUleufQfUKYjJTKpsF5RS1OnN+xdAO0mJq5qJTtOQpppWAU0VrmDFEVZ6R3qvm0A==", + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/@luma.gl/gltf/-/gltf-9.3.3.tgz", + "integrity": "sha512-/wty4PHYeQelXvDJesyuMdqtAfpL1XcyEQffcEAwKwu9w7JdkygSShdUwTT1iF7no0uGKuWgq824dVC9WBBQcw==", "license": "MIT", "dependencies": { - "@loaders.gl/core": "^4.2.0", - "@loaders.gl/gltf": "^4.2.0", - "@loaders.gl/textures": "^4.2.0", + "@loaders.gl/core": "~4.4.0", + "@loaders.gl/gltf": "~4.4.0", + "@loaders.gl/textures": "~4.4.0", "@math.gl/core": "^4.1.0" }, "peerDependencies": { - "@luma.gl/constants": "~9.2.0", - "@luma.gl/core": "~9.2.0", - "@luma.gl/engine": "~9.2.0", - "@luma.gl/shadertools": "~9.2.0" + "@luma.gl/core": "~9.3.0", + "@luma.gl/engine": "~9.3.0", + "@luma.gl/shadertools": "~9.3.0" } }, "node_modules/@luma.gl/shadertools": { - "version": "9.2.6", - "resolved": "https://registry.npmjs.org/@luma.gl/shadertools/-/shadertools-9.2.6.tgz", - "integrity": "sha512-4+uUbynqPUra9d/z1nQChyHmhLgmKfSMjS7kOwLB6exSnhKnpHL3+Hu9fv55qyaX50nGH3oHawhGtJ6RRvu65w==", + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/@luma.gl/shadertools/-/shadertools-9.3.3.tgz", + "integrity": "sha512-4ZfG4/Utix951vqyiG/JIx+Eg+GMNwOxgr/07/i0gf7bK1gJZIEQ5BxVcDw4MCQfdoVlGPGzl0cQKbdqBvaCAQ==", "license": "MIT", "dependencies": { "@math.gl/core": "^4.1.0", - "@math.gl/types": "^4.1.0", - "wgsl_reflect": "^1.2.0" + "@math.gl/types": "^4.1.0" }, "peerDependencies": { - "@luma.gl/core": "~9.2.0" + "@luma.gl/core": "~9.3.0" } }, "node_modules/@luma.gl/webgl": { - "version": "9.2.6", - "resolved": "https://registry.npmjs.org/@luma.gl/webgl/-/webgl-9.2.6.tgz", - "integrity": "sha512-NGBTdxJMk7j8Ygr1zuTyAvr1Tw+EpupMIQo7RelFjEsZXg6pujFqiDMM+rgxex8voCeuhWBJc7Rs+MoSqd46UQ==", + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/@luma.gl/webgl/-/webgl-9.3.3.tgz", + "integrity": "sha512-X+aavdP5o6VFHSA0es9gKZTT145jfcFbhKJt/gwJrptnKNoIW4+Y37ZEpCo1AzAnr+FQCxjgcM2kOCpoWMfSVA==", "license": "MIT", "dependencies": { - "@luma.gl/constants": "9.2.6", "@math.gl/types": "^4.1.0", - "@probe.gl/env": "^4.0.8" + "@probe.gl/env": "^4.1.1" }, "peerDependencies": { - "@luma.gl/core": "~9.2.0" - } - }, - "node_modules/@mapbox/geojson-rewind": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", - "integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==", - "license": "ISC", - "dependencies": { - "get-stream": "^6.0.1", - "minimist": "^1.2.6" - }, - "bin": { - "geojson-rewind": "geojson-rewind" + "@luma.gl/core": "~9.3.0" } }, "node_modules/@mapbox/jsonlint-lines-primitives": { @@ -3755,9 +3984,9 @@ "license": "ISC" }, "node_modules/@mapbox/tiny-sdf": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.7.tgz", - "integrity": "sha512-25gQLQMcpivjOSA40g3gO6qgiFPDpWRoMfd+G/GoppPIeP6JDaMMkMrEJnMZhKyyS6iKwVt5YKu02vCUyJM3Ug==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.2.0.tgz", + "integrity": "sha512-LVL4wgI9YAum5V+LNVQO6QgFBPw7/MIIY4XJPNsPDMrjEwcE+JfKk1LuIl8GnF197ejVdC9QdPaxrx5gfgdGXg==", "license": "BSD-2-Clause" }, "node_modules/@mapbox/unitbezier": { @@ -3784,18 +4013,26 @@ "node": ">=6.0.0" } }, + "node_modules/@maplibre/geojson-vt": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@maplibre/geojson-vt/-/geojson-vt-6.1.0.tgz", + "integrity": "sha512-2eIY4gZxeKIVOZVNkAMb+5NgXhgsMQpOveTQAvnp53LYqHGJZDidk7Ew0Tged9PThidpbS+NFTh0g4zivhPDzQ==", + "license": "ISC", + "dependencies": { + "kdbush": "^4.0.2" + } + }, "node_modules/@maplibre/maplibre-gl-style-spec": { - "version": "20.4.0", - "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-20.4.0.tgz", - "integrity": "sha512-AzBy3095fTFPjDjmWpR2w6HVRAZJ6hQZUCwk5Plz6EyfnfuQW1odeW5i2Ai47Y6TBA2hQnC+azscjBSALpaWgw==", + "version": "24.8.5", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-24.8.5.tgz", + "integrity": "sha512-EzEJmMt6thioRH7GI9LWS7ahXTcAhAPGWCe6oTP2Ps4YnsXOOAfeqx854lZaiDnwURfHmcCKV1mr6oo0i23x6w==", "license": "ISC", "dependencies": { "@mapbox/jsonlint-lines-primitives": "~2.0.2", "@mapbox/unitbezier": "^0.0.1", "json-stringify-pretty-compact": "^4.0.0", "minimist": "^1.2.8", - "quickselect": "^2.0.0", - "rw": "^1.3.3", + "quickselect": "^3.0.0", "tinyqueue": "^3.0.0" }, "bin": { @@ -3804,12 +4041,71 @@ "gl-style-validate": "dist/gl-style-validate.mjs" } }, - "node_modules/@maplibre/maplibre-gl-style-spec/node_modules/quickselect": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", - "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==", + "node_modules/@maplibre/mlt": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@maplibre/mlt/-/mlt-1.1.9.tgz", + "integrity": "sha512-g/tD8EYJB97udq33ipuJ9a4Q7fcbZnTEnUrgnEc/tLMmEL+zaCbR+X5fkDBO2dgpaAMsLH179qE3UXg2N0Nc/g==", + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "@mapbox/point-geometry": "^1.1.0" + } + }, + "node_modules/@maplibre/mlt/node_modules/@mapbox/point-geometry": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-1.1.0.tgz", + "integrity": "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==", "license": "ISC" }, + "node_modules/@maplibre/vt-pbf": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@maplibre/vt-pbf/-/vt-pbf-4.3.0.tgz", + "integrity": "sha512-jIvp8F5hQCcreqOOpEt42TJMUlsrEcpf/kI1T2v85YrQRV6PPXUcEXUg5karKtH6oh47XJZ4kHu56pUkOuqA7w==", + "license": "MIT", + "dependencies": { + "@mapbox/point-geometry": "^1.1.0", + "@mapbox/vector-tile": "^2.0.4", + "@maplibre/geojson-vt": "^5.0.4", + "@types/geojson": "^7946.0.16", + "@types/supercluster": "^7.1.3", + "pbf": "^4.0.1", + "supercluster": "^8.0.1" + } + }, + "node_modules/@maplibre/vt-pbf/node_modules/@mapbox/point-geometry": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-1.1.0.tgz", + "integrity": "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==", + "license": "ISC" + }, + "node_modules/@maplibre/vt-pbf/node_modules/@mapbox/vector-tile": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-2.0.4.tgz", + "integrity": "sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg==", + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/point-geometry": "~1.1.0", + "@types/geojson": "^7946.0.16", + "pbf": "^4.0.1" + } + }, + "node_modules/@maplibre/vt-pbf/node_modules/@maplibre/geojson-vt": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@maplibre/geojson-vt/-/geojson-vt-5.0.4.tgz", + "integrity": "sha512-KGg9sma45S+stfH9vPCJk1J0lSDLWZgCT9Y8u8qWZJyjFlP8MNP1WGTxIMYJZjDvVT3PDn05kN1C95Sut1HpgQ==", + "license": "ISC" + }, + "node_modules/@maplibre/vt-pbf/node_modules/pbf": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-4.0.1.tgz", + "integrity": "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==", + "license": "BSD-3-Clause", + "dependencies": { + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, "node_modules/@math.gl/core": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@math.gl/core/-/core-4.1.0.tgz", @@ -3888,61 +4184,22 @@ "@emnapi/runtime": "^1.7.1" } }, - "node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } + "node_modules/@nodable/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" }, "node_modules/@oxc-project/types": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", - "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", + "version": "0.128.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.128.0.tgz", + "integrity": "sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ==", "dev": true, "license": "MIT", "funding": { @@ -3950,141 +4207,151 @@ } }, "node_modules/@peculiar/asn1-cms": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.6.0.tgz", - "integrity": "sha512-2uZqP+ggSncESeUF/9Su8rWqGclEfEiz1SyU02WX5fUONFfkjzS2Z/F1Li0ofSmf4JqYXIOdCAZqIXAIBAT1OA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.7.0.tgz", + "integrity": "sha512-hew63shtzzvBcSHbhm+cyAmKe6AIfinT9hzEqSPjDC6opTTMKmTkQ0gHuN2KsWlvqiKw1S/fS94fhag/FJkioQ==", "dev": true, "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", - "@peculiar/asn1-x509-attr": "^2.6.0", + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/asn1-x509": "^2.7.0", + "@peculiar/asn1-x509-attr": "^2.7.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-csr": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.6.0.tgz", - "integrity": "sha512-BeWIu5VpTIhfRysfEp73SGbwjjoLL/JWXhJ/9mo4vXnz3tRGm+NGm3KNcRzQ9VMVqwYS2RHlolz21svzRXIHPQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.7.0.tgz", + "integrity": "sha512-VVsAyGqErT9D1SY4aEqozThXMVI+ssVRiv2DDeYuvpBKLIgZ3hYs3Ay3u/VSoKq6ESFi9cf6rf3IOOzfwh7oMA==", "dev": true, "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/asn1-x509": "^2.7.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-ecc": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.6.0.tgz", - "integrity": "sha512-FF3LMGq6SfAOwUG2sKpPXblibn6XnEIKa+SryvUl5Pik+WR9rmRA3OCiwz8R3lVXnYnyRkSZsSLdml8H3UiOcw==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.7.0.tgz", + "integrity": "sha512-n7KEs/Q/wrB415cxy4fHOBhegp4NdJ15fkJPwcB/3/8iNBQC2L/N7SChJPKDJPZGYH0jD4Tg4/0vnHmwghnbKw==", "dev": true, "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/asn1-x509": "^2.7.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-pfx": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.6.0.tgz", - "integrity": "sha512-rtUvtf+tyKGgokHHmZzeUojRZJYPxoD/jaN1+VAB4kKR7tXrnDCA/RAWXAIhMJJC+7W27IIRGe9djvxKgsldCQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.7.0.tgz", + "integrity": "sha512-V/nrlQVmhg7lYAsM7E13UDL5erAwFv6kCIVFqNaMIHSVi7dngcT839JkRTkQBqznMG98l2XjxYk74ZztAohZzA==", "dev": true, "license": "MIT", "dependencies": { - "@peculiar/asn1-cms": "^2.6.0", - "@peculiar/asn1-pkcs8": "^2.6.0", - "@peculiar/asn1-rsa": "^2.6.0", - "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-cms": "^2.7.0", + "@peculiar/asn1-pkcs8": "^2.7.0", + "@peculiar/asn1-rsa": "^2.7.0", + "@peculiar/asn1-schema": "^2.7.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-pkcs8": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.6.0.tgz", - "integrity": "sha512-KyQ4D8G/NrS7Fw3XCJrngxmjwO/3htnA0lL9gDICvEQ+GJ+EPFqldcJQTwPIdvx98Tua+WjkdKHSC0/Km7T+lA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.7.0.tgz", + "integrity": "sha512-9GTl1nE8Mx1kTZ+7QyYatDyKsm34QcWRBFkY1iPvWC3X4Dona5s/tlLiQsx5WzVdZqiMBZNYT0buyw4/vbhnjw==", "dev": true, "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/asn1-x509": "^2.7.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-pkcs9": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.6.0.tgz", - "integrity": "sha512-b78OQ6OciW0aqZxdzliXGYHASeCvvw5caqidbpQRYW2mBtXIX2WhofNXTEe7NyxTb0P6J62kAAWLwn0HuMF1Fw==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.7.0.tgz", + "integrity": "sha512-Bh7m+OuIaSEllPQcSd9OSp93F4ROWH7sbITWV8MI+8dwsjE5111/87VxiWVvYFKyww3vp39geLv9ENqhwWHcew==", "dev": true, "license": "MIT", "dependencies": { - "@peculiar/asn1-cms": "^2.6.0", - "@peculiar/asn1-pfx": "^2.6.0", - "@peculiar/asn1-pkcs8": "^2.6.0", - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", - "@peculiar/asn1-x509-attr": "^2.6.0", + "@peculiar/asn1-cms": "^2.7.0", + "@peculiar/asn1-pfx": "^2.7.0", + "@peculiar/asn1-pkcs8": "^2.7.0", + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/asn1-x509": "^2.7.0", + "@peculiar/asn1-x509-attr": "^2.7.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-rsa": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.6.0.tgz", - "integrity": "sha512-Nu4C19tsrTsCp9fDrH+sdcOKoVfdfoQQ7S3VqjJU6vedR7tY3RLkQ5oguOIB3zFW33USDUuYZnPEQYySlgha4w==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.7.0.tgz", + "integrity": "sha512-/qvENQrXyTZURjMqSeofHul0JJt2sNSzSwk36pl2olkHbaioMQgrASDZAlHXl0xUlnVbHj0uGgOrBMTb5x2aJQ==", "dev": true, "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/asn1-x509": "^2.7.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-schema": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", - "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.7.0.tgz", + "integrity": "sha512-W8ZfWzLmQnrcky+eh3tni4IozMdqBDiHWU0N+vve/UGjMaUs8c0L7A2oEdkBXS8rTpWDpK/aoI3DG/L/hxmxPg==", "dev": true, "license": "MIT", "dependencies": { + "@peculiar/utils": "^2.0.2", "asn1js": "^3.0.6", - "pvtsutils": "^1.3.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-x509": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.6.0.tgz", - "integrity": "sha512-uzYbPEpoQiBoTq0/+jZtpM6Gq6zADBx+JNFP3yqRgziWBxQ/Dt/HcuvRfm9zJTPdRcBqPNdaRHTVwpyiq6iNMA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.7.0.tgz", + "integrity": "sha512-mUn9RRrkGDnG4ALfunDmzyRW5dg+sWCj/pfnCCqEHYbkGxEpvUt6iVJv8Yw1cyp6SWZ26ZE5oSmI5SqEaen15g==", "dev": true, "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/utils": "^2.0.2", "asn1js": "^3.0.6", - "pvtsutils": "^1.3.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-x509-attr": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.6.0.tgz", - "integrity": "sha512-MuIAXFX3/dc8gmoZBkwJWxUWOSvG4MMDntXhrOZpJVMkYX+MYc/rUAU2uJOved9iJEoiUx7//3D8oG83a78UJA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.7.0.tgz", + "integrity": "sha512-NS8e7SOgXipkzUPLF/sce7ukpMpWjhxYsH0n6Y+bHYo4TTxOb95Zv7hqwSuL212mj5YxovjdOKQOgH1As3E94w==", "dev": true, "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/asn1-x509": "^2.7.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, "node_modules/@peculiar/x509": { "version": "1.14.3", "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz", @@ -4109,15 +4376,17 @@ } }, "node_modules/@plausible-analytics/tracker": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@plausible-analytics/tracker/-/tracker-0.4.4.tgz", - "integrity": "sha512-fz0NOYUEYXtg1TBaPEEvtcBq3FfmLFuTe1VZw4M8sTWX129br5dguu3M15+plOQnc181ShYe67RfwhKgK89VnA==" + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@plausible-analytics/tracker/-/tracker-0.4.5.tgz", + "integrity": "sha512-6BfAGejXY+YA3Cw6LYT2Zpn4hTxDtPQAawFsYUsQCOg78wIS5C4deAGXTfJffa5VleMWITv5lpJ/EYuQBl1tPA==", + "license": "MIT" }, "node_modules/@pmmmwh/react-refresh-webpack-plugin": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.6.2.tgz", "integrity": "sha512-IhIAD5n4XvGHuL9nAgWfsBR0TdxtjrUWETYKCBHxauYXEv+b+ctEbs9neEgPC7Ecgzv4bpZTBwesAoGDeFymzA==", "dev": true, + "license": "MIT", "dependencies": { "anser": "^2.1.1", "core-js-pure": "^3.23.3", @@ -4160,55 +4429,48 @@ } } }, - "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/source-map": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, "node_modules/@probe.gl/env": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@probe.gl/env/-/env-4.1.0.tgz", - "integrity": "sha512-5ac2Jm2K72VCs4eSMsM7ykVRrV47w32xOGMvcgqn8vQdEMF9PRXyBGYEV9YbqRKWNKpNKmQJVi4AHM/fkCxs9w==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@probe.gl/env/-/env-4.1.1.tgz", + "integrity": "sha512-+68seNDMVsEegRB47pFA/Ws1Fjy8agcFYXxzorKToyPcD6zd+gZ5uhwoLd7TzsSw6Ydns//2KEszWn+EnNHTbA==", "license": "MIT" }, "node_modules/@probe.gl/log": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@probe.gl/log/-/log-4.1.0.tgz", - "integrity": "sha512-r4gRReNY6f+OZEMgfWEXrAE2qJEt8rX0HsDJQXUBMoc+5H47bdB7f/5HBHAmapK8UydwPKL9wCDoS22rJ0yq7Q==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@probe.gl/log/-/log-4.1.1.tgz", + "integrity": "sha512-kcZs9BT44pL7hS1OkRGKYRXI/SN9KejUlPD+BY40DguRLzdC5tLG/28WGMyfKdn/51GT4a0p+0P8xvDn1Ez+Kg==", "license": "MIT", "dependencies": { - "@probe.gl/env": "4.1.0" + "@probe.gl/env": "4.1.1" } }, "node_modules/@probe.gl/stats": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@probe.gl/stats/-/stats-4.1.0.tgz", - "integrity": "sha512-EI413MkWKBDVNIfLdqbeNSJTs7ToBz/KVGkwi3D+dQrSIkRI2IYbWGAU3xX+D6+CI4ls8ehxMhNpUVMaZggDvQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@probe.gl/stats/-/stats-4.1.1.tgz", + "integrity": "sha512-4VpAyMHOqydSvPlEyHwXaE+AkIdR03nX+Qhlxsk2D/IW4OVmDZgIsvJB1cDzyEEtcfKcnaEbfXeiPgejBceT6g==", "license": "MIT" }, "node_modules/@protomaps/basemaps": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@protomaps/basemaps/-/basemaps-5.7.0.tgz", - "integrity": "sha512-vIInnzVSxHuOcvj1BFGkCjlFxG/9a1GV23t98kGEVcPUM7aEqTnf6loUHTRJYX5eCz+WCO16N0aibr1SLg830Q==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/@protomaps/basemaps/-/basemaps-5.7.2.tgz", + "integrity": "sha512-K1Yk6bWdULulYg+R2QRVXx4NzJZan5YQhpejEG0c1/sXruJrfPIPZuakpf3jwAgVmjIRVQwAv+yRafDeN0aaUQ==", + "license": "BSD-3-Clause", "bin": { "generate_style": "src/cli.ts" } }, "node_modules/@puppeteer/browsers": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.11.2.tgz", - "integrity": "sha512-GBY0+2lI9fDrjgb5dFL9+enKXqyOPok9PXg/69NVkjW3bikbK9RQrNrI3qccQXmDNN7ln4j/yL89Qgvj/tfqrw==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.1.tgz", + "integrity": "sha512-zmS4RTK9fbrc++WlAJhxYbfz3IjDeOmkK/CwwbLmk7ydfS9e2CiEeRJHEPvjDVElO/bwXbidwGA37Bsm6LzCnQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "debug": "^4.4.3", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", - "semver": "^7.7.3", + "semver": "^7.7.4", "tar-fs": "^3.1.1", "yargs": "^17.7.2" }, @@ -4219,6 +4481,19 @@ "node": ">=18" } }, + "node_modules/@puppeteer/browsers/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@radix-ui/number": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", @@ -4749,9 +5024,9 @@ "license": "MIT" }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.18.tgz", + "integrity": "sha512-lIDyUAfD7U3+BWKzdxMbJcsYHuqXqmGz40aeRqvuAm3y5TkJSYTBW2RDrn65DJFPQqVjUAUqq5uz8urzQ8aBdQ==", "cpu": [ "arm64" ], @@ -4766,9 +5041,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.18.tgz", + "integrity": "sha512-apJq2ktnGp27nSInMR5Vcj8kY6xJzDAvfdIFlpDcAK/w4cDO58qVoi1YQsES/SKiFNge/6e4CUzgjfHduYqWpQ==", "cpu": [ "arm64" ], @@ -4783,9 +5058,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.18.tgz", + "integrity": "sha512-5Ofot8xbs+pxRHJqm9/9N/4sTQOvdrwEsmPE9pdLEEoAbdZtG6F2LMDfO1sp6ZAtXJuJV/21ew2srq3W8NXB5g==", "cpu": [ "x64" ], @@ -4800,9 +5075,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.18.tgz", + "integrity": "sha512-7h8eeOTT1eyqJyx64BFCnWZpNm486hGWt2sqeLLgDxA0xI1oGZ9H7gK1S85uNGmBhkdPwa/6reTxfFFKvIsebw==", "cpu": [ "x64" ], @@ -4817,9 +5092,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", - "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.18.tgz", + "integrity": "sha512-eRcm/HVt9U/JFu5RKAEKwGQYtDCKWLiaH6wOnsSEp6NMBb/3Os8LgHZlNyzMpFVNmiiMFlfb2zEnebfzJrHFmg==", "cpu": [ "arm" ], @@ -4834,9 +5109,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-SOrT/cT4ukTmgnrEz/Hg3m7LBnuCLW9psDeMKrimRWY4I8DmnO7Lco8W2vtqPmMkbVu8iJ+g4GFLVLLOVjJ9DQ==", "cpu": [ "arm64" ], @@ -4851,9 +5126,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.18.tgz", + "integrity": "sha512-QWjdxN1HJCpBTAcZ5N5F7wju3gVPzRzSpmGzx7na0c/1qpN9CFil+xt+l9lV/1M6/gqHSNXCiqPfwhVJPeLnug==", "cpu": [ "arm64" ], @@ -4868,9 +5143,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-ugCOyj7a4d9h3q9B+wXmf6g3a68UsjGh6dob5DHevHGMwDUbhsYNbSPxJsENcIttJZ9jv7qGM2UesLw5jqIhdg==", "cpu": [ "ppc64" ], @@ -4885,9 +5160,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-kKWRhbsotpXkGbcd5dllUWg5gEXcDAa8u5YnP9AV5DYNbvJHGzzuwv7dpmhc8NqKMJldl0a+x76IHbspEpEmdA==", "cpu": [ "s390x" ], @@ -4902,9 +5177,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-uCo8ElcCIAMyYAZyuIZ81oFkhTSIllNvUCHCAlbhlN4ji3uC28h7IIdlXyIvGO7HsuqnV9p3rD/bpH7XhIyhRw==", "cpu": [ "x64" ], @@ -4919,9 +5194,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.18.tgz", + "integrity": "sha512-XNOQZtuE6yUIvx4rwGemwh8kpL1xvU41FXy/s9K7T/3JVcqGzo3NfKM2HrbrGgfPYGFW42f07Wk++aOC6B9NWA==", "cpu": [ "x64" ], @@ -4936,9 +5211,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.18.tgz", + "integrity": "sha512-tSn/kzrfa7tNOXr7sEacDBN4YsIqTyLqh45IO0nHDwtpKIDNDJr+VFojt+4klSpChxB29JLyduSsE0MKEwa65A==", "cpu": [ "arm64" ], @@ -4953,9 +5228,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", - "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.18.tgz", + "integrity": "sha512-+J9YGmc+czgqlhYmwun3S3O0FIZhsH8ep2456xwjAdIOmuJxM7xz4P4PtrxU+Bz17a/5bqPA8o3HAAoX0teUdg==", "cpu": [ "wasm32" ], @@ -4972,9 +5247,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.18.tgz", + "integrity": "sha512-zsu47DgU0FQzSwi6sU9dZoEdUv7pc1AptSEz/Z8HBg54sV0Pbs3N0+CrIbTsgiu6EyoaNN9CHboqbLaz9lhOyQ==", "cpu": [ "arm64" ], @@ -4989,9 +5264,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.18.tgz", + "integrity": "sha512-7H+3yqGgmnlDTRRhw/xpYY9J1kf4GC681nVc4GqKhExZTDrVVrV2tsOR9kso0fvgBdcTCcQShx4SLLoHgaLwhg==", "cpu": [ "x64" ], @@ -5006,9 +5281,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", - "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.18.tgz", + "integrity": "sha512-CUY5Mnhe64xQBGZEEXQ5WyZwsc1JU3vAZLIxtrsBt3LO6UOb+C8GunVKqe9sT8NeWb4lqSaoJtp2xo6GxT1MNw==", "dev": true, "license": "MIT" }, @@ -5019,6 +5294,286 @@ "dev": true, "license": "MIT" }, + "node_modules/@swc/helpers": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.21.tgz", + "integrity": "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.4.tgz", + "integrity": "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.4" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.4.tgz", + "integrity": "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.4", + "@tailwindcss/oxide-darwin-arm64": "4.2.4", + "@tailwindcss/oxide-darwin-x64": "4.2.4", + "@tailwindcss/oxide-freebsd-x64": "4.2.4", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.4", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.4", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.4", + "@tailwindcss/oxide-linux-x64-musl": "4.2.4", + "@tailwindcss/oxide-wasm32-wasi": "4.2.4", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.4", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.4" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.4.tgz", + "integrity": "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.4.tgz", + "integrity": "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.4.tgz", + "integrity": "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.4.tgz", + "integrity": "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.4.tgz", + "integrity": "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.4.tgz", + "integrity": "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.4.tgz", + "integrity": "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.4.tgz", + "integrity": "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.4.tgz", + "integrity": "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.4.tgz", + "integrity": "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==", + "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.8.1", + "@emnapi/runtime": "^1.8.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.4.tgz", + "integrity": "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.4.tgz", + "integrity": "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.2.4.tgz", + "integrity": "sha512-wgAVj6nUWAolAu8YFvzT2cTBIElWHkjZwFYovF+xsqKsW2ADxM/X2opxj5NsF/qVccAOjRNe8X2IdPzMsWyHTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.2.4", + "@tailwindcss/oxide": "4.2.4", + "postcss": "^8.5.6", + "tailwindcss": "4.2.4" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -5072,7 +5627,8 @@ "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@turf/boolean-clockwise": { "version": "5.1.5", @@ -5171,10 +5727,11 @@ } }, "node_modules/@types/brotli": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@types/brotli/-/brotli-1.3.4.tgz", - "integrity": "sha512-cKYjgaS2DMdCKF7R0F5cgx1nfBYObN2ihIuPGQ4/dlIY6RpV7OWNwe9L8V4tTVKL2eZqOkNM9FM/rgTvLf4oXw==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/brotli/-/brotli-1.3.5.tgz", + "integrity": "sha512-9xoNr+bcxT236/7ZgcWw/6Pb2RRetE13p4bFy1xYSckKwyOiRfmInay8baUWZgH7/284Wl6IPe7+nOI9+OQg/A==", "license": "MIT", + "optional": true, "dependencies": { "@types/node": "*" } @@ -5190,6 +5747,18 @@ "assertion-error": "^2.0.1" } }, + "node_modules/@types/command-line-args": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/command-line-args/-/command-line-args-5.2.3.tgz", + "integrity": "sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw==", + "license": "MIT" + }, + "node_modules/@types/command-line-usage": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/command-line-usage/-/command-line-usage-5.0.4.tgz", + "integrity": "sha512-BwR5KP3Es/CSht0xqBcUXS3qCAUVXwpRKsV2+arxeb65atasuXG9LykC9Ab10Cw3s2raH92ZqOeILaQbsB2ACg==", + "license": "MIT" + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -5247,9 +5816,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, @@ -5285,15 +5854,6 @@ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", "license": "MIT" }, - "node_modules/@types/geojson-vt": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@types/geojson-vt/-/geojson-vt-3.2.5.tgz", - "integrity": "sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==", - "license": "MIT", - "dependencies": { - "@types/geojson": "*" - } - }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -5325,32 +5885,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/mapbox__point-geometry": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz", - "integrity": "sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==", - "license": "MIT" - }, - "node_modules/@types/mapbox__vector-tile": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.4.tgz", - "integrity": "sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg==", - "license": "MIT", - "dependencies": { - "@types/geojson": "*", - "@types/mapbox__point-geometry": "*", - "@types/pbf": "*" - } - }, - "node_modules/@types/mapbox-gl": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-3.4.1.tgz", - "integrity": "sha512-NsGKKtgW93B+UaLPti6B7NwlxYlES5DpV5Gzj9F75rK5ALKsqSk15CiEHbOnTr09RGbr6ZYiCdI+59NNNcAImg==", - "license": "MIT", - "dependencies": { - "@types/geojson": "*" - } - }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -5359,12 +5893,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", - "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", + "version": "25.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.1.tgz", + "integrity": "sha512-coJCN8O1q4AGyyqCAUSP06P+SrMTu18BkEj3NVAK07q6QUneD2wzj3CLv9+yP+BMeZQlMvneXqqvDe3w+xcq7g==", + "devOptional": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.19.0" } }, "node_modules/@types/offscreencanvas": { @@ -5379,22 +5914,10 @@ "integrity": "sha512-YBtzT2ztNF6R/9+UXj2wTGFnC9NklAnASt3sC0h2m1bbH7G6FyBIkt4AN8ThZpNfxUo1b2iMVO0UawiJymEt8A==", "license": "MIT" }, - "node_modules/@types/pbf": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.5.tgz", - "integrity": "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==", - "license": "MIT" - }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "license": "MIT" - }, "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", "dev": true, "license": "MIT" }, @@ -5406,23 +5929,22 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.27", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", - "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", "dependencies": { - "@types/prop-types": "*", "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { - "version": "18.3.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", - "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", "peerDependencies": { - "@types/react": "^18.0.0" + "@types/react": "^19.2.0" } }, "node_modules/@types/retry": { @@ -5489,6 +6011,7 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz", "integrity": "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==", + "license": "MIT", "dependencies": { "@types/geojson": "*" } @@ -5508,128 +6031,156 @@ "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "@types/node": "*" } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", - "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.2.tgz", + "integrity": "sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/type-utils": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.59.2", + "@typescript-eslint/type-utils": "8.59.2", + "@typescript-eslint/utils": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@typescript-eslint/parser": "^8.59.2", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", - "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.2.tgz", + "integrity": "sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.59.2", + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2", + "debug": "^4.4.3" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.2.tgz", + "integrity": "sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.59.2", + "@typescript-eslint/types": "^8.59.2", + "debug": "^4.4.3" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", - "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.2.tgz", + "integrity": "sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0" + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", - "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.2.tgz", + "integrity": "sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw==", "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" - }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.2.tgz", + "integrity": "sha512-nhqaj1nmTdVVl/BP5omXNRGO38jn5iosis2vbdmupF2txCf8ylWT8lx+JlvMYYVqzGVKtjojUFoQ3JRWK+mfzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2", + "@typescript-eslint/utils": "8.59.2", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", - "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.2.tgz", + "integrity": "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -5637,81 +6188,160 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", - "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", - "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.2.tgz", + "integrity": "sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0" + "@typescript-eslint/project-service": "8.59.2", + "@typescript-eslint/tsconfig-utils": "8.59.2", + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", - "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.2.tgz", + "integrity": "sha512-Juw3EinkXqjaffxz6roowvV7GZT/kET5vSKKZT6upl5TXdWkLkYmNPXwDDL2Vkt2DPn0nODIS4egC/0AGxKo/Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.18.0", - "eslint-visitor-keys": "^3.4.3" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.59.2", + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.2.tgz", + "integrity": "sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.2", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, - "license": "ISC" + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vis.gl/react-mapbox": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@vis.gl/react-mapbox/-/react-mapbox-8.1.1.tgz", + "integrity": "sha512-KMDTjtWESXxHS4uqWxjsvgQUHvuL3Z6SdKe68o7Nxma2qUfuyH3x4TCkIqGn3FQTrFvZLWvTnSAbGvtm+Kd13A==", + "license": "MIT", + "peerDependencies": { + "mapbox-gl": ">=3.5.0", + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + }, + "peerDependenciesMeta": { + "mapbox-gl": { + "optional": true + } + } + }, + "node_modules/@vis.gl/react-maplibre": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@vis.gl/react-maplibre/-/react-maplibre-8.1.1.tgz", + "integrity": "sha512-iUOfzJAhFAJwEZp1644tQb7LOTFgi5/GzdaztkhzNgFVuoF2Ez7guvwZjQAKB9CN2TlHTgNuYH8UW85kO7cVhw==", + "license": "MIT", + "dependencies": { + "@maplibre/maplibre-gl-style-spec": "^19.2.1" + }, + "peerDependencies": { + "maplibre-gl": ">=4.0.0", + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + }, + "peerDependenciesMeta": { + "maplibre-gl": { + "optional": true + } + } + }, + "node_modules/@vis.gl/react-maplibre/node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "19.3.3", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-19.3.3.tgz", + "integrity": "sha512-cOZZOVhDSulgK0meTsTkmNXb1ahVvmTmWmfx9gRBwc6hq98wS9JP35ESIoNq3xqEan+UN+gn8187Z6E4NKhLsw==", + "license": "ISC", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "~2.0.2", + "@mapbox/unitbezier": "^0.0.1", + "json-stringify-pretty-compact": "^3.0.0", + "minimist": "^1.2.8", + "rw": "^1.3.3", + "sort-object": "^3.0.3" + }, + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" + } + }, + "node_modules/@vis.gl/react-maplibre/node_modules/json-stringify-pretty-compact": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz", + "integrity": "sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA==", + "license": "MIT" }, "node_modules/@vitest/expect": { "version": "4.1.5", @@ -5987,53 +6617,6 @@ "@xtuc/long": "4.2.2" } }, - "node_modules/@webpack-cli/configtest": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", - "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/info": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", - "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/serve": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", - "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } - } - }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -6049,9 +6632,9 @@ "license": "Apache-2.0" }, "node_modules/a5-js": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/a5-js/-/a5-js-0.5.0.tgz", - "integrity": "sha512-VAw19sWdYadhdovb0ViOIi1SdKx6H6LwcGMRFKwMfgL5gcmL/1fKJHfgsNgNaJ7xC/eEyjs6VK+VVd4N0a+peg==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/a5-js/-/a5-js-0.7.3.tgz", + "integrity": "sha512-3aoMwHmNkyuMDHS4q6GRRInpOawamen2pokIbc0MQmR9cqG0Y9+B0bZpzswwetjrSG2ckbYtShH+nKru6+3O5Q==", "license": "Apache-2.0", "dependencies": { "gl-matrix": "^3.4.3" @@ -6082,9 +6665,9 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -6122,21 +6705,22 @@ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 14" } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, "funding": { "type": "github", @@ -6161,24 +6745,36 @@ } } }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "peerDependencies": { - "ajv": "^8.8.2" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/anser": { "version": "2.3.5", "resolved": "https://registry.npmjs.org/anser/-/anser-2.3.5.tgz", "integrity": "sha512-vcZjxvvVoxTeR5XBNJB38oTu/7eDCZlwdz32N1eNgpyPF7j/Z7Idf+CUwQOkKKpJ7RJyjxgLHCM7vdIK0iCNMQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/ansi-html-community": { "version": "0.0.8", @@ -6207,7 +6803,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -6219,13 +6814,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, - "license": "MIT" - }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -6240,11 +6828,52 @@ "node": ">= 8" } }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/apache-arrow": { + "version": "21.1.0", + "resolved": "https://registry.npmjs.org/apache-arrow/-/apache-arrow-21.1.0.tgz", + "integrity": "sha512-kQrYLxhC+NTVVZ4CCzGF6L/uPVOzJmD1T3XgbiUnP7oTeVFOFgEUu6IKNwCDkpFoBVqDKQivlX4RUFqqnWFlEA==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.11", + "@types/command-line-args": "^5.2.3", + "@types/command-line-usage": "^5.0.4", + "@types/node": "^24.0.3", + "command-line-args": "^6.0.1", + "command-line-usage": "^7.0.1", + "flatbuffers": "^25.1.24", + "json-bignum": "^0.0.3", + "tslib": "^2.6.2" + }, + "bin": { + "arrow2csv": "bin/arrow2csv.js" + } + }, + "node_modules/apache-arrow/node_modules/@types/node": { + "version": "24.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", + "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/apache-arrow/node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "license": "MIT" }, "node_modules/argparse": { @@ -6286,6 +6915,15 @@ "node": ">=0.10.0" } }, + "node_modules/array-back": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.3.tgz", + "integrity": "sha512-SGDvmg6QTYiTxCBkYVmThcoa67uLl35pyzRHdpCGBOcqFy6BtwnphoFPk7LhJshD+Yk1Kt35WGWeZPTgwR4Fhw==", + "license": "MIT", + "engines": { + "node": ">=12.17" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", @@ -6333,16 +6971,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/array.prototype.findlast": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", @@ -6442,14 +7070,14 @@ } }, "node_modules/asn1js": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.7.tgz", - "integrity": "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "pvtsutils": "^1.3.6", - "pvutils": "^1.1.3", + "pvutils": "^1.1.5", "tslib": "^2.8.1" }, "engines": { @@ -6480,6 +7108,7 @@ "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^2.0.1" }, @@ -6502,14 +7131,15 @@ "resolved": "https://registry.npmjs.org/author-regex/-/author-regex-1.0.0.tgz", "integrity": "sha512-KbWgR8wOYRAPekEmMXrYYdc7BRyhn2Ftk7KWfMUnQ43hFdojWEFRxhhRUm3/OFEdPa1r0KAvTTg9YQK57xTe0g==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8" } }, "node_modules/autoprefixer": { - "version": "10.4.23", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", - "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", "dev": true, "funding": [ { @@ -6527,8 +7157,8 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.28.1", - "caniuse-lite": "^1.0.30001760", + "browserslist": "^4.28.2", + "caniuse-lite": "^1.0.30001787", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -6559,11 +7189,27 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/babel-loader": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-10.0.0.tgz", - "integrity": "sha512-z8jt+EdS61AMw22nSfoNJAZ0vrtmhPRVi6ghL3rCeRZI8cdNYFiV5xeV3HbE7rlZZNmGH8BVccwWt8/ED0QOHA==", + "node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/babel-loader": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-10.1.1.tgz", + "integrity": "sha512-JwKSzk2kjIe7mgPK+/lyZ2QAaJcpahNAdM+hgR2HI8D0OJVkdj8Rl6J3kaLYki9pwF7P2iWnD8qVv80Lq1ABtg==", + "dev": true, + "license": "MIT", "dependencies": { "find-up": "^5.0.0" }, @@ -6571,40 +7217,42 @@ "node": "^18.20.0 || ^20.10.0 || >=22.0.0" }, "peerDependencies": { - "@babel/core": "^7.12.0", + "@babel/core": "^7.12.0 || ^8.0.0-beta.1", + "@rspack/core": "^1.0.0 || ^2.0.0-0", "webpack": ">=5.61.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz", - "integrity": "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==", + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", "dev": true, + "license": "MIT", "dependencies": { "@babel/compat-data": "^7.28.6", - "@babel/helper-define-polyfill-provider": "^0.6.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.0.tgz", - "integrity": "sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", + "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", "core-js-compat": "^3.48.0" }, "peerDependencies": { @@ -6612,29 +7260,34 @@ } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz", - "integrity": "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==", + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.6" + "@babel/helper-define-polyfill-provider": "^0.6.8" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/bare-events": { "version": "2.8.2", "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", "dev": true, + "license": "Apache-2.0", "peerDependencies": { "bare-abort-controller": "*" }, @@ -6645,11 +7298,11 @@ } }, "node_modules/bare-fs": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.3.tgz", - "integrity": "sha512-9+kwVx8QYvt3hPWnmb19tPnh38c6Nihz8Lx3t0g9+4GoIf3/fTgYwM4Z6NxgI+B9elLQA7mLE9PpqcWtOMRDiQ==", + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.1.tgz", + "integrity": "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==", "dev": true, - "optional": true, + "license": "Apache-2.0", "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", @@ -6670,11 +7323,11 @@ } }, "node_modules/bare-os": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", - "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.1.tgz", + "integrity": "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==", "dev": true, - "optional": true, + "license": "Apache-2.0", "engines": { "bare": ">=1.14.0" } @@ -6684,25 +7337,30 @@ "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", "dev": true, - "optional": true, + "license": "Apache-2.0", "dependencies": { "bare-os": "^3.0.1" } }, "node_modules/bare-stream": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", - "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.1.tgz", + "integrity": "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==", "dev": true, - "optional": true, + "license": "Apache-2.0", "dependencies": { - "streamx": "^2.21.0" + "streamx": "^2.25.0", + "teex": "^1.0.1" }, "peerDependencies": { + "bare-abort-controller": "*", "bare-buffer": "*", "bare-events": "*" }, "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, "bare-buffer": { "optional": true }, @@ -6712,11 +7370,11 @@ } }, "node_modules/bare-url": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", - "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.3.tgz", + "integrity": "sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ==", "dev": true, - "optional": true, + "license": "Apache-2.0", "dependencies": { "bare-path": "^3.0.0" } @@ -6743,20 +7401,24 @@ "optional": true }, "node_modules/baseline-browser-mapping": { - "version": "2.9.18", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.18.tgz", - "integrity": "sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==", + "version": "2.10.27", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.27.tgz", + "integrity": "sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==", "dev": true, "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/basic-ftp": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.1.0.tgz", - "integrity": "sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", + "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.0.0" } @@ -6792,9 +7454,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", "dev": true, "license": "MIT", "dependencies": { @@ -6806,7 +7468,7 @@ "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", - "qs": "~6.14.0", + "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" @@ -6833,6 +7495,22 @@ "dev": true, "license": "MIT" }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/bonjour-service": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", @@ -6852,13 +7530,16 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/braces": { @@ -6885,9 +7566,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "dev": true, "funding": [ { @@ -6905,11 +7586,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -6932,6 +7613,7 @@ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "dev": true, + "license": "MIT", "engines": { "node": "*" } @@ -6999,15 +7681,15 @@ } }, "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" }, "engines": { @@ -7069,20 +7751,10 @@ "tslib": "^2.0.3" } }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/caniuse-lite": { - "version": "1.0.30001766", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", - "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", + "version": "1.0.30001792", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", + "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==", "dev": true, "funding": [ { @@ -7114,7 +7786,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -7127,17 +7798,19 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "node_modules/chalk-template": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "chalk": "^4.1.2" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" } }, "node_modules/charenc": { @@ -7198,10 +7871,11 @@ } }, "node_modules/chromium-bidi": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-13.0.1.tgz", - "integrity": "sha512-c+RLxH0Vg2x2syS9wPw378oJgiJNXtYXUvnVAldUlt5uaHekn0CCU7gPksNgHjrH1qFhmjVXQj4esvuthuC7OQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-14.0.0.tgz", + "integrity": "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "mitt": "^3.0.1", "zod": "^3.24.1" @@ -7210,6 +7884,16 @@ "devtools-protocol": "*" } }, + "node_modules/chromium-bidi/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/clean-css": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", @@ -7223,11 +7907,22 @@ "node": ">= 10.0" } }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -7270,7 +7965,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -7283,7 +7977,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/color-string": { @@ -7304,6 +7997,44 @@ "dev": true, "license": "MIT" }, + "node_modules/command-line-args": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-6.0.2.tgz", + "integrity": "sha512-AIjYVxrV9X752LmPDLbVYv8aMCuHPSLZJXEo2qo/xJfv+NYhaZ4sMSF01rM+gHPaMgvPM0l5D/F+Qx+i2WfSmQ==", + "license": "MIT", + "dependencies": { + "array-back": "^6.2.3", + "find-replace": "^5.0.2", + "lodash.camelcase": "^4.3.0", + "typical": "^7.3.0" + }, + "engines": { + "node": ">=12.20" + }, + "peerDependencies": { + "@75lb/nature": "latest" + }, + "peerDependenciesMeta": { + "@75lb/nature": { + "optional": true + } + } + }, + "node_modules/command-line-usage": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-7.0.4.tgz", + "integrity": "sha512-85UdvzTNx/+s5CkSgBm/0hzP80RFHAa7PsfeADE5ezZF3uHz3/Tqj9gIKGT9PTtpycc3Ua64T0oVulGfKxzfqg==", + "license": "MIT", + "dependencies": { + "array-back": "^6.2.2", + "chalk-template": "^0.4.0", + "table-layout": "^4.1.1", + "typical": "^7.3.0" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/commander": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", @@ -7449,7 +8180,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cookie": { "version": "0.7.2", @@ -7469,19 +8201,20 @@ "license": "MIT" }, "node_modules/copy-webpack-plugin": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-13.0.1.tgz", - "integrity": "sha512-J+YV3WfhY6W/Xf9h+J1znYuqTye2xkBUIGyTPWuBAT27qajBa5mR4f8WBmfDY3YjRftT2kqZZiLi1qf0H+UOFw==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-14.0.0.tgz", + "integrity": "sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==", "dev": true, + "license": "MIT", "dependencies": { "glob-parent": "^6.0.1", "normalize-path": "^3.0.0", "schema-utils": "^4.2.0", - "serialize-javascript": "^6.0.2", + "serialize-javascript": "^7.0.3", "tinyglobby": "^0.2.12" }, "engines": { - "node": ">= 18.12.0" + "node": ">= 20.9.0" }, "funding": { "type": "opencollective", @@ -7505,10 +8238,11 @@ } }, "node_modules/core-js-compat": { - "version": "3.48.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz", - "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", + "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", "dev": true, + "license": "MIT", "dependencies": { "browserslist": "^4.28.1" }, @@ -7518,11 +8252,12 @@ } }, "node_modules/core-js-pure": { - "version": "3.48.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.48.0.tgz", - "integrity": "sha512-1slJgk89tWC51HQ1AEqG+s2VuwpTRr8ocu4n20QUcH1v9lAN0RXen0Q0AABa/DK1I7RrNWLucplOHMx8hfTGTw==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.49.0.tgz", + "integrity": "sha512-XM4RFka59xATyJv/cS3O3Kml72hQXUeGRuuTmMYFxwzc9/7C8OYTaIR/Ji+Yt8DXzsFLNhat15cE/JP15HrCgw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" @@ -7535,9 +8270,9 @@ "license": "MIT" }, "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", + "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7576,29 +8311,6 @@ "node": ">= 8" } }, - "node_modules/cross-spawn/node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/cross-spawn/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/crypt": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", @@ -7609,20 +8321,20 @@ } }, "node_modules/css-loader": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", - "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.4.tgz", + "integrity": "sha512-vv3J9tlOl04WjiMvHQI/9tmIrCxVrj6PFbHemBB1iihpeRbi/I4h033eoFIhwxBBqLhI0KYFS7yvynBFhIZfTw==", "dev": true, "license": "MIT", "dependencies": { "icss-utils": "^5.1.0", - "postcss": "^8.4.33", + "postcss": "^8.4.40", "postcss-modules-extract-imports": "^3.1.0", "postcss-modules-local-by-default": "^4.0.5", "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", - "semver": "^7.5.4" + "semver": "^7.6.3" }, "engines": { "node": ">= 18.12.0" @@ -7632,7 +8344,7 @@ "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "@rspack/core": "0.x || 1.x", + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", "webpack": "^5.27.0" }, "peerDependenciesMeta": { @@ -7644,6 +8356,19 @@ } } }, + "node_modules/css-loader/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/css-select": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", @@ -7712,6 +8437,7 @@ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 14" } @@ -7809,12 +8535,6 @@ "dev": true, "license": "MIT" }, - "node_modules/deep-diff": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-1.0.2.tgz", - "integrity": "sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info." - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -7834,18 +8554,10 @@ "node": ">=0.10.0" } }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/default-browser": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz", - "integrity": "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", "dev": true, "license": "MIT", "dependencies": { @@ -7926,6 +8638,7 @@ "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", "dev": true, + "license": "MIT", "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", @@ -7972,6 +8685,7 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=8" } @@ -7990,37 +8704,11 @@ "license": "MIT" }, "node_modules/devtools-protocol": { - "version": "0.0.1551306", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1551306.tgz", - "integrity": "sha512-CFx8QdSim8iIv+2ZcEOclBKTQY6BI1IEDa7Tm9YkwAXzEWFndTEzpTo5jAUhSnq24IC7xaDw0wvGcm96+Y3PEg==", - "dev": true - }, - "node_modules/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "version": "0.0.1608973", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1608973.tgz", + "integrity": "sha512-Tpm17fxYzt+J7VrGdc1k8YdRqS3YV7se/M6KeemEqvUbq/n7At1rWVuXMxQgpWkdwSdIEKYbU//Bve+Shm4YNQ==", "dev": true, - "license": "Apache-2.0" - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true, - "license": "MIT" + "license": "BSD-3-Clause" }, "node_modules/dns-packet": { "version": "5.6.1", @@ -8036,16 +8724,16 @@ } }, "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=0.10.0" } }, "node_modules/dom-accessibility-api": { @@ -8081,6 +8769,16 @@ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/domelementtype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", @@ -8171,9 +8869,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.278", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.278.tgz", - "integrity": "sha512-dQ0tM1svDRQOwxnXxm+twlGTjr9Upvt8UFWAgmLsxEzFQxhbti4VwxmMjsDxVC51Zo84swW7FVCXEV+VAkhuPw==", + "version": "1.5.352", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.352.tgz", + "integrity": "sha512-9wHk8x6dyuimoe18EdiDPWKExNdxYqo4fn4FwOVVper6RxT3cmpBwBkWWfSOCYJjQdIco/nPhJhNLmn4Ufg1Yg==", "dev": true, "license": "ISC" }, @@ -8181,7 +8879,8 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", @@ -8198,30 +8897,34 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "dev": true, + "license": "MIT", "dependencies": { "once": "^1.4.0" } }, "node_modules/enhanced-resolve": { - "version": "5.18.4", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", - "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "version": "5.21.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.1.tgz", + "integrity": "sha512-8p7DUVq6XJnZEz9W4oSwiwycxBIjHjRzYb3Je3zVN+geKTRQKzAkR/K4PBExlS0090d9nshak6phMUxr3PDjmQ==", "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.3" }, "engines": { "node": ">=10.13.0" } }, "node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "dev": true, "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } @@ -8264,14 +8967,15 @@ "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", "dev": true, + "license": "MIT", "dependencies": { "stackframe": "^1.3.4" } }, "node_modules/es-abstract": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", - "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", "dev": true, "license": "MIT", "dependencies": { @@ -8358,16 +9062,16 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", - "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.2.tgz", + "integrity": "sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", + "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.24.1", + "es-abstract": "^1.24.2", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", @@ -8379,16 +9083,16 @@ "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", - "safe-array-concat": "^1.1.3" + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" } }, "node_modules/es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", "dev": true, "license": "MIT" }, @@ -8487,6 +9191,7 @@ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", @@ -8503,70 +9208,75 @@ "source-map": "~0.6.1" } }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", + "optional": true, "engines": { - "node": ">=4.0" + "node": ">=0.10.0" } }, "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-plugin-react": { @@ -8603,22 +9313,36 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", - "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, "engines": { - "node": ">=10" + "node": ">=18" }, "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, + "node_modules/eslint-plugin-react/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/eslint-plugin-react/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -8626,33 +9350,10 @@ "concat-map": "0.0.1" } }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-react/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, "node_modules/eslint-plugin-react/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -8663,45 +9364,44 @@ } }, "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "version": "2.0.0-next.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", + "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.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/eslint-plugin-react/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "estraverse": "^5.2.0" }, "engines": { - "node": ">=8.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { @@ -8717,27 +9417,17 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/eslint/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } + "license": "MIT" }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -8745,44 +9435,33 @@ "concat-map": "0.0.1" } }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, + "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "engines": { - "node": ">=4.0" + "node": ">= 4" } }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -8793,18 +9472,31 @@ } }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -8815,6 +9507,7 @@ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, + "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -8836,16 +9529,6 @@ "node": ">=0.10" } }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -8859,7 +9542,7 @@ "node": ">=4.0" } }, - "node_modules/esrecurse/node_modules/estraverse": { + "node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", @@ -8869,16 +9552,6 @@ "node": ">=4.0" } }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -8931,6 +9604,7 @@ "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "bare-events": "^2.7.0" } @@ -9047,6 +9721,7 @@ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", @@ -9062,21 +9737,6 @@ "@types/yauzl": "^2.9.1" } }, - "node_modules/extract-zip/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -9088,37 +9748,8 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } + "license": "MIT" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", @@ -9135,9 +9766,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "dev": true, "funding": [ { @@ -9151,10 +9782,10 @@ ], "license": "BSD-3-Clause" }, - "node_modules/fast-xml-parser": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", - "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", + "node_modules/fast-xml-builder": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.9.tgz", + "integrity": "sha512-jcyKVSEX13iseJqg7n/KWw+xnu/7fdrZ333Fac54KjHDIELVCfDDJXYIm6DTJ0Su4gSzrhqiK0DzY/wZbF40mw==", "funding": [ { "type": "github", @@ -9163,7 +9794,25 @@ ], "license": "MIT", "dependencies": { - "strnum": "^1.1.1" + "path-expression-matcher": "^1.1.3" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.3.tgz", + "integrity": "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^2.1.0", + "fast-xml-builder": "^1.1.7", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" @@ -9179,21 +9828,12 @@ "node": ">= 4.9.1" } }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, "node_modules/favicons": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/favicons/-/favicons-7.2.0.tgz", "integrity": "sha512-k/2rVBRIRzOeom3wI9jBPaSEvoTSQEW4iM0EveBmBBKFxO8mSyyRWtDlfC3VnEfu0avmjrMzy8/ZFPSe6F71Hw==", "dev": true, + "license": "MIT", "dependencies": { "escape-html": "^1.0.3", "sharp": "^0.33.1", @@ -9208,6 +9848,7 @@ "resolved": "https://registry.npmjs.org/favicons-webpack-plugin/-/favicons-webpack-plugin-6.0.1.tgz", "integrity": "sha512-Gl0Co4zIZq74EKXdpfe8FaoJqbuf0undV4UgpsL34vqICRAYUDwQdp3D+z+uxEOV0i9o+vHDn7Q6jaSxRiDJUA==", "dev": true, + "license": "MIT", "dependencies": { "find-root": "^1.1.0", "parse-author": "^2.0.0", @@ -9604,6 +10245,19 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/favicons/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/favicons/node_modules/sharp": { "version": "0.33.5", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", @@ -9662,10 +10316,29 @@ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", "dev": true, + "license": "MIT", "dependencies": { "pend": "~1.2.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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/fflate": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", @@ -9673,16 +10346,16 @@ "license": "MIT" }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/fill-range": { @@ -9734,11 +10407,29 @@ "dev": true, "license": "MIT" }, + "node_modules/find-replace": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-5.0.2.tgz", + "integrity": "sha512-Y45BAiE3mz2QsrN2fb5QEtO4qb44NcS7en/0y9PEVsg351HsLeVclP8QPMH79Le9sH3rs5RSwJu99W0WPZO43Q==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@75lb/nature": "latest" + }, + "peerDependenciesMeta": { + "@75lb/nature": { + "optional": true + } + } + }, "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/find-up": { "version": "5.0.0", @@ -9768,31 +10459,36 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, + "node_modules/flatbuffers": { + "version": "25.9.23", + "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-25.9.23.tgz", + "integrity": "sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==", + "license": "Apache-2.0" + }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "dev": true, "funding": [ { @@ -9860,13 +10556,6 @@ "node": ">= 0.6" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -9938,21 +10627,17 @@ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/geojson-vt": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-4.0.2.tgz", - "integrity": "sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==", - "license": "ISC" - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, + "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -10006,12 +10691,16 @@ } }, "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -10040,6 +10729,7 @@ "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", "dev": true, + "license": "MIT", "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", @@ -10064,28 +10754,6 @@ "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", "license": "MIT" }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -10123,55 +10791,14 @@ "dev": true, "license": "BSD-2-Clause" }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/global-prefix": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-4.0.0.tgz", - "integrity": "sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA==", - "license": "MIT", - "dependencies": { - "ini": "^4.1.3", - "kind-of": "^6.0.3", - "which": "^4.0.0" - }, - "engines": { - "node": ">=16" - } - }, "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", + "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", "dev": true, "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -10194,27 +10821,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -10235,13 +10841,6 @@ "dev": true, "license": "ISC" }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, "node_modules/h3-js": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/h3-js/-/h3-js-4.4.0.tgz", @@ -10277,7 +10876,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10342,9 +10940,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", "dev": true, "license": "MIT", "dependencies": { @@ -10364,6 +10962,23 @@ "he": "bin/he" } }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, "node_modules/hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", @@ -10390,39 +11005,6 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/html-encoding-sniffer/node_modules/@exodus/bytes": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", - "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@noble/hashes": "^1.8.0 || ^2.0.0" - }, - "peerDependenciesMeta": { - "@noble/hashes": { - "optional": true - } - } - }, - "node_modules/html-encoding-sniffer/node_modules/@noble/hashes": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", - "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/html-entities": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", @@ -10437,7 +11019,8 @@ "type": "patreon", "url": "https://patreon.com/mdevils" } - ] + ], + "license": "MIT" }, "node_modules/html-minifier-terser": { "version": "6.1.0", @@ -10471,9 +11054,9 @@ } }, "node_modules/html-webpack-plugin": { - "version": "5.6.6", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.6.tgz", - "integrity": "sha512-bLjW01UTrvoWTJQL5LsMRo1SypHW80FTm12OJRSnr3v6YHNhfe+1r0MYUZJMACxnCHURVnBWRwAsWs2yPU9Ezw==", + "version": "5.6.7", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.7.tgz", + "integrity": "sha512-md+vXtdCAe60s1k6AU3dUyMJnDxUyQAwfwPKoLisvgUF1IXjtlLsk2se54+qfL9Mdm26bbwvjJybpNx48NKRLw==", "dev": true, "license": "MIT", "dependencies": { @@ -10523,6 +11106,16 @@ "entities": "^2.0.0" } }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/http-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", @@ -10578,6 +11171,7 @@ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, + "license": "MIT", "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" @@ -10616,6 +11210,7 @@ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, + "license": "MIT", "dependencies": { "agent-base": "^7.1.2", "debug": "4" @@ -10635,9 +11230,9 @@ } }, "node_modules/i18next": { - "version": "26.0.3", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.0.3.tgz", - "integrity": "sha512-1571kXINxHKY7LksWp8wP+zP0YqHSSpl/OW0Y0owFEf2H3s8gCAffWaZivcz14rMkOvn3R/psiQxVsR9t2Nafg==", + "version": "26.0.10", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.0.10.tgz", + "integrity": "sha512-k3yGPAlWR2RdMYoVXJoDZDT87qeHIWKH7gVksdZMpRty7QX/D9QZeYGvN08KGbKHke9wn01eYT+EEsrqX/YTlw==", "funding": [ { "type": "individual", @@ -10653,9 +11248,6 @@ } ], "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.29.2" - }, "peerDependencies": { "typescript": "^5 || ^6" }, @@ -10712,9 +11304,9 @@ "license": "BSD-3-Clause" }, "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, "license": "MIT", "engines": { @@ -10776,75 +11368,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-local/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/import-local/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/import-local/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-local/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/import-local/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -10855,33 +11378,12 @@ "node": ">=0.8.19" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, - "node_modules/ini": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", - "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -10908,18 +11410,19 @@ } }, "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 12" } }, "node_modules/ipaddr.js": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", - "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.4.0.tgz", + "integrity": "sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==", "dev": true, "license": "MIT", "engines": { @@ -11037,13 +11540,13 @@ } }, "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "dev": true, "license": "MIT", "dependencies": { - "hasown": "^2.0.2" + "hasown": "^2.0.3" }, "engines": { "node": ">= 0.4" @@ -11149,6 +11652,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -11206,9 +11710,10 @@ } }, "node_modules/is-lite": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-1.2.1.tgz", - "integrity": "sha512-pgF+L5bxC+10hLBgf6R2P4ZZUBOQIIacbdo8YvuCP8/JvsWxG7aZ9p10DYuLtifFci4l3VITphhMlMV4Y+urPw==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-2.0.0.tgz", + "integrity": "sha512-70f2BMIQlbSUXVKaZUd9a9fJH3IH1PDckV0m4BIIO4LjnNYvOh4Ng7vXIXEwpA0KDZknRq+7fHwGTu0jIdx28g==", + "license": "MIT" }, "node_modules/is-map": { "version": "2.0.3", @@ -11237,9 +11742,9 @@ } }, "node_modules/is-network-error": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", - "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.1.tgz", + "integrity": "sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw==", "dev": true, "license": "MIT", "engines": { @@ -11276,16 +11781,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-obj": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", @@ -11464,9 +11959,9 @@ } }, "node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", "dev": true, "license": "MIT", "dependencies": { @@ -11486,13 +11981,11 @@ "license": "MIT" }, "node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "license": "ISC", - "engines": { - "node": ">=16" - } + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" }, "node_modules/isobject": { "version": "3.0.1", @@ -11536,10 +12029,26 @@ "node": ">= 10.13.0" } }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", "dev": true, "license": "MIT", "bin": { @@ -11550,6 +12059,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -11606,39 +12116,6 @@ } } }, - "node_modules/jsdom/node_modules/@exodus/bytes": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", - "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@noble/hashes": "^1.8.0 || ^2.0.0" - }, - "peerDependenciesMeta": { - "@noble/hashes": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/@noble/hashes": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", - "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/jsdom/node_modules/entities": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", @@ -11653,9 +12130,9 @@ } }, "node_modules/jsdom/node_modules/lru-cache": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", - "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", + "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -11680,6 +12157,7 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, @@ -11687,6 +12165,14 @@ "node": ">=6" } }, + "node_modules/json-bignum": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/json-bignum/-/json-bignum-0.0.3.tgz", + "integrity": "sha512-2WHyXj3OfHSgNyuzDbSxI1w2jgw5gkWSWhS7Qg4bWXx1nLk3jnbwfUeS0PSba3IzpTUWdHxBieELUzXRjQB2zg==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -11702,9 +12188,9 @@ "license": "MIT" }, "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, "license": "MIT" }, @@ -11726,6 +12212,7 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, + "license": "MIT", "bin": { "json5": "lib/cli.js" }, @@ -11781,6 +12268,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -11793,9 +12281,9 @@ "license": "MIT" }, "node_modules/launch-editor": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.12.0.tgz", - "integrity": "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==", + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.13.2.tgz", + "integrity": "sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==", "dev": true, "license": "MIT", "dependencies": { @@ -12087,19 +12575,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -12108,9 +12583,9 @@ "license": "MIT" }, "node_modules/loader-runner": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", - "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz", + "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", "dev": true, "license": "MIT", "engines": { @@ -12138,17 +12613,24 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "dev": true, "license": "MIT" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -12170,6 +12652,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -12189,12 +12672,13 @@ } }, "node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, - "engines": { - "node": ">=12" + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" } }, "node_modules/lz-string": { @@ -12215,12 +12699,6 @@ "license": "ISC", "optional": true }, - "node_modules/lzo-wasm": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/lzo-wasm/-/lzo-wasm-0.0.4.tgz", - "integrity": "sha512-VKlnoJRFrB8SdJhlVKvW5vI1gGwcZ+mvChEXcSX6r2xDNc/Q2FD9esfBmGCuPZdrJ1feO+YcVFd2PTk0c137Gw==", - "license": "BSD-2-Clause" - }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -12232,37 +12710,30 @@ } }, "node_modules/maplibre-gl": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-4.7.1.tgz", - "integrity": "sha512-lgL7XpIwsgICiL82ITplfS7IGwrB1OJIw/pCvprDp2dhmSSEBgmPzYRvwYYYvJGJD7fxUv1Tvpih4nZ6VrLuaA==", + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.24.0.tgz", + "integrity": "sha512-ALyFxgtd5R+65UqZ/++lOqwWcC0SNho9c27fYSyLmG7AfnAul2o46F05aDJGPbFU57wos9dgcIySHs0Xe6ia3A==", "license": "BSD-3-Clause", "dependencies": { - "@mapbox/geojson-rewind": "^0.5.2", "@mapbox/jsonlint-lines-primitives": "^2.0.2", - "@mapbox/point-geometry": "^0.1.0", - "@mapbox/tiny-sdf": "^2.0.6", + "@mapbox/point-geometry": "^1.1.0", + "@mapbox/tiny-sdf": "^2.1.0", "@mapbox/unitbezier": "^0.0.1", - "@mapbox/vector-tile": "^1.3.1", + "@mapbox/vector-tile": "^2.0.4", "@mapbox/whoots-js": "^3.1.0", - "@maplibre/maplibre-gl-style-spec": "^20.3.1", - "@types/geojson": "^7946.0.14", - "@types/geojson-vt": "3.2.5", - "@types/mapbox__point-geometry": "^0.1.4", - "@types/mapbox__vector-tile": "^1.3.4", - "@types/pbf": "^3.0.5", - "@types/supercluster": "^7.1.3", - "earcut": "^3.0.0", - "geojson-vt": "^4.0.2", - "gl-matrix": "^3.4.3", - "global-prefix": "^4.0.0", + "@maplibre/geojson-vt": "^6.1.0", + "@maplibre/maplibre-gl-style-spec": "^24.8.1", + "@maplibre/mlt": "^1.1.8", + "@maplibre/vt-pbf": "^4.3.0", + "@types/geojson": "^7946.0.16", + "earcut": "^3.0.2", + "gl-matrix": "^3.4.4", "kdbush": "^4.0.2", "murmurhash-js": "^1.0.0", - "pbf": "^3.3.0", - "potpack": "^2.0.0", + "pbf": "^4.0.1", + "potpack": "^2.1.0", "quickselect": "^3.0.0", - "supercluster": "^8.0.1", - "tinyqueue": "^3.0.0", - "vt-pbf": "^3.1.3" + "tinyqueue": "^3.0.0" }, "engines": { "node": ">=16.14.0", @@ -12272,12 +12743,41 @@ "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1" } }, + "node_modules/maplibre-gl/node_modules/@mapbox/point-geometry": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-1.1.0.tgz", + "integrity": "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==", + "license": "ISC" + }, + "node_modules/maplibre-gl/node_modules/@mapbox/vector-tile": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-2.0.4.tgz", + "integrity": "sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg==", + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/point-geometry": "~1.1.0", + "@types/geojson": "^7946.0.16", + "pbf": "^4.0.1" + } + }, "node_modules/maplibre-gl/node_modules/earcut": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz", "integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==", "license": "ISC" }, + "node_modules/maplibre-gl/node_modules/pbf": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-4.0.1.tgz", + "integrity": "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==", + "license": "BSD-3-Clause", + "dependencies": { + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -12317,20 +12817,20 @@ } }, "node_modules/memfs": { - "version": "4.56.10", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.56.10.tgz", - "integrity": "sha512-eLvzyrwqLHnLYalJP7YZ3wBe79MXktMdfQbvMrVD80K+NhrIukCVBvgP30zTJYEEDh9hZ/ep9z0KOdD7FSHo7w==", + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.57.2.tgz", + "integrity": "sha512-2nWzSsJzrukurSDna4Z0WywuScK4Id3tSKejgu74u8KCdW4uNrseKRSIDg75C6Yw5ZRqBe0F0EtMNlTbUq8bAQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-core": "4.56.10", - "@jsonjoy.com/fs-fsa": "4.56.10", - "@jsonjoy.com/fs-node": "4.56.10", - "@jsonjoy.com/fs-node-builtins": "4.56.10", - "@jsonjoy.com/fs-node-to-fsa": "4.56.10", - "@jsonjoy.com/fs-node-utils": "4.56.10", - "@jsonjoy.com/fs-print": "4.56.10", - "@jsonjoy.com/fs-snapshot": "4.56.10", + "@jsonjoy.com/fs-core": "4.57.2", + "@jsonjoy.com/fs-fsa": "4.57.2", + "@jsonjoy.com/fs-node": "4.57.2", + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-to-fsa": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2", + "@jsonjoy.com/fs-print": "4.57.2", + "@jsonjoy.com/fs-snapshot": "4.57.2", "@jsonjoy.com/json-pack": "^1.11.0", "@jsonjoy.com/util": "^1.9.0", "glob-to-regex.js": "^1.0.1", @@ -12363,16 +12863,6 @@ "dev": true, "license": "MIT" }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -12397,6 +12887,19 @@ "node": ">=8.6" } }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -12411,9 +12914,9 @@ } }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "dev": true, "license": "MIT", "engines": { @@ -12433,11 +12936,22 @@ "node": ">= 0.6" } }, - "node_modules/mini-css-extract-plugin": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.10.0.tgz", - "integrity": "sha512-540P2c5dYnJlyJxTaSloliZexv8rji6rY8FhQN+WF/82iHQfA23j/xtJx97L+mXOML27EqksSek/g4eK7jaL3g==", + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.10.2.tgz", + "integrity": "sha512-AOSS0IdEB95ayVkxn5oGzNQwqAi2J0Jb/kKm43t7H73s8+f5873g0yuj0PNvK4dO75mu5DHg4nlgp4k6Kga8eg==", + "dev": true, + "license": "MIT", "dependencies": { "schema-utils": "^4.0.0", "tapable": "^2.2.1" @@ -12461,16 +12975,16 @@ "license": "ISC" }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.5" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -12489,7 +13003,8 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/mjolnir.js": { "version": "3.0.0", @@ -12524,22 +13039,10 @@ "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==", "license": "MIT" }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "dev": true, "funding": [ { @@ -12580,10 +13083,11 @@ "license": "MIT" }, "node_modules/netmask": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4.0" } @@ -12599,10 +13103,29 @@ "tslib": "^2.0.3" } }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", "dev": true, "license": "MIT" }, @@ -12633,19 +13156,10 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", "dev": true, "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=0.10.0" } }, "node_modules/object-inspect": { @@ -12917,6 +13431,7 @@ "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", "dev": true, + "license": "MIT", "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.1.2", @@ -12936,6 +13451,7 @@ "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", "dev": true, + "license": "MIT", "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" @@ -12979,6 +13495,7 @@ "resolved": "https://registry.npmjs.org/parse-author/-/parse-author-2.0.0.tgz", "integrity": "sha512-yx5DfvkN8JsHL2xk2Os9oTia467qnvRgey4ahSm2X8epehBLx/gWLcy5KI+Y36ful5DzGbCS6RazqZGgy1gHNw==", "dev": true, + "license": "MIT", "dependencies": { "author-regex": "^1.0.0" }, @@ -13010,6 +13527,7 @@ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", "dev": true, + "license": "MIT", "dependencies": { "entities": "^6.0.0" }, @@ -13017,18 +13535,6 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/parse5/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -13060,14 +13566,19 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, + "node_modules/path-expression-matcher": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=14.0.0" } }, "node_modules/path-key": { @@ -13088,22 +13599,12 @@ "license": "MIT" }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "dev": true, "license": "MIT" }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -13128,7 +13629,8 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", @@ -13138,42 +13640,91 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, "engines": { - "node": ">= 6" + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/pkijs": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.3.3.tgz", - "integrity": "sha512-+KD8hJtqQMYoTuL1bbGOqxb4z+nZkTAwVdNtWwe8Tc2xNbEmdJYIYoc6Qt0uF55e6YW6KuTHw1DjQ18gMhzepw==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.0.tgz", + "integrity": "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -13188,20 +13739,24 @@ "node": ">=16.0.0" } }, + "node_modules/pkijs/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/pocketbase": { "version": "0.26.8", "resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.26.8.tgz", - "integrity": "sha512-aQ/ewvS7ncvAE8wxoW10iAZu6ElgbeFpBhKPnCfvRovNzm2gW8u/sQNPGN6vNgVEagz44kK//C61oKjfa+7Low==" - }, - "node_modules/popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", - "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } + "integrity": "sha512-aQ/ewvS7ncvAE8wxoW10iAZu6ElgbeFpBhKPnCfvRovNzm2gW8u/sQNPGN6vNgVEagz44kK//C61oKjfa+7Low==", + "license": "MIT" }, "node_modules/possible-typed-array-names": { "version": "1.1.0", @@ -13214,9 +13769,9 @@ } }, "node_modules/postcss": { - "version": "8.5.13", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz", - "integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==", + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", "dev": true, "funding": [ { @@ -13242,97 +13797,10 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-js": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", - "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "peerDependencies": { - "postcss": "^8.4.21" - } - }, - "node_modules/postcss-load-config": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", - "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "lilconfig": "^3.1.1" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "jiti": ">=1.21.0", - "postcss": ">=8.0.9", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - }, - "postcss": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, "node_modules/postcss-loader": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.2.0.tgz", - "integrity": "sha512-tHX+RkpsXVcc7st4dSdDGliI+r4aAQDuv+v3vFYHixb6YgjreG5AG4SEB0kDK8u2s6htqEEpKlkhSBUTvWKYnA==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.2.1.tgz", + "integrity": "sha512-k98jtRzthjj3f76MYTs9JTpRqV1RaaMhEU0Lpw9OTmQZQdppg4B30VZ74BojuBHt3F4KyubHJoXCMUeM8Bqeow==", "dev": true, "license": "MIT", "dependencies": { @@ -13348,7 +13816,7 @@ "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "@rspack/core": "0.x || 1.x", + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", "postcss": "^7.0.0 || ^8.0.1", "webpack": "^5.0.0" }, @@ -13361,6 +13829,19 @@ } } }, + "node_modules/postcss-loader/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/postcss-modules-extract-imports": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", @@ -13424,46 +13905,6 @@ "postcss": "^8.1.0" } }, - "node_modules/postcss-nested": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", - "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.1.1" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-nested/node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-selector-parser": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", @@ -13492,11 +13933,10 @@ "license": "ISC" }, "node_modules/preact": { - "version": "10.28.2", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.2.tgz", - "integrity": "sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==", + "version": "10.29.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.1.tgz", + "integrity": "sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==", "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -13513,9 +13953,9 @@ } }, "node_modules/prettier": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", - "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "license": "MIT", "bin": { @@ -13569,14 +14009,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/pretty-format/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -13588,6 +14020,7 @@ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -13596,6 +14029,7 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -13603,10 +14037,17 @@ "react-is": "^16.13.1" } }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/protocol-buffers-schema": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", - "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.1.tgz", + "integrity": "sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==", "license": "MIT" }, "node_modules/proxy-addr": { @@ -13638,6 +14079,7 @@ "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", "dev": true, + "license": "MIT", "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", @@ -13652,17 +14094,29 @@ "node": ">= 14" } }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", "dev": true, + "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -13679,18 +14133,19 @@ } }, "node_modules/puppeteer": { - "version": "24.36.1", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.36.1.tgz", - "integrity": "sha512-uPiDUyf7gd7Il1KnqfNUtHqntL0w1LapEw5Zsuh8oCK8GsqdxySX1PzdIHKB2Dw273gWY4MW0zC5gy3Re9XlqQ==", + "version": "24.43.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.43.0.tgz", + "integrity": "sha512-DRnMFz+J3s4lFUQcjqKl0/7h0jzlCZuUFU9lNjtKrnMl5WI1RwCaIItpHVu9empuPyUreYueN0sUW3/pnfdqsg==", "dev": true, "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.11.2", - "chromium-bidi": "13.0.1", + "@puppeteer/browsers": "2.13.1", + "chromium-bidi": "14.0.0", "cosmiconfig": "^9.0.0", - "devtools-protocol": "0.0.1551306", - "puppeteer-core": "24.36.1", - "typed-query-selector": "^2.12.0" + "devtools-protocol": "0.0.1608973", + "puppeteer-core": "24.43.0", + "typed-query-selector": "^2.12.2" }, "bin": { "puppeteer": "lib/cjs/puppeteer/node/cli.js" @@ -13700,18 +14155,19 @@ } }, "node_modules/puppeteer-core": { - "version": "24.36.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.36.1.tgz", - "integrity": "sha512-L7ykMWc3lQf3HS7ME3PSjp7wMIjJeW6+bKfH/RSTz5l6VUDGubnrC2BKj3UvM28Y5PMDFW0xniJOZHBZPpW1dQ==", + "version": "24.43.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.43.0.tgz", + "integrity": "sha512-cCRNXsUlhyPoKDz6+TiSpfZpRS3mD6Y1YFKhkdr6ik6TMfuJb7fAtXq9ThUFc4sphxObDk3BuAvdxc1Y6YOnqQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.11.2", - "chromium-bidi": "13.0.1", + "@puppeteer/browsers": "2.13.1", + "chromium-bidi": "14.0.0", "debug": "^4.4.3", - "devtools-protocol": "0.0.1551306", - "typed-query-selector": "^2.12.0", - "webdriver-bidi-protocol": "0.4.0", - "ws": "^8.19.0" + "devtools-protocol": "0.0.1608973", + "typed-query-selector": "^2.12.2", + "webdriver-bidi-protocol": "0.4.1", + "ws": "^8.20.0" }, "engines": { "node": ">=18" @@ -13738,9 +14194,9 @@ } }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -13753,43 +14209,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/quickselect": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", "license": "ISC" }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -13817,69 +14242,30 @@ } }, "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^18.3.1" - } - }, - "node_modules/react-floater": { - "version": "0.7.9", - "resolved": "https://registry.npmjs.org/react-floater/-/react-floater-0.7.9.tgz", - "integrity": "sha512-NXqyp9o8FAXOATOEo0ZpyaQ2KPb4cmPMXGWkx377QtJkIXHlHRAGer7ai0r0C1kG5gf+KJ6Gy+gdNIiosvSicg==", - "dependencies": { - "deepmerge": "^4.3.1", - "is-lite": "^0.8.2", - "popper.js": "^1.16.0", - "prop-types": "^15.8.1", - "tree-changes": "^0.9.1" - }, - "peerDependencies": { - "react": "15 - 18", - "react-dom": "15 - 18" - } - }, - "node_modules/react-floater/node_modules/@gilbarbara/deep-equal": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.1.2.tgz", - "integrity": "sha512-jk+qzItoEb0D0xSSmrKDDzf9sheQj/BAPxlgNxgmOaA3mxpUa6ndJLYGZKsJnIVEQSD8zcTbyILz7I0HcnBCRA==" - }, - "node_modules/react-floater/node_modules/is-lite": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-0.8.2.tgz", - "integrity": "sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw==" - }, - "node_modules/react-floater/node_modules/tree-changes": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.9.3.tgz", - "integrity": "sha512-vvvS+O6kEeGRzMglTKbc19ltLWNtmNt1cpBoSYLj/iEcPVvpJasemKOlxBrmZaCtDJoF+4bwv3m01UKYi8mukQ==", - "dependencies": { - "@gilbarbara/deep-equal": "^0.1.1", - "is-lite": "^0.8.2" + "react": "^19.2.6" } }, "node_modules/react-i18next": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.2.tgz", - "integrity": "sha512-shBftH2vaTWK2Bsp7FiL+cevx3xFJlvFxmsDFQSrJc+6twHkP0tv/bGa01VVWzpreUVVwU+3Hev5iFqRg65RwA==", + "version": "17.0.7", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.7.tgz", + "integrity": "sha512-rwtPXsb/zwzDafN+gytcjF5YnqGQQIRmCQ6DctBC1VSipRB8GD/MWEVrFP42vjMyuYydxWxM8CZRt+yiNuuoHg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.29.2", @@ -13887,7 +14273,7 @@ "use-sync-external-store": "^1.6.0" }, "peerDependencies": { - "i18next": ">= 26.0.1", + "i18next": ">= 26.0.10", "react": ">= 16.8.0", "typescript": "^5 || ^6" }, @@ -13907,62 +14293,54 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/react-innertext/-/react-innertext-1.1.5.tgz", "integrity": "sha512-PWAqdqhxhHIv80dT9znP2KvS+hfkbRovFp4zFYHFFlOoQLRiawIic81gKb3U1wEyJZgMwgs3JoLtwryASRWP3Q==", + "license": "MIT", "peerDependencies": { "@types/react": ">=0.0.0 <=99", "react": ">=0.0.0 <=99" } }, "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true }, "node_modules/react-joyride": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/react-joyride/-/react-joyride-2.9.3.tgz", - "integrity": "sha512-1+Mg34XK5zaqJ63eeBhqdbk7dlGCFp36FXwsEvgpjqrtyywX2C6h9vr3jgxP0bGHCw8Ilsp/nRDzNVq6HJ3rNw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/react-joyride/-/react-joyride-3.1.0.tgz", + "integrity": "sha512-+UEDpNsYSHhhSW/OQcNl6+oODYx20EP6TykSD45if0MqAAZMYD+3DU64w9wP3fBjQswvq5BgK99w3rw6ing69g==", + "license": "MIT", "dependencies": { - "@gilbarbara/deep-equal": "^0.3.1", - "deep-diff": "^1.0.2", - "deepmerge": "^4.3.1", - "is-lite": "^1.2.1", - "react-floater": "^0.7.9", + "@fastify/deepmerge": "^3.2.1", + "@floating-ui/react-dom": "^2.1.8", + "@gilbarbara/deep-equal": "^0.4.1", + "@gilbarbara/hooks": "^0.11.0", + "@gilbarbara/types": "^0.2.2", + "is-lite": "^2.0.0", "react-innertext": "^1.1.5", - "react-is": "^16.13.1", "scroll": "^3.0.1", "scrollparent": "^2.1.0", - "tree-changes": "^0.11.2", - "type-fest": "^4.27.0" + "use-sync-external-store": "^1.6.0" }, "peerDependencies": { - "react": "15 - 18", - "react-dom": "15 - 18" - } - }, - "node_modules/react-joyride/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "react": "16.8 - 19", + "react-dom": "16.8 - 19" } }, "node_modules/react-map-gl": { - "version": "7.1.9", - "resolved": "https://registry.npmjs.org/react-map-gl/-/react-map-gl-7.1.9.tgz", - "integrity": "sha512-KsCc8Gyn05wVGlHZoopaiiCr0RCAQ6LDISo5sEy1/pV/d7RlozkF946tiX7IgyijJQMRujHol5QdwUPESjh73w==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/react-map-gl/-/react-map-gl-8.1.1.tgz", + "integrity": "sha512-aSqFAFoxvY7wxbGI93Dz0E41171mkAb3GcNbnkFIotmu88OFw495os6mIDZSi7irYNT/PZEIOEHUxhun4ToGuQ==", "license": "MIT", "dependencies": { - "@maplibre/maplibre-gl-style-spec": "^19.2.1", - "@types/mapbox-gl": ">=1.0.0" + "@vis.gl/react-mapbox": "8.1.1", + "@vis.gl/react-maplibre": "8.1.1" }, "peerDependencies": { "mapbox-gl": ">=1.13.0", - "maplibre-gl": ">=1.13.0 <5.0.0", + "maplibre-gl": ">=1.13.0", "react": ">=16.3.0", "react-dom": ">=16.3.0" }, @@ -13975,36 +14353,12 @@ } } }, - "node_modules/react-map-gl/node_modules/@maplibre/maplibre-gl-style-spec": { - "version": "19.3.3", - "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-19.3.3.tgz", - "integrity": "sha512-cOZZOVhDSulgK0meTsTkmNXb1ahVvmTmWmfx9gRBwc6hq98wS9JP35ESIoNq3xqEan+UN+gn8187Z6E4NKhLsw==", - "license": "ISC", - "dependencies": { - "@mapbox/jsonlint-lines-primitives": "~2.0.2", - "@mapbox/unitbezier": "^0.0.1", - "json-stringify-pretty-compact": "^3.0.0", - "minimist": "^1.2.8", - "rw": "^1.3.3", - "sort-object": "^3.0.3" - }, - "bin": { - "gl-style-format": "dist/gl-style-format.mjs", - "gl-style-migrate": "dist/gl-style-migrate.mjs", - "gl-style-validate": "dist/gl-style-validate.mjs" - } - }, - "node_modules/react-map-gl/node_modules/json-stringify-pretty-compact": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz", - "integrity": "sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA==", - "license": "MIT" - }, "node_modules/react-refresh": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -14078,16 +14432,6 @@ } } }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^2.3.0" - } - }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -14116,6 +14460,19 @@ "node": ">=8.10.0" } }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/rechoir": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", @@ -14163,13 +14520,15 @@ "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/regenerate-unicode-properties": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", "dev": true, + "license": "MIT", "dependencies": { "regenerate": "^1.4.2" }, @@ -14203,6 +14562,7 @@ "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", "dev": true, + "license": "MIT", "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", @@ -14219,13 +14579,15 @@ "version": "0.8.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/regjsparser": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", - "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.1.tgz", + "integrity": "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "jsesc": "~3.1.0" }, @@ -14262,6 +14624,7 @@ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -14284,12 +14647,13 @@ "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "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" @@ -14356,43 +14720,15 @@ "node": ">= 4" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/rolldown": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", - "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.18.tgz", + "integrity": "sha512-phmyKBpuBdRYDf4hgyynGAYn/rDDe+iZXKVJ7WX5b1zQzpLkP5oJRPGsfJuHdzPMlyyEO/4sPW6yfSx2gf7lVg==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.127.0", - "@rolldown/pluginutils": "1.0.0-rc.17" + "@oxc-project/types": "=0.128.0", + "@rolldown/pluginutils": "1.0.0-rc.18" }, "bin": { "rolldown": "bin/cli.mjs" @@ -14401,21 +14737,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-x64": "1.0.0-rc.17", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" + "@rolldown/binding-android-arm64": "1.0.0-rc.18", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.18", + "@rolldown/binding-darwin-x64": "1.0.0-rc.18", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.18", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.18", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.18", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.18", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.18", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.18", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.18", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.18" } }, "node_modules/run-applescript": { @@ -14431,30 +14767,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, "node_modules/rw": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", @@ -14462,15 +14774,15 @@ "license": "BSD-3-Clause" }, "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", "has-symbols": "^1.1.0", "isarray": "^2.0.5" }, @@ -14544,10 +14856,11 @@ "license": "MIT" }, "node_modules/sax": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", - "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", "dev": true, + "license": "BlueOak-1.0.0", "engines": { "node": ">=11.0.0" } @@ -14566,13 +14879,10 @@ } }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" }, "node_modules/schema-utils": { "version": "4.3.3", @@ -14594,15 +14904,54 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/scroll": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/scroll/-/scroll-3.0.1.tgz", - "integrity": "sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg==" + "integrity": "sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg==", + "license": "MIT" }, "node_modules/scrollparent": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/scrollparent/-/scrollparent-2.1.0.tgz", - "integrity": "sha512-bnnvJL28/Rtz/kz2+4wpBjHzWoEzXhVg/TE8BeVGJHUqE8THNIRnDxDWMktwM+qahvlRdvlLdsQfYe+cuqfZeA==" + "integrity": "sha512-bnnvJL28/Rtz/kz2+4wpBjHzWoEzXhVg/TE8BeVGJHUqE8THNIRnDxDWMktwM+qahvlRdvlLdsQfYe+cuqfZeA==", + "license": "ISC" }, "node_modules/select-hose": { "version": "2.0.0", @@ -14626,16 +14975,13 @@ } }, "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" } }, "node_modules/send": { @@ -14681,13 +15027,13 @@ "license": "MIT" }, "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", + "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", "dev": true, "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" + "engines": { + "node": ">=20.0.0" } }, "node_modules/serve-index": { @@ -14918,6 +15264,19 @@ "@img/sharp-win32-x64": "0.34.5" } }, + "node_modules/sharp/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -14975,14 +15334,14 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -15054,21 +15413,12 @@ "dev": true, "license": "MIT" }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" @@ -15093,12 +15443,13 @@ } }, "node_modules/socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.8.tgz", + "integrity": "sha512-NlGELfPrgX2f1TAAcz0WawlLn+0r3FyhhCRpFFK2CemXenPYvzMWWZINv3eDNo9ucdwme7oCHRY0Jnbs4aIkog==", "dev": true, + "license": "MIT", "dependencies": { - "ip-address": "^10.0.1", + "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" }, "engines": { @@ -15111,6 +15462,7 @@ "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "dev": true, + "license": "MIT", "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", @@ -15156,13 +15508,13 @@ } }, "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true, "license": "BSD-3-Clause", "engines": { - "node": ">=0.10.0" + "node": ">= 12" } }, "node_modules/source-map-js": { @@ -15186,6 +15538,16 @@ "source-map": "^0.6.0" } }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", @@ -15287,7 +15649,8 @@ "version": "1.3.4", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/statuses": { "version": "2.0.2", @@ -15321,10 +15684,11 @@ } }, "node_modules/streamx": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", - "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "version": "2.25.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz", + "integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==", "dev": true, + "license": "MIT", "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", @@ -15345,6 +15709,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -15479,9 +15844,9 @@ } }, "node_modules/strnum": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", - "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", + "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", "funding": [ { "type": "github", @@ -15507,61 +15872,25 @@ "webpack": "^5.27.0" } }, - "node_modules/sucrase": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", - "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "tinyglobby": "^0.2.11", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/sucrase/node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/supercluster": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==", + "license": "ISC", "dependencies": { "kdbush": "^4.0.2" } }, "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": ">=8" } }, "node_modules/supports-preserve-symlinks-flag": { @@ -15584,72 +15913,30 @@ "dev": true, "license": "MIT" }, + "node_modules/table-layout": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-4.1.1.tgz", + "integrity": "sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==", + "license": "MIT", + "dependencies": { + "array-back": "^6.2.2", + "wordwrapjs": "^5.1.0" + }, + "engines": { + "node": ">=12.17" + } + }, "node_modules/tailwindcss": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", - "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.4.tgz", + "integrity": "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==", "dev": true, - "license": "MIT", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.6.0", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.21.7", - "lilconfig": "^3.1.3", - "micromatch": "^4.0.8", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.4.47", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", - "postcss-nested": "^6.2.0", - "postcss-selector-parser": "^6.1.2", - "resolve": "^1.22.8", - "sucrase": "^3.35.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tailwindcss/node_modules/jiti": { - "version": "1.21.7", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "bin/jiti.js" - } - }, - "node_modules/tailwindcss/node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } + "license": "MIT" }, "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "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": { @@ -15661,10 +15948,11 @@ } }, "node_modules/tar-fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", - "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz", + "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==", "dev": true, + "license": "MIT", "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" @@ -15675,34 +15963,32 @@ } }, "node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", + "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", "dev": true, + "license": "MIT", "dependencies": { "b4a": "^1.6.4", + "bare-fs": "^4.5.5", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, - "node_modules/tar-stream/node_modules/b4a": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", - "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", "dev": true, - "peerDependencies": { - "react-native-b4a": "*" - }, - "peerDependenciesMeta": { - "react-native-b4a": { - "optional": true - } + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" } }, "node_modules/terser": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", - "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "version": "5.47.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.47.1.tgz", + "integrity": "sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -15719,16 +16005,15 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.16", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", - "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.5.0.tgz", + "integrity": "sha512-UYhptBwhWvfIjKd/UuFo6D8uq9xpGLDK+z8EDsj/zWhrTaH34cKEbrkMKfV5YWqGBvAYA3tlzZbs2R+qYrbQJA==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", "terser": "^5.31.1" }, "engines": { @@ -15761,35 +16046,15 @@ "license": "MIT" }, "node_modules/text-decoder": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", - "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "b4a": "^1.6.4" } }, - "node_modules/text-decoder/node_modules/b4a": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", - "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", - "dev": true, - "peerDependencies": { - "react-native-b4a": "*" - }, - "peerDependenciesMeta": { - "react-native-b4a": { - "optional": true - } - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, "node_modules/texture-compressor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/texture-compressor/-/texture-compressor-1.0.2.tgz", @@ -15812,33 +16077,10 @@ "sprintf-js": "~1.0.2" } }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/thingies": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.5.0.tgz", - "integrity": "sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.0.tgz", + "integrity": "sha512-rMHRjmlFLM1R96UYPvpmnc3LYtdFrT33JIB7L9hetGue1qAPfn1N2LJeEjxUSidu1Iku+haLZXDuEXUHNGO/lg==", "dev": true, "license": "MIT", "engines": { @@ -15893,37 +16135,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/tinyqueue": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", @@ -16009,15 +16220,6 @@ "node": ">=20" } }, - "node_modules/tree-changes": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.11.3.tgz", - "integrity": "sha512-r14mvDZ6tqz8PRQmlFKjhUVngu4VZ9d92ON3tp0EGpFBE6PAHOq8Bx8m8ahbNoGE3uI/npjYcJiqVydyOiYXag==", - "dependencies": { - "@gilbarbara/deep-equal": "^0.3.1", - "is-lite": "^1.2.1" - } - }, "node_modules/tree-dump": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", @@ -16036,29 +16238,22 @@ } }, "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/ts-loader": { - "version": "9.5.4", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.4.tgz", - "integrity": "sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==", + "version": "9.5.7", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.7.tgz", + "integrity": "sha512-/ZNrKgA3K3PtpMYOC71EeMWIloGw3IYEa5/t1cyz2r5/PyUwTXGzYJvcD3kfUvmhlfpz1rhV8B2O6IVTQ0avsg==", "dev": true, "license": "MIT", "dependencies": { @@ -16076,14 +16271,17 @@ "webpack": "^5.0.0" } }, - "node_modules/ts-loader/node_modules/source-map": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "node_modules/ts-loader/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, - "license": "BSD-3-Clause", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">= 12" + "node": ">=10" } }, "node_modules/tslib": { @@ -16126,13 +16324,12 @@ } }, "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=10" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -16231,15 +16428,16 @@ } }, "node_modules/typed-query-selector": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", - "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", - "dev": true + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz", + "integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==", + "dev": true, + "license": "MIT" }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "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": { @@ -16265,6 +16463,15 @@ "integrity": "sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg==", "license": "MIT" }, + "node_modules/typical": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-7.3.0.tgz", + "integrity": "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==", + "license": "MIT", + "engines": { + "node": ">=12.17" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -16295,9 +16502,10 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "devOptional": true, "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -16305,6 +16513,7 @@ "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -16314,6 +16523,7 @@ "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", "dev": true, + "license": "MIT", "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" @@ -16327,6 +16537,7 @@ "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -16336,6 +16547,7 @@ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -16485,6 +16697,7 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", "dev": true, "license": "MIT", "bin": { @@ -16502,16 +16715,16 @@ } }, "node_modules/vite": { - "version": "8.0.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", - "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", + "version": "8.0.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.11.tgz", + "integrity": "sha512-Jz1mxtUBR5xTT65VOdJZUUeoyLtqljmFkiUXhPTLZka3RDc9vpi/xXkyrnsdRcm2lIi3l3GPMnAidTsEGIj3Ow==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", - "postcss": "^8.5.10", - "rolldown": "1.0.0-rc.17", + "postcss": "^8.5.14", + "rolldown": "1.0.0-rc.18", "tinyglobby": "^0.2.16" }, "bin": { @@ -16528,7 +16741,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", + "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -16579,19 +16792,6 @@ } } }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/vitest": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", @@ -16682,19 +16882,6 @@ } } }, - "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/void-elements": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", @@ -16704,17 +16891,6 @@ "node": ">=0.10.0" } }, - "node_modules/vt-pbf": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", - "integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==", - "license": "MIT", - "dependencies": { - "@mapbox/point-geometry": "0.1.0", - "@mapbox/vector-tile": "^1.3.1", - "pbf": "^3.2.1" - } - }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -16753,10 +16929,11 @@ } }, "node_modules/webdriver-bidi-protocol": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.0.tgz", - "integrity": "sha512-U9VIlNRrq94d1xxR9JrCEAx5Gv/2W7ERSv8oWRoNe/QYbfccS0V3h/H6qeNeCRJxXGMhhnkqvwNrvPAYeuP9VA==", - "dev": true + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.1.tgz", + "integrity": "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==", + "dev": true, + "license": "Apache-2.0" }, "node_modules/webidl-conversions": { "version": "8.0.1", @@ -16769,9 +16946,9 @@ } }, "node_modules/webpack": { - "version": "5.104.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", - "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", + "version": "5.106.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.106.2.tgz", + "integrity": "sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==", "dev": true, "license": "MIT", "dependencies": { @@ -16781,25 +16958,24 @@ "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.15.0", + "acorn": "^8.16.0", "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.4", + "enhanced-resolve": "^5.20.0", "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.3.1", - "mime-types": "^2.1.27", + "mime-db": "^1.54.0", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.16", - "watchpack": "^2.4.4", - "webpack-sources": "^3.3.3" + "terser-webpack-plugin": "^5.3.17", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.4" }, "bin": { "webpack": "bin/webpack.js" @@ -16818,43 +16994,38 @@ } }, "node_modules/webpack-cli": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", - "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-7.0.2.tgz", + "integrity": "sha512-dB0R4T+C/8YuvM+fabdvil6QE44/ChDXikV5lOOkrUeCkW5hTJv2pGLE3keh+D5hjYw8icBaJkZzpFoaHV4T+g==", "dev": true, "license": "MIT", "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.1", - "@webpack-cli/info": "^2.0.2", - "@webpack-cli/serve": "^2.0.5", - "colorette": "^2.0.14", - "commander": "^10.0.1", - "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", + "@discoveryjs/json-ext": "^1.0.0", + "commander": "^14.0.3", + "cross-spawn": "^7.0.6", + "envinfo": "^7.14.0", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", "interpret": "^3.1.1", "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" + "webpack-merge": "^6.0.1" }, "bin": { "webpack-cli": "bin/cli.js" }, "engines": { - "node": ">=14.15.0" + "node": ">=20.9.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "5.x.x" + "webpack": "^5.101.0", + "webpack-bundle-analyzer": "^4.0.0 || ^5.0.0", + "webpack-dev-server": "^5.0.0" }, "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, "webpack-bundle-analyzer": { "optional": true }, @@ -16864,13 +17035,13 @@ } }, "node_modules/webpack-cli/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "dev": true, "license": "MIT", "engines": { - "node": ">=14" + "node": ">=20" } }, "node_modules/webpack-dev-middleware": { @@ -16903,16 +17074,6 @@ } } }, - "node_modules/webpack-dev-middleware/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/webpack-dev-middleware/node_modules/mime-types": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", @@ -16989,30 +17150,54 @@ } }, "node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", "dev": true, "license": "MIT", "dependencies": { "clone-deep": "^4.0.1", "flat": "^5.0.2", - "wildcard": "^2.0.0" + "wildcard": "^2.0.1" }, "engines": { - "node": ">=10.0.0" + "node": ">=18.0.0" } }, "node_modules/webpack-sources": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", - "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.4.1.tgz", + "integrity": "sha512-eACpxRN02yaawnt+uUNIF7Qje6A9zArxBbcAJjK1PK3S9Ycg5jIuJ8pW4q8EMnwNZCEGltcjkRx1QzOxOkKD8A==", "dev": true, "license": "MIT", "engines": { "node": ">=10.13.0" } }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", @@ -17038,12 +17223,6 @@ "node": ">=0.8.0" } }, - "node_modules/wgsl_reflect": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/wgsl_reflect/-/wgsl_reflect-1.2.3.tgz", - "integrity": "sha512-BQWBIsOn411M+ffBxmA6QRLvAOVbuz3Uk4NusxnqC1H7aeQcVLhdA3k2k/EFFFtqVjhz3z7JOOZF1a9hj2tv4Q==", - "license": "MIT" - }, "node_modules/whatwg-mimetype": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", @@ -17069,52 +17248,20 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/whatwg-url/node_modules/@exodus/bytes": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", - "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@noble/hashes": "^1.8.0 || ^2.0.0" - }, - "peerDependenciesMeta": { - "@noble/hashes": { - "optional": true - } - } - }, - "node_modules/whatwg-url/node_modules/@noble/hashes": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", - "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { - "isexe": "^3.1.1" + "isexe": "^2.0.0" }, "bin": { - "node-which": "bin/which.js" + "node-which": "bin/node-which" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": ">= 8" } }, "node_modules/which-boxed-primitive": { @@ -17247,11 +17394,21 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrapjs": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-5.1.1.tgz", + "integrity": "sha512-0yweIbkINJodk27gX9LBGMzyQdBDan3s/dEAiwBOj+Mf0PPyWL6/rikalkv8EeD0E8jm4o5RXEOrFTP3NXbhJg==", + "license": "MIT", + "engines": { + "node": ">=12.17" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -17272,9 +17429,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", - "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", "dev": true, "license": "MIT", "engines": { @@ -17324,6 +17481,7 @@ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", "dev": true, + "license": "MIT", "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" @@ -17337,6 +17495,7 @@ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4.0" } @@ -17353,6 +17512,7 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } @@ -17361,13 +17521,15 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, + "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -17386,6 +17548,7 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, + "license": "ISC", "engines": { "node": ">=12" } @@ -17395,6 +17558,7 @@ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", "dev": true, + "license": "MIT", "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" @@ -17414,14 +17578,28 @@ } }, "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, "node_modules/zstd-codec": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/zstd-codec/-/zstd-codec-0.1.5.tgz", diff --git a/frontend/package.json b/frontend/package.json index db8b3d6..75b43e7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,65 +11,72 @@ "lint": "eslint src --ext .ts,.tsx", "lint:fix": "eslint src --ext .ts,.tsx --fix", "format": "prettier --write \"src/**/*.{ts,tsx,css}\"", - "format:check": "prettier --check \"src/**/*.{ts,tsx,css}\"" + "format:check": "prettier --check \"src/**/*.{ts,tsx,css}\"", + "check:i18n": "node scripts/check-translations.mjs" }, "dependencies": { - "@deck.gl/core": "^9.0.0", - "@deck.gl/geo-layers": "^9.0.0", - "@deck.gl/layers": "^9.0.0", - "@deck.gl/mapbox": "^9.2.6", - "@deck.gl/react": "^9.0.0", - "@plausible-analytics/tracker": "^0.4.4", - "@protomaps/basemaps": "^5.7.0", - "@radix-ui/react-select": "^2.0.0", - "@radix-ui/react-slider": "^1.1.0", + "@deck.gl/core": "^9.3.2", + "@deck.gl/extensions": "^9.3.2", + "@deck.gl/geo-layers": "^9.3.2", + "@deck.gl/layers": "^9.3.2", + "@deck.gl/mapbox": "^9.3.2", + "@deck.gl/mesh-layers": "^9.3.2", + "@deck.gl/react": "^9.3.2", + "@deck.gl/widgets": "^9.3.2", + "@plausible-analytics/tracker": "^0.4.5", + "@protomaps/basemaps": "^5.7.2", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-slider": "^1.3.6", "@types/supercluster": "^7.1.3", - "i18next": "^26.0.3", - "maplibre-gl": "^4.0.0", + "i18next": "^26.0.10", + "maplibre-gl": "^5.24.0", "pocketbase": "^0.26.8", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-i18next": "^17.0.2", - "react-joyride": "^2.9.3", - "react-map-gl": "^7.1.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "react-i18next": "^17.0.7", + "react-joyride": "^3.1.0", + "react-map-gl": "^8.1.1", "supercluster": "^8.0.1" }, "devDependencies": { "@babel/core": "^7.29.0", - "@babel/preset-env": "^7.29.0", + "@babel/preset-env": "^7.29.5", "@babel/preset-react": "^7.28.5", "@babel/preset-typescript": "^7.28.5", + "@eslint/js": "^9.39.4", "@pmmmwh/react-refresh-webpack-plugin": "^0.6.2", + "@tailwindcss/postcss": "^4.2.4", "@testing-library/react": "^16.3.2", - "@types/react": "^18.2.0", - "@types/react-dom": "^18.2.0", - "@typescript-eslint/eslint-plugin": "^7.0.0", - "@typescript-eslint/parser": "^7.0.0", - "autoprefixer": "^10.4.0", - "babel-loader": "^10.0.0", - "copy-webpack-plugin": "^13.0.1", - "css-loader": "^7.0.0", - "eslint": "^8.57.0", - "eslint-plugin-react": "^7.34.0", - "eslint-plugin-react-hooks": "^4.6.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@typescript-eslint/eslint-plugin": "^8.59.2", + "@typescript-eslint/parser": "^8.59.2", + "autoprefixer": "^10.5.0", + "babel-loader": "^10.1.1", + "copy-webpack-plugin": "^14.0.0", + "css-loader": "^7.1.4", + "eslint": "^9.39.4", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.1.1", "favicons": "^7.2.0", "favicons-webpack-plugin": "^6.0.1", - "html-webpack-plugin": "^5.6.0", + "globals": "^17.6.0", + "html-webpack-plugin": "^5.6.7", "jsdom": "^29.1.1", - "mini-css-extract-plugin": "^2.9.0", - "postcss": "^8.4.0", - "postcss-loader": "^8.0.0", - "prettier": "^3.2.0", - "puppeteer": "^24.0.0", + "mini-css-extract-plugin": "^2.10.2", + "postcss": "^8.5.14", + "postcss-loader": "^8.2.1", + "prettier": "^3.8.3", + "puppeteer": "^24.43.0", "react-refresh": "^0.18.0", "sharp": "^0.34.5", "style-loader": "^4.0.0", - "tailwindcss": "^3.4.0", - "ts-loader": "^9.5.0", - "typescript": "^5.4.0", + "tailwindcss": "^4.2.4", + "ts-loader": "^9.5.7", + "typescript": "^6.0.3", "vitest": "^4.1.5", - "webpack": "^5.90.0", - "webpack-cli": "^5.1.0", - "webpack-dev-server": "^5.0.0" + "webpack": "^5.106.2", + "webpack-cli": "^7.0.2", + "webpack-dev-server": "^5.2.3" } } diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js index 12a703d..b4bee66 100644 --- a/frontend/postcss.config.js +++ b/frontend/postcss.config.js @@ -1,6 +1,6 @@ module.exports = { plugins: { - tailwindcss: {}, + '@tailwindcss/postcss': {}, autoprefixer: {}, }, }; diff --git a/frontend/public/assets/poi-icons/brands_2023/supermarkets/farmfoods.svg b/frontend/public/assets/poi-icons/brands_2023/supermarkets/farmfoods.svg new file mode 100644 index 0000000..438b4a7 --- /dev/null +++ b/frontend/public/assets/poi-icons/brands_2023/supermarkets/farmfoods.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/assets/poi-icons/brands_2023/supermarkets/heron_foods.svg b/frontend/public/assets/poi-icons/brands_2023/supermarkets/heron_foods.svg new file mode 100644 index 0000000..bb8cd82 --- /dev/null +++ b/frontend/public/assets/poi-icons/brands_2023/supermarkets/heron_foods.svg @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/assets/poi-icons/brands_2023/supermarkets/little_waitrose.svg b/frontend/public/assets/poi-icons/brands_2023/supermarkets/little_waitrose.svg new file mode 100644 index 0000000..785dbe1 --- /dev/null +++ b/frontend/public/assets/poi-icons/brands_2023/supermarkets/little_waitrose.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + diff --git a/frontend/public/assets/poi-icons/brands_2024/amazon_fresh.svg b/frontend/public/assets/poi-icons/brands_2024/amazon_fresh.svg new file mode 100644 index 0000000..aedf6da --- /dev/null +++ b/frontend/public/assets/poi-icons/brands_2024/amazon_fresh.svg @@ -0,0 +1,50 @@ + + diff --git a/frontend/public/assets/poi-icons/brands_2024/booths.svg b/frontend/public/assets/poi-icons/brands_2024/booths.svg new file mode 100644 index 0000000..be3d2da --- /dev/null +++ b/frontend/public/assets/poi-icons/brands_2024/booths.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/assets/poi-icons/brands_2024/budgens.svg b/frontend/public/assets/poi-icons/brands_2024/budgens.svg new file mode 100644 index 0000000..44f1aab --- /dev/null +++ b/frontend/public/assets/poi-icons/brands_2024/budgens.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/assets/poi-icons/brands_2024/cook.svg b/frontend/public/assets/poi-icons/brands_2024/cook.svg new file mode 100644 index 0000000..aab0d8c --- /dev/null +++ b/frontend/public/assets/poi-icons/brands_2024/cook.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/assets/poi-icons/brands_2024/dunnes_stores.svg b/frontend/public/assets/poi-icons/brands_2024/dunnes_stores.svg new file mode 100644 index 0000000..4152b47 --- /dev/null +++ b/frontend/public/assets/poi-icons/brands_2024/dunnes_stores.svg @@ -0,0 +1,23 @@ + + diff --git a/frontend/public/assets/poi-icons/brands_2024/iceland.svg b/frontend/public/assets/poi-icons/brands_2024/iceland.svg new file mode 100644 index 0000000..96b6263 --- /dev/null +++ b/frontend/public/assets/poi-icons/brands_2024/iceland.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + diff --git a/frontend/public/assets/poi-icons/brands_2024/makro.svg b/frontend/public/assets/poi-icons/brands_2024/makro.svg new file mode 100644 index 0000000..9c82068 --- /dev/null +++ b/frontend/public/assets/poi-icons/brands_2024/makro.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/poi-icons/brands_2024/mns.svg b/frontend/public/assets/poi-icons/brands_2024/mns.svg new file mode 100644 index 0000000..deb8ec7 --- /dev/null +++ b/frontend/public/assets/poi-icons/brands_2024/mns.svg @@ -0,0 +1,49 @@ + + diff --git a/frontend/public/assets/poi-icons/brands_2024/morrisons_daily.svg b/frontend/public/assets/poi-icons/brands_2024/morrisons_daily.svg new file mode 100644 index 0000000..b9d4881 --- /dev/null +++ b/frontend/public/assets/poi-icons/brands_2024/morrisons_daily.svg @@ -0,0 +1,45 @@ + + diff --git a/frontend/public/assets/poi-icons/brands_2024/sainsburys_local.svg b/frontend/public/assets/poi-icons/brands_2024/sainsburys_local.svg new file mode 100644 index 0000000..2cdd79b --- /dev/null +++ b/frontend/public/assets/poi-icons/brands_2024/sainsburys_local.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + Local + diff --git a/frontend/public/assets/poi-icons/brands_2024/wholefoods.svg b/frontend/public/assets/poi-icons/brands_2024/wholefoods.svg new file mode 100644 index 0000000..7827a6d --- /dev/null +++ b/frontend/public/assets/poi-icons/brands_2024/wholefoods.svg @@ -0,0 +1,47 @@ + + diff --git a/frontend/public/assets/poi-icons/logos/aldi.svg b/frontend/public/assets/poi-icons/logos/aldi.svg new file mode 100644 index 0000000..e2341dd --- /dev/null +++ b/frontend/public/assets/poi-icons/logos/aldi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/poi-icons/logos/asda.svg b/frontend/public/assets/poi-icons/logos/asda.svg new file mode 100644 index 0000000..63d0219 --- /dev/null +++ b/frontend/public/assets/poi-icons/logos/asda.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/poi-icons/logos/centra.svg b/frontend/public/assets/poi-icons/logos/centra.svg new file mode 100644 index 0000000..93bf2c9 --- /dev/null +++ b/frontend/public/assets/poi-icons/logos/centra.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/poi-icons/logos/coop.svg b/frontend/public/assets/poi-icons/logos/coop.svg new file mode 100644 index 0000000..a80894e --- /dev/null +++ b/frontend/public/assets/poi-icons/logos/coop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/poi-icons/logos/costco.svg b/frontend/public/assets/poi-icons/logos/costco.svg new file mode 100644 index 0000000..9d6edef --- /dev/null +++ b/frontend/public/assets/poi-icons/logos/costco.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/public/assets/poi-icons/logos/lidl.svg b/frontend/public/assets/poi-icons/logos/lidl.svg new file mode 100644 index 0000000..0a23b87 --- /dev/null +++ b/frontend/public/assets/poi-icons/logos/lidl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/poi-icons/logos/morrisons.svg b/frontend/public/assets/poi-icons/logos/morrisons.svg new file mode 100644 index 0000000..b9c3f2f --- /dev/null +++ b/frontend/public/assets/poi-icons/logos/morrisons.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/poi-icons/logos/planet_organic.svg b/frontend/public/assets/poi-icons/logos/planet_organic.svg new file mode 100644 index 0000000..7e91604 --- /dev/null +++ b/frontend/public/assets/poi-icons/logos/planet_organic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/poi-icons/logos/sainsburys.svg b/frontend/public/assets/poi-icons/logos/sainsburys.svg new file mode 100644 index 0000000..8eb3a1e --- /dev/null +++ b/frontend/public/assets/poi-icons/logos/sainsburys.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/poi-icons/logos/spar.svg b/frontend/public/assets/poi-icons/logos/spar.svg new file mode 100644 index 0000000..9ddc906 --- /dev/null +++ b/frontend/public/assets/poi-icons/logos/spar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/poi-icons/logos/tesco.svg b/frontend/public/assets/poi-icons/logos/tesco.svg new file mode 100644 index 0000000..7945d31 --- /dev/null +++ b/frontend/public/assets/poi-icons/logos/tesco.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/poi-icons/logos/tesco_express.svg b/frontend/public/assets/poi-icons/logos/tesco_express.svg new file mode 100644 index 0000000..28a8f3e --- /dev/null +++ b/frontend/public/assets/poi-icons/logos/tesco_express.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/poi-icons/logos/tesco_extra.svg b/frontend/public/assets/poi-icons/logos/tesco_extra.svg new file mode 100644 index 0000000..a505255 --- /dev/null +++ b/frontend/public/assets/poi-icons/logos/tesco_extra.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/poi-icons/logos/the_food_warehouse.png b/frontend/public/assets/poi-icons/logos/the_food_warehouse.png new file mode 100644 index 0000000..0fb14a2 Binary files /dev/null and b/frontend/public/assets/poi-icons/logos/the_food_warehouse.png differ diff --git a/frontend/public/assets/poi-icons/logos/waitrose.svg b/frontend/public/assets/poi-icons/logos/waitrose.svg new file mode 100644 index 0000000..d04fd57 --- /dev/null +++ b/frontend/public/assets/poi-icons/logos/waitrose.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/poi-icons/public_transport/london_tube.svg b/frontend/public/assets/poi-icons/public_transport/london_tube.svg new file mode 100644 index 0000000..86bf3db --- /dev/null +++ b/frontend/public/assets/poi-icons/public_transport/london_tube.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/poi-icons/visuals/mns.svg b/frontend/public/assets/poi-icons/visuals/mns.svg new file mode 100644 index 0000000..90d315f --- /dev/null +++ b/frontend/public/assets/poi-icons/visuals/mns.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/home-hex-pattern-dark.svg b/frontend/public/home-hex-pattern-dark.svg new file mode 100644 index 0000000..51f941a --- /dev/null +++ b/frontend/public/home-hex-pattern-dark.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/public/home-hex-pattern.svg b/frontend/public/home-hex-pattern.svg new file mode 100644 index 0000000..255767b --- /dev/null +++ b/frontend/public/home-hex-pattern.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/public/sitemap.xml b/frontend/public/sitemap.xml index da7f388..a74e07d 100644 --- a/frontend/public/sitemap.xml +++ b/frontend/public/sitemap.xml @@ -5,11 +5,6 @@ weekly 1.0 - - https://perfect-postcode.co.uk/dashboard - daily - 0.9 - https://perfect-postcode.co.uk/learn monthly @@ -20,4 +15,59 @@ monthly 0.8 + + https://perfect-postcode.co.uk/property-price-map + monthly + 0.85 + + + https://perfect-postcode.co.uk/postcode-property-search + monthly + 0.85 + + + https://perfect-postcode.co.uk/commute-property-search + monthly + 0.85 + + + https://perfect-postcode.co.uk/school-property-search + monthly + 0.8 + + + https://perfect-postcode.co.uk/postcode-checker + monthly + 0.8 + + + https://perfect-postcode.co.uk/property-search/birmingham + monthly + 0.75 + + + https://perfect-postcode.co.uk/property-search/manchester + monthly + 0.75 + + + https://perfect-postcode.co.uk/property-search/bristol + monthly + 0.75 + + + https://perfect-postcode.co.uk/data-sources + monthly + 0.7 + + + https://perfect-postcode.co.uk/methodology + monthly + 0.7 + + + https://perfect-postcode.co.uk/privacy-security + monthly + 0.6 + diff --git a/frontend/public/video/poster.jpg b/frontend/public/video/poster.jpg new file mode 100644 index 0000000..88dc2b5 Binary files /dev/null and b/frontend/public/video/poster.jpg differ diff --git a/frontend/public/video/recording.mp4 b/frontend/public/video/recording.mp4 new file mode 100644 index 0000000..8902753 Binary files /dev/null and b/frontend/public/video/recording.mp4 differ diff --git a/frontend/scripts/check-translations.mjs b/frontend/scripts/check-translations.mjs new file mode 100644 index 0000000..c68effd --- /dev/null +++ b/frontend/scripts/check-translations.mjs @@ -0,0 +1,259 @@ +#!/usr/bin/env node +// Validates that every translation file under src/i18n is complete and consistent. +// +// Checks: +// 1. Locales declared in SUPPORTED_LANGUAGES (index.ts) match the files in locales/ +// and the language records in descriptions.ts / details.ts. +// 2. Every leaf key in en.ts is present and non-empty in every other locale. +// 3. Every {{placeholder}} and HTML tag in an English string also appears, +// with the same multiset, in the translated string. +// 4. descriptions.ts and details.ts: the union of feature-name keys across +// languages is treated as canonical; every language must cover all of them. +// +// The script parses the TypeScript source with the compiler API and walks the +// AST — no runtime import, no transpilation, no temp files. Run it with: +// node frontend/scripts/check-translations.mjs + +import { readFileSync, readdirSync } from 'fs'; +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import ts from 'typescript'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const I18N_DIR = join(__dirname, '..', 'src', 'i18n'); +const LOCALES_DIR = join(I18N_DIR, 'locales'); + +const PLACEHOLDER_RE = /\{\{\s*[a-zA-Z_][\w]*\s*\}\}/g; +const HTML_TAG_RE = /<\/?[a-zA-Z][\w]*\b[^>]*>/g; + +const errors = []; +const warnings = []; +const fail = (msg) => errors.push(msg); +const warn = (msg) => warnings.push(msg); + +function parseFile(path) { + const src = readFileSync(path, 'utf8'); + return ts.createSourceFile(path, src, ts.ScriptTarget.Latest, true); +} + +// Recursively turn a TS literal expression into a plain JS value. +// Returns undefined for nodes we don't understand — callers must check. +function literalToJs(node) { + if (!node) return undefined; + if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text; + if (ts.isAsExpression(node) || ts.isParenthesizedExpression(node)) { + return literalToJs(node.expression); + } + if (ts.isObjectLiteralExpression(node)) { + const out = {}; + for (const prop of node.properties) { + if (!ts.isPropertyAssignment(prop)) continue; + const k = prop.name; + let key; + if (ts.isIdentifier(k)) key = k.text; + else if (ts.isStringLiteral(k) || ts.isNoSubstitutionTemplateLiteral(k)) key = k.text; + else continue; + out[key] = literalToJs(prop.initializer); + } + return out; + } + if (ts.isArrayLiteralExpression(node)) { + return node.elements.map((e) => literalToJs(e)); + } + return undefined; +} + +function findVarInitializer(sourceFile, name) { + let result; + function visit(node) { + if (ts.isVariableStatement(node)) { + for (const decl of node.declarationList.declarations) { + if (ts.isIdentifier(decl.name) && decl.name.text === name) { + result = decl.initializer; + } + } + } + ts.forEachChild(node, visit); + } + visit(sourceFile); + return result; +} + +function readSupportedLanguages() { + const sf = parseFile(join(I18N_DIR, 'index.ts')); + const init = findVarInitializer(sf, 'SUPPORTED_LANGUAGES'); + if (!init) throw new Error('Could not find SUPPORTED_LANGUAGES in index.ts'); + const arr = literalToJs(init); + if (!Array.isArray(arr)) throw new Error('SUPPORTED_LANGUAGES is not an array literal'); + return arr.map((entry) => entry.code); +} + +function readLocale(code) { + const path = join(LOCALES_DIR, `${code}.ts`); + const sf = parseFile(path); + const init = findVarInitializer(sf, code); + if (!init) throw new Error(`Could not find const ${code} in ${path}`); + const obj = literalToJs(init); + if (!obj || typeof obj !== 'object') throw new Error(`${code}.ts: not an object literal`); + return obj; +} + +function readNamedRecord(file, varName) { + const sf = parseFile(join(I18N_DIR, file)); + const init = findVarInitializer(sf, varName); + if (!init) throw new Error(`Could not find ${varName} in ${file}`); + const obj = literalToJs(init); + if (!obj || typeof obj !== 'object') throw new Error(`${file}: ${varName} is not an object`); + return obj; +} + +function flatten(obj, prefix = '', out = new Map()) { + for (const [k, v] of Object.entries(obj)) { + const path = prefix ? `${prefix}.${k}` : k; + if (v !== null && typeof v === 'object' && !Array.isArray(v)) { + flatten(v, path, out); + } else { + out.set(path, v); + } + } + return out; +} + +function tokenMultiset(s, re) { + const matches = String(s).match(re) || []; + // Normalise whitespace inside placeholders so '{{ count }}' == '{{count}}'. + return matches.map((t) => t.replace(/\s+/g, '')).sort(); +} + +function multisetsEqual(a, b) { + if (a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false; + return true; +} + +function checkLeafConsistency(path, enValue, trValue, lang) { + if (typeof trValue !== 'string') { + fail(`[${lang}] ${path}: missing translation`); + return; + } + if (trValue.trim() === '') { + fail(`[${lang}] ${path}: empty translation`); + return; + } + for (const [re, label] of [ + [PLACEHOLDER_RE, 'placeholder'], + [HTML_TAG_RE, 'HTML tag'], + ]) { + const want = tokenMultiset(enValue, re); + const got = tokenMultiset(trValue, re); + if (!multisetsEqual(want, got)) { + fail( + `[${lang}] ${path}: ${label} mismatch — en=${JSON.stringify(want)} ` + + `${lang}=${JSON.stringify(got)}` + ); + } + } +} + +function checkLocales(supportedCodes) { + const localeFiles = readdirSync(LOCALES_DIR) + .filter((f) => f.endsWith('.ts')) + .map((f) => f.replace(/\.ts$/, '')); + + for (const code of supportedCodes) { + if (!localeFiles.includes(code)) { + fail(`SUPPORTED_LANGUAGES lists "${code}" but locales/${code}.ts is missing`); + } + } + for (const code of localeFiles) { + if (!supportedCodes.includes(code)) { + warn(`locales/${code}.ts exists but is not listed in SUPPORTED_LANGUAGES`); + } + } + + const en = flatten(readLocale('en')); + for (const code of supportedCodes) { + if (code === 'en') continue; + if (!localeFiles.includes(code)) continue; + const tr = flatten(readLocale(code)); + for (const [path, enValue] of en) { + checkLeafConsistency(path, enValue, tr.get(path), code); + } + for (const path of tr.keys()) { + if (!en.has(path)) warn(`[${code}] ${path}: extra key not in en.ts`); + } + } +} + +function checkRecordCoverage(file, varName, supportedCodes, serverKeys) { + const record = readNamedRecord(file, varName); + const expected = supportedCodes.filter((c) => c !== 'en'); + const present = Object.keys(record); + + for (const code of expected) { + if (!present.includes(code)) { + fail(`${file}: missing language record "${code}"`); + } + } + for (const code of present) { + if (!expected.includes(code)) { + warn(`${file}: unexpected language record "${code}"`); + } + } + + // Use the union of feature-name keys across languages as canonical. + const union = new Set(); + for (const code of expected) { + if (record[code]) for (const k of Object.keys(record[code])) union.add(k); + } + + for (const code of expected) { + const langKeys = new Set(Object.keys(record[code] ?? {})); + for (const key of union) { + if (!langKeys.has(key)) { + fail(`${file} [${code}]: missing translation for "${key}"`); + } else { + const v = record[code][key]; + if (typeof v !== 'string' || v.trim() === '') { + fail(`${file} [${code}]: empty translation for "${key}"`); + } + } + } + } + + // Every key here must also be a translatable feature name in en.ts > server. + // Otherwise the description is unreachable — ts() looks up server.${name}. + for (const key of union) { + if (!serverKeys.has(key)) { + fail(`${file}: key "${key}" has no matching entry in en.ts > server`); + } + } +} + +function main() { + let supportedCodes; + try { + supportedCodes = readSupportedLanguages(); + } catch (e) { + console.error(`fatal: ${e.message}`); + process.exit(2); + } + + checkLocales(supportedCodes); + const en = readLocale('en'); + const serverKeys = new Set(Object.keys(en.server ?? {})); + checkRecordCoverage('descriptions.ts', 'descriptions', supportedCodes, serverKeys); + checkRecordCoverage('details.ts', 'details', supportedCodes, serverKeys); + + for (const w of warnings) console.warn(`warn: ${w}`); + if (errors.length > 0) { + for (const e of errors) console.error(`error: ${e}`); + console.error(`\n${errors.length} translation error(s).`); + process.exit(1); + } + console.log( + `i18n OK — ${supportedCodes.length} languages, ${warnings.length} warning(s).` + ); +} + +main(); diff --git a/frontend/scripts/prerender.mjs b/frontend/scripts/prerender.mjs index 098130c..e5bb063 100644 --- a/frontend/scripts/prerender.mjs +++ b/frontend/scripts/prerender.mjs @@ -1,10 +1,141 @@ import { createServer } from 'http'; -import { readFileSync, writeFileSync, existsSync, statSync } from 'fs'; -import { join, extname } from 'path'; +import { readFileSync, writeFileSync, existsSync, statSync, mkdirSync } from 'fs'; +import { join, extname, dirname } from 'path'; import { launch } from 'puppeteer'; const DIST_DIR = join(import.meta.dirname, '..', 'dist'); const INDEX_PATH = join(DIST_DIR, 'index.html'); +const PUBLIC_URL = 'https://perfect-postcode.co.uk'; +const OG_PLACEHOLDER = ''; + +const ROUTES = [ + { + path: '/', + output: 'index.html', + title: 'Perfect Postcode - Find where to buy before browsing listings', + description: + 'Search every postcode by budget, commute, schools, safety, noise, broadband, prices and more. Build a better home-buying shortlist before viewings.', + }, + { + path: '/learn', + output: 'learn/index.html', + title: 'How Perfect Postcode works - Data sources, FAQ and support', + description: + 'Learn how Perfect Postcode combines property prices, EPC records, travel times, crime, schools, broadband, noise, amenities and open data for postcode research.', + }, + { + path: '/pricing', + output: 'pricing/index.html', + title: 'Perfect Postcode pricing - Lifetime property search map access', + description: + 'Get lifetime access to the postcode property search map for England, including filters, saved searches, exports, and future data updates.', + }, + { + path: '/property-price-map', + output: 'property-price-map/index.html', + title: 'Property price map for England - Compare postcodes before viewing', + description: + 'Compare sold prices, estimated current value, price per square metre and local context across English postcodes before searching listings.', + }, + { + path: '/postcode-property-search', + output: 'postcode-property-search/index.html', + title: 'Postcode property search - Find areas that match your criteria', + description: + 'Search every postcode by budget, property type, floor area, tenure, commute, schools, crime, broadband, noise, parks and local amenities.', + }, + { + path: '/commute-property-search', + output: 'commute-property-search/index.html', + title: 'Commute property search - Find places to live by travel time', + description: + 'Filter postcodes by commute time, then compare price, schools, safety, broadband, road noise, parks and property data on one map.', + }, + { + path: '/school-property-search', + output: 'school-property-search/index.html', + title: 'School property search - Compare postcodes for family moves', + description: + 'Compare nearby schools, property size, prices, parks, safety, commute and local amenities before building a viewing shortlist.', + }, + { + path: '/postcode-checker', + output: 'postcode-checker/index.html', + title: 'Postcode checker - Property, crime, broadband, noise and schools', + description: + 'Check postcode-level property prices, EPC data, crime, broadband, road noise, schools, council tax, amenities and travel-time context.', + }, + { + path: '/property-search/birmingham', + output: 'property-search/birmingham/index.html', + title: 'Birmingham property search - Compare postcodes by price and commute', + description: + 'Use postcode-level data to compare Birmingham property prices, commute trade-offs, schools, crime, broadband and local amenities before viewings.', + }, + { + path: '/property-search/manchester', + output: 'property-search/manchester/index.html', + title: 'Manchester property search - Compare postcodes before viewing', + description: + 'Compare Manchester-area postcodes by budget, commute, property type, schools, broadband, crime, noise and amenities before booking viewings.', + }, + { + path: '/property-search/bristol', + output: 'property-search/bristol/index.html', + title: 'Bristol property search - Compare postcodes by commute and price', + description: + 'Compare Bristol postcodes by price, commute, property size, schools, broadband, crime, road noise, parks and amenities before viewings.', + }, + { + path: '/data-sources', + output: 'data-sources/index.html', + title: 'Perfect Postcode data sources - Property, schools, commute and local context', + description: + 'Review the public and official datasets used by Perfect Postcode, including property prices, EPC, schools, crime, broadband, noise and travel-time context.', + }, + { + path: '/methodology', + output: 'methodology/index.html', + title: 'Perfect Postcode methodology - How to interpret postcode property data', + description: + 'Understand how to use postcode filters, property estimates, travel-time data, school context and local signals as a home-buying shortlist tool.', + }, + { + path: '/privacy-security', + output: 'privacy-security/index.html', + title: 'Perfect Postcode privacy and security - Saved searches and account data', + description: + 'Learn how Perfect Postcode treats saved searches, account data and property research workflows with privacy and security in mind.', + }, +]; + +const FAQ_SCHEMA_ITEMS = [ + { + question: 'Where should I look once the obvious areas are too expensive?', + answer: + 'Set your budget, property type, floor area, commute, schools, crime, noise, broadband, parks, and other must-haves. The map removes postcodes that fail those tests, so overlooked areas can surface before you start searching listings.', + }, + { + question: 'What should I do when my search returns too many or too few areas?', + answer: + 'Start with hard limits, then colour the map by a trade-off such as price per sqm, road noise, school score, or commute time. If the map gets too narrow, relax one slider and you can see exactly which compromise opens up more options.', + }, + { + question: 'How are the travel times calculated?', + answer: + 'Travel times are precomputed with Conveyal R5, a routing engine used for transport analysis. For each supported destination we route to reachable postcodes over the street and transit network, then store sparse postcode travel-time files for car, cycling, walking, and public transport.', + }, + { + question: 'How does the estimated current price algorithm work?', + answer: + 'The estimate starts with the last HM Land Registry sale price, adjusts it to current-market terms using repeat-sales modelling and fallback models, then blends that result with a nearest-neighbour estimate from nearby, recently sold, same-type homes.', + }, + { + question: 'Should I use this before or after checking Rightmove?', + answer: + 'Use Perfect Postcode before and alongside listing portals. Rightmove, Zoopla, and OnTheMarket are still where you check live availability, photos, agent contact, viewings, and alerts.', + }, +]; const MIME_TYPES = { '.html': 'text/html', @@ -13,9 +144,151 @@ const MIME_TYPES = { '.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg', + '.mp4': 'video/mp4', + '.webm': 'video/webm', '.svg': 'image/svg+xml', + '.ico': 'image/x-icon', }; +function escapeAttr(value) { + return value + .replaceAll('&', '&') + .replaceAll('"', '"') + .replaceAll('<', '<') + .replaceAll('>', '>'); +} + +function routeUrl(pathname) { + return `${PUBLIC_URL}${pathname === '/' ? '/' : pathname}`; +} + +function jsonLd(data) { + return ``; +} + +function structuredDataForRoute(route) { + const url = routeUrl(route.path); + const base = [ + { + '@context': 'https://schema.org', + '@type': 'WebPage', + '@id': `${url}#webpage`, + url, + name: route.title, + description: route.description, + isPartOf: { '@id': `${PUBLIC_URL}/#website` }, + }, + { + '@context': 'https://schema.org', + '@type': 'BreadcrumbList', + itemListElement: [ + { + '@type': 'ListItem', + position: 1, + name: 'Perfect Postcode', + item: `${PUBLIC_URL}/`, + }, + ...(route.path === '/' + ? [] + : [ + { + '@type': 'ListItem', + position: 2, + name: route.title.split(' - ')[0], + item: url, + }, + ]), + ], + }, + ]; + + if (route.path === '/') { + base.push( + { + '@context': 'https://schema.org', + '@type': 'Organization', + '@id': `${PUBLIC_URL}/#organization`, + name: 'Perfect Postcode', + url: `${PUBLIC_URL}/`, + logo: `${PUBLIC_URL}/favicon.svg`, + }, + { + '@context': 'https://schema.org', + '@type': 'WebSite', + '@id': `${PUBLIC_URL}/#website`, + name: 'Perfect Postcode', + url: `${PUBLIC_URL}/`, + publisher: { '@id': `${PUBLIC_URL}/#organization` }, + }, + { + '@context': 'https://schema.org', + '@type': 'SoftwareApplication', + name: 'Perfect Postcode', + applicationCategory: 'BusinessApplication', + operatingSystem: 'Web', + url: `${PUBLIC_URL}/`, + description: route.description, + } + ); + } + + if (route.path === '/learn') { + base.push({ + '@context': 'https://schema.org', + '@type': 'FAQPage', + mainEntity: FAQ_SCHEMA_ITEMS.map((item) => ({ + '@type': 'Question', + name: item.question, + acceptedAnswer: { + '@type': 'Answer', + text: item.answer, + }, + })), + }); + } + + if (route.path === '/pricing') { + base.push({ + '@context': 'https://schema.org', + '@type': 'Product', + name: 'Perfect Postcode lifetime access', + description: route.description, + brand: { '@type': 'Brand', name: 'Perfect Postcode' }, + }); + } + + return base; +} + +function updateHead(indexHtml, route) { + const structuredData = structuredDataForRoute(route).map(jsonLd).join('\n '); + return indexHtml + .replace(/.*?<\/title>/, `<title>${escapeAttr(route.title)}`) + .replace( + //, + `` + ) + .replace(OG_PLACEHOLDER, `${OG_PLACEHOLDER}\n ${structuredData}`); +} + +function cleanBaseIndexHtml(indexHtml) { + const withoutStructuredData = indexHtml.replace( + / diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 7ba9ccf..acdfe12 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -1,6 +1,6 @@ -import { createRoot, hydrateRoot } from 'react-dom/client'; +import { createRoot } from 'react-dom/client'; import App from './App'; -import './i18n'; +import { i18nReady } from './i18n'; import './index.css'; import './hooks/usePlausible'; @@ -8,9 +8,17 @@ const container = document.getElementById('root'); if (!container) { throw new Error('Root element not found'); } +const root = container; -if (container.children.length > 0) { - hydrateRoot(container, ); -} else { - createRoot(container).render(); +function renderApp() { + const hasPrerenderedMarkup = root.children.length > 0; + + if (hasPrerenderedMarkup) { + root.textContent = ''; + } + root.removeAttribute('data-prerender-path'); + + createRoot(root).render(); } + +void i18nReady.then(renderApp); diff --git a/frontend/src/lib/PieHexExtension.ts b/frontend/src/lib/PieHexExtension.ts index f077260..c9cfa08 100644 --- a/frontend/src/lib/PieHexExtension.ts +++ b/frontend/src/lib/PieHexExtension.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { LayerExtension } from '@deck.gl/core'; -import { ENUM_PALETTE } from './consts'; /** * LayerExtension that turns polygon fills into pie charts. @@ -12,7 +11,7 @@ import { ENUM_PALETTE } from './consts'; * - stepMode:'dynamic' handles per-instance counting automatically. * - isEnabled() restricts to SolidPolygonLayer (fill) sublayers only. * - * Accepts an optional custom palette in the constructor for per-feature color overrides. + * Accepts the configured enum palette in the constructor. */ function paletteToGlsl(palette: [number, number, number][]): string { @@ -35,9 +34,9 @@ export class PieHexExtension extends LayerExtension { private paletteGlsl: string; - constructor(palette?: [number, number, number][]) { + constructor(palette: [number, number, number][]) { super(); - this.paletteGlsl = paletteToGlsl(palette ?? ENUM_PALETTE); + this.paletteGlsl = paletteToGlsl(palette); } isEnabled(layer: any): boolean { diff --git a/frontend/src/lib/api.test.ts b/frontend/src/lib/api.test.ts index 3fc4809..59fb00a 100644 --- a/frontend/src/lib/api.test.ts +++ b/frontend/src/lib/api.test.ts @@ -3,6 +3,13 @@ import { describe, expect, it } from 'vitest'; import type { FeatureMeta } from '../types'; import { apiUrl, assertOk, buildFilterString, isAbortError } from './api'; import { createSchoolFilterKey } from './school-filter'; +import { createSpecificCrimeFilterKey } from './crime-filter'; +import { createEthnicityFilterKey } from './ethnicity-filter'; +import { + POI_COUNT_2KM_FILTER_NAME, + createPoiDistanceFilterKey, + createPoiFilterKey, +} from './poi-distance-filter'; describe('api utilities', () => { it('builds API URLs from endpoint names, paths, and params', () => { @@ -81,4 +88,69 @@ describe('api utilities', () => { ) ).toBe('Good+ primary schools within 2km:2:8'); }); + + it('serializes specific crime filters using their selected backend crime feature', () => { + const features: FeatureMeta[] = [ + { name: 'Burglary (avg/yr)', type: 'numeric', min: 0, max: 20 }, + { name: 'Vehicle crime (avg/yr)', type: 'numeric', min: 0, max: 30 }, + ]; + + expect( + buildFilterString( + { + [createSpecificCrimeFilterKey('Burglary (avg/yr)', 1)]: [0, 5], + [createSpecificCrimeFilterKey('Vehicle crime (avg/yr)', 2)]: [1, 10], + }, + features + ) + ).toBe('Burglary (avg/yr):0:5;;Vehicle crime (avg/yr):1:10'); + }); + + it('deduplicates repeated ethnicity filters to the strictest backend range', () => { + const features: FeatureMeta[] = [{ name: '% White', type: 'numeric', min: 0, max: 100 }]; + + expect( + buildFilterString( + { + [createEthnicityFilterKey('% White', 1)]: [10, 90], + [createEthnicityFilterKey('% White', 2)]: [20, 80], + }, + features + ) + ).toBe('% White:20:80'); + }); + + it('serializes POI distance filters using their selected backend feature', () => { + const features: FeatureMeta[] = [ + { name: 'Distance to nearest park (km)', type: 'numeric', min: 0, max: 2 }, + { name: 'Distance to nearest Tesco (km)', type: 'numeric', min: 0, max: 5 }, + ]; + + expect( + buildFilterString( + { + [createPoiDistanceFilterKey('Distance to nearest park (km)', 1)]: [0, 0.5], + [createPoiDistanceFilterKey('Distance to nearest Tesco (km)', 2)]: [0, 1], + }, + features + ) + ).toBe('Distance to nearest park (km):0:0.5;;Distance to nearest Tesco (km):0:1'); + }); + + it('serializes POI count filters using their selected backend feature', () => { + const features: FeatureMeta[] = [ + { name: 'Number of Cafe POIs within 2km', type: 'numeric', min: 0, max: 20 }, + ]; + + expect( + buildFilterString( + { + [createPoiFilterKey(POI_COUNT_2KM_FILTER_NAME, 'Number of Cafe POIs within 2km', 1)]: [ + 2, 10, + ], + }, + features + ) + ).toBe('Number of Cafe POIs within 2km:2:10'); + }); }); diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 651ad00..6f3a0a7 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -2,6 +2,9 @@ import type { FeatureMeta, FeatureFilters } from '../types'; import { INITIAL_RETRY_MS, MAX_RETRY_MS } from './consts'; import pb from './pocketbase'; import { getSchoolBackendFeatureName } from './school-filter'; +import { getSpecificCrimeFeatureName } from './crime-filter'; +import { getEthnicityFeatureName } from './ethnicity-filter'; +import { getPoiDistanceFeatureName } from './poi-distance-filter'; export function logNonAbortError(label: string, error: unknown): void { if (error instanceof Error && error.name === 'AbortError') { @@ -87,7 +90,12 @@ export function buildFilterString( const merged = new Map(); for (const [name, value] of entries) { if (name === exclude) continue; - const backendName = getSchoolBackendFeatureName(name) ?? name; + const backendName = + getSchoolBackendFeatureName(name) ?? + getSpecificCrimeFeatureName(name) ?? + getEthnicityFeatureName(name) ?? + getPoiDistanceFeatureName(name) ?? + name; const prev = merged.get(backendName); if ( prev && diff --git a/frontend/src/lib/consts.ts b/frontend/src/lib/consts.ts index c6bc59d..df38613 100644 --- a/frontend/src/lib/consts.ts +++ b/frontend/src/lib/consts.ts @@ -35,6 +35,10 @@ export const ZOOM_TO_RESOLUTION_THRESHOLDS = [ { maxZoom: 13, resolution: 9 }, ] as const; +export const SMALLEST_VISIBLE_HEXAGON_RESOLUTION = Math.max( + ...ZOOM_TO_RESOLUTION_THRESHOLDS.map(({ resolution }) => resolution) +); + export const POSTCODE_ZOOM_THRESHOLD = 15; export const FEATURE_GRADIENT: { t: number; color: [number, number, number] }[] = [ @@ -68,7 +72,7 @@ export const PARTY_FEATURE_GRADIENTS: Record = { '% Liberal Democrat': partyGradient([255, 100, 0]), // Liberal Democrat orange '% Reform UK': partyGradient([18, 182, 207]), // Reform UK cyan '% Green': partyGradient([106, 176, 35]), // Green Party green - '% Other parties': partyGradient([107, 114, 128]), // neutral fallback for grouped parties + '% Other parties': partyGradient([107, 114, 128]), // neutral color for grouped parties }; export const PARTY_FEATURE_COLORS: Record = Object.fromEntries( @@ -123,48 +127,62 @@ export const POI_GROUP_COLORS: Record = { Shops: [99, 102, 241], }; -/** Default color for unknown POI groups */ -export const POI_DEFAULT_COLOR: [number, number, number] = [107, 114, 128]; - /** POI category → icon/logo URL for branded and transport categories */ export const POI_CATEGORY_LOGOS: Record = { Airport: '/assets/twemoji/2708.png', - Aldi: 'https://geolytix.github.io/MapIcons/brands/aldi_24px.svg', - Amazon: 'https://geolytix.github.io/MapIcons/brands/amazon_fresh_alt_24px.svg', - Asda: 'https://geolytix.github.io/MapIcons/asda/asda_primary.svg', + Aldi: '/assets/poi-icons/logos/aldi.svg', + Amazon: '/assets/poi-icons/brands_2024/amazon_fresh.svg', + Asda: '/assets/poi-icons/logos/asda.svg', + 'Asda Express': '/assets/poi-icons/logos/asda.svg', + 'Asda Living': '/assets/poi-icons/logos/asda.svg', + 'Asda PFS': '/assets/poi-icons/logos/asda.svg', + 'Asda Supercentre': '/assets/poi-icons/logos/asda.svg', + 'Asda Supermarket': '/assets/poi-icons/logos/asda.svg', + 'Asda Superstore': '/assets/poi-icons/logos/asda.svg', Bakery: '/assets/twemoji/1f950.png', - Booths: 'https://geolytix.github.io/MapIcons/brands/booths_24px.svg', - Budgens: 'https://geolytix.github.io/MapIcons/brands/budgens_24px.svg', + Booths: '/assets/poi-icons/brands_2024/booths.svg', + Budgens: '/assets/poi-icons/brands_2024/budgens.svg', 'Bus station': '/assets/twemoji/1f68c.png', 'Bus stop': '/assets/twemoji/1f68f.png', 'Butcher & Fishmonger': '/assets/twemoji/1f969.png', - Centra: 'https://geolytix.github.io/MapIcons/brands/centra_24px.svg', - 'Co-op': 'https://geolytix.github.io/MapIcons/brands/coop_24px.svg', - COOK: 'https://geolytix.github.io/MapIcons/brands/cook.svg', + Centra: '/assets/poi-icons/logos/centra.svg', + 'Co-op': '/assets/poi-icons/logos/coop.svg', + COOK: '/assets/poi-icons/brands_2024/cook.svg', 'Convenience Store': '/assets/twemoji/1f3ea.png', - Costco: 'https://geolytix.github.io/MapIcons/brands/costco_24px.svg', + Costco: '/assets/poi-icons/logos/costco.svg', 'Deli & Specialty': '/assets/twemoji/1f9c6.png', - 'Dunnes Stores': 'https://geolytix.github.io/MapIcons/brands/dunnes_stores_24px.svg', - Farmfoods: 'https://geolytix.github.io/MapIcons/brands/farmfoods_updated_24px.svg', + 'Dunnes Stores': '/assets/poi-icons/brands_2024/dunnes_stores.svg', + Farmfoods: '/assets/poi-icons/brands_2023/supermarkets/farmfoods.svg', Ferry: '/assets/twemoji/26f4.png', Greengrocer: '/assets/twemoji/1f96c.png', - 'Heron Foods': 'https://geolytix.github.io/MapIcons/brands/heron_24px.svg', - Iceland: 'https://geolytix.github.io/MapIcons/brands/iceland_24px.svg', - Lidl: 'https://geolytix.github.io/MapIcons/brands/lidl_24px.svg', - Makro: 'https://geolytix.github.io/MapIcons/brands/makro_24px.svg', - 'M&S': 'https://geolytix.github.io/MapIcons/brands/mns_24px.svg', - Morrisons: 'https://geolytix.github.io/MapIcons/brands/morrisons_24px.svg', + 'Heron Foods': '/assets/poi-icons/brands_2023/supermarkets/heron_foods.svg', + Iceland: '/assets/poi-icons/brands_2024/iceland.svg', + Lidl: '/assets/poi-icons/logos/lidl.svg', + Makro: '/assets/poi-icons/brands_2024/makro.svg', + 'M&S': '/assets/poi-icons/brands_2024/mns.svg', + 'M&S Clothing': '/assets/poi-icons/brands_2024/mns.svg', + 'M&S Food': '/assets/poi-icons/visuals/mns.svg', + 'M&S Hospital': '/assets/poi-icons/brands_2024/mns.svg', + 'M&S MSA': '/assets/poi-icons/brands_2024/mns.svg', + 'M&S Outlet': '/assets/poi-icons/brands_2024/mns.svg', + Morrisons: '/assets/poi-icons/logos/morrisons.svg', + 'Morrisons Daily': '/assets/poi-icons/brands_2024/morrisons_daily.svg', 'Off-Licence': '/assets/twemoji/1f377.png', - 'Planet Organic': 'https://geolytix.github.io/MapIcons/logos/planet_organic_24px.svg', + 'Planet Organic': '/assets/poi-icons/logos/planet_organic.svg', 'Rail station': '/assets/twemoji/1f686.png', - "Sainsbury's": 'https://geolytix.github.io/MapIcons/brands/sainsburys_24px.svg', - Spar: 'https://geolytix.github.io/MapIcons/brands/spar_24px.svg', + "Sainsbury's": '/assets/poi-icons/logos/sainsburys.svg', + "Sainsbury's Local": '/assets/poi-icons/brands_2024/sainsburys_local.svg', + Spar: '/assets/poi-icons/logos/spar.svg', Supermarket: '/assets/twemoji/1f6d2.png', - Tesco: 'https://geolytix.github.io/MapIcons/brands/tesco_24px.svg', + Tesco: '/assets/poi-icons/logos/tesco.svg', + 'Tesco Express': '/assets/poi-icons/logos/tesco_express.svg', + 'Tesco Extra': '/assets/poi-icons/logos/tesco_extra.svg', 'Taxi rank': '/assets/twemoji/1f695.png', - 'Tube station': 'https://geolytix.github.io/MapIcons/public_transport/london_tube.svg', - Waitrose: 'https://geolytix.github.io/MapIcons/brands/waitrose_24px.svg', - 'Whole Foods Market': 'https://geolytix.github.io/MapIcons/brands/wholefoods_24px.svg', + 'The Food Warehouse': '/assets/poi-icons/logos/the_food_warehouse.png', + 'Tube station': '/assets/poi-icons/public_transport/london_tube.svg', + Waitrose: '/assets/poi-icons/logos/waitrose.svg', + 'Little Waitrose': '/assets/poi-icons/brands_2023/supermarkets/little_waitrose.svg', + 'Whole Foods Market': '/assets/poi-icons/brands_2024/wholefoods.svg', }; /** Categories only shown when zoomed in past MINOR_POI_ZOOM_THRESHOLD */ @@ -308,9 +326,8 @@ export const ENUM_PALETTE: [number, number, number][] = [ ]; /** - * Per-feature color overrides for enum values on the map and dashboard. + * Per-feature color definitions for enum values on the map and dashboard. * Keys are feature names (as returned by the server), values map enum value → RGB. - * Any value not listed falls back to ENUM_PALETTE by index. */ export const ENUM_COLOR_OVERRIDES: Record> = { 'Property type': { @@ -320,49 +337,105 @@ export const ENUM_COLOR_OVERRIDES: Record = { + 'Violence and sexual offences (avg/yr)': '#ef4444', + 'Robbery (avg/yr)': '#f97316', + 'Burglary (avg/yr)': '#eab308', + 'Possession of weapons (avg/yr)': '#8b5cf6', + 'Anti-social behaviour (avg/yr)': '#14b8a6', + 'Criminal damage and arson (avg/yr)': '#f97316', + 'Shoplifting (avg/yr)': '#ec4899', + 'Bicycle theft (avg/yr)': '#22c55e', + 'Theft from the person (avg/yr)': '#d946ef', + 'Other theft (avg/yr)': '#06b6d4', + 'Vehicle crime (avg/yr)': '#3b82f6', + 'Public order (avg/yr)': '#8b5cf6', + 'Drugs (avg/yr)': '#22c55e', + 'Other crime (avg/yr)': '#6b7280', + '% White': '#3b82f6', + '% South Asian': '#f97316', + '% East Asian': '#eab308', + '% Black': '#8b5cf6', + '% Mixed': '#14b8a6', + '% Other': '#6b7280', + 'Anti-social': '#14b8a6', + Vehicle: '#3b82f6', + Burglary: '#eab308', + Other: '#6b7280', +}; diff --git a/frontend/src/lib/crime-filter.ts b/frontend/src/lib/crime-filter.ts new file mode 100644 index 0000000..76fca4f --- /dev/null +++ b/frontend/src/lib/crime-filter.ts @@ -0,0 +1,116 @@ +import type { FeatureFilters, FeatureMeta } from '../types'; + +export const SPECIFIC_CRIMES_FILTER_NAME = 'Specific crimes'; +export const SPECIFIC_CRIMES_FILTER_KEY_PREFIX = `${SPECIFIC_CRIMES_FILTER_NAME}:`; + +export const SPECIFIC_CRIME_FEATURE_NAMES = [ + 'Violence and sexual offences (avg/yr)', + 'Burglary (avg/yr)', + 'Robbery (avg/yr)', + 'Vehicle crime (avg/yr)', + 'Anti-social behaviour (avg/yr)', + 'Criminal damage and arson (avg/yr)', + 'Other theft (avg/yr)', + 'Theft from the person (avg/yr)', + 'Shoplifting (avg/yr)', + 'Bicycle theft (avg/yr)', + 'Drugs (avg/yr)', + 'Possession of weapons (avg/yr)', + 'Public order (avg/yr)', + 'Other crime (avg/yr)', +] as const; + +const SPECIFIC_CRIME_FEATURE_NAME_SET = new Set(SPECIFIC_CRIME_FEATURE_NAMES); + +export function isSpecificCrimeFeatureName(name: string): boolean { + return SPECIFIC_CRIME_FEATURE_NAME_SET.has(name); +} + +export function isSpecificCrimeFilterName(name: string): boolean { + return isSpecificCrimeFeatureName(name) || name.startsWith(SPECIFIC_CRIMES_FILTER_KEY_PREFIX); +} + +export function createSpecificCrimeFilterKey(featureName: string, id: number | string): string { + return `${SPECIFIC_CRIMES_FILTER_KEY_PREFIX}${encodeURIComponent(featureName)}:${id}`; +} + +export function getSpecificCrimeFilterKeyId(name: string): string | null { + if (!name.startsWith(SPECIFIC_CRIMES_FILTER_KEY_PREFIX)) return null; + const rest = name.substring(SPECIFIC_CRIMES_FILTER_KEY_PREFIX.length); + const lastColon = rest.lastIndexOf(':'); + return lastColon === -1 ? null : rest.substring(lastColon + 1); +} + +export function parseSpecificCrimeFilterKey(name: string): string | null { + if (!name.startsWith(SPECIFIC_CRIMES_FILTER_KEY_PREFIX)) return null; + const rest = name.substring(SPECIFIC_CRIMES_FILTER_KEY_PREFIX.length); + const lastColon = rest.lastIndexOf(':'); + if (lastColon === -1) return null; + + const decoded = decodeURIComponent(rest.substring(0, lastColon)); + return isSpecificCrimeFeatureName(decoded) ? decoded : null; +} + +export function getSpecificCrimeFeatureName(name: string): string | null { + if (isSpecificCrimeFeatureName(name)) return name; + return parseSpecificCrimeFilterKey(name); +} + +export function replaceSpecificCrimeFilterKeySelection(key: string, featureName: string): string { + const id = getSpecificCrimeFilterKeyId(key) ?? '0'; + return createSpecificCrimeFilterKey(featureName, id); +} + +export function getDefaultSpecificCrimeFeatureName(features: FeatureMeta[]): string | null { + return ( + SPECIFIC_CRIME_FEATURE_NAMES.find((name) => + features.some((feature) => feature.name === name) + ) ?? null + ); +} + +export function normalizeSpecificCrimeFilters(filters: FeatureFilters): FeatureFilters { + let changed = false; + const next: FeatureFilters = {}; + + for (const [name, value] of Object.entries(filters)) { + if (isSpecificCrimeFeatureName(name)) { + next[createSpecificCrimeFilterKey(name, Object.keys(next).length)] = value; + changed = true; + continue; + } + next[name] = value; + } + + return changed ? next : filters; +} + +export function getSpecificCrimeFilterMeta(features: FeatureMeta[]): FeatureMeta { + const sourceFeatureName = getDefaultSpecificCrimeFeatureName(features); + const sourceFeature = sourceFeatureName + ? features.find((feature) => feature.name === sourceFeatureName) + : undefined; + + return { + name: SPECIFIC_CRIMES_FILTER_NAME, + type: 'numeric', + group: 'Crime', + min: sourceFeature?.min ?? 0, + max: sourceFeature?.max ?? 100, + step: 1, + description: + 'Violence, burglary, robbery, drugs, shoplifting, vehicle crime, anti-social behaviour, public order, theft, and other crime types', + detail: 'Filter by one street-level crime category at a time using yearly averages per LSOA.', + source: 'crime', + suffix: '/yr', + }; +} + +export function clampSpecificCrimeRange( + value: [number, number], + feature?: FeatureMeta +): [number, number] { + const min = feature?.histogram?.min ?? feature?.min ?? 0; + const max = feature?.histogram?.max ?? feature?.max ?? Math.max(1, value[1]); + return [Math.max(min, Math.min(value[0], max)), Math.max(min, Math.min(value[1], max))]; +} diff --git a/frontend/src/lib/dom-scroll.test.ts b/frontend/src/lib/dom-scroll.test.ts new file mode 100644 index 0000000..86e43d2 --- /dev/null +++ b/frontend/src/lib/dom-scroll.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it } from 'vitest'; +import { canWheelScrollInsideTarget } from './dom-scroll'; + +function setElementMetrics( + element: HTMLElement, + metrics: Partial<{ + clientHeight: number; + clientWidth: number; + scrollHeight: number; + scrollWidth: number; + }> +) { + for (const [key, value] of Object.entries(metrics)) { + Object.defineProperty(element, key, { configurable: true, value }); + } +} + +describe('canWheelScrollInsideTarget', () => { + it('allows horizontal wheel gestures inside a horizontal scroller', () => { + const scroller = document.createElement('div'); + scroller.style.overflowX = 'auto'; + scroller.scrollLeft = 20; + setElementMetrics(scroller, { clientWidth: 100, scrollWidth: 240 }); + + const child = document.createElement('button'); + scroller.appendChild(child); + document.body.appendChild(scroller); + + expect(canWheelScrollInsideTarget(child, 40, 0)).toBe(true); + scroller.remove(); + }); + + it('allows vertical wheel gestures inside a vertical scroller', () => { + const scroller = document.createElement('div'); + scroller.style.overflowY = 'auto'; + scroller.scrollTop = 20; + setElementMetrics(scroller, { clientHeight: 100, scrollHeight: 240 }); + + const child = document.createElement('button'); + scroller.appendChild(child); + document.body.appendChild(scroller); + + expect(canWheelScrollInsideTarget(child, 60, 20)).toBe(true); + scroller.remove(); + }); + + it('does not allow horizontal gestures at the edge of a scroller', () => { + const scroller = document.createElement('div'); + scroller.style.overflowX = 'auto'; + scroller.scrollLeft = 0; + setElementMetrics(scroller, { clientWidth: 100, scrollWidth: 240 }); + + document.body.appendChild(scroller); + + expect(canWheelScrollInsideTarget(scroller, -40, 0)).toBe(false); + scroller.remove(); + }); +}); diff --git a/frontend/src/lib/dom-scroll.ts b/frontend/src/lib/dom-scroll.ts new file mode 100644 index 0000000..286e0f7 --- /dev/null +++ b/frontend/src/lib/dom-scroll.ts @@ -0,0 +1,45 @@ +const SCROLLABLE_OVERFLOW = new Set(['auto', 'scroll', 'overlay']); + +function canScrollHorizontally(element: HTMLElement, deltaX: number): boolean { + if (deltaX === 0) return false; + + const style = window.getComputedStyle(element); + if (!SCROLLABLE_OVERFLOW.has(style.overflowX)) return false; + if (element.scrollWidth <= element.clientWidth) return false; + + const maxScrollLeft = element.scrollWidth - element.clientWidth; + if (deltaX < 0) return element.scrollLeft > 0; + return element.scrollLeft < maxScrollLeft - 1; +} + +function canScrollVertically(element: HTMLElement, deltaY: number): boolean { + if (deltaY === 0) return false; + + const style = window.getComputedStyle(element); + if (!SCROLLABLE_OVERFLOW.has(style.overflowY)) return false; + if (element.scrollHeight <= element.clientHeight) return false; + + const maxScrollTop = element.scrollHeight - element.clientHeight; + if (deltaY < 0) return element.scrollTop > 0; + return element.scrollTop < maxScrollTop - 1; +} + +export function canWheelScrollInsideTarget( + target: EventTarget | null, + deltaX: number, + deltaY: number +): boolean { + let element = target instanceof Element ? target : null; + + while (element && element !== document.body && element !== document.documentElement) { + if ( + element instanceof HTMLElement && + (canScrollHorizontally(element, deltaX) || canScrollVertically(element, deltaY)) + ) { + return true; + } + element = element.parentElement; + } + + return false; +} diff --git a/frontend/src/lib/ethnicity-filter.ts b/frontend/src/lib/ethnicity-filter.ts new file mode 100644 index 0000000..5352e3d --- /dev/null +++ b/frontend/src/lib/ethnicity-filter.ts @@ -0,0 +1,106 @@ +import type { FeatureFilters, FeatureMeta } from '../types'; + +export const ETHNICITIES_FILTER_NAME = 'Ethnicities'; +export const ETHNICITIES_FILTER_KEY_PREFIX = `${ETHNICITIES_FILTER_NAME}:`; + +export const ETHNICITY_FEATURE_NAMES = [ + '% White', + '% South Asian', + '% East Asian', + '% Black', + '% Mixed', + '% Other', +] as const; + +const ETHNICITY_FEATURE_NAME_SET = new Set(ETHNICITY_FEATURE_NAMES); + +export function isEthnicityFeatureName(name: string): boolean { + return ETHNICITY_FEATURE_NAME_SET.has(name); +} + +export function isEthnicityFilterName(name: string): boolean { + return isEthnicityFeatureName(name) || name.startsWith(ETHNICITIES_FILTER_KEY_PREFIX); +} + +export function createEthnicityFilterKey(featureName: string, id: number | string): string { + return `${ETHNICITIES_FILTER_KEY_PREFIX}${encodeURIComponent(featureName)}:${id}`; +} + +export function getEthnicityFilterKeyId(name: string): string | null { + if (!name.startsWith(ETHNICITIES_FILTER_KEY_PREFIX)) return null; + const rest = name.substring(ETHNICITIES_FILTER_KEY_PREFIX.length); + const lastColon = rest.lastIndexOf(':'); + return lastColon === -1 ? null : rest.substring(lastColon + 1); +} + +export function parseEthnicityFilterKey(name: string): string | null { + if (!name.startsWith(ETHNICITIES_FILTER_KEY_PREFIX)) return null; + const rest = name.substring(ETHNICITIES_FILTER_KEY_PREFIX.length); + const lastColon = rest.lastIndexOf(':'); + if (lastColon === -1) return null; + + const decoded = decodeURIComponent(rest.substring(0, lastColon)); + return isEthnicityFeatureName(decoded) ? decoded : null; +} + +export function getEthnicityFeatureName(name: string): string | null { + if (isEthnicityFeatureName(name)) return name; + return parseEthnicityFilterKey(name); +} + +export function replaceEthnicityFilterKeySelection(key: string, featureName: string): string { + const id = getEthnicityFilterKeyId(key) ?? '0'; + return createEthnicityFilterKey(featureName, id); +} + +export function getDefaultEthnicityFeatureName(features: FeatureMeta[]): string | null { + return ( + ETHNICITY_FEATURE_NAMES.find((name) => features.some((feature) => feature.name === name)) ?? + null + ); +} + +export function normalizeEthnicityFilters(filters: FeatureFilters): FeatureFilters { + let changed = false; + const next: FeatureFilters = {}; + + for (const [name, value] of Object.entries(filters)) { + if (isEthnicityFeatureName(name)) { + next[createEthnicityFilterKey(name, Object.keys(next).length)] = value; + changed = true; + continue; + } + next[name] = value; + } + + return changed ? next : filters; +} + +export function getEthnicityFilterMeta(features: FeatureMeta[]): FeatureMeta { + const sourceFeatureName = getDefaultEthnicityFeatureName(features); + const sourceFeature = sourceFeatureName + ? features.find((feature) => feature.name === sourceFeatureName) + : undefined; + + return { + name: ETHNICITIES_FILTER_NAME, + type: 'numeric', + group: 'Demographics', + min: sourceFeature?.min ?? 0, + max: sourceFeature?.max ?? 100, + step: 0.1, + description: 'Population percentage by ethnic group', + detail: 'Filter by one Census 2021 ethnicity percentage at a time.', + source: 'ethnicity', + suffix: '%', + }; +} + +export function clampEthnicityRange( + value: [number, number], + feature?: FeatureMeta +): [number, number] { + const min = feature?.histogram?.min ?? feature?.min ?? 0; + const max = feature?.histogram?.max ?? feature?.max ?? Math.max(1, value[1]); + return [Math.max(min, Math.min(value[0], max)), Math.max(min, Math.min(value[1], max))]; +} diff --git a/frontend/src/lib/feature-icons.tsx b/frontend/src/lib/feature-icons.tsx index da249bd..eaea677 100644 --- a/frontend/src/lib/feature-icons.tsx +++ b/frontend/src/lib/feature-icons.tsx @@ -19,6 +19,12 @@ const FEATURE_ICON_PATHS: Record = { ), + 'Estimated price': ( + <> + + + + ), 'Price per sqm': ( <> @@ -109,6 +115,18 @@ const FEATURE_ICON_PATHS: Record = { ), + 'Travel time to nearest train or tube station (min)': ( + <> + + + + + + + + + + ), // ── Education ──────────────────────────────── 'Education, Skills and Training Score': ( @@ -170,14 +188,14 @@ const FEATURE_ICON_PATHS: Record = { ), // ── Deprivation ────────────────────────────── - 'Income Score (rate)': ( + 'Income Score': ( <> ), - 'Employment Score (rate)': ( + 'Employment Score': ( <> @@ -189,19 +207,13 @@ const FEATURE_ICON_PATHS: Record = { ), - 'Living Environment Score': ( - <> - - - - ), - 'Indoors Sub-domain Score': ( + 'Housing Conditions Score': ( <> ), - 'Outdoors Sub-domain Score': ( + 'Air Quality and Road Safety Score': ( <> diff --git a/frontend/src/lib/features.ts b/frontend/src/lib/features.ts index 11c0020..2850b66 100644 --- a/frontend/src/lib/features.ts +++ b/frontend/src/lib/features.ts @@ -5,7 +5,10 @@ export function groupFeaturesByCategory(features: FeatureMeta[]): FeatureGroup[] const seen = new Map(); for (const feature of features) { - const groupName = feature.group || 'Other'; + if (!feature.group) { + throw new Error(`Feature '${feature.name}' is missing its group`); + } + const groupName = feature.group; let arr = seen.get(groupName); if (!arr) { arr = []; diff --git a/frontend/src/lib/format.test.ts b/frontend/src/lib/format.test.ts index 65be1f6..cbc0ce9 100644 --- a/frontend/src/lib/format.test.ts +++ b/frontend/src/lib/format.test.ts @@ -14,6 +14,7 @@ describe('format utilities', () => { expect(formatFilterValue(1250)).toBe('1.3k'); expect(formatFilterValue(1_250_000)).toBe('1.3M'); expect(formatFilterValue(1250, true)).toBe('1250'); + expect(formatFilterValue(87.4, { raw: true, suffix: '%' })).toBe('87%'); expect(formatTransactionDate(2024.5)).toBe('Jul 2024'); }); diff --git a/frontend/src/lib/format.ts b/frontend/src/lib/format.ts index 27a2496..ebce87f 100644 --- a/frontend/src/lib/format.ts +++ b/frontend/src/lib/format.ts @@ -38,18 +38,21 @@ export function formatValue(value: number, fmt?: ValueFormat): string { return `${p}${value.toFixed(1)}${s}`; } -export function formatFilterValue(value: number, raw?: boolean): string { - if (raw) return Math.round(value).toString(); +export function formatFilterValue(value: number, rawOrFmt?: boolean | ValueFormat): string { + const fmt = typeof rawOrFmt === 'object' ? rawOrFmt : { raw: rawOrFmt }; + const p = fmt?.prefix ?? ''; + const s = fmt?.suffix ?? ''; + if (fmt?.raw) return `${p}${Math.round(value)}${s}`; if (usesChineseNumberUnits()) { const chineseCompactValue = formatChineseCompactNumber(value); - if (chineseCompactValue) return chineseCompactValue; - if (Number.isInteger(value)) return value.toString(); - return value.toFixed(2); + if (chineseCompactValue) return `${p}${chineseCompactValue}${s}`; + if (Number.isInteger(value)) return `${p}${value}${s}`; + return `${p}${value.toFixed(2)}${s}`; } - if (Math.abs(value) >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`; - if (Math.abs(value) >= 1_000) return `${(value / 1_000).toFixed(1)}k`; - if (Number.isInteger(value)) return value.toString(); - return value.toFixed(2); + if (Math.abs(value) >= 1_000_000) return `${p}${(value / 1_000_000).toFixed(1)}M${s}`; + if (Math.abs(value) >= 1_000) return `${p}${(value / 1_000).toFixed(1)}k${s}`; + if (Number.isInteger(value)) return `${p}${value}${s}`; + return `${p}${value.toFixed(2)}${s}`; } /** Parse a user-typed value like "250k", "1.2M", "£300000", "50 sqm" back to a number. */ diff --git a/frontend/src/lib/group-icons.ts b/frontend/src/lib/group-icons.ts index 95108f0..4a4f24c 100644 --- a/frontend/src/lib/group-icons.ts +++ b/frontend/src/lib/group-icons.ts @@ -7,6 +7,7 @@ import { ShieldIcon, UsersIcon, ShoppingBagIcon, + MapPinIcon, TreeIcon, TagIcon, } from '../components/ui/icons'; @@ -18,6 +19,7 @@ const GROUP_ICONS: Record> = { Deprivation: ChartBarIcon, Crime: ShieldIcon, Demographics: UsersIcon, + 'Nearby POIs': MapPinIcon, Amenities: ShoppingBagIcon, Environment: TreeIcon, Property: TagIcon, diff --git a/frontend/src/lib/h3-selection.test.ts b/frontend/src/lib/h3-selection.test.ts new file mode 100644 index 0000000..31a6953 --- /dev/null +++ b/frontend/src/lib/h3-selection.test.ts @@ -0,0 +1,46 @@ +import { cellToChildren, cellToLatLng, latLngToCell } from 'h3-js'; +import { describe, expect, it } from 'vitest'; +import type { HexagonData } from '../types'; +import { findOverlappingMatchingHexagon, hasMatchingHexagonAtResolution } from './h3-selection'; + +function hexagonData(h3: string, count: number): HexagonData { + const [lat, lon] = cellToLatLng(h3); + return { h3, count, lat, lon }; +} + +describe('h3 selection helpers', () => { + it('finds a matching higher-resolution hexagon that overlaps the previous hexagon', () => { + const parent = latLngToCell(51.5, -0.1, 8); + const children = cellToChildren(parent, 9); + const selected = findOverlappingMatchingHexagon( + parent, + [hexagonData(children[0], 0), hexagonData(children[1], 4)], + 9 + ); + + expect(selected?.h3).toBe(children[1]); + }); + + it('rejects candidates that do not overlap or have no matches', () => { + const parent = latLngToCell(51.5, -0.1, 8); + const nearbyChild = cellToChildren(parent, 9)[0]; + const distant = latLngToCell(52.2, -0.1, 9); + + expect( + findOverlappingMatchingHexagon( + parent, + [hexagonData(nearbyChild, 0), hexagonData(distant, 12)], + 9 + ) + ).toBeNull(); + }); + + it('detects when target-resolution matching data is loaded', () => { + const parent = latLngToCell(51.5, -0.1, 8); + const child = cellToChildren(parent, 9)[0]; + + expect(hasMatchingHexagonAtResolution([hexagonData(child, 1)], 9)).toBe(true); + expect(hasMatchingHexagonAtResolution([hexagonData(child, 0)], 9)).toBe(false); + expect(hasMatchingHexagonAtResolution([hexagonData(parent, 1)], 9)).toBe(false); + }); +}); diff --git a/frontend/src/lib/h3-selection.ts b/frontend/src/lib/h3-selection.ts new file mode 100644 index 0000000..4671d7a --- /dev/null +++ b/frontend/src/lib/h3-selection.ts @@ -0,0 +1,125 @@ +import { cellToBoundary, cellToLatLng, getResolution } from 'h3-js'; +import type { HexagonData } from '../types'; + +type Point = [number, number]; + +const EPSILON = 1e-12; + +function samePoint(a: Point, b: Point): boolean { + return Math.abs(a[0] - b[0]) <= EPSILON && Math.abs(a[1] - b[1]) <= EPSILON; +} + +function cellBoundary(h3: string): Point[] { + const boundary = cellToBoundary(h3, true) as Point[]; + if (boundary.length > 1 && samePoint(boundary[0], boundary[boundary.length - 1])) { + return boundary.slice(0, -1); + } + return boundary; +} + +function orientation(a: Point, b: Point, c: Point): number { + return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]); +} + +function pointOnSegment(point: Point, start: Point, end: Point): boolean { + if (Math.abs(orientation(start, end, point)) > EPSILON) return false; + return ( + point[0] >= Math.min(start[0], end[0]) - EPSILON && + point[0] <= Math.max(start[0], end[0]) + EPSILON && + point[1] >= Math.min(start[1], end[1]) - EPSILON && + point[1] <= Math.max(start[1], end[1]) + EPSILON + ); +} + +function segmentsIntersect(a: Point, b: Point, c: Point, d: Point): boolean { + const abC = orientation(a, b, c); + const abD = orientation(a, b, d); + const cdA = orientation(c, d, a); + const cdB = orientation(c, d, b); + + if ( + ((abC > EPSILON && abD < -EPSILON) || (abC < -EPSILON && abD > EPSILON)) && + ((cdA > EPSILON && cdB < -EPSILON) || (cdA < -EPSILON && cdB > EPSILON)) + ) { + return true; + } + + return ( + pointOnSegment(c, a, b) || + pointOnSegment(d, a, b) || + pointOnSegment(a, c, d) || + pointOnSegment(b, c, d) + ); +} + +function pointInPolygon(point: Point, polygon: Point[]): boolean { + let inside = false; + for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { + const current = polygon[i]; + const previous = polygon[j]; + + if (pointOnSegment(point, previous, current)) return true; + + if (current[1] > point[1] !== previous[1] > point[1]) { + const x = + ((previous[0] - current[0]) * (point[1] - current[1])) / (previous[1] - current[1]) + + current[0]; + if (point[0] < x) inside = !inside; + } + } + return inside; +} + +export function polygonsOverlap(a: Point[], b: Point[]): boolean { + if (a.length < 3 || b.length < 3) return false; + + if (a.some((point) => pointInPolygon(point, b))) return true; + if (b.some((point) => pointInPolygon(point, a))) return true; + + for (let i = 0; i < a.length; i++) { + const aNext = (i + 1) % a.length; + for (let j = 0; j < b.length; j++) { + const bNext = (j + 1) % b.length; + if (segmentsIntersect(a[i], a[aNext], b[j], b[bNext])) return true; + } + } + + return false; +} + +export function hasMatchingHexagonAtResolution( + hexagons: HexagonData[], + resolution: number +): boolean { + return hexagons.some((hexagon) => hexagon.count > 0 && getResolution(hexagon.h3) === resolution); +} + +export function findOverlappingMatchingHexagon( + previousH3: string, + hexagons: HexagonData[], + resolution: number +): HexagonData | null { + const previousBoundary = cellBoundary(previousH3); + const [previousLat, previousLng] = cellToLatLng(previousH3); + let best: HexagonData | null = null; + let bestDistance = Infinity; + + for (const hexagon of hexagons) { + if (hexagon.count <= 0 || getResolution(hexagon.h3) !== resolution) continue; + if (!polygonsOverlap(previousBoundary, cellBoundary(hexagon.h3))) continue; + + const distance = (hexagon.lat - previousLat) ** 2 + (hexagon.lon - previousLng) ** 2; + if ( + !best || + distance < bestDistance - EPSILON || + (Math.abs(distance - bestDistance) <= EPSILON && + (hexagon.count > best.count || + (hexagon.count === best.count && hexagon.h3.localeCompare(best.h3) < 0))) + ) { + best = hexagon; + bestDistance = distance; + } + } + + return best; +} diff --git a/frontend/src/lib/map-utils.test.ts b/frontend/src/lib/map-utils.test.ts index 899b310..5f628c1 100644 --- a/frontend/src/lib/map-utils.test.ts +++ b/frontend/src/lib/map-utils.test.ts @@ -1,11 +1,23 @@ import { describe, expect, it } from 'vitest'; +import { existsSync, readFileSync } from 'fs'; +import { join } from 'path'; -import { DENSITY_GRADIENT, ENUM_PALETTE, FEATURE_GRADIENT } from './consts'; +import { + DENSITY_GRADIENT, + ENUM_PALETTE, + FEATURE_GRADIENT, + MAP_BOUNDS, + POI_CATEGORY_LOGOS, + SMALLEST_VISIBLE_HEXAGON_RESOLUTION, +} from './consts'; import { emojiToTwemojiUrl, enumIndexToColor, getBoundsFromViewState, + getBoundsWithBottomScreenInset, + getLatitudeAtVerticalPixelOffset, getFeatureFillColor, + getMapCenterForTargetScreenPoint, getPoiIconUrl, zoomToResolution, } from './map-utils'; @@ -16,6 +28,7 @@ describe('map utilities', () => { expect(zoomToResolution(7)).toBe(6); expect(zoomToResolution(10.6)).toBe(8); expect(zoomToResolution(14)).toBe(9); + expect(SMALLEST_VISIBLE_HEXAGON_RESOLUTION).toBe(9); }); it('computes buffered bounds around a view state', () => { @@ -31,17 +44,94 @@ describe('map utilities', () => { expect(bounds.east).toBeGreaterThan(-0.1); }); - it('builds twemoji URLs and wraps enum colors', () => { - expect(emojiToTwemojiUrl('🛒')).toBe('/assets/twemoji/1f6d2.png'); - expect(emojiToTwemojiUrl('')).toBe('/assets/twemoji/1f4cd.png'); - expect(enumIndexToColor(ENUM_PALETTE.length)).toEqual(ENUM_PALETTE[0]); + it('moves the map center so a target lands in the requested screen position', () => { + const centered = getMapCenterForTargetScreenPoint(51.5, -0.1, 17, 390, 844, 195, 42.2); + + expect(centered.longitude).toBeCloseTo(-0.1, 6); + expect(centered.latitude).toBeLessThan(51.5); }); - it('prefers POI category logos before falling back to emoji icons', () => { - expect(getPoiIconUrl('Waitrose', '🛒')).toBe( - 'https://geolytix.github.io/MapIcons/brands/waitrose_24px.svg' + it('expands the southern map bound by a covered screen area', () => { + const shiftedSouth = getLatitudeAtVerticalPixelOffset(MAP_BOUNDS[1], 5.5, 320); + const shiftedNorth = getLatitudeAtVerticalPixelOffset(MAP_BOUNDS[1], 5.5, -320); + const expandedBounds = getBoundsWithBottomScreenInset(MAP_BOUNDS, 5.5, 320); + + expect(shiftedSouth).toBeLessThan(MAP_BOUNDS[1]); + expect(shiftedNorth).toBeGreaterThan(MAP_BOUNDS[1]); + expect(expandedBounds[0]).toBe(MAP_BOUNDS[0]); + expect(expandedBounds[1]).toBeCloseTo(shiftedSouth, 6); + expect(expandedBounds[2]).toBe(MAP_BOUNDS[2]); + expect(expandedBounds[3]).toBe(MAP_BOUNDS[3]); + expect(getBoundsWithBottomScreenInset(MAP_BOUNDS, 5.5, 0)).toEqual(MAP_BOUNDS); + }); + + it('builds twemoji URLs and wraps enum colors', () => { + expect(emojiToTwemojiUrl('🛒')).toBe('/assets/twemoji/1f6d2.png'); + expect(() => emojiToTwemojiUrl('')).toThrow('Cannot build a Twemoji URL without an emoji'); + expect(enumIndexToColor(ENUM_PALETTE.length, ENUM_PALETTE)).toEqual(ENUM_PALETTE[0]); + }); + + it('resolves POI category logos and rejects unknown icon categories', () => { + expect(getPoiIconUrl('Waitrose', '🛒')).toBe('/assets/poi-icons/logos/waitrose.svg'); + expect(getPoiIconUrl('Iceland', '🛒', 'The Food Warehouse')).toBe( + '/assets/poi-icons/logos/the_food_warehouse.png' ); - expect(getPoiIconUrl('Unknown category', '🛒')).toBe('/assets/twemoji/1f6d2.png'); + expect(getPoiIconUrl("Sainsbury's", '🛒', undefined, 'Sainsburys Earlsfield Local')).toBe( + '/assets/poi-icons/brands_2024/sainsburys_local.svg' + ); + expect(getPoiIconUrl('Costco', '🛒')).toBe('/assets/poi-icons/logos/costco.svg'); + expect(getPoiIconUrl('M&S', '🛒', undefined, 'M&S Simply Food')).toBe( + '/assets/poi-icons/visuals/mns.svg' + ); + expect(() => getPoiIconUrl('Unknown category', '🛒')).toThrow( + "Missing POI icon for category 'Unknown category'" + ); + }); + + it('keeps POI icon URLs bundled locally', () => { + expect(Object.values(POI_CATEGORY_LOGOS).filter((url) => /^https?:\/\//.test(url))).toEqual([]); + expect( + Object.values(POI_CATEGORY_LOGOS) + .filter((url) => url.startsWith('/assets/poi-icons/')) + .filter((url) => !existsSync(join(process.cwd(), 'public', url.slice(1)))) + ).toEqual([]); + }); + + it('does not use pin-shaped SVGs for branded POI logos', () => { + const pinSignatures = ['viewBox="0 0 400 520"', 'C 18.914 185.931']; + const svgUrls = [ + ...new Set( + Object.values(POI_CATEGORY_LOGOS) + .filter((url) => url.startsWith('/assets/poi-icons/')) + .filter((url) => url.endsWith('.svg')) + ), + ]; + + expect( + svgUrls.filter((url) => { + const content = readFileSync(join(process.cwd(), 'public', url.slice(1)), 'utf8'); + return pinSignatures.some((signature) => content.includes(signature)); + }) + ).toEqual([]); + }); + + it('keeps bundled SVG logos large enough for the map icon atlas', () => { + const svgUrls = [ + ...new Set( + Object.values(POI_CATEGORY_LOGOS) + .filter((url) => url.startsWith('/assets/poi-icons/')) + .filter((url) => url.endsWith('.svg')) + ), + ]; + + expect( + svgUrls.filter((url) => { + const content = readFileSync(join(process.cwd(), 'public', url.slice(1)), 'utf8'); + const width = Number(content.match(/width="([0-9.]+)/)?.[1]); + const height = Number(content.match(/height="([0-9.]+)/)?.[1]); + return Math.max(width, height) < 256; + }) + ).toEqual([]); }); it('returns fallback, filtered, enum, feature, and density colors', () => { diff --git a/frontend/src/lib/map-utils.ts b/frontend/src/lib/map-utils.ts index 164e9e3..51db978 100644 --- a/frontend/src/lib/map-utils.ts +++ b/frontend/src/lib/map-utils.ts @@ -13,6 +13,59 @@ import { type GradientStop, } from './consts'; const ROAD_OPACITY = 0.4; +const TILE_SIZE = 512; +const MAX_MERCATOR_LATITUDE = 85; + +function clampLatitude(latitude: number): number { + return Math.max(-MAX_MERCATOR_LATITUDE, Math.min(MAX_MERCATOR_LATITUDE, latitude)); +} + +function longitudeToWorldX(longitude: number, worldSize: number): number { + return ((longitude + 180) / 360) * worldSize; +} + +function latitudeToWorldY(latitude: number, worldSize: number): number { + const clampedLat = clampLatitude(latitude); + const latRad = (clampedLat * Math.PI) / 180; + const mercatorY = (1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2; + return mercatorY * worldSize; +} + +function worldXToLongitude(pixelX: number, worldSize: number): number { + const longitude = (pixelX / worldSize) * 360 - 180; + return ((((longitude + 180) % 360) + 360) % 360) - 180; +} + +function worldYToLatitude(pixelY: number, worldSize: number): number { + const mercY = Math.max(0.001, Math.min(0.999, pixelY / worldSize)); + const latRadians = Math.atan(Math.sinh(Math.PI * (1 - 2 * mercY))); + return (latRadians * 180) / Math.PI; +} + +export function getMapCenterForTargetScreenPoint( + targetLatitude: number, + targetLongitude: number, + zoom: number, + width: number, + height: number, + targetScreenX: number, + targetScreenY: number +): Pick { + if (width <= 0 || height <= 0) { + return { latitude: targetLatitude, longitude: targetLongitude }; + } + + const worldSize = TILE_SIZE * Math.pow(2, zoom); + const targetWorldX = longitudeToWorldX(targetLongitude, worldSize); + const targetWorldY = latitudeToWorldY(targetLatitude, worldSize); + const centerWorldX = targetWorldX + width / 2 - targetScreenX; + const centerWorldY = targetWorldY + height / 2 - targetScreenY; + + return { + latitude: worldYToLatitude(centerWorldY, worldSize), + longitude: worldXToLongitude(centerWorldX, worldSize), + }; +} export function getMapStyle(theme: 'light' | 'dark'): StyleSpecification { const flavor = namedFlavor(theme); @@ -158,8 +211,7 @@ export function getBoundsFromViewState( height: number ): Bounds { const { longitude, latitude, zoom } = viewState; - const clampedLat = Math.max(-85, Math.min(85, latitude)); - const TILE_SIZE = 512; + const clampedLat = clampLatitude(latitude); const scale = Math.pow(2, zoom); const worldSize = TILE_SIZE * scale; @@ -169,21 +221,13 @@ export function getBoundsFromViewState( const degreesPerPixelLng = 360 / worldSize; const halfWidthDeg = (bufferedWidth / 2) * degreesPerPixelLng; - const latRad = (clampedLat * Math.PI) / 180; - const mercatorY = (1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2; - const centerPixelY = mercatorY * worldSize; + const centerPixelY = latitudeToWorldY(clampedLat, worldSize); const topPixelY = centerPixelY - bufferedHeight / 2; const bottomPixelY = centerPixelY + bufferedHeight / 2; - const pixelYToLat = (pixelY: number): number => { - const mercY = Math.max(0.001, Math.min(0.999, pixelY / worldSize)); - const latRadians = Math.atan(Math.sinh(Math.PI * (1 - 2 * mercY))); - return (latRadians * 180) / Math.PI; - }; - - const north = Math.min(85, pixelYToLat(topPixelY)); - const south = Math.max(-85, pixelYToLat(bottomPixelY)); + const north = Math.min(MAX_MERCATOR_LATITUDE, worldYToLatitude(topPixelY, worldSize)); + const south = Math.max(-MAX_MERCATOR_LATITUDE, worldYToLatitude(bottomPixelY, worldSize)); const west = Math.max(-180, longitude - halfWidthDeg); const east = Math.min(180, longitude + halfWidthDeg); @@ -197,7 +241,60 @@ export function emojiToTwemojiUrl(emoji: string): string { return `${TWEMOJI_BASE}${hex}.png`; } -export function getPoiIconUrl(category: string, emoji: string): string { +function inferPoiIconCategory(category: string, name?: string): string | undefined { + if (!name) return undefined; + const text = `${category} ${name}`.toLowerCase(); + + switch (category) { + case 'Asda': + if (text.includes('asda express') || text.includes(' express')) return 'Asda Express'; + if (text.includes('asda living')) return 'Asda Living'; + if (text.includes('asda pfs') || /\bpfs\b/.test(text)) return 'Asda PFS'; + return undefined; + case 'Iceland': + return text.includes('food warehouse') ? 'The Food Warehouse' : undefined; + case 'M&S': + if (text.includes('hospital')) return 'M&S Hospital'; + if (text.includes('moto')) return 'M&S MSA'; + if (text.includes('outlet')) return 'M&S Outlet'; + if ( + text.includes('foodhall') || + text.includes('simply food') || + text.includes('food to go') || + text.includes(' bp') || + /\bsf\b/.test(text) + ) { + return 'M&S Food'; + } + if (text.includes('clothing')) return 'M&S Clothing'; + return undefined; + case 'Morrisons': + return text.includes('morrisons daily') || text.includes('morrisons dailly') + ? 'Morrisons Daily' + : undefined; + case "Sainsbury's": + return text.includes('local') ? "Sainsbury's Local" : undefined; + case 'Tesco': + if (text.includes('tesco extra')) return 'Tesco Extra'; + if (text.includes('tesco express') || text.includes(' express')) return 'Tesco Express'; + return undefined; + case 'Waitrose': + return text.includes('little waitrose') ? 'Little Waitrose' : undefined; + default: + return undefined; + } +} + +export function getPoiIconUrl( + category: string, + emoji: string, + iconCategory?: string, + name?: string +): string { + const resolvedIconCategory = iconCategory || inferPoiIconCategory(category, name); + if (resolvedIconCategory && POI_CATEGORY_LOGOS[resolvedIconCategory]) { + return POI_CATEGORY_LOGOS[resolvedIconCategory]; + } return POI_CATEGORY_LOGOS[category] ?? emojiToTwemojiUrl(emoji); } diff --git a/frontend/src/lib/poi-distance-filter.ts b/frontend/src/lib/poi-distance-filter.ts new file mode 100644 index 0000000..5c63a98 --- /dev/null +++ b/frontend/src/lib/poi-distance-filter.ts @@ -0,0 +1,291 @@ +import type { FeatureFilters, FeatureMeta } from '../types'; + +export const POI_DISTANCE_FILTER_NAME = 'POI distance'; +export const POI_COUNT_2KM_FILTER_NAME = 'POIs within 2km'; +export const POI_COUNT_5KM_FILTER_NAME = 'POIs within 5km'; + +export const POI_FILTER_NAMES = [ + POI_DISTANCE_FILTER_NAME, + POI_COUNT_2KM_FILTER_NAME, + POI_COUNT_5KM_FILTER_NAME, +] as const; + +export type PoiFilterName = (typeof POI_FILTER_NAMES)[number]; +type PoiMetric = 'distance' | 'count_2km' | 'count_5km'; + +export const POI_DISTANCE_FILTER_KEY_PREFIX = `${POI_DISTANCE_FILTER_NAME}:`; + +export const POI_DISTANCE_FEATURE_NAMES = [ + 'Distance to nearest park (km)', + 'Distance to nearest grocery store (km)', + 'Distance to nearest tube station (km)', + 'Distance to nearest rail station (km)', + 'Distance to nearest Waitrose (km)', + 'Distance to nearest Tesco (km)', + 'Distance to nearest cafe (km)', + 'Distance to nearest pub (km)', + 'Distance to nearest restaurant (km)', +] as const; + +const LEGACY_POI_DISTANCE_FEATURE_NAME_SET = new Set(POI_DISTANCE_FEATURE_NAMES); +const LEGACY_POI_DISTANCE_AGGREGATE_OPTIONS = [ + 'Distance to nearest park (km)', + 'Distance to nearest grocery store (km)', +] as const; + +const DYNAMIC_DISTANCE_RE = /^Distance to nearest (.+) POI \(km\)$/; +const DYNAMIC_COUNT_RE = /^Number of (.+) POIs within (2|5)km$/; + +const POI_FILTER_CONFIGS: Record< + PoiFilterName, + { + metric: PoiMetric; + keyPrefix: string; + description: string; + detail: string; + defaultMax: number; + step: number; + suffix: string; + } +> = { + [POI_DISTANCE_FILTER_NAME]: { + metric: 'distance', + keyPrefix: POI_DISTANCE_FILTER_KEY_PREFIX, + description: 'Distance to nearby points of interest', + detail: 'Filter by distance to one nearby point-of-interest type at a time.', + defaultMax: 5, + step: 0.1, + suffix: ' km', + }, + [POI_COUNT_2KM_FILTER_NAME]: { + metric: 'count_2km', + keyPrefix: `${POI_COUNT_2KM_FILTER_NAME}:`, + description: 'Number of nearby points of interest within 2km', + detail: 'Filter by the count of one point-of-interest type within 2km.', + defaultMax: 20, + step: 1, + suffix: '', + }, + [POI_COUNT_5KM_FILTER_NAME]: { + metric: 'count_5km', + keyPrefix: `${POI_COUNT_5KM_FILTER_NAME}:`, + description: 'Number of nearby points of interest within 5km', + detail: 'Filter by the count of one point-of-interest type within 5km.', + defaultMax: 50, + step: 1, + suffix: '', + }, +}; + +function isPoiFilterNameValue(name: string): name is PoiFilterName { + return POI_FILTER_NAMES.includes(name as PoiFilterName); +} + +function getConfig(filterName: PoiFilterName) { + return POI_FILTER_CONFIGS[filterName]; +} + +function isDynamicPoiDistanceFeatureName(name: string): boolean { + return DYNAMIC_DISTANCE_RE.test(name); +} + +function getPoiMetric(name: string): PoiMetric | null { + if (isDynamicPoiDistanceFeatureName(name) || LEGACY_POI_DISTANCE_FEATURE_NAME_SET.has(name)) { + return 'distance'; + } + + const countMatch = name.match(DYNAMIC_COUNT_RE); + if (!countMatch) return null; + return countMatch[2] === '2' ? 'count_2km' : 'count_5km'; +} + +function getFilterNameForMetric(metric: PoiMetric): PoiFilterName { + if (metric === 'count_2km') return POI_COUNT_2KM_FILTER_NAME; + if (metric === 'count_5km') return POI_COUNT_5KM_FILTER_NAME; + return POI_DISTANCE_FILTER_NAME; +} + +export function getPoiFeatureCategory(name: string): string | null { + const distanceMatch = name.match(DYNAMIC_DISTANCE_RE); + if (distanceMatch) return distanceMatch[1]; + + const countMatch = name.match(DYNAMIC_COUNT_RE); + if (countMatch) return countMatch[1]; + + return null; +} + +export function isPoiDistanceFeatureName(name: string): boolean { + return isDynamicPoiDistanceFeatureName(name) || LEGACY_POI_DISTANCE_FEATURE_NAME_SET.has(name); +} + +export function isPoiFilterFeatureName(name: string): boolean { + return getPoiMetric(name) != null; +} + +export function getPoiFilterName(name: string): PoiFilterName | null { + for (const filterName of POI_FILTER_NAMES) { + if (name.startsWith(getConfig(filterName).keyPrefix)) return filterName; + } + const metric = getPoiMetric(name); + return metric ? getFilterNameForMetric(metric) : null; +} + +export function isPoiDistanceFilterName(name: string): boolean { + return getPoiFilterName(name) != null; +} + +export function createPoiFilterKey( + filterName: PoiFilterName, + featureName: string, + id: number | string +): string { + return `${getConfig(filterName).keyPrefix}${encodeURIComponent(featureName)}:${id}`; +} + +export function createPoiDistanceFilterKey(featureName: string, id: number | string): string { + return createPoiFilterKey(POI_DISTANCE_FILTER_NAME, featureName, id); +} + +export function getPoiFilterKeyId(name: string): string | null { + const filterName = getPoiFilterName(name); + if (!filterName) return null; + const prefix = getConfig(filterName).keyPrefix; + if (!name.startsWith(prefix)) return null; + const rest = name.substring(prefix.length); + const lastColon = rest.lastIndexOf(':'); + return lastColon === -1 ? null : rest.substring(lastColon + 1); +} + +export function getPoiDistanceFilterKeyId(name: string): string | null { + return getPoiFilterKeyId(name); +} + +export function parsePoiFilterKey(name: string): string | null { + const filterName = getPoiFilterName(name); + if (!filterName) return null; + const prefix = getConfig(filterName).keyPrefix; + if (!name.startsWith(prefix)) return null; + const rest = name.substring(prefix.length); + const lastColon = rest.lastIndexOf(':'); + if (lastColon === -1) return null; + + const decoded = decodeURIComponent(rest.substring(0, lastColon)); + const metric = getPoiMetric(decoded); + return metric === getConfig(filterName).metric ? decoded : null; +} + +export function parsePoiDistanceFilterKey(name: string): string | null { + return parsePoiFilterKey(name); +} + +export function getPoiDistanceFeatureName(name: string): string | null { + if (isPoiFilterFeatureName(name)) return name; + return parsePoiFilterKey(name); +} + +export function replacePoiFilterKeySelection(key: string, featureName: string): string { + const filterName = getPoiFilterName(key) ?? getFilterNameForMetric(getPoiMetric(featureName)!); + const id = getPoiFilterKeyId(key) ?? '0'; + return createPoiFilterKey(filterName, featureName, id); +} + +export function replacePoiDistanceFilterKeySelection(key: string, featureName: string): string { + return replacePoiFilterKeySelection(key, featureName); +} + +export function getPoiFilterFeatureOptions( + features: FeatureMeta[], + filterName: PoiFilterName +): FeatureMeta[] { + const metric = getConfig(filterName).metric; + const dynamicOptions = features.filter((feature) => { + const featureMetric = getPoiMetric(feature.name); + if (featureMetric !== metric) return false; + return metric !== 'distance' || isDynamicPoiDistanceFeatureName(feature.name); + }); + + if (dynamicOptions.length > 0 && metric === 'distance') { + const aggregateOptions = LEGACY_POI_DISTANCE_AGGREGATE_OPTIONS.map((name) => + features.find((feature) => feature.name === name) + ).filter((feature): feature is FeatureMeta => Boolean(feature)); + return [...dynamicOptions, ...aggregateOptions]; + } + + if (dynamicOptions.length > 0 || metric !== 'distance') { + return dynamicOptions; + } + + return POI_DISTANCE_FEATURE_NAMES.map((name) => + features.find((feature) => feature.name === name) + ).filter((feature): feature is FeatureMeta => Boolean(feature)); +} + +export function getDefaultPoiFilterFeatureName( + features: FeatureMeta[], + filterName: PoiFilterName +): string | null { + return getPoiFilterFeatureOptions(features, filterName)[0]?.name ?? null; +} + +export function getDefaultPoiDistanceFeatureName(features: FeatureMeta[]): string | null { + return getDefaultPoiFilterFeatureName(features, POI_DISTANCE_FILTER_NAME); +} + +export function getPoiFilterMeta(features: FeatureMeta[], filterName: PoiFilterName): FeatureMeta { + const sourceFeatureName = getDefaultPoiFilterFeatureName(features, filterName); + const sourceFeature = sourceFeatureName + ? features.find((feature) => feature.name === sourceFeatureName) + : undefined; + const config = getConfig(filterName); + + return { + name: filterName, + type: 'numeric', + group: 'Nearby POIs', + min: sourceFeature?.min ?? 0, + max: sourceFeature?.max ?? config.defaultMax, + step: config.step, + description: config.description, + detail: config.detail, + source: sourceFeature?.source ?? 'osm-pois', + suffix: config.suffix, + }; +} + +export function getPoiDistanceFilterMeta(features: FeatureMeta[]): FeatureMeta { + return getPoiFilterMeta(features, POI_DISTANCE_FILTER_NAME); +} + +export function normalizePoiDistanceFilters(filters: FeatureFilters): FeatureFilters { + let changed = false; + const next: FeatureFilters = {}; + + for (const [name, value] of Object.entries(filters)) { + if (isPoiFilterFeatureName(name)) { + const filterName = getPoiFilterName(name) ?? POI_DISTANCE_FILTER_NAME; + next[createPoiFilterKey(filterName, name, Object.keys(next).length)] = value; + changed = true; + continue; + } + next[name] = value; + } + + return changed ? next : filters; +} + +export function clampPoiFilterRange( + value: [number, number], + feature?: FeatureMeta +): [number, number] { + const min = feature?.histogram?.min ?? feature?.min ?? 0; + const max = feature?.histogram?.max ?? feature?.max ?? Math.max(1, value[1]); + return [Math.max(min, Math.min(value[0], max)), Math.max(min, Math.min(value[1], max))]; +} + +export function clampPoiDistanceRange( + value: [number, number], + feature?: FeatureMeta +): [number, number] { + return clampPoiFilterRange(value, feature); +} diff --git a/frontend/src/lib/seoLandingPages.ts b/frontend/src/lib/seoLandingPages.ts new file mode 100644 index 0000000..2b391d5 --- /dev/null +++ b/frontend/src/lib/seoLandingPages.ts @@ -0,0 +1,755 @@ +import type { SeoContentKey, SeoLandingKey } from './seoRoutes'; + +export type { SeoContentKey, SeoLandingKey, SeoPageKey } from './seoRoutes'; + +export interface SeoFaq { + question: string; + answer: string; +} + +export interface SeoLink { + label: string; + path: string; + description: string; +} + +export interface SeoSection { + title: string; + body: string; + bullets?: string[]; +} + +export interface SeoLandingContent { + path: string; + eyebrow: string; + title: string; + metaTitle: string; + metaDescription: string; + intro: string; + points: string[]; + workflows: SeoSection[]; + sections: SeoSection[]; + methodology: SeoSection[]; + faq: SeoFaq[]; + relatedLinks: SeoLink[]; + cta: string; +} + +export interface SeoContentPage { + path: string; + eyebrow: string; + title: string; + metaTitle: string; + metaDescription: string; + intro: string; + sections: SeoSection[]; + faq: SeoFaq[]; + relatedLinks: SeoLink[]; + cta?: string; +} + +const COMMON_RELATED_LINKS: SeoLink[] = [ + { + label: 'Data sources and coverage', + path: '/data-sources', + description: 'See which datasets sit behind the postcode filters and where they have limits.', + }, + { + label: 'Methodology', + path: '/methodology', + description: + 'Understand how the map is intended to support shortlisting, not replace due diligence.', + }, + { + label: 'Postcode checker', + path: '/postcode-checker', + description: 'Check one postcode before you spend time on a viewing.', + }, +]; + +export const SEO_LANDING_PAGES: Record = { + 'property-price-map': { + path: '/property-price-map', + eyebrow: 'Property price map', + title: 'Compare property prices across every postcode in England', + metaTitle: 'Property price map for England - Compare postcodes before viewing', + metaDescription: + 'Compare sold prices, estimated current value, price per square metre and local context across English postcodes before searching listings.', + intro: + 'Perfect Postcode maps sold prices, estimated current value, price per square metre, property type, floor area, tenure, and local context so buyers can find realistic search areas before opening listing portals.', + points: [ + 'Screen historical sale prices and current-value estimates by postcode.', + 'Compare value with commute, schools, broadband, crime, noise, and amenities.', + 'Build a shortlist before spending weekends on viewings.', + ], + workflows: [ + { + title: 'Find postcodes that fit the budget before listings appear', + body: 'Start with a maximum price and property type, then colour the map by price per square metre or estimated current price. This helps reveal areas where similar homes have historically traded within reach, even when there are no live listings today.', + bullets: [ + 'Filter by last known sale price, estimated current value, property type, tenure, and floor area.', + 'Compare nearby postcodes using the same criteria instead of relying on area reputation.', + 'Use the results as a shortlist for listing alerts, local research, and viewings.', + ], + }, + { + title: 'Separate cheap from good value', + body: 'A lower price can reflect smaller homes, weaker transport, more noise, or fewer local services. The map keeps those trade-offs visible so the cheapest postcode isn’t automatically treated as the best option.', + }, + ], + sections: [ + { + title: 'Start from area value, not listing availability', + body: 'Listing portals only show homes for sale today. A postcode-level property price map lets you compare wider areas, understand local price patterns, and avoid missing places where the next suitable listing might appear.', + }, + { + title: 'Use prices alongside real constraints', + body: 'Budget rarely matters on its own. Perfect Postcode combines price filters with travel time, school quality, property size, energy performance, local environment, and services so your shortlist reflects how you actually want to live.', + }, + ], + methodology: [ + { + title: 'What the price data is for', + body: 'Use the map to compare areas and spot search candidates. It isn’t a valuation, mortgage decision, survey, legal search, or live listing feed.', + }, + { + title: 'How to validate a promising area', + body: 'Once a postcode looks promising, check current listings, sold-price comparables, agent details, flood searches, legal packs, surveys, and local authority information before making a decision.', + }, + ], + faq: [ + { + question: 'Is this a replacement for Rightmove or Zoopla?', + answer: + 'No. Use it before and alongside listing portals. Perfect Postcode helps decide where to look; listing portals show what’s currently for sale.', + }, + { + question: 'Can I compare price with schools or commute time?', + answer: + 'Yes. Price filters can be combined with travel-time, schools, crime, broadband, road-noise, amenities, and environment filters.', + }, + { + question: 'Does the map cover all of the UK?', + answer: + 'The current product focuses on England because several core property and postcode datasets are England-specific.', + }, + ], + relatedLinks: [ + { + label: 'Birmingham property search guide', + path: '/property-search/birmingham', + description: 'A worked example for balancing price, commute, and family trade-offs.', + }, + ...COMMON_RELATED_LINKS, + ], + cta: 'Explore the property map', + }, + 'postcode-property-search': { + path: '/postcode-property-search', + eyebrow: 'Postcode property search', + title: 'Find postcodes that match your property search criteria', + metaTitle: 'Postcode property search - Find areas that match your criteria', + metaDescription: + 'Search every postcode by budget, property type, floor area, tenure, commute, schools, crime, broadband, noise, parks and local amenities.', + intro: + 'Search every postcode by budget, property type, size, tenure, commute, schools, crime, broadband, noise, parks, and local amenities instead of checking areas one at a time.', + points: [ + 'Filter England-wide postcode data from one map.', + 'Shortlist unfamiliar areas with comparable evidence.', + 'Save and share search areas before booking viewings.', + ], + workflows: [ + { + title: 'Turn a broad brief into postcode candidates', + body: 'Enter the practical constraints first: budget, property size, tenure, travel time, school needs, broadband, and tolerance for road noise or crime levels. The map removes places that fail those constraints and keeps the remaining options comparable.', + }, + { + title: 'Relax one constraint at a time', + body: 'When the search becomes too narrow, loosen a single filter and watch which postcodes reappear. This makes compromise explicit instead of relying on guesswork.', + }, + ], + sections: [ + { + title: 'Turn vague areas into specific postcodes', + body: 'Broad town or borough searches hide large differences between streets. Perfect Postcode helps you move from a general area to postcodes that satisfy your hard requirements.', + }, + { + title: 'Keep trade-offs visible', + body: 'When there are too many or too few matches, adjust one constraint at a time and see exactly which postcodes reappear. That makes compromises explicit instead of relying on guesswork.', + }, + ], + methodology: [ + { + title: 'Why postcode-level comparison matters', + body: 'Two nearby postcodes can differ on schools, road noise, transport access, property mix, and price. Comparing at postcode level reduces the chance of treating a whole town as one uniform market.', + }, + { + title: 'How to use the results', + body: 'Treat matching postcodes as a research queue: check live listings, visit streets, confirm schools and admissions, and review current official sources.', + }, + ], + faq: [ + { + question: 'Can I save a postcode property search?', + answer: + 'Yes. Licensed users can save searches and return to them later. Saved searches are designed for shortlists and comparison notes.', + }, + { + question: 'Can I search without knowing the area?', + answer: + 'Yes. The map is designed to surface unfamiliar areas that match practical constraints, not just places you already know.', + }, + { + question: 'Are the results live property listings?', + answer: + 'No. The tool compares postcode data and historical/contextual property signals. You still need listing portals for current availability.', + }, + ], + relatedLinks: [ + { + label: 'Manchester property search guide', + path: '/property-search/manchester', + description: 'A regional guide for narrowing a broad search around Greater Manchester.', + }, + ...COMMON_RELATED_LINKS, + ], + cta: 'Start a postcode search', + }, + 'commute-property-search': { + path: '/commute-property-search', + eyebrow: 'Commute property search', + title: 'Search for places to live by commute time', + metaTitle: 'Commute property search - Find places to live by travel time', + metaDescription: + 'Filter postcodes by commute time, then compare price, schools, safety, broadband, road noise, parks and property data on one map.', + intro: + 'Filter postcodes by modelled car, cycling, walking, and public transport travel times, then layer on property price, schools, crime, broadband, noise, and local amenities.', + points: [ + 'Compare reachable postcodes by realistic travel-time bands.', + 'Search by destination first, then filter for property and neighbourhood fit.', + 'Avoid areas that look close on a map but fail the daily journey.', + ], + workflows: [ + { + title: 'Start with the destination that matters', + body: 'Choose a commute destination, transport mode, and time range, then add the property filters. This prevents a cheap-looking area from reaching the shortlist if the daily journey doesn’t work.', + }, + { + title: 'Compare the commute against the rest of daily life', + body: 'A fast commute isn’t enough if the property size, school context, safety threshold, broadband, or road-noise exposure don’t fit. The map keeps those signals side by side.', + }, + ], + sections: [ + { + title: 'Commute from postcodes, not just place names', + body: 'Two streets in the same town can have very different station access, road routes, and public transport options. Postcode-level travel-time filtering keeps that difference visible.', + }, + { + title: 'Balance journey time with the rest of the move', + body: 'A fast commute only helps if the area also fits your budget, housing needs, school preferences, safety threshold, broadband requirement, and tolerance for road noise.', + }, + ], + methodology: [ + { + title: 'How travel-time filters should be interpreted', + body: 'Travel-time modelling is useful for comparing areas consistently. Before committing, check current timetables, disruption patterns, parking, cycling conditions, and walking routes.', + }, + { + title: 'Why commute filters are combined with property data', + body: 'Commute search is most useful when it removes impossible areas while still showing whether the remaining options are affordable and liveable.', + }, + ], + faq: [ + { + question: 'Can I compare car, cycling, walking, and public transport?', + answer: + 'The product supports multiple travel modes where precomputed destination data is available.', + }, + { + question: 'Are travel times exact?', + answer: + 'No. Treat them as a consistent comparison model, then verify the real route before making viewing or purchase decisions.', + }, + { + question: 'Can I combine commute filters with schools and price?', + answer: + 'Yes. The commute filter can be layered with property price, size, schools, broadband, crime, amenities, and environmental signals.', + }, + ], + relatedLinks: [ + { + label: 'Bristol property search guide', + path: '/property-search/bristol', + description: 'A worked example for balancing city access, price, and local context.', + }, + ...COMMON_RELATED_LINKS, + ], + cta: 'Search by commute time', + }, + 'school-property-search': { + path: '/school-property-search', + eyebrow: 'Schools and property search', + title: 'Find property search areas with schools and family trade-offs in view', + metaTitle: 'School property search - Compare postcodes for family moves', + metaDescription: + 'Compare nearby schools, property size, prices, parks, safety, commute and local amenities before building a viewing shortlist.', + intro: + 'Compare nearby Ofsted ratings, education context, property size, budget, safety, parks, commute, and local amenities before narrowing your viewing shortlist.', + points: [ + 'Filter for nearby school quality alongside housing requirements.', + 'Compare family-friendly trade-offs across unfamiliar postcodes.', + 'Use the map as a shortlist tool before checking admissions and catchments.', + ], + workflows: [ + { + title: 'Use school context without ignoring the home', + body: 'Start with property size, budget, and commute constraints, then layer in nearby school quality and local context. This prevents school-led searches from hiding affordability or daily-life problems.', + }, + { + title: 'Verify admissions before deciding', + body: 'School data can point to promising areas, but admissions rules and catchments can change. Confirm current arrangements with schools and local authorities.', + }, + ], + sections: [ + { + title: 'School quality is one part of the shortlist', + body: 'Perfect Postcode helps you compare nearby school data with the other practical constraints that shape a family move: space, price, commute, parks, safety, and local services.', + }, + { + title: 'Check catchments before making decisions', + body: 'Admissions rules and catchment boundaries can change. Use postcode-level school data to find promising areas, then verify current admissions details with the school or local authority.', + }, + ], + methodology: [ + { + title: 'How to treat school filters', + body: 'Use school filters to narrow research, not to assume admission eligibility. Ratings, distance, admissions criteria, and school capacity should all be checked with current official sources.', + }, + { + title: 'Family trade-offs to compare', + body: 'Combine schools with parks, road noise, crime, property size, commute, broadband, and price so the shortlist reflects the whole move.', + }, + ], + faq: [ + { + question: 'Does this show school catchment guarantees?', + answer: + 'No. It helps identify promising areas, but catchments and admissions must be verified with the school or local authority.', + }, + { + question: 'Can I combine school filters with parks and safety?', + answer: + 'Yes. School-aware search can be combined with crime, parks, commute, price, property size, and local services.', + }, + { + question: 'Is Ofsted the only school signal?', + answer: + 'No single score should decide a move. Use the map as a starting point, then review current school information in detail.', + }, + ], + relatedLinks: [ + { + label: 'Data sources and coverage', + path: '/data-sources', + description: 'See where education, property, transport, and environment data comes from.', + }, + ...COMMON_RELATED_LINKS, + ], + cta: 'Explore school-aware searches', + }, + 'postcode-checker': { + path: '/postcode-checker', + eyebrow: 'Postcode checker', + title: 'Check postcode data before you book a viewing', + metaTitle: 'Postcode checker - Property, crime, broadband, noise and schools', + metaDescription: + 'Check postcode-level property prices, EPC data, crime, broadband, road noise, schools, council tax, amenities and travel-time context.', + intro: + 'Review property prices, EPC context, crime, broadband, road noise, local amenities, schools, deprivation, council tax, and travel-time data from one postcode-first map.', + points: [ + 'Check multiple local signals before visiting a street.', + 'Use official and open datasets rather than reputation alone.', + 'Compare postcodes consistently across England.', + ], + workflows: [ + { + title: 'Check the street before spending a viewing slot', + body: 'Use the postcode checker to review price history, local context, amenities, schools, and environment signals before you commit time to visiting.', + }, + { + title: 'Compare neighbouring postcodes', + body: 'If one postcode looks promising, compare adjacent areas using the same filters. This often reveals whether a concern is street-specific or part of a wider pattern.', + }, + ], + sections: [ + { + title: 'Useful before and alongside listing portals', + body: 'Listing photos rarely tell you enough about the surrounding street. Perfect Postcode gives you an evidence-led postcode check before you commit time to a viewing.', + }, + { + title: 'A screening tool, not professional advice', + body: 'The data is designed for shortlisting and comparison. Any purchase still needs current listing checks, legal due diligence, flood searches, lender requirements, and survey findings.', + }, + ], + methodology: [ + { + title: 'What a postcode check can catch', + body: 'A postcode check can surface price context, environmental signals, nearby amenities, and other local indicators that are easy to miss in a listing.', + }, + { + title: 'What a postcode check can’t prove', + body: 'It can’t confirm the condition of a home, future development, legal title, lender requirements, or current street-level experience. Those still need direct checks.', + }, + ], + faq: [ + { + question: 'Can I use the checker before a viewing?', + answer: + 'Yes. That’s one of the main use cases: screen the postcode first, then decide whether the viewing is worth the time.', + }, + { + question: 'Does the checker include exact property condition?', + answer: 'No. Property condition requires listing details, surveys, and direct inspection.', + }, + { + question: 'Can I compare multiple postcodes?', + answer: 'Yes. The map is designed for consistent comparison across postcodes.', + }, + ], + relatedLinks: COMMON_RELATED_LINKS, + cta: 'Check postcodes on the map', + }, +}; + +export const SEO_CONTENT_PAGES: Record = { + 'birmingham-property-search': { + path: '/property-search/birmingham', + eyebrow: 'Regional guide', + title: 'How to compare Birmingham postcodes before a property search', + metaTitle: 'Birmingham property search - Compare postcodes by price and commute', + metaDescription: + 'Use postcode-level data to compare Birmingham property prices, commute trade-offs, schools, crime, broadband and local amenities before viewings.', + intro: + 'Birmingham searches can change quickly from street to street. Use postcode-level evidence to compare budget, commute, schools, noise, crime, and local services before deciding where to watch listings.', + sections: [ + { + title: 'Start with commute corridors', + body: 'Choose the destination that matters, such as a workplace, station, university, or hospital, then compare reachable postcodes by transport mode and travel-time band.', + bullets: [ + 'Use commute time as a hard filter before judging price.', + 'Compare public transport with car, cycling, or walking where available.', + 'Check the route manually before booking viewings.', + ], + }, + { + title: 'Compare price with property type', + body: 'Median prices alone can be misleading if the local property mix changes. Add property type, tenure, floor area, and price filters so similar areas are compared fairly.', + }, + { + title: 'Keep family and environment trade-offs visible', + body: 'Layer school context, parks, road noise, broadband, and crime signals on top of the property filters. That makes it easier to decide which compromises are acceptable.', + }, + ], + faq: [ + { + question: 'Can Perfect Postcode tell me the best area in Birmingham?', + answer: + 'No tool can decide the best area for every buyer. It helps compare postcodes against your own constraints so you can build a better shortlist.', + }, + { + question: 'Should I use this instead of local knowledge?', + answer: + 'No. Use it to find and compare candidates, then validate them with visits, local advice, listings, and official checks.', + }, + ], + relatedLinks: [ + { + label: 'Property price map', + path: '/property-price-map', + description: 'Compare price patterns before looking at live listings.', + }, + { + label: 'Commute property search', + path: '/commute-property-search', + description: 'Search by travel time and then layer on property requirements.', + }, + { + label: 'Methodology', + path: '/methodology', + description: 'Understand how to interpret filters and limitations.', + }, + ], + cta: 'Compare Birmingham postcodes', + }, + 'manchester-property-search': { + path: '/property-search/manchester', + eyebrow: 'Regional guide', + title: 'How to compare Manchester postcodes for a property search', + metaTitle: 'Manchester property search - Compare postcodes before viewing', + metaDescription: + 'Compare Manchester-area postcodes by budget, commute, property type, schools, broadband, crime, noise and amenities before booking viewings.', + intro: + 'A Manchester-area search can span city-centre, suburban, and commuter options. Perfect Postcode helps keep each postcode comparable against the same property and daily-life constraints.', + sections: [ + { + title: 'Use travel time to define the real search area', + body: 'Start from the destinations that matter, then compare reachable postcodes rather than assuming every nearby place has the same practical journey.', + }, + { + title: 'Compare housing requirements before lifestyle preferences', + body: 'Filter by property type, floor area, tenure, and price before judging amenities. That keeps the shortlist grounded in homes that could realistically work.', + }, + { + title: 'Check local context consistently', + body: 'Use broadband, crime, road noise, parks, schools, and amenities as comparable signals. Then validate the strongest candidates with current local checks.', + }, + ], + faq: [ + { + question: 'Can I compare Manchester suburbs with city-centre postcodes?', + answer: + 'Yes. Use the same budget, property, commute, and local-context filters across both so trade-offs remain visible.', + }, + { + question: 'Does this include live listings?', + answer: + 'No. Use it to decide where to search, then use listing portals for current homes for sale.', + }, + ], + relatedLinks: [ + { + label: 'Postcode property search', + path: '/postcode-property-search', + description: 'Move from a broad search brief to specific postcode candidates.', + }, + { + label: 'Data sources', + path: '/data-sources', + description: 'Review the datasets used for property and local-context comparison.', + }, + { + label: 'Postcode checker', + path: '/postcode-checker', + description: 'Check a single postcode before arranging a viewing.', + }, + ], + cta: 'Compare Manchester postcodes', + }, + 'bristol-property-search': { + path: '/property-search/bristol', + eyebrow: 'Regional guide', + title: 'How to compare Bristol postcodes before a property search', + metaTitle: 'Bristol property search - Compare postcodes by commute and price', + metaDescription: + 'Compare Bristol postcodes by price, commute, property size, schools, broadband, crime, road noise, parks and amenities before viewings.', + intro: + 'Bristol searches often involve sharp trade-offs between price, journey time, property size, and neighbourhood context. A postcode-first comparison keeps those trade-offs visible.', + sections: [ + { + title: 'Make commute constraints explicit', + body: 'If access to the centre, a station, hospital, university, or business park matters, use travel-time filters first and then compare the remaining postcodes by property data.', + }, + { + title: 'Compare value, not just headline price', + body: 'Use price, property type, and floor-area filters together. This helps distinguish lower-cost areas from areas that simply contain smaller or different homes.', + }, + { + title: 'Screen environmental and local-service signals', + body: 'Road noise, parks, broadband, crime, and amenities can affect whether a property works day to day. Use them as screening criteria before booking viewings.', + }, + ], + faq: [ + { + question: 'Can I use this for commuter villages around Bristol?', + answer: + 'Yes, where the relevant postcode and travel-time data is available. Always verify routes and services manually before deciding.', + }, + { + question: 'Can this tell me whether a listing is good value?', + answer: + 'It can provide area context, but a specific listing still needs comparable sales, condition checks, survey findings, and professional advice where appropriate.', + }, + ], + relatedLinks: [ + { + label: 'Commute property search', + path: '/commute-property-search', + description: 'Search by reachable postcodes before refining by budget and local context.', + }, + { + label: 'Property price map', + path: '/property-price-map', + description: 'Understand price patterns before setting listing alerts.', + }, + { + label: 'Privacy and security', + path: '/privacy-security', + description: 'How account and saved-search data is handled in the product.', + }, + ], + cta: 'Compare Bristol postcodes', + }, + 'data-sources': { + path: '/data-sources', + eyebrow: 'Trust and coverage', + title: 'Perfect Postcode data sources and coverage', + metaTitle: 'Perfect Postcode data sources - Property, schools, commute and local context', + metaDescription: + 'Review the public and official datasets used by Perfect Postcode, including property prices, EPC, schools, crime, broadband, noise and travel-time context.', + intro: + 'Perfect Postcode combines property, transport, education, environment, and local-service datasets so buyers can compare postcodes consistently. This page explains what the data is for and where it should be verified.', + sections: [ + { + title: 'Property and housing context', + body: 'The product uses property transaction and housing-context datasets to support filters such as sale price, property type, tenure, floor area, energy performance, and estimated current value.', + bullets: [ + 'Use these fields to compare areas, not as a formal valuation.', + 'Check current listings, title information, lender requirements, and survey results before buying.', + ], + }, + { + title: 'Schools, safety, broadband, and environment', + body: 'Local-context filters help compare postcodes on signals that affect daily life. They should be treated as screening data and checked against current official sources for decisions.', + }, + { + title: 'Travel-time data', + body: 'Travel-time filters are designed for consistent area comparison. Route availability, disruption, parking, walking access, and timetable details should be verified before committing to an area.', + }, + ], + faq: [ + { + question: 'Why does coverage focus on England?', + answer: + 'Several core property, education, and local-context datasets are jurisdiction-specific. England coverage keeps comparisons more consistent.', + }, + { + question: 'How should I handle stale or missing data?', + answer: + 'Use the map as a shortlist tool. If a postcode matters, verify the latest details with current official sources and direct local checks.', + }, + ], + relatedLinks: [ + { + label: 'Methodology', + path: '/methodology', + description: 'How filters and comparisons should be interpreted.', + }, + { + label: 'Postcode checker', + path: '/postcode-checker', + description: 'Review postcode-level context before a viewing.', + }, + { + label: 'Privacy and security', + path: '/privacy-security', + description: 'How saved searches and account data are handled.', + }, + ], + }, + methodology: { + path: '/methodology', + eyebrow: 'How to use the map', + title: 'Methodology for postcode property research', + metaTitle: 'Perfect Postcode methodology - How to interpret postcode property data', + metaDescription: + 'Understand how to use postcode filters, property estimates, travel-time data, school context and local signals as a home-buying shortlist tool.', + intro: + 'Perfect Postcode is designed to make area shortlisting more evidence-led. It doesn’t replace estate agents, surveyors, conveyancers, lenders, school admissions teams, or local authority checks.', + sections: [ + { + title: 'Start with hard constraints', + body: 'Begin with non-negotiables such as budget, property type, floor area, commute time, and essential services. This removes impossible postcodes before softer preferences are considered.', + }, + { + title: 'Use colour layers for trade-offs', + body: 'After filtering, colour the remaining map by one signal at a time: price per square metre, road noise, school context, commute time, broadband, or crime. This makes trade-offs easier to discuss.', + }, + { + title: 'Measure what’s working', + body: 'Use Search Console and analytics to track which public pages are indexed, which queries produce impressions, and which pages convert visitors into dashboard exploration. Review Core Web Vitals after every substantial frontend change.', + }, + ], + faq: [ + { + question: 'Can the tool choose the right postcode for me?', + answer: + 'No. It helps compare evidence and reduce the search area. The final decision needs direct visits, current listings, legal checks, surveys, and personal judgement.', + }, + { + question: 'How should I use estimates?', + answer: + 'Use estimates as comparison signals, not as professional valuations or purchase advice.', + }, + ], + relatedLinks: [ + { + label: 'Data sources and coverage', + path: '/data-sources', + description: 'Understand where key filters come from.', + }, + { + label: 'Property price map', + path: '/property-price-map', + description: 'Apply the methodology to price-led area comparison.', + }, + { + label: 'Commute property search', + path: '/commute-property-search', + description: 'Apply the methodology to destination-led search.', + }, + ], + }, + 'privacy-security': { + path: '/privacy-security', + eyebrow: 'Trust', + title: 'Privacy and security for saved property searches', + metaTitle: 'Perfect Postcode privacy and security - Saved searches and account data', + metaDescription: + 'Learn how Perfect Postcode treats saved searches, account data and property research workflows with privacy and security in mind.', + intro: + 'Property research can reveal personal priorities, budgets, and locations. The product keeps public SEO pages separate from account-only areas and marks private dashboard/account routes as noindex.', + sections: [ + { + title: 'Public pages and private areas are separated', + body: 'Marketing, methodology, guide, and support pages are indexable. Dashboard, account, saved searches, invites, and invitation routes are marked noindex or blocked from crawler access where appropriate.', + }, + { + title: 'Saved search data is account-scoped', + body: 'Saved searches and properties are intended for signed-in use. They aren’t included in the public sitemap and shouldn’t be crawlable as public content.', + }, + { + title: 'Search measurement without exposing private data', + body: 'SEO measurement should happen on public pages using aggregated analytics and Search Console data. Private query parameters and account views shouldn’t become indexable landing pages.', + }, + ], + faq: [ + { + question: 'Are saved searches listed in the sitemap?', + answer: + 'No. Public SEO pages are listed; account and saved-search routes are intentionally excluded.', + }, + { + question: 'Can private dashboard URLs appear in search?', + answer: + 'They shouldn’t be indexed. The server marks private routes noindex and the sitemap only lists public pages.', + }, + ], + relatedLinks: [ + { + label: 'Methodology', + path: '/methodology', + description: 'How to use public postcode data responsibly.', + }, + { + label: 'Data sources and coverage', + path: '/data-sources', + description: 'What data powers the public comparisons.', + }, + { + label: 'Postcode property search', + path: '/postcode-property-search', + description: 'Explore public postcode-search workflows.', + }, + ], + }, +}; diff --git a/frontend/src/lib/seoRoutes.ts b/frontend/src/lib/seoRoutes.ts new file mode 100644 index 0000000..f9df58e --- /dev/null +++ b/frontend/src/lib/seoRoutes.ts @@ -0,0 +1,51 @@ +export type SeoLandingKey = + | 'property-price-map' + | 'postcode-property-search' + | 'commute-property-search' + | 'school-property-search' + | 'postcode-checker'; + +export type SeoContentKey = + | 'birmingham-property-search' + | 'manchester-property-search' + | 'bristol-property-search' + | 'data-sources' + | 'methodology' + | 'privacy-security'; + +export type SeoPageKey = SeoLandingKey | SeoContentKey; + +export const SEO_LANDING_PATHS: Record = { + 'property-price-map': '/property-price-map', + 'postcode-property-search': '/postcode-property-search', + 'commute-property-search': '/commute-property-search', + 'school-property-search': '/school-property-search', + 'postcode-checker': '/postcode-checker', +}; + +export const SEO_CONTENT_PATHS: Record = { + 'birmingham-property-search': '/property-search/birmingham', + 'manchester-property-search': '/property-search/manchester', + 'bristol-property-search': '/property-search/bristol', + 'data-sources': '/data-sources', + methodology: '/methodology', + 'privacy-security': '/privacy-security', +}; + +export function getSeoLandingPage(pathname: string): SeoLandingKey | null { + const match = Object.entries(SEO_LANDING_PATHS).find(([, path]) => path === pathname); + return (match?.[0] as SeoLandingKey | undefined) ?? null; +} + +export function getSeoContentPage(pathname: string): SeoContentKey | null { + const match = Object.entries(SEO_CONTENT_PATHS).find(([, path]) => path === pathname); + return (match?.[0] as SeoContentKey | undefined) ?? null; +} + +export function isSeoLandingKey(page: string): page is SeoLandingKey { + return Object.prototype.hasOwnProperty.call(SEO_LANDING_PATHS, page); +} + +export function isSeoContentKey(page: string): page is SeoContentKey { + return Object.prototype.hasOwnProperty.call(SEO_CONTENT_PATHS, page); +} diff --git a/frontend/src/lib/tutorial-styles.ts b/frontend/src/lib/tutorial-styles.ts index db0ba6b..c3983dc 100644 --- a/frontend/src/lib/tutorial-styles.ts +++ b/frontend/src/lib/tutorial-styles.ts @@ -1,52 +1,57 @@ -import type { Styles } from 'react-joyride'; +import type { Options, Styles } from 'react-joyride'; -export function getTutorialStyles(theme: 'light' | 'dark'): Partial { +export function getTutorialStyles(theme: 'light' | 'dark'): { + options: Partial; + styles: Partial; +} { const isDark = theme === 'dark'; return { options: { arrowColor: isDark ? '#292524' : '#ffffff', backgroundColor: isDark ? '#292524' : '#ffffff', + disableFocusTrap: true, + hideOverlay: true, overlayColor: isDark ? 'rgba(10,14,26,0.75)' : 'rgba(0,0,0,0.5)', primaryColor: '#00a28c', + spotlightRadius: 8, textColor: isDark ? '#d6d3d1' : '#44403c', zIndex: 1000, }, - tooltip: { - borderRadius: 8, - padding: 16, - }, - tooltipTitle: { - color: isDark ? '#f5f5f4' : '#0a0e1a', - fontSize: 15, - fontWeight: 600, - }, - tooltipContent: { - fontSize: 13, - lineHeight: 1.5, - padding: '8px 0 0', - }, - buttonNext: { - borderRadius: 6, - fontSize: 13, - fontWeight: 500, - padding: '6px 14px', - }, - buttonBack: { - color: isDark ? '#a8a29e' : '#78716c', - fontSize: 13, - fontWeight: 500, - marginRight: 8, - }, - buttonSkip: { - color: isDark ? '#78716c' : '#a8a29e', - fontSize: 12, - }, - buttonClose: { - color: isDark ? '#a8a29e' : '#78716c', - }, - spotlight: { - borderRadius: 8, + styles: { + tooltip: { + borderRadius: 8, + padding: 16, + }, + tooltipTitle: { + color: isDark ? '#f5f5f4' : '#0a0e1a', + fontSize: 15, + fontWeight: 600, + }, + tooltipContent: { + fontSize: 13, + lineHeight: 1.5, + padding: '8px 0 0', + }, + buttonPrimary: { + borderRadius: 6, + fontSize: 13, + fontWeight: 500, + padding: '6px 14px', + }, + buttonBack: { + color: isDark ? '#a8a29e' : '#78716c', + fontSize: 13, + fontWeight: 500, + marginRight: 8, + }, + buttonSkip: { + color: isDark ? '#78716c' : '#a8a29e', + fontSize: 12, + }, + buttonClose: { + color: isDark ? '#a8a29e' : '#78716c', + }, }, }; } diff --git a/frontend/src/lib/url-state.test.ts b/frontend/src/lib/url-state.test.ts index ea7ad13..45c2123 100644 --- a/frontend/src/lib/url-state.test.ts +++ b/frontend/src/lib/url-state.test.ts @@ -2,7 +2,15 @@ import { beforeEach, describe, expect, it } from 'vitest'; import type { FeatureMeta } from '../types'; import { parseUrlState, stateToParams } from './url-state'; +import { INITIAL_VIEW_STATE } from './consts'; import { createSchoolFilterKey } from './school-filter'; +import { createSpecificCrimeFilterKey } from './crime-filter'; +import { createEthnicityFilterKey } from './ethnicity-filter'; +import { + POI_COUNT_2KM_FILTER_NAME, + createPoiDistanceFilterKey, + createPoiFilterKey, +} from './poi-distance-filter'; describe('url-state', () => { beforeEach(() => { @@ -42,6 +50,15 @@ describe('url-state', () => { ]); }); + it('leaves POIs unselected when URL params are omitted', () => { + const state = parseUrlState(); + + expect(state.viewState).toEqual(INITIAL_VIEW_STATE); + expect(state.filters).toEqual({}); + expect(state.poiCategories).toEqual(new Set()); + expect(state.tab).toBe('area'); + }); + it('serializes map state and active filters into stable URL params', () => { const features: FeatureMeta[] = [ { name: 'Last known price', type: 'numeric' }, @@ -80,6 +97,17 @@ describe('url-state', () => { expect(params.getAll('tt')).toEqual(['bicycle:bank:Bank:5:25']); }); + it('round-trips an explicitly empty POI selection', () => { + const params = stateToParams(null, {}, [], new Set(), 'area'); + + expect(params.getAll('poi')).toEqual(['__none']); + + window.history.replaceState({}, '', `/?${params.toString()}`); + const state = parseUrlState(); + + expect(state.poiCategories).toEqual(new Set()); + }); + it('round-trips repeated school filters with dedicated URL params', () => { const schoolOne = createSchoolFilterKey('primary', 'good', 2, 1); const schoolTwo = createSchoolFilterKey('secondary', 'outstanding', 5, 2); @@ -110,6 +138,121 @@ describe('url-state', () => { }); }); + it('round-trips repeated specific crime filters with dedicated URL params', () => { + const burglary = createSpecificCrimeFilterKey('Burglary (avg/yr)', 1); + const vehicleCrime = createSpecificCrimeFilterKey('Vehicle crime (avg/yr)', 2); + + const params = stateToParams( + null, + { + [burglary]: [0, 5], + [vehicleCrime]: [1, 10], + }, + [], + new Set(), + 'area' + ); + + expect(params.getAll('crime')).toEqual([ + 'Burglary (avg/yr):0:5', + 'Vehicle crime (avg/yr):1:10', + ]); + expect(params.getAll('filter')).toEqual([]); + + window.history.replaceState({}, '', `/?${params.toString()}`); + const state = parseUrlState(); + + expect(state.filters).toEqual({ + [createSpecificCrimeFilterKey('Burglary (avg/yr)', 0)]: [0, 5], + [createSpecificCrimeFilterKey('Vehicle crime (avg/yr)', 1)]: [1, 10], + }); + }); + + it('round-trips repeated ethnicity filters with dedicated URL params', () => { + const white = createEthnicityFilterKey('% White', 3); + const southAsian = createEthnicityFilterKey('% South Asian', 4); + + const params = stateToParams( + null, + { + [white]: [10, 80], + [southAsian]: [5, 35], + }, + [], + new Set(), + 'area' + ); + + expect(params.getAll('ethnicity')).toEqual(['% White:10:80', '% South Asian:5:35']); + expect(params.getAll('filter')).toEqual([]); + + window.history.replaceState({}, '', `/?${params.toString()}`); + const state = parseUrlState(); + + expect(state.filters).toEqual({ + [createEthnicityFilterKey('% White', 0)]: [10, 80], + [createEthnicityFilterKey('% South Asian', 1)]: [5, 35], + }); + }); + + it('round-trips repeated POI distance filters with dedicated URL params', () => { + const park = createPoiDistanceFilterKey('Distance to nearest park (km)', 3); + const tesco = createPoiDistanceFilterKey('Distance to nearest Tesco (km)', 4); + + const params = stateToParams( + null, + { + [park]: [0, 0.4], + [tesco]: [0, 1.5], + }, + [], + new Set(), + 'area' + ); + + expect(params.getAll('poiDistance')).toEqual([ + 'Distance%20to%20nearest%20park%20(km):0:0.4', + 'Distance%20to%20nearest%20Tesco%20(km):0:1.5', + ]); + expect(params.getAll('filter')).toEqual([]); + + window.history.replaceState({}, '', `/?${params.toString()}`); + const state = parseUrlState(); + + expect(state.filters).toEqual({ + [createPoiDistanceFilterKey('Distance to nearest park (km)', 0)]: [0, 0.4], + [createPoiDistanceFilterKey('Distance to nearest Tesco (km)', 1)]: [0, 1.5], + }); + }); + + it('round-trips POI count filters with dedicated URL params', () => { + const cafes = createPoiFilterKey( + POI_COUNT_2KM_FILTER_NAME, + 'Number of Cafe POIs within 2km', + 3 + ); + + const params = stateToParams( + null, + { + [cafes]: [2, 8], + }, + [], + new Set(), + 'area' + ); + + expect(params.getAll('poiCount2km')).toEqual(['Number%20of%20Cafe%20POIs%20within%202km:2:8']); + expect(params.getAll('filter')).toEqual([]); + + window.history.replaceState({}, '', `/?${params.toString()}`); + const state = parseUrlState(); + + expect(state.filters).toEqual({ + [createPoiFilterKey(POI_COUNT_2KM_FILTER_NAME, 'Number of Cafe POIs within 2km', 0)]: [2, 8], + }); + }); + it('omits the default area tab', () => { const params = stateToParams(null, {}, [], new Set(), 'area'); diff --git a/frontend/src/lib/url-state.ts b/frontend/src/lib/url-state.ts index bfb93f0..c201b83 100644 --- a/frontend/src/lib/url-state.ts +++ b/frontend/src/lib/url-state.ts @@ -14,11 +14,21 @@ import { type SchoolPhase, type SchoolRating, } from './school-filter'; +import { + SPECIFIC_CRIMES_FILTER_NAME, + createSpecificCrimeFilterKey, + getSpecificCrimeFeatureName, + isSpecificCrimeFeatureName, + isSpecificCrimeFilterName, +} from './crime-filter'; function parseFilters(params: URLSearchParams): FeatureFilters | undefined { const filterParams = params.getAll('filter'); const schoolParams = params.getAll('school'); - if (filterParams.length === 0 && schoolParams.length === 0) return undefined; + const crimeParams = params.getAll('crime'); + if (filterParams.length === 0 && schoolParams.length === 0 && crimeParams.length === 0) { + return undefined; + } const filters: FeatureFilters = {}; for (const entry of filterParams) { @@ -60,6 +70,18 @@ function parseFilters(params: URLSearchParams): FeatureFilters | undefined { filters[createSchoolFilterKey(phase, rating, distance, index)] = [min, max]; }); + crimeParams.forEach((entry, index) => { + const parts = entry.split(':'); + if (parts.length < 3) return; + const featureName = parts.slice(0, -2).join(':'); + const min = Number(parts[parts.length - 2]); + const max = Number(parts[parts.length - 1]); + if (!isSpecificCrimeFeatureName(featureName) || isNaN(min) || isNaN(max)) { + return; + } + filters[createSpecificCrimeFilterKey(featureName, index)] = [min, max]; + }); + return Object.keys(filters).length > 0 ? filters : undefined; } @@ -180,6 +202,13 @@ export function stateToParams( continue; } + const specificCrimeFeatureName = getSpecificCrimeFeatureName(name); + if (specificCrimeFeatureName && isSpecificCrimeFilterName(name)) { + const [min, max] = value as [number, number]; + params.append('crime', `${specificCrimeFeatureName}:${min}:${max}`); + continue; + } + const meta = features.find((f) => f.name === name); if (meta?.type === 'enum') { params.append('filter', `${name}:${(value as string[]).join('|')}`); @@ -225,14 +254,19 @@ export function summarizeParams(queryString: string): string { const filterParams = params.getAll('filter'); const schoolParams = params.getAll('school'); - if (filterParams.length > 0 || schoolParams.length > 0) { + const crimeParams = params.getAll('crime'); + if (filterParams.length > 0 || schoolParams.length > 0 || crimeParams.length > 0) { const filterNames = filterParams .map((entry) => { const colonIdx = entry.indexOf(':'); - return colonIdx > 0 ? entry.substring(0, colonIdx) : entry; + const name = colonIdx > 0 ? entry.substring(0, colonIdx) : entry; + return isSpecificCrimeFeatureName(name) ? SPECIFIC_CRIMES_FILTER_NAME : name; }) .filter((n) => n); for (let i = 0; i < schoolParams.length; i++) filterNames.push(SCHOOL_FILTER_NAME); + for (let i = 0; i < crimeParams.length; i++) { + filterNames.push(SPECIFIC_CRIMES_FILTER_NAME); + } if (filterNames.length > 0) { parts.push( filterNames.length <= 2 diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 3284fb6..8087535 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -66,6 +66,22 @@ export interface ViewState { bearing?: number; } +export interface MapVisibleAreaInsets { + top?: number; + right?: number; + bottom?: number; + left?: number; + topRatio?: number; + rightRatio?: number; + bottomRatio?: number; + leftRatio?: number; +} + +export interface MapFlyToOptions { + visibleArea?: MapVisibleAreaInsets; + visibleViewportArea?: MapVisibleAreaInsets; +} + export interface ViewChangeParams { resolution: number; bounds: Bounds; @@ -82,6 +98,7 @@ export interface POI { id: string; name: string; category: string; + icon_category?: string; group: string; lat: number; lng: number; @@ -110,6 +127,13 @@ export interface PlaceResult { city?: string; } +export interface AddressResult { + address: string; + postcode: string; + lat: number; + lon: number; +} + export interface JourneyLeg { mode: string; from?: string; diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 8be4415..a09ac10 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -31,9 +31,11 @@ module.exports = { 950: '#003330', }, coral: { + 50: '#fff7ed', 400: '#fb923c', 500: '#f97316', 600: '#ea580c', + 700: '#c2410c', }, warm: { 50: '#fafaf9', diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index cc94c18..b273d90 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -11,11 +11,7 @@ "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "isolatedModules": true, - "allowSyntheticDefaultImports": true, - "baseUrl": ".", - "paths": { - "@/*": ["src/*"] - } + "allowSyntheticDefaultImports": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index 7ad1aba..41373a0 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -4,6 +4,10 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); const FaviconsWebpackPlugin = require('favicons-webpack-plugin'); +const sharp = require('sharp'); +const webpack = require('webpack'); + +const HOUSE_IMAGE_WIDTH = 260; module.exports = (env, argv) => { const isProduction = argv.mode === 'production'; @@ -12,7 +16,8 @@ module.exports = (env, argv) => { entry: './src/index.tsx', output: { path: path.resolve(__dirname, 'dist'), - filename: 'bundle.js', + filename: isProduction ? '[name].[contenthash:8].js' : 'bundle.js', + chunkFilename: isProduction ? '[name].[contenthash:8].js' : '[name].bundle.js', clean: true, publicPath: '/', @@ -48,11 +53,41 @@ module.exports = (env, argv) => { ], }, plugins: [ + new webpack.DefinePlugin({ + __DEV__: JSON.stringify(!isProduction), + }), new HtmlWebpackPlugin({ template: './src/index.html', }), new CopyWebpackPlugin({ - patterns: [{ from: 'public', noErrorOnMissing: true }], + patterns: [ + { + from: 'public', + noErrorOnMissing: true, + globOptions: { + ignore: ['**/house.png'], + }, + }, + isProduction + ? { + from: 'public/house.png', + to: 'house.png', + noErrorOnMissing: true, + transform: { + transformer(content) { + return sharp(content) + .resize({ width: HOUSE_IMAGE_WIDTH, withoutEnlargement: true }) + .png({ compressionLevel: 9, palette: true, quality: 85 }) + .toBuffer(); + }, + }, + } + : { + from: 'public/house.png', + to: 'house.png', + noErrorOnMissing: true, + }, + ], }), new FaviconsWebpackPlugin({ logo: './public/favicon.svg', @@ -69,9 +104,111 @@ module.exports = (env, argv) => { }, }), ...(isProduction - ? [new MiniCssExtractPlugin()] + ? [ + new MiniCssExtractPlugin({ + filename: '[name].[contenthash:8].css', + chunkFilename: '[name].[contenthash:8].css', + }), + ] : [new ReactRefreshWebpackPlugin()]), ], + optimization: isProduction + ? { + splitChunks: { + chunks: 'all', + cacheGroups: { + maplibre: { + test: /[\\/]node_modules[\\/]maplibre-gl[\\/]/, + name: 'vendor-maplibre', + chunks: 'async', + priority: 50, + enforce: true, + reuseExistingChunk: true, + }, + h3: { + test: /[\\/]node_modules[\\/]h3-js[\\/]/, + name: 'vendor-h3', + chunks: 'async', + priority: 45, + enforce: true, + reuseExistingChunk: true, + }, + deckMapbox: { + test: /[\\/]node_modules[\\/]@deck\.gl[\\/]mapbox[\\/]/, + name: 'vendor-deck-mapbox', + chunks: 'async', + priority: 44, + enforce: true, + reuseExistingChunk: true, + }, + deckCore: { + test: /[\\/]node_modules[\\/]@deck\.gl[\\/]core[\\/]/, + name: 'vendor-deck-core', + chunks: 'async', + priority: 43, + enforce: true, + reuseExistingChunk: true, + }, + deckLayers: { + test: /[\\/]node_modules[\\/]@deck\.gl[\\/](?:layers|geo-layers)[\\/]/, + name: 'vendor-deck-layers', + chunks: 'async', + priority: 42, + enforce: true, + reuseExistingChunk: true, + }, + deck: { + test: /[\\/]node_modules[\\/]@deck\.gl[\\/]/, + name: 'vendor-deck', + chunks: 'async', + priority: 40, + enforce: true, + reuseExistingChunk: true, + }, + luma: { + test: /[\\/]node_modules[\\/]@luma\.gl[\\/]/, + name: 'vendor-luma', + chunks: 'async', + priority: 35, + enforce: true, + reuseExistingChunk: true, + }, + webgpuSupport: { + test: /[\\/]node_modules[\\/]wgsl_reflect[\\/]/, + name: 'vendor-webgpu-support', + chunks: 'async', + priority: 34, + enforce: true, + reuseExistingChunk: true, + }, + mapData: { + test: /[\\/]node_modules[\\/](?:@mapbox[\\/]tiny-sdf|@protomaps[\\/]basemaps|earcut|supercluster)[\\/]/, + name: 'vendor-map-data', + chunks: 'async', + priority: 33, + enforce: true, + reuseExistingChunk: true, + }, + mapSupport: { + test: /[\\/]node_modules[\\/](?:@loaders\.gl|@math\.gl|@probe\.gl|@vis\.gl|mjolnir\.js|react-map-gl)[\\/]/, + name: 'vendor-map-support', + chunks: 'async', + priority: 30, + enforce: true, + reuseExistingChunk: true, + }, + joyride: { + test: /[\\/]node_modules[\\/](?:react-joyride|deepmerge|scroll|scrollparent|react-innertext)[\\/]/, + name: 'vendor-joyride', + chunks: 'async', + priority: 25, + enforce: true, + reuseExistingChunk: true, + }, + }, + }, + } + : undefined, devServer: { host: '0.0.0.0', port: 3001, diff --git a/pipeline/check_travel_times.py b/pipeline/check_travel_times.py index 561d310..79683e2 100644 --- a/pipeline/check_travel_times.py +++ b/pipeline/check_travel_times.py @@ -81,11 +81,7 @@ def find_bad_files( bad: list[BadFile] = [] stats: dict[str, dict] = {} - modes = sorted( - d - for d in os.listdir(base_dir) - if (base_dir / d).is_dir() - ) + modes = sorted(d for d in os.listdir(base_dir) if (base_dir / d).is_dir()) for mode in modes: mode_dir = base_dir / mode @@ -149,7 +145,9 @@ def find_duplicates(base_dir: Path) -> tuple[list[BadFile], dict[str, dict]]: # Keep the file with the most rows files.sort(key=lambda x: x[1], reverse=True) for filename, rows in files[1:]: - dupes.append(BadFile(mode=mode, filename=filename, slug=slug, rows=rows)) + dupes.append( + BadFile(mode=mode, filename=filename, slug=slug, rows=rows) + ) mode_dupes += 1 duped_slugs = sum(1 for fs in slug_files.values() if len(fs) > 1) @@ -197,7 +195,9 @@ def main() -> None: bad_files, stats = find_bad_files(args.travel_times, args.threshold_pct) print("=== Per-mode summary ===\n") - print(f"{'Mode':<10} {'Total':>6} {'Bad':>5} {'Threshold':>10} {'Median':>8} {'Range':>20}") + print( + f"{'Mode':<10} {'Total':>6} {'Bad':>5} {'Threshold':>10} {'Median':>8} {'Range':>20}" + ) print("-" * 65) for mode, s in sorted(stats.items()): rng = f"{s['min']:,}–{s['max']:,}" @@ -231,7 +231,9 @@ def main() -> None: total_removable = sum(s["removable"] for s in dupe_stats.values()) if total_removable > 0: print(f"\n=== Duplicates ({total_removable} removable files) ===\n") - print(f"{'Mode':<10} {'Total':>6} {'Unique':>7} {'Duped slugs':>12} {'Removable':>10}") + print( + f"{'Mode':<10} {'Total':>6} {'Unique':>7} {'Duped slugs':>12} {'Removable':>10}" + ) print("-" * 50) for mode, s in sorted(dupe_stats.items()): if s["removable"] > 0: @@ -242,9 +244,15 @@ def main() -> None: if args.dedup: # Exclude files already deleted by --delete - deleted_set = {(bf.mode, bf.filename) for bf in bad_files} if args.delete else set() - to_delete = [df for df in dupe_files if (df.mode, df.filename) not in deleted_set] - print(f"\nRemoving {len(to_delete)} duplicate files (keeping largest per slug)...") + deleted_set = ( + {(bf.mode, bf.filename) for bf in bad_files} if args.delete else set() + ) + to_delete = [ + df for df in dupe_files if (df.mode, df.filename) not in deleted_set + ] + print( + f"\nRemoving {len(to_delete)} duplicate files (keeping largest per slug)..." + ) deleted = _delete_files(args.travel_times, to_delete) print(f"Deleted {deleted}/{len(to_delete)} files.") else: diff --git a/pipeline/download/geolytix_retail_points.py b/pipeline/download/geolytix_retail_points.py index 97ee72d..3520105 100644 --- a/pipeline/download/geolytix_retail_points.py +++ b/pipeline/download/geolytix_retail_points.py @@ -42,9 +42,7 @@ def select_latest_csv_name(names: list[str]) -> str: match = CSV_NAME_RE.match(path.name) if not match: continue - candidates.append( - (match.group("release"), int(match.group("version")), name) - ) + candidates.append((match.group("release"), int(match.group("version")), name)) if not candidates: raise ValueError("No root-level GEOLYTIX retail points CSV found") diff --git a/pipeline/download/map_assets.py b/pipeline/download/map_assets.py index 5bc8dae..1de7d84 100644 --- a/pipeline/download/map_assets.py +++ b/pipeline/download/map_assets.py @@ -9,6 +9,7 @@ from pipeline.transform.transform_poi import NAPTAN_EMOJIS, _CATEGORIES GLYPHS_BASE = "https://protomaps.github.io/basemaps-assets/fonts" SPRITES_BASE = "https://protomaps.github.io/basemaps-assets/sprites/v4" TWEMOJI_BASE = "https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72" +POI_ICON_BASE = "https://geolytix.github.io/MapIcons" # Font stacks used by @protomaps/basemaps with lang='en' FONT_STACKS = ["Noto Sans Regular", "Noto Sans Italic", "Noto Sans Medium"] @@ -16,6 +17,50 @@ FONT_STACKS = ["Noto Sans Regular", "Noto Sans Italic", "Noto Sans Medium"] # Fallback emoji not in any category _FALLBACK_EMOJIS = ["📍"] +POI_ICON_PATHS = [ + "asda/asda_express_24px.svg", + "asda/asda_green_basket_24px.svg", + "asda/asda_green_trolley_24px.svg", + "asda/asda_living_24px.svg", + "asda/asda_pfs_24px.svg", + "asda/asda_primary.svg", + "asda/asda_superstore_green_trolley_24px.svg", + "brands/aldi_24px.svg", + "brands/amazon_fresh_alt_24px.svg", + "brands/booths_24px.svg", + "brands/budgens_24px.svg", + "brands/centra_24px.svg", + "brands/cook.svg", + "brands/coop_24px.svg", + "brands/costco_24px.svg", + "brands/dunnes_stores_24px.svg", + "brands/farmfoods_updated_24px.svg", + "brands/heron_24px.svg", + "brands/iceland_24px.svg", + "brands/iceland_food_warehouse_24px.svg", + "brands/lidl_24px.svg", + "brands/little_waitrose_24px.svg", + "brands/makro_24px.svg", + "brands/mns_24px.svg", + "brands/mns_food_24px.svg", + "brands/mns_high_street_24px.svg", + "brands/mns_hospital_24px.svg", + "brands/mns_moto_24px.svg", + "brands/mns_outlet_24px.svg", + "brands/morrisons_24px.svg", + "brands/morrisons_daily_24px.svg", + "brands/sainsburys_24px.svg", + "brands/sainsburys_local_24px.svg", + "brands/spar_24px.svg", + "brands/tesco_24px.svg", + "brands/tesco_express_24px.svg", + "brands/tesco_extra_24px.svg", + "brands/waitrose_24px.svg", + "brands/wholefoods_24px.svg", + "logos/planet_organic_24px.svg", + "public_transport/london_tube.svg", +] + def collect_twemoji_codes() -> list[str]: """Derive twemoji hex codes from transform_poi categories. @@ -93,6 +138,12 @@ def main(): url = f"{TWEMOJI_BASE}/{code}.png" tasks.append((url, twemoji_dir / f"{code}.png")) + # Branded POI icons are served from this local bundle at runtime. + poi_icons_dir = out / "poi-icons" + for icon_path in POI_ICON_PATHS: + url = f"{POI_ICON_BASE}/{icon_path}" + tasks.append((url, poi_icons_dir / icon_path)) + # Skip already-downloaded files remaining = [(url, dest) for url, dest in tasks] diff --git a/pipeline/download/median_age.py b/pipeline/download/median_age.py index 1e262b1..4471ce8 100644 --- a/pipeline/download/median_age.py +++ b/pipeline/download/median_age.py @@ -23,24 +23,24 @@ PAGE_SIZE = 25000 # Five-year age bands in order, with lower bounds for interpolation. # The last band (85+) is open-ended — we treat it as 85-89 for median purposes. AGE_BANDS = [ - (0, 5), # Aged 0 to 4 years - (5, 5), # Aged 5 to 9 years - (10, 5), # Aged 10 to 14 years - (15, 5), # Aged 15 to 19 years - (20, 5), # Aged 20 to 24 years - (25, 5), # Aged 25 to 29 years - (30, 5), # Aged 30 to 34 years - (35, 5), # Aged 35 to 39 years - (40, 5), # Aged 40 to 44 years - (45, 5), # Aged 45 to 49 years - (50, 5), # Aged 50 to 54 years - (55, 5), # Aged 55 to 59 years - (60, 5), # Aged 60 to 64 years - (65, 5), # Aged 65 to 69 years - (70, 5), # Aged 70 to 74 years - (75, 5), # Aged 75 to 79 years - (80, 5), # Aged 80 to 84 years - (85, 5), # Aged 85 years and over + (0, 5), # Aged 0 to 4 years + (5, 5), # Aged 5 to 9 years + (10, 5), # Aged 10 to 14 years + (15, 5), # Aged 15 to 19 years + (20, 5), # Aged 20 to 24 years + (25, 5), # Aged 25 to 29 years + (30, 5), # Aged 30 to 34 years + (35, 5), # Aged 35 to 39 years + (40, 5), # Aged 40 to 44 years + (45, 5), # Aged 45 to 49 years + (50, 5), # Aged 50 to 54 years + (55, 5), # Aged 55 to 59 years + (60, 5), # Aged 60 to 64 years + (65, 5), # Aged 65 to 69 years + (70, 5), # Aged 70 to 74 years + (75, 5), # Aged 75 to 79 years + (80, 5), # Aged 80 to 84 years + (85, 5), # Aged 85 years and over ] @@ -110,14 +110,18 @@ def download_and_convert(output_path: Path) -> None: for row in rows: counts = [row[col] for col in band_cols] median = compute_median_age(counts) - medians.append({"lsoa21": row["GEOGRAPHY_CODE"], "median_age": round(median, 1)}) + medians.append( + {"lsoa21": row["GEOGRAPHY_CODE"], "median_age": round(median, 1)} + ) result = pl.DataFrame(medians).with_columns( pl.col("median_age").cast(pl.Float32), ) print(f"England LSOAs: {result.height}") - print(f"Median age range: {result['median_age'].min()} - {result['median_age'].max()}") + print( + f"Median age range: {result['median_age'].min()} - {result['median_age'].max()}" + ) print(f"Mean of medians: {result['median_age'].mean():.1f}") output_path.parent.mkdir(parents=True, exist_ok=True) diff --git a/pipeline/download/naptan.py b/pipeline/download/naptan.py index 094cbef..6c80de5 100644 --- a/pipeline/download/naptan.py +++ b/pipeline/download/naptan.py @@ -2,12 +2,17 @@ import argparse import io +import math +import re import urllib.request +from dataclasses import dataclass from pathlib import Path import polars as pl NAPTAN_CSV_URL = "https://naptan.api.dft.gov.uk/v1/access-nodes?dataFormat=csv" +TUBE_STATION_CATEGORY = "Tube station" +TUBE_STATION_MERGE_RADIUS_DEGREES = 0.01 STOP_TYPES = { @@ -25,6 +30,41 @@ STOP_TYPES = { OUTPUT_COLUMNS = ["id", "name", "category", "lat", "lng"] +def canonical_station_name(name: str | None) -> str: + """Normalize station names so entrances/transport-mode variants collapse.""" + if not name: + return "" + + normalized = name.lower() + normalized = re.sub(r"\([^)]*\)", " ", normalized) + normalized = re.sub(r"['’`]", "", normalized) + normalized = normalized.replace("&", " and ") + normalized = re.sub(r"[^a-z0-9]+", " ", normalized) + words = normalized.split() + + suffixes = ( + ("underground", "station"), + ("tube", "station"), + ("dlr", "station"), + ("metro", "station"), + ("tram", "stop"), + ("rail", "station"), + ("railway", "station"), + ("station",), + ("stop",), + ) + while True: + suffix = next( + (suffix for suffix in suffixes if words[-len(suffix) :] == list(suffix)), + None, + ) + if suffix is None: + break + del words[-len(suffix) :] + + return " ".join(words) + + def canonical_station_name_expr(name_col: str = "name") -> pl.Expr: """Normalize station names so entrances/transport-mode variants collapse.""" expr = pl.col(name_col).str.to_lowercase() @@ -45,67 +85,158 @@ def _has_locality() -> pl.Expr: return pl.col("locality").is_not_null() & (pl.col("locality") != "") -def _deduplicate_tube_partition( - df: pl.DataFrame, group_cols: list[str] -) -> pl.DataFrame: - if len(df) == 0: - return pl.DataFrame( - { - "id": pl.Series([], dtype=pl.String), - "name": pl.Series([], dtype=pl.String), - "category": pl.Series([], dtype=pl.String), - "lat": pl.Series([], dtype=pl.Float64), - "lng": pl.Series([], dtype=pl.Float64), - } - ) - - name_len = pl.col("name").str.len_chars() - return ( - df.group_by(group_cols) - .agg( - pl.col("id").sort_by(name_len).first(), - pl.col("name").sort_by(name_len).first(), - pl.col("category").first(), - pl.col("lat").mean(), - pl.col("lng").mean(), - ) - .select(OUTPUT_COLUMNS) +def _empty_output_frame() -> pl.DataFrame: + return pl.DataFrame( + { + "id": pl.Series([], dtype=pl.String), + "name": pl.Series([], dtype=pl.String), + "category": pl.Series([], dtype=pl.String), + "lat": pl.Series([], dtype=pl.Float64), + "lng": pl.Series([], dtype=pl.Float64), + } ) +def station_name_score(name: str) -> tuple[int, int]: + lower = name.lower() + suffix_penalty = int( + lower.endswith( + ( + " underground station", + " tube station", + " dlr station", + " metro station", + " tram stop", + " station", + " stop", + ) + ) + ) + return (suffix_penalty, len(name)) + + +@dataclass +class StationAccumulator: + id: str + name: str + category: str + lat_sum: float + lng_sum: float + count: int = 1 + + @property + def lat(self) -> float: + return self.lat_sum / self.count + + @property + def lng(self) -> float: + return self.lng_sum / self.count + + def same_area(self, lat: float, lng: float) -> bool: + dlat = self.lat - lat + dlng = (self.lng - lng) * math.cos(math.radians(self.lat)) + return (dlat * dlat + dlng * dlng) <= TUBE_STATION_MERGE_RADIUS_DEGREES**2 + + def merge(self, row: dict[str, object]) -> None: + self.lat_sum += float(row["lat"]) + self.lng_sum += float(row["lng"]) + self.count += 1 + + name = str(row["name"] or "") + if station_name_score(name) < station_name_score(self.name): + self.id = str(row["id"] or "") + self.name = name + + +def _station_from_row(row: dict[str, object]) -> StationAccumulator: + return StationAccumulator( + id=str(row["id"] or ""), + name=str(row["name"] or ""), + category=str(row["category"] or ""), + lat_sum=float(row["lat"]), + lng_sum=float(row["lng"]), + ) + + +def _deduplicate_tube_stations(df: pl.DataFrame) -> pl.DataFrame: + if len(df) == 0: + return _empty_output_frame() + + selected: list[StationAccumulator] = [] + groups: dict[str, list[int]] = {} + + for row in df.iter_rows(named=True): + station_key = canonical_station_name(str(row["name"] or "")) + if not station_key: + selected.append(_station_from_row(row)) + continue + + existing = next( + ( + index + for index in groups.get(station_key, []) + if selected[index].same_area(float(row["lat"]), float(row["lng"])) + ), + None, + ) + if existing is not None: + selected[existing].merge(row) + continue + + index = len(selected) + selected.append(_station_from_row(row)) + groups.setdefault(station_key, []).append(index) + + return pl.DataFrame( + { + "id": [station.id for station in selected], + "name": [station.name for station in selected], + "category": [station.category for station in selected], + "lat": [station.lat for station in selected], + "lng": [station.lng for station in selected], + } + ).select(OUTPUT_COLUMNS) + + +def _deduplicate_non_tube_stops(df: pl.DataFrame) -> pl.DataFrame: + if len(df) == 0: + return _empty_output_frame() + + has_loc = df.filter(_has_locality()) + no_loc = df.filter(~_has_locality()) + + # First pass: one record per exact stop name/category/locality. + frames = [] + if len(has_loc) > 0: + frames.append( + has_loc.group_by("name", "category", "locality") + .agg( + pl.col("id").first(), + pl.col("lat").mean(), + pl.col("lng").mean(), + ) + .select(OUTPUT_COLUMNS) + ) + if len(no_loc) > 0: + frames.append(no_loc.select(OUTPUT_COLUMNS)) + + if not frames: + return _empty_output_frame() + + return pl.concat(frames).select(OUTPUT_COLUMNS) + + def deduplicate_naptan(df: pl.DataFrame) -> pl.DataFrame: - """Deduplicate NaPTAN stops, with stricter station-level merging for Tube POIs.""" - has_loc = df.filter(_has_locality()) - no_loc = df.filter(~_has_locality()) - cols_with_locality = [*OUTPUT_COLUMNS, "locality"] + """Deduplicate NaPTAN stops, with station-level merging for Tube POIs.""" + tube = df.filter(pl.col("category") == TUBE_STATION_CATEGORY) + other = df.filter(pl.col("category") != TUBE_STATION_CATEGORY) - # First pass: one record per exact stop name/category/locality. - deduped_has_loc = ( - has_loc.group_by("name", "category", "locality") - .agg( - pl.col("id").first(), - pl.col("lat").mean(), - pl.col("lng").mean(), - ) - .select(cols_with_locality) - ) - df = pl.concat([deduped_has_loc, no_loc.select(cols_with_locality)]) - - tube = df.filter(pl.col("category") == "Tube station").with_columns( - canonical_station_name_expr().alias("_station_key") - ) - other = df.filter(pl.col("category") != "Tube station") - - tube_with_loc = tube.filter(_has_locality()) - tube_no_loc = tube.filter(~_has_locality()) - deduped_tube = pl.concat( + return pl.concat( [ - _deduplicate_tube_partition(tube_with_loc, ["_station_key", "locality"]), - _deduplicate_tube_partition(tube_no_loc, ["_station_key"]), + _deduplicate_non_tube_stops(other), + _deduplicate_tube_stations(tube), ] - ) - - return pl.concat([other.select(OUTPUT_COLUMNS), deduped_tube]) + ).select(OUTPUT_COLUMNS) def download_naptan(output: Path) -> None: @@ -140,7 +271,7 @@ def download_naptan(output: Path) -> None: print( f"Deduplicated {before:,} → {len(df):,} stops " - "(by name+category+locality; tube stations by normalized station name)" + "(by name+category+locality; tube stations by normalized name+area)" ) df.write_parquet(output) diff --git a/pipeline/download/places.py b/pipeline/download/places.py index bab0fdf..e2a24b8 100644 --- a/pipeline/download/places.py +++ b/pipeline/download/places.py @@ -1,7 +1,7 @@ """Extract place=* nodes and railway stations from OSM PBF → data/places.parquet. -Extracts named place nodes (cities, towns, suburbs, etc.) and railway stations -(tube, national rail, DLR, etc.) for typeahead search. +Extracts named place nodes and railway stations (tube, national rail, DLR, +etc.) for typeahead search. Reuses the same england-latest.osm.pbf as pois.py. """ @@ -21,7 +21,22 @@ from pipeline.utils.england_geometry import ( load_england_polygon, ) -PLACE_TYPES = {"city"} +# Search can use a wider set of OSM place nodes, but travel-time destinations +# must remain restricted to the historical city/station origin set. +SEARCH_PLACE_TYPES = { + "city", + "town", + "village", + "suburb", + "neighbourhood", + "quarter", + "borough", + "locality", + "hamlet", + "isolated_dwelling", + "island", +} +TRAVEL_DESTINATION_PLACE_TYPES = {"city"} # Suffixes to strip from raw station names before appending the typed suffix. _STATION_STRIP = ( @@ -71,7 +86,13 @@ class PlaceHandler(osmium.SimpleHandler): self._england = england_polygon def _add( - self, name: str, place_type: str, lat: float, lon: float, population: int + self, + name: str, + place_type: str, + lat: float, + lon: float, + population: int, + travel_destination: bool, ) -> None: self.places.append( { @@ -80,6 +101,7 @@ class PlaceHandler(osmium.SimpleHandler): "lat": lat, "lon": lon, "population": population, + "travel_destination": travel_destination, } ) self._progress.set_postfix(places=f"{len(self.places):,}", refresh=False) @@ -107,10 +129,17 @@ class PlaceHandler(osmium.SimpleHandler): except ValueError: population = 0 - # place=* nodes (cities, towns, suburbs, etc.) + # place=* nodes place_type = n.tags.get("place") - if place_type in PLACE_TYPES: - self._add(name, place_type, lat, lon, population) + if place_type in SEARCH_PLACE_TYPES: + self._add( + name, + place_type, + lat, + lon, + population, + travel_destination=place_type in TRAVEL_DESTINATION_PLACE_TYPES, + ) return # Railway stations (tube, national rail, DLR, overground, Elizabeth line) @@ -126,7 +155,14 @@ class PlaceHandler(osmium.SimpleHandler): ): return display_name = _station_display_name(name, tags) - self._add(display_name, "station", lat, lon, population) + self._add( + display_name, + "station", + lat, + lon, + population, + travel_destination=True, + ) return @@ -147,7 +183,7 @@ def main() -> None: pbf_file = args.pbf england_polygon = load_england_polygon(args.boundary) - print("Extracting place nodes: cities + railway stations") + print("Extracting search place nodes + railway stations") with tqdm( unit=" elements", unit_scale=True, diff --git a/pipeline/download/rental_prices.py b/pipeline/download/rental_prices.py index e44c82e..ccb6735 100644 --- a/pipeline/download/rental_prices.py +++ b/pipeline/download/rental_prices.py @@ -43,9 +43,7 @@ def convert_to_parquet(xlsx_path: Path, parquet_path: Path) -> None: # Filter to English local authorities df = df.filter( - pl.any_horizontal( - pl.col("area_code").str.starts_with(p) for p in LA_PREFIXES - ) + pl.any_horizontal(pl.col("area_code").str.starts_with(p) for p in LA_PREFIXES) ) # Use only the latest month diff --git a/pipeline/download/test_naptan.py b/pipeline/download/test_naptan.py index 14f0ced..5c5875e 100644 --- a/pipeline/download/test_naptan.py +++ b/pipeline/download/test_naptan.py @@ -1,19 +1,24 @@ import polars as pl import pytest -from pipeline.download.naptan import canonical_station_name_expr, deduplicate_naptan +from pipeline.download.naptan import ( + canonical_station_name, + canonical_station_name_expr, + deduplicate_naptan, +) def test_canonical_station_name_expr_normalizes_transport_suffixes(): + names = [ + "Bank", + "Bank Underground Station", + "Bank DLR Station", + "Pleasure Beach (Blackpool Tramway)", + "Earl's Court Tube Station", + ] df = pl.DataFrame( { - "name": [ - "Bank", - "Bank Underground Station", - "Bank DLR Station", - "Pleasure Beach (Blackpool Tramway)", - "Earl's Court Tube Station", - ] + "name": names, } ) @@ -26,30 +31,45 @@ def test_canonical_station_name_expr_normalizes_transport_suffixes(): "pleasure beach", "earls court", ] + assert [canonical_station_name(name) for name in names] == result -def test_deduplicate_naptan_merges_tube_station_variants_by_locality(): +def test_deduplicate_naptan_merges_tube_station_variants_by_area(): df = pl.DataFrame( { - "id": ["bank", "bank-lu", "bank-dlr", "other-bank"], + "id": [ + "bank", + "bank-lu", + "bank-dlr", + "other-bank", + "central-a", + "central-b", + ], "name": [ "Bank", "Bank Underground Station", "Bank DLR Station", "Bank Underground Station", + "Central Tube Station", + "Central Tube Station", ], - "category": ["Tube station"] * 4, - "lat": [51.5129, 51.5134, 51.5132, 55.0140], - "lng": [-0.0889, -0.0890, -0.0885, -1.6781], - "locality": ["LOC1", "LOC1", "LOC1", "LOC2"], + "category": ["Tube station"] * 6, + "lat": [51.5129, 51.5134, 51.5132, 55.0140, 51.5, 53.0], + "lng": [-0.0889, -0.0890, -0.0885, -1.6781, -0.1, -2.0], + "locality": ["LOC1", "LOC1", "LOC2", "LOC1", None, None], } ) result = deduplicate_naptan(df).sort("lat") - assert len(result) == 2 - assert result["name"].to_list() == ["Bank", "Bank Underground Station"] - assert result["lat"].to_list()[0] == pytest.approx( + assert len(result) == 4 + assert result["name"].to_list() == [ + "Central Tube Station", + "Bank", + "Central Tube Station", + "Bank Underground Station", + ] + assert result.filter(pl.col("name") == "Bank")["lat"][0] == pytest.approx( (51.5129 + 51.5134 + 51.5132) / 3 ) diff --git a/pipeline/download/tiles.py b/pipeline/download/tiles.py index 1fece9c..a8c0b2f 100644 --- a/pipeline/download/tiles.py +++ b/pipeline/download/tiles.py @@ -23,7 +23,9 @@ def find_latest_build() -> str: for i in range(MAX_AGE_DAYS): d = today - timedelta(days=i) url = f"{PROTOMAPS_BASE}/{d:%Y%m%d}.pmtiles" - req = urllib.request.Request(url, method="HEAD", headers={"User-Agent": USER_AGENT}) + req = urllib.request.Request( + url, method="HEAD", headers={"User-Agent": USER_AGENT} + ) try: urllib.request.urlopen(req) print(f"Found build: {d:%Y%m%d}") diff --git a/pipeline/transform/join_epc_pp.py b/pipeline/transform/join_epc_pp.py index f0aba76..2f90389 100644 --- a/pipeline/transform/join_epc_pp.py +++ b/pipeline/transform/join_epc_pp.py @@ -128,9 +128,7 @@ def main(): # Social tenure fork: flag properties that were ever social housing social_tenure = ( - epc_base.filter( - pl.col("TENURE").str.to_lowercase().str.contains("social") - ) + epc_base.filter(pl.col("TENURE").str.to_lowercase().str.contains("social")) .select("epc_address", "POSTCODE") .unique() .with_columns(pl.lit("Yes").alias("was_council_house")) @@ -139,16 +137,20 @@ def main(): print(f"Former council houses (EPC social tenure): {social_tenure.height}") # Left-join events and social tenure back onto dedup EPC - epc = epc.join( - events.lazy(), - on=["epc_address", "POSTCODE"], - how="left", - ).join( - social_tenure.lazy(), - on=["epc_address", "POSTCODE"], - how="left", - ).with_columns( - pl.col("was_council_house").fill_null("No"), + epc = ( + epc.join( + events.lazy(), + on=["epc_address", "POSTCODE"], + how="left", + ) + .join( + social_tenure.lazy(), + on=["epc_address", "POSTCODE"], + how="left", + ) + .with_columns( + pl.col("was_council_house").fill_null("No"), + ) ) print("EPC dataset") diff --git a/pipeline/transform/merge.py b/pipeline/transform/merge.py index 8d5a447..8b485a2 100644 --- a/pipeline/transform/merge.py +++ b/pipeline/transform/merge.py @@ -13,13 +13,12 @@ _AREA_COLUMNS = [ "lat", "lon", # Deprivation - "Income Score (rate)", - "Employment Score (rate)", + "Income Score", + "Employment Score", "Education, Skills and Training Score", "Health Deprivation and Disability Score", - "Living Environment Score", - "Indoors Sub-domain Score", - "Outdoors Sub-domain Score", + "Housing Conditions Score", + "Air Quality and Road Safety Score", # Ethnicity "% South Asian", "% East Asian", @@ -144,7 +143,6 @@ def _build( "Income Score (rate)", "Employment Score (rate)", "Health Deprivation and Disability Score", - "Living Environment Score", "Indoors Sub-domain Score", "Outdoors Sub-domain Score", ] @@ -319,6 +317,7 @@ def _build( "Adult Skills Sub-domain Score", "Children and Young People Sub-domain Score", "Crime Score", + "Living Environment Score", "Index of Multiple Deprivation (IMD) Score", "Income Deprivation Affecting Older People (IDAOPI) Score (rate)", "Income Deprivation Affecting Children Index (IDACI) Score (rate)", @@ -335,6 +334,10 @@ def _build( "date_of_transfer": "Date of last transaction", "construction_age_band": "Construction year", "is_construction_date_approximate": "Is construction date approximate", + "Income Score (rate)": "Income Score", + "Employment Score (rate)": "Employment Score", + "Indoors Sub-domain Score": "Housing Conditions Score", + "Outdoors Sub-domain Score": "Air Quality and Road Safety Score", "pp_address": "Address per Property Register", "epc_address": "Address per EPC", "postcode": "Postcode", diff --git a/pipeline/transform/test_transform_poi.py b/pipeline/transform/test_transform_poi.py index 6708e86..2d14716 100644 --- a/pipeline/transform/test_transform_poi.py +++ b/pipeline/transform/test_transform_poi.py @@ -17,11 +17,14 @@ def test_transform_grocery_retail_points_outputs_chain_categories(): pois = transform_grocery_retail_points(raw) - assert pois.select("id", "name", "category", "group", "emoji").to_dicts() == [ + assert pois.select( + "id", "name", "category", "icon_category", "group", "emoji" + ).to_dicts() == [ { "id": "glx-101", "name": "Waitrose Test", "category": "Waitrose", + "icon_category": "Waitrose", "group": "Groceries", "emoji": "🛒", }, @@ -29,6 +32,7 @@ def test_transform_grocery_retail_points_outputs_chain_categories(): "id": "glx-102", "name": "Sainsbury's Test", "category": "Sainsbury's", + "icon_category": "Sainsbury's Local", "group": "Groceries", "emoji": "🛒", }, @@ -36,12 +40,45 @@ def test_transform_grocery_retail_points_outputs_chain_categories(): "id": "glx-103", "name": "Co-op Test", "category": "Co-op", + "icon_category": "Co-op", "group": "Groceries", "emoji": "🛒", }, ] +def test_transform_grocery_retail_points_keeps_fascia_icon_category(): + raw = pl.DataFrame( + { + "id": [101, 102, 103, 104], + "retailer": ["Tesco", "Iceland", "Waitrose", "Morrisons"], + "fascia": [ + "Tesco Express Esso", + "The Food Warehouse", + "Little Waitrose Shell", + "Morrisons Daily", + ], + "store_name": [ + "Tesco Test Express", + "Iceland Test Food Warehouse", + "Little Waitrose Test", + "Morrisons Daily Test", + ], + "long_wgs": [-0.141, -0.142, -0.143, -0.144], + "lat_wgs": [51.515, 51.516, 51.517, 51.518], + } + ) + + pois = transform_grocery_retail_points(raw) + + assert pois.select("category", "icon_category").to_dicts() == [ + {"category": "Tesco", "icon_category": "Tesco Express"}, + {"category": "Iceland", "icon_category": "The Food Warehouse"}, + {"category": "Waitrose", "icon_category": "Little Waitrose"}, + {"category": "Morrisons", "icon_category": "Morrisons Daily"}, + ] + + def test_transform_grocery_retail_points_drops_invalid_rows(): raw = pl.DataFrame( { diff --git a/pipeline/transform/transform_poi.py b/pipeline/transform/transform_poi.py index 88cdf81..831d6d2 100644 --- a/pipeline/transform/transform_poi.py +++ b/pipeline/transform/transform_poi.py @@ -1086,12 +1086,57 @@ GROCERY_RETAILER_DISPLAY_NAMES: dict[str, str] = { } +GROCERY_FASCIA_ICON_NAMES: dict[str, str] = { + "Aldi Local": "Aldi", + "Asda Express": "Asda Express", + "Asda Living": "Asda Living", + "Asda PFS": "Asda PFS", + "Cooltrader": "Heron Foods", + "Co-op Food": "Co-op", + "Cook": "COOK", + "Eurospar": "Spar", + "Eurospar PFS": "Spar", + "Heron": "Heron Foods", + "Little Waitrose": "Little Waitrose", + "Little Waitrose Shell": "Little Waitrose", + "Marks and Spencer": "M&S", + "Marks and Spencer BP": "M&S Food", + "Marks and Spencer Clothing": "M&S Clothing", + "Marks and Spencer Food To Go": "M&S Food", + "Marks and Spencer Food Outlet": "M&S Outlet", + "Marks and Spencer Foodhall": "M&S Food", + "Marks and Spencer Hospital": "M&S Hospital", + "Marks and Spencer MSA": "M&S MSA", + "Marks and Spencer Outlet": "M&S Outlet", + "Marks and Spencer Simply Food": "M&S Food", + "Marks and Spencer Travel SF": "M&S Food", + "Morrisons Daily": "Morrisons Daily", + "Morrisons Select": "Morrisons", + "Sainsburys": "Sainsbury's", + "Sainsburys Local": "Sainsbury's Local", + "Spar PFS": "Spar", + "Tesco Express": "Tesco Express", + "Tesco Express Esso": "Tesco Express", + "Tesco Extra": "Tesco Extra", + "The Co-operative Food": "Co-op", + "The Co-operative Food PFS": "Co-op", + "The Food Warehouse": "The Food Warehouse", + "Waitrose MSA": "Waitrose", +} + + def normalize_grocery_retailer(retailer: str | None) -> str: if retailer is None: return "" return GROCERY_RETAILER_DISPLAY_NAMES.get(retailer, retailer) +def normalize_grocery_icon_category(fascia: str | None, retailer: str | None) -> str: + if fascia: + return GROCERY_FASCIA_ICON_NAMES.get(fascia, normalize_grocery_retailer(fascia)) + return normalize_grocery_retailer(retailer) + + def transform_grocery_retail_points( grocery_df: pl.DataFrame, boundary_path: Path | None = None, @@ -1100,9 +1145,7 @@ def transform_grocery_retail_points( required = {"id", "retailer", "fascia", "store_name", "long_wgs", "lat_wgs"} missing = required - set(grocery_df.columns) if missing: - raise ValueError( - f"GEOLYTIX retail points missing columns: {sorted(missing)}" - ) + raise ValueError(f"GEOLYTIX retail points missing columns: {sorted(missing)}") df = ( grocery_df.select( @@ -1133,9 +1176,15 @@ def transform_grocery_retail_points( pl.col("retailer") .map_elements(normalize_grocery_retailer, return_dtype=pl.String) .alias("category"), + pl.struct(["fascia", "retailer"]) + .map_elements( + lambda row: normalize_grocery_icon_category(row["fascia"], row["retailer"]), + return_dtype=pl.String, + ) + .alias("icon_category"), pl.lit("Groceries").alias("group"), pl.lit("🛒").alias("emoji"), - ).select("id", "name", "category", "group", "lat", "lng", "emoji") + ).select("id", "name", "category", "icon_category", "group", "lat", "lng", "emoji") def transform( @@ -1189,6 +1238,7 @@ def transform( lf = lf.with_columns( pl.col("category").replace_strict(group_mapping).alias("group"), pl.col("category").replace_strict(name_mapping).alias("category"), + pl.col("category").replace_strict(name_mapping).alias("icon_category"), pl.col("category").replace_strict(emoji_mapping).alias("emoji"), ) @@ -1203,6 +1253,7 @@ def transform( naptan = naptan_df.lazy().with_columns( pl.col("category").replace_strict(NAPTAN_EMOJIS).alias("emoji"), pl.lit("Public Transport").alias("group"), + pl.col("category").alias("icon_category"), ) frames = [lf, naptan] diff --git a/pyproject.toml b/pyproject.toml index bf7a91d..23fa328 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ dependencies = [ "rasterio>=1.5.0", "pyproj>=3.7.2", "pyshp>=2.3.0", + "pillow>=12.0.0", "folium>=0.20.0", "httpx", "polars", @@ -41,8 +42,8 @@ dev = [ ] [tool.deptry] -# analyses/ and scripts/ use transitive deps -exclude = ["\\.venv", "analyses", "scripts"] +# analyses/ and scripts/ use transitive deps; video/tts has its own UV project. +exclude = ["\\.venv", "analyses", "scripts", "video/tts"] [tool.deptry.per_rule_ignores] # pyarrow/fastexcel: runtime backends for polars parquet/Excel I/O diff --git a/r5-java/src/main/java/propertymap/App.java b/r5-java/src/main/java/propertymap/App.java index f0b6bdd..cdec421 100644 --- a/r5-java/src/main/java/propertymap/App.java +++ b/r5-java/src/main/java/propertymap/App.java @@ -65,7 +65,7 @@ public class App { String[] originNames = places.names(); double[] originLats = places.lats(), originLons = places.lons(); int nOrigins = originLats.length; - System.err.printf(" %,d places (total)%n", nOrigins); + System.err.printf(" %,d travel-eligible places%n", nOrigins); // Filter places to England only (must be near at least one England postcode) Set englandIndices = filterEnglandPlaces( @@ -89,7 +89,7 @@ public class App { System.err.printf("DEMO MODE: %d places (transit only)%n", originIndices.length); for (int i : originIndices) System.err.printf(" - %s%n", originNames[i]); } else { - // Normal mode: use all England places + // Normal mode: use all travel-eligible England places originIndices = englandIndices.stream().sorted() .mapToInt(Integer::intValue).toArray(); modes = MODES; diff --git a/r5-java/src/main/java/propertymap/Parquet.java b/r5-java/src/main/java/propertymap/Parquet.java index 72f4c03..cb16f6c 100644 --- a/r5-java/src/main/java/propertymap/Parquet.java +++ b/r5-java/src/main/java/propertymap/Parquet.java @@ -59,9 +59,15 @@ public class Parquet { /** Load places deduplicated by lat/lon, write reference parquet, return names + flat lat/lon arrays. */ static Places loadPlaces(String parquetPath, Path refOut) throws Exception { try (DuckDBConnection conn = connect(); Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE all_places AS SELECT * FROM read_parquet('" + + escapePath(parquetPath) + "')"); + boolean hasTravelDestination = tableHasColumn(stmt, "all_places", "travel_destination"); + String source = hasTravelDestination + ? "(SELECT * FROM all_places WHERE COALESCE(travel_destination, true))" + : "all_places"; stmt.execute("CREATE TABLE places AS SELECT * EXCLUDE (rn) FROM (" + "SELECT *, ROW_NUMBER() OVER (PARTITION BY lat, lon) AS rn " - + "FROM read_parquet('" + escapePath(parquetPath) + "')) WHERE rn = 1"); + + "FROM " + source + " AS p) WHERE rn = 1"); copyToParquet(stmt, "SELECT * FROM places", refOut); try (ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM places")) { @@ -85,6 +91,17 @@ public class Parquet { } } + private static boolean tableHasColumn(Statement stmt, String tableName, String columnName) + throws Exception { + try (ResultSet rs = stmt.executeQuery( + "SELECT COUNT(*) FROM information_schema.columns " + + "WHERE table_name = '" + tableName + "' " + + "AND column_name = '" + columnName + "'")) { + rs.next(); + return rs.getInt(1) > 0; + } + } + /** Write postcode travel times as a ZSTD-compressed parquet (atomic via tmp + rename). */ static void writeTravelTimes(DuckDBConnection conn, Path outPath, String[] postcodes, short[] times) throws Exception { diff --git a/screenshot/package-lock.json b/screenshot/package-lock.json index 2683569..7782b0a 100644 --- a/screenshot/package-lock.json +++ b/screenshot/package-lock.json @@ -8,13 +8,13 @@ "name": "screenshot", "version": "1.0.0", "dependencies": { - "express": "^4.21.0", - "playwright": "^1.49.0" + "express": "^5.2.1", + "playwright": "^1.59.1" }, "devDependencies": { - "@types/express": "^5.0.0", - "@types/node": "^22.0.0", - "typescript": "^5.7.0" + "@types/express": "^5.0.6", + "@types/node": "^25.6.1", + "typescript": "^6.0.3" } }, "node_modules/@types/body-parser": { @@ -66,12 +66,13 @@ "dev": true }, "node_modules/@types/node": { - "version": "22.19.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", - "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", + "version": "25.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.1.tgz", + "integrity": "sha512-coJCN8O1q4AGyyqCAUSP06P+SrMTu18BkEj3NVAK07q6QUneD2wzj3CLv9+yP+BMeZQlMvneXqqvDe3w+xcq7g==", "dev": true, + "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.19.0" } }, "node_modules/@types/qs": { @@ -106,49 +107,47 @@ } }, "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { "node": ">= 0.6" } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, "node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.14.0", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -157,6 +156,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -169,6 +169,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -181,20 +182,23 @@ } }, "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -208,39 +212,45 @@ } }, "node_modules/cookie-signature": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } }, "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { - "ms": "2.0.0" + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -253,12 +263,14 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -267,6 +279,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -275,6 +288,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -283,6 +297,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, @@ -293,55 +308,55 @@ "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "~1.20.3", - "content-disposition": "~0.5.4", - "content-type": "~1.0.4", - "cookie": "~0.7.1", - "cookie-signature": "~1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.3.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "~0.1.12", - "proxy-addr": "~2.0.7", - "qs": "~6.14.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "~0.19.0", - "serve-static": "~1.16.2", - "setprototypeof": "1.2.0", - "statuses": "~2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" }, "engines": { - "node": ">= 0.10.0" + "node": ">= 18" }, "funding": { "type": "opencollective", @@ -349,20 +364,24 @@ } }, "node_modules/finalhandler": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", - "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "statuses": "~2.0.2", - "unpipe": "~1.0.0" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/forwarded": { @@ -374,11 +393,12 @@ } }, "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/fsevents": { @@ -398,6 +418,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -406,6 +427,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -429,6 +451,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -441,6 +464,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -452,6 +476,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -460,9 +485,10 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -474,6 +500,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", @@ -490,20 +517,26 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, "node_modules/ipaddr.js": { "version": "1.9.1", @@ -513,77 +546,78 @@ "node": ">= 0.10" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -592,6 +626,7 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -603,6 +638,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -610,25 +646,41 @@ "node": ">= 0.8" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, "node_modules/playwright": { - "version": "1.58.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.1.tgz", - "integrity": "sha512-+2uTZHxSCcxjvGc5C891LrS1/NlxglGxzrC4seZiVjcYVQfUa87wBL6rTDqzGjuoWNjnBzRqKmF6zRYGMvQUaQ==", + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", + "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", + "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.58.1" + "playwright-core": "1.59.1" }, "bin": { "playwright": "cli.js" @@ -641,9 +693,10 @@ } }, "node_modules/playwright-core": { - "version": "1.58.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.1.tgz", - "integrity": "sha512-bcWzOaTxcW+VOOGBCQgnaKToLJ65d6AqfLVKEWvexyS3AS6rbXl+xdpYRMGSRBClPvyj44njOWoxjNdL/H9UNg==", + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", + "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", + "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, @@ -664,9 +717,10 @@ } }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" }, @@ -681,99 +735,104 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", + "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" }, "node_modules/send": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", - "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.4.1", - "range-parser": "~1.2.1", - "statuses": "~2.0.2" + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, "node_modules/serve-static": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", - "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "~0.19.1" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -789,12 +848,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -807,6 +867,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -824,6 +885,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -842,6 +904,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -850,27 +913,31 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } }, "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { "node": ">= 0.6" } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -880,27 +947,21 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "dev": true, + "license": "MIT" }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -908,6 +969,12 @@ "engines": { "node": ">= 0.8" } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" } } } diff --git a/screenshot/package.json b/screenshot/package.json index a103db2..285d677 100644 --- a/screenshot/package.json +++ b/screenshot/package.json @@ -9,12 +9,12 @@ "dev": "tsc --watch & node --watch dist/server.js" }, "dependencies": { - "express": "^4.21.0", - "playwright": "^1.49.0" + "express": "^5.2.1", + "playwright": "^1.59.1" }, "devDependencies": { - "@types/express": "^5.0.0", - "@types/node": "^22.0.0", - "typescript": "^5.7.0" + "@types/express": "^5.0.6", + "@types/node": "^25.6.1", + "typescript": "^6.0.3" } } diff --git a/screenshot/src/cache.ts b/screenshot/src/cache.ts index d9cd029..a7ebe5f 100644 --- a/screenshot/src/cache.ts +++ b/screenshot/src/cache.ts @@ -17,14 +17,12 @@ export class ScreenshotCache { /** * Build a cache key by quantizing view params and hashing. - * - lat/lon quantized to 2 decimal places - * - zoom quantized to integer - * - filters and POI categories sorted alphabetically + * lat/lon are rounded to 2 decimals and zoom to an integer so nearby views + * share a cache entry; all other params are sorted for order-independence. */ buildKey(params: URLSearchParams): string { const normalized: Record = {}; - // Quantize lat/lon/zoom const lat = params.get('lat'); const lon = params.get('lon'); const zoom = params.get('zoom'); @@ -34,34 +32,10 @@ export class ScreenshotCache { normalized.zoom = Math.round(parseFloat(zoom)).toString(); } - // Sort filters - const filters = params.getAll('filter').sort(); - if (filters.length > 0) { - normalized.filters = filters.join(','); - } - - // Sort POI categories - const pois = params.getAll('poi').sort(); - if (pois.length > 0) { - normalized.poi = pois.join(','); - } - - // Sort travel time entries - const tt = params.getAll('tt').sort(); - if (tt.length > 0) { - normalized.tt = tt.join(','); - } - - if (params.get('tab')) { - normalized.tab = params.get('tab')!; - } - - if (params.get('og')) { - normalized.og = params.get('og')!; - } - - if (params.get('path')) { - normalized.path = params.get('path')!; + const quantized = new Set(['lat', 'lon', 'zoom']); + const keys = [...new Set(params.keys())].filter((k) => !quantized.has(k)).sort(); + for (const key of keys) { + normalized[key] = params.getAll(key).sort().join(','); } const input = JSON.stringify(normalized); diff --git a/screenshot/src/screenshot.ts b/screenshot/src/screenshot.ts index 097f078..38f48dd 100644 --- a/screenshot/src/screenshot.ts +++ b/screenshot/src/screenshot.ts @@ -292,8 +292,7 @@ export async function checkWebGL(): Promise> { await page.setContent(''); const info = await page.evaluate(() => { const canvas = document.getElementById('c') as HTMLCanvasElement; - const gl = - canvas.getContext('webgl2') || canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); + const gl = canvas.getContext('webgl2'); if (!gl) return { webgl: false, error: 'No WebGL context available' }; const g = gl as WebGLRenderingContext; const debugExt = g.getExtension('WEBGL_debug_renderer_info'); diff --git a/screenshot/src/server.ts b/screenshot/src/server.ts index 4d60f4b..b6e6738 100644 --- a/screenshot/src/server.ts +++ b/screenshot/src/server.ts @@ -1,28 +1,37 @@ -import express, { type Request, type Response } from 'express'; -import { ScreenshotCache } from './cache.js'; -import { takeScreenshot, checkWebGL, closeBrowser, initialize } from './screenshot.js'; -import { buildScreenshotRequest, ValidationError } from './validation.js'; +import express, { type Request, type Response } from "express"; +import { ScreenshotCache } from "./cache.js"; +import { + takeScreenshot, + checkWebGL, + closeBrowser, + initialize, +} from "./screenshot.js"; +import { buildScreenshotRequest, ValidationError } from "./validation.js"; -const PORT = parseInt(process.env.PORT || '8002', 10); +const PORT = parseRequiredPositiveIntEnv("PORT"); const APP_URL = process.env.APP_URL; const CACHE_DIR = process.env.CACHE_DIR; -const SCREENSHOT_CONCURRENCY = parsePositiveIntEnv('SCREENSHOT_CONCURRENCY', 3); -const RATE_LIMIT_WINDOW_MS = parsePositiveIntEnv('SCREENSHOT_RATE_WINDOW_MS', 60_000); -const RATE_LIMIT_MAX = parsePositiveIntEnv('SCREENSHOT_RATE_LIMIT', 30); +const SCREENSHOT_CONCURRENCY = parseRequiredPositiveIntEnv( + "SCREENSHOT_CONCURRENCY", +); +const RATE_LIMIT_WINDOW_MS = parseRequiredPositiveIntEnv( + "SCREENSHOT_RATE_WINDOW_MS", +); +const RATE_LIMIT_MAX = parseRequiredPositiveIntEnv("SCREENSHOT_RATE_LIMIT"); if (!APP_URL) { - console.error('Error: APP_URL environment variable is required'); + console.error("Error: APP_URL environment variable is required"); process.exit(1); } if (!CACHE_DIR) { - console.error('Error: CACHE_DIR environment variable is required'); + console.error("Error: CACHE_DIR environment variable is required"); process.exit(1); } const cache = new ScreenshotCache(CACHE_DIR); const app = express(); -app.set('trust proxy', true); +app.set("trust proxy", true); let activeScreenshots = 0; let lastRateLimitPrune = 0; @@ -34,9 +43,16 @@ type PendingScreenshotSlot = { }; const screenshotSlotQueue: PendingScreenshotSlot[] = []; -function parsePositiveIntEnv(name: string, fallback: number): number { - const value = Number.parseInt(process.env[name] || '', 10); - return Number.isFinite(value) && value > 0 ? value : fallback; +function parseRequiredPositiveIntEnv(name: string): number { + const raw = process.env[name]; + if (!raw) { + throw new Error(`${name} environment variable is required`); + } + const value = Number.parseInt(raw, 10); + if (!Number.isFinite(value) || value <= 0) { + throw new Error(`${name} must be a positive integer`); + } + return value; } function grantScreenshotSlot(): ReleaseScreenshotSlot { @@ -51,7 +67,10 @@ function grantScreenshotSlot(): ReleaseScreenshotSlot { } function drainScreenshotSlotQueue(): void { - while (activeScreenshots < SCREENSHOT_CONCURRENCY && screenshotSlotQueue.length > 0) { + while ( + activeScreenshots < SCREENSHOT_CONCURRENCY && + screenshotSlotQueue.length > 0 + ) { const pending = screenshotSlotQueue.shift(); if (!pending) return; pending.cleanup(); @@ -59,7 +78,9 @@ function drainScreenshotSlotQueue(): void { } } -function acquireScreenshotSlot(res: Response): Promise { +function acquireScreenshotSlot( + res: Response, +): Promise { if (activeScreenshots < SCREENSHOT_CONCURRENCY) { return Promise.resolve(grantScreenshotSlot()); } @@ -76,17 +97,23 @@ function acquireScreenshotSlot(res: Response): Promise res.off('close', onClose), + cleanup: () => res.off("close", onClose), }; - res.on('close', onClose); + res.on("close", onClose); screenshotSlotQueue.push(pending); - console.log(`Queued screenshot request; queue length: ${screenshotSlotQueue.length}`); + console.log( + `Queued screenshot request; queue length: ${screenshotSlotQueue.length}`, + ); }); } function rateLimitKey(req: Request): string { - const forwardedFor = req.get('x-forwarded-for')?.split(',')[0]?.trim(); - return forwardedFor || req.ip || req.socket.remoteAddress || 'unknown'; + const forwardedFor = req.get("x-forwarded-for")?.split(",")[0]?.trim(); + const key = forwardedFor || req.ip || req.socket.remoteAddress; + if (!key) { + throw new Error("Unable to determine request IP for rate limiting"); + } + return key; } function allowScreenshotRequest(req: Request): boolean { @@ -113,11 +140,11 @@ function allowScreenshotRequest(req: Request): boolean { return true; } -app.get('/health', (_req, res) => { - res.status(200).send('ok'); +app.get("/health", (_req, res) => { + res.status(200).send("ok"); }); -app.get('/debug', async (_req, res) => { +app.get("/debug", async (_req, res) => { try { const info = await checkWebGL(); res.json(info); @@ -126,32 +153,34 @@ app.get('/debug', async (_req, res) => { } }); -app.get('/screenshot', async (req, res) => { +app.get("/screenshot", async (req, res) => { let releaseSlot: (() => void) | null = null; try { - const { pagePath, qs } = buildScreenshotRequest(req.query as Record); - if (pagePath !== '/') qs.set('path', pagePath); + const { pagePath, qs } = buildScreenshotRequest( + req.query as Record, + ); + if (pagePath !== "/") qs.set("path", pagePath); // Include auth status in cache key so authenticated screenshots // (with hexagons outside free zone) are cached separately const authHeader = req.headers.authorization; - if (authHeader) qs.set('_auth', '1'); + if (authHeader) qs.set("_auth", "1"); const cacheKey = cache.buildKey(qs); - qs.delete('_auth'); - qs.delete('path'); + qs.delete("_auth"); + qs.delete("path"); // Check cache first const cached = cache.get(cacheKey); if (cached) { - res.setHeader('Content-Type', 'image/jpeg'); - res.setHeader('Cache-Control', 'public, max-age=86400'); - res.setHeader('X-Cache', 'HIT'); + res.setHeader("Content-Type", "image/jpeg"); + res.setHeader("Cache-Control", "public, max-age=86400"); + res.setHeader("X-Cache", "HIT"); cached.pipe(res); return; } if (!allowScreenshotRequest(req)) { - res.status(429).json({ error: 'Screenshot rate limit exceeded' }); + res.status(429).json({ error: "Screenshot rate limit exceeded" }); return; } @@ -161,26 +190,28 @@ app.get('/screenshot', async (req, res) => { } // Build the URL for the frontend in screenshot mode - qs.set('screenshot', '1'); + qs.set("screenshot", "1"); const url = `${APP_URL}${pagePath}?${qs}`; - console.log(`Taking screenshot: ${url}${authHeader ? ' (authenticated)' : ''}`); + console.log( + `Taking screenshot: ${url}${authHeader ? " (authenticated)" : ""}`, + ); const jpeg = await takeScreenshot(url, authHeader); // Cache it cache.set(cacheKey, jpeg); - res.setHeader('Content-Type', 'image/jpeg'); - res.setHeader('Cache-Control', 'public, max-age=86400'); - res.setHeader('X-Cache', 'MISS'); + res.setHeader("Content-Type", "image/jpeg"); + res.setHeader("Cache-Control", "public, max-age=86400"); + res.setHeader("X-Cache", "MISS"); res.send(jpeg); } catch (err) { if (err instanceof ValidationError) { res.status(err.status).json({ error: err.message }); return; } - console.error('Screenshot failed:', err); - res.status(500).json({ error: 'Screenshot failed' }); + console.error("Screenshot failed:", err); + res.status(500).json({ error: "Screenshot failed" }); } finally { releaseSlot?.(); } @@ -191,18 +222,20 @@ const server = app.listen(PORT, () => { console.log(` APP_URL: ${APP_URL}`); console.log(` CACHE_DIR: ${CACHE_DIR}`); console.log(` SCREENSHOT_CONCURRENCY: ${SCREENSHOT_CONCURRENCY}`); - console.log(` SCREENSHOT_RATE_LIMIT: ${RATE_LIMIT_MAX}/${RATE_LIMIT_WINDOW_MS}ms`); + console.log( + ` SCREENSHOT_RATE_LIMIT: ${RATE_LIMIT_MAX}/${RATE_LIMIT_WINDOW_MS}ms`, + ); // Pre-warm browser and populate network cache in background. // The health endpoint is available immediately; screenshot requests // during warm-up will still work (just slower on the first call). initialize(APP_URL).catch((err) => { - console.error('Initialization failed:', err); + console.error("Initialization failed:", err); }); }); // Graceful shutdown -for (const signal of ['SIGTERM', 'SIGINT']) { +for (const signal of ["SIGTERM", "SIGINT"]) { process.on(signal, async () => { console.log(`Received ${signal}, shutting down...`); server.close(); diff --git a/screenshot/src/validation.test.ts b/screenshot/src/validation.test.ts index 79ac1f1..39723e7 100644 --- a/screenshot/src/validation.test.ts +++ b/screenshot/src/validation.test.ts @@ -12,6 +12,9 @@ test('buildScreenshotRequest accepts supported screenshot parameters', () => { og: '1', path: '/invite/abc123', filter: ['Last known price:100000:500000', 'Total floor area (sqm):50:150'], + school: 'primary:good:2:1:10', + crime: ['Burglary (avg/yr):0:5', 'Vehicle crime (avg/yr):0:10'], + ethnicity: ['% White:10:80', '% South Asian:5:35'], poi: 'supermarket', tt: 'transit:kings-cross:Kings Cross:b:0:30', }); @@ -25,6 +28,12 @@ test('buildScreenshotRequest accepts supported screenshot parameters', () => { 'Last known price:100000:500000', 'Total floor area (sqm):50:150', ]); + assert.deepEqual(result.qs.getAll('school'), ['primary:good:2:1:10']); + assert.deepEqual(result.qs.getAll('crime'), [ + 'Burglary (avg/yr):0:5', + 'Vehicle crime (avg/yr):0:10', + ]); + assert.deepEqual(result.qs.getAll('ethnicity'), ['% White:10:80', '% South Asian:5:35']); }); test('buildScreenshotRequest rejects invalid numeric values', () => { diff --git a/screenshot/src/validation.ts b/screenshot/src/validation.ts index f295616..2468855 100644 --- a/screenshot/src/validation.ts +++ b/screenshot/src/validation.ts @@ -12,7 +12,7 @@ const MAX_VALUE_LENGTH = 500; const NUMERIC_RE = /^-?(?:\d+|\d*\.\d+)$/; const PATH_RE = /^\/(?:invite\/[A-Za-z0-9]{1,20})?$/; const SAFE_VALUE_RE = /^[^\u0000-\u001f\u007f]+$/; -const REPEATED_KEYS = ['filter', 'poi', 'tt'] as const; +const REPEATED_KEYS = ['filter', 'school', 'crime', 'ethnicity', 'poi', 'tt'] as const; type Query = Record; diff --git a/server-rs/Cargo.lock b/server-rs/Cargo.lock index 6f8a242..7ac125c 100644 --- a/server-rs/Cargo.lock +++ b/server-rs/Cargo.lock @@ -16,7 +16,6 @@ checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "const-random", - "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -52,6 +51,12 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -63,9 +68,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -78,15 +83,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -113,9 +118,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "ar_archive_writer" @@ -126,21 +131,13 @@ dependencies = [ "object", ] -[[package]] -name = "arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" -dependencies = [ - "derive_arbitrary", -] - [[package]] name = "argminmax" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70f13d10a41ac8d2ec79ee34178d61e6f47a29c2edfe7ef1721c7383b0359e65" dependencies = [ + "half", "num-traits", ] @@ -151,10 +148,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed51fe0f224d1d4ea768be38c51f9f831dee9d05c163c11fba0b8c44387b1fc3" [[package]] -name = "async-compression" -version = "0.4.37" +name = "arrayref" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d10e4f991a553474232bc0a31799f6d24b034a84c0971d80d2e2f78b2e576e40" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-compression" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" dependencies = [ "compression-codecs", "compression-core", @@ -162,6 +183,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -197,9 +229,9 @@ dependencies = [ [[package]] name = "atoi_simd" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a49e05797ca52e312a0c658938b7d00693ef037799ef7187678f212d7684cf" +checksum = "8ad17c7c205c2c28b527b9845eeb91cf1b4d008b438f98ce0e628227a822758e" dependencies = [ "debug_unsafe", ] @@ -210,6 +242,22 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "attohttpc" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" +dependencies = [ + "base64", + "http 1.4.0", + "log", + "rustls 0.23.40", + "serde", + "serde_json", + "url", + "webpki-roots", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -217,10 +265,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] -name = "aws-lc-rs" -version = "1.15.4" +name = "aws-credential-types" +version = "1.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +checksum = "8f20799b373a1be121fe3005fba0c2090af9411573878f224df44b42727fcaf7" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-creds" +version = "0.39.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3b85155d265df828f84e53886ed9e427aed979dd8a39f5b8b2162c77e142d7" +dependencies = [ + "attohttpc", + "home", + "log", + "quick-xml 0.38.4", + "rust-ini", + "serde", + "thiserror", + "time", + "url", +] + +[[package]] +name = "aws-lc-rs" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" dependencies = [ "aws-lc-sys", "zeroize", @@ -228,9 +305,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.37.0" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" dependencies = [ "cc", "cmake", @@ -239,19 +316,335 @@ dependencies = [ ] [[package]] -name = "axum" -version = "0.8.8" +name = "aws-region" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +checksum = "838b36c8dc927b6db1b6c6b8f5d05865f2213550b9e83bf92fa99ed6525472c0" +dependencies = [ + "thiserror", +] + +[[package]] +name = "aws-runtime" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dcd93c82209ac7413532388067dce79be5a8780c1786e5fae3df22e4dee2864" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "bytes-utils", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "http-body 1.0.1", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-s3" +version = "1.132.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5575840a3a6b11f6011463ebe359320dfe5b67babb5e9b06fed6ddf809a9ab40" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-checksums", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "bytes", + "fastrand", + "hex", + "hmac 0.13.0", + "http 0.2.12", + "http 1.4.0", + "http-body 1.0.1", + "lru", + "percent-encoding", + "regex-lite", + "sha2 0.11.0", + "tracing", + "url", +] + +[[package]] +name = "aws-sigv4" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68dc0b907359b120170613b5c09ccc61304eac3998ff6274b97d93ee6490115a" +dependencies = [ + "aws-credential-types", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "crypto-bigint 0.5.5", + "form_urlencoded", + "hex", + "hmac 0.13.0", + "http 0.2.12", + "http 1.4.0", + "p256", + "percent-encoding", + "ring", + "sha2 0.11.0", + "subtle", + "time", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffcaf626bdda484571968400c326a244598634dc75fd451325a54ad1a59acfc" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-checksums" +version = "0.64.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10efbbcec1e044b81600e2fc562a391951d291152d95b482d5b7e7132299d762" +dependencies = [ + "aws-smithy-http", + "aws-smithy-types", + "bytes", + "crc-fast", + "hex", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "md-5", + "pin-project-lite", + "sha1", + "sha2 0.11.0", + "tracing", +] + +[[package]] +name = "aws-smithy-eventstream" +version = "0.60.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf09d74e5e32f76b8762da505a3cd59303e367a664ca67295387baa8c1d7548" +dependencies = [ + "aws-smithy-types", + "bytes", + "crc32fast", +] + +[[package]] +name = "aws-smithy-http" +version = "0.63.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1ab2dc1c2c3749ead27180d333c42f11be8b0e934058fb4b2258ee8dbe5231" +dependencies = [ + "aws-smithy-eventstream", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-http-client" +version = "1.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a2f165a7feee6f263028b899d0a181987f4fa7179a6411a32a439fba7c5f769" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2 0.3.27", + "h2 0.4.14", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper 1.9.0", + "hyper-rustls 0.24.2", + "hyper-rustls 0.27.9", + "hyper-util", + "pin-project-lite", + "rustls 0.21.12", + "rustls 0.23.40", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.62.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9648b0bb82a2eedd844052c6ad2a1a822d1f8e3adee5fbf668366717e428856a" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-observability" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06c2315d173edbf1920da8ba3a7189695827002e4c0fc961973ab1c54abca9c" +dependencies = [ + "aws-smithy-runtime-api", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0504b1ab12debb5959e5165ee5fe97dd387e7aa7ea6a477bfd7635dfe769a4f5" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-http-client", + "aws-smithy-observability", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "pin-project-lite", + "pin-utils", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71a13df6ada0aafbf21a73bdfcdf9324cfa9df77d96b8446045be3cde61b42e" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api-macros", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.4.0", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-runtime-api-macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d7396fd9500589e62e460e987ecb671bad374934e55ec3b5f498cc7a8a8a7b7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "aws-smithy-types" +version = "1.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d73dbfbaa8e4bc57b9045137680b958d274823509a360abfd8e1d514d40c95c" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce02add1aa3677d022f8adf81dcbe3046a95f17a1b1e8979c145cd21d3d22b3" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4bbcaa9304ea40902d3d5f42a0428d1bd895a2b0f6999436fb279ffddc58ac" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version", + "tracing", +] + +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core", "bytes", "form_urlencoded", "futures-util", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.9.0", "hyper-util", "itoa", "matchit", @@ -279,8 +672,8 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", @@ -290,6 +683,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + [[package]] name = "base64" version = "0.22.1" @@ -297,10 +696,63 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "bitflags" -version = "2.10.0" +name = "base64-simd" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +dependencies = [ + "serde_core", +] + +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.0", +] [[package]] name = "block-buffer" @@ -312,10 +764,25 @@ dependencies = [ ] [[package]] -name = "brotli" -version = "7.0.0" +name = "block-buffer" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "boxcar" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f64beae40a84da1b4b26ff2761a5b895c12adc41dc25aaee1c4f2bbfe97a6e" + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -324,9 +791,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.3" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -334,9 +801,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytemuck" @@ -373,6 +840,16 @@ dependencies = [ "serde", ] +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + [[package]] name = "castaway" version = "0.2.4" @@ -384,9 +861,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.55" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", "jobserver", @@ -407,14 +884,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] -name = "chrono" -version = "0.4.43" +name = "chacha20" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", "iana-time-zone", "num-traits", - "windows-link", + "serde", + "windows-link 0.1.3", ] [[package]] @@ -429,9 +919,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.57" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -439,9 +929,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.57" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -451,9 +941,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", @@ -463,24 +953,40 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.7" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cmake" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] [[package]] -name = "colorchoice" -version = "1.0.4" +name = "cmov" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] [[package]] name = "comfy-table" @@ -495,9 +1001,9 @@ dependencies = [ [[package]] name = "compact_str" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" dependencies = [ "castaway", "cfg-if", @@ -510,10 +1016,11 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.36" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" +checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" dependencies = [ + "brotli", "compression-core", "flate2", "memchr", @@ -523,9 +1030,30 @@ dependencies = [ [[package]] name = "compression-core" -version = "0.4.31" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" +checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" [[package]] name = "const-random" @@ -547,6 +1075,12 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "core-foundation" version = "0.9.4" @@ -573,6 +1107,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "countio" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9702aee5d1d744c01d82f6915644f950f898e014903385464c773b96fefdecb" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -582,6 +1122,42 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + +[[package]] +name = "crc-fast" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd92aca2c6001b1bf5ba0ff84ee74ec8501b52bbef0cac80bf25a6c1d87a83d" +dependencies = [ + "crc", + "digest 0.10.7", + "rustversion", + "spin", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -644,7 +1220,7 @@ dependencies = [ "crossterm_winapi", "document-features", "parking_lot", - "rustix 1.1.3", + "rustix 1.1.4", "winapi", ] @@ -663,6 +1239,28 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-bigint" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -674,29 +1272,47 @@ dependencies = [ ] [[package]] -name = "debug_unsafe" -version = "0.1.3" +name = "crypto-common" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85d3cef41d236720ed453e102153a53e4cc3d2fde848c0078a50cf249e8e3e5b" - -[[package]] -name = "deranged" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" dependencies = [ - "powerfmt", + "hybrid-array", ] [[package]] -name = "derive_arbitrary" -version = "1.4.2" +name = "ctutils" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" dependencies = [ - "proc-macro2", - "quote", - "syn", + "cmov", +] + +[[package]] +name = "debug_unsafe" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eed2c4702fa172d1ce21078faa7c5203e69f5394d48cc436d25928394a867a2" + +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid 0.9.6", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", ] [[package]] @@ -705,11 +1321,23 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.0", + "const-oid 0.10.2", + "crypto-common 0.2.1", + "ctutils", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -721,6 +1349,15 @@ dependencies = [ "syn", ] +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + [[package]] name = "document-features" version = "0.2.12" @@ -742,12 +1379,44 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "ecdsa" +version = "0.14.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature", +] + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "elliptic-curve" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +dependencies = [ + "base16ct", + "crypto-bigint 0.4.9", + "der", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encoding_rs" version = "0.8.35" @@ -787,9 +1456,41 @@ dependencies = [ [[package]] name = "ethnum" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca81e6b4777c89fd810c25a4be2b1bd93ea034fbe58e6a75216a34c6b82c539b" +checksum = "40404c3f5f511ec4da6fe866ddf6a717c309fdbb69fbbad7b0f3edab8f2e835f" + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "evmap" +version = "11.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8874945f036109c72242964c1174cf99434e30cfa45bf45fedc983f50046f8" +dependencies = [ + "hashbag", + "left-right", + "smallvec", +] [[package]] name = "fallible-streaming-iterator" @@ -804,10 +1505,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" [[package]] -name = "fastrand" -version = "2.3.0" +name = "fast_hilbert" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "4f551b382f601850b4f57052d064d1c60dca971aeee4ccb40ac103b87f766640" + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] [[package]] name = "find-msvc-tools" @@ -841,7 +1558,7 @@ dependencies = [ "byteorder", "bytes", "enum_dispatch", - "fs4", + "fs4 0.12.0", "memmap2", "parse-display", "pin-project-lite", @@ -861,19 +1578,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] -name = "foreign-types" -version = "0.3.2" +name = "foldhash" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "form_urlencoded" @@ -895,6 +1603,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fs4" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4" +dependencies = [ + "rustix 1.1.4", + "windows-sys 0.59.0", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -903,9 +1621,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -918,9 +1636,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -928,15 +1646,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -945,15 +1663,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -962,21 +1680,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -986,10 +1704,24 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] +[[package]] +name = "generator" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link 0.2.1", + "windows-result 0.4.1", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1022,11 +1754,25 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasip2", + "wasip3", +] + [[package]] name = "glob" version = "0.3.3" @@ -1034,17 +1780,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] -name = "h2" -version = "0.4.13" +name = "group" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http", + "http 1.4.0", "indexmap", "slab", "tokio", @@ -1054,9 +1830,9 @@ dependencies = [ [[package]] name = "h3o" -version = "0.7.1" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537b141fa7998c2c993b9431247f6e2eb69d606bd51173ab85394792f3a7cdf7" +checksum = "eaed017cf85070a94c3426db925ea8de919abaa26f495da26a8f23ecd28335ec" dependencies = [ "ahash", "either", @@ -1071,6 +1847,26 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b42eb4efef1f96510ae1a33b2682562a677d504641e9903a77bf5c666b9013e" +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "bytemuck", + "cfg-if", + "crunchy", + "num-traits", + "serde", + "zerocopy", +] + +[[package]] +name = "hashbag" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7040a10f52cba493ddb09926e15d10a9d8a28043708a405931fe4c6f19fac064" + [[package]] name = "hashbrown" version = "0.14.5" @@ -1079,8 +1875,6 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", - "rayon", - "serde", ] [[package]] @@ -1091,9 +1885,7 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", - "foldhash", - "rayon", - "serde", + "foldhash 0.1.5", ] [[package]] @@ -1101,6 +1893,20 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", + "rayon", + "serde", + "serde_core", +] + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "heck" @@ -1114,19 +1920,22 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hilbert_2d" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705f81e042b11734af35c701c7f6b65f8a968a430621fa2c95e72e27f9f8be5c" - [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", +] + +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", ] [[package]] @@ -1138,6 +1947,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.4.0" @@ -1148,6 +1968,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.1" @@ -1155,7 +1986,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.4.0", ] [[package]] @@ -1166,8 +1997,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -1190,23 +2021,61 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] -name = "hyper" -version = "1.8.1" +name = "humantime" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "hybrid-array" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", - "h2", - "http", - "http-body", + "h2 0.4.14", + "http 1.4.0", + "http-body 1.0.1", "httparse", "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -1214,36 +2083,34 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-native-certs", - "rustls-pki-types", + "futures-util", + "http 0.2.12", + "hyper 0.14.32", + "log", + "rustls 0.21.12", "tokio", - "tokio-rustls", - "tower-service", - "webpki-roots", + "tokio-rustls 0.24.1", ] [[package]] -name = "hyper-tls" -version = "0.6.0" +name = "hyper-rustls" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "bytes", - "http-body-util", - "hyper", + "http 1.4.0", + "hyper 1.9.0", "hyper-util", - "native-tls", + "rustls 0.23.40", + "rustls-native-certs", "tokio", - "tokio-native-tls", + "tokio-rustls 0.26.4", "tower-service", + "webpki-roots", ] [[package]] @@ -1256,14 +2123,14 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", - "http-body", - "hyper", + "http 1.4.0", + "http-body 1.0.1", + "hyper 1.9.0", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.3", "system-configuration", "tokio", "tower-service", @@ -1297,12 +2164,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -1310,9 +2178,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -1323,9 +2191,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1337,15 +2205,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -1357,15 +2225,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -1376,6 +2244,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "idna" version = "1.1.0" @@ -1389,9 +2263,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -1399,31 +2273,21 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" -dependencies = [ - "memchr", - "serde", -] +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "is_terminal_polyfill" @@ -1432,10 +2296,68 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] -name = "itoa" -version = "1.0.17" +name = "itertools" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror", + "walkdir", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] [[package]] name = "jobserver" @@ -1449,10 +2371,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -1473,10 +2397,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] -name = "libc" -version = "0.2.180" +name = "leb128fmt" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "left-right" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0c21e4c8ff95f487fb34e6f9182875f42c84cef966d29216bf115d9bba835a" +dependencies = [ + "crossbeam-utils", + "loom", + "slab", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libm" @@ -1492,15 +2433,15 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "litrs" @@ -1523,6 +2464,28 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "lru" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" +dependencies = [ + "hashbrown 0.16.1", +] + [[package]] name = "lru-slab" version = "0.1.2" @@ -1564,64 +2527,94 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] -name = "memchr" -version = "2.7.6" +name = "maybe-async" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "md-5" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" +dependencies = [ + "cfg-if", + "digest 0.11.3", +] + +[[package]] +name = "md5" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmap2" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" dependencies = [ "libc", ] [[package]] name = "metrics" -version = "0.24.3" +version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8" +checksum = "ff56c2e7dce6bd462e3b8919986a617027481b1dcc703175b58cf9dd98a2f071" dependencies = [ - "ahash", "portable-atomic", + "rapidhash", ] [[package]] name = "metrics-exporter-prometheus" -version = "0.16.2" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd7399781913e5393588a8d8c6a2867bf85fb38eaf2502fdce465aad2dc6f034" +checksum = "1db0d8f1fc9e62caebd0319e11eaec5822b0186c171568f0480b46a0137f9108" dependencies = [ "base64", + "evmap", "http-body-util", - "hyper", - "hyper-rustls", + "hyper 1.9.0", + "hyper-rustls 0.27.9", "hyper-util", "indexmap", "ipnet", "metrics", "metrics-util", "quanta", - "thiserror 1.0.69", + "rustls 0.23.40", + "thiserror", "tokio", "tracing", ] [[package]] name = "metrics-util" -version = "0.19.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8496cc523d1f94c1385dd8f0f0c2c480b2b8aeccb5b7e4485ad6365523ae376" +checksum = "9e56997f084e57b045edf17c3ed8ba7f9f779c670df8206dfd1c736f4c02dc4a" dependencies = [ "crossbeam-epoch", "crossbeam-utils", - "hashbrown 0.15.5", + "hashbrown 0.16.1", "metrics", "quanta", - "rand 0.9.2", + "rand 0.9.4", "rand_xoshiro", + "rapidhash", "sketches-ddsketch", ] @@ -1653,9 +2646,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi", @@ -1663,20 +2656,23 @@ dependencies = [ ] [[package]] -name = "native-tls" -version = "0.2.14" +name = "moka" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe 0.1.6", - "openssl-sys", - "schannel", - "security-framework 2.11.1", - "security-framework-sys", - "tempfile", + "async-lock", + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "event-listener", + "futures-util", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", ] [[package]] @@ -1690,9 +2686,9 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" dependencies = [ "winapi", ] @@ -1708,9 +2704,29 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] [[package]] name = "num-traits" @@ -1722,6 +2738,25 @@ dependencies = [ "libm", ] +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + [[package]] name = "object" version = "0.37.3" @@ -1732,10 +2767,47 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.21.3" +name = "object_store" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "622acbc9100d3c10e2ee15804b0caa40e55c933d5aa53814cd520805b7958a49" +dependencies = [ + "async-trait", + "base64", + "bytes", + "chrono", + "form_urlencoded", + "futures-channel", + "futures-core", + "futures-util", + "http 1.4.0", + "http-body-util", + "humantime", + "hyper 1.9.0", + "itertools", + "parking_lot", + "percent-encoding", + "quick-xml 0.39.3", + "rand 0.10.1", + "reqwest 0.12.28", + "ring", + "serde", + "serde_json", + "serde_urlencoded", + "thiserror", + "tokio", + "tracing", + "url", + "walkdir", + "wasm-bindgen-futures", + "web-time", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -1743,38 +2815,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" -[[package]] -name = "openssl" -version = "0.10.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - [[package]] name = "openssl-probe" version = "0.2.1" @@ -1782,17 +2822,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] -name = "openssl-sys" -version = "0.9.111" +name = "ordered-multimap" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", + "dlv-list", + "hashbrown 0.14.5", ] +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "p256" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" +dependencies = [ + "ecdsa", + "elliptic-curve", + "sha2 0.10.9", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.5" @@ -1813,7 +2874,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -1867,9 +2928,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -1878,43 +2939,72 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "pkg-config" -version = "0.3.32" +name = "pkcs8" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "planus" -version = "0.3.1" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1691dd09e82f428ce8d6310bd6d5da2557c82ff17694d2a32cad7242aea89f" +checksum = "3daf8e3d4b712abe1d690838f6e29fb76b76ea19589c4afa39ec30e12f62af71" dependencies = [ "array-init-cursor", + "hashbrown 0.15.5", ] [[package]] name = "pmtiles" -version = "0.12.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a83db53da21fbade9a8738bccb1ef54c405ef3db02b7fa81dfbd01933bc1a838" +checksum = "579153334aed2e066da3e2509b03c686b9d6e737ee2c3635f5baa90a57a762e1" dependencies = [ "async-compression", + "async-stream", + "aws-sdk-s3", + "brotli", "bytes", + "countio", + "fast_hilbert", + "flate2", "fmmap", - "hilbert_2d", - "thiserror 2.0.18", + "futures-util", + "moka", + "object_store", + "reqwest 0.13.3", + "rust-s3", + "serde", + "serde_json", + "thiserror", + "tilejson", "tokio", + "twox-hash", "varint-rs", + "zstd", ] [[package]] name = "polars" -version = "0.46.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72571dde488ecccbe799798bf99ab7308ebdb7cf5d95bcc498dbd5a132f0da4d" +checksum = "899852b723e563dc3cbdc7ea833b14ec44e61309f55df29ba86d45cfd6bc141a" dependencies = [ "getrandom 0.2.17", + "getrandom 0.3.4", "polars-arrow", + "polars-buffer", + "polars-compute", "polars-core", "polars-error", "polars-io", @@ -1929,31 +3019,34 @@ dependencies = [ [[package]] name = "polars-arrow" -version = "0.46.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6611c758d52e799761cc25900666b71552e6c929d88052811bc9daad4b3321a8" +checksum = "6f672743a042b72ace4f88b29f8205ab200b29c5ac976c0560899680c07d2d09" dependencies = [ - "ahash", "atoi_simd", + "bitflags", "bytemuck", + "bytes", "chrono", "chrono-tz", "dyn-clone", "either", "ethnum", "getrandom 0.2.17", - "hashbrown 0.15.5", + "getrandom 0.3.4", + "half", + "hashbrown 0.16.1", "itoa", "lz4", "num-traits", - "parking_lot", "polars-arrow-format", + "polars-buffer", "polars-error", "polars-schema", "polars-utils", + "serde", "simdutf8", "streaming-iterator", - "strength_reduce", "strum_macros", "version_check", "zstd", @@ -1961,94 +3054,130 @@ dependencies = [ [[package]] name = "polars-arrow-format" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b0ef2474af9396b19025b189d96e992311e6a47f90c53cd998b36c4c64b84c" +checksum = "a556ac0ee744e61e167f34c1eb0013ce740e0ee6cd8c158b2ec0b518f10e6675" dependencies = [ "planus", "serde", ] [[package]] -name = "polars-compute" -version = "0.46.0" +name = "polars-buffer" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "332f2547dbb27599a8ffe68e56159f5996ba03d1dad0382ccb62c109ceacdeb6" +checksum = "5d7011424c3a79ca9c1272c7b4f5fe98695d3bed45595e37bb23c16a2978c80c" +dependencies = [ + "bytemuck", + "either", + "serde", + "version_check", +] + +[[package]] +name = "polars-compute" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a32eca8e08ac4cc5de2ac3996d2b38567bba72cdb19bbfd94c370193ed51dd" dependencies = [ "atoi_simd", "bytemuck", "chrono", "either", "fast-float2", + "hashbrown 0.16.1", "itoa", "num-traits", "polars-arrow", + "polars-buffer", "polars-error", "polars-utils", - "ryu", + "rand 0.9.4", + "serde", "strength_reduce", + "strum_macros", "version_check", + "zmij", ] [[package]] name = "polars-core" -version = "0.46.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "796d06eae7e6e74ed28ea54a8fccc584ebac84e6cf0e1e9ba41ffc807b169a01" +checksum = "726296966d04268ee9679c2062af2d06c83c7a87379be471defe616b244c5029" dependencies = [ - "ahash", "bitflags", + "boxcar", "bytemuck", "chrono", "chrono-tz", "comfy-table", "either", - "hashbrown 0.14.5", - "hashbrown 0.15.5", + "getrandom 0.3.4", + "hashbrown 0.16.1", "indexmap", "itoa", "num-traits", - "once_cell", "polars-arrow", + "polars-buffer", "polars-compute", + "polars-dtype", "polars-error", "polars-row", "polars-schema", "polars-utils", - "rand 0.8.5", + "rand 0.9.4", "rand_distr", "rayon", "regex", + "serde", + "serde_json", "strum_macros", - "thiserror 2.0.18", + "uuid", "version_check", "xxhash-rust", ] [[package]] -name = "polars-error" -version = "0.46.0" +name = "polars-dtype" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d6529cae0d1db5ed690e47de41fac9b35ae0c26d476830c2079f130887b847" +checksum = "51976dc46d42cd1e7ca252a9e3bdc90c63b0bfa7030047ebaf5250c2b7838fa6" dependencies = [ + "boxcar", + "hashbrown 0.16.1", + "polars-arrow", + "polars-error", + "polars-utils", + "serde", + "uuid", +] + +[[package]] +name = "polars-error" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c13126f8baebc13dadf26a80dcf69a607977fc8a67b18671ad2cefc713a7bdd" +dependencies = [ + "object_store", + "parking_lot", "polars-arrow-format", "regex", + "signal-hook", "simdutf8", - "thiserror 2.0.18", ] [[package]] name = "polars-expr" -version = "0.46.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e639991a8ad4fb12880ab44bcc3cf44a5703df003142334d9caf86d77d77e7" +checksum = "2151f54b0ae5d6b86c3c47df0898ff90edfe774807823f742f36e44973d51ea1" dependencies = [ - "ahash", "bitflags", - "hashbrown 0.15.5", + "hashbrown 0.16.1", "num-traits", - "once_cell", "polars-arrow", + "polars-buffer", "polars-compute", "polars-core", "polars-io", @@ -2057,33 +3186,39 @@ dependencies = [ "polars-row", "polars-time", "polars-utils", - "rand 0.8.5", + "rand 0.9.4", "rayon", + "recursive", + "regex", + "version_check", ] [[package]] name = "polars-io" -version = "0.46.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719a77e94480f6be090512da196e378cbcbeb3584c6fe1134c600aee906e38ab" +checksum = "059724d7762d7332cbc225e6504d996091b28fa1337716e06e5a81d9e54a34ad" dependencies = [ - "ahash", "async-trait", "atoi_simd", + "blake3", "bytes", "chrono", "fast-float2", + "fs4 0.13.1", "futures", "glob", - "hashbrown 0.15.5", + "hashbrown 0.16.1", "home", "itoa", "memchr", "memmap2", "num-traits", - "once_cell", + "object_store", "percent-encoding", "polars-arrow", + "polars-buffer", + "polars-compute", "polars-core", "polars-error", "polars-parquet", @@ -2092,30 +3227,32 @@ dependencies = [ "polars-utils", "rayon", "regex", - "ryu", + "reqwest 0.12.28", + "serde", + "serde_json", "simdutf8", "tokio", - "tokio-util", + "zmij", ] [[package]] name = "polars-lazy" -version = "0.46.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0a731a672dfc8ac38c1f73c9a4b2ae38d2fc8ac363bfb64c5f3a3e072ffc5ad" +checksum = "02e1e24d4db8c349e9576564cfff47a3f08bb831dba9168f6599be178bc725e8" dependencies = [ - "ahash", "bitflags", "chrono", + "either", "memchr", - "once_cell", "polars-arrow", + "polars-buffer", + "polars-compute", "polars-core", "polars-expr", "polars-io", "polars-mem-engine", "polars-ops", - "polars-pipe", "polars-plan", "polars-stream", "polars-time", @@ -2126,9 +3263,9 @@ dependencies = [ [[package]] name = "polars-mem-engine" -version = "0.46.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33442189bcbf2e2559aa7914db3835429030a13f4f18e43af5fba9d1b018cf12" +checksum = "c394e4cd90186043d4051ce118e90794afbe81ac5eb9a51e358a56728e8ebde3" dependencies = [ "memmap2", "polars-arrow", @@ -2141,28 +3278,29 @@ dependencies = [ "polars-time", "polars-utils", "rayon", + "recursive", ] [[package]] name = "polars-ops" -version = "0.46.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbb83218b0c216104f0076cd1a005128be078f958125f3d59b094ee73d78c18e" +checksum = "7e47b2d9b3627662650da0a8c76ce5101ed1c61b104cb2b3663e0dc711571b12" dependencies = [ - "ahash", "argminmax", "base64", "bytemuck", "chrono", "chrono-tz", "either", - "hashbrown 0.15.5", + "hashbrown 0.16.1", "hex", "indexmap", + "libm", "memchr", "num-traits", - "once_cell", "polars-arrow", + "polars-buffer", "polars-compute", "polars-core", "polars-error", @@ -2179,11 +3317,10 @@ dependencies = [ [[package]] name = "polars-parquet" -version = "0.46.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c60ee85535590a38db6c703a21be4cb25342e40f573f070d1e16f9d84a53ac7" +checksum = "436bae3e89438cafe69400e7567057d7d9820d21ac9a4f69a33b413f2666f03d" dependencies = [ - "ahash", "async-stream", "base64", "brotli", @@ -2191,14 +3328,17 @@ dependencies = [ "ethnum", "flate2", "futures", - "hashbrown 0.15.5", + "hashbrown 0.16.1", "lz4", "num-traits", "polars-arrow", + "polars-buffer", "polars-compute", "polars-error", "polars-parquet-format", "polars-utils", + "regex", + "serde", "simdutf8", "snap", "streaming-decompression", @@ -2215,54 +3355,29 @@ dependencies = [ "futures", ] -[[package]] -name = "polars-pipe" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d238fb76698f56e51ddfa89b135e4eda56a4767c6e8859eed0ab78386fcd52" -dependencies = [ - "crossbeam-channel", - "crossbeam-queue", - "enum_dispatch", - "futures", - "hashbrown 0.15.5", - "num-traits", - "once_cell", - "polars-arrow", - "polars-compute", - "polars-core", - "polars-expr", - "polars-io", - "polars-ops", - "polars-plan", - "polars-row", - "polars-utils", - "rayon", - "uuid", - "version_check", -] - [[package]] name = "polars-plan" -version = "0.46.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f03533a93aa66127fcb909a87153a3c7cfee6f0ae59f497e73d7736208da54c" +checksum = "f7930d5ae1d006179e65f01af57c859307b5875a4cc078dc75257250b9ae5162" dependencies = [ - "ahash", "bitflags", + "blake3", "bytemuck", "bytes", "chrono", "chrono-tz", "either", - "hashbrown 0.15.5", + "futures", + "hashbrown 0.16.1", "memmap2", "num-traits", - "once_cell", "percent-encoding", "polars-arrow", + "polars-buffer", "polars-compute", "polars-core", + "polars-error", "polars-io", "polars-ops", "polars-parquet", @@ -2271,42 +3386,49 @@ dependencies = [ "rayon", "recursive", "regex", + "sha2 0.10.9", + "slotmap", "strum_macros", + "tokio", "version_check", ] [[package]] name = "polars-row" -version = "0.46.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf47f7409f8e75328d7d034be390842924eb276716d0458607be0bddb8cc839" +checksum = "d29ea1a4554fe06442db1d6229235cd358e8eacba96aed8718f612caf3e3a646" dependencies = [ "bitflags", "bytemuck", "polars-arrow", + "polars-buffer", "polars-compute", + "polars-dtype", "polars-error", "polars-utils", ] [[package]] name = "polars-schema" -version = "0.46.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416621ae82b84466cf4ff36838a9b0aeb4a67e76bd3065edc8c9cb7da19b1bc7" +checksum = "d688e73f9156f93cb29350be144c8f1e84c1bc705f00ee7f15eb9706a7971273" dependencies = [ "indexmap", "polars-error", "polars-utils", + "serde", "version_check", ] [[package]] name = "polars-sql" -version = "0.46.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edaab553b90aa4d6743bb538978e1982368acb58a94408d7dd3299cad49c7083" +checksum = "100415f86069d7e9fbf54737148fc161a7c7316a6a7d375fb6cfc7fc64f570ae" dependencies = [ + "bitflags", "hex", "polars-core", "polars-error", @@ -2315,7 +3437,6 @@ dependencies = [ "polars-plan", "polars-time", "polars-utils", - "rand 0.8.5", "regex", "serde", "sqlparser", @@ -2323,17 +3444,30 @@ dependencies = [ [[package]] name = "polars-stream" -version = "0.46.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "498997b656c779610c1496b3d96a59fe569ef22a5b81ccfe5325cb3df8dff2fd" +checksum = "65a0c054bdf16efd16bbc587e8d5418ae28464d61afd735513579cd3c338fa70" dependencies = [ + "async-channel", + "async-trait", "atomic-waker", + "bitflags", + "bytes", + "chrono-tz", + "crossbeam-channel", "crossbeam-deque", + "crossbeam-queue", "crossbeam-utils", "futures", + "memchr", "memmap2", + "num-traits", "parking_lot", + "percent-encoding", "pin-project-lite", + "polars-arrow", + "polars-buffer", + "polars-compute", "polars-core", "polars-error", "polars-expr", @@ -2342,8 +3476,9 @@ dependencies = [ "polars-ops", "polars-parquet", "polars-plan", + "polars-time", "polars-utils", - "rand 0.8.5", + "rand 0.9.4", "rayon", "recursive", "slotmap", @@ -2353,9 +3488,9 @@ dependencies = [ [[package]] name = "polars-time" -version = "0.46.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d192efbdab516d28b3fab1709a969e3385bd5cda050b7c9aa9e2502a01fda879" +checksum = "72e80404e1e418c997230e3b2972c3be331f45df8bdd3150fe3bef562c7a332f" dependencies = [ "atoi_simd", "bytemuck", @@ -2363,7 +3498,6 @@ dependencies = [ "chrono-tz", "now", "num-traits", - "once_cell", "polars-arrow", "polars-compute", "polars-core", @@ -2377,26 +3511,37 @@ dependencies = [ [[package]] name = "polars-utils" -version = "0.46.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f6c8166a4a7fbc15b87c81645ed9e1f0651ff2e8c96cafc40ac5bf43441a10" +checksum = "c97cabf53eb8fbf6050cde3fef8f596c51cc25fd7d55fbde108d815ee6674abf" dependencies = [ - "ahash", + "argminmax", + "bincode", "bytemuck", "bytes", "compact_str", - "hashbrown 0.15.5", + "either", + "flate2", + "foldhash 0.2.0", + "half", + "hashbrown 0.16.1", "indexmap", "libc", "memmap2", + "num-derive", "num-traits", - "once_cell", "polars-error", - "rand 0.8.5", + "rand 0.9.4", "raw-cpuid", "rayon", + "regex", + "rmp-serde", + "serde", + "serde_json", + "serde_stacker", + "slotmap", "stacker", - "sysinfo", + "uuid", "version_check", ] @@ -2408,9 +3553,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -2430,6 +3575,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -2448,7 +3603,7 @@ dependencies = [ "clap", "h3o", "hex", - "hmac", + "hmac 0.13.0", "lasso", "libc", "metrics", @@ -2456,14 +3611,14 @@ dependencies = [ "parking_lot", "pmtiles", "polars", - "rand 0.9.2", + "rand 0.10.1", "rayon", - "reqwest", + "reqwest 0.13.3", "rust_xlsxwriter", "rustc-hash", "serde", "serde_json", - "sha2", + "sha2 0.11.0", "tokio", "tower", "tower-http", @@ -2475,9 +3630,9 @@ dependencies = [ [[package]] name = "psm" -version = "0.1.29" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa96cb91275ed31d6da3e983447320c4eb219ac180fa1679a0889ff32861e2d" +checksum = "645dbe486e346d9b5de3ef16ede18c26e6c70ad97418f4874b8b1889d6e761ea" dependencies = [ "ar_archive_writer", "cc", @@ -2498,6 +3653,26 @@ dependencies = [ "winapi", ] +[[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quick-xml" +version = "0.39.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721da970c312655cde9b4ffe0547f20a8494866a4af5ff51f18b7c633d0c870b" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "quinn" version = "0.11.9" @@ -2510,9 +3685,9 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls", - "socket2", - "thiserror 2.0.18", + "rustls 0.23.40", + "socket2 0.6.3", + "thiserror", "tokio", "tracing", "web-time", @@ -2520,20 +3695,21 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ + "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.2", + "rand 0.9.4", "ring", "rustc-hash", - "rustls", + "rustls 0.23.40", "rustls-pki-types", "slab", - "thiserror 2.0.18", + "thiserror", "tinyvec", "tracing", "web-time", @@ -2548,16 +3724,16 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.6.3", "tracing", "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -2569,34 +3745,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] -name = "rand" -version = "0.8.5" +name = "r-efi" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ - "rand_chacha 0.9.0", + "rand_chacha", "rand_core 0.9.5", ] [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "rand" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", ] [[package]] @@ -2628,13 +3800,19 @@ dependencies = [ ] [[package]] -name = "rand_distr" -version = "0.4.3" +name = "rand_core" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_distr" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.9.4", ] [[package]] @@ -2646,6 +3824,15 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rapidhash" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" +dependencies = [ + "rustversion", +] + [[package]] name = "raw-cpuid" version = "11.6.0" @@ -2657,9 +3844,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -2728,10 +3915,16 @@ dependencies = [ ] [[package]] -name = "regex-syntax" -version = "0.8.9" +name = "regex-lite" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" @@ -2741,33 +3934,29 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64", "bytes", - "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "h2 0.4.14", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", + "hyper 1.9.0", + "hyper-rustls 0.27.9", "hyper-util", "js-sys", "log", - "mime", - "native-tls", "percent-encoding", "pin-project-lite", "quinn", - "rustls", + "rustls 0.23.40", + "rustls-native-certs", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-native-tls", - "tokio-rustls", + "tokio-rustls 0.26.4", "tokio-util", "tower", "tower-http", @@ -2775,11 +3964,66 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", + "wasm-streams 0.4.2", "web-sys", "webpki-roots", ] +[[package]] +name = "reqwest" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.14", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.9.0", + "hyper-rustls 0.27.9", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.40", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls 0.26.4", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams 0.5.0", + "web-sys", +] + +[[package]] +name = "rfc6979" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint 0.4.9", + "hmac 0.12.1", + "zeroize", +] + [[package]] name = "ring" version = "0.17.14" @@ -2795,19 +4039,91 @@ dependencies = [ ] [[package]] -name = "rust_xlsxwriter" -version = "0.79.4" +name = "rmp" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c743cb9f2a4524676020e26ee5f298445a82d882b09956811b1e78ca7e42b440" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rmp-serde" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +dependencies = [ + "rmp", + "serde", +] + +[[package]] +name = "rust-ini" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rust-s3" +version = "0.37.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeedb13abdaa7e48d391de05b0569b37fa0a7a64a668dff6ffb2141ad0c2527e" +dependencies = [ + "async-trait", + "aws-creds", + "aws-region", + "base64", + "bytes", + "cfg-if", + "futures-util", + "hex", + "hmac 0.12.1", + "http 1.4.0", + "log", + "maybe-async", + "md5", + "percent-encoding", + "quick-xml 0.38.4", + "reqwest 0.12.28", + "serde", + "serde_derive", + "serde_json", + "sha2 0.10.9", + "sysinfo", + "thiserror", + "time", + "tokio", + "tokio-stream", + "url", +] + +[[package]] +name = "rust_xlsxwriter" +version = "0.94.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b61e4bcbf5d8d3d2149ffa462e1cec5259108e59f03af9e4e4ddebe6cd7c67f7" dependencies = [ "zip", ] [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] [[package]] name = "rustix" @@ -2824,28 +4140,41 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys 0.11.0", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.36" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "aws-lc-rs", + "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.103.13", "subtle", "zeroize", ] @@ -2856,27 +4185,64 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe 0.2.1", + "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.5.1", + "security-framework", ] [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "web-time", "zeroize", ] [[package]] -name = "rustls-webpki" -version = "0.103.9" +name = "rustls-platform-verifier" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls 0.23.40", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki 0.103.13", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", @@ -2892,19 +4258,34 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -2912,23 +4293,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "security-framework" -version = "2.11.1" +name = "sct" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "bitflags", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", + "ring", + "untrusted", +] + +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", ] [[package]] name = "security-framework" -version = "3.5.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags", "core-foundation 0.10.1", @@ -2939,14 +4331,20 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", ] +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "serde" version = "1.0.228" @@ -3001,6 +4399,38 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_stacker" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4936375d50c4be7eff22293a9344f8e46f323ed2b3c243e52f89138d9bb0f4a" +dependencies = [ + "serde", + "serde_core", + "stacker", +] + +[[package]] +name = "serde_tuple" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af196b9c06f0aa5555ab980c01a2527b0f67517da8d68b1731b9d4764846a6f" +dependencies = [ + "serde", + "serde_tuple_macros", +] + +[[package]] +name = "serde_tuple_macros" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3a1e7d2eadec84deabd46ae061bf480a91a6bce74d25dad375bd656f2e19d8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3013,6 +4443,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + [[package]] name = "sha2" version = "0.10.9" @@ -3020,8 +4461,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -3039,6 +4491,16 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a0c28ca5908dbdbcd52e6fdaa00358ab88637f8ab33e1f188dd510eb44b53d" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" version = "1.4.8" @@ -3050,10 +4512,30 @@ dependencies = [ ] [[package]] -name = "simd-adler32" -version = "0.3.8" +name = "signature" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] [[package]] name = "simdutf8" @@ -3063,15 +4545,15 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "siphasher" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] name = "sketches-ddsketch" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e9a774a6c28142ac54bb25d25562e6bcf957493a184f15ad4eebccb23e410a" +checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b" [[package]] name = "slab" @@ -3102,21 +4584,60 @@ checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" [[package]] name = "socket2" -version = "0.6.2" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" + +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der", ] [[package]] name = "sqlparser" -version = "0.53.0" +version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05a528114c392209b3264855ad491fcce534b94a38771b0a0b97a79379275ce8" +checksum = "505aa16b045c4c1375bf5f125cce3813d0176325bfe9ffc4a903f423de7774ff" dependencies = [ "log", + "recursive", + "sqlparser_derive", +] + +[[package]] +name = "sqlparser_derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028e551d5e270b31b9f3ea271778d9d827148d4287a5d96167b6bb9787f5cc38" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -3127,15 +4648,15 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "stacker" -version = "0.1.22" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" +checksum = "640c8cdd92b6b12f5bcb1803ca3bbf5ab96e5e6b6b96b9ab77dabe9e880b3190" dependencies = [ "cc", "cfg-if", "libc", "psm", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3196,14 +4717,13 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.26.4" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ "heck", "proc-macro2", "quote", - "rustversion", "syn", ] @@ -3214,10 +4734,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] -name = "syn" -version = "2.0.114" +name = "symlink" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -3246,14 +4772,15 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.33.1" +version = "0.37.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" +checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" dependencies = [ - "core-foundation-sys", "libc", "memchr", "ntapi", + "objc2-core-foundation", + "objc2-io-kit", "windows", ] @@ -3279,26 +4806,10 @@ dependencies = [ ] [[package]] -name = "tempfile" -version = "3.24.0" +name = "tagptr" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" -dependencies = [ - "fastrand", - "getrandom 0.3.4", - "once_cell", - "rustix 1.1.3", - "windows-sys 0.61.2", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "thiserror" @@ -3306,18 +4817,7 @@ version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "thiserror-impl", ] [[package]] @@ -3340,6 +4840,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "tilejson" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09df9bd16a6684e8c9962ef4a2e19c42fe21424c55ee99d71ebc55935a2f1eb6" +dependencies = [ + "serde", + "serde_json", + "serde_tuple", + "thiserror", +] + [[package]] name = "time" version = "0.3.47" @@ -3382,9 +4894,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -3392,9 +4904,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -3407,9 +4919,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.49.0" +version = "1.52.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" dependencies = [ "bytes", "libc", @@ -3417,16 +4929,16 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.3", "tokio-macros", "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -3434,12 +4946,12 @@ dependencies = [ ] [[package]] -name = "tokio-native-tls" -version = "0.3.1" +name = "tokio-rustls" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "native-tls", + "rustls 0.21.12", "tokio", ] @@ -3449,7 +4961,18 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls", + "rustls 0.23.40", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", "tokio", ] @@ -3485,21 +5008,20 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" dependencies = [ "async-compression", "bitflags", "bytes", "futures-core", "futures-util", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", "http-range-header", "httpdate", - "iri-string", "mime", "mime_guess", "percent-encoding", @@ -3510,6 +5032,7 @@ dependencies = [ "tower-layer", "tower-service", "tracing", + "url", ] [[package]] @@ -3538,12 +5061,13 @@ dependencies = [ [[package]] name = "tracing-appender" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" dependencies = [ "crossbeam-channel", - "thiserror 2.0.18", + "symlink", + "thiserror", "time", "tracing-subscriber", ] @@ -3582,9 +5106,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "nu-ansi-term", @@ -3605,10 +5129,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] -name = "typenum" -version = "1.19.0" +name = "twox-hash" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" + +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "unicase" @@ -3618,9 +5154,9 @@ checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-normalization" @@ -3642,9 +5178,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-width" @@ -3652,12 +5188,24 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + [[package]] name = "url" version = "2.5.8" @@ -3690,12 +5238,13 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.20.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.2", "js-sys", + "serde_core", "wasm-bindgen", ] @@ -3711,18 +5260,34 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f54a172d0620933a27a4360d3db3e2ae0dd6cceae9730751a036bbf182c4b23" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -3740,18 +5305,27 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" dependencies = [ "cfg-if", "once_cell", @@ -3762,23 +5336,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.58" +version = "0.4.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" dependencies = [ - "cfg-if", - "futures-util", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3786,9 +5356,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" dependencies = [ "bumpalo", "proc-macro2", @@ -3799,13 +5369,35 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + [[package]] name = "wasm-streams" version = "0.4.2" @@ -3820,10 +5412,35 @@ dependencies = [ ] [[package]] -name = "web-sys" -version = "0.3.85" +name = "wasm-streams" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" dependencies = [ "js-sys", "wasm-bindgen", @@ -3840,10 +5457,19 @@ dependencies = [ ] [[package]] -name = "webpki-roots" -version = "1.0.6" +name = "webpki-root-certs" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" dependencies = [ "rustls-pki-types", ] @@ -3864,6 +5490,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -3872,24 +5507,37 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.57.0" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-core 0.57.0", - "windows-targets 0.52.6", + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", ] [[package]] name = "windows-core" -version = "0.57.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement 0.57.0", - "windows-interface 0.57.0", - "windows-result 0.1.2", - "windows-targets 0.52.6", + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] @@ -3898,22 +5546,22 @@ version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", - "windows-link", + "windows-implement", + "windows-interface", + "windows-link 0.2.1", "windows-result 0.4.1", - "windows-strings", + "windows-strings 0.5.1", ] [[package]] -name = "windows-implement" -version = "0.57.0" +name = "windows-future" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "proc-macro2", - "quote", - "syn", + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", ] [[package]] @@ -3927,17 +5575,6 @@ dependencies = [ "syn", ] -[[package]] -name = "windows-interface" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "windows-interface" version = "0.59.3" @@ -3949,30 +5586,46 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + [[package]] name = "windows-registry" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-link", + "windows-link 0.2.1", "windows-result 0.4.1", - "windows-strings", + "windows-strings 0.5.1", ] [[package]] name = "windows-result" -version = "0.1.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-targets 0.52.6", + "windows-link 0.1.3", ] [[package]] @@ -3981,7 +5634,16 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", ] [[package]] @@ -3990,7 +5652,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -4026,7 +5688,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -4051,7 +5713,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link", + "windows-link 0.2.1", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", @@ -4062,6 +5724,15 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -4163,12 +5834,106 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" [[package]] name = "xxhash-rust" @@ -4178,9 +5943,9 @@ checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -4189,9 +5954,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -4201,18 +5966,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.38" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57cf3aa6855b23711ee9852dfc97dfaa51c45feaba5b645d0c777414d494a961" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.38" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a616990af1a287837c4fe6596ad77ef57948f787e46ce28e166facc0cc1cb75" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -4221,18 +5986,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -4248,9 +6013,9 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -4259,9 +6024,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -4270,9 +6035,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", @@ -4281,32 +6046,29 @@ dependencies = [ [[package]] name = "zip" -version = "2.4.2" +version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +checksum = "c42e33efc22a0650c311c2ef19115ce232583abbe80850bc8b66509ebef02de0" dependencies = [ - "arbitrary", "crc32fast", - "crossbeam-utils", - "displaydoc", "flate2", "indexmap", "memchr", - "thiserror 2.0.18", + "typed-path", "zopfli", ] [[package]] name = "zlib-rs" -version = "0.6.0" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7948af682ccbc3342b6e9420e8c51c1fe5d7bf7756002b4a3c6cabfe96a7e3c" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" [[package]] name = "zmij" -version = "1.0.19" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "zopfli" diff --git a/server-rs/Cargo.toml b/server-rs/Cargo.toml index 01a06eb..8fda10d 100644 --- a/server-rs/Cargo.toml +++ b/server-rs/Cargo.toml @@ -9,8 +9,8 @@ clap = { version = "4", features = ["derive", "env"] } axum = "0.8" tower-http = { version = "0.6", features = ["cors", "fs", "compression-gzip", "compression-zstd", "trace"] } tokio = { version = "1", features = ["full"] } -polars = { version = "0.46", features = ["parquet", "lazy", "dtype-struct", "dtype-u8", "dtype-u16", "dtype-i8", "dtype-i16", "round_series"] } -h3o = "0.7" +polars = { version = "0.53", features = ["parquet", "lazy", "dtype-struct", "dtype-u8", "dtype-u16", "dtype-i8", "dtype-i16", "round_series"] } +h3o = "0.9" serde = { version = "1", features = ["derive"] } serde_json = "1" parking_lot = "0.12" @@ -21,14 +21,14 @@ tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } tracing-appender = "0.2" metrics = "0.24" -metrics-exporter-prometheus = "0.16" -reqwest = { version = "0.12", features = ["rustls-tls", "json", "stream"] } +metrics-exporter-prometheus = "0.18" +reqwest = { version = "0.13", features = ["rustls", "json", "stream", "form"] } urlencoding = "2" -rust_xlsxwriter = "0.79" -pmtiles = { version = "0.12", features = ["mmap-async-tokio"] } -rand = "0.9" -hmac = "0.12" -sha2 = "0.10" +rust_xlsxwriter = "0.94" +pmtiles = { version = "0.23", features = ["mmap-async-tokio"] } +rand = "0.10" +hmac = "0.13" +sha2 = "0.11" hex = "0.4" tower = { version = "0.5", features = ["limit"] } libc = "0.2" diff --git a/server-rs/logs/server.log.2026-05-04 b/server-rs/logs/server.log.2026-05-04 deleted file mode 100644 index 42aa26f..0000000 --- a/server-rs/logs/server.log.2026-05-04 +++ /dev/null @@ -1,1620 +0,0 @@ -2026-05-04T16:24:26.361386Z INFO property_map_server: Prometheus metrics initialized -2026-05-04T16:24:26.361534Z INFO property_map_server: Loading property data from /app/data/properties.parquet, /app/data/postcode.parquet -2026-05-04T16:24:26.361545Z INFO property_map_server::data::property: Loading postcode features from "/app/data/postcode.parquet" -2026-05-04T16:24:26.536642Z INFO property_map_server::data::property: Postcode features loaded rows=1263786 -2026-05-04T16:24:26.536654Z INFO property_map_server::data::property: Loading properties from "/app/data/properties.parquet" -2026-05-04T16:24:29.416013Z INFO property_map_server::data::property: Properties joined with postcodes rows=15268176 -2026-05-04T16:24:29.416046Z INFO property_map_server::data::property: Feature columns from config numeric=63 enums=6 total=69 -2026-05-04T16:24:32.703052Z WARN property_map_server::data::property: Dropped properties with missing postcode coordinates rows=743076 -2026-05-04T16:24:32.703062Z INFO property_map_server::data::property: Combined data selected rows=14525100 -2026-05-04T16:24:32.828025Z INFO property_map_server::data::property: Extracting numeric feature columns -2026-05-04T16:24:33.156275Z INFO property_map_server::data::property: Computing histograms for numeric features -2026-05-04T16:24:34.291666Z INFO property_map_server::data::property: Extracting string columns -2026-05-04T16:24:35.567759Z INFO property_map_server::data::property: Building enum features -2026-05-04T16:24:36.731373Z INFO property_map_server::data::property: Extracting renovation history -2026-05-04T16:24:44.067059Z INFO property_map_server: Prometheus metrics initialized -2026-05-04T16:24:44.067224Z INFO property_map_server: Loading property data from /app/data/properties.parquet, /app/data/postcode.parquet -2026-05-04T16:24:44.067239Z INFO property_map_server::data::property: Loading postcode features from "/app/data/postcode.parquet" -2026-05-04T16:24:44.132872Z INFO property_map_server::data::property: Postcode features loaded rows=1263786 -2026-05-04T16:24:44.132883Z INFO property_map_server::data::property: Loading properties from "/app/data/properties.parquet" -2026-05-04T16:24:46.752832Z INFO property_map_server::data::property: Properties joined with postcodes rows=15268176 -2026-05-04T16:24:46.752925Z INFO property_map_server::data::property: Feature columns from config numeric=63 enums=6 total=69 -2026-05-04T16:24:51.431716Z WARN property_map_server::data::property: Dropped properties with missing postcode coordinates rows=743076 -2026-05-04T16:24:51.431726Z INFO property_map_server::data::property: Combined data selected rows=14525100 -2026-05-04T16:24:51.555919Z INFO property_map_server::data::property: Extracting numeric feature columns -2026-05-04T16:24:51.929914Z INFO property_map_server::data::property: Computing histograms for numeric features -2026-05-04T16:24:53.386992Z INFO property_map_server::data::property: Extracting string columns -2026-05-04T16:24:54.755992Z INFO property_map_server::data::property: Building enum features -2026-05-04T16:24:55.968130Z INFO property_map_server::data::property: Extracting renovation history -2026-05-04T16:24:58.228125Z INFO property_map_server::data::property: Renovation history extracted properties_with_events=1741939 -2026-05-04T16:24:58.228135Z INFO property_map_server::data::property: Sorting rows by spatial locality -2026-05-04T16:24:59.203922Z INFO property_map_server::data::property: Building interned strings -2026-05-04T16:25:04.927897Z INFO property_map_server::data::property: Transposing to row-major layout (spatially sorted, quantized to u16) -2026-05-04T16:25:08.105335Z INFO property_map_server::data::property: Data loading complete -2026-05-04T16:25:09.830771Z INFO property_map_server: Allocator trim label="property data load" trimmed=true rss_before_mib=12855.6 rss_after_mib=3403.5 released_mib=9452.2 -2026-05-04T16:25:09.830783Z INFO property_map_server: Property data loaded rows=14525100 features=69 enums=6 -2026-05-04T16:25:09.830786Z INFO property_map_server: Building spatial grid index (0.01° cells) -2026-05-04T16:25:09.942635Z INFO property_map_server: Precomputing H3 cells at resolution 12 -2026-05-04T16:25:09.942645Z INFO property_map_server::data::property: Precomputing H3 cells at resolution 12 -2026-05-04T16:25:10.306827Z INFO property_map_server::data::property: H3 precomputation complete (14525100 cells) -2026-05-04T16:25:10.306857Z INFO property_map_server: Loading POI data from /app/data/filtered_uk_pois.parquet -2026-05-04T16:25:10.306864Z INFO property_map_server::data::poi: Loading POI data from "/app/data/filtered_uk_pois.parquet"... -2026-05-04T16:25:10.369598Z INFO property_map_server::data::poi: Loaded 567534 POIs -2026-05-04T16:25:10.513535Z INFO property_map_server::data::poi: POI string columns interned category_unique=94 group_unique=11 emoji_unique=71 -2026-05-04T16:25:10.514657Z INFO property_map_server::data::poi: POI data loading complete. -2026-05-04T16:25:10.560531Z INFO property_map_server: Allocator trim label="poi data load" trimmed=true rss_before_mib=3810.0 rss_after_mib=3621.0 released_mib=189.0 -2026-05-04T16:25:10.560541Z INFO property_map_server: POI data loaded pois=567534 -2026-05-04T16:25:10.560543Z INFO property_map_server: Building POI spatial grid index -2026-05-04T16:25:10.567362Z INFO property_map_server: Loading place data from /app/data/places.parquet -2026-05-04T16:25:10.567374Z INFO property_map_server::data::places: Loading place data from "/app/data/places.parquet"... -2026-05-04T16:25:10.569093Z INFO property_map_server::data::places: Loaded 3474 places -2026-05-04T16:25:10.570039Z INFO property_map_server::data::places: Place data loaded places=3474 types=2 with_population=71 with_city=3392 -2026-05-04T16:25:10.573398Z INFO property_map_server: Allocator trim label="place data load" trimmed=true rss_before_mib=3630.1 rss_after_mib=3625.7 released_mib=4.4 -2026-05-04T16:25:10.573403Z INFO property_map_server: Place data loaded places=3474 -2026-05-04T16:25:10.573411Z INFO property_map_server: Loading postcode boundaries from /app/data/postcode_boundaries -2026-05-04T16:25:10.573415Z INFO property_map_server::data::postcodes: Loading postcode boundaries from "/app/data/postcode_boundaries" -2026-05-04T16:25:10.584080Z INFO property_map_server::data::postcodes: Found GeoJSON files to process files=2361 -2026-05-04T16:25:18.723063Z INFO property_map_server::data::postcodes: Postcode boundary data ready postcodes=1490140 -2026-05-04T16:25:19.054258Z INFO property_map_server: Allocator trim label="postcode boundary load" trimmed=true rss_before_mib=10946.3 rss_after_mib=10753.6 released_mib=192.8 -2026-05-04T16:25:19.054270Z INFO property_map_server: Postcode boundaries loaded postcodes=1490140 -2026-05-04T16:25:19.195126Z INFO property_map_server::data::postcodes: Outcode data derived from postcodes outcodes=2361 -2026-05-04T16:25:19.195176Z INFO property_map_server: Loading PMTiles from /app/data/uk.pmtiles -2026-05-04T16:25:19.212588Z INFO property_map_server: PMTiles loaded successfully -2026-05-04T16:25:19.249528Z INFO property_map_server: No --dist provided; static serving and OG injection disabled -2026-05-04T16:25:19.314387Z INFO property_map_server: Screenshot service configured: http://screenshot:8002 -2026-05-04T16:25:19.314534Z INFO property_map_server: Precomputed features response groups=8 -2026-05-04T16:25:19.314548Z INFO property_map_server: PocketBase configured: http://pocketbase:8090 -2026-05-04T16:25:19.420784Z INFO property_map_server::pocketbase: PocketBase users collection already has all required fields -2026-05-04T16:25:19.449243Z INFO property_map_server::pocketbase: PocketBase collection 'saved_searches' API rules updated -2026-05-04T16:25:19.465332Z INFO property_map_server::pocketbase: PocketBase collection 'saved_properties' API rules updated -2026-05-04T16:25:23.979357Z INFO property_map_server::pocketbase: PocketBase meta.appURL set to https://perfect-postcodes.co.uk/pb -2026-05-04T16:25:24.002744Z INFO property_map_server::pocketbase: PocketBase OAuth configured on users collection -2026-05-04T16:25:24.002796Z INFO property_map_server: Gemini configured (model: gemini-3-flash-preview) -2026-05-04T16:25:24.002816Z INFO property_map_server: Loading travel time data from /app/data/travel-times -2026-05-04T16:25:24.067846Z INFO property_map_server::data::travel_time: Travel time mode discovered mode="bicycle" destinations=2753 -2026-05-04T16:25:24.094429Z INFO property_map_server::data::travel_time: Travel time mode discovered mode="walking" destinations=2753 -2026-05-04T16:25:24.164916Z INFO property_map_server::data::travel_time: Travel time mode discovered mode="car" destinations=2753 -2026-05-04T16:25:24.195787Z INFO property_map_server::data::travel_time: Travel time mode discovered mode="transit" destinations=2752 -2026-05-04T16:25:24.195823Z INFO property_map_server: Travel time store loaded modes=4 -2026-05-04T16:25:24.195890Z INFO property_map_server: Precomputed AI filters system prompt -2026-05-04T16:25:59.854933Z INFO property_map_server: All memory pages locked (mlockall) -2026-05-04T16:25:59.854973Z INFO property_map_server: Server listening on 0.0.0.0:8001 -2026-05-04T16:33:12.504278Z INFO property_map_server::routes::pois: GET /api/pois results=337 candidates=34206 categories=1 categories_raw="Bakery" ms=1.5 -2026-05-04T16:33:12.507287Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=501590 parallel=true cells_before_filter=43 cells_after_filter=34 truncated=false bounds=51.4639,-0.2078,51.5523,0.0016 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:4:4" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=4.3 json_ms=0.0 total_ms=4.3 -2026-05-04T16:33:12.753975Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=501590 filters=2 travel=0 total=8128 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:4:4" ms=2.3 -2026-05-04T16:33:15.076379Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=156212 parallel=true cells_before_filter=0 cells_after_filter=0 truncated=false bounds=51.4941,-0.1494,51.5403,-0.0399 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:4:4" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.7 json_ms=0.0 total_ms=0.7 -2026-05-04T16:33:15.081953Z INFO property_map_server::routes::pois: GET /api/pois results=146 candidates=15316 categories=1 categories_raw="Bakery" ms=0.3 -2026-05-04T16:33:15.777094Z INFO property_map_server::routes::pois: GET /api/pois results=1234 candidates=191594 categories=1 categories_raw="Bakery" ms=2.3 -2026-05-04T16:33:15.813170Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=6 rows=5259222 parallel=true cells_before_filter=48 cells_after_filter=48 truncated=false bounds=50.6262,-1.8129,51.8797,1.1415 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:4:4" fields=0 travel_entries=0 grid_ms=0.1 agg_ms=38.3 json_ms=0.0 total_ms=38.4 -2026-05-04T16:33:16.051536Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=5259222 filters=2 travel=0 total=245108 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:4:4" ms=27.8 -2026-05-04T16:33:17.789858Z INFO property_map_server::routes::pois: GET /api/pois results=3 candidates=173 categories=1 categories_raw="Bakery" ms=0.0 -2026-05-04T16:33:17.789972Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=7000 parallel=false cells_before_filter=0 cells_after_filter=0 truncated=false bounds=52.4593,0.8156,52.5646,1.0709 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:4:4" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.1 json_ms=0.0 total_ms=0.1 -2026-05-04T16:33:17.996574Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=7000 filters=2 travel=0 total=0 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:4:4" ms=0.1 -2026-05-04T16:33:18.491913Z INFO property_map_server::routes::pois: GET /api/pois results=0 candidates=13 categories=1 categories_raw="Bakery" ms=0.0 -2026-05-04T16:33:18.491954Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=2208 parallel=false cells_before_filter=0 cells_after_filter=0 truncated=false bounds=52.4951,0.9091,52.5324,0.9996 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:4:4" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.0 json_ms=0.0 total_ms=0.0 -2026-05-04T16:33:18.755504Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=2208 filters=2 travel=0 total=0 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:4:4" ms=0.0 -2026-05-04T16:33:19.464638Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=5813 parallel=false cells_before_filter=0 cells_after_filter=0 truncated=false bounds=52.4761,0.8660,52.5482,1.0407 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:4:4" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.1 json_ms=0.0 total_ms=0.1 -2026-05-04T16:33:19.465914Z INFO property_map_server::routes::pois: GET /api/pois results=2 candidates=106 categories=1 categories_raw="Bakery" ms=0.0 -2026-05-04T16:33:19.713721Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=5813 filters=2 travel=0 total=0 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:4:4" ms=0.1 -2026-05-04T16:33:21.541755Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=5813 parallel=false cells_before_filter=212 cells_after_filter=200 truncated=false bounds=52.4761,0.8660,52.5482,1.0407 filters=1 filters_raw="Outstanding primary schools within 5km:0:13" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=0.2 json_ms=0.1 total_ms=0.3 -2026-05-04T16:33:21.689938Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=5813 parallel=false cells_before_filter=0 cells_after_filter=0 truncated=false bounds=52.4761,0.8660,52.5482,1.0407 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:4:4" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=0.1 json_ms=0.0 total_ms=0.1 -2026-05-04T16:33:21.935287Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=5813 parallel=false cells_before_filter=212 cells_after_filter=200 truncated=false bounds=52.4761,0.8660,52.5482,1.0407 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.3 json_ms=0.2 total_ms=0.5 -2026-05-04T16:33:22.184166Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=5813 filters=2 travel=0 total=5813 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" ms=0.1 -2026-05-04T16:33:23.065248Z INFO property_map_server::routes::hexagon_stats: GET /api/hexagon-stats h3=89194162883ffff resolution=9 total_count=8 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" ms=0.1 -2026-05-04T16:33:23.382213Z INFO property_map_server::routes::pois: GET /api/pois results=2 candidates=100 categories=1 categories_raw="Bakery" ms=0.0 -2026-05-04T16:33:23.382464Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=3843 parallel=false cells_before_filter=136 cells_after_filter=131 truncated=false bounds=52.4761,0.8897,52.5482,1.0170 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.2 json_ms=0.1 total_ms=0.3 -2026-05-04T16:33:23.632327Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=3843 filters=2 travel=0 total=3843 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" ms=0.0 -2026-05-04T16:33:24.723887Z INFO property_map_server::routes::screenshot: Fetching screenshot from: http://screenshot:8002/screenshot?og=1&lat=52.5122&lon=0.9534&zoom=13.1&school=primary%3Aoutstanding%3A5%3A0%3A13&school=secondary%3Aoutstanding%3A5%3A0%3A4&poi=Bakery -2026-05-04T16:33:28.689144Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:33:28.689206Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:33:28.797267Z INFO property_map_server::routes::pois: GET /api/pois results=2 candidates=106 categories=1 categories_raw="Bakery" ms=0.0 -2026-05-04T16:33:29.148232Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=5749 filters=2 travel=0 total=5749 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" ms=0.0 -2026-05-04T16:33:31.157780Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:33:31.158900Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:33:32.275864Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:33:32.275869Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:33:32.654590Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:33:32.655162Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=83099 parallel=true cells_before_filter=0 cells_after_filter=0 truncated=false bounds=51.4958,-0.1756,51.5342,-0.0844 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.6 json_ms=0.0 total_ms=0.6 -2026-05-04T16:33:32.655169Z INFO property_map_server::routes::pois: GET /api/pois results=136 candidates=14319 categories=1 categories_raw="Bakery" ms=0.6 -2026-05-04T16:33:32.656028Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:33:32.656467Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=4490 parallel=false cells_before_filter=139 cells_after_filter=126 truncated=false bounds=52.4892,0.8813,52.5352,1.0255 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.2 json_ms=0.1 total_ms=0.3 -2026-05-04T16:33:32.657565Z INFO property_map_server::routes::pois: GET /api/pois results=2 candidates=92 categories=1 categories_raw="Bakery" ms=0.0 -2026-05-04T16:33:32.853023Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:33:32.853024Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:33:32.908907Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=83099 filters=2 travel=0 total=0 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" ms=0.6 -2026-05-04T16:33:33.165458Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:33:33.165461Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:33:33.408861Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:33:33.408859Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:33:33.636864Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:33:33.636880Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:33:33.844608Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:33:33.844612Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:33:34.174004Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:33:34.175086Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:33:34.378600Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:33:34.378602Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:33:34.504438Z INFO property_map_server::routes::pois: GET /api/pois results=620 candidates=66807 categories=1 categories_raw="Bakery" ms=1.3 -2026-05-04T16:33:34.602799Z WARN property_map_server::routes::screenshot: Screenshot service returned 500 Internal Server Error: {"error":"Screenshot failed"} -2026-05-04T16:33:34.602833Z ERROR tower_http::trace::on_failure: response failed classification=Status code: 502 Bad Gateway latency=9878 ms -2026-05-04T16:33:34.756167Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=1555849 filters=2 travel=0 total=452550 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" ms=8.5 -2026-05-04T16:33:35.982661Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=83099 parallel=true cells_before_filter=0 cells_after_filter=0 truncated=false bounds=51.4958,-0.1756,51.5342,-0.0844 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.5 json_ms=0.0 total_ms=0.5 -2026-05-04T16:33:35.984926Z INFO property_map_server::routes::pois: GET /api/pois results=136 candidates=14319 categories=1 categories_raw="Bakery" ms=0.3 -2026-05-04T16:33:36.255875Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=83099 filters=2 travel=0 total=0 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" ms=0.4 -2026-05-04T16:33:39.615709Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:33:39.615713Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:33:39.807124Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=71850 parallel=true cells_before_filter=281 cells_after_filter=221 truncated=false bounds=51.5024,-0.1686,51.5276,-0.0914 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=4.0 json_ms=0.1 total_ms=4.2 -2026-05-04T16:33:40.495077Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=71850 parallel=true cells_before_filter=281 cells_after_filter=221 truncated=false bounds=51.5024,-0.1686,51.5276,-0.0914 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=3.5 json_ms=0.2 total_ms=3.7 -2026-05-04T16:34:07.509866Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:34:07.509946Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:34:07.611127Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:34:07.612193Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:34:07.889701Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=2.0 json_ms=0.7 total_ms=2.7 -2026-05-04T16:34:14.334723Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:34:14.337390Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:34:14.491807Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:34:14.493124Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:34:14.778805Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=3.2 json_ms=0.6 total_ms=3.8 -2026-05-04T16:34:43.581458Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:34:43.588215Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:34:43.713295Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:34:43.714928Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:34:43.997860Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.7 json_ms=0.5 total_ms=2.2 -2026-05-04T16:34:51.018981Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:34:51.018985Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:34:51.170017Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:34:51.172856Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:34:51.445786Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.5 json_ms=0.4 total_ms=2.0 -2026-05-04T16:35:33.953632Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:35:33.954861Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:35:34.097014Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:35:34.098093Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:35:34.374003Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.5 json_ms=0.5 total_ms=2.0 -2026-05-04T16:35:42.141838Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:35:42.143317Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:35:42.274342Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:35:42.274559Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:35:42.545901Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.5 json_ms=0.4 total_ms=1.9 -2026-05-04T16:36:20.658389Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:36:20.658391Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:36:20.795952Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:36:20.797839Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:36:21.077586Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.5 json_ms=0.5 total_ms=2.0 -2026-05-04T16:36:30.210679Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:36:30.210688Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:36:30.347013Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:36:30.348059Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:36:30.634397Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.3 json_ms=0.4 total_ms=1.8 -2026-05-04T16:37:00.881004Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:37:00.881756Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:37:01.014400Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:37:01.015618Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:37:01.298842Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.5 json_ms=0.5 total_ms=2.1 -2026-05-04T16:37:07.528356Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:37:07.534379Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:37:07.683099Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:37:07.684122Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:37:07.964419Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.4 json_ms=0.5 total_ms=2.0 -2026-05-04T16:41:53.484148Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:41:53.484270Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:41:53.625058Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:41:53.627511Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:41:53.941582Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.4 json_ms=0.5 total_ms=1.8 -2026-05-04T16:42:10.923902Z INFO property_map_server: Prometheus metrics initialized -2026-05-04T16:42:10.924042Z INFO property_map_server: Loading property data from /app/data/properties.parquet, /app/data/postcode.parquet -2026-05-04T16:42:10.924049Z INFO property_map_server::data::property: Loading postcode features from "/app/data/postcode.parquet" -2026-05-04T16:42:11.094026Z INFO property_map_server::data::property: Postcode features loaded rows=1263786 -2026-05-04T16:42:11.094035Z INFO property_map_server::data::property: Loading properties from "/app/data/properties.parquet" -2026-05-04T16:42:13.827665Z INFO property_map_server::data::property: Properties joined with postcodes rows=15268176 -2026-05-04T16:42:13.827705Z INFO property_map_server::data::property: Feature columns from config numeric=63 enums=6 total=69 -2026-05-04T16:42:17.729486Z WARN property_map_server::data::property: Dropped properties with missing postcode coordinates rows=743076 -2026-05-04T16:42:17.729497Z INFO property_map_server::data::property: Combined data selected rows=14525100 -2026-05-04T16:42:17.849623Z INFO property_map_server::data::property: Extracting numeric feature columns -2026-05-04T16:42:18.291464Z INFO property_map_server::data::property: Computing histograms for numeric features -2026-05-04T16:42:19.592229Z INFO property_map_server::data::property: Extracting string columns -2026-05-04T16:42:27.719470Z INFO property_map_server: Prometheus metrics initialized -2026-05-04T16:42:27.719671Z INFO property_map_server: Loading property data from /app/data/properties.parquet, /app/data/postcode.parquet -2026-05-04T16:42:27.719681Z INFO property_map_server::data::property: Loading postcode features from "/app/data/postcode.parquet" -2026-05-04T16:42:27.793178Z INFO property_map_server::data::property: Postcode features loaded rows=1263786 -2026-05-04T16:42:27.793189Z INFO property_map_server::data::property: Loading properties from "/app/data/properties.parquet" -2026-05-04T16:42:30.659605Z INFO property_map_server::data::property: Properties joined with postcodes rows=15268176 -2026-05-04T16:42:30.659645Z INFO property_map_server::data::property: Feature columns from config numeric=63 enums=6 total=69 -2026-05-04T16:42:50.820991Z INFO property_map_server: Prometheus metrics initialized -2026-05-04T16:42:50.821208Z INFO property_map_server: Loading property data from /app/data/properties.parquet, /app/data/postcode.parquet -2026-05-04T16:42:50.821219Z INFO property_map_server::data::property: Loading postcode features from "/app/data/postcode.parquet" -2026-05-04T16:42:50.918750Z INFO property_map_server::data::property: Postcode features loaded rows=1263786 -2026-05-04T16:42:50.918761Z INFO property_map_server::data::property: Loading properties from "/app/data/properties.parquet" -2026-05-04T16:42:57.851045Z INFO property_map_server: Prometheus metrics initialized -2026-05-04T16:42:57.851212Z INFO property_map_server: Loading property data from /app/data/properties.parquet, /app/data/postcode.parquet -2026-05-04T16:42:57.851222Z INFO property_map_server::data::property: Loading postcode features from "/app/data/postcode.parquet" -2026-05-04T16:42:57.931582Z INFO property_map_server::data::property: Postcode features loaded rows=1263786 -2026-05-04T16:42:57.931593Z INFO property_map_server::data::property: Loading properties from "/app/data/properties.parquet" -2026-05-04T16:43:00.770232Z INFO property_map_server::data::property: Properties joined with postcodes rows=15268176 -2026-05-04T16:43:00.770272Z INFO property_map_server::data::property: Feature columns from config numeric=63 enums=6 total=69 -2026-05-04T16:43:04.255567Z WARN property_map_server::data::property: Dropped properties with missing postcode coordinates rows=743076 -2026-05-04T16:43:04.255577Z INFO property_map_server::data::property: Combined data selected rows=14525100 -2026-05-04T16:43:04.391949Z INFO property_map_server::data::property: Extracting numeric feature columns -2026-05-04T16:43:04.774580Z INFO property_map_server::data::property: Computing histograms for numeric features -2026-05-04T16:43:06.026169Z INFO property_map_server::data::property: Extracting string columns -2026-05-04T16:43:09.128196Z INFO property_map_server::data::property: Building enum features -2026-05-04T16:43:12.906716Z INFO property_map_server::data::property: Extracting renovation history -2026-05-04T16:43:16.270759Z INFO property_map_server::data::property: Renovation history extracted properties_with_events=1741939 -2026-05-04T16:43:16.270789Z INFO property_map_server::data::property: Sorting rows by spatial locality -2026-05-04T16:43:18.401462Z INFO property_map_server::data::property: Building interned strings -2026-05-04T16:43:27.431736Z INFO property_map_server::data::property: Transposing to row-major layout (spatially sorted, quantized to u16) -2026-05-04T16:43:30.335068Z INFO property_map_server::data::property: Data loading complete -2026-05-04T16:45:19.338545Z INFO property_map_server: Prometheus metrics initialized -2026-05-04T16:45:19.338692Z INFO property_map_server: Loading property data from /app/data/properties.parquet, /app/data/postcode.parquet -2026-05-04T16:45:19.338704Z INFO property_map_server::data::property: Loading postcode features from "/app/data/postcode.parquet" -2026-05-04T16:45:19.412115Z INFO property_map_server::data::property: Postcode features loaded rows=1263786 -2026-05-04T16:45:19.412126Z INFO property_map_server::data::property: Loading properties from "/app/data/properties.parquet" -2026-05-04T16:45:22.338627Z INFO property_map_server::data::property: Properties joined with postcodes rows=15268176 -2026-05-04T16:45:22.338667Z INFO property_map_server::data::property: Feature columns from config numeric=63 enums=6 total=69 -2026-05-04T16:45:25.112569Z WARN property_map_server::data::property: Dropped properties with missing postcode coordinates rows=743076 -2026-05-04T16:45:25.112579Z INFO property_map_server::data::property: Combined data selected rows=14525100 -2026-05-04T16:45:25.239061Z INFO property_map_server::data::property: Extracting numeric feature columns -2026-05-04T16:45:25.639028Z INFO property_map_server::data::property: Computing histograms for numeric features -2026-05-04T16:45:26.973065Z INFO property_map_server::data::property: Extracting string columns -2026-05-04T16:45:28.297017Z INFO property_map_server::data::property: Building enum features -2026-05-04T16:45:30.086686Z INFO property_map_server::data::property: Extracting renovation history -2026-05-04T16:45:42.387619Z INFO property_map_server::data::property: Renovation history extracted properties_with_events=1741939 -2026-05-04T16:45:42.387628Z INFO property_map_server::data::property: Sorting rows by spatial locality -2026-05-04T16:46:26.166388Z INFO property_map_server: Prometheus metrics initialized -2026-05-04T16:46:26.166547Z INFO property_map_server: Loading property data from /app/data/properties.parquet, /app/data/postcode.parquet -2026-05-04T16:46:26.166557Z INFO property_map_server::data::property: Loading postcode features from "/app/data/postcode.parquet" -2026-05-04T16:46:26.235389Z INFO property_map_server::data::property: Postcode features loaded rows=1263786 -2026-05-04T16:46:26.235399Z INFO property_map_server::data::property: Loading properties from "/app/data/properties.parquet" -2026-05-04T16:46:29.258413Z INFO property_map_server::data::property: Properties joined with postcodes rows=15268176 -2026-05-04T16:46:29.258459Z INFO property_map_server::data::property: Feature columns from config numeric=63 enums=6 total=69 -2026-05-04T16:46:31.897499Z WARN property_map_server::data::property: Dropped properties with missing postcode coordinates rows=743076 -2026-05-04T16:46:31.897509Z INFO property_map_server::data::property: Combined data selected rows=14525100 -2026-05-04T16:46:32.031482Z INFO property_map_server::data::property: Extracting numeric feature columns -2026-05-04T16:46:32.419698Z INFO property_map_server::data::property: Computing histograms for numeric features -2026-05-04T16:46:33.763754Z INFO property_map_server::data::property: Extracting string columns -2026-05-04T16:46:35.076058Z INFO property_map_server::data::property: Building enum features -2026-05-04T16:46:36.278281Z INFO property_map_server::data::property: Extracting renovation history -2026-05-04T16:46:38.340164Z INFO property_map_server::data::property: Renovation history extracted properties_with_events=1741939 -2026-05-04T16:46:38.340172Z INFO property_map_server::data::property: Sorting rows by spatial locality -2026-05-04T16:46:39.172699Z INFO property_map_server::data::property: Building interned strings -2026-05-04T16:46:44.579476Z INFO property_map_server::data::property: Transposing to row-major layout (spatially sorted, quantized to u16) -2026-05-04T16:46:47.660894Z INFO property_map_server: Prometheus metrics initialized -2026-05-04T16:46:47.661061Z INFO property_map_server: Loading property data from /app/data/properties.parquet, /app/data/postcode.parquet -2026-05-04T16:46:47.661070Z INFO property_map_server::data::property: Loading postcode features from "/app/data/postcode.parquet" -2026-05-04T16:46:47.743195Z INFO property_map_server::data::property: Postcode features loaded rows=1263786 -2026-05-04T16:46:47.743207Z INFO property_map_server::data::property: Loading properties from "/app/data/properties.parquet" -2026-05-04T16:47:00.841916Z INFO property_map_server: Prometheus metrics initialized -2026-05-04T16:47:00.842064Z INFO property_map_server: Loading property data from /app/data/properties.parquet, /app/data/postcode.parquet -2026-05-04T16:47:00.842075Z INFO property_map_server::data::property: Loading postcode features from "/app/data/postcode.parquet" -2026-05-04T16:47:00.908914Z INFO property_map_server::data::property: Postcode features loaded rows=1263786 -2026-05-04T16:47:00.908923Z INFO property_map_server::data::property: Loading properties from "/app/data/properties.parquet" -2026-05-04T16:47:03.639664Z INFO property_map_server::data::property: Properties joined with postcodes rows=15268176 -2026-05-04T16:47:03.639695Z INFO property_map_server::data::property: Feature columns from config numeric=63 enums=6 total=69 -2026-05-04T16:47:06.148292Z WARN property_map_server::data::property: Dropped properties with missing postcode coordinates rows=743076 -2026-05-04T16:47:06.148303Z INFO property_map_server::data::property: Combined data selected rows=14525100 -2026-05-04T16:47:06.279025Z INFO property_map_server::data::property: Extracting numeric feature columns -2026-05-04T16:47:06.652207Z INFO property_map_server::data::property: Computing histograms for numeric features -2026-05-04T16:47:07.840677Z INFO property_map_server::data::property: Extracting string columns -2026-05-04T16:47:09.177884Z INFO property_map_server::data::property: Building enum features -2026-05-04T16:47:10.365612Z INFO property_map_server::data::property: Extracting renovation history -2026-05-04T16:47:12.564052Z INFO property_map_server::data::property: Renovation history extracted properties_with_events=1741939 -2026-05-04T16:47:12.564060Z INFO property_map_server::data::property: Sorting rows by spatial locality -2026-05-04T16:47:13.560189Z INFO property_map_server::data::property: Building interned strings -2026-05-04T16:47:19.116752Z INFO property_map_server::data::property: Transposing to row-major layout (spatially sorted, quantized to u16) -2026-05-04T16:51:59.629509Z INFO property_map_server: Prometheus metrics initialized -2026-05-04T16:51:59.629660Z INFO property_map_server: Loading property data from /app/data/properties.parquet, /app/data/postcode.parquet -2026-05-04T16:51:59.629669Z INFO property_map_server::data::property: Loading postcode features from "/app/data/postcode.parquet" -2026-05-04T16:51:59.697083Z INFO property_map_server::data::property: Postcode features loaded rows=1263786 -2026-05-04T16:51:59.697094Z INFO property_map_server::data::property: Loading properties from "/app/data/properties.parquet" -2026-05-04T16:52:02.401421Z INFO property_map_server::data::property: Properties joined with postcodes rows=15268176 -2026-05-04T16:52:02.401453Z INFO property_map_server::data::property: Feature columns from config numeric=63 enums=6 total=69 -2026-05-04T16:52:05.016345Z WARN property_map_server::data::property: Dropped properties with missing postcode coordinates rows=743076 -2026-05-04T16:52:05.016356Z INFO property_map_server::data::property: Combined data selected rows=14525100 -2026-05-04T16:52:05.141380Z INFO property_map_server::data::property: Extracting numeric feature columns -2026-05-04T16:52:05.500468Z INFO property_map_server::data::property: Computing histograms for numeric features -2026-05-04T16:52:06.879667Z INFO property_map_server::data::property: Extracting string columns -2026-05-04T16:52:08.239559Z INFO property_map_server::data::property: Building enum features -2026-05-04T16:52:09.441595Z INFO property_map_server::data::property: Extracting renovation history -2026-05-04T16:52:11.513183Z INFO property_map_server::data::property: Renovation history extracted properties_with_events=1741939 -2026-05-04T16:52:11.513191Z INFO property_map_server::data::property: Sorting rows by spatial locality -2026-05-04T16:52:12.358820Z INFO property_map_server::data::property: Building interned strings -2026-05-04T16:52:17.933075Z INFO property_map_server::data::property: Transposing to row-major layout (spatially sorted, quantized to u16) -2026-05-04T16:52:21.489218Z INFO property_map_server::data::property: Data loading complete -2026-05-04T16:52:22.907975Z INFO property_map_server: Allocator trim label="property data load" trimmed=true rss_before_mib=12322.4 rss_after_mib=3296.5 released_mib=9025.8 -2026-05-04T16:52:22.907987Z INFO property_map_server: Property data loaded rows=14525100 features=69 enums=6 -2026-05-04T16:52:22.907990Z INFO property_map_server: Building spatial grid index (0.01° cells) -2026-05-04T16:52:23.018806Z INFO property_map_server: Precomputing H3 cells at resolution 12 -2026-05-04T16:52:23.018816Z INFO property_map_server::data::property: Precomputing H3 cells at resolution 12 -2026-05-04T16:52:23.436522Z INFO property_map_server::data::property: H3 precomputation complete (14525100 cells) -2026-05-04T16:52:23.436556Z INFO property_map_server: Loading POI data from /app/data/filtered_uk_pois.parquet -2026-05-04T16:52:23.436564Z INFO property_map_server::data::poi: Loading POI data from "/app/data/filtered_uk_pois.parquet"... -2026-05-04T16:52:23.506644Z INFO property_map_server::data::poi: Loaded 567534 POIs -2026-05-04T16:52:23.661450Z INFO property_map_server::data::poi: POI string columns interned category_unique=94 group_unique=11 emoji_unique=71 -2026-05-04T16:52:23.662610Z INFO property_map_server::data::poi: POI data loading complete. -2026-05-04T16:52:23.710519Z INFO property_map_server: Allocator trim label="poi data load" trimmed=true rss_before_mib=3704.2 rss_after_mib=3513.9 released_mib=190.3 -2026-05-04T16:52:23.710531Z INFO property_map_server: POI data loaded pois=567534 -2026-05-04T16:52:23.710533Z INFO property_map_server: Building POI spatial grid index -2026-05-04T16:52:23.717701Z INFO property_map_server: Loading place data from /app/data/places.parquet -2026-05-04T16:52:23.717714Z INFO property_map_server::data::places: Loading place data from "/app/data/places.parquet"... -2026-05-04T16:52:23.719392Z INFO property_map_server::data::places: Loaded 3474 places -2026-05-04T16:52:23.720489Z INFO property_map_server::data::places: Place data loaded places=3474 types=2 with_population=71 with_city=3392 -2026-05-04T16:52:23.724484Z INFO property_map_server: Allocator trim label="place data load" trimmed=true rss_before_mib=3523.0 rss_after_mib=3518.6 released_mib=4.4 -2026-05-04T16:52:23.724490Z INFO property_map_server: Place data loaded places=3474 -2026-05-04T16:52:23.724499Z INFO property_map_server: Loading postcode boundaries from /app/data/postcode_boundaries -2026-05-04T16:52:23.724510Z INFO property_map_server::data::postcodes: Loading postcode boundaries from "/app/data/postcode_boundaries" -2026-05-04T16:52:23.725513Z INFO property_map_server::data::postcodes: Found GeoJSON files to process files=2361 -2026-05-04T16:52:32.438645Z INFO property_map_server::data::postcodes: Postcode boundary data ready postcodes=1490140 -2026-05-04T16:52:32.834664Z INFO property_map_server: Allocator trim label="postcode boundary load" trimmed=true rss_before_mib=10832.1 rss_after_mib=10645.7 released_mib=186.3 -2026-05-04T16:52:32.834676Z INFO property_map_server: Postcode boundaries loaded postcodes=1490140 -2026-05-04T16:52:32.988891Z INFO property_map_server::data::postcodes: Outcode data derived from postcodes outcodes=2361 -2026-05-04T16:52:32.988947Z INFO property_map_server: Loading PMTiles from /app/data/uk.pmtiles -2026-05-04T16:52:33.010108Z INFO property_map_server: PMTiles loaded successfully -2026-05-04T16:52:33.046813Z INFO property_map_server: No --dist provided; static serving and OG injection disabled -2026-05-04T16:52:33.083599Z INFO property_map_server: Screenshot service configured: http://screenshot:8002 -2026-05-04T16:52:33.083763Z INFO property_map_server: Precomputed features response groups=8 -2026-05-04T16:52:33.083775Z INFO property_map_server: PocketBase configured: http://pocketbase:8090 -2026-05-04T16:52:33.158769Z INFO property_map_server::pocketbase: PocketBase users collection already has all required fields -2026-05-04T16:52:33.166182Z INFO property_map_server::pocketbase: PocketBase collection 'saved_searches' API rules updated -2026-05-04T16:52:33.169642Z INFO property_map_server::pocketbase: PocketBase collection 'saved_properties' API rules updated -2026-05-04T16:52:34.891679Z INFO property_map_server::pocketbase: PocketBase meta.appURL set to https://perfect-postcodes.co.uk/pb -2026-05-04T16:52:34.898075Z INFO property_map_server::pocketbase: PocketBase OAuth configured on users collection -2026-05-04T16:52:34.898119Z INFO property_map_server: Gemini configured (model: gemini-3-flash-preview) -2026-05-04T16:52:34.898137Z INFO property_map_server: Loading travel time data from /app/data/travel-times -2026-05-04T16:52:34.899509Z INFO property_map_server::data::travel_time: Travel time mode discovered mode="bicycle" destinations=2753 -2026-05-04T16:52:34.900985Z INFO property_map_server::data::travel_time: Travel time mode discovered mode="walking" destinations=2753 -2026-05-04T16:52:34.902638Z INFO property_map_server::data::travel_time: Travel time mode discovered mode="car" destinations=2753 -2026-05-04T16:52:34.903880Z INFO property_map_server::data::travel_time: Travel time mode discovered mode="transit" destinations=2752 -2026-05-04T16:52:34.903900Z INFO property_map_server: Travel time store loaded modes=4 -2026-05-04T16:52:34.903948Z INFO property_map_server: Precomputed AI filters system prompt -2026-05-04T16:52:43.879829Z INFO property_map_server: All memory pages locked (mlockall) -2026-05-04T16:52:43.879872Z INFO property_map_server: Server listening on 0.0.0.0:8001 -2026-05-04T16:52:44.837043Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:52:44.838743Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:52:46.237131Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=8.9 json_ms=4.5 total_ms=13.4 -2026-05-04T16:52:50.343572Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:52:50.343601Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:52:50.654459Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=4.9 json_ms=0.6 total_ms=5.4 -2026-05-04T16:52:50.924418Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.9 json_ms=0.5 total_ms=2.4 -2026-05-04T16:52:55.327723Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=4.1 json_ms=0.5 total_ms=4.6 -2026-05-04T16:52:59.123773Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:52:59.124405Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:52:59.309646Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.9 json_ms=0.6 total_ms=2.4 -2026-05-04T16:52:59.856314Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:52:59.856333Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:53:06.621399Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:53:12.319647Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:53:12.320043Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:53:12.505843Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.5 json_ms=0.5 total_ms=1.9 -2026-05-04T16:53:12.847079Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:53:12.847083Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:53:13.768136Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:53:20.023336Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.7 json_ms=0.5 total_ms=2.2 -2026-05-04T16:53:31.973189Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.6 json_ms=0.5 total_ms=2.1 -2026-05-04T16:53:36.438564Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.6 json_ms=0.5 total_ms=2.2 -2026-05-04T16:53:40.877291Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.7 json_ms=0.5 total_ms=2.2 -2026-05-04T16:53:45.260349Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.4 json_ms=0.5 total_ms=1.9 -2026-05-04T16:53:52.933614Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=2.5 json_ms=0.5 total_ms=3.0 -2026-05-04T16:53:57.242184Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=122687 parallel=true cells_before_filter=928 cells_after_filter=889 truncated=false bounds=51.3737,-0.0587,51.4622,0.0559 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.6 json_ms=0.6 total_ms=2.2 -2026-05-04T16:54:11.694784Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:54:11.696755Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:54:11.886944Z INFO property_map_server::routes::pois: GET /api/pois results=2 candidates=106 categories=1 categories_raw="Bakery" ms=0.0 -2026-05-04T16:54:11.959114Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=5749 parallel=false cells_before_filter=205 cells_after_filter=193 truncated=false bounds=52.4771,0.8684,52.5473,1.0384 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.2 json_ms=0.1 total_ms=0.3 -2026-05-04T16:54:12.166041Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=5749 filters=2 travel=0 total=5749 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" ms=0.1 -2026-05-04T16:54:54.425156Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:54:54.426587Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:54:54.663377Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=3.8 json_ms=0.4 total_ms=4.2 -2026-05-04T16:54:54.908059Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=3.5 json_ms=0.4 total_ms=3.9 -2026-05-04T16:55:30.573292Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:55:30.573295Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:55:30.830914Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=2.0 json_ms=0.3 total_ms=2.3 -2026-05-04T16:56:20.574567Z INFO property_map_server: Prometheus metrics initialized -2026-05-04T16:56:20.574750Z INFO property_map_server: Loading property data from /app/data/properties.parquet, /app/data/postcode.parquet -2026-05-04T16:56:20.574761Z INFO property_map_server::data::property: Loading postcode features from "/app/data/postcode.parquet" -2026-05-04T16:56:20.727321Z INFO property_map_server::data::property: Postcode features loaded rows=1263786 -2026-05-04T16:56:20.727332Z INFO property_map_server::data::property: Loading properties from "/app/data/properties.parquet" -2026-05-04T16:56:23.848405Z INFO property_map_server::data::property: Properties joined with postcodes rows=15268176 -2026-05-04T16:56:23.848446Z INFO property_map_server::data::property: Feature columns from config numeric=63 enums=6 total=69 -2026-05-04T16:56:28.900388Z WARN property_map_server::data::property: Dropped properties with missing postcode coordinates rows=743076 -2026-05-04T16:56:28.900399Z INFO property_map_server::data::property: Combined data selected rows=14525100 -2026-05-04T16:56:29.024977Z INFO property_map_server::data::property: Extracting numeric feature columns -2026-05-04T16:56:29.390713Z INFO property_map_server::data::property: Computing histograms for numeric features -2026-05-04T16:56:30.749182Z INFO property_map_server::data::property: Extracting string columns -2026-05-04T16:56:32.160165Z INFO property_map_server::data::property: Building enum features -2026-05-04T16:56:33.392343Z INFO property_map_server::data::property: Extracting renovation history -2026-05-04T16:56:35.651195Z INFO property_map_server::data::property: Renovation history extracted properties_with_events=1741939 -2026-05-04T16:56:35.651205Z INFO property_map_server::data::property: Sorting rows by spatial locality -2026-05-04T16:56:36.629897Z INFO property_map_server::data::property: Building interned strings -2026-05-04T16:56:42.602979Z INFO property_map_server::data::property: Transposing to row-major layout (spatially sorted, quantized to u16) -2026-05-04T16:56:45.386258Z INFO property_map_server::data::property: Data loading complete -2026-05-04T16:56:46.870061Z INFO property_map_server: Allocator trim label="property data load" trimmed=true rss_before_mib=11393.9 rss_after_mib=3417.4 released_mib=7976.5 -2026-05-04T16:56:46.870073Z INFO property_map_server: Property data loaded rows=14525100 features=69 enums=6 -2026-05-04T16:56:46.870077Z INFO property_map_server: Building spatial grid index (0.01° cells) -2026-05-04T16:56:46.984635Z INFO property_map_server: Precomputing H3 cells at resolution 12 -2026-05-04T16:56:46.984644Z INFO property_map_server::data::property: Precomputing H3 cells at resolution 12 -2026-05-04T16:56:47.414376Z INFO property_map_server::data::property: H3 precomputation complete (14525100 cells) -2026-05-04T16:56:47.414403Z INFO property_map_server: Loading POI data from /app/data/filtered_uk_pois.parquet -2026-05-04T16:56:47.414408Z INFO property_map_server::data::poi: Loading POI data from "/app/data/filtered_uk_pois.parquet"... -2026-05-04T16:56:47.439121Z INFO property_map_server::data::poi: Loaded 567534 POIs -2026-05-04T16:56:47.592475Z INFO property_map_server::data::poi: POI string columns interned category_unique=94 group_unique=11 emoji_unique=71 -2026-05-04T16:56:47.593681Z INFO property_map_server::data::poi: POI data loading complete. -2026-05-04T16:56:47.644111Z INFO property_map_server: Allocator trim label="poi data load" trimmed=true rss_before_mib=3825.5 rss_after_mib=3635.0 released_mib=190.5 -2026-05-04T16:56:47.644123Z INFO property_map_server: POI data loaded pois=567534 -2026-05-04T16:56:47.644126Z INFO property_map_server: Building POI spatial grid index -2026-05-04T16:56:47.652418Z INFO property_map_server: Loading place data from /app/data/places.parquet -2026-05-04T16:56:47.652433Z INFO property_map_server::data::places: Loading place data from "/app/data/places.parquet"... -2026-05-04T16:56:47.655243Z INFO property_map_server::data::places: Loaded 3474 places -2026-05-04T16:56:47.656242Z INFO property_map_server::data::places: Place data loaded places=3474 types=2 with_population=71 with_city=3392 -2026-05-04T16:56:47.659926Z INFO property_map_server: Allocator trim label="place data load" trimmed=true rss_before_mib=3644.2 rss_after_mib=3639.7 released_mib=4.5 -2026-05-04T16:56:47.659933Z INFO property_map_server: Place data loaded places=3474 -2026-05-04T16:56:47.659941Z INFO property_map_server: Loading postcode boundaries from /app/data/postcode_boundaries -2026-05-04T16:56:47.659946Z INFO property_map_server::data::postcodes: Loading postcode boundaries from "/app/data/postcode_boundaries" -2026-05-04T16:56:47.676634Z INFO property_map_server::data::postcodes: Found GeoJSON files to process files=2361 -2026-05-04T16:56:56.616118Z INFO property_map_server::data::postcodes: Postcode boundary data ready postcodes=1490140 -2026-05-04T16:56:56.999856Z INFO property_map_server: Allocator trim label="postcode boundary load" trimmed=true rss_before_mib=10961.0 rss_after_mib=10765.9 released_mib=195.1 -2026-05-04T16:56:56.999867Z INFO property_map_server: Postcode boundaries loaded postcodes=1490140 -2026-05-04T16:56:57.159359Z INFO property_map_server::data::postcodes: Outcode data derived from postcodes outcodes=2361 -2026-05-04T16:56:57.159430Z INFO property_map_server: Loading PMTiles from /app/data/uk.pmtiles -2026-05-04T16:56:57.168952Z INFO property_map_server: PMTiles loaded successfully -2026-05-04T16:56:57.206010Z INFO property_map_server: No --dist provided; static serving and OG injection disabled -2026-05-04T16:56:57.232621Z INFO property_map_server: Screenshot service configured: http://screenshot:8002 -2026-05-04T16:56:57.232814Z INFO property_map_server: Precomputed features response groups=8 -2026-05-04T16:56:57.232849Z INFO property_map_server: PocketBase configured: http://pocketbase:8090 -2026-05-04T16:56:57.281369Z INFO property_map_server::pocketbase: PocketBase users collection already has all required fields -2026-05-04T16:56:57.285433Z INFO property_map_server::pocketbase: PocketBase collection 'saved_searches' API rules updated -2026-05-04T16:56:57.288969Z INFO property_map_server::pocketbase: PocketBase collection 'saved_properties' API rules updated -2026-05-04T16:56:58.609462Z INFO property_map_server::pocketbase: PocketBase meta.appURL set to https://perfect-postcodes.co.uk/pb -2026-05-04T16:56:58.614311Z INFO property_map_server::pocketbase: PocketBase OAuth configured on users collection -2026-05-04T16:56:58.614349Z INFO property_map_server: Gemini configured (model: gemini-3-flash-preview) -2026-05-04T16:56:58.614367Z INFO property_map_server: Loading travel time data from /app/data/travel-times -2026-05-04T16:56:58.654171Z INFO property_map_server::data::travel_time: Travel time mode discovered mode="bicycle" destinations=2753 -2026-05-04T16:56:58.679772Z INFO property_map_server::data::travel_time: Travel time mode discovered mode="walking" destinations=2753 -2026-05-04T16:56:58.709956Z INFO property_map_server::data::travel_time: Travel time mode discovered mode="car" destinations=2753 -2026-05-04T16:56:58.743398Z INFO property_map_server::data::travel_time: Travel time mode discovered mode="transit" destinations=2752 -2026-05-04T16:56:58.743442Z INFO property_map_server: Travel time store loaded modes=4 -2026-05-04T16:56:58.743498Z INFO property_map_server: Precomputed AI filters system prompt -2026-05-04T16:57:02.850161Z INFO property_map_server: All memory pages locked (mlockall) -2026-05-04T16:57:02.850207Z INFO property_map_server: Server listening on 0.0.0.0:8001 -2026-05-04T16:57:04.355091Z INFO property_map_server::routes::pois: GET /api/pois results=2 candidates=106 categories=1 categories_raw="Bakery" ms=0.0 -2026-05-04T16:57:04.356014Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=5749 parallel=false cells_before_filter=205 cells_after_filter=193 truncated=false bounds=52.4771,0.8684,52.5473,1.0384 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.3 json_ms=0.2 total_ms=0.5 -2026-05-04T16:57:04.601910Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=5749 filters=2 travel=0 total=5749 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" ms=0.1 -2026-05-04T16:57:06.048394Z INFO property_map_server::routes::hexagon_stats: GET /api/hexagon-stats h3=89194162c6bffff resolution=9 total_count=1 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" ms=0.1 -2026-05-04T16:57:06.367641Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=3843 parallel=false cells_before_filter=136 cells_after_filter=130 truncated=false bounds=52.4771,0.8914,52.5473,1.0154 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.2 json_ms=0.1 total_ms=0.3 -2026-05-04T16:57:06.369945Z INFO property_map_server::routes::pois: GET /api/pois results=2 candidates=97 categories=1 categories_raw="Bakery" ms=0.0 -2026-05-04T16:57:06.616929Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=3843 filters=2 travel=0 total=3843 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" ms=0.0 -2026-05-04T16:57:07.424823Z INFO property_map_server::routes::screenshot: Fetching screenshot from: http://screenshot:8002/screenshot?og=1&share=pp3xxg72&lat=52.5122&lon=0.9534&zoom=13.1&school=primary%3Aoutstanding%3A5%3A0%3A13&school=secondary%3Aoutstanding%3A5%3A0%3A4&poi=Bakery -2026-05-04T16:57:11.553390Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:11.558010Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:11.763671Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:11.763679Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:11.915058Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:11.919901Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:11.931486Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=6.2 json_ms=0.3 total_ms=6.5 -2026-05-04T16:57:12.096817Z INFO property_map_server::routes::pois: GET /api/pois results=2 candidates=106 categories=1 categories_raw="Bakery" ms=0.0 -2026-05-04T16:57:12.171317Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=5749 parallel=false cells_before_filter=205 cells_after_filter=193 truncated=false bounds=52.4771,0.8684,52.5473,1.0384 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.2 json_ms=0.1 total_ms=0.3 -2026-05-04T16:57:12.358432Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:12.358437Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:12.411186Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=5749 filters=2 travel=0 total=5749 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" ms=0.1 -2026-05-04T16:57:12.536798Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:12.536868Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:12.700634Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:12.700638Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:12.865329Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:12.865848Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:13.044744Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:13.044745Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:13.262950Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:13.262954Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:13.443061Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:13.443560Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:13.630643Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:13.631971Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:13.780658Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:13.780661Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:13.948593Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:13.948597Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:14.131284Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:14.131289Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:14.308283Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:14.308302Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:14.501404Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:14.501448Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:14.690626Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:14.690636Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:14.858729Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:14.860095Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:15.027207Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:15.027205Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:15.214232Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:15.214637Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:15.381513Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:15.381514Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:15.537921Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:15.541833Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:15.705519Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:15.705552Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:15.869023Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:15.869537Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:16.033918Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:16.033920Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:16.247673Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:16.248375Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:16.412004Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:16.412032Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:16.626881Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:16.626907Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:16.794209Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:16.794231Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:16.997566Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:16.997568Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:17.174350Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:17.174380Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:17.335021Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:17.335027Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:17.539926Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:17.539930Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:17.705442Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:17.705446Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:18.127037Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:18.127040Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:18.295345Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:18.295351Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:18.473546Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:18.473550Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:18.636387Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:18.637454Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:18.843085Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:18.843089Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:19.020688Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:19.020692Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:19.201992Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:19.201997Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:19.369243Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:19.369297Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:19.535715Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:19.535824Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:19.760089Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:19.761305Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:19.935717Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:19.935721Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:20.162450Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:20.162454Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:20.333244Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:20.333303Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:20.547095Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:20.547113Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:20.734211Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:20.734214Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:20.933985Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:20.935086Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:21.117872Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:21.118620Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:21.314798Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:21.314812Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:21.414208Z INFO property_map_server::routes::pois: GET /api/pois results=0 candidates=0 categories=1 categories_raw="Bakery" ms=0.0 -2026-05-04T16:57:21.414913Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=106 parallel=false cells_before_filter=17 cells_after_filter=17 truncated=false bounds=52.5012,0.9270,52.5245,0.9835 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.0 json_ms=0.0 total_ms=0.1 -2026-05-04T16:57:21.568965Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:21.569086Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:21.706044Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=106 filters=2 travel=0 total=106 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" ms=0.0 -2026-05-04T16:57:21.756890Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:21.757039Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:21.955414Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:21.955448Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:22.147162Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:22.147165Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:22.330539Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:22.332673Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:22.426605Z INFO property_map_server::routes::pois: GET /api/pois results=0 candidates=14 categories=1 categories_raw="Bakery" ms=0.0 -2026-05-04T16:57:22.426755Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=2226 parallel=false cells_before_filter=70 cells_after_filter=69 truncated=false bounds=52.4917,0.9019,52.5346,1.0058 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.1 json_ms=0.1 total_ms=0.2 -2026-05-04T16:57:22.541861Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:22.541871Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:22.681029Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=2226 filters=2 travel=0 total=2226 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" ms=0.0 -2026-05-04T16:57:22.741493Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:22.742137Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:22.936184Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:22.937320Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:23.113311Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:23.115082Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:23.316783Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:23.316849Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:23.508060Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:23.508083Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:23.704874Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:23.704878Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:23.887959Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:23.887962Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:24.104294Z INFO property_map_server::routes::pois: GET /api/pois results=0 candidates=14 categories=2 categories_raw="Bakery,Airport" ms=0.0 -2026-05-04T16:57:24.117936Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:24.118035Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:24.306274Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:24.306279Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:24.504949Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:24.504987Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:24.681516Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:24.683038Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:24.878853Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:24.878857Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:25.074316Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:25.074329Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:25.300703Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:25.300731Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:25.491570Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:25.491616Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:25.582060Z INFO property_map_server::routes::pois: GET /api/pois results=0 candidates=14 categories=3 categories_raw="Bakery,Airport,Aldi" ms=0.0 -2026-05-04T16:57:25.711228Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:25.711231Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:25.883944Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:25.883951Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:26.080336Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:26.080340Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:26.419818Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:26.419822Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:26.427398Z INFO property_map_server::routes::pois: GET /api/pois results=0 candidates=14 categories=4 categories_raw="Bakery,Airport,Aldi,Asda" ms=0.0 -2026-05-04T16:57:26.623712Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:26.623719Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:26.976432Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:26.976605Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:27.158303Z INFO property_map_server::routes::pois: GET /api/pois results=0 candidates=14 categories=10 categories_raw="Bakery,Airport,Aldi,Asda,Bus station,Bus stop,Ferry,Rail station,Taxi rank,Tube station" ms=0.0 -2026-05-04T16:57:27.165617Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:27.165649Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:27.519358Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:27.519367Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:27.712236Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:27.712251Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:28.079929Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:28.079940Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:28.261204Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:28.261360Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:28.450319Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:28.452104Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:28.610701Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:28.610706Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:28.787485Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:28.787523Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:28.836508Z INFO property_map_server::routes::pois: GET /api/pois results=9 candidates=469 categories=10 categories_raw="Bakery,Airport,Aldi,Asda,Bus station,Bus stop,Ferry,Rail station,Taxi rank,Tube station" ms=0.1 -2026-05-04T16:57:28.837299Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=15604 parallel=false cells_before_filter=618 cells_after_filter=618 truncated=false bounds=52.4290,0.7821,52.5750,1.1357 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.6 json_ms=0.3 total_ms=0.9 -2026-05-04T16:57:28.961952Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:28.961954Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:29.114483Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=15604 filters=2 travel=0 total=15604 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" ms=0.1 -2026-05-04T16:57:29.149020Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:29.150154Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:29.325989Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:29.326049Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:29.509513Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:29.509562Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:29.681383Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:29.681452Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:29.860032Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:29.860130Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:30.037351Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:30.037362Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:30.218280Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:30.219884Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:30.396337Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:30.396391Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:30.600189Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:30.600218Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:30.769945Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:30.769962Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:30.922902Z INFO property_map_server::routes::pois: GET /api/pois results=12 candidates=866 categories=10 categories_raw="Bakery,Airport,Aldi,Asda,Bus station,Bus stop,Ferry,Rail station,Taxi rank,Tube station" ms=0.1 -2026-05-04T16:57:30.949401Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:30.949408Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:31.118825Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:31.118826Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:31.168572Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=31273 filters=2 travel=0 total=31273 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" ms=0.2 -2026-05-04T16:57:31.304370Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:31.304375Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:31.470466Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:31.470477Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:31.654850Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:31.654851Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:31.830251Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:31.830253Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:32.025962Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:32.025964Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:32.187866Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:32.187874Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:32.363389Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:32.363395Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:32.526389Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:32.526388Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:32.708754Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:32.708756Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:32.874828Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:32.874835Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:33.066317Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:33.066344Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:33.178764Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=83099 parallel=true cells_before_filter=0 cells_after_filter=0 truncated=false bounds=51.4958,-0.1756,51.5342,-0.0844 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.5 json_ms=0.0 total_ms=0.5 -2026-05-04T16:57:33.184576Z INFO property_map_server::routes::pois: GET /api/pois results=818 candidates=14319 categories=10 categories_raw="Bakery,Airport,Aldi,Asda,Bus station,Bus stop,Ferry,Rail station,Taxi rank,Tube station" ms=1.1 -2026-05-04T16:57:33.231876Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:33.231880Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:33.415747Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:33.415752Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:33.428604Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=83099 filters=2 travel=0 total=0 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" ms=0.5 -2026-05-04T16:57:33.590144Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:33.590174Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:33.769332Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:33.769378Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:33.944883Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:33.944901Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:34.156932Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:34.157036Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:34.321339Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:34.321423Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:34.508651Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:34.508727Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:34.672554Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:34.672596Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:34.812145Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=107108 parallel=true cells_before_filter=0 cells_after_filter=0 truncated=false bounds=51.4998,-0.1465,51.5383,-0.0553 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.8 json_ms=0.0 total_ms=0.8 -2026-05-04T16:57:34.813887Z INFO property_map_server::routes::pois: GET /api/pois results=870 candidates=13715 categories=10 categories_raw="Bakery,Airport,Aldi,Asda,Bus station,Bus stop,Ferry,Rail station,Taxi rank,Tube station" ms=0.8 -2026-05-04T16:57:34.848347Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:34.849823Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:35.013624Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:35.013633Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:35.086005Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=107108 filters=2 travel=0 total=0 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" ms=0.7 -2026-05-04T16:57:35.186931Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:35.187028Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:35.361580Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:35.361635Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:35.544543Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:35.544565Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:35.717843Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:35.717900Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:35.901422Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:35.901512Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:36.080739Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:36.080803Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:36.256927Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:36.256980Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:36.425042Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:36.425096Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:36.597991Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:36.598058Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:36.774872Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:36.774922Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:36.945925Z INFO property_map_server::routes::pois: GET /api/pois results=6519 candidates=55638 categories=10 categories_raw="Bakery,Airport,Aldi,Asda,Bus station,Bus stop,Ferry,Rail station,Taxi rank,Tube station" ms=4.4 -2026-05-04T16:57:36.955770Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:36.955772Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:37.128544Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:37.128545Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:37.193684Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=1124084 filters=2 travel=0 total=210501 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" ms=7.2 -2026-05-04T16:57:37.303108Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:37.303116Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:37.474117Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:37.474121Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:37.659956Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:37.660038Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:37.835019Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:37.835029Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:38.013537Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:38.014575Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:38.178135Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:38.178138Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:38.365886Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:38.365888Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:38.528940Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:38.528988Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:38.649036Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=83099 parallel=true cells_before_filter=0 cells_after_filter=0 truncated=false bounds=51.4958,-0.1756,51.5342,-0.0844 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.5 json_ms=0.0 total_ms=0.5 -2026-05-04T16:57:38.651162Z INFO property_map_server::routes::pois: GET /api/pois results=818 candidates=14319 categories=10 categories_raw="Bakery,Airport,Aldi,Asda,Bus station,Bus stop,Ferry,Rail station,Taxi rank,Tube station" ms=0.8 -2026-05-04T16:57:38.703757Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:38.703765Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:38.878318Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:38.878376Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:38.923539Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=83099 filters=2 travel=0 total=0 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" ms=0.4 -2026-05-04T16:57:39.077673Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:39.077857Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:39.391489Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:39.391528Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:39.574670Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:39.574675Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:39.744998Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:39.745109Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:39.921045Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:39.921052Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:40.094946Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:40.095013Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:40.271084Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:40.271102Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:40.442667Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:40.442720Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:40.615514Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:40.615515Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:40.781681Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:40.781694Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:40.958954Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:40.961294Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:41.146209Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:41.146218Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:41.174410Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:41.174416Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:41.335491Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:41.335496Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:41.380501Z INFO property_map_server::routes::pois: GET /api/pois results=818 candidates=14319 categories=10 categories_raw="Bakery,Airport,Aldi,Asda,Bus station,Bus stop,Ferry,Rail station,Taxi rank,Tube station" ms=0.9 -2026-05-04T16:57:41.384727Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=83099 parallel=true cells_before_filter=0 cells_after_filter=0 truncated=false bounds=51.4958,-0.1756,51.5342,-0.0844 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.5 json_ms=0.0 total_ms=0.5 -2026-05-04T16:57:41.505426Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:41.505550Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:41.632660Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=83099 filters=2 travel=0 total=0 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" ms=0.7 -2026-05-04T16:57:41.692939Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:41.694600Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:41.866758Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:41.866801Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:42.054415Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:42.054430Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:44.925653Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=67753 parallel=true cells_before_filter=0 cells_after_filter=0 truncated=false bounds=51.5462,-0.1779,51.5847,-0.0868 filters=2 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.4 json_ms=0.0 total_ms=0.4 -2026-05-04T16:57:44.925825Z INFO property_map_server::routes::pois: GET /api/pois results=544 candidates=4484 categories=10 categories_raw="Bakery,Airport,Aldi,Asda,Bus station,Bus stop,Ferry,Rail station,Taxi rank,Tube station" ms=0.6 -2026-05-04T16:57:45.171774Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=67753 filters=2 travel=0 total=0 filters_raw="Outstanding primary schools within 5km:0:13;;Outstanding secondary schools within 5km:0:4" ms=0.4 -2026-05-04T16:57:57.458132Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:57:57.459237Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:57:57.943610Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=5.4 json_ms=0.5 total_ms=5.9 -2026-05-04T16:58:28.777550Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T16:58:28.777658Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T16:58:29.127088Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.6 json_ms=0.3 total_ms=1.9 -2026-05-04T16:58:29.288159Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.5 json_ms=0.3 total_ms=1.8 -2026-05-04T20:30:19.557826Z INFO property_map_server: Prometheus metrics initialized -2026-05-04T20:30:19.557989Z INFO property_map_server: Loading property data from /app/data/properties.parquet, /app/data/postcode.parquet -2026-05-04T20:30:19.557997Z INFO property_map_server::data::property: Loading postcode features from "/app/data/postcode.parquet" -2026-05-04T20:30:19.661100Z INFO property_map_server::data::property: Postcode features loaded rows=1263786 -2026-05-04T20:30:19.661114Z INFO property_map_server::data::property: Loading properties from "/app/data/properties.parquet" -2026-05-04T20:30:22.562738Z INFO property_map_server::data::property: Properties joined with postcodes rows=15268176 -2026-05-04T20:30:22.562783Z INFO property_map_server::data::property: Feature columns from config numeric=63 enums=6 total=69 -2026-05-04T20:30:26.039464Z WARN property_map_server::data::property: Dropped properties with missing postcode coordinates rows=743076 -2026-05-04T20:30:26.039472Z INFO property_map_server::data::property: Combined data selected rows=14525100 -2026-05-04T20:30:26.192620Z INFO property_map_server::data::property: Extracting numeric feature columns -2026-05-04T20:30:26.565556Z INFO property_map_server::data::property: Computing histograms for numeric features -2026-05-04T20:30:27.922455Z INFO property_map_server::data::property: Extracting string columns -2026-05-04T20:30:29.213846Z INFO property_map_server::data::property: Building enum features -2026-05-04T20:30:30.356475Z INFO property_map_server::data::property: Extracting renovation history -2026-05-04T20:30:32.289458Z INFO property_map_server::data::property: Renovation history extracted properties_with_events=1741939 -2026-05-04T20:30:32.289466Z INFO property_map_server::data::property: Sorting rows by spatial locality -2026-05-04T20:30:33.124493Z INFO property_map_server::data::property: Building interned strings -2026-05-04T20:30:38.166699Z INFO property_map_server::data::property: Transposing to row-major layout (spatially sorted, quantized to u16) -2026-05-04T20:30:40.831663Z INFO property_map_server::data::property: Data loading complete -2026-05-04T20:30:42.537764Z INFO property_map_server: Allocator trim label="property data load" trimmed=true rss_before_mib=12145.1 rss_after_mib=3347.9 released_mib=8797.2 -2026-05-04T20:30:42.537774Z INFO property_map_server: Property data loaded rows=14525100 features=69 enums=6 -2026-05-04T20:30:42.537777Z INFO property_map_server: Building spatial grid index (0.01° cells) -2026-05-04T20:30:42.639662Z INFO property_map_server: Precomputing H3 cells at resolution 12 -2026-05-04T20:30:42.639670Z INFO property_map_server::data::property: Precomputing H3 cells at resolution 12 -2026-05-04T20:30:42.983845Z INFO property_map_server::data::property: H3 precomputation complete (14525100 cells) -2026-05-04T20:30:42.983892Z INFO property_map_server: Loading POI data from /app/data/filtered_uk_pois.parquet -2026-05-04T20:30:42.983899Z INFO property_map_server::data::poi: Loading POI data from "/app/data/filtered_uk_pois.parquet"... -2026-05-04T20:30:43.020359Z INFO property_map_server::data::poi: Loaded 567534 POIs -2026-05-04T20:30:43.162023Z INFO property_map_server::data::poi: POI string columns interned category_unique=94 group_unique=11 emoji_unique=71 -2026-05-04T20:30:43.163139Z INFO property_map_server::data::poi: POI data loading complete. -2026-05-04T20:30:43.208910Z INFO property_map_server: Allocator trim label="poi data load" trimmed=true rss_before_mib=3756.2 rss_after_mib=3565.4 released_mib=190.8 -2026-05-04T20:30:43.208921Z INFO property_map_server: POI data loaded pois=567534 -2026-05-04T20:30:43.208924Z INFO property_map_server: Building POI spatial grid index -2026-05-04T20:30:43.215749Z INFO property_map_server: Loading place data from /app/data/places.parquet -2026-05-04T20:30:43.215762Z INFO property_map_server::data::places: Loading place data from "/app/data/places.parquet"... -2026-05-04T20:30:43.216707Z INFO property_map_server::data::places: Loaded 3474 places -2026-05-04T20:30:43.217692Z INFO property_map_server::data::places: Place data loaded places=3474 types=2 with_population=71 with_city=3392 -2026-05-04T20:30:43.221077Z INFO property_map_server: Allocator trim label="place data load" trimmed=true rss_before_mib=3574.5 rss_after_mib=3570.1 released_mib=4.4 -2026-05-04T20:30:43.221082Z INFO property_map_server: Place data loaded places=3474 -2026-05-04T20:30:43.221088Z INFO property_map_server: Loading postcode boundaries from /app/data/postcode_boundaries -2026-05-04T20:30:43.221092Z INFO property_map_server::data::postcodes: Loading postcode boundaries from "/app/data/postcode_boundaries" -2026-05-04T20:30:43.222031Z INFO property_map_server::data::postcodes: Found GeoJSON files to process files=2361 -2026-05-04T20:30:51.654250Z INFO property_map_server::data::postcodes: Postcode boundary data ready postcodes=1490140 -2026-05-04T20:30:52.077729Z INFO property_map_server: Allocator trim label="postcode boundary load" trimmed=true rss_before_mib=10882.4 rss_after_mib=10688.5 released_mib=193.9 -2026-05-04T20:30:52.077742Z INFO property_map_server: Postcode boundaries loaded postcodes=1490140 -2026-05-04T20:30:52.220308Z INFO property_map_server::data::postcodes: Outcode data derived from postcodes outcodes=2361 -2026-05-04T20:30:52.220362Z INFO property_map_server: Loading PMTiles from /app/data/uk.pmtiles -2026-05-04T20:30:52.273585Z INFO property_map_server: PMTiles loaded successfully -2026-05-04T20:30:52.309238Z INFO property_map_server: No --dist provided; static serving and OG injection disabled -2026-05-04T20:30:52.338585Z INFO property_map_server: Screenshot service configured: http://screenshot:8002 -2026-05-04T20:30:52.338730Z INFO property_map_server: Precomputed features response groups=8 -2026-05-04T20:30:52.338742Z INFO property_map_server: PocketBase configured: http://pocketbase:8090 -2026-05-04T20:30:52.419011Z INFO property_map_server::pocketbase: PocketBase users collection already has all required fields -2026-05-04T20:30:52.424109Z INFO property_map_server::pocketbase: PocketBase collection 'saved_searches' API rules updated -2026-05-04T20:30:52.427292Z INFO property_map_server::pocketbase: PocketBase collection 'saved_properties' API rules updated -2026-05-04T20:30:52.513510Z INFO property_map_server::pocketbase: PocketBase meta.appURL set to https://perfect-postcodes.co.uk/pb -2026-05-04T20:30:52.516839Z INFO property_map_server::pocketbase: PocketBase OAuth configured on users collection -2026-05-04T20:30:52.516862Z INFO property_map_server: Gemini configured (model: gemini-3-flash-preview) -2026-05-04T20:30:52.516881Z INFO property_map_server: Loading travel time data from /app/data/travel-times -2026-05-04T20:30:52.518702Z INFO property_map_server::data::travel_time: Travel time mode discovered mode="bicycle" destinations=2753 -2026-05-04T20:30:52.520363Z INFO property_map_server::data::travel_time: Travel time mode discovered mode="walking" destinations=2753 -2026-05-04T20:30:52.522195Z INFO property_map_server::data::travel_time: Travel time mode discovered mode="car" destinations=2753 -2026-05-04T20:30:52.523772Z INFO property_map_server::data::travel_time: Travel time mode discovered mode="transit" destinations=2752 -2026-05-04T20:30:52.523786Z INFO property_map_server: Travel time store loaded modes=4 -2026-05-04T20:30:52.523828Z INFO property_map_server: Precomputed AI filters system prompt -2026-05-04T20:31:09.448584Z INFO property_map_server: All memory pages locked (mlockall) -2026-05-04T20:31:09.448653Z INFO property_map_server: Server listening on 0.0.0.0:8001 -2026-05-04T20:31:09.954559Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:31:22.353875Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:31:22.353952Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:31:23.626789Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=6.3 json_ms=0.3 total_ms=6.6 -2026-05-04T20:32:15.348905Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:32:15.350168Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:32:15.807753Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=4.1 json_ms=0.2 total_ms=4.4 -2026-05-04T20:32:19.685730Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:32:19.685759Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:32:19.966538Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=3.5 json_ms=0.3 total_ms=3.7 -2026-05-04T20:32:27.438356Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:32:27.438476Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:32:27.784172Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=2.0 json_ms=0.4 total_ms=2.4 -2026-05-04T20:32:54.407882Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:32:54.407932Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:32:54.660005Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=2.4 json_ms=0.3 total_ms=2.7 -2026-05-04T20:33:09.800713Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:33:09.800731Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:33:10.426238Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.5 json_ms=0.3 total_ms=1.8 -2026-05-04T20:33:12.770380Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.9 json_ms=0.3 total_ms=2.2 -2026-05-04T20:33:53.200280Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:33:53.203908Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:18.328076Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:18.328080Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:18.583018Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.6 json_ms=0.3 total_ms=1.8 -2026-05-04T20:34:23.052991Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=83099 parallel=true cells_before_filter=312 cells_after_filter=300 truncated=false bounds=51.4958,-0.1756,51.5342,-0.0844 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.0 json_ms=0.2 total_ms=1.2 -2026-05-04T20:34:25.158814Z INFO property_map_server::routes::hexagon_stats: GET /api/hexagon-stats h3=89195da49c3ffff resolution=9 total_count=238 filters=0 filters_raw="-" ms=0.2 -2026-05-04T20:34:25.576335Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=64311 parallel=true cells_before_filter=249 cells_after_filter=229 truncated=false bounds=51.4958,-0.1632,51.5342,-0.0968 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.5 json_ms=0.2 total_ms=1.7 -2026-05-04T20:34:26.166477Z INFO property_map_server::routes::hexagon_stats: GET /api/hexagon-stats h3=88195da49dfffff resolution=8 total_count=1598 filters=0 filters_raw="-" ms=1.1 -2026-05-04T20:34:26.198050Z INFO property_map_server::routes::hexagon_stats: GET /api/hexagon-stats h3=87195da49ffffff resolution=7 total_count=7707 filters=0 filters_raw="-" ms=2.9 -2026-05-04T20:34:26.276613Z INFO property_map_server::routes::hexagon_stats: GET /api/hexagon-stats h3=86195da4fffffff resolution=6 total_count=94429 filters=0 filters_raw="-" ms=38.2 -2026-05-04T20:34:26.465598Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=6 rows=7286993 parallel=true cells_before_filter=1523 cells_after_filter=1523 truncated=false bounds=49.8232,-2.2817,52.4581,2.2344 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.2 agg_ms=39.6 json_ms=1.1 total_ms=40.9 -2026-05-04T20:34:27.933150Z INFO property_map_server::routes::hexagon_stats: GET /api/hexagon-stats h3=87195da48ffffff resolution=7 total_count=15303 filters=0 filters_raw="-" ms=6.4 -2026-05-04T20:34:28.584652Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=7 rows=597761 parallel=true cells_before_filter=972 cells_after_filter=972 truncated=false bounds=50.7289,-1.6798,51.3320,-0.6488 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=3.7 json_ms=0.4 total_ms=4.1 -2026-05-04T20:34:28.905846Z INFO property_map_server::routes::hexagon_stats: GET /api/hexagon-stats h3=88195da481fffff resolution=8 total_count=1907 filters=0 filters_raw="-" ms=0.9 -2026-05-04T20:34:29.424084Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=8 rows=186703 parallel=true cells_before_filter=945 cells_after_filter=941 truncated=false bounds=50.8643,-1.4587,51.1420,-0.9843 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.6 json_ms=0.5 total_ms=2.1 -2026-05-04T20:34:30.488144Z INFO property_map_server::routes::hexagon_stats: GET /api/hexagon-stats h3=8819598e67fffff resolution=8 total_count=7 filters=0 filters_raw="-" ms=0.1 -2026-05-04T20:34:31.111777Z INFO property_map_server::routes::hexagon_stats: GET /api/hexagon-stats h3=8819598e2dfffff resolution=8 total_count=60 filters=0 filters_raw="-" ms=0.1 -2026-05-04T20:34:31.378980Z INFO property_map_server::routes::hexagon_stats: GET /api/hexagon-stats h3=8919598e2c3ffff resolution=9 total_count=0 filters=0 filters_raw="-" ms=0.0 -2026-05-04T20:34:31.842706Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=1307 parallel=false cells_before_filter=65 cells_after_filter=52 truncated=false bounds=50.9992,-1.2517,51.0771,-1.1185 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.1 json_ms=0.1 total_ms=0.1 -2026-05-04T20:34:32.910988Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=2465 parallel=false cells_before_filter=94 cells_after_filter=82 truncated=false bounds=51.0079,-1.2472,51.0858,-1.1141 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.1 json_ms=0.1 total_ms=0.2 -2026-05-04T20:34:32.928239Z INFO property_map_server::routes::hexagon_stats: GET /api/hexagon-stats h3=8919598e2d7ffff resolution=9 total_count=44 filters=0 filters_raw="-" ms=0.1 -2026-05-04T20:34:33.808761Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:33.808804Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:34.471770Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.7 json_ms=0.3 total_ms=2.0 -2026-05-04T20:34:34.717103Z INFO property_map_server::routes::screenshot: Fetching screenshot from: http://screenshot:8002/screenshot?og=1&lat=51.0468&lon=-1.1806&zoom=13.0 -2026-05-04T20:34:35.099478Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:35.099493Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:35.404694Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:35.404725Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:35.621134Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:35.621143Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:35.820743Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:35.820748Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:36.031322Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:36.031329Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:36.220252Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:36.220273Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:36.417725Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:36.417764Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:36.596564Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:36.596567Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:36.805201Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:36.807262Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:36.979234Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:36.980372Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:37.178187Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:37.178191Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:37.374722Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:37.374774Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:37.578691Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:37.578748Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:37.763311Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:37.763336Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:37.959917Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:37.961434Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:38.149214Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:38.149218Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:38.338272Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:38.338330Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:38.523357Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:38.523384Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:38.647361Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:38.648448Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:38.722548Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:38.722571Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:38.907929Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:38.908520Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:38.999919Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=3047 parallel=false cells_before_filter=155 cells_after_filter=134 truncated=false bounds=51.0079,-1.2718,51.0856,-1.0894 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.1 json_ms=0.1 total_ms=0.2 -2026-05-04T20:34:39.103175Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:39.103190Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:39.297466Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:39.297503Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:39.496714Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:39.496723Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:39.690316Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:39.690319Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:39.885749Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:39.885750Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:40.071280Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:40.071281Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:40.272875Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:40.272877Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:40.455413Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:40.456062Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:40.650200Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:40.650208Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:40.845522Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:40.845559Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:41.038761Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:41.038768Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:41.229273Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:41.229275Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:41.430964Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:41.430979Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:41.623261Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:41.623265Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:41.824373Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:41.824375Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:42.019133Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:42.020313Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:42.212760Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:42.212863Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:42.411007Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:42.411009Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:42.607078Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:42.607110Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:42.793714Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:42.793749Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:42.989510Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:42.989513Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:43.183559Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:43.183560Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:43.387718Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:43.387725Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:43.585185Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:43.586557Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:43.779686Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:43.779689Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:43.967841Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:43.967853Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:44.166724Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:44.166725Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:44.394604Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:44.394609Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:44.588595Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:44.588598Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:44.787714Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:44.787719Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:44.984488Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:44.984510Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:45.180962Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:45.180969Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:45.376625Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=3047 parallel=false cells_before_filter=155 cells_after_filter=134 truncated=false bounds=51.0079,-1.2718,51.0856,-1.0894 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.2 json_ms=0.1 total_ms=0.3 -2026-05-04T20:34:45.387583Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:45.387593Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:45.580533Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:45.580596Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:45.773425Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:45.773473Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:45.967029Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:45.967068Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:46.166279Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:46.166350Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:46.357911Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:46.358486Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:46.550836Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:46.550891Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:46.784106Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:46.784160Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:46.983704Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:46.983708Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:47.198317Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:47.198328Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:47.394310Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:47.394315Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:47.610059Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:47.610060Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:47.800028Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:47.800044Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:48.014658Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:48.014666Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:48.219421Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:48.219435Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:48.434789Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:48.434790Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:48.647418Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:48.647423Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:48.898225Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:48.898228Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:49.108187Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:49.108205Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:49.342153Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:49.342155Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:49.545502Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:49.545518Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:49.778569Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:49.778587Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:49.999270Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:49.999274Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:50.333862Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:50.333934Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:50.544353Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:50.544371Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:50.798443Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:50.798447Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:51.000381Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:51.000385Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:51.374154Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:51.374161Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:51.570709Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:51.570714Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:51.916953Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:51.916957Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:52.125325Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:52.125330Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:52.482832Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:52.482835Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:52.670560Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:52.670564Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:52.872611Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:52.872615Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:53.060615Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:53.060616Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:53.251200Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:53.251202Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:53.428146Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:53.428148Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:53.623610Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:53.623614Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:53.811624Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:53.811663Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:54.009761Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:54.009828Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:54.190575Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:54.190579Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:54.379891Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:54.379898Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:54.567380Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:54.567402Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:54.758550Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:54.758551Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:54.935097Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:54.935104Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:55.122992Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:55.122997Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:55.305012Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:55.305016Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:55.488758Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:55.488771Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:55.661696Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:55.661743Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:55.850007Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:55.851110Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:56.021055Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:56.021093Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:56.204565Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:56.204571Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:56.378525Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:56.378554Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:56.569251Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:56.569259Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:56.751520Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:56.751531Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:57.240645Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:57.240996Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:57.241239Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=326 parallel=false cells_before_filter=41 cells_after_filter=41 truncated=false bounds=51.0213,-1.2578,51.0723,-1.1034 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.1 json_ms=0.0 total_ms=0.1 -2026-05-04T20:34:57.424507Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:57.424534Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:57.599879Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:57.599944Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:57.770786Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:57.770819Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:57.956206Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:57.956233Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:58.135904Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:58.135908Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:58.321905Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:58.321912Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:58.497793Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:58.497797Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:58.695097Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:58.695098Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:58.884432Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:58.884442Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:59.074754Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:59.074757Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:59.257817Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:59.257819Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:59.443034Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:59.443038Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:59.616255Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:59.616264Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:59.806095Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:59.806096Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:34:59.996128Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:34:59.996198Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:00.202642Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:00.202647Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:00.415170Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:00.415200Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:00.622447Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:00.622452Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:00.819512Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:00.820079Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:01.018420Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:01.018432Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:01.223860Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:01.223861Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:01.428276Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:01.428296Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:01.632732Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:01.632734Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:01.825994Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:01.826009Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:02.010964Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:02.010970Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:02.207162Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:02.207165Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:02.383218Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:02.384358Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:02.571962Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:02.571993Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:02.751751Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:02.752992Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:02.941788Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:02.941802Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:03.116109Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:03.116116Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:03.304169Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:03.304171Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:03.480506Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:03.480515Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:03.664726Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:03.664730Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:03.832155Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:03.832156Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:04.018658Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:04.018659Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:04.195730Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:04.195732Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:04.378322Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:04.378331Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:04.543718Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:04.543724Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:04.732208Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:04.732669Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:04.912266Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:04.912342Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:05.102562Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:05.102568Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:05.284289Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:05.284345Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:05.481744Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:05.481770Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:05.669280Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:05.669312Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:05.854490Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:05.855575Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:53.196583Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:53.197811Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:53.878390Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:35:53.878412Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:35:54.470543Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.3 json_ms=0.2 total_ms=1.6 -2026-05-04T20:36:09.276001Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:36:09.281237Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:36:09.367289Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:36:09.367312Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:37:02.804001Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:37:02.805317Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:37:02.885691Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:37:02.886138Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:37:19.771121Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:37:19.815442Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:37:19.884568Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:37:19.884572Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:37:41.735578Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:37:41.736828Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:37:41.847861Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:37:41.847873Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:37:57.095462Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:37:57.095764Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:37:57.200811Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:37:57.200824Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:38:11.018411Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:38:11.059896Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:38:11.133358Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:38:11.134108Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:38:52.095388Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:38:52.098225Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:38:53.368198Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:38:53.429393Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:38:54.163956Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.1 json_ms=0.3 total_ms=1.4 -2026-05-04T20:39:06.050601Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:39:06.052116Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:39:06.570770Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.8 json_ms=0.3 total_ms=2.1 -2026-05-04T20:39:23.011298Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.1 json_ms=0.0 total_ms=1.1 -2026-05-04T20:39:23.243791Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=192 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=2.1 -2026-05-04T20:39:24.980683Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=392 cells_after_filter=297 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=3 filters_raw="Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=1.5 json_ms=0.3 total_ms=1.8 -2026-05-04T20:39:25.125095Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=1.5 json_ms=0.1 total_ms=1.5 -2026-05-04T20:39:31.465483Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=261 cells_after_filter=182 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=2.0 json_ms=0.1 total_ms=2.1 -2026-05-04T20:39:31.722990Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=2266 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=3.3 -2026-05-04T20:39:38.401017Z INFO property_map_server::routes::hexagon_stats: GET /api/hexagon-stats h3=89194ad325bffff resolution=9 total_count=1 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=0.2 -2026-05-04T20:39:39.087171Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=108397 parallel=true cells_before_filter=183 cells_after_filter=134 truncated=false bounds=51.4943,-0.1671,51.5357,-0.0929 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.0 json_ms=0.1 total_ms=1.1 -2026-05-04T20:39:39.300308Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=108397 filters=4 travel=0 total=1583 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=1.7 -2026-05-04T20:41:08.067710Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:41:08.069119Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:41:08.529787Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.5 json_ms=0.3 total_ms=1.8 -2026-05-04T20:41:17.449649Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.9 json_ms=0.0 total_ms=1.0 -2026-05-04T20:41:17.701651Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=192 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=2.4 -2026-05-04T20:41:19.350217Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=392 cells_after_filter=297 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=3 filters_raw="Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=1.3 json_ms=0.3 total_ms=1.6 -2026-05-04T20:41:19.498459Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=1.1 json_ms=0.0 total_ms=1.1 -2026-05-04T20:41:25.104140Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=261 cells_after_filter=182 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.8 json_ms=0.1 total_ms=1.9 -2026-05-04T20:41:25.354462Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=2266 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=3.4 -2026-05-04T20:41:30.534640Z INFO property_map_server::routes::hexagon_stats: GET /api/hexagon-stats h3=89194ad325bffff resolution=9 total_count=1 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=0.2 -2026-05-04T20:41:31.159443Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=108397 parallel=true cells_before_filter=183 cells_after_filter=134 truncated=false bounds=51.4943,-0.1671,51.5357,-0.0929 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.9 json_ms=0.1 total_ms=1.0 -2026-05-04T20:41:31.409981Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=108397 filters=4 travel=0 total=1583 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=1.8 -2026-05-04T20:42:30.707655Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:42:30.708065Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:42:30.744186Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:42:30.745673Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:43:04.554809Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:43:04.554814Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:43:04.584716Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:43:04.587412Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:43:12.275304Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:43:12.275307Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:43:12.298346Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:43:12.299436Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:43:41.034309Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:43:41.039356Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:43:41.059494Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:43:41.059502Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:43:52.892976Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:43:52.894374Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:43:53.353713Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.8 json_ms=0.2 total_ms=2.0 -2026-05-04T20:43:59.658270Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.9 json_ms=0.0 total_ms=0.9 -2026-05-04T20:43:59.907585Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=192 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=2.4 -2026-05-04T20:44:00.977870Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=392 cells_after_filter=297 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=3 filters_raw="Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=1.7 json_ms=0.5 total_ms=2.2 -2026-05-04T20:44:01.129206Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=1.7 json_ms=0.1 total_ms=1.8 -2026-05-04T20:44:05.395927Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=261 cells_after_filter=182 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=2.2 json_ms=0.2 total_ms=2.3 -2026-05-04T20:44:05.646861Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=2266 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=3.5 -2026-05-04T20:44:10.435176Z INFO property_map_server::routes::hexagon_stats: GET /api/hexagon-stats h3=89194ad325bffff resolution=9 total_count=1 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=0.2 -2026-05-04T20:44:11.096593Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=108397 parallel=true cells_before_filter=183 cells_after_filter=134 truncated=false bounds=51.4943,-0.1671,51.5357,-0.0929 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.9 json_ms=0.1 total_ms=1.0 -2026-05-04T20:44:11.346615Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=108397 filters=4 travel=0 total=1583 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=1.9 -2026-05-04T20:44:25.051474Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=83099 parallel=true cells_before_filter=312 cells_after_filter=300 truncated=false bounds=51.4958,-0.1756,51.5342,-0.0844 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.9 json_ms=0.2 total_ms=1.1 -2026-05-04T20:44:27.031361Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=83099 parallel=true cells_before_filter=312 cells_after_filter=300 truncated=false bounds=51.4958,-0.1756,51.5342,-0.0844 filters=1 filters_raw="Income Score (rate):0:0.996" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.3 json_ms=0.2 total_ms=1.5 -2026-05-04T20:44:27.281363Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=83099 filters=1 travel=0 total=83099 filters_raw="Income Score (rate):0:0.996" ms=2.4 -2026-05-04T20:44:27.603993Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=83099 parallel=true cells_before_filter=312 cells_after_filter=300 truncated=false bounds=51.4958,-0.1756,51.5342,-0.0844 filters=0 filters_raw="-" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=1.3 json_ms=0.3 total_ms=1.7 -2026-05-04T20:44:27.762778Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=83099 parallel=true cells_before_filter=312 cells_after_filter=300 truncated=false bounds=51.4958,-0.1756,51.5342,-0.0844 filters=1 filters_raw="Income Score (rate):0:0.996" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=1.1 json_ms=0.2 total_ms=1.3 -2026-05-04T20:44:27.996689Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=83099 parallel=true cells_before_filter=305 cells_after_filter=293 truncated=false bounds=51.4958,-0.1756,51.5342,-0.0844 filters=1 filters_raw="Income Score (rate):0.85:0.996" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.9 json_ms=0.2 total_ms=2.1 -2026-05-04T20:44:28.242605Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=83099 filters=1 travel=0 total=77783 filters_raw="Income Score (rate):0.85:0.996" ms=2.0 -2026-05-04T20:45:00.911232Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:45:00.914605Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:45:02.434769Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.7 json_ms=0.3 total_ms=2.0 -2026-05-04T20:45:07.311390Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.3 json_ms=0.0 total_ms=1.3 -2026-05-04T20:45:07.554657Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=192 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=3.0 -2026-05-04T20:45:29.666656Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:45:29.667346Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:45:30.123472Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.6 json_ms=0.3 total_ms=1.9 -2026-05-04T20:45:35.839057Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.3 json_ms=0.0 total_ms=1.3 -2026-05-04T20:45:36.085660Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=192 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=2.2 -2026-05-04T20:46:46.804618Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:46:46.804924Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:46:48.403073Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=2.0 json_ms=0.4 total_ms=2.4 -2026-05-04T20:46:53.927368Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.9 json_ms=0.0 total_ms=1.0 -2026-05-04T20:46:54.173346Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=192 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=2.1 -2026-05-04T20:46:55.390070Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=392 cells_after_filter=297 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=3 filters_raw="Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=2.1 json_ms=0.4 total_ms=2.5 -2026-05-04T20:46:55.535270Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=1.3 json_ms=0.0 total_ms=1.3 -2026-05-04T20:47:00.487676Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=261 cells_after_filter=182 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.4 json_ms=0.2 total_ms=1.5 -2026-05-04T20:47:00.738490Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=2266 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=3.2 -2026-05-04T20:47:05.776570Z INFO property_map_server::routes::hexagon_stats: GET /api/hexagon-stats h3=89194ad325bffff resolution=9 total_count=1 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=0.2 -2026-05-04T20:47:06.355877Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=108397 parallel=true cells_before_filter=183 cells_after_filter=134 truncated=false bounds=51.4943,-0.1671,51.5357,-0.0929 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.9 json_ms=0.1 total_ms=1.0 -2026-05-04T20:47:06.606689Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=108397 filters=4 travel=0 total=1583 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=1.7 -2026-05-04T20:47:32.866710Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:47:32.866743Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:47:32.885838Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:47:32.886993Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:47:49.386505Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:47:49.386515Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:47:49.401614Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:47:49.402891Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:47:57.416750Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:47:57.418210Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:47:58.098569Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=2.8 json_ms=0.3 total_ms=3.1 -2026-05-04T20:47:58.404720Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:47:58.405522Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:47:58.430526Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:47:58.430833Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:48:01.191206Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:48:01.192318Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:48:01.523111Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=2.0 json_ms=0.3 total_ms=2.3 -2026-05-04T20:48:07.772669Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.2 json_ms=0.0 total_ms=1.3 -2026-05-04T20:48:08.027177Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=192 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=3.9 -2026-05-04T20:48:08.796621Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:48:08.796636Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:48:08.812196Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:48:08.813328Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:48:08.885819Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:48:08.886773Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:48:09.227901Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=2.5 json_ms=0.0 total_ms=2.6 -2026-05-04T20:48:11.122327Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=192 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=4.5 -2026-05-04T20:48:17.124780Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:48:17.125594Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:48:17.145187Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:48:17.147472Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:48:17.230192Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:48:17.230475Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:48:17.594663Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.6 json_ms=0.1 total_ms=1.6 -2026-05-04T20:50:12.098973Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:50:12.100126Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:50:13.707247Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.2 json_ms=0.3 total_ms=1.5 -2026-05-04T20:50:19.331174Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.9 json_ms=0.0 total_ms=0.9 -2026-05-04T20:50:19.582430Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=192 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=2.1 -2026-05-04T20:50:20.799127Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=392 cells_after_filter=297 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=3 filters_raw="Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=1.8 json_ms=0.4 total_ms=2.1 -2026-05-04T20:50:20.942190Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=1.0 json_ms=0.1 total_ms=1.1 -2026-05-04T20:50:25.736222Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=261 cells_after_filter=182 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=3.1 json_ms=0.2 total_ms=3.3 -2026-05-04T20:50:25.983567Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=2266 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=3.6 -2026-05-04T20:50:30.835124Z INFO property_map_server::routes::hexagon_stats: GET /api/hexagon-stats h3=89194ad325bffff resolution=9 total_count=1 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=0.2 -2026-05-04T20:50:31.454678Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=108397 parallel=true cells_before_filter=183 cells_after_filter=134 truncated=false bounds=51.4943,-0.1671,51.5357,-0.0929 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.8 json_ms=0.1 total_ms=1.0 -2026-05-04T20:50:31.700575Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=108397 filters=4 travel=0 total=1583 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=2.7 -2026-05-04T20:50:47.310479Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:50:47.310885Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:50:47.469513Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:50:47.469538Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:51:28.250512Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:51:28.250780Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:51:28.322463Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:51:28.323245Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:51:51.130489Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:51:51.147506Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:51:51.158397Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:51:51.158440Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:51:59.494152Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:51:59.494317Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:51:59.563015Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:51:59.564521Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:52:19.421059Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:52:19.422551Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:52:19.898720Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.3 json_ms=0.3 total_ms=1.5 -2026-05-04T20:52:27.078291Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.0 json_ms=0.0 total_ms=1.0 -2026-05-04T20:52:27.315257Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=192 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=2.2 -2026-05-04T20:52:28.560429Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=392 cells_after_filter=297 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=3 filters_raw="Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=2.2 json_ms=0.4 total_ms=2.6 -2026-05-04T20:52:28.707137Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=2.5 json_ms=0.1 total_ms=2.5 -2026-05-04T20:52:34.007339Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=261 cells_after_filter=182 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=2.3 json_ms=0.3 total_ms=2.6 -2026-05-04T20:52:34.257812Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=2266 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=2.6 -2026-05-04T20:52:39.215463Z INFO property_map_server::routes::hexagon_stats: GET /api/hexagon-stats h3=89194ad325bffff resolution=9 total_count=1 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=0.2 -2026-05-04T20:52:39.798060Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=108397 parallel=true cells_before_filter=183 cells_after_filter=134 truncated=false bounds=51.4943,-0.1671,51.5357,-0.0929 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.9 json_ms=0.1 total_ms=1.0 -2026-05-04T20:52:40.048243Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=108397 filters=4 travel=0 total=1583 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=1.7 -2026-05-04T20:52:42.277856Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:52:42.279501Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:52:42.372314Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:52:42.374214Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:53:11.233529Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:53:11.234510Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:53:11.336527Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:53:11.336577Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:53:22.955423Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:53:22.959946Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:53:24.369428Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.2 json_ms=0.3 total_ms=1.5 -2026-05-04T20:53:30.515118Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.4 json_ms=0.0 total_ms=1.4 -2026-05-04T20:53:30.765241Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=192 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=2.7 -2026-05-04T20:53:31.991439Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=392 cells_after_filter=297 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=3 filters_raw="Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=2.7 json_ms=0.3 total_ms=3.1 -2026-05-04T20:53:32.133094Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=1.3 json_ms=0.1 total_ms=1.4 -2026-05-04T20:53:36.787086Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=261 cells_after_filter=182 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=2.7 json_ms=0.2 total_ms=2.9 -2026-05-04T20:53:37.045855Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=2266 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=2.3 -2026-05-04T20:53:41.988437Z INFO property_map_server::routes::hexagon_stats: GET /api/hexagon-stats h3=89194ad325bffff resolution=9 total_count=1 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=0.2 -2026-05-04T20:53:42.536699Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=108397 parallel=true cells_before_filter=183 cells_after_filter=134 truncated=false bounds=51.4943,-0.1671,51.5357,-0.0929 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.1 json_ms=0.1 total_ms=1.2 -2026-05-04T20:53:42.787270Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=108397 filters=4 travel=0 total=1583 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=1.7 -2026-05-04T20:54:06.229359Z INFO property_map_server: Prometheus metrics initialized -2026-05-04T20:54:06.229589Z INFO property_map_server: Loading property data from /app/data/properties.parquet, /app/data/postcode.parquet -2026-05-04T20:54:06.229609Z INFO property_map_server::data::property: Loading postcode features from "/app/data/postcode.parquet" -2026-05-04T20:54:06.354418Z INFO property_map_server::data::property: Postcode features loaded rows=1263786 -2026-05-04T20:54:06.354428Z INFO property_map_server::data::property: Loading properties from "/app/data/properties.parquet" -2026-05-04T20:54:08.909626Z INFO property_map_server::data::property: Properties joined with postcodes rows=15268176 -2026-05-04T20:54:08.909665Z INFO property_map_server::data::property: Feature columns from config numeric=63 enums=6 total=69 -2026-05-04T20:54:11.687418Z WARN property_map_server::data::property: Dropped properties with missing postcode coordinates rows=743076 -2026-05-04T20:54:11.687429Z INFO property_map_server::data::property: Combined data selected rows=14525100 -2026-05-04T20:54:11.818997Z INFO property_map_server::data::property: Extracting numeric feature columns -2026-05-04T20:54:12.212459Z INFO property_map_server::data::property: Computing histograms for numeric features -2026-05-04T20:54:13.526055Z INFO property_map_server::data::property: Extracting string columns -2026-05-04T20:54:14.916447Z INFO property_map_server::data::property: Building enum features -2026-05-04T20:54:16.192264Z INFO property_map_server::data::property: Extracting renovation history -2026-05-04T20:54:18.405306Z INFO property_map_server::data::property: Renovation history extracted properties_with_events=1741939 -2026-05-04T20:54:18.405317Z INFO property_map_server::data::property: Sorting rows by spatial locality -2026-05-04T20:54:19.406915Z INFO property_map_server::data::property: Building interned strings -2026-05-04T20:54:24.606232Z INFO property_map_server::data::property: Transposing to row-major layout (spatially sorted, quantized to u16) -2026-05-04T20:54:27.365951Z INFO property_map_server::data::property: Data loading complete -2026-05-04T20:54:28.895749Z INFO property_map_server: Allocator trim label="property data load" trimmed=true rss_before_mib=13477.7 rss_after_mib=3259.3 released_mib=10218.4 -2026-05-04T20:54:28.895760Z INFO property_map_server: Property data loaded rows=14525100 features=69 enums=6 -2026-05-04T20:54:28.895764Z INFO property_map_server: Building spatial grid index (0.01° cells) -2026-05-04T20:54:29.002022Z INFO property_map_server: Precomputing H3 cells at resolution 12 -2026-05-04T20:54:29.002032Z INFO property_map_server::data::property: Precomputing H3 cells at resolution 12 -2026-05-04T20:54:29.380275Z INFO property_map_server::data::property: H3 precomputation complete (14525100 cells) -2026-05-04T20:54:29.380348Z INFO property_map_server: Loading POI data from /app/data/filtered_uk_pois.parquet -2026-05-04T20:54:29.380360Z INFO property_map_server::data::poi: Loading POI data from "/app/data/filtered_uk_pois.parquet"... -2026-05-04T20:54:29.408407Z INFO property_map_server::data::poi: Loaded 567534 POIs -2026-05-04T20:54:29.551580Z INFO property_map_server::data::poi: POI string columns interned category_unique=94 group_unique=11 emoji_unique=71 -2026-05-04T20:54:29.552752Z INFO property_map_server::data::poi: POI data loading complete. -2026-05-04T20:54:29.598119Z INFO property_map_server: Allocator trim label="poi data load" trimmed=true rss_before_mib=3669.8 rss_after_mib=3477.2 released_mib=192.6 -2026-05-04T20:54:29.598128Z INFO property_map_server: POI data loaded pois=567534 -2026-05-04T20:54:29.598130Z INFO property_map_server: Building POI spatial grid index -2026-05-04T20:54:29.605198Z INFO property_map_server: Loading place data from /app/data/places.parquet -2026-05-04T20:54:29.605209Z INFO property_map_server::data::places: Loading place data from "/app/data/places.parquet"... -2026-05-04T20:54:29.606706Z INFO property_map_server::data::places: Loaded 3474 places -2026-05-04T20:54:29.607673Z INFO property_map_server::data::places: Place data loaded places=3474 types=2 with_population=71 with_city=3392 -2026-05-04T20:54:29.611580Z INFO property_map_server: Allocator trim label="place data load" trimmed=true rss_before_mib=3486.3 rss_after_mib=3481.9 released_mib=4.4 -2026-05-04T20:54:29.611588Z INFO property_map_server: Place data loaded places=3474 -2026-05-04T20:54:29.611595Z INFO property_map_server: Loading postcode boundaries from /app/data/postcode_boundaries -2026-05-04T20:54:29.611605Z INFO property_map_server::data::postcodes: Loading postcode boundaries from "/app/data/postcode_boundaries" -2026-05-04T20:54:29.615798Z INFO property_map_server::data::postcodes: Found GeoJSON files to process files=2361 -2026-05-04T20:54:37.460224Z INFO property_map_server::data::postcodes: Postcode boundary data ready postcodes=1490140 -2026-05-04T20:54:37.828187Z INFO property_map_server: Allocator trim label="postcode boundary load" trimmed=true rss_before_mib=10796.8 rss_after_mib=10613.1 released_mib=183.7 -2026-05-04T20:54:37.828199Z INFO property_map_server: Postcode boundaries loaded postcodes=1490140 -2026-05-04T20:54:37.978691Z INFO property_map_server::data::postcodes: Outcode data derived from postcodes outcodes=2361 -2026-05-04T20:54:37.978752Z INFO property_map_server: Loading PMTiles from /app/data/uk.pmtiles -2026-05-04T20:54:37.982160Z INFO property_map_server: PMTiles loaded successfully -2026-05-04T20:54:38.023911Z INFO property_map_server: No --dist provided; static serving and OG injection disabled -2026-05-04T20:54:38.053118Z INFO property_map_server: Screenshot service configured: http://screenshot:8002 -2026-05-04T20:54:38.053290Z INFO property_map_server: Precomputed features response groups=8 -2026-05-04T20:54:38.053307Z INFO property_map_server: PocketBase configured: http://pocketbase:8090 -2026-05-04T20:54:38.117382Z INFO property_map_server::pocketbase: PocketBase users collection already has all required fields -2026-05-04T20:54:38.127369Z INFO property_map_server::pocketbase: PocketBase collection 'saved_searches' API rules updated -2026-05-04T20:54:38.131471Z INFO property_map_server::pocketbase: PocketBase collection 'saved_properties' API rules updated -2026-05-04T20:54:39.029859Z INFO property_map_server::pocketbase: PocketBase meta.appURL set to https://perfect-postcodes.co.uk/pb -2026-05-04T20:54:39.034138Z INFO property_map_server::pocketbase: PocketBase OAuth configured on users collection -2026-05-04T20:54:39.034181Z INFO property_map_server: Gemini configured (model: gemini-3-flash-preview) -2026-05-04T20:54:39.034199Z INFO property_map_server: Loading travel time data from /app/data/travel-times -2026-05-04T20:54:39.044450Z INFO property_map_server::data::travel_time: Travel time mode discovered mode="bicycle" destinations=2753 -2026-05-04T20:54:39.052389Z INFO property_map_server::data::travel_time: Travel time mode discovered mode="walking" destinations=2753 -2026-05-04T20:54:39.061567Z INFO property_map_server::data::travel_time: Travel time mode discovered mode="car" destinations=2753 -2026-05-04T20:54:39.069032Z INFO property_map_server::data::travel_time: Travel time mode discovered mode="transit" destinations=2752 -2026-05-04T20:54:39.069058Z INFO property_map_server: Travel time store loaded modes=4 -2026-05-04T20:54:39.069116Z INFO property_map_server: Precomputed AI filters system prompt -2026-05-04T20:54:42.362073Z INFO property_map_server: All memory pages locked (mlockall) -2026-05-04T20:54:42.362120Z INFO property_map_server: Server listening on 0.0.0.0:8001 -2026-05-04T20:54:44.656878Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:54:44.659588Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:54:45.153839Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=7.8 json_ms=0.3 total_ms=8.1 -2026-05-04T20:54:52.225820Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.8 json_ms=0.0 total_ms=1.8 -2026-05-04T20:54:52.486600Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=192 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=3.3 -2026-05-04T20:54:53.693488Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=392 cells_after_filter=297 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=3 filters_raw="Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=4.5 json_ms=0.3 total_ms=4.8 -2026-05-04T20:54:53.835597Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=2.0 json_ms=0.1 total_ms=2.1 -2026-05-04T20:54:59.069769Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=261 cells_after_filter=182 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=3.0 json_ms=0.2 total_ms=3.2 -2026-05-04T20:54:59.318387Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=2266 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=3.1 -2026-05-04T20:55:04.869956Z INFO property_map_server::routes::hexagon_stats: GET /api/hexagon-stats h3=89194ad325bffff resolution=9 total_count=1 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=0.1 -2026-05-04T20:55:05.492428Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=108397 parallel=true cells_before_filter=183 cells_after_filter=134 truncated=false bounds=51.4943,-0.1671,51.5357,-0.0929 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.0 json_ms=0.1 total_ms=1.1 -2026-05-04T20:55:05.740336Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=108397 filters=4 travel=0 total=1583 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=1.9 -2026-05-04T20:56:01.654138Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:56:01.655404Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:56:01.767071Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:56:01.767084Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:56:10.929872Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:56:10.931080Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:56:11.027480Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:56:11.027485Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:56:18.496076Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:56:18.498033Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:56:18.609718Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:56:18.609722Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:56:25.513529Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:56:25.514564Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:56:25.597043Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:56:25.597046Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:56:31.486143Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:56:31.487527Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:56:31.606622Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:56:31.606639Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:58:22.820424Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=3047 parallel=false cells_before_filter=155 cells_after_filter=134 truncated=false bounds=51.0079,-1.2718,51.0856,-1.0894 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.1 json_ms=0.1 total_ms=0.3 -2026-05-04T20:58:24.198002Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:58:24.202971Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:58:30.601261Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:58:32.202753Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:58:32.204070Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:58:33.011967Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.8 json_ms=0.2 total_ms=2.1 -2026-05-04T20:58:39.599425Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.0 json_ms=0.0 total_ms=1.1 -2026-05-04T20:58:39.850441Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=192 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=2.4 -2026-05-04T20:58:41.031219Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=392 cells_after_filter=297 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=3 filters_raw="Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=1.8 json_ms=0.3 total_ms=2.1 -2026-05-04T20:58:41.174917Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=2.1 json_ms=0.1 total_ms=2.1 -2026-05-04T20:58:46.162873Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=261 cells_after_filter=182 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=3.4 json_ms=0.2 total_ms=3.6 -2026-05-04T20:58:46.412423Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=2266 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=2.4 -2026-05-04T20:58:50.430953Z INFO property_map_server::routes::hexagon_stats: GET /api/hexagon-stats h3=89194ad325bffff resolution=9 total_count=1 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=0.2 -2026-05-04T20:58:51.017617Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=108397 parallel=true cells_before_filter=183 cells_after_filter=134 truncated=false bounds=51.4943,-0.1671,51.5357,-0.0929 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.0 json_ms=0.1 total_ms=1.1 -2026-05-04T20:58:51.263827Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=108397 filters=4 travel=0 total=1583 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=1.6 -2026-05-04T20:59:12.741182Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:59:14.169075Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:59:14.169081Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:59:14.985002Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T20:59:14.986260Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T20:59:15.452832Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=537 cells_after_filter=412 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=0 filters_raw="-" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.9 json_ms=0.4 total_ms=2.3 -2026-05-04T20:59:22.096990Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.1 json_ms=0.0 total_ms=1.2 -2026-05-04T20:59:22.344347Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=192 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=2.2 -2026-05-04T20:59:23.588305Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=392 cells_after_filter=297 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=3 filters_raw="Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=1.4 json_ms=0.3 total_ms=1.6 -2026-05-04T20:59:23.737506Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=52 cells_after_filter=37 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:0:600000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=1 travel_entries=0 grid_ms=0.0 agg_ms=1.1 json_ms=0.0 total_ms=1.2 -2026-05-04T20:59:28.698263Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=152340 parallel=true cells_before_filter=261 cells_after_filter=182 truncated=false bounds=51.4943,-0.1794,51.5357,-0.0806 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=1.9 json_ms=0.2 total_ms=2.1 -2026-05-04T20:59:28.949287Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=152340 filters=4 travel=0 total=2266 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=3.4 -2026-05-04T20:59:33.021084Z INFO property_map_server::routes::hexagon_stats: GET /api/hexagon-stats h3=89194ad325bffff resolution=9 total_count=1 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=0.1 -2026-05-04T20:59:33.564060Z INFO property_map_server::routes::hexagons: GET /api/hexagons resolution=9 rows=108397 parallel=true cells_before_filter=183 cells_after_filter=134 truncated=false bounds=51.4943,-0.1671,51.5357,-0.0929 filters=4 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" fields=0 travel_entries=0 grid_ms=0.0 agg_ms=0.9 json_ms=0.1 total_ms=1.0 -2026-05-04T20:59:33.817368Z INFO property_map_server::routes::filter_counts: GET /api/filter-counts rows=108397 filters=4 travel=0 total=1583 filters_raw="Estimated current price:12976.594:1380000;;Number of bedrooms & living rooms:4:6;;Property type:Detached|Semi-Detached|Terraced;;Distance to nearest train or tube station (km):0:1" ms=1.9 -2026-05-04T21:01:19.542960Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T21:01:19.544562Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 -2026-05-04T21:01:19.556893Z INFO property_map_server::routes::features: GET /api/features -2026-05-04T21:01:19.558130Z INFO property_map_server::routes::pois: GET /api/poi-categories count=94 groups=11 diff --git a/server-rs/src/data.rs b/server-rs/src/data.rs index 6087166..e91f73c 100644 --- a/server-rs/src/data.rs +++ b/server-rs/src/data.rs @@ -4,10 +4,40 @@ mod postcodes; mod property; pub mod travel_time; -pub use places::PlaceData; -pub use poi::{POICategoryGroup, POIData}; +fn panic_payload_message(payload: &(dyn std::any::Any + Send)) -> String { + if let Some(message) = payload.downcast_ref::<&'static str>() { + (*message).to_string() + } else if let Some(message) = payload.downcast_ref::() { + message.clone() + } else { + "unknown panic payload".to_string() + } +} + +pub(super) fn run_polars_io(work: F) -> anyhow::Result +where + T: Send, + F: FnOnce() -> anyhow::Result + Send, +{ + if tokio::runtime::Handle::try_current().is_err() { + return work(); + } + + std::thread::scope(|scope| { + scope.spawn(work).join().map_err(|payload| { + anyhow::anyhow!( + "Polars worker thread panicked: {}", + panic_payload_message(payload.as_ref()) + ) + })? + }) +} + +pub use places::{normalize_search_text, PlaceData}; +pub use poi::{resolve_poi_category_filter, POICategoryGroup, POIData}; pub use postcodes::{OutcodeData, PostcodeData}; pub use property::{ - precompute_h3, FeatureStats, Histogram, PropertyData, QuantRef, RenovationEvent, + precompute_h3, FeatureStats, Histogram, PostcodePoiMetrics, PropertyData, QuantRef, + RenovationEvent, }; pub use travel_time::{slugify, TravelTimeStore}; diff --git a/server-rs/src/data/places.rs b/server-rs/src/data/places.rs index 25f4f6c..108bc3c 100644 --- a/server-rs/src/data/places.rs +++ b/server-rs/src/data/places.rs @@ -11,22 +11,127 @@ use crate::utils::InternedColumn; pub struct PlaceData { pub name: Vec, pub name_lower: Vec, + pub name_search: Vec, pub place_type: InternedColumn, pub type_rank: Vec, pub population: Vec, pub lat: Vec, pub lon: Vec, pub city: Vec>, + pub travel_destination: Vec, } fn type_rank(place_type: &str) -> u8 { match place_type { "city" => 0, - "station" => 1, - _ => 2, + "town" => 1, + "village" => 2, + "suburb" | "neighbourhood" | "quarter" | "borough" | "locality" => 3, + "station" => 4, + "hamlet" | "isolated_dwelling" | "island" => 5, + _ => 6, } } +pub fn is_travel_destination_type(place_type: &str) -> bool { + matches!(place_type, "city" | "station") +} + +pub fn normalize_search_text(text: &str) -> String { + let mut result = String::with_capacity(text.len()); + let mut last_was_space = true; + + for ch in text.chars() { + if ch == '\'' || ch == '’' || ch == '`' { + continue; + } + + let lower = ch.to_ascii_lowercase(); + if lower.is_ascii_alphanumeric() { + result.push(lower); + last_was_space = false; + } else if !last_was_space { + result.push(' '); + last_was_space = true; + } + } + + if result.ends_with(' ') { + result.pop(); + } + result +} + +fn replace_token(text: &str, from: &str, to: &str) -> Option { + let mut changed = false; + let replaced: Vec<&str> = text + .split_whitespace() + .map(|token| { + if token == from { + changed = true; + to + } else { + token + } + }) + .collect(); + + changed.then(|| replaced.join(" ")) +} + +fn push_alias(aliases: &mut Vec, alias: String) { + if !alias.is_empty() && !aliases.iter().any(|existing| existing == &alias) { + aliases.push(alias); + } +} + +fn build_search_text(name: &str, place_type: &str) -> String { + let primary = normalize_search_text(name); + let mut aliases = vec![primary.clone()]; + + if let Some(alias) = replace_token(&primary, "st", "saint") { + push_alias(&mut aliases, alias); + } + if let Some(alias) = replace_token(&primary, "saint", "st") { + push_alias(&mut aliases, alias); + } + + if place_type == "station" { + let suffix_aliases: [(&str, &[&str]); 5] = [ + ( + " tube station", + &[" underground station", " station", " tube", " underground"], + ), + ( + " underground station", + &[" tube station", " station", " tube", " underground"], + ), + ( + " railway station", + &[" rail station", " station", " railway", " rail"], + ), + ( + " overground station", + &[" station", " overground", " railway station"], + ), + ( + " elizabeth line station", + &[" station", " elizabeth line", " crossrail station"], + ), + ]; + + for (suffix, replacements) in suffix_aliases { + if let Some(stem) = primary.strip_suffix(suffix) { + for replacement in replacements { + push_alias(&mut aliases, format!("{stem}{replacement}")); + } + } + } + } + + aliases.join(" | ") +} + fn extract_str_col(df: &DataFrame, name: &str) -> anyhow::Result> { let column = df .column(name) @@ -56,6 +161,23 @@ fn extract_f32_col(df: &DataFrame, name: &str) -> anyhow::Result> { .collect()) } +fn extract_bool_col_or_default( + df: &DataFrame, + name: &str, + default_value: bool, +) -> anyhow::Result> { + let Ok(column) = df.column(name) else { + return Ok(vec![default_value; df.height()]); + }; + let bool_column = column + .bool() + .with_context(|| format!("Column '{name}' is not a boolean column"))?; + Ok(bool_column + .into_iter() + .map(|value| value.unwrap_or(default_value)) + .collect()) +} + impl PlaceData { pub fn load(parquet_path: &Path) -> anyhow::Result { info!("Loading place data from {:?}...", parquet_path); @@ -80,8 +202,21 @@ impl PlaceData { }; let name_lower: Vec = name.iter().map(|nm| nm.to_lowercase()).collect(); + let name_search: Vec = name + .iter() + .zip(&place_type_raw) + .map(|(nm, pt)| build_search_text(nm, pt)) + .collect(); let type_rank_vec: Vec = place_type_raw.iter().map(|pt| type_rank(pt)).collect(); let place_type = InternedColumn::build(&place_type_raw); + let travel_destination = if df.column("travel_destination").is_ok() { + extract_bool_col_or_default(&df, "travel_destination", true)? + } else { + place_type_raw + .iter() + .map(|place_type| is_travel_destination_type(place_type)) + .collect() + }; // Precompute nearest city for each non-city place let city_indices: Vec = type_rank_vec @@ -133,12 +268,14 @@ impl PlaceData { Ok(PlaceData { name, name_lower, + name_search, place_type, type_rank: type_rank_vec, population, lat, lon, city, + travel_destination, }) } } @@ -149,7 +286,23 @@ mod tests { #[test] fn type_rank_ordering() { - assert!(type_rank("city") < type_rank("station")); + assert!(type_rank("city") < type_rank("town")); + assert!(type_rank("town") < type_rank("station")); assert!(type_rank("station") < type_rank("unknown")); } + + #[test] + fn search_text_handles_common_address_variants() { + assert!(build_search_text("King's Cross tube station", "station") + .contains("kings cross underground")); + assert!(build_search_text("St Albans", "city").contains("saint albans")); + } + + #[test] + fn travel_destination_types_match_legacy_places() { + assert!(is_travel_destination_type("city")); + assert!(is_travel_destination_type("station")); + assert!(!is_travel_destination_type("town")); + assert!(!is_travel_destination_type("suburb")); + } } diff --git a/server-rs/src/data/poi.rs b/server-rs/src/data/poi.rs index 8488cfb..503610b 100644 --- a/server-rs/src/data/poi.rs +++ b/server-rs/src/data/poi.rs @@ -26,6 +26,7 @@ pub struct POIData { id_lengths: Vec, pub group: InternedColumn, pub category: InternedColumn, + pub icon_category: InternedColumn, pub name: Vec, pub lat: Vec, pub lng: Vec, @@ -93,6 +94,15 @@ impl POIData { let lat = extract_f32_col(&df, "lat", 0.0)?; let lng = extract_f32_col(&df, "lng", 0.0)?; let emoji_raw = extract_str_col(&df, "emoji")?; + let icon_category_raw = if df + .get_column_names() + .iter() + .any(|name| name.as_str() == "icon_category") + { + extract_str_col(&df, "icon_category")? + } else { + category_raw.clone() + }; // Pack POI IDs into a contiguous buffer let total_id_bytes: usize = id_raw.iter().map(|s| s.len()).sum(); @@ -108,11 +118,13 @@ impl POIData { } let category = InternedColumn::build(&category_raw); + let icon_category = InternedColumn::build(&icon_category_raw); let group = InternedColumn::build(&group_raw); let emoji = InternedColumn::build(&emoji_raw); info!( category_unique = category.values.len(), + icon_category_unique = icon_category.values.len(), group_unique = group.values.len(), emoji_unique = emoji.values.len(), "POI string columns interned" @@ -131,6 +143,7 @@ impl POIData { id_lengths, name, category, + icon_category, group, lat, lng, diff --git a/server-rs/src/data/property.rs b/server-rs/src/data/property.rs index 3126ae0..612a021 100644 --- a/server-rs/src/data/property.rs +++ b/server-rs/src/data/property.rs @@ -5,11 +5,16 @@ use rayon::prelude::*; use serde::Serialize; use std::path::Path; -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap, FxHashSet}; use crate::consts::{H3_PRECOMPUTE_MAX, HISTOGRAM_BINS, NAN_U16, QUANT_SCALE}; use crate::features::{self, Bounds}; +const ADDRESS_SEARCH_CANDIDATE_LIMIT: usize = 50_000; +const ADDRESS_SEARCH_MAX_POSTINGS_PER_TOKEN: usize = 250_000; +const ADDRESS_SEARCH_PREFIX_MIN_LEN: usize = 4; +const ADDRESS_SEARCH_PREFIX_MAX_LEN: usize = 8; + fn is_numeric_dtype(dtype: &DataType) -> bool { matches!( dtype, @@ -32,6 +37,361 @@ fn is_datetime_dtype(dtype: &DataType) -> bool { matches!(dtype, DataType::Datetime(_, _) | DataType::Date) } +#[derive(Clone, Debug)] +struct AddressTermGroup { + alternatives: Vec, +} + +#[derive(Debug)] +struct AddressQuery { + full_postcode: Option, + text_groups: Vec, + numeric_terms: Vec, + candidate_terms: Vec, +} + +fn tokenize_address_text(text: &str) -> Vec { + let mut tokens = Vec::new(); + let mut current = String::new(); + + for ch in text.chars() { + if ch.is_ascii_alphanumeric() { + current.push(ch.to_ascii_lowercase()); + } else if matches!(ch, '\'' | '’' | '`') { + continue; + } else if !current.is_empty() { + tokens.push(std::mem::take(&mut current)); + } + } + + if !current.is_empty() { + tokens.push(current); + } + + tokens +} + +fn is_full_postcode_compact(compact: &str) -> bool { + let bytes = compact.as_bytes(); + let len = bytes.len(); + if !(5..=7).contains(&len) { + return false; + } + + let inward = &bytes[len - 3..]; + if !inward[0].is_ascii_digit() + || !inward[1].is_ascii_alphabetic() + || !inward[2].is_ascii_alphabetic() + { + return false; + } + + let outward = &bytes[..len - 3]; + if !(2..=4).contains(&outward.len()) { + return false; + } + + outward[0].is_ascii_alphabetic() + && outward.iter().all(u8::is_ascii_alphanumeric) + && outward.iter().any(u8::is_ascii_digit) +} + +fn canonical_postcode_from_compact(compact: &str) -> String { + let upper = compact.to_ascii_uppercase(); + let split = upper.len() - 3; + format!("{} {}", &upper[..split], &upper[split..]) +} + +fn extract_full_postcode(tokens: &[String]) -> Option<(String, Vec)> { + for (idx, token) in tokens.iter().enumerate() { + let compact = token.to_ascii_uppercase(); + if is_full_postcode_compact(&compact) { + return Some((canonical_postcode_from_compact(&compact), vec![idx])); + } + } + + for idx in 0..tokens.len().saturating_sub(1) { + let compact = format!( + "{}{}", + tokens[idx].to_ascii_uppercase(), + tokens[idx + 1].to_ascii_uppercase() + ); + if is_full_postcode_compact(&compact) { + return Some(( + canonical_postcode_from_compact(&compact), + vec![idx, idx + 1], + )); + } + } + + None +} + +fn looks_like_postcode_fragment(token: &str) -> bool { + (2..=4).contains(&token.len()) + && token + .chars() + .next() + .is_some_and(|ch| ch.is_ascii_alphabetic()) + && token.chars().any(|ch| ch.is_ascii_digit()) + && token.chars().all(|ch| ch.is_ascii_alphanumeric()) +} + +fn is_numeric_address_token(token: &str) -> bool { + token.chars().all(|ch| ch.is_ascii_digit()) +} + +fn address_token_aliases(token: &str) -> Vec<&'static str> { + match token { + "apt" => vec!["apt", "apartment"], + "apartment" => vec!["apartment", "apt"], + "ave" => vec!["ave", "avenue"], + "avenue" => vec!["avenue", "ave"], + "blvd" => vec!["blvd", "boulevard"], + "boulevard" => vec!["boulevard", "blvd"], + "cl" => vec!["cl", "close"], + "close" => vec!["close", "cl"], + "ct" => vec!["ct", "court"], + "court" => vec!["court", "ct"], + "cres" => vec!["cres", "crescent"], + "crescent" => vec!["crescent", "cres"], + "dr" => vec!["dr", "drive"], + "drive" => vec!["drive", "dr"], + "fl" => vec!["fl", "flat"], + "flat" => vec!["flat", "fl"], + "gdns" => vec!["gdns", "gardens", "garden"], + "garden" => vec!["garden", "gardens", "gdns"], + "gardens" => vec!["gardens", "garden", "gdns"], + "hse" => vec!["hse", "house"], + "house" => vec!["house", "hse"], + "ln" => vec!["ln", "lane"], + "lane" => vec!["lane", "ln"], + "rd" => vec!["rd", "road"], + "road" => vec!["road", "rd"], + "sq" => vec!["sq", "square"], + "square" => vec!["square", "sq"], + "st" => vec!["st", "street", "saint"], + "street" => vec!["street", "st"], + "saint" => vec!["saint", "st"], + "terr" => vec!["terr", "terrace"], + "terrace" => vec!["terrace", "terr"], + _ => Vec::new(), + } +} + +fn is_address_stop_token(token: &str) -> bool { + matches!( + token, + "a" | "an" + | "and" + | "apartment" + | "apt" + | "avenue" + | "ave" + | "block" + | "building" + | "bungalow" + | "close" + | "cl" + | "court" + | "ct" + | "cres" + | "crescent" + | "drive" + | "dr" + | "estate" + | "flat" + | "fl" + | "floor" + | "garden" + | "gardens" + | "gdns" + | "grove" + | "house" + | "hse" + | "lane" + | "ln" + | "lodge" + | "mansions" + | "mews" + | "of" + | "park" + | "place" + | "road" + | "rd" + | "room" + | "row" + | "saint" + | "sq" + | "square" + | "st" + | "street" + | "terr" + | "terrace" + | "the" + | "unit" + | "view" + | "villas" + | "walk" + | "way" + | "yard" + ) +} + +fn address_term_group(token: &str) -> Option { + if token.len() < 3 || is_numeric_address_token(token) || looks_like_postcode_fragment(token) { + return None; + } + + let mut alternatives = Vec::new(); + alternatives.push(token.to_string()); + for alias in address_token_aliases(token) { + if !alternatives.iter().any(|existing| existing == alias) { + alternatives.push(alias.to_string()); + } + } + + if alternatives + .iter() + .all(|alternative| is_address_stop_token(alternative)) + { + return None; + } + + Some(AddressTermGroup { alternatives }) +} + +fn address_search_tokens(text: &str) -> Vec { + let mut tokens: Vec = tokenize_address_text(text) + .into_iter() + .filter(|token| is_address_search_token(token)) + .collect(); + tokens.sort_unstable(); + tokens.dedup(); + tokens +} + +fn is_address_search_token(token: &str) -> bool { + if looks_like_postcode_fragment(token) { + return false; + } + + if is_numeric_address_token(token) { + return true; + } + + if token.chars().any(|ch| ch.is_ascii_digit()) { + return token.len() >= 2; + } + + token.len() >= 3 +} + +fn is_address_candidate_token(token: &str) -> bool { + !is_numeric_address_token(token) + && !looks_like_postcode_fragment(token) + && (token.chars().any(|ch| ch.is_ascii_digit()) + || (token.len() >= 3 && !is_address_stop_token(token))) +} + +fn address_prefix_key(term: &str) -> &str { + if term.len() > ADDRESS_SEARCH_PREFIX_MAX_LEN { + &term[..ADDRESS_SEARCH_PREFIX_MAX_LEN] + } else { + term + } +} + +fn build_address_prefix_index( + address_token_index: &FxHashMap>, +) -> FxHashMap> { + let mut prefix_index: FxHashMap> = FxHashMap::default(); + + for token in address_token_index.keys() { + let max_prefix_len = token.len().min(ADDRESS_SEARCH_PREFIX_MAX_LEN); + for prefix_len in ADDRESS_SEARCH_PREFIX_MIN_LEN..=max_prefix_len { + prefix_index + .entry(token[..prefix_len].to_string()) + .or_default() + .push(token.clone()); + } + } + + for tokens in prefix_index.values_mut() { + tokens.sort_unstable(); + tokens.dedup(); + } + + prefix_index +} + +fn parse_address_query(query: &str) -> AddressQuery { + let tokens = tokenize_address_text(query); + let (full_postcode, postcode_token_indices) = extract_full_postcode(&tokens) + .map(|(postcode, indices)| (Some(postcode), indices)) + .unwrap_or((None, Vec::new())); + + let skip_postcode_tokens: FxHashSet = postcode_token_indices.into_iter().collect(); + let mut text_groups = Vec::new(); + let mut numeric_terms = Vec::new(); + let mut candidate_terms = Vec::new(); + + for (idx, token) in tokens.iter().enumerate() { + if skip_postcode_tokens.contains(&idx) || looks_like_postcode_fragment(token) { + continue; + } + + if is_numeric_address_token(token) { + numeric_terms.push(token.clone()); + continue; + } + + if let Some(group) = address_term_group(token) { + for alternative in &group.alternatives { + if !is_address_stop_token(alternative) + && !candidate_terms.iter().any(|term| term == alternative) + { + candidate_terms.push(alternative.clone()); + } + } + text_groups.push(group); + } else if token.chars().any(|ch| ch.is_ascii_digit()) && token.len() >= 2 { + numeric_terms.push(token.clone()); + if !candidate_terms.iter().any(|term| term == token) { + candidate_terms.push(token.clone()); + } + } + } + + text_groups.dedup_by(|left, right| left.alternatives == right.alternatives); + numeric_terms.sort_unstable(); + numeric_terms.dedup(); + + AddressQuery { + full_postcode, + text_groups, + numeric_terms, + candidate_terms, + } +} + +fn token_matches_query_term(token: &str, query_term: &str) -> bool { + token == query_term || (query_term.len() >= 3 && token.starts_with(query_term)) +} + +fn token_matches_numeric_term(token: &str, query_term: &str) -> bool { + token == query_term || token.starts_with(query_term) +} + +#[cfg(test)] +fn address_tokens_match_group(tokens: &[String], group: &AddressTermGroup) -> bool { + group.alternatives.iter().any(|alternative| { + tokens + .iter() + .any(|token| token_matches_query_term(token, alternative)) + }) +} + /// Histogram with outlier buckets at the edges. /// - Bin 0: [min, p1) — low outliers /// - Bins 1 to n-2: [p1, p99) — main distribution, evenly divided @@ -154,6 +514,8 @@ pub struct PropertyData { /// Per-feature: max - min (for encoding filter bounds). quant_range: Vec, pub feature_stats: Vec, + /// Unquantized last sale price used by the price-history chart. + last_known_price_raw: Vec, /// Contiguous buffer holding all address strings end-to-end. address_buffer: String, /// Byte offset into `address_buffer` where each row's address starts. @@ -163,6 +525,20 @@ pub struct PropertyData { /// Interned postcodes: reader is thread-safe, keys index into it. postcode_interner: lasso::RodeoReader, postcode_keys: Vec, + /// Rows for each postcode, keyed by the interned postcode key. + postcode_row_index: FxHashMap>, + /// Inverted index from address tokens to property rows. + address_token_index: FxHashMap>, + /// Prefix lookup from typed address-token prefix to indexed full address tokens. + address_prefix_index: FxHashMap>, + /// Interned normalized address-search tokens used for per-row scoring. + address_search_interner: lasso::RodeoReader, + /// Flat per-row normalized address-search token keys. + address_search_token_keys: Vec, + /// Offset into `address_search_token_keys` for each row. + address_search_token_offsets: Vec, + /// Number of normalized address-search token keys for each row. + address_search_token_lengths: Vec, /// For enum features: maps feature index to list of possible string values. /// Index in values list corresponds to the u16 value stored in feature_data. pub enum_values: rustc_hash::FxHashMap>, @@ -197,6 +573,164 @@ impl PropertyData { (&self.postcode_interner, &self.postcode_keys) } + fn row_address_search_tokens(&self, row: usize) -> &[lasso::Spur] { + let offset = self.address_search_token_offsets[row] as usize; + let length = self.address_search_token_lengths[row] as usize; + &self.address_search_token_keys[offset..offset + length] + } + + /// Search individual property addresses. Full postcode queries use a direct row index; + /// free-text queries use a small inverted index over distinctive address tokens. + pub fn search_addresses(&self, query: &str, limit: usize) -> Vec { + if limit == 0 { + return Vec::new(); + } + + let parsed = parse_address_query(query); + if parsed.full_postcode.is_none() + && parsed.text_groups.is_empty() + && parsed.numeric_terms.is_empty() + { + return Vec::new(); + } + + let candidate_rows: Vec = if let Some(postcode) = parsed.full_postcode.as_deref() { + self.postcode_interner + .get(postcode) + .and_then(|key| self.postcode_row_index.get(&key)) + .map(|rows| rows.to_vec()) + .unwrap_or_default() + } else if let Some(rows) = self.best_address_token_rows(&parsed.candidate_terms) { + rows.iter() + .take(ADDRESS_SEARCH_CANDIDATE_LIMIT) + .copied() + .collect() + } else { + Vec::new() + }; + + if candidate_rows.is_empty() { + return Vec::new(); + } + + let mut scored: Vec<(i32, usize, usize)> = candidate_rows + .into_iter() + .filter_map(|row| { + let row = row as usize; + self.address_match_score(row, &parsed) + .map(|score| (score, self.address(row).len(), row)) + }) + .collect(); + + scored.sort_unstable_by(|left, right| { + right + .0 + .cmp(&left.0) + .then(left.1.cmp(&right.1)) + .then(left.2.cmp(&right.2)) + }); + + let mut seen = FxHashSet::default(); + let mut results = Vec::with_capacity(limit); + for (_, _, row) in scored { + let address = self.address(row).trim(); + if address.is_empty() { + continue; + } + let key = format!("{}\n{}", address.to_ascii_lowercase(), self.postcode(row)); + if !seen.insert(key) { + continue; + } + results.push(row); + if results.len() == limit { + break; + } + } + + results + } + + fn best_address_token_rows(&self, terms: &[String]) -> Option<&[u32]> { + let mut best: Option<&[u32]> = None; + + for term in terms { + if let Some(rows) = self.address_token_index.get(term) { + if best.is_none_or(|current| rows.len() < current.len()) { + best = Some(rows.as_slice()); + } + continue; + } + + if term.len() < 4 { + continue; + } + + if let Some(tokens) = self.address_prefix_index.get(address_prefix_key(term)) { + for token in tokens { + if !token.starts_with(term) { + continue; + } + if let Some(rows) = self.address_token_index.get(token) { + if best.is_none_or(|current| rows.len() < current.len()) { + best = Some(rows.as_slice()); + } + } + } + } + } + + best + } + + fn address_match_score(&self, row: usize, parsed: &AddressQuery) -> Option { + if self.address(row).trim().is_empty() { + return None; + } + + let tokens = self.row_address_search_tokens(row); + if parsed + .text_groups + .iter() + .any(|group| !self.address_tokens_match_group(tokens, group)) + { + return None; + } + + let numeric_matches = parsed + .numeric_terms + .iter() + .filter(|term| { + tokens.iter().any(|token| { + token_matches_numeric_term(self.address_search_interner.resolve(token), term) + }) + }) + .count(); + + if !parsed.numeric_terms.is_empty() && numeric_matches == 0 { + return None; + } + + let mut score = 0; + if parsed.full_postcode.is_some() { + score += 1_000; + } + score += (parsed.text_groups.len() as i32) * 200; + score += (numeric_matches as i32) * 90; + if numeric_matches == parsed.numeric_terms.len() && numeric_matches > 0 { + score += 50; + } + + Some(score) + } + + fn address_tokens_match_group(&self, tokens: &[lasso::Spur], group: &AddressTermGroup) -> bool { + group.alternatives.iter().any(|alternative| { + tokens.iter().any(|token| { + token_matches_query_term(self.address_search_interner.resolve(token), alternative) + }) + }) + } + /// Get the is_approx_build_date flag for a given row (bit-packed). pub fn is_approx_build_date(&self, row: usize) -> bool { let byte = self.approx_build_date_bits[row / 8]; @@ -223,6 +757,12 @@ impl PropertyData { self.price_qualifier.get(&(row as u32)).map(String::as_str) } + /// Get the unquantized last sale price for charting. + #[inline] + pub fn last_known_price_raw(&self, row: usize) -> f32 { + self.last_known_price_raw[row] + } + /// Decode a single feature value from quantized u16 storage. #[inline] pub fn get_feature(&self, row: usize, feat_idx: usize) -> f32 { @@ -945,28 +1485,80 @@ impl PropertyData { .iter() .map(|&perm_index| lon[perm_index as usize]) .collect(); + let last_known_price_raw: Vec = numeric_names + .iter() + .position(|&name| name == "Last known price") + .map(|price_idx| { + perm.iter() + .map(|&perm_index| numeric_col_major[price_idx][perm_index as usize]) + .collect() + }) + .unwrap_or_else(|| vec![f32::NAN; row_count]); - // Build contiguous address buffer (permuted) + // Build contiguous address buffer and address search index (permuted) tracing::info!("Building interned strings"); let total_addr_bytes: usize = address_raw.iter().map(|text| text.len()).sum(); let mut address_buffer = String::with_capacity(total_addr_bytes); let mut address_offsets = Vec::with_capacity(row_count); let mut address_lengths = Vec::with_capacity(row_count); - for &perm_index in &perm { + let mut address_token_index: FxHashMap> = FxHashMap::default(); + let mut address_search_rodeo = lasso::Rodeo::default(); + let mut address_search_token_keys: Vec = Vec::new(); + let mut address_search_token_offsets = Vec::with_capacity(row_count); + let mut address_search_token_lengths = Vec::with_capacity(row_count); + for (new_row, &perm_index) in perm.iter().enumerate() { let addr = &address_raw[perm_index as usize]; let offset = address_buffer.len() as u32; let length = addr.len().min(u16::MAX as usize) as u16; address_offsets.push(offset); address_lengths.push(length); address_buffer.push_str(&addr[..length as usize]); + + let search_tokens = address_search_tokens(addr); + let token_offset = address_search_token_keys.len() as u32; + let token_length = search_tokens.len().min(u16::MAX as usize) as u16; + address_search_token_offsets.push(token_offset); + address_search_token_lengths.push(token_length); + + for token in search_tokens.iter().take(token_length as usize) { + let key = address_search_rodeo.get_or_intern(token); + address_search_token_keys.push(key); + + if is_address_candidate_token(token) { + address_token_index + .entry(token.clone()) + .or_default() + .push(new_row as u32); + } + } } + let address_token_count_before_prune = address_token_index.len(); + address_token_index.retain(|_, rows| rows.len() <= ADDRESS_SEARCH_MAX_POSTINGS_PER_TOKEN); + let address_prefix_index = build_address_prefix_index(&address_token_index); + let address_search_interner = address_search_rodeo.into_reader(); + let address_postings_count: usize = address_token_index.values().map(Vec::len).sum(); + tracing::info!( + tokens = address_token_index.len(), + prefixes = address_prefix_index.len(), + pruned_tokens = + address_token_count_before_prune.saturating_sub(address_token_index.len()), + postings = address_postings_count, + row_tokens = address_search_token_keys.len(), + "Address search index built" + ); // Intern postcodes (permuted) let mut postcode_rodeo = lasso::Rodeo::default(); - let postcode_keys: Vec = perm - .iter() - .map(|&perm_index| postcode_rodeo.get_or_intern(&postcode_raw[perm_index as usize])) - .collect(); + let mut postcode_keys: Vec = Vec::with_capacity(row_count); + let mut postcode_row_index: FxHashMap> = FxHashMap::default(); + for (new_row, &perm_index) in perm.iter().enumerate() { + let key = postcode_rodeo.get_or_intern(&postcode_raw[perm_index as usize]); + postcode_keys.push(key); + postcode_row_index + .entry(key) + .or_default() + .push(new_row as u32); + } let postcode_interner = postcode_rodeo.into_reader(); // Pack is_approx_build_date into a bitvec (8 bools per byte) @@ -1105,11 +1697,19 @@ impl PropertyData { quant_min, quant_range, feature_stats, + last_known_price_raw, address_buffer, address_offsets, address_lengths, postcode_interner, postcode_keys, + postcode_row_index, + address_token_index, + address_prefix_index, + address_search_interner, + address_search_token_keys, + address_search_token_offsets, + address_search_token_lengths, enum_values, enum_counts, approx_build_date_bits, @@ -1133,6 +1733,120 @@ mod tests { Bounds::Percentile { low, high } } + #[test] + fn full_postcode_detection_accepts_common_formats() { + assert!(is_full_postcode_compact("SW1A1AA")); + assert!(is_full_postcode_compact("E142DG")); + assert!(is_full_postcode_compact("M11AE")); + assert!(!is_full_postcode_compact("E14")); + assert!(!is_full_postcode_compact("DOWNING")); + assert!(!is_full_postcode_compact("10A")); + } + + #[test] + fn address_query_parsing_skips_postcodes_and_street_suffixes() { + let parsed = parse_address_query("Flat 2, 10 Downing St, SW1A 2AA"); + + assert_eq!(parsed.full_postcode.as_deref(), Some("SW1A 2AA")); + assert_eq!( + parsed.numeric_terms, + vec!["10".to_string(), "2".to_string()] + ); + assert_eq!(parsed.candidate_terms, vec!["downing".to_string()]); + assert_eq!(parsed.text_groups.len(), 1); + assert_eq!( + parsed.text_groups[0].alternatives, + vec!["downing".to_string()] + ); + } + + #[test] + fn address_query_parsing_handles_compact_postcodes() { + let parsed = parse_address_query("10 downing street sw1a1aa"); + + assert_eq!(parsed.full_postcode.as_deref(), Some("SW1A 1AA")); + assert_eq!(parsed.numeric_terms, vec!["10".to_string()]); + assert_eq!(parsed.candidate_terms, vec!["downing".to_string()]); + } + + #[test] + fn address_query_parsing_keeps_partial_terms_for_row_matching() { + let parsed = parse_address_query("settlers cour"); + + assert_eq!(parsed.full_postcode, None); + assert_eq!(parsed.numeric_terms, Vec::::new()); + assert_eq!( + parsed.candidate_terms, + vec!["settlers".to_string(), "cour".to_string()] + ); + assert_eq!(parsed.text_groups.len(), 2); + assert_eq!( + parsed.text_groups[0].alternatives, + vec!["settlers".to_string()] + ); + assert_eq!(parsed.text_groups[1].alternatives, vec!["cour".to_string()]); + } + + #[test] + fn address_search_tokens_keep_actual_address_terms_for_scoring() { + let tokens = address_search_tokens("Flat 2, 10 Downing Cour"); + + assert_eq!( + tokens, + vec![ + "10".to_string(), + "2".to_string(), + "cour".to_string(), + "downing".to_string(), + "flat".to_string() + ] + ); + } + + #[test] + fn address_prefix_index_finds_partial_address_terms() { + let mut token_index: FxHashMap> = FxHashMap::default(); + token_index.insert("downing".to_string(), vec![1]); + token_index.insert("downton".to_string(), vec![2]); + token_index.insert("market".to_string(), vec![3]); + + let prefix_index = build_address_prefix_index(&token_index); + + assert_eq!( + prefix_index.get("down").cloned().unwrap_or_default(), + vec!["downing".to_string(), "downton".to_string()] + ); + assert_eq!( + prefix_index.get("downi").cloned().unwrap_or_default(), + vec!["downing".to_string()] + ); + assert_eq!( + prefix_index.get("downt").cloned().unwrap_or_default(), + vec!["downton".to_string()] + ); + assert!(!prefix_index.contains_key("do")); + } + + #[test] + fn address_term_matching_allows_prefixes_and_aliases() { + let tokens = tokenize_address_text("10 Downing Street"); + let prefix_group = address_term_group("down").expect("prefix term should be searchable"); + let alias_group = AddressTermGroup { + alternatives: vec!["st".to_string(), "street".to_string()], + }; + + assert!(address_tokens_match_group(&tokens, &prefix_group)); + assert!(address_tokens_match_group(&tokens, &alias_group)); + } + + #[test] + fn address_term_matching_uses_actual_token_prefixes() { + let tokens = tokenize_address_text("12 Settlers Court"); + let prefix_group = address_term_group("cou").expect("partial term should be searchable"); + + assert!(address_tokens_match_group(&tokens, &prefix_group)); + } + #[test] fn histogram_empty_data() { let data: Vec = vec![]; @@ -1212,6 +1926,16 @@ mod tests { assert!(stats.slider_max < 1000.0); } + #[test] + fn fixed_price_bounds_keep_slider_cap() { + let data = vec![400_000.0_f32, 2_500_000.0, 3_750_000.0]; + let bounds = make_fixed_bounds(0.0, 2_500_000.0); + let stats = compute_feature_stats(&data, &bounds, false); + + assert_eq!(stats.slider_min, 0.0); + assert_eq!(stats.slider_max, 2_500_000.0); + } + #[test] fn histogram_bin_for_value() { let hist = Histogram { diff --git a/server-rs/src/features.rs b/server-rs/src/features.rs index e8b9fe3..d4025b8 100644 --- a/server-rs/src/features.rs +++ b/server-rs/src/features.rs @@ -412,8 +412,8 @@ pub static FEATURE_GROUPS: &[FeatureGroup] = &[ name: "Deprivation", features: &[ Feature::Numeric(FeatureConfig { - name: "Income Score (rate)", - bounds: Bounds::Fixed { min: 0.0, max: 0.6 }, + name: "Income Score", + bounds: Bounds::Fixed { min: 0.0, max: 1.0 }, step: 0.01, description: "Income deprivation rate, inverted (higher = less deprived)", detail: "From the English Indices of Deprivation (inverted so higher = better). Higher values indicate less income deprivation. Based on Income Support, income-based Jobseeker's Allowance, income-based Employment and Support Allowance, Pension Credit, Working Tax Credit and Child Tax Credit, Universal Credit, and asylum seekers.", @@ -424,8 +424,8 @@ pub static FEATURE_GROUPS: &[FeatureGroup] = &[ absolute: false, }), Feature::Numeric(FeatureConfig { - name: "Employment Score (rate)", - bounds: Bounds::Fixed { min: 0.0, max: 0.4 }, + name: "Employment Score", + bounds: Bounds::Fixed { min: 0.0, max: 1.0 }, step: 0.01, description: "Employment deprivation rate, inverted (higher = less deprived)", detail: "From the English Indices of Deprivation (inverted so higher = better). Higher values indicate less employment deprivation. Based on claimants of Jobseeker's Allowance, Employment and Support Allowance, Incapacity Benefit, Severe Disablement Allowance, Carer's Allowance, and relevant Universal Credit claimants.", @@ -451,22 +451,7 @@ pub static FEATURE_GROUPS: &[FeatureGroup] = &[ absolute: false, }), Feature::Numeric(FeatureConfig { - name: "Living Environment Score", - bounds: Bounds::Percentile { - low: 2.0, - high: 98.0, - }, - step: 0.1, - description: "Quality of the local indoor and outdoor environment (higher = better)", - detail: "From the English Indices of Deprivation (inverted so higher = better). Combines housing quality (condition, central heating) and outdoor environment (air quality, road safety). Higher scores indicate better living environments.", - source: "iod", - prefix: "", - suffix: "", - raw: false, - absolute: false, - }), - Feature::Numeric(FeatureConfig { - name: "Indoors Sub-domain Score", + name: "Housing Conditions Score", bounds: Bounds::Percentile { low: 2.0, high: 98.0, @@ -481,7 +466,7 @@ pub static FEATURE_GROUPS: &[FeatureGroup] = &[ absolute: false, }), Feature::Numeric(FeatureConfig { - name: "Outdoors Sub-domain Score", + name: "Air Quality and Road Safety Score", bounds: Bounds::Percentile { low: 2.0, high: 98.0, diff --git a/server-rs/src/main.rs b/server-rs/src/main.rs index 51889dd..611616a 100644 --- a/server-rs/src/main.rs +++ b/server-rs/src/main.rs @@ -19,6 +19,7 @@ use std::sync::Arc; use std::time::Duration; use anyhow::{bail, Context}; +use axum::http::{header, HeaderValue}; use axum::middleware; use axum::routing::{any, get, patch, post}; use axum::Router; @@ -37,6 +38,67 @@ use tracing_subscriber::EnvFilter; use state::{AppState, SharedState}; +fn is_api_path(path: &str) -> bool { + path.starts_with("/api/") + || path.starts_with("/pb/") + || path.starts_with("/s/") + || matches!(path, "/health" | "/metrics") +} + +fn is_fingerprinted_asset(path: &str) -> bool { + let Some(filename) = path.rsplit('/').next() else { + return false; + }; + let Some((stem, extension)) = filename.rsplit_once('.') else { + return false; + }; + if !matches!(extension, "css" | "js") { + return false; + } + let Some((_, hash)) = stem.rsplit_once('.') else { + return false; + }; + hash.len() >= 8 && hash.bytes().all(|byte| byte.is_ascii_hexdigit()) +} + +fn is_static_asset_path(path: &str) -> bool { + path.rsplit('/') + .next() + .is_some_and(|segment| segment.contains('.')) +} + +async fn static_cache_headers( + request: axum::extract::Request, + next: middleware::Next, +) -> axum::response::Response { + let path = request.uri().path().to_string(); + let mut response = next.run(request).await; + + if is_api_path(&path) || response.headers().contains_key(header::CACHE_CONTROL) { + return response; + } + + let cache_control = response + .headers() + .get(header::CONTENT_TYPE) + .and_then(|value| value.to_str().ok()) + .filter(|content_type| content_type.contains("text/html")) + .map(|_| HeaderValue::from_static("no-cache, must-revalidate")) + .or_else(|| { + is_fingerprinted_asset(&path) + .then(|| HeaderValue::from_static("public, max-age=31536000, immutable")) + }) + .or_else(|| { + is_static_asset_path(&path).then(|| HeaderValue::from_static("public, max-age=3600")) + }); + + if let Some(value) = cache_control { + response.headers_mut().insert(header::CACHE_CONTROL, value); + } + + response +} + #[cfg(target_os = "linux")] fn resident_memory_kib() -> Option { let status = std::fs::read_to_string("/proc/self/status").ok()?; @@ -558,6 +620,7 @@ async fn main() -> anyhow::Result<()> { } }, )) + .layer(middleware::from_fn(static_cache_headers)) .layer(cors) .layer(CompressionLayer::new().zstd(true).gzip(true)) .layer(TraceLayer::new_for_http()); diff --git a/server-rs/src/og_middleware.rs b/server-rs/src/og_middleware.rs index f65229e..c987f1d 100644 --- a/server-rs/src/og_middleware.rs +++ b/server-rs/src/og_middleware.rs @@ -1,16 +1,26 @@ use std::sync::Arc; -use axum::body::Body; +use axum::body::{to_bytes, Body}; use axum::extract::Request; -use axum::http::header; +use axum::http::{header, StatusCode}; use axum::middleware::Next; use axum::response::Response; +use tracing::warn; use crate::state::AppState; const OG_PLACEHOLDER: &str = r#""#; +const HTML_BODY_LIMIT: usize = 5 * 1024 * 1024; + +struct SeoPage { + canonical_path: &'static str, + title: &'static str, + description: &'static str, + indexable: bool, +} + /// Escape a string for safe inclusion inside a double-quoted HTML attribute value. fn escape_attr(s: &str) -> String { let mut out = String::with_capacity(s.len()); @@ -26,6 +36,279 @@ fn escape_attr(s: &str) -> String { out } +fn trim_trailing_slash(path: &str) -> &str { + if path.len() > 1 { + path.trim_end_matches('/') + } else { + path + } +} + +fn seo_page_for_path(path: &str) -> Option { + let path = trim_trailing_slash(path); + match path { + "/" => Some(SeoPage { + canonical_path: "/", + title: "Perfect Postcode - Find where to buy before browsing listings", + description: "Search every postcode by budget, commute, schools, safety, noise, broadband, prices and more. Build a better home-buying shortlist before viewings.", + indexable: true, + }), + "/learn" | "/support" => Some(SeoPage { + canonical_path: "/learn", + title: "How Perfect Postcode works - Data sources, FAQ and support", + description: "Learn how Perfect Postcode combines property prices, EPC records, travel times, crime, schools, broadband, noise, amenities and open data for postcode research.", + indexable: true, + }), + "/pricing" => Some(SeoPage { + canonical_path: "/pricing", + title: "Perfect Postcode pricing - Lifetime property search map access", + description: "Get lifetime access to the postcode property search map for England, including filters, saved searches, exports, and future data updates.", + indexable: true, + }), + "/property-price-map" => Some(SeoPage { + canonical_path: "/property-price-map", + title: "Property price map for England - Compare postcodes before viewing", + description: "Compare sold prices, estimated current value, price per square metre and local context across English postcodes before searching listings.", + indexable: true, + }), + "/postcode-property-search" => Some(SeoPage { + canonical_path: "/postcode-property-search", + title: "Postcode property search - Find areas that match your criteria", + description: "Search every postcode by budget, property type, floor area, tenure, commute, schools, crime, broadband, noise, parks and local amenities.", + indexable: true, + }), + "/commute-property-search" => Some(SeoPage { + canonical_path: "/commute-property-search", + title: "Commute property search - Find places to live by travel time", + description: "Filter postcodes by commute time, then compare price, schools, safety, broadband, road noise, parks and property data on one map.", + indexable: true, + }), + "/school-property-search" => Some(SeoPage { + canonical_path: "/school-property-search", + title: "School property search - Compare postcodes for family moves", + description: "Compare nearby schools, property size, prices, parks, safety, commute and local amenities before building a viewing shortlist.", + indexable: true, + }), + "/postcode-checker" => Some(SeoPage { + canonical_path: "/postcode-checker", + title: "Postcode checker - Property, crime, broadband, noise and schools", + description: "Check postcode-level property prices, EPC data, crime, broadband, road noise, schools, council tax, amenities and travel-time context.", + indexable: true, + }), + "/property-search/birmingham" => Some(SeoPage { + canonical_path: "/property-search/birmingham", + title: "Birmingham property search - Compare postcodes by price and commute", + description: "Use postcode-level data to compare Birmingham property prices, commute trade-offs, schools, crime, broadband and local amenities before viewings.", + indexable: true, + }), + "/property-search/manchester" => Some(SeoPage { + canonical_path: "/property-search/manchester", + title: "Manchester property search - Compare postcodes before viewing", + description: "Compare Manchester-area postcodes by budget, commute, property type, schools, broadband, crime, noise and amenities before booking viewings.", + indexable: true, + }), + "/property-search/bristol" => Some(SeoPage { + canonical_path: "/property-search/bristol", + title: "Bristol property search - Compare postcodes by commute and price", + description: "Compare Bristol postcodes by price, commute, property size, schools, broadband, crime, road noise, parks and amenities before viewings.", + indexable: true, + }), + "/data-sources" => Some(SeoPage { + canonical_path: "/data-sources", + title: "Perfect Postcode data sources - Property, schools, commute and local context", + description: "Review the public and official datasets used by Perfect Postcode, including property prices, EPC, schools, crime, broadband, noise and travel-time context.", + indexable: true, + }), + "/methodology" => Some(SeoPage { + canonical_path: "/methodology", + title: "Perfect Postcode methodology - How to interpret postcode property data", + description: "Understand how to use postcode filters, property estimates, travel-time data, school context and local signals as a home-buying shortlist tool.", + indexable: true, + }), + "/privacy-security" => Some(SeoPage { + canonical_path: "/privacy-security", + title: "Perfect Postcode privacy and security - Saved searches and account data", + description: "Learn how Perfect Postcode treats saved searches, account data and property research workflows with privacy and security in mind.", + indexable: true, + }), + "/dashboard" => Some(SeoPage { + canonical_path: "/dashboard", + title: "Perfect Postcode dashboard", + description: "Explore postcode property data, travel times, prices, schools, crime, noise, broadband and amenities on the interactive map.", + indexable: false, + }), + "/saved" => Some(SeoPage { + canonical_path: "/saved", + title: "Perfect Postcode account", + description: "Manage your Perfect Postcode account, saved searches, saved properties and invitations.", + indexable: false, + }), + "/invites" => Some(SeoPage { + canonical_path: "/invites", + title: "Perfect Postcode account", + description: "Manage your Perfect Postcode account, saved searches, saved properties and invitations.", + indexable: false, + }), + "/account" => Some(SeoPage { + canonical_path: "/account", + title: "Perfect Postcode account", + description: "Manage your Perfect Postcode account, saved searches, saved properties and invitations.", + indexable: false, + }), + _ if path.starts_with("/invite/") => Some(SeoPage { + canonical_path: "/invite", + title: "You're invited to Perfect Postcode", + description: "Accept your invitation to explore property prices, energy ratings, crime stats, school ratings, and more across England.", + indexable: false, + }), + _ => None, + } +} + +fn is_passthrough_path(path: &str) -> bool { + path.starts_with("/api/") + || path.starts_with("/pb/") + || path.starts_with("/s/") + || path.starts_with("/assets/") + || matches!( + path, + "/health" + | "/metrics" + | "/robots.txt" + | "/sitemap.xml" + | "/favicon.svg" + | "/bundle.js" + | "/main.css" + | "/house.png" + ) + || path + .rsplit('/') + .next() + .is_some_and(|segment| segment.contains('.')) +} + +fn should_return_404(path: &str) -> bool { + !is_passthrough_path(path) && seo_page_for_path(path).is_none() +} + +fn not_found_response(public_url: &str, path: &str) -> Response { + let public_url_e = escape_attr(public_url); + let path_e = escape_attr(path); + let html = format!( + r#" + + + + + +Page not found - Perfect Postcode + + + + +
+

Page not found

+

The requested path was not found: {path_e}

+

Go to Perfect Postcode

+
+ +"# + ); + let mut response = Response::new(Body::from(html)); + *response.status_mut() = StatusCode::NOT_FOUND; + response.headers_mut().insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/html; charset=utf-8"), + ); + response +} + +fn route_seo_tags(page: &SeoPage, path: &str, query_string: &str, public_url: &str) -> String { + let path_e = escape_attr(path); + let query_e = escape_attr(query_string); + let public_url_e = escape_attr(public_url.trim_end_matches('/')); + let canonical_path_e = escape_attr(page.canonical_path); + let title_e = escape_attr(page.title); + let description_e = escape_attr(page.description); + + let is_invite = path.starts_with("/invite/"); + let og_image_url = if is_invite { + if query_string.is_empty() { + format!("{public_url_e}/api/screenshot?og=1&path={path_e}") + } else { + format!("{public_url_e}/api/screenshot?og=1&path={path_e}&{query_e}") + } + } else if query_string.is_empty() { + format!("{public_url_e}/api/screenshot?og=1") + } else { + format!("{public_url_e}/api/screenshot?og=1&{query_e}") + }; + + let canonical_url = format!("{public_url_e}{canonical_path_e}"); + let og_url = if query_string.is_empty() { + format!("{public_url_e}{path_e}") + } else { + format!("{public_url_e}{path_e}?{query_e}") + }; + let robots = if page.indexable { + "index,follow" + } else { + "noindex,follow" + }; + + format!( + r#" + + + + + + + + + + + + + + "# + ) +} + +fn inject_tags(mut html: String, page: &SeoPage, tags: &str) -> String { + if let Some(start) = html.find("") { + if let Some(end_offset) = html[start..].find("") { + let end = start + end_offset + "".len(); + html.replace_range( + start..end, + &format!("{}", escape_attr(page.title)), + ); + } + } + + if let Some(start) = html.find(r#"') { + let end = start + end_offset + 1; + html.replace_range( + start..end, + &format!( + r#""#, + escape_attr(page.description) + ), + ); + } + } + + if html.contains(OG_PLACEHOLDER) { + return html.replace(OG_PLACEHOLDER, tags); + } + + if let Some(index) = html.find("") { + html.insert_str(index, tags); + } + html +} + pub async fn og_middleware(request: Request, next: Next) -> Response { let path = request.uri().path().to_string(); // Capture the query string before passing the request through @@ -34,6 +317,12 @@ pub async fn og_middleware(request: Request, next: Next) -> Response { // Get state from extensions let state = request.extensions().get::>().cloned(); + if let Some(st) = &state { + if !st.is_dev && should_return_404(&path) { + return not_found_response(&st.public_url, &path); + } + } + let response = next.run(request).await; // Only inject OG tags into SPA HTML responses, not proxied PocketBase responses @@ -56,68 +345,25 @@ pub async fn og_middleware(request: Request, next: Next) -> Response { None => return response, }; - let index_html = match &state.index_html { - Some(html) => html, + let page = match seo_page_for_path(&path) { + Some(page) => page, None => return response, }; - // Build OG-injected HTML (og=1 triggers heading overlay on screenshot). - // All URL components are HTML-escaped before interpolation into attributes - // because path/query are attacker-controlled. - let is_invite = path.starts_with("/invite/"); - let path_e = escape_attr(&path); - let query_e = escape_attr(&query_string); - let public_url_e = escape_attr(&state.public_url); - - let og_image_url = if is_invite { - // Include path= so the screenshot service navigates to /invite/CODE - if query_string.is_empty() { - format!("{public_url_e}/api/screenshot?og=1&path={path_e}") - } else { - format!("{public_url_e}/api/screenshot?og=1&path={path_e}&{query_e}") + let (mut parts, body) = response.into_parts(); + let bytes = match to_bytes(body, HTML_BODY_LIMIT).await { + Ok(bytes) => bytes, + Err(err) => { + warn!("Failed to buffer HTML body for SEO tag injection: {err}"); + let mut response = Response::from_parts(parts, Body::empty()); + *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; + return response; } - } else if query_string.is_empty() { - format!("{public_url_e}/api/screenshot?og=1") - } else { - format!("{public_url_e}/api/screenshot?og=1&{query_e}") }; - let og_url = if query_string.is_empty() { - format!("{public_url_e}{path_e}") - } else { - format!("{public_url_e}{path_e}?{query_e}") - }; - - let og_logo = format!("{public_url_e}/favicon.svg"); - - let (og_title, og_description) = if is_invite { - ( - "You\u{2019}re invited to Perfect Postcode", - "Accept your invitation to explore property prices, energy ratings, crime stats, school ratings, and more across England.", - ) - } else { - ( - "Perfect Postcode \u{2014} Every neighbourhood in England", - "Explore property prices, energy ratings, crime stats, school ratings, and more across England on one interactive map.", - ) - }; - - let og_tags = format!( - r#" - - - - - - - - - - "# - ); - - let html = index_html.replace(OG_PLACEHOLDER, &og_tags); - - let (parts, _body) = response.into_parts(); + let html = String::from_utf8_lossy(&bytes).into_owned(); + let tags = route_seo_tags(&page, &path, &query_string, &state.public_url); + let html = inject_tags(html, &page, &tags); + parts.headers.remove(header::CONTENT_LENGTH); Response::from_parts(parts, Body::from(html)) } diff --git a/server-rs/src/routes/ai_filters.rs b/server-rs/src/routes/ai_filters.rs index 7dfed82..9461571 100644 --- a/server-rs/src/routes/ai_filters.rs +++ b/server-rs/src/routes/ai_filters.rs @@ -13,7 +13,7 @@ use crate::auth::OptionalUser; use crate::consts::{AI_FILTERS_MAX_TOKENS, AI_FILTERS_TEMPERATURE, AI_FILTERS_WEEKLY_TOKEN_LIMIT}; use crate::data::slugify; use crate::data::travel_time::TravelData; -use crate::parsing::{parse_filters, row_passes_filters}; +use crate::parsing::{parse_filters_with_poi, row_passes_filters, row_passes_poi_filters}; use crate::pocketbase::{get_superuser_token, log_ai_query}; use crate::routes::{FeatureInfo, FeaturesResponse}; use crate::state::{AppState, SharedState}; @@ -143,6 +143,9 @@ fn execute_destination_search(state: &AppState, query: &str, mode: &str) -> Valu .iter() .enumerate() .filter_map(|(idx, name_lower)| { + if !pd.travel_destination[idx] { + return None; + } let words_match = query_words.iter().all(|word| name_lower.contains(word)); let slug = slugify(&pd.name[idx]); let slug_match = slug.contains(&query_slug) || query_slug.contains(&slug); @@ -169,6 +172,9 @@ fn execute_destination_search(state: &AppState, query: &str, mode: &str) -> Valu .iter() .enumerate() .find_map(|(idx, name_lower)| { + if !pd.travel_destination[idx] { + return None; + } let words_match = query_words.iter().all(|word| name_lower.contains(word)); let slug = slugify(&pd.name[idx]); let slug_match = slug.contains(&query_slug) || query_slug.contains(&slug); @@ -186,6 +192,9 @@ fn execute_destination_search(state: &AppState, query: &str, mode: &str) -> Valu .iter() .enumerate() .filter_map(|(idx, city_opt)| { + if !pd.travel_destination[idx] { + return None; + } let city = city_opt.as_deref()?; if city.to_lowercase() != city_lower { return None; @@ -646,14 +655,17 @@ fn count_matching_rows( let filter_str = filters_to_filter_string(filters); let quant = state.data.quant_ref(); - let (parsed_filters, parsed_enum_filters) = if filter_str.is_empty() { - (Vec::new(), Vec::new()) + let poi_quant = state.data.poi_metrics.quant_ref(); + let (parsed_filters, parsed_enum_filters, parsed_poi_filters) = if filter_str.is_empty() { + (Vec::new(), Vec::new(), Vec::new()) } else { - match parse_filters( + match parse_filters_with_poi( Some(&filter_str), &state.feature_name_to_index, &state.data.enum_values, &quant, + &state.data.poi_metrics.name_to_index, + &poi_quant, ) { Ok(f) => f, Err(err) => { @@ -677,6 +689,7 @@ fn count_matching_rows( let num_features = state.data.num_features; let num_rows = state.data.lat.len(); let (pc_interner, pc_keys) = state.data.postcode_parts(); + let has_poi_filters = !parsed_poi_filters.is_empty(); let mut count = 0usize; for (row, pc_key) in pc_keys.iter().enumerate().take(num_rows) { @@ -689,6 +702,11 @@ fn count_matching_rows( ) { continue; } + if has_poi_filters + && !row_passes_poi_filters(row, &parsed_poi_filters, &state.data.poi_metrics) + { + continue; + } if has_travel { let postcode = pc_interner.resolve(pc_key); diff --git a/server-rs/src/routes/hexagon_stats.rs b/server-rs/src/routes/hexagon_stats.rs index e97637a..9bbb4cf 100644 --- a/server-rs/src/routes/hexagon_stats.rs +++ b/server-rs/src/routes/hexagon_stats.rs @@ -19,6 +19,7 @@ use crate::parsing::{ use crate::state::SharedState; use super::stats; +use super::travel_time::{load_travel_data, parse_optional_travel, row_passes_travel_filters}; #[derive(Serialize)] pub struct HistogramStats { @@ -76,6 +77,9 @@ pub struct HexagonStatsParams { /// shortest travel time for this mode+slug (so it has journey data). pub journey_mode: Option, pub journey_slug: Option, + /// Pipe-separated travel time entries: `mode:slug|mode:slug:min:max`. + /// Optional min:max applies as a filter (exclude properties outside range). + pub travel: Option, /// Share-link code; grants bbox-scoped access for unlicensed users. pub share: Option, } @@ -118,6 +122,9 @@ pub async fn get_hexagon_stats( let (fields_specified, field_set) = parse_field_set(params.fields.as_deref()); + let travel_entries = parse_optional_travel(params.travel.as_deref()) + .map_err(|err| (StatusCode::BAD_REQUEST, err).into_response())?; + // Load travel time data for central_postcode selection (if requested) let journey_travel_data = match (¶ms.journey_mode, ¶ms.journey_slug) { (Some(mode), Some(slug)) if state.travel_time_store.has_destination(mode, slug) => { @@ -134,6 +141,8 @@ pub async fn get_hexagon_stats( let need_parent = needs_parent(resolution); let num_features = state.data.num_features; let feature_data = &state.data.feature_data; + let travel_data = load_travel_data(&state.travel_time_store, &travel_entries)?; + let has_travel = !travel_entries.is_empty(); let (min_lat, min_lon, max_lat, max_lon) = h3_cell_bounds(cell, 0.001); @@ -153,6 +162,12 @@ pub async fn get_hexagon_stats( num_features, ) { + if has_travel { + let postcode = state.data.postcode(row); + if !row_passes_travel_filters(postcode, &travel_entries, &travel_data) { + return; + } + } matching_rows.push(row); } }); @@ -235,6 +250,7 @@ pub async fn get_hexagon_stats( total_count, filters = num_filters, filters_raw = filters_str.as_deref().unwrap_or("-"), + travel_entries = travel_entries.len(), ms = format_args!("{:.1}", elapsed.as_secs_f64() * 1000.0), "GET /api/hexagon-stats" ); diff --git a/server-rs/src/routes/places.rs b/server-rs/src/routes/places.rs index 2f2410b..76bb64b 100644 --- a/server-rs/src/routes/places.rs +++ b/server-rs/src/routes/places.rs @@ -6,7 +6,7 @@ use axum::response::Json; use serde::{Deserialize, Serialize}; use tracing::info; -use crate::data::slugify; +use crate::data::{normalize_search_text, slugify}; use crate::state::SharedState; #[derive(Serialize)] @@ -20,9 +20,21 @@ pub struct PlaceResult { city: Option, } +#[derive(Serialize)] +pub struct AddressResult { + address: String, + postcode: String, + lat: f32, + lon: f32, +} + #[derive(Serialize)] pub struct PlacesResponse { places: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + postcodes: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + addresses: Vec, } #[derive(Deserialize)] @@ -34,6 +46,53 @@ pub struct PlacesParams { mode: Option, } +fn compact_postcode_query(query: &str) -> String { + query + .chars() + .filter(|ch| !ch.is_whitespace()) + .map(|ch| ch.to_ascii_uppercase()) + .collect() +} + +fn looks_like_postcode_prefix(query: &str) -> bool { + let compact = compact_postcode_query(query); + if compact.len() < 2 || compact.len() > 7 { + return false; + } + compact + .chars() + .next() + .is_some_and(|ch| ch.is_ascii_alphabetic()) + && compact.chars().all(|ch| ch.is_ascii_alphanumeric()) + && compact.chars().any(|ch| ch.is_ascii_digit()) +} + +fn postcode_starts_with_compact(postcode: &str, compact_query: &str) -> bool { + let mut query_chars = compact_query.chars(); + let mut current = query_chars.next(); + if current.is_none() { + return false; + } + + for postcode_char in postcode.chars() { + if postcode_char.is_whitespace() { + continue; + } + + match current { + Some(query_char) if postcode_char.to_ascii_uppercase() == query_char => { + current = query_chars.next(); + if current.is_none() { + return true; + } + } + _ => return false, + } + } + + current.is_none() +} + pub async fn get_places( State(shared): State>, Query(params): Query, @@ -51,31 +110,39 @@ pub async fn get_places( let places = tokio::task::spawn_blocking(move || { let t0 = std::time::Instant::now(); let query_lower = query.to_lowercase(); + let query_search = normalize_search_text(&query); let pd = &state.place_data; let od = &state.outcode_data; + let postcode_data = &state.postcode_data; let tt_store = &state.travel_time_store; + let property_data = &state.data; // Linear scan — ~50-100k rows, <1ms // Tuple: (row_idx, is_exact, is_prefix, type_rank, population, name_len, slug) let mut matches: Vec<(usize, bool, bool, u8, u32, usize, String)> = pd - .name_lower + .name_search .iter() .enumerate() - .filter_map(|(idx, name)| { - if !name.contains(&query_lower) { + .filter_map(|(idx, search_text)| { + if query_search.is_empty() || !search_text.contains(&query_search) { return None; } let slug = slugify(&pd.name[idx]); - // If mode filter is set, only include places with travel data + // If mode filter is set, keep the historical travel destination set only. if let Some(ref mode) = mode_filter { - if !tt_store.has_destination(mode, &slug) { + if !pd.travel_destination[idx] || !tt_store.has_destination(mode, &slug) { return None; } } - let is_exact = name.len() == query_lower.len(); - let is_prefix = name.starts_with(&query_lower); + let is_exact = search_text + .split(" | ") + .any(|alias| alias == query_search || pd.name_lower[idx] == query_lower); + let is_prefix = search_text + .split(" | ") + .any(|alias| alias.starts_with(&query_search)) + || pd.name_lower[idx].starts_with(&query_lower); Some(( idx, is_exact, @@ -153,20 +220,76 @@ pub async fn get_places( results = outcode_results; } + let postcodes: Vec = if mode_filter.is_none() && looks_like_postcode_prefix(&query) + { + let compact_query = compact_postcode_query(&query); + postcode_data + .postcodes + .iter() + .filter(|postcode| postcode_starts_with_compact(postcode, &compact_query)) + .take(limit) + .cloned() + .collect() + } else { + Vec::new() + }; + + let addresses: Vec = if mode_filter.is_none() { + property_data + .search_addresses(&query, limit) + .into_iter() + .map(|row| AddressResult { + address: property_data.address(row).trim().to_string(), + postcode: property_data.postcode(row).to_string(), + lat: property_data.lat[row], + lon: property_data.lon[row], + }) + .collect() + } else { + Vec::new() + }; + let elapsed = t0.elapsed(); info!( query = query.as_str(), results = results.len(), + postcodes = postcodes.len(), + addresses = addresses.len(), scanned = pd.name_lower.len(), mode = mode_filter.as_deref().unwrap_or("-"), ms = format_args!("{:.1}", elapsed.as_secs_f64() * 1000.0), "GET /api/places" ); - results + (results, postcodes, addresses) }) .await .map_err(|error| (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()))?; - Ok(Json(PlacesResponse { places })) + Ok(Json(PlacesResponse { + places: places.0, + postcodes: places.1, + addresses: places.2, + })) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn detects_postcode_prefixes() { + assert!(looks_like_postcode_prefix("EC2R")); + assert!(looks_like_postcode_prefix("sw1a 1")); + assert!(looks_like_postcode_prefix("M4")); + assert!(!looks_like_postcode_prefix("London")); + assert!(!looks_like_postcode_prefix("E")); + } + + #[test] + fn postcode_prefix_match_ignores_spaces() { + assert!(postcode_starts_with_compact("EC2R 8AH", "EC2R8")); + assert!(postcode_starts_with_compact("SW1A 1AA", "SW1A1")); + assert!(!postcode_starts_with_compact("SW1A 1AA", "SW1A2")); + } } diff --git a/server-rs/src/routes/pois.rs b/server-rs/src/routes/pois.rs index 92cff51..e6f4cde 100644 --- a/server-rs/src/routes/pois.rs +++ b/server-rs/src/routes/pois.rs @@ -3,24 +3,21 @@ use std::sync::Arc; use axum::extract::{Query, State}; use axum::http::StatusCode; use axum::response::Json; -use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; use tracing::info; use crate::consts::MAX_POIS_PER_REQUEST; -use crate::data::{POICategoryGroup, POIData}; +use crate::data::POICategoryGroup; use crate::parsing::require_bounds; use crate::state::SharedState; -const TUBE_STATION_CATEGORY: &str = "Tube station"; -const TUBE_STATION_MERGE_RADIUS_DEGREES: f32 = 0.01; - #[derive(Serialize)] #[allow(clippy::upper_case_acronyms)] pub struct POI { id: String, name: String, category: String, + icon_category: String, group: String, lat: f32, lng: f32, @@ -39,167 +36,6 @@ pub struct POIParams { categories: Option, } -struct SelectedPOIRow { - row: usize, - id_override: Option, - name_override: Option, - lat: f32, - lng: f32, - lat_sum: f32, - lng_sum: f32, - count: u32, - priority: u32, -} - -impl SelectedPOIRow { - fn new(data: &POIData, row: usize, override_identity: bool) -> Self { - Self { - row, - id_override: override_identity.then(|| data.id(row).to_string()), - name_override: override_identity.then(|| data.name[row].clone()), - lat: data.lat[row], - lng: data.lng[row], - lat_sum: data.lat[row], - lng_sum: data.lng[row], - count: 1, - priority: data.priority[row], - } - } - - fn merge_tube_station(&mut self, data: &POIData, row: usize) { - self.lat_sum += data.lat[row]; - self.lng_sum += data.lng[row]; - self.count += 1; - self.lat = self.lat_sum / self.count as f32; - self.lng = self.lng_sum / self.count as f32; - self.priority = self.priority.min(data.priority[row]); - - let current_name = self - .name_override - .as_deref() - .unwrap_or(&data.name[self.row]); - let candidate_name = &data.name[row]; - if tube_station_name_score(candidate_name) < tube_station_name_score(current_name) { - self.id_override = Some(data.id(row).to_string()); - self.name_override = Some(candidate_name.clone()); - } - } - - fn id(&self, data: &POIData) -> String { - self.id_override - .clone() - .unwrap_or_else(|| data.id(self.row).to_string()) - } - - fn name(&self, data: &POIData) -> String { - self.name_override - .clone() - .unwrap_or_else(|| data.name[self.row].clone()) - } -} - -fn dedupe_tube_stations(data: &POIData, rows: Vec) -> Vec { - let mut selected = Vec::with_capacity(rows.len()); - let mut tube_groups: FxHashMap> = FxHashMap::default(); - - for row in rows { - if data.category.get(row) != TUBE_STATION_CATEGORY { - selected.push(SelectedPOIRow::new(data, row, false)); - continue; - } - - let station_key = canonical_tube_station_name(&data.name[row]); - if station_key.is_empty() { - selected.push(SelectedPOIRow::new(data, row, false)); - continue; - } - - let existing = tube_groups.get(&station_key).and_then(|indices| { - indices.iter().copied().find(|&index| { - same_tube_station_area(&selected[index], data.lat[row], data.lng[row]) - }) - }); - - if let Some(index) = existing { - selected[index].merge_tube_station(data, row); - } else { - let index = selected.len(); - selected.push(SelectedPOIRow::new(data, row, true)); - tube_groups.entry(station_key).or_default().push(index); - } - } - - selected -} - -fn canonical_tube_station_name(name: &str) -> String { - let mut normalized = String::with_capacity(name.len()); - let mut paren_depth = 0u32; - - for ch in name.chars() { - match ch { - '(' => { - paren_depth += 1; - normalized.push(' '); - } - ')' => { - paren_depth = paren_depth.saturating_sub(1); - normalized.push(' '); - } - _ if paren_depth > 0 => {} - '\'' | '’' | '`' => {} - '&' => normalized.push_str(" and "), - _ if ch.is_ascii_alphanumeric() => normalized.push(ch.to_ascii_lowercase()), - _ => normalized.push(' '), - } - } - - let mut words: Vec<&str> = normalized.split_whitespace().collect(); - const SUFFIXES: &[&[&str]] = &[ - &["underground", "station"], - &["tube", "station"], - &["dlr", "station"], - &["metro", "station"], - &["tram", "stop"], - &["rail", "station"], - &["railway", "station"], - &["station"], - &["stop"], - ]; - - loop { - let Some(suffix) = SUFFIXES.iter().find(|suffix| words.ends_with(suffix)) else { - break; - }; - words.truncate(words.len() - suffix.len()); - } - - words.join(" ") -} - -fn same_tube_station_area(station: &SelectedPOIRow, lat: f32, lng: f32) -> bool { - let dlat = station.lat - lat; - let dlng = (station.lng - lng) * station.lat.to_radians().cos(); - (dlat * dlat + dlng * dlng) <= TUBE_STATION_MERGE_RADIUS_DEGREES.powi(2) -} - -fn tube_station_name_score(name: &str) -> (u8, usize) { - let lower = name.to_ascii_lowercase(); - let suffix_penalty = if lower.ends_with(" underground station") - || lower.ends_with(" tube station") - || lower.ends_with(" dlr station") - || lower.ends_with(" metro station") - || lower.ends_with(" tram stop") - || lower.ends_with(" station") - || lower.ends_with(" stop") - { - 1 - } else { - 0 - }; - (suffix_penalty, name.len()) -} - pub async fn get_pois( State(shared): State>, Query(params): Query, @@ -246,32 +82,30 @@ pub async fn get_pois( }) .collect(); - let mut matching_pois = dedupe_tube_stations(&state.poi_data, matching_rows); + let mut matching_pois = matching_rows; if matching_pois.len() > MAX_POIS_PER_REQUEST { let ratio = (matching_pois.len() / MAX_POIS_PER_REQUEST) as u32; let step = ratio.next_power_of_two(); let mask = step - 1; - matching_pois.retain(|poi| poi.priority & mask == 0); + matching_pois.retain(|&row| state.poi_data.priority[row] & mask == 0); if matching_pois.len() > MAX_POIS_PER_REQUEST { - matching_pois.sort_unstable_by_key(|poi| poi.priority); + matching_pois.sort_unstable_by_key(|&row| state.poi_data.priority[row]); matching_pois.truncate(MAX_POIS_PER_REQUEST); } } let pois: Vec = matching_pois .iter() - .map(|poi| { - let row = poi.row; - POI { - id: poi.id(&state.poi_data), - name: poi.name(&state.poi_data), - category: state.poi_data.category.get(row).to_string(), - group: state.poi_data.group.get(row).to_string(), - lat: poi.lat, - lng: poi.lng, - emoji: state.poi_data.emoji.get(row).to_string(), - } + .map(|&row| POI { + id: state.poi_data.id(row).to_string(), + name: state.poi_data.name[row].clone(), + category: state.poi_data.category.get(row).to_string(), + icon_category: state.poi_data.icon_category.get(row).to_string(), + group: state.poi_data.group.get(row).to_string(), + lat: state.poi_data.lat[row], + lng: state.poi_data.lng[row], + emoji: state.poi_data.emoji.get(row).to_string(), }) .collect(); @@ -313,53 +147,3 @@ pub async fn get_poi_categories( Json(POICategoriesResponse { groups }) } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn canonical_tube_station_name_strips_transport_suffixes() { - assert_eq!(canonical_tube_station_name("Bank"), "bank"); - assert_eq!( - canonical_tube_station_name("Bank Underground Station"), - "bank" - ); - assert_eq!(canonical_tube_station_name("Bank DLR Station"), "bank"); - assert_eq!( - canonical_tube_station_name("Pleasure Beach (Blackpool Tramway)"), - "pleasure beach" - ); - assert_eq!( - canonical_tube_station_name("Earl's Court Tube Station"), - "earls court" - ); - } - - #[test] - fn same_tube_station_area_keeps_distant_names_separate() { - let station = SelectedPOIRow { - row: 0, - id_override: None, - name_override: None, - lat: 51.5130, - lng: -0.0889, - lat_sum: 51.5130, - lng_sum: -0.0889, - count: 1, - priority: 0, - }; - - assert!(same_tube_station_area(&station, 51.5132, -0.0885)); - assert!(!same_tube_station_area(&station, 55.0140, -1.6781)); - } - - #[test] - fn tube_station_name_score_prefers_plain_station_names() { - assert!(tube_station_name_score("Bank") < tube_station_name_score("Bank DLR Station")); - assert!( - tube_station_name_score("Acton Town") - < tube_station_name_score("Acton Town Underground Station") - ); - } -} diff --git a/server-rs/src/routes/postcode_properties.rs b/server-rs/src/routes/postcode_properties.rs index 96e0803..c954c26 100644 --- a/server-rs/src/routes/postcode_properties.rs +++ b/server-rs/src/routes/postcode_properties.rs @@ -15,13 +15,19 @@ use crate::state::SharedState; use crate::utils::normalize_postcode; use super::properties::{HexagonPropertiesResponse, Property}; +use super::travel_time::{load_travel_data, parse_optional_travel, row_passes_travel_filters}; #[derive(Deserialize)] pub struct PostcodePropertiesParams { pub postcode: String, pub filters: Option, + /// Pipe-separated travel time entries: `mode:slug|mode:slug:min:max`. + /// Optional min:max applies as a filter (exclude properties outside range). + pub travel: Option, pub limit: Option, pub offset: Option, + /// Exact address to rank first when opening properties from address search. + pub focus_address: Option, /// Share-link code; grants bbox-scoped access for unlicensed users. pub share: Option, } @@ -65,8 +71,16 @@ pub async fn get_postcode_properties( .map_err(|err| (StatusCode::BAD_REQUEST, err).into_response())?; let num_filters = parsed_filters.len() + parsed_enum_filters.len(); let filters_str = params.filters; + let travel_entries = parse_optional_travel(params.travel.as_deref()) + .map_err(|err| (StatusCode::BAD_REQUEST, err).into_response())?; let postcode_str = normalized; + let focus_address = params + .focus_address + .as_deref() + .map(str::trim) + .filter(|address| !address.is_empty()) + .map(str::to_ascii_lowercase); let result = tokio::task::spawn_blocking(move || { let t0 = std::time::Instant::now(); @@ -75,6 +89,8 @@ pub async fn get_postcode_properties( let feature_names = &state.data.feature_names; let feature_name_to_index = &state.feature_name_to_index; let enum_values = &state.data.enum_values; + let travel_data = load_travel_data(&state.travel_time_store, &travel_entries)?; + let has_travel = !travel_entries.is_empty(); let offset_deg: f64 = POSTCODE_SEARCH_OFFSET; let min_lat = centroid_lat as f64 - offset_deg; @@ -96,11 +112,33 @@ pub async fn get_postcode_properties( num_features, ) { + if has_travel + && !row_passes_travel_filters( + state.data.postcode(row), + &travel_entries, + &travel_data, + ) + { + return; + } matching_rows.push(row); } }); - matching_rows.sort_unstable_by_key(|&row| state.data.address(row).trim().is_empty()); + matching_rows.sort_unstable_by(|&left, &right| { + let left_address = state.data.address(left).trim(); + let right_address = state.data.address(right).trim(); + let left_focused = focus_address + .as_ref() + .is_some_and(|address| left_address.eq_ignore_ascii_case(address)); + let right_focused = focus_address + .as_ref() + .is_some_and(|address| right_address.eq_ignore_ascii_case(address)); + + right_focused + .cmp(&left_focused) + .then(left_address.is_empty().cmp(&right_address.is_empty())) + }); let total = matching_rows.len(); let limit = params @@ -133,6 +171,7 @@ pub async fn get_postcode_properties( offset = page_offset, filters = num_filters, filters_raw = filters_str.as_deref().unwrap_or("-"), + travel_entries = travel_entries.len(), ms = format_args!("{:.1}", elapsed.as_secs_f64() * 1000.0), "GET /api/postcode-properties" ); diff --git a/server-rs/src/routes/postcode_stats.rs b/server-rs/src/routes/postcode_stats.rs index 0ca7480..c2f9ac4 100644 --- a/server-rs/src/routes/postcode_stats.rs +++ b/server-rs/src/routes/postcode_stats.rs @@ -16,6 +16,7 @@ use crate::utils::normalize_postcode; use super::hexagon_stats::HexagonStatsResponse; use super::stats; +use super::travel_time::{load_travel_data, parse_optional_travel, row_passes_travel_filters}; #[derive(Deserialize)] pub struct PostcodeStatsParams { @@ -24,6 +25,9 @@ pub struct PostcodeStatsParams { /// Comma-separated feature names to include in stats response. /// Only listed features are computed; if absent or empty, no features are returned. pub fields: Option, + /// Pipe-separated travel time entries: `mode:slug|mode:slug:min:max`. + /// Optional min:max applies as a filter (exclude properties outside range). + pub travel: Option, /// Share-link code; grants bbox-scoped access for unlicensed users. pub share: Option, } @@ -71,6 +75,8 @@ pub async fn get_postcode_stats( let filters_str = params.filters; let (fields_specified, field_set) = parse_field_set(params.fields.as_deref()); + let travel_entries = parse_optional_travel(params.travel.as_deref()) + .map_err(|err| (StatusCode::BAD_REQUEST, err).into_response())?; let postcode_str = normalized; @@ -78,6 +84,8 @@ pub async fn get_postcode_stats( let start_time = std::time::Instant::now(); let num_features = state.data.num_features; let feature_data = &state.data.feature_data; + let travel_data = load_travel_data(&state.travel_time_store, &travel_entries)?; + let has_travel = !travel_entries.is_empty(); // Search around centroid (generous for a postcode) let offset: f64 = POSTCODE_SEARCH_OFFSET; @@ -101,6 +109,11 @@ pub async fn get_postcode_stats( num_features, ) { + if has_travel + && !row_passes_travel_filters(row_postcode, &travel_entries, &travel_data) + { + return; + } matching_rows.push(row); } }); @@ -126,6 +139,7 @@ pub async fn get_postcode_stats( total_count, filters = num_filters, filters_raw = filters_str.as_deref().unwrap_or("-"), + travel_entries = travel_entries.len(), ms = format_args!("{:.1}", elapsed.as_secs_f64() * 1000.0), "GET /api/postcode-stats" ); diff --git a/server-rs/src/routes/properties.rs b/server-rs/src/routes/properties.rs index 514429a..f935c5a 100644 --- a/server-rs/src/routes/properties.rs +++ b/server-rs/src/routes/properties.rs @@ -19,11 +19,16 @@ use crate::parsing::{ }; use crate::state::{AppState, SharedState}; +use super::travel_time::{load_travel_data, parse_optional_travel, row_passes_travel_filters}; + #[derive(Deserialize)] pub struct HexagonPropertiesParams { pub h3: String, pub resolution: u8, pub filters: Option, + /// Pipe-separated travel time entries: `mode:slug|mode:slug:min:max`. + /// Optional min:max applies as a filter (exclude properties outside range). + pub travel: Option, pub limit: Option, pub offset: Option, /// Share-link code; grants bbox-scoped access for unlicensed users. @@ -203,6 +208,8 @@ pub async fn get_hexagon_properties( .map_err(|err| (StatusCode::BAD_REQUEST, err).into_response())?; let num_filters = parsed_filters.len() + parsed_enum_filters.len(); let filters_str = params.filters; + let travel_entries = parse_optional_travel(params.travel.as_deref()) + .map_err(|err| (StatusCode::BAD_REQUEST, err).into_response())?; let result = tokio::task::spawn_blocking(move || { let t0 = std::time::Instant::now(); @@ -215,6 +222,8 @@ pub async fn get_hexagon_properties( let feature_names = &state.data.feature_names; let feature_name_to_index = &state.feature_name_to_index; let enum_values = &state.data.enum_values; + let travel_data = load_travel_data(&state.travel_time_store, &travel_entries)?; + let has_travel = !travel_entries.is_empty(); let (min_lat, min_lon, max_lat, max_lon) = h3_cell_bounds(cell, 0.001); @@ -234,6 +243,12 @@ pub async fn get_hexagon_properties( num_features, ) { + if has_travel { + let postcode = state.data.postcode(row); + if !row_passes_travel_filters(postcode, &travel_entries, &travel_data) { + return; + } + } matching_rows.push(row); } }); @@ -273,6 +288,7 @@ pub async fn get_hexagon_properties( offset, filters = num_filters, filters_raw = filters_str.as_deref().unwrap_or("-"), + travel_entries = travel_entries.len(), ms = format_args!("{:.1}", elapsed.as_secs_f64() * 1000.0), "GET /api/hexagon-properties" ); diff --git a/server-rs/src/routes/shorten.rs b/server-rs/src/routes/shorten.rs index f468604..aacdc87 100644 --- a/server-rs/src/routes/shorten.rs +++ b/server-rs/src/routes/shorten.rs @@ -4,7 +4,7 @@ use axum::extract::{Path, State}; use axum::http::{header, StatusCode}; use axum::response::{Html, IntoResponse, Response}; use axum::Json; -use rand::Rng; +use rand::RngExt; use serde::{Deserialize, Serialize}; use tracing::warn; diff --git a/server-rs/src/routes/stats.rs b/server-rs/src/routes/stats.rs index 6729062..46def2d 100644 --- a/server-rs/src/routes/stats.rs +++ b/server-rs/src/routes/stats.rs @@ -17,14 +17,13 @@ pub fn extract_price_history( let year_idx = feature_name_to_index .get("Date of last transaction") .copied(); - let price_idx = feature_name_to_index.get("Last known price").copied(); - match (year_idx, price_idx) { - (Some(yi), Some(pi)) => { + match year_idx { + Some(yi) => { let mut points: Vec = matching_rows .iter() .filter_map(|&row| { let year = data.get_feature(row, yi); - let price = data.get_feature(row, pi); + let price = data.last_known_price_raw(row); if year.is_finite() && price.is_finite() { Some(PricePoint { year, price }) } else { @@ -46,7 +45,7 @@ pub fn extract_price_history( } points } - _ => Vec::new(), + None => Vec::new(), } } diff --git a/server-rs/src/routes/tiles.rs b/server-rs/src/routes/tiles.rs index 837d76f..b9efefd 100644 --- a/server-rs/src/routes/tiles.rs +++ b/server-rs/src/routes/tiles.rs @@ -3,8 +3,7 @@ use std::sync::Arc; use axum::extract::{Path, Query, State}; use axum::http::{header, StatusCode}; use axum::response::{IntoResponse, Response}; -use pmtiles::async_reader::AsyncPmTilesReader; -use pmtiles::MmapBackend; +use pmtiles::{AsyncPmTilesReader, MmapBackend, TileCoord}; use serde::Deserialize; use tracing::warn; @@ -14,7 +13,15 @@ pub async fn get_tile( State(reader): State>, Path((zoom, col, row)): Path<(u8, u32, u32)>, ) -> Response { - match reader.get_tile(zoom, col as u64, row as u64).await { + let tile_coord = match TileCoord::new(zoom, col, row) { + Ok(tile_coord) => tile_coord, + Err(err) => { + warn!(zoom, col, row, error = %err, "Invalid tile coordinate"); + return StatusCode::BAD_REQUEST.into_response(); + } + }; + + match reader.get_tile(tile_coord).await { Ok(Some(tile_bytes)) => ( StatusCode::OK, [ diff --git a/server-rs/src/routes/travel_destinations.rs b/server-rs/src/routes/travel_destinations.rs index a80dba2..df2fd4d 100644 --- a/server-rs/src/routes/travel_destinations.rs +++ b/server-rs/src/routes/travel_destinations.rs @@ -54,6 +54,9 @@ pub async fn get_travel_destinations( .iter() .enumerate() .filter_map(|(idx, name)| { + if !pd.travel_destination[idx] { + return None; + } let slug = slugify(name); if slug_set.contains(&slug) { Some((idx, slug, pd.type_rank[idx], pd.population[idx], name.len())) diff --git a/server-rs/src/routes/travel_time.rs b/server-rs/src/routes/travel_time.rs index 886ba51..c557af8 100644 --- a/server-rs/src/routes/travel_time.rs +++ b/server-rs/src/routes/travel_time.rs @@ -1,3 +1,5 @@ +use crate::data::travel_time::{TravelData, TravelTimeStore}; + /// Parse the optional `travel` query param, returning an empty Vec when absent or empty. pub fn parse_optional_travel(travel: Option<&str>) -> Result, String> { match travel.filter(|val| !val.is_empty()) { @@ -15,6 +17,46 @@ pub struct TravelEntry { pub filter_max: Option, } +pub fn load_travel_data( + store: &TravelTimeStore, + entries: &[TravelEntry], +) -> Result, String> { + entries + .iter() + .map(|entry| { + store + .get(&entry.mode, &entry.slug) + .map_err(|err| format!("Failed to load travel data: {}", err)) + }) + .collect() +} + +#[inline] +pub fn row_passes_travel_filters( + postcode: &str, + entries: &[TravelEntry], + travel_data: &[TravelData], +) -> bool { + for (index, entry) in entries.iter().enumerate() { + let (Some(fmin), Some(fmax)) = (entry.filter_min, entry.filter_max) else { + continue; + }; + let Some(row_data) = travel_data.get(index).and_then(|data| data.get(postcode)) else { + return false; + }; + let minutes = if entry.use_best { + row_data.best_minutes.unwrap_or(row_data.minutes) + } else { + row_data.minutes + }; + if (minutes as f32) < fmin || (minutes as f32) > fmax { + return false; + } + } + + true +} + /// Parse `travel` param into a list of travel entries. /// Format: `mode:slug` or `mode:slug:best` or `mode:slug:min:max` or `mode:slug:best:min:max` fn parse_travel_entries(travel_str: &str) -> Result, String> { diff --git a/uv.lock b/uv.lock index a30c1ae..e6ee8b4 100644 --- a/uv.lock +++ b/uv.lock @@ -1376,6 +1376,7 @@ dependencies = [ { name = "numpy", marker = "python_full_version < '3.14' and sys_platform == 'linux'" }, { name = "osmium", marker = "python_full_version < '3.14' and sys_platform == 'linux'" }, { name = "pandas", marker = "python_full_version < '3.14' and sys_platform == 'linux'" }, + { name = "pillow", marker = "python_full_version < '3.14' and sys_platform == 'linux'" }, { name = "plotly", marker = "python_full_version < '3.14' and sys_platform == 'linux'" }, { name = "polars", marker = "python_full_version < '3.14' and sys_platform == 'linux'" }, { name = "pyarrow", marker = "python_full_version < '3.14' and sys_platform == 'linux'" }, @@ -1407,6 +1408,7 @@ requires-dist = [ { name = "numpy", specifier = ">=1.26.0" }, { name = "osmium", specifier = ">=4.0.0" }, { name = "pandas", specifier = ">=2.0.0" }, + { name = "pillow", specifier = ">=12.0.0" }, { name = "plotly", specifier = ">=6.5.2" }, { name = "polars" }, { name = "polars", specifier = ">=1.37.1" }, diff --git a/video/package-lock.json b/video/package-lock.json index ef567aa..ed8a2d8 100644 --- a/video/package-lock.json +++ b/video/package-lock.json @@ -8,21 +8,21 @@ "name": "video", "version": "1.0.0", "dependencies": { - "playwright": "^1.49.0" + "playwright": "^1.59.1" }, "devDependencies": { - "@types/node": "^22.0.0", - "typescript": "^5.7.0" + "@types/node": "^25.6.1", + "typescript": "^6.0.3" } }, "node_modules/@types/node": { - "version": "22.19.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", - "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", + "version": "25.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.1.tgz", + "integrity": "sha512-coJCN8O1q4AGyyqCAUSP06P+SrMTu18BkEj3NVAK07q6QUneD2wzj3CLv9+yP+BMeZQlMvneXqqvDe3w+xcq7g==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.19.0" } }, "node_modules/fsevents": { @@ -70,9 +70,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -84,9 +84,9 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", "dev": true, "license": "MIT" } diff --git a/video/package.json b/video/package.json index fd50735..5992dda 100644 --- a/video/package.json +++ b/video/package.json @@ -6,17 +6,19 @@ "type": "module", "scripts": { "build": "tsc", + "bootstrap-admin": "tsc && node dist/pb-admin.js", "setup-auth": "tsc && node dist/auth.js", "record": "tsc && node dist/record.js", "record:vertical": "tsc && ASPECT=9x16 node dist/record.js", - "encode": "ffmpeg -y -i output/recording.webm -c:v libx264 -pix_fmt yuv420p -crf 16 -preset slow -movflags +faststart output/recording.mp4", + "encode": "ffmpeg -y -i output/recording.webm -c:v libx264 -pix_fmt yuv420p -crf 14 -preset fast -movflags +faststart output/recording.mp4", + "verify-output": "tsc && node dist/verify.js", "render": "./render.sh" }, "dependencies": { - "playwright": "^1.49.0" + "playwright": "^1.59.1" }, "devDependencies": { - "@types/node": "^22.0.0", - "typescript": "^5.7.0" + "@types/node": "^25.6.1", + "typescript": "^6.0.3" } } diff --git a/video/render.sh b/video/render.sh index 6585c12..dc800a3 100755 --- a/video/render.sh +++ b/video/render.sh @@ -10,28 +10,54 @@ # ./render.sh # full pipeline (uses cached auth.json if fresh) # ./render.sh --fresh-auth # force re-auth even if auth.json exists # ./render.sh --no-encode # stop at WebM, skip MP4 encode +# ./render.sh --no-audio # skip Qwen3-TTS narration; publish silent MP4 # FORCE_AUTH=1 ./render.sh # same as --fresh-auth # APP_URL=http://localhost:3001 ./render.sh # override frontend URL +# TTS_SPEAKER=aiden ./render.sh # override CustomVoice speaker set -euo pipefail # -- config (override via env) ------------------------------------------------- -APP_URL="${APP_URL:-http://host.docker.internal:3001}" -PB_URL="${PB_URL:-http://host.docker.internal:8090}" -API_URL="${API_URL:-http://host.docker.internal:8001}" +export APP_URL="${APP_URL:-http://host.docker.internal:3001}" +export PB_URL="${PB_URL:-http://host.docker.internal:8090}" +export API_URL="${API_URL:-http://host.docker.internal:8001}" +PB_ADMIN_EMAIL="${PB_ADMIN_EMAIL:-admin@propertymap.local}" +PB_ADMIN_PASSWORD="${PB_ADMIN_PASSWORD:-propertymap-dev-2024}" PB_EMAIL="${PB_EMAIL:-demo-video@local.test}" PB_PASSWORD="${PB_PASSWORD:-DemoVideoPass123!}" -MAX_DURATION_S="${MAX_DURATION_S:-15}" AUTH_TTL_HOURS="${AUTH_TTL_HOURS:-24}" # re-auth if auth.json older than this +# Where the homepage