diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..595b1c6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +.git +.reference +.local +node_modules +dist +web/dist diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..92ce723 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + validate: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v7 + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Typecheck + run: bun run typecheck + + - name: Typecheck web + run: bun run web:typecheck + + - name: Test + run: bun run test diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b9e348c..d1ff534 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -7,21 +7,21 @@ on: workflow_dispatch: permissions: - contents: read + contents: write id-token: write # required for npm Trusted Publishing (OIDC) jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: oven-sh/setup-bun@v2 with: bun-version: latest # npm Trusted Publishing requires npm >= 11.5.1 and Node >= 22.14. - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: "24" registry-url: "https://registry.npmjs.org" @@ -35,7 +35,16 @@ jobs: - name: Test run: bun run test + - name: Validate package + run: bun run release:validate + # npm publish runs `prepublishOnly` (bun run web:build) automatically, # so we don't need an explicit build step. - name: Publish (OIDC, no token) run: npm publish --provenance + + - name: Create GitHub release + if: github.ref_type == 'tag' + env: + GH_TOKEN: ${{ github.token }} + run: gh release create "${GITHUB_REF_NAME}" --generate-notes --latest diff --git a/.gitignore b/.gitignore index 387b856..acca150 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ node_modules/ .otel-data/ .motel-data/ web/dist/ +.local/ +dist/ +.reference/ diff --git a/AGENTS.md b/AGENTS.md index 0c88187..71dbbc9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -132,7 +132,7 @@ The repo is wired up with `@effect/language-service` as a `tsconfig.json` `plugi ## Env Vars - `MOTEL_OTEL_ENABLED`: defaults to `false` (set to `true` to emit self-traces for debugging motel itself) -- `MOTEL_OTEL_SERVICE_NAME`: defaults to `motel-otel-tui` +- `MOTEL_OTEL_SERVICE_NAME`: defaults to `motel-otel-tui`; a nonblank explicit value also selects the initial TUI service, overriding the remembered selection (unset or blank values keep the remembered selection) - `MOTEL_OTEL_BASE_URL`: defaults to `http://127.0.0.1:27686` - `MOTEL_OTEL_HOST`: defaults to `127.0.0.1` - `MOTEL_OTEL_PORT`: defaults to `27686` diff --git a/bun.lock b/bun.lock index ca70423..39f5e5b 100644 --- a/bun.lock +++ b/bun.lock @@ -5,129 +5,146 @@ "": { "name": "motel", "dependencies": { - "@effect/atom-react": "^4.0.0-beta.49", - "@effect/opentelemetry": "^4.0.0-beta.49", - "@effect/platform-bun": "^4.0.0-beta.50", + "@effect/atom-react": "4.0.0-beta.90", + "@effect/opentelemetry": "4.0.0-beta.90", + "@effect/platform-bun": "4.0.0-beta.90", "@opentelemetry/api": "^1.9.0", - "@opentelemetry/exporter-logs-otlp-http": "^0.214.0", - "@opentelemetry/exporter-trace-otlp-http": "^0.211.0", - "@opentelemetry/sdk-logs": "^0.214.0", - "@opentelemetry/sdk-node": "^0.214.0", - "@opentelemetry/sdk-trace-base": "^2.5.0", - "@opentelemetry/sdk-trace-node": "^2.6.1", - "@opentui/core": "^0.1.99", - "@opentui/react": "^0.1.99", - "effect": "^4.0.0-beta.49", - "react": "^19.2.5", + "@opentelemetry/exporter-logs-otlp-http": "^0.219.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.219.0", + "@opentelemetry/otlp-transformer": "0.214.0", + "@opentelemetry/sdk-logs": "^0.219.0", + "@opentelemetry/sdk-node": "^0.219.0", + "@opentelemetry/sdk-trace-base": "^2.8.0", + "@opentelemetry/sdk-trace-node": "^2.8.0", + "@opentui/core": "0.4.2", + "@opentui/react": "0.4.2", + "effect": "4.0.0-beta.90", + "protobufjs": "7.6.4", + "react": "^19.2.7", "scheduler": "^0.27.0", }, "devDependencies": { - "@effect/language-service": "^0.85.1", - "@types/bun": "^1.3.12", - "@types/react": "^19.2.14", - "typescript": "^6.0.2", + "@cloudflare/workers-types": "5.20260923.1", + "@effect/language-service": "^0.86.2", + "@types/bun": "^1.3.14", + "@types/react": "^19.2.17", + "esbuild": "0.28.2", + "typescript": "^6.0.3", + "workerd": "1.20260901.1", }, }, "web": { "name": "motel-web", "dependencies": { - "@effect/atom-react": "^4.0.0-beta.49", - "@tailwindcss/vite": "^4.2.2", - "@tanstack/react-virtual": "^3.13.23", - "effect": "^4.0.0-beta.49", - "react": "^19.2.5", - "react-dom": "^19.2.5", - "react-router-dom": "^7.14.1", + "@effect/atom-react": "4.0.0-beta.90", + "@tailwindcss/vite": "^4.3.1", + "@tanstack/react-virtual": "^3.14.3", + "effect": "4.0.0-beta.90", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "react-router-dom": "^7.18.0", "scheduler": "^0.27.0", - "tailwindcss": "^4.2.2", + "tailwindcss": "^4.3.1", }, "devDependencies": { - "@types/react": "^19.2.14", + "@types/react": "^19.2.17", "@types/react-dom": "^19.1.0", - "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", - "vite": "^8.0.8", + "@vitejs/plugin-react": "^6.0.3", + "typescript": "^6.0.3", + "vite": "^8.1.0", }, }, }, + "overrides": { + "@grpc/grpc-js": "1.14.4", + "protobufjs": "7.6.4", + "shell-quote": "1.9.0", + "ws": "8.21.0", + }, "packages": { - "@dimforge/rapier2d-simd-compat": ["@dimforge/rapier2d-simd-compat@0.17.3", "", {}, "sha512-bijvwWz6NHsNj5e5i1vtd3dU2pDhthSaTUZSh14DUGGKJfw8eMnlWZsxwHBxB/a3AXVNDjL9abuHw1k9FGR+jg=="], + "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260901.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-5nJCpnclV6cc5v3aZOewu+djFvNvm9A/owQ+WJ2G1450roeFHfZGsyW7vVjSRWx3v2iykfkviyJfGm9ApeFDGg=="], - "@effect/atom-react": ["@effect/atom-react@4.0.0-beta.49", "", { "peerDependencies": { "effect": "^4.0.0-beta.49", "react": "^19.2.4", "scheduler": "*" } }, "sha512-21PmDZFy8SIoxiV4L1bd0v1NQDqPKHOSB9y5NVwab5soFjE7l88t2I52WkDgGpc/cM4xp0TPCVhyAQ1JMIq53g=="], + "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260901.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-nLwfUVoYmFNW1Ntp0BfZ2K1UWIll4zTTYP8Ni7NQnyrNkTnFeU/prrllWtnw7OG8Z/jyaEAAFaDFEBobrNTU3A=="], - "@effect/language-service": ["@effect/language-service@0.85.1", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-EXnJjIy6zQ3nUO/MZ+ynWUb8B895KZPotd1++oTs9JjDkplwM7cb6zo8Zq2zU6piwq+KflO7amXbEfj1UMpHkw=="], + "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260901.1", "", { "os": "linux", "cpu": "x64" }, "sha512-wzCO0cMrFLRRs6bG5KXDpLSe70GpzpsxPjxwBzQngQOfyI2cxs5i4Rb5rDB0GG8E19feZEhjGXcHgY9+5Lq8YA=="], - "@effect/opentelemetry": ["@effect/opentelemetry@4.0.0-beta.49", "", { "peerDependencies": { "@opentelemetry/api": "^1.9", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": "^2.0.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@opentelemetry/sdk-trace-web": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.33.0", "effect": "^4.0.0-beta.49" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-u8DxpROcwtIbr+7hTf/HF1HLMbDYciQT2K+J7YD6ky5gyXQR4z557uq5AsZKDoHFtOnTepNlsGDpzU633YwwUg=="], + "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260901.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-aBk82+sSphMa6uR6h/CFN/GXmELJ2YOgQKYSsOsCTvbVLvHjclmyoEdLTY5FEImvo1ch1uXpGQgTjI8/suHGEg=="], - "@effect/platform-bun": ["@effect/platform-bun@4.0.0-beta.50", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.50" }, "peerDependencies": { "effect": "^4.0.0-beta.50" } }, "sha512-KnDleO9PMr++HoLl/VmzR3hNcOG3Hz5+xAtO5ec5SzN7QwedPbPWBZYR88tv5GTeKLgBvPTAjoTt2kbbNh5Jzw=="], + "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260901.1", "", { "os": "win32", "cpu": "x64" }, "sha512-3oJnCV7mWwzqmqz+3E+KYLbw1FylH+8n+p84srWv9MEsY9II1QuKiRKVf3CQTIdTDb6rkr5fCuRtkmLgRpAsNg=="], - "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.50", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.20.0" }, "peerDependencies": { "effect": "^4.0.0-beta.50" } }, "sha512-aFHifdtDae0ih4d2m6j7cdEWLZcn/g8nmxn1XU2PwLezb5oAyu5ljibayYRIvJNTbmJXRk9sKjbl2NC0xQMTYg=="], + "@cloudflare/workers-types": ["@cloudflare/workers-types@5.20260923.1", "", {}, "sha512-zuOqyxfhzhN2LlxxcpnAS79aGVm9ku3ZcWKyvVLHyjPV6oHsuHSFidzSHhHE9ho9UTcUxU1IdtpdVuFXluKB+g=="], - "@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], + "@effect/atom-react": ["@effect/atom-react@4.0.0-beta.90", "", { "peerDependencies": { "effect": "^4.0.0-beta.90", "react": "^19.2.4", "scheduler": "*" } }, "sha512-E1GFtNT+ZeIA3//g+IcbaAfjVAn5VZ08q4/MTEEV/862AlCfia8nRETJqpvVcTEK4X6EnebjTXdWu7tTV9wggg=="], - "@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], + "@effect/language-service": ["@effect/language-service@0.86.2", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-SaPln+8srOqDJDUwNTDmP5e+IYpEDr9+1epGznnsLqu8xvo6VnxyWARdeLpqvZJlb0Pgy9ca7ppqvvdWbHPXAg=="], - "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + "@effect/opentelemetry": ["@effect/opentelemetry@4.0.0-beta.90", "", { "peerDependencies": { "@opentelemetry/api": "^1.9", "@opentelemetry/api-logs": ">=0.203.0 <0.300.0", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": "^2.0.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@opentelemetry/sdk-trace-web": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.33.0", "effect": "^4.0.0-beta.90" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/api-logs", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-PGThhkj60NKX6HkKXDEmmmVTc8dxedP8XurE+eM1n+eGOQ2wMxLLnFLBEoGCrnyLFo6gG1tHPsSRcJMfUyP2QA=="], - "@grpc/grpc-js": ["@grpc/grpc-js@1.14.3", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA=="], + "@effect/platform-bun": ["@effect/platform-bun@4.0.0-beta.90", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.90" }, "peerDependencies": { "effect": "^4.0.0-beta.90" } }, "sha512-EL6IVL8+HcrldPcJvQLXtj6kwsM5RZ57PqY54mPE5B+KSbNmT5T04Kuso6PAnl8tQtiOqLI8X+RG6fyKXggwjQ=="], - "@grpc/proto-loader": ["@grpc/proto-loader@0.8.0", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.3", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ=="], + "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.90", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.20.0" }, "peerDependencies": { "effect": "^4.0.0-beta.90" } }, "sha512-0UlZgqJdAjcEr6RtLSqeOtTBeuLO03cTX4FKpzW1nTCScz3wGpELgcaU/T+fURf4LBiD0QQS3tcOYTN/T+qFsw=="], + + "@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], - "@jimp/core": ["@jimp/core@1.6.0", "", { "dependencies": { "@jimp/file-ops": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "await-to-js": "^3.0.0", "exif-parser": "^0.1.12", "file-type": "^16.0.0", "mime": "3" } }, "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ=="], - "@jimp/diff": ["@jimp/diff@1.6.0", "", { "dependencies": { "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "pixelmatch": "^5.3.0" } }, "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw=="], + "@esbuild/android-arm": ["@esbuild/android-arm@0.28.2", "", { "os": "android", "cpu": "arm" }, "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg=="], - "@jimp/file-ops": ["@jimp/file-ops@1.6.0", "", {}, "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ=="], + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A=="], - "@jimp/js-bmp": ["@jimp/js-bmp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "bmp-ts": "^1.0.9" } }, "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw=="], + "@esbuild/android-x64": ["@esbuild/android-x64@0.28.2", "", { "os": "android", "cpu": "x64" }, "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q=="], - "@jimp/js-gif": ["@jimp/js-gif@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "gifwrap": "^0.10.1", "omggif": "^1.0.10" } }, "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g=="], + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw=="], - "@jimp/js-jpeg": ["@jimp/js-jpeg@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "jpeg-js": "^0.4.4" } }, "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA=="], + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw=="], - "@jimp/js-png": ["@jimp/js-png@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "pngjs": "^7.0.0" } }, "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg=="], + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw=="], - "@jimp/js-tiff": ["@jimp/js-tiff@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "utif2": "^4.1.0" } }, "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw=="], + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg=="], - "@jimp/plugin-blit": ["@jimp/plugin-blit@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA=="], + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.2", "", { "os": "linux", "cpu": "arm" }, "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w=="], - "@jimp/plugin-blur": ["@jimp/plugin-blur@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw=="], + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug=="], - "@jimp/plugin-circle": ["@jimp/plugin-circle@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw=="], + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ=="], - "@jimp/plugin-color": ["@jimp/plugin-color@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "tinycolor2": "^1.6.0", "zod": "^3.23.8" } }, "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA=="], + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ=="], - "@jimp/plugin-contain": ["@jimp/plugin-contain@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ=="], + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA=="], - "@jimp/plugin-cover": ["@jimp/plugin-cover@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA=="], + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ=="], - "@jimp/plugin-crop": ["@jimp/plugin-crop@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang=="], + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA=="], - "@jimp/plugin-displace": ["@jimp/plugin-displace@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q=="], + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg=="], - "@jimp/plugin-dither": ["@jimp/plugin-dither@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0" } }, "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ=="], + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ=="], - "@jimp/plugin-fisheye": ["@jimp/plugin-fisheye@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA=="], + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw=="], - "@jimp/plugin-flip": ["@jimp/plugin-flip@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg=="], + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.2", "", { "os": "none", "cpu": "x64" }, "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw=="], - "@jimp/plugin-hash": ["@jimp/plugin-hash@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "any-base": "^1.1.0" } }, "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q=="], + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ=="], - "@jimp/plugin-mask": ["@jimp/plugin-mask@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA=="], + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw=="], - "@jimp/plugin-print": ["@jimp/plugin-print@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/types": "1.6.0", "parse-bmfont-ascii": "^1.0.6", "parse-bmfont-binary": "^1.0.6", "parse-bmfont-xml": "^1.1.6", "simple-xml-to-json": "^1.2.2", "zod": "^3.23.8" } }, "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A=="], + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q=="], - "@jimp/plugin-quantize": ["@jimp/plugin-quantize@1.6.0", "", { "dependencies": { "image-q": "^4.0.0", "zod": "^3.23.8" } }, "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg=="], + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g=="], - "@jimp/plugin-resize": ["@jimp/plugin-resize@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA=="], + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ=="], - "@jimp/plugin-rotate": ["@jimp/plugin-rotate@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw=="], + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA=="], - "@jimp/plugin-threshold": ["@jimp/plugin-threshold@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w=="], + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g=="], - "@jimp/types": ["@jimp/types@1.6.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg=="], + "@grpc/grpc-js": ["@grpc/grpc-js@1.14.4", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ=="], - "@jimp/utils": ["@jimp/utils@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "tinycolor2": "^1.6.0" } }, "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA=="], + "@grpc/proto-loader": ["@grpc/proto-loader@0.8.0", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.3", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ=="], "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], @@ -141,235 +158,211 @@ "@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="], - "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="], + "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="], - "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="], + "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w=="], - "@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw=="], + "@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4", "", { "os": "linux", "cpu": "arm" }, "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw=="], - "@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg=="], + "@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw=="], - "@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg=="], + "@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ=="], - "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ=="], + "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="], - "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="], "@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="], - "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA=="], + "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.219.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-FFx7YnaYJlIjqWW/AG/yAZ0L/NEY724PipXXXQLdtZPbLwBGbUMTGL1i/esI56TWfTUXxhLfpgrnWJCG8aUJyg=="], - "@opentelemetry/configuration": ["@opentelemetry/configuration@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "yaml": "^2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0" } }, "sha512-Q+awuEwxhETwIAXuxHvIY5ZMEP0ZqvxLTi9kclrkyVJppEUXYL3Bhiw3jYrxdHYMh0Y0tVInQH9FEZ1aMinvLA=="], + "@opentelemetry/configuration": ["@opentelemetry/configuration@0.219.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "yaml": "^2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0" } }, "sha512-wXZUYv4ngu43nA4WEhuXNacm46LW+17LRM8nKyIhBzroRA24PBYjMnakwzR/w777nFUB5xlgsYTTeuXxumZM1Q=="], - "@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.6.1", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-XHzhwRNkBpeP8Fs/qjGrAf9r9PRv67wkJQ/7ZPaBQQ68DYlTBBx5MF9LvPx7mhuXcDessKK2b+DcxqwpgkcivQ=="], + "@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.8.0", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-/3FIraneMcng67SUJCxvyInk/oxzwsxyadufk0wwfOBLf5wqtAGX4MoQASwSbndBPeARzBryUM9Azr5kHIdWLw=="], - "@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + "@opentelemetry/core": ["@opentelemetry/core@2.8.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww=="], - "@opentelemetry/exporter-logs-otlp-grpc": ["@opentelemetry/exporter-logs-otlp-grpc@0.214.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-grpc-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/sdk-logs": "0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-SwmFRwO8mi6nndzbsjPgSFg7qy1WeNHRFD+s6uCsdiUDUt3+yzI2qiHE3/ub2f37+/CbeGcG+Ugc8Gwr6nu2Aw=="], + "@opentelemetry/exporter-logs-otlp-grpc": ["@opentelemetry/exporter-logs-otlp-grpc@0.219.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.8.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/otlp-grpc-exporter-base": "0.219.0", "@opentelemetry/otlp-transformer": "0.219.0", "@opentelemetry/sdk-logs": "0.219.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-7SvzDCIclHWAcCwZ1MTOLcwn4BVNPGI3QxS/DJraPNe1TTL+4TvUBq5zeQV8tsnYvtDN7wKW2qocVmaCP2l7sQ=="], - "@opentelemetry/exporter-logs-otlp-http": ["@opentelemetry/exporter-logs-otlp-http@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/sdk-logs": "0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-9qv2Tl/Hq6qc5pJCbzFJnzA0uvlb9DgM70yGJPYf3bA5LlLkRCpcn81i4JbcIH4grlQIWY6A+W7YG0LLvS1BAw=="], + "@opentelemetry/exporter-logs-otlp-http": ["@opentelemetry/exporter-logs-otlp-http@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/otlp-transformer": "0.219.0", "@opentelemetry/sdk-logs": "0.219.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-mhl2HL6GmZI8b8PwPfqMws/5ovJfbRTxwc9Y5agVVHiQ+e5SL1btsFr/kJDgt7YCexDtsUn5HAreHQO9szFS0A=="], - "@opentelemetry/exporter-logs-otlp-proto": ["@opentelemetry/exporter-logs-otlp-proto@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-logs": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IWAVvCO1TlpotRjFmhQFz9RSfQy5BsLtDRBtptSrXZRwfyRPpuql/RMe5zdmu0Gxl3ERDFwOzOqkf3bwy7Jzcw=="], + "@opentelemetry/exporter-logs-otlp-proto": ["@opentelemetry/exporter-logs-otlp-proto@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/otlp-transformer": "0.219.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Ayw4Gf71PS9jhBVaYywa4WsajnqfDehMkTdVH3TSAVHqPcsAv/AhH/wTNRYNt99szeYr6Gbd/D6RjZD77wAxHg=="], - "@opentelemetry/exporter-metrics-otlp-grpc": ["@opentelemetry/exporter-metrics-otlp-grpc@0.214.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.6.1", "@opentelemetry/exporter-metrics-otlp-http": "0.214.0", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-grpc-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-metrics": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-0NGxWHVYHgbp51SEzmsP+Hdups81eRs229STcSWHo3WO0aqY6RpJ9csxfyEtFgaNrBDv6UfOh0je4ss/ROS6XA=="], + "@opentelemetry/exporter-metrics-otlp-grpc": ["@opentelemetry/exporter-metrics-otlp-grpc@0.219.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.8.0", "@opentelemetry/exporter-metrics-otlp-http": "0.219.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/otlp-grpc-exporter-base": "0.219.0", "@opentelemetry/otlp-transformer": "0.219.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-metrics": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-6LaaSrPxK5L55bXevWajvOMxGOpNm0n12tG53TeZaUeNzXwLPg6d2KCC1zAlGsojan+xRG71mA4Qqs9K2VVrKQ=="], - "@opentelemetry/exporter-metrics-otlp-http": ["@opentelemetry/exporter-metrics-otlp-http@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-metrics": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Tx/59RmjBgkXJ3qnsD04rpDrVWL53LU/czpgLJh+Ab98nAroe91I7vZ3uGN9mxwPS0jsZEnmqmHygVwB2vRMlA=="], + "@opentelemetry/exporter-metrics-otlp-http": ["@opentelemetry/exporter-metrics-otlp-http@0.219.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/otlp-transformer": "0.219.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-metrics": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-6CaDRbMVHZSDWzNXwrR8y/H4B/Z1eMNnkHiPQlTx3Ojz2OHY4X/aff/UC4P/3pHUQSuTfi3oh2UsPPZppw+Vrg=="], - "@opentelemetry/exporter-metrics-otlp-proto": ["@opentelemetry/exporter-metrics-otlp-proto@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/exporter-metrics-otlp-http": "0.214.0", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-metrics": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pJIcghFGhx3VSCgP5U+yZx+OMNj0t+ttnhC8IjL5Wza7vWIczctF6t3AGcVQffi2dEqX+ZHANoBwoPR8y6RMKA=="], + "@opentelemetry/exporter-metrics-otlp-proto": ["@opentelemetry/exporter-metrics-otlp-proto@0.219.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/exporter-metrics-otlp-http": "0.219.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/otlp-transformer": "0.219.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-metrics": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-DUS7XyIiEnoeccQUvuKy0G2/YqeKhpN8FVIrGbrLNIVMj10yeIFLRzRv0tibCI2kXXvlTTABVexGAk78wHk2ug=="], - "@opentelemetry/exporter-prometheus": ["@opentelemetry/exporter-prometheus@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-metrics": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-4TGYoZKebUWVuYkV6r5wS2dUF4zH7EbWFw/Uqz1ZM1tGHQeFT9wzHGXq3iSIXMUrwu5jRdxjfMaXrYejPu2kpQ=="], + "@opentelemetry/exporter-prometheus": ["@opentelemetry/exporter-prometheus@0.219.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-TxOnJ85eWJY5JyOJsNMXiRTYlkDcOv0u3KbXEzWCc+tUS9sjL/BC6BcdxZ0B9r2OFVqsrZFXUzSD2sZUy42Ucw=="], - "@opentelemetry/exporter-trace-otlp-grpc": ["@opentelemetry/exporter-trace-otlp-grpc@0.214.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-grpc-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-FWRZ7AWoTryYhthralHkfXUuyO3l7cRsnr49WcDio1orl2a7KxT8aDZdwQtV1adzoUvZ9Gfo+IstElghCS4zfw=="], + "@opentelemetry/exporter-trace-otlp-grpc": ["@opentelemetry/exporter-trace-otlp-grpc@0.219.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.8.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/otlp-grpc-exporter-base": "0.219.0", "@opentelemetry/otlp-transformer": "0.219.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-BkDNv1UD6BscW19MxbAxVmSYSSFuyeqR6buV2/HTYqA7GrR0EbTFzqG6h86T3PtXmpdbsWjMGLDdjG2rikG27Q=="], - "@opentelemetry/exporter-trace-otlp-http": ["@opentelemetry/exporter-trace-otlp-http@0.211.0", "", { "dependencies": { "@opentelemetry/core": "2.5.0", "@opentelemetry/otlp-exporter-base": "0.211.0", "@opentelemetry/otlp-transformer": "0.211.0", "@opentelemetry/resources": "2.5.0", "@opentelemetry/sdk-trace-base": "2.5.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-F1Rv3JeMkgS//xdVjbQMrI3+26e5SXC7vXA6trx8SWEA0OUhw4JHB+qeHtH0fJn46eFItrYbL5m8j4qi9Sfaxw=="], + "@opentelemetry/exporter-trace-otlp-http": ["@opentelemetry/exporter-trace-otlp-http@0.219.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/otlp-transformer": "0.219.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-9t6SvBXXBEjOBcIzgozvBbd3jWrv3Gt3ngGhl1fhdZ/zRc7oZDVOFEqbi2zlBpW9BXhgDMKv422J0DL/3iQWfw=="], - "@opentelemetry/exporter-trace-otlp-proto": ["@opentelemetry/exporter-trace-otlp-proto@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-ON0spYWb2yAdQ9b+ItNyK0c6qdtcs+0eVR4YFJkhJL7agfT8sHFg0e5EesauSRiTHPZHiDobI92k77q0lwAmqg=="], + "@opentelemetry/exporter-trace-otlp-proto": ["@opentelemetry/exporter-trace-otlp-proto@0.219.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/otlp-transformer": "0.219.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-lF/LUBfhOFmxJa+SQsLN7ziV4MHa2pyKgOM6JNehSOfU+npjM4gwm9oIKEJrzrWcexMcqydiyoFy0XCb1Ql3wQ=="], - "@opentelemetry/exporter-zipkin": ["@opentelemetry/exporter-zipkin@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-km2/hD3inLTqtLnUAHDGz7ZP/VOyZNslrC/iN66x4jkmpckwlONW54LRPNI6fm09/musDtZga9EWsxgwnjGUlw=="], + "@opentelemetry/exporter-zipkin": ["@opentelemetry/exporter-zipkin@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-Mj84UkEa17BK2o903VTXW3wM8CrSZexGs4tRGVZVIMM9ni1T6TuGx5IrRfoWKAbshx42D5/kc7YV+axypLPYyA=="], - "@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "import-in-the-middle": "^3.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-MHqEX5Dk59cqVah5LiARMACku7jXSVk9iVDWOea4x3cr7VfdByeDCURK6o1lntT1JS/Tsovw01UJrBhN3/uC5w=="], + "@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "import-in-the-middle": "^3.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-X5t7I8GyIO9rmGHwoedZLREpQqrF1WW2nxzNNym6HOKpFiE+rvqV3ngC0xcZVO2YwIGf3KKmRdWrYwdwz3H9RQ=="], - "@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-transformer": "0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-u1Gdv0/E9wP+apqWf7Wv2npXmgJtxsW2XL0TEv9FZloTZRuMBKmu8cYVXwS4Hm3q/f/3FuCnPTgiwYvIqRSpRg=="], + "@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.219.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/otlp-transformer": "0.219.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-zvIxQX/AZUVKDU+hCuYx+7UkiP7GRdnk1ZbFQRYzHvYp47cAWR4j3IhoPhV9KaeXEv2xdGq3IA6PnpzDmLcmSA=="], - "@opentelemetry/otlp-grpc-exporter-base": ["@opentelemetry/otlp-grpc-exporter-base@0.214.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IDP6zcyA24RhNZ289MP6eToIZcinlmirHjX8v3zKCQ2ZhPpt5cGwkN91tCth337lqHIgWcTy90uKRiX/SzALDw=="], + "@opentelemetry/otlp-grpc-exporter-base": ["@opentelemetry/otlp-grpc-exporter-base@0.219.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.8.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/otlp-transformer": "0.219.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-iIk/s8QQu39zpTrRRmsW/Eg3SE2+Hg8tLWepr2FLRgmwUpNd0IpCTLJEHJ77hpt4hgIS8MAh44UYI4xQPZwWlw=="], "@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-logs": "0.214.0", "@opentelemetry/sdk-metrics": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1", "protobufjs": "^7.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-DSaYcuBRh6uozfsWN3R8HsN0yDhCuWP7tOFdkUOVaWD1KVJg8m4qiLUsg/tNhTLS9HUYUcwNpwL2eroLtsZZ/w=="], - "@opentelemetry/propagator-b3": ["@opentelemetry/propagator-b3@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dvz9TA6cPqIbxolSzQ5x9br6iQlqdGhVYrm+oYc7pfJ7LaVXz8F0XIqhWbnKB5YvfZ6SUmabBUUxnvHs/9uhxA=="], + "@opentelemetry/propagator-b3": ["@opentelemetry/propagator-b3@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SazlvuSKi5533rPHTW2TwBwdMakhjZST4SYs0YauuvfGDkT13KbG1gJS75hV0uWVeevhtVP9sAIlaZLTHdSbMg=="], - "@opentelemetry/propagator-jaeger": ["@opentelemetry/propagator-jaeger@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-kKFMxBcjBZAC1vBch5mtZ/dJQvcAEKWga+c+q5iGgRLPIE6Mc649zEwMaCIQCzalziMJQiyUadFYMHmELB7AFw=="], + "@opentelemetry/propagator-jaeger": ["@opentelemetry/propagator-jaeger@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Xnz9zZvvQzUw+9DrOn0MomR7BxFCkA2pcfXBQuHC28ndJpSbjLs7knzYb05kw5SyCjSsEWombkZMgGcJSk8JVg=="], - "@opentelemetry/resources": ["@opentelemetry/resources@2.5.0", "", { "dependencies": { "@opentelemetry/core": "2.5.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g=="], + "@opentelemetry/resources": ["@opentelemetry/resources@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg=="], - "@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-zf6acnScjhsaBUU22zXZ/sLWim1dfhUAbGXdMmHmNG3LfBnQ3DKsOCITb2IZwoUsNNMTogqFKBnlIPPftUgGwA=="], + "@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-s6lTKRakaPClvKoWHRChxnXjDMkM/TQ30ff78jN6EBGf7MI7VzANE5PU3f4z9qDUudWjvZjOLHG0rBnBKYvoXA=="], "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-9t9hJHX15meBy2NmTJxL+NJfXmnausR2xUDvE19XQce0Qi/GBtDGamU8nS1RMbdgDmhgpm3VaOu2+fiS/SfTpQ=="], - "@opentelemetry/sdk-node": ["@opentelemetry/sdk-node@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/configuration": "0.214.0", "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/core": "2.6.1", "@opentelemetry/exporter-logs-otlp-grpc": "0.214.0", "@opentelemetry/exporter-logs-otlp-http": "0.214.0", "@opentelemetry/exporter-logs-otlp-proto": "0.214.0", "@opentelemetry/exporter-metrics-otlp-grpc": "0.214.0", "@opentelemetry/exporter-metrics-otlp-http": "0.214.0", "@opentelemetry/exporter-metrics-otlp-proto": "0.214.0", "@opentelemetry/exporter-prometheus": "0.214.0", "@opentelemetry/exporter-trace-otlp-grpc": "0.214.0", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/exporter-trace-otlp-proto": "0.214.0", "@opentelemetry/exporter-zipkin": "2.6.1", "@opentelemetry/instrumentation": "0.214.0", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/propagator-b3": "2.6.1", "@opentelemetry/propagator-jaeger": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-logs": "0.214.0", "@opentelemetry/sdk-metrics": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/sdk-trace-node": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-gl2XvQBJuPjhGcw9SsnQO5qxChAPMuGRPFaD8lqtF+Cey91NgGUQ0sio2vlDFOSm3JOLzc44vL+OAfx1dXuZjg=="], + "@opentelemetry/sdk-node": ["@opentelemetry/sdk-node@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/configuration": "0.219.0", "@opentelemetry/context-async-hooks": "2.8.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/exporter-logs-otlp-grpc": "0.219.0", "@opentelemetry/exporter-logs-otlp-http": "0.219.0", "@opentelemetry/exporter-logs-otlp-proto": "0.219.0", "@opentelemetry/exporter-metrics-otlp-grpc": "0.219.0", "@opentelemetry/exporter-metrics-otlp-http": "0.219.0", "@opentelemetry/exporter-metrics-otlp-proto": "0.219.0", "@opentelemetry/exporter-prometheus": "0.219.0", "@opentelemetry/exporter-trace-otlp-grpc": "0.219.0", "@opentelemetry/exporter-trace-otlp-http": "0.219.0", "@opentelemetry/exporter-trace-otlp-proto": "0.219.0", "@opentelemetry/exporter-zipkin": "2.8.0", "@opentelemetry/instrumentation": "0.219.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/otlp-grpc-exporter-base": "0.219.0", "@opentelemetry/propagator-b3": "2.8.0", "@opentelemetry/propagator-jaeger": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0", "@opentelemetry/sdk-trace-node": "2.8.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-NWLpWLEb8gV3+JBHYoIrktbM385wyHpRJoh3J/4Q52d4PR+AlPMNGJT3DzBUrDSUEVbKAXoHR+EDAPxtiNcj8g=="], - "@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw=="], + "@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-mhU4jp+vW0mGbFRd+GeXHvmfA4aDqWjBjLC3pE5XMpLs0IE2ryYb019Ts2AQrOq67gaTF25D91+fgvEHDZEnuQ=="], - "@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@2.6.1", "", { "dependencies": { "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/core": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Hh2i4FwHWRFhnO2Q/p6svMxy8MPsNCG0uuzUY3glqm0rwM0nQvbTO1dXSp9OqQoTKXcQzaz9q1f65fsurmOhNw=="], + "@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@2.8.0", "", { "dependencies": { "@opentelemetry/context-async-hooks": "2.8.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-nZt9OGufioAc3AfoLTqA9bsAeaMJAictYDdI2VcNQ+PmT+3rfKjAZDZvgPfd8VPX0O5Bw1hdQF6kDK8VSpZiWg=="], "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], - "@opentui/core": ["@opentui/core@0.1.99", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.99", "@opentui/core-darwin-x64": "0.1.99", "@opentui/core-linux-arm64": "0.1.99", "@opentui/core-linux-x64": "0.1.99", "@opentui/core-win32-arm64": "0.1.99", "@opentui/core-win32-x64": "0.1.99", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-I3+AEgGzqNWIpWX9g2WOscSPwtQDNOm4KlBjxBWCZjLxkF07u77heWXF7OiAdhKLtNUW6TFiyt6yznqAZPdG3A=="], + "@opentui/core": ["@opentui/core@0.4.2", "", { "dependencies": { "bun-ffi-structs": "0.2.3", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.4.2", "@opentui/core-darwin-x64": "0.4.2", "@opentui/core-linux-arm64": "0.4.2", "@opentui/core-linux-arm64-musl": "0.4.2", "@opentui/core-linux-x64": "0.4.2", "@opentui/core-linux-x64-musl": "0.4.2", "@opentui/core-win32-arm64": "0.4.2", "@opentui/core-win32-x64": "0.4.2" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-ulx6RMqftf2fm7Itf9e81GcCDMNY6NAhmnKYhllDOMYD+PxYXR+vomy2bxQNV5ow31RE7s8WQFnb7hWTRUbx2g=="], + + "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.4.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-is+O+sS/l3E9cZXyM9pRF1WhqnE+hYSPYoZkbseR9CthJcaWPGi3R3jUJa1cLj325252jWgxVupnDqFUtKg36w=="], + + "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.4.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-ACi42h81DurSeybUAD1XyKT6xmXZcKeTxS54lZFi0CVZh46w0g99vNj8PlQzIFXvvFLT0e0IlRS//eWSWS2zGQ=="], - "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.99", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bzVrqeX2vb5iWrc/ftOUOqeUY8XO+qSgoTwj5TXHuwagavgwD3Hpeyjx8+icnTTeM4pao0som1WR9xfye6/X5Q=="], + "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.4.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-RjOx2HcjLRtGSy9WrAGSdr5M9SpJuPifPORpImx6Mciovw0ltnE0uoYjIyor82uf6/LExWC7YA2AcAl+YBxayA=="], - "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.99", "", { "os": "darwin", "cpu": "x64" }, "sha512-VE4FrXBYpkxnvkqcCV1a8aN9jyyMJMihVW+V2NLCtp+4yQsj0AapG5TiUSN76XnmSZRptxDy5rBmEempeoIZbg=="], + "@opentui/core-linux-arm64-musl": ["@opentui/core-linux-arm64-musl@0.4.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-heNciL2ngPU+kq1h01PHLsxn6Fr8iqTFtbxSdVbhaY3XihuIjkuXyEhFeuoa1lsXY7Bb2gpWnX5EQVWnZsAuDQ=="], - "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.99", "", { "os": "linux", "cpu": "arm64" }, "sha512-viXQsbpS7yHjYkl7+am32JdvG96QU9lvHh1UiZtpOxcNUUqiYmA2ZwZFPD2Bi54jNyj5l2hjH6YkD3DzE2FEWA=="], + "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.4.2", "", { "os": "linux", "cpu": "x64" }, "sha512-9s0s/ooK+AhWP306By3gu+XhzcVEThC2sqKMPK1nQmGDujQhd+xOrtbtfCVcJSx62UzAovC2VNqypvP8vHByOg=="], - "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.99", "", { "os": "linux", "cpu": "x64" }, "sha512-WLoEFINOSp0tZSR9y4LUuGc7n4Y7H1wcpjUPzQ9vChkYDXrfZltEanzoDWbDcQ4kZQW5tHVC7LrZHpAsRLwFZg=="], + "@opentui/core-linux-x64-musl": ["@opentui/core-linux-x64-musl@0.4.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cjv6Bv7l3p/KLNJr5RyqCS0FmRlAGJnkA2IK3S+HkHhCOv/O02S1G+DBUY6POnyjp1eNy95vauustApobhdbig=="], - "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.99", "", { "os": "win32", "cpu": "arm64" }, "sha512-yWMOLWCEO8HdrctU1dMkgZC8qGkiO4Dwr4/e11tTvVpRmYhDsP/IR89ZjEEtOwnKwFOFuB/MxvflqaEWVQ2g5Q=="], + "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.4.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-mfJZrJ0TNPFRZUzXNsxAPe1YdiWsy/vbTl93+yeXGHPI1B8Qnk9V5hpzSxxEyBGhlTHSfGNtgiO+VrrdRC3kZA=="], - "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.99", "", { "os": "win32", "cpu": "x64" }, "sha512-aYRlsL2w8YRL6vPd7/hrqlNVkXU3QowWb01TOvAcHS8UAsXaGFUr47kSDyjxDi1wg1MzmVduCfsC7T3NoThV1w=="], + "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.4.2", "", { "os": "win32", "cpu": "x64" }, "sha512-P2oguG3ng3OMjAdasFSA3GhHaQXtzDUsIRDGbzWFOimpZ/zMemidp+JQ0V8V6XwK6Utk5G0aQ03oBaRCoLyYDw=="], - "@opentui/react": ["@opentui/react@0.1.99", "", { "dependencies": { "@opentui/core": "0.1.99", "react-reconciler": "^0.32.0" }, "peerDependencies": { "react": ">=19.0.0", "react-devtools-core": "^7.0.1", "ws": "^8.18.0" } }, "sha512-Z7IWg2QLa2FOuaJEYvl6Uwi9vXobpkbFWFPPUN22aAQSKi1E7aOUJ0oNMvObB8eSnsJzv0bdvTP4CuvtnYEz/g=="], + "@opentui/react": ["@opentui/react@0.4.2", "", { "dependencies": { "@opentui/core": "0.4.2", "react-reconciler": "^0.33.0" }, "peerDependencies": { "react": ">=19.2.0", "react-devtools-core": "^7.0.1", "ws": "^8.18.0" } }, "sha512-SsteuclEw72gUBzp/mZjADSNS1jJJr4htB89euicn+Atl62ZvO22wuLE9hBFDCpumtgG3sbSe+7XtV6vW1orXA=="], - "@oxc-project/types": ["@oxc-project/types@0.124.0", "", {}, "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg=="], + "@oxc-project/types": ["@oxc-project/types@0.137.0", "", {}, "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA=="], "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], - "@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="], + "@protobufjs/codegen": ["@protobufjs/codegen@2.0.5", "", {}, "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g=="], - "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="], + "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.1", "", {}, "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg=="], - "@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="], + "@protobufjs/fetch": ["@protobufjs/fetch@1.1.1", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1" } }, "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw=="], "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], - "@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="], - "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], - "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], + "@protobufjs/utf8": ["@protobufjs/utf8@1.1.1", "", {}, "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg=="], - "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.15", "", { "os": "android", "cpu": "arm64" }, "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.1.3", "", { "os": "android", "cpu": "arm64" }, "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g=="], - "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg=="], + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.1.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw=="], - "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.15", "", { "os": "darwin", "cpu": "x64" }, "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw=="], + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.1.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw=="], - "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.15", "", { "os": "freebsd", "cpu": "x64" }, "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw=="], + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.1.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw=="], - "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15", "", { "os": "linux", "cpu": "arm" }, "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA=="], + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.1.3", "", { "os": "linux", "cpu": "arm" }, "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg=="], - "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w=="], + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.1.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA=="], - "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ=="], + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.1.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w=="], - "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15", "", { "os": "linux", "cpu": "ppc64" }, "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ=="], + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.1.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw=="], - "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15", "", { "os": "linux", "cpu": "s390x" }, "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ=="], + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.1.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA=="], - "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.15", "", { "os": "linux", "cpu": "x64" }, "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA=="], + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.1.3", "", { "os": "linux", "cpu": "x64" }, "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg=="], - "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.15", "", { "os": "linux", "cpu": "x64" }, "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw=="], + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.1.3", "", { "os": "linux", "cpu": "x64" }, "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g=="], - "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.15", "", { "os": "none", "cpu": "arm64" }, "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg=="], + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.1.3", "", { "os": "none", "cpu": "arm64" }, "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ=="], - "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.15", "", { "dependencies": { "@emnapi/core": "1.9.2", "@emnapi/runtime": "1.9.2", "@napi-rs/wasm-runtime": "^1.1.3" }, "cpu": "none" }, "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q=="], + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.1.3", "", { "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg=="], - "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15", "", { "os": "win32", "cpu": "arm64" }, "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA=="], + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.1.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g=="], - "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.15", "", { "os": "win32", "cpu": "x64" }, "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g=="], + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.1.3", "", { "os": "win32", "cpu": "x64" }, "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA=="], - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.7", "", {}, "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "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.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="], - - "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="], + "@tailwindcss/node": ["@tailwindcss/node@4.3.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "5.21.6", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.1" } }, "sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A=="], - "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.2", "", { "os": "android", "cpu": "arm64" }, "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg=="], + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.1", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.1", "@tailwindcss/oxide-darwin-arm64": "4.3.1", "@tailwindcss/oxide-darwin-x64": "4.3.1", "@tailwindcss/oxide-freebsd-x64": "4.3.1", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.1", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.1", "@tailwindcss/oxide-linux-arm64-musl": "4.3.1", "@tailwindcss/oxide-linux-x64-gnu": "4.3.1", "@tailwindcss/oxide-linux-x64-musl": "4.3.1", "@tailwindcss/oxide-wasm32-wasi": "4.3.1", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.1", "@tailwindcss/oxide-win32-x64-msvc": "4.3.1" } }, "sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA=="], - "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg=="], + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.3.1", "", { "os": "android", "cpu": "arm64" }, "sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ=="], - "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw=="], + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.3.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA=="], - "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ=="], + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.3.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg=="], - "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ=="], + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.3.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g=="], - "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw=="], + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.3.1", "", { "os": "linux", "cpu": "arm" }, "sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg=="], - "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag=="], + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.3.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ=="], - "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg=="], + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.3.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA=="], - "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ=="], + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg=="], - "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.2", "", { "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" }, "cpu": "none" }, "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q=="], + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ=="], - "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ=="], + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.3.1", "", { "dependencies": { "@emnapi/core": "^1.10.0", "@emnapi/runtime": "^1.10.0", "@emnapi/wasi-threads": "^1.2.1", "@napi-rs/wasm-runtime": "^1.1.4", "@tybys/wasm-util": "^0.10.2", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA=="], - "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA=="], + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.3.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg=="], - "@tailwindcss/vite": ["@tailwindcss/vite@4.2.2", "", { "dependencies": { "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "tailwindcss": "4.2.2" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w=="], + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.3.1", "", { "os": "win32", "cpu": "x64" }, "sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA=="], - "@tanstack/react-virtual": ["@tanstack/react-virtual@3.13.23", "", { "dependencies": { "@tanstack/virtual-core": "3.13.23" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-XnMRnHQ23piOVj2bzJqHrRrLg4r+F86fuBcwteKfbIjJrtGxb4z7tIvPVAe4B+4UVwo9G4Giuz5fmapcrnZ0OQ=="], + "@tailwindcss/vite": ["@tailwindcss/vite@4.3.1", "", { "dependencies": { "@tailwindcss/node": "4.3.1", "@tailwindcss/oxide": "4.3.1", "tailwindcss": "4.3.1" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-hItDHuIIlEV61R+faXu66s1K36aTurO/Qw0e45Vskz57gXl9pWOT6eg3zmcEui6CZXddbN7zd41bwmvag4JGwQ=="], - "@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.23", "", {}, "sha512-zSz2Z2HNyLjCplANTDyl3BcdQJc2k1+yyFoKhNRmCr7V7dY8o8q5m8uFTI1/Pg1kL+Hgrz6u3Xo6eFUB7l66cg=="], + "@tanstack/react-virtual": ["@tanstack/react-virtual@3.14.3", "", { "dependencies": { "@tanstack/virtual-core": "3.17.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-k/cnHPVaOfn46hSbiY6n4Dzf4QjCGWSF40zR5QIIYUqPAjpA6TN7InfYmcMiDVQGP2iUn9xsRbAl8u1v3UmeVQ=="], - "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], + "@tanstack/virtual-core": ["@tanstack/virtual-core@3.17.1", "", {}, "sha512-VZyW2Uiml5tmBZwPGrSD3Sz73OxzljQMCmzYHsUTPEuTsERf5xwa+uWb01xEzkz3ZSYTjj8NEb/mKHvgKxyZdA=="], - "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], - "@types/bun": ["@types/bun@1.3.12", "", { "dependencies": { "bun-types": "1.3.12" } }, "sha512-DBv81elK+/VSwXHDlnH3Qduw+KxkTIWi7TXkAeh24zpi5l0B2kUg9Ga3tb4nJaPcOFswflgi/yAvMVBPrxMB+A=="], + "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], - "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], + "@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="], "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - "@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="], - - "@webgpu/types": ["@webgpu/types@0.1.69", "", {}, "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ=="], - - "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], + "@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.3", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg=="], "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], "acorn-import-attributes": ["acorn-import-attributes@1.9.5", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ=="], - "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - "any-base": ["any-base@1.1.0", "", {}, "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg=="], - - "await-to-js": ["await-to-js@3.0.0", "", {}, "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g=="], - - "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - - "bmp-ts": ["bmp-ts@1.0.9", "", {}, "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw=="], - - "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], - - "bun-ffi-structs": ["bun-ffi-structs@0.1.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w=="], - - "bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="], - - "bun-webgpu": ["bun-webgpu@0.1.5", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.5", "bun-webgpu-darwin-x64": "^0.1.5", "bun-webgpu-linux-x64": "^0.1.5", "bun-webgpu-win32-x64": "^0.1.5" } }, "sha512-91/K6S5whZKX7CWAm9AylhyKrLGRz6BUiiPiM/kXadSnD4rffljCD/q9cNFftm5YXhx4MvLqw33yEilxogJvwA=="], + "bun-ffi-structs": ["bun-ffi-structs@0.2.3", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-pgJiXP+hEgFo9qG51J6ItfY4ocs3vniwNzJ9WhoakB3QB2GdzQxX2EXssentPYlB2hOfJrTjO6iIQkWYzUodpg=="], - "bun-webgpu-darwin-arm64": ["bun-webgpu-darwin-arm64@0.1.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lIsDkPzJzPl6yrB5CUOINJFPnTRv6fF/Q8J1mAr43ogSp86WZEg9XZKaT6f3EUJ+9ETogGoMnoj1q0AwHUTbAQ=="], - - "bun-webgpu-darwin-x64": ["bun-webgpu-darwin-x64@0.1.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-uEddf5U7GvKIkM/BV18rUKtYHL6d0KeqBjNHwfqDH9QgEo9KVSKvJXS5I/sMefk5V5pIYE+8tQhtrREevhocng=="], - - "bun-webgpu-linux-x64": ["bun-webgpu-linux-x64@0.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-Y/f15j9r8ba0xUz+3lATtS74OE+PPzQXO7Do/1eCluJcuOlfa77kMjvBK/ShWnem3Y9xqi59pebTPOGRB+CaJA=="], - - "bun-webgpu-win32-x64": ["bun-webgpu-win32-x64@0.1.6", "", { "os": "win32", "cpu": "x64" }, "sha512-MHSFAKqizISb+C5NfDrFe3g0Al5Njnu0j/A+oO2Q+bIWX+fUYjBSowiYE1ZXJx65KuryuB+tiM7Qh6cQbVvkEg=="], + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], "cjs-module-lexer": ["cjs-module-lexer@2.2.0", "", {}, "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ=="], @@ -387,54 +380,40 @@ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="], - - "effect": ["effect@4.0.0-beta.49", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-4+mx856l4zxJJ+ybVNamqjAAinYsTNDVGZ5H3kstqKhDxCBW7sK/mZTtHuI7azt5AXEKX2ZXsi3EPAQVnkZk0w=="], - - "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "diff": ["diff@9.0.0", "", {}, "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw=="], - "enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="], + "effect": ["effect@4.0.0-beta.90", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.8.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.1", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^14.0.0", "yaml": "^2.9.0" } }, "sha512-A0U3OE+2oyK/iFG6VYbFj9gwjJ7rFXjgP7qV+m7n/4lOREp9Lfk1///SlGCpX7HRueOCZO1l7aW0KByXuJeiPA=="], - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], - "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], + "enhanced-resolve": ["enhanced-resolve@5.21.6", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ=="], - "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], + "esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="], - "exif-parser": ["exif-parser@0.1.12", "", {}, "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="], + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], - "fast-check": ["fast-check@4.6.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-h7H6Dm0Fy+H4ciQYFxFjXnXkzR2kr9Fb22c0UBpHnm59K2zpr2t13aPTHlltFiNT6zuxp6HMPAVVvgur4BLdpA=="], + "fast-check": ["fast-check@4.8.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - "file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="], - "find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], - "gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="], + "get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], - "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - - "image-q": ["image-q@4.0.0", "", { "dependencies": { "@types/node": "16.9.1" } }, "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw=="], - "import-in-the-middle": ["import-in-the-middle@3.0.1", "", { "dependencies": { "acorn": "^8.15.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" } }, "sha512-pYkiyXVL2Mf3pozdlDGV6NAObxQx13Ae8knZk1UJRJ6uRW/ZRmTGHlQYtrsSl7ubuE5F8CD1z+s1n4RHNuTtuA=="], - "ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="], + "ini": ["ini@7.0.0", "", {}, "sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w=="], "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], - "jimp": ["jimp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/diff": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-gif": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-blur": "1.6.0", "@jimp/plugin-circle": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-contain": "1.6.0", "@jimp/plugin-cover": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-displace": "1.6.0", "@jimp/plugin-dither": "1.6.0", "@jimp/plugin-fisheye": "1.6.0", "@jimp/plugin-flip": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/plugin-mask": "1.6.0", "@jimp/plugin-print": "1.6.0", "@jimp/plugin-quantize": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/plugin-rotate": "1.6.0", "@jimp/plugin-threshold": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg=="], - "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], - "jpeg-js": ["jpeg-js@0.4.4", "", {}, "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="], - "kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="], "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], @@ -469,216 +448,196 @@ "marked": ["marked@17.0.1", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg=="], - "mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="], - "module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="], "motel-web": ["motel-web@workspace:web"], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "msgpackr": ["msgpackr@1.11.9", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-FkoAAyyA6HM8wL882EcEyFZ9s7hVADSwG9xrVx3dxxNQAtgADTrJoEWivID82Iv1zWDsv/OtbrrcZAzGzOMdNw=="], + "msgpackr": ["msgpackr@2.0.4", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-o1C5KRmuRt+apqMr1HuGSqWStZoRBUpEsCsl15uM9VdAF1qHLtvMOU2En747EnTyEl6c4pzPewRMFF31s1CNbA=="], - "msgpackr-extract": ["msgpackr-extract@3.0.3", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA=="], + "msgpackr-extract": ["msgpackr-extract@3.0.4", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw=="], "multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="], - "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "nanoid": ["nanoid@3.3.15", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA=="], "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], - "omggif": ["omggif@1.0.10", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="], - - "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], - - "parse-bmfont-ascii": ["parse-bmfont-ascii@1.0.6", "", {}, "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA=="], - - "parse-bmfont-binary": ["parse-bmfont-binary@1.0.6", "", {}, "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA=="], - - "parse-bmfont-xml": ["parse-bmfont-xml@1.1.6", "", { "dependencies": { "xml-parse-from-string": "^1.0.0", "xml2js": "^0.5.0" } }, "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA=="], - - "peek-readable": ["peek-readable@4.1.0", "", {}, "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="], - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], - "pixelmatch": ["pixelmatch@5.3.0", "", { "dependencies": { "pngjs": "^6.0.0" }, "bin": { "pixelmatch": "bin/pixelmatch" } }, "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q=="], + "postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="], - "planck": ["planck@1.5.0", "", { "peerDependencies": { "stage-js": "^1.0.0-alpha.12" } }, "sha512-dlvqJE+FscZgrGUXJ5ybd0o5bvZ5XXyZNbm08xGsXp9WjXeAyWSFT6n9s/1PQcUBo4546fDXA5RMA4wbDyZw6g=="], - - "pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="], - - "postcss": ["postcss@8.5.10", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ=="], - - "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], - - "protobufjs": ["protobufjs@7.5.5", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg=="], + "protobufjs": ["protobufjs@7.6.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw=="], "pure-rand": ["pure-rand@8.4.0", "", {}, "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A=="], - "react": ["react@19.2.5", "", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="], + "react": ["react@19.2.7", "", {}, "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ=="], "react-devtools-core": ["react-devtools-core@7.0.1", "", { "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" } }, "sha512-C3yNvRHaizlpiASzy7b9vbnBGLrhvdhl1CbdU6EnZgxPNbai60szdLtl+VL76UNOt5bOoVTOz5rNWZxgGt+Gsw=="], - "react-dom": ["react-dom@19.2.5", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.5" } }, "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag=="], - - "react-reconciler": ["react-reconciler@0.32.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ=="], + "react-dom": ["react-dom@19.2.7", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.7" } }, "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ=="], - "react-router": ["react-router@7.14.1", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-5BCvFskyAAVumqhEKh/iPhLOIkfxcEUz8WqFIARCkMg8hZZzDYX9CtwxXA0e+qT8zAxmMC0x3Ckb9iMONwc5jg=="], + "react-reconciler": ["react-reconciler@0.33.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA=="], - "react-router-dom": ["react-router-dom@7.14.1", "", { "dependencies": { "react-router": "7.14.1" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-ZkrQuwwhGibjQLqH1eCdyiZyLWglPxzxdl5tgwgKEyCSGC76vmAjleGocRe3J/MLfzMUIKwaFJWpFVJhK3d2xA=="], + "react-router": ["react-router@7.18.0", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-pTTGt8J+ji1NOmYnjzT+bAJy/1zD+Jp4ziO6cL7T3ZLvXKtusO7BpFqlRXitqpcPVqllsIXFHRMt+2/k3Xn6HQ=="], - "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], - - "readable-web-to-node-stream": ["readable-web-to-node-stream@3.0.4", "", { "dependencies": { "readable-stream": "^4.7.0" } }, "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw=="], + "react-router-dom": ["react-router-dom@7.18.0", "", { "dependencies": { "react-router": "7.18.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-Fi0yY6kgtKae/Th2xibdWK0KSdYZ4B53Gyf6wRtomOKWgpNm7H7+DyfDhncdz9FKbpS+1jmDhg3F4WoGJ+yFOA=="], "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], "require-in-the-middle": ["require-in-the-middle@8.0.1", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3" } }, "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ=="], - "rolldown": ["rolldown@1.0.0-rc.15", "", { "dependencies": { "@oxc-project/types": "=0.124.0", "@rolldown/pluginutils": "1.0.0-rc.15" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.15", "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", "@rolldown/binding-darwin-x64": "1.0.0-rc.15", "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g=="], - - "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], - - "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], + "rolldown": ["rolldown@1.1.3", "", { "dependencies": { "@oxc-project/types": "=0.137.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.1.3", "@rolldown/binding-darwin-arm64": "1.1.3", "@rolldown/binding-darwin-x64": "1.1.3", "@rolldown/binding-freebsd-x64": "1.1.3", "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", "@rolldown/binding-linux-arm64-gnu": "1.1.3", "@rolldown/binding-linux-arm64-musl": "1.1.3", "@rolldown/binding-linux-ppc64-gnu": "1.1.3", "@rolldown/binding-linux-s390x-gnu": "1.1.3", "@rolldown/binding-linux-x64-gnu": "1.1.3", "@rolldown/binding-linux-x64-musl": "1.1.3", "@rolldown/binding-openharmony-arm64": "1.1.3", "@rolldown/binding-wasm32-wasi": "1.1.3", "@rolldown/binding-win32-arm64-msvc": "1.1.3", "@rolldown/binding-win32-x64-msvc": "1.1.3" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g=="], "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], - "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="], - - "simple-xml-to-json": ["simple-xml-to-json@1.2.7", "", {}, "sha512-mz9VXphOxQWX3eQ/uXCtm6upltoN0DLx8Zb5T4TFC4FHB7S9FDPGre8CfLWqPWQQH/GrQYd2AXhhVM5LDpYx6Q=="], + "shell-quote": ["shell-quote@1.9.0", "", {}, "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - "stage-js": ["stage-js@1.0.2", "", {}, "sha512-EWTRBYlg7Qv9wGUao99/PfRe3KaiQqWmgSvTOXvaWnu1Jk/q/vV8yJVu6bi/3EqDZeMVnCPAjheba6OFc5k1GQ=="], - - "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], - "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], - "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "tailwindcss": ["tailwindcss@4.3.1", "", {}, "sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q=="], - "strtok3": ["strtok3@6.3.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" } }, "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw=="], + "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], - "tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="], - - "tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="], - - "three": ["three@0.177.0", "", {}, "sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg=="], - - "tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="], - - "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], - - "token-types": ["token-types@4.2.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ=="], + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], "toml": ["toml@4.1.1", "", {}, "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "typescript": ["typescript@6.0.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ=="], + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], - "utif2": ["utif2@4.1.0", "", { "dependencies": { "pako": "^1.0.11" } }, "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w=="], + "uuid": ["uuid@14.0.1", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew=="], - "uuid": ["uuid@13.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="], - - "vite": ["vite@8.0.8", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.15", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw=="], + "vite": ["vite@8.1.0", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.15", "rolldown": "~1.1.2", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q=="], "web-tree-sitter": ["web-tree-sitter@0.25.10", "", { "peerDependencies": { "@types/emscripten": "^1.40.0" }, "optionalPeers": ["@types/emscripten"] }, "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA=="], - "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], - - "xml-parse-from-string": ["xml-parse-from-string@1.0.1", "", {}, "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g=="], + "workerd": ["workerd@1.20260901.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260901.1", "@cloudflare/workerd-darwin-arm64": "1.20260901.1", "@cloudflare/workerd-linux-64": "1.20260901.1", "@cloudflare/workerd-linux-arm64": "1.20260901.1", "@cloudflare/workerd-windows-64": "1.20260901.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-Z5PVprrPRlV70+RzgbFpzoPpjCLGtErhsQ9Ahe96pe7hJGMsmexTU4+rhUPH+2Sguf7vRAxFFsqXpkIleRFSPA=="], - "xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="], + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - "xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], + "ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], - "yaml": ["yaml@2.8.3", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="], + "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], - "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], + "@opentelemetry/configuration/yaml": ["yaml@2.8.3", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="], + + "@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], + + "@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], + + "@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], + + "@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], - "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg=="], - "@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="], + "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], - "@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="], + "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg=="], - "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="], + "@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], - "@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="], + "@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg=="], - "@opentelemetry/exporter-prometheus/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="], + "@opentelemetry/exporter-prometheus/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg=="], - "@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="], + "@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], - "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.5.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ=="], + "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], - "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.211.0", "", { "dependencies": { "@opentelemetry/core": "2.5.0", "@opentelemetry/otlp-transformer": "0.211.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-bp1+63V8WPV+bRI9EQG6E9YID1LIHYSZVbp7f+44g9tRzCq+rtw/o4fpL5PC31adcUsFiz/oN0MdLISSrZDdrg=="], + "@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], - "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.211.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.211.0", "@opentelemetry/core": "2.5.0", "@opentelemetry/resources": "2.5.0", "@opentelemetry/sdk-logs": "0.211.0", "@opentelemetry/sdk-metrics": "2.5.0", "@opentelemetry/sdk-trace-base": "2.5.0", "protobufjs": "8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-julhCJ9dXwkOg9svuuYqqjXLhVaUgyUvO2hWbTxwjvLXX2rG3VtAaB0SzxMnGTuoCZizBT7Xqqm2V7+ggrfCXA=="], + "@opentelemetry/otlp-exporter-base/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], - "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.5.0", "", { "dependencies": { "@opentelemetry/core": "2.5.0", "@opentelemetry/resources": "2.5.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ=="], + "@opentelemetry/otlp-grpc-exporter-base/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], - "@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="], + "@opentelemetry/otlp-transformer/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA=="], - "@opentelemetry/exporter-zipkin/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="], + "@opentelemetry/otlp-transformer/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], "@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="], - "@opentelemetry/resources/@opentelemetry/core": ["@opentelemetry/core@2.5.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ=="], + "@opentelemetry/otlp-transformer/@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-zf6acnScjhsaBUU22zXZ/sLWim1dfhUAbGXdMmHmNG3LfBnQ3DKsOCITb2IZwoUsNNMTogqFKBnlIPPftUgGwA=="], - "@opentelemetry/sdk-logs/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="], + "@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw=="], - "@opentelemetry/sdk-metrics/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="], + "@opentelemetry/sdk-metrics/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - "@opentelemetry/sdk-node/@opentelemetry/exporter-trace-otlp-http": ["@opentelemetry/exporter-trace-otlp-http@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-kIN8nTBMgV2hXzV/a20BCFilPZdAIMYYJGSgfMMRm/Xa+07y5hRDS2Vm12A/z8Cdu3Sq++ZvJfElokX2rkgGgw=="], + "@opentelemetry/sdk-metrics/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="], - "@opentelemetry/sdk-node/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="], + "@opentelemetry/sdk-node/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg=="], - "@opentelemetry/sdk-trace-base/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="], + "@tailwindcss/node/jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="], - "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "image-q/@types/node": ["@types/node@16.9.1", "", {}, "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g=="], + "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg=="], + + "@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg=="], + + "@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg=="], + + "@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg=="], + + "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg=="], + + "@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg=="], + + "@opentelemetry/otlp-exporter-base/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg=="], - "pixelmatch/pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="], + "@opentelemetry/otlp-grpc-exporter-base/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg=="], - "react-devtools-core/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], + "cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - "react-reconciler/scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], + "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.15", "", {}, "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g=="], + "wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.211.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-swFdZq8MCdmdR22jTVGQDhwqDzcI4M10nhjXkLr1EsIzXgZBqm4ZlmmcWsg3TSNf+3mzgOiqveXmBLZuDi2Lgg=="], + "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.211.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.211.0", "@opentelemetry/core": "2.5.0", "@opentelemetry/resources": "2.5.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-O5nPwzgg2JHzo59kpQTPUOTzFi0Nv5LxryG27QoXBciX3zWM3z83g+SNOHhiQVYRWFSxoWn1JM2TGD5iNjOwdA=="], + "yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.5.0", "", { "dependencies": { "@opentelemetry/core": "2.5.0", "@opentelemetry/resources": "2.5.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-BeJLtU+f5Gf905cJX9vXFQorAr6TAfK3SPvTFqP+scfIpDQEJfRaGJWta7sJgP+m4dNtBf9y3yvBKVAZZtJQVA=="], + "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@8.0.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-jx6+sE9h/UryaCZhsJWbJtTEy47yXoGNYI4z8ZaRncM0zBKeRqjO2JEcOUYwrYGb1WLhXM1FfMzW3annvFv0rw=="], + "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], } } diff --git a/package.json b/package.json index 5933a40..92ceeaf 100644 --- a/package.json +++ b/package.json @@ -1,101 +1,119 @@ { - "name": "@executor-js/motel", - "version": "0.2.5-executor.1", - "description": "A local OpenTelemetry ingest + TUI viewer for development, backed by SQLite. (Executor fork of @kitlangton/motel)", - "type": "module", - "license": "MIT", - "author": "Kit Langton", - "repository": { - "type": "git", - "url": "https://github.com/UsefulSoftwareCo/motel.git" - }, - "homepage": "https://github.com/kitlangton/motel#readme", - "bugs": { - "url": "https://github.com/kitlangton/motel/issues" - }, - "keywords": [ - "opentelemetry", - "otel", - "tracing", - "observability", - "tui", - "sqlite", - "effect", - "opentui" - ], - "workspaces": [ - "web" - ], - "engines": { - "bun": ">=1.1.0" - }, - "publishConfig": { - "access": "public" - }, - "files": [ - "src", - "web/dist", - "LICENSE", - "README.md", - "AGENTS.md" - ], - "bin": { - "motel": "src/motel.ts", - "motel-mcp": "src/mcp.ts" - }, - "scripts": { - "clear-motel-debug": "bun run skills/motel-debug/scripts/clear-motel-debug.ts", - "dev": "MOTEL_OTEL_ENABLED=true bun run src/motel.ts tui", - "start": "bun run src/motel.ts tui", - "daemon": "bun run src/motel.ts daemon", - "status": "bun run src/motel.ts status", - "stop": "bun run src/motel.ts stop", - "restart": "bun run src/motel.ts restart", - "server": "bun run src/motel.ts server", - "mcp": "bun run src/mcp.ts", - "test": "bun test", - "cli": "bun run src/cli.ts", - "instructions": "bun run src/cli.ts instructions", - "services": "bun run src/cli.ts services", - "logs": "bun run src/cli.ts logs", - "trace-logs": "bun run src/cli.ts trace-logs", - "span-logs": "bun run src/cli.ts span-logs", - "span": "bun run src/cli.ts span", - "trace-spans": "bun run src/cli.ts trace-spans", - "search-spans": "bun run src/cli.ts search-spans", - "trace-stats": "bun run src/cli.ts trace-stats", - "log-stats": "bun run src/cli.ts log-stats", - "web:dev": "bun run --cwd web dev", - "web:build": "bun run --cwd web build", - "story:chat": "bun run src/storybook/aiChatStory.tsx", - "bench:motel-cold-start": "bun run scripts/bench-motel-cold-start.ts", - "bench:ingest-logs": "bun run scripts/bench-ingest-logs.ts", - "bench:ingest-traces": "bun run scripts/bench-ingest-traces.ts", - "bench:search-spans": "bun run scripts/bench-search-spans.ts", - "typecheck": "tsc --noEmit", - "prepublishOnly": "bun run web:build" - }, - "devDependencies": { - "@effect/language-service": "^0.85.1", - "@types/bun": "^1.3.12", - "@types/react": "^19.2.14", - "typescript": "^6.0.2" - }, - "dependencies": { - "@effect/atom-react": "^4.0.0-beta.49", - "@effect/opentelemetry": "^4.0.0-beta.49", - "@effect/platform-bun": "^4.0.0-beta.50", - "@opentelemetry/api": "^1.9.0", - "@opentelemetry/exporter-logs-otlp-http": "^0.214.0", - "@opentelemetry/exporter-trace-otlp-http": "^0.211.0", - "@opentelemetry/sdk-logs": "^0.214.0", - "@opentelemetry/sdk-node": "^0.214.0", - "@opentelemetry/sdk-trace-base": "^2.5.0", - "@opentelemetry/sdk-trace-node": "^2.6.1", - "@opentui/core": "^0.1.99", - "@opentui/react": "^0.1.99", - "effect": "^4.0.0-beta.49", - "react": "^19.2.5", - "scheduler": "^0.27.0" - } + "name": "@executor-js/motel", + "version": "0.2.8-executor.1", + "description": "A local OpenTelemetry collector and viewer, with Bun and workerd runtimes.", + "type": "module", + "license": "MIT", + "author": "Kit Langton", + "repository": { + "type": "git", + "url": "https://github.com/UsefulSoftwareCo/motel.git" + }, + "homepage": "https://github.com/kitlangton/motel#readme", + "bugs": { + "url": "https://github.com/kitlangton/motel/issues" + }, + "keywords": [ + "opentelemetry", + "otel", + "tracing", + "observability", + "tui", + "sqlite", + "effect", + "opentui" + ], + "workspaces": [ + "web" + ], + "engines": { + "bun": ">=1.3.0" + }, + "publishConfig": { + "access": "public" + }, + "overrides": { + "@grpc/grpc-js": "1.14.4", + "protobufjs": "7.6.4", + "shell-quote": "1.9.0", + "ws": "8.21.0" + }, + "files": [ + "src", + "web/dist", + "skills/motel-debug/SKILL.md", + "skills/motel-debug/references/effect.md", + "LICENSE", + "README.md", + "AGENTS.md" + ], + "bin": { + "motel": "src/motel.ts", + "motel-mcp": "src/mcp.ts" + }, + "scripts": { + "clear-motel-debug": "bun run skills/motel-debug/scripts/clear-motel-debug.ts", + "dev": "MOTEL_OTEL_ENABLED=true bun run src/motel.ts tui", + "start": "bun run src/motel.ts tui", + "daemon": "bun run src/motel.ts daemon", + "status": "bun run src/motel.ts status", + "stop": "bun run src/motel.ts stop", + "restart": "bun run src/motel.ts restart", + "server": "bun run src/motel.ts server", + "mcp": "bun run src/mcp.ts", + "test": "bun run web:build && bun run workerd:build && bun test", + "cli": "bun run src/cli.ts", + "instructions": "bun run src/cli.ts instructions", + "services": "bun run src/cli.ts services", + "logs": "bun run src/cli.ts logs", + "trace-logs": "bun run src/cli.ts trace-logs", + "span-logs": "bun run src/cli.ts span-logs", + "span": "bun run src/cli.ts span", + "trace-spans": "bun run src/cli.ts trace-spans", + "search-spans": "bun run src/cli.ts search-spans", + "trace-stats": "bun run src/cli.ts trace-stats", + "log-stats": "bun run src/cli.ts log-stats", + "web:dev": "bun run --cwd web dev", + "web:build": "bun run --cwd web build", + "story:chat": "bun run src/storybook/aiChatStory.tsx", + "bench:motel-cold-start": "bun run scripts/bench-motel-cold-start.ts", + "bench:ingest-logs": "bun run scripts/bench-ingest-logs.ts", + "bench:ingest-traces": "bun run scripts/bench-ingest-traces.ts", + "bench:search-spans": "bun run scripts/bench-search-spans.ts", + "release:validate": "bun run scripts/validate-release.ts", + "typecheck": "tsc --noEmit", + "web:typecheck": "tsc --noEmit -p web/tsconfig.json", + "prepublishOnly": "bun run web:build", + "workerd:build": "bun run scripts/build-workerd.ts", + "workerd:serve": "workerd serve workerd/motel.capnp --experimental", + "workerd:test": "bun run web:build && bun run workerd:build && bun test src/workerd.integration.test.ts" + }, + "devDependencies": { + "@cloudflare/workers-types": "5.20260923.1", + "@effect/language-service": "^0.86.2", + "@types/bun": "^1.3.14", + "@types/react": "^19.2.17", + "esbuild": "0.28.2", + "typescript": "^6.0.3", + "workerd": "1.20260901.1" + }, + "dependencies": { + "@effect/atom-react": "4.0.0-beta.90", + "@effect/opentelemetry": "4.0.0-beta.90", + "@effect/platform-bun": "4.0.0-beta.90", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/exporter-logs-otlp-http": "^0.219.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.219.0", + "@opentelemetry/otlp-transformer": "0.214.0", + "@opentelemetry/sdk-logs": "^0.219.0", + "@opentelemetry/sdk-node": "^0.219.0", + "@opentelemetry/sdk-trace-base": "^2.8.0", + "@opentelemetry/sdk-trace-node": "^2.8.0", + "@opentui/core": "0.4.2", + "@opentui/react": "0.4.2", + "effect": "4.0.0-beta.90", + "protobufjs": "7.6.4", + "react": "^19.2.7", + "scheduler": "^0.27.0" + } } diff --git a/scripts/bench-ingest-logs.ts b/scripts/bench-ingest-logs.ts index e6b4238..7754062 100644 --- a/scripts/bench-ingest-logs.ts +++ b/scripts/bench-ingest-logs.ts @@ -103,7 +103,7 @@ const runOne = async (seed: number): Promise => { const payload = makePayload(seed) const startedAt = performance.now() const result = await loaded.storeRuntime.runPromise( - Effect.flatMap(loaded.TelemetryStore.asEffect(), (store) => store.ingestLogs(payload)).pipe( + Effect.flatMap(loaded.TelemetryStore, (store) => store.ingestLogs(payload)).pipe( Effect.provideService(References.MinimumLogLevel, "None"), ), ) diff --git a/scripts/bench-ingest-traces.ts b/scripts/bench-ingest-traces.ts index 2ab0e5d..c693bae 100644 --- a/scripts/bench-ingest-traces.ts +++ b/scripts/bench-ingest-traces.ts @@ -131,7 +131,7 @@ const runOne = async (seed: number): Promise => { const payload = makePayload(seed) const startedAt = performance.now() const result = await loaded.storeRuntime.runPromise( - Effect.flatMap(loaded.TelemetryStore.asEffect(), (store) => store.ingestTraces(payload)).pipe( + Effect.flatMap(loaded.TelemetryStore, (store) => store.ingestTraces(payload)).pipe( Effect.provideService(References.MinimumLogLevel, "None"), ), ) diff --git a/scripts/bench-search-spans.ts b/scripts/bench-search-spans.ts index 31d1c73..9e65de8 100644 --- a/scripts/bench-search-spans.ts +++ b/scripts/bench-search-spans.ts @@ -124,7 +124,7 @@ const seedStore = async ( const traceCount = Math.min(batchSize, traces - start) const payload = makeTraceBatch(start, traceCount, startedAtNanos) await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => store.ingestTraces(payload)).pipe( + Effect.flatMap(TelemetryStore, (store) => store.ingestTraces(payload)).pipe( Effect.provideService(References.MinimumLogLevel, "None"), ), ) @@ -134,7 +134,7 @@ const seedStore = async ( const runOne = async (loaded: LoadedRuntime): Promise => { const startedAt = performance.now() const result = await loaded.queryRuntime.runPromise( - Effect.flatMap(loaded.TelemetryStoreReadonly.asEffect(), (store) => + Effect.flatMap(loaded.TelemetryStoreReadonly, (store) => store.searchSpans({ serviceName: "bench-api", operation: "search.target", diff --git a/scripts/build-workerd.ts b/scripts/build-workerd.ts new file mode 100644 index 0000000..63cc617 --- /dev/null +++ b/scripts/build-workerd.ts @@ -0,0 +1,33 @@ +import { build } from "esbuild" +import { mkdir, readFile } from "node:fs/promises" +import { fileURLToPath } from "node:url" +const root = fileURLToPath(new URL("../", import.meta.url)) +const docs = { + debug: await readFile(`${root}skills/motel-debug/SKILL.md`, "utf8"), + effect: await readFile(`${root}skills/motel-debug/references/effect.md`, "utf8"), +} +await mkdir(`${root}.local/workerd-data`, { recursive: true }) +await build({ + absWorkingDir: root, + entryPoints: ["src/workerd.ts"], + outfile: "dist/workerd/motel.mjs", + bundle: true, + format: "esm", + platform: "browser", + target: "es2022", + conditions: ["workerd"], + external: ["node:*", "cloudflare:*"], + minify: true, + sourcemap: "external", + sourcesContent: false, + legalComments: "external", + plugins: [ + { + name: "motel-documents", + setup(builder) { + builder.onResolve({ filter: /^motel:documents$/ }, () => ({ path: "documents", namespace: "motel" })) + builder.onLoad({ filter: /.*/, namespace: "motel" }, () => ({ contents: `export default ${JSON.stringify(docs)}`, loader: "js" })) + }, + }, + ], +}) diff --git a/scripts/validate-release.ts b/scripts/validate-release.ts new file mode 100644 index 0000000..7f57347 --- /dev/null +++ b/scripts/validate-release.ts @@ -0,0 +1,125 @@ +import { mkdtemp, mkdir, readFile, rm } from "node:fs/promises" +import { tmpdir } from "node:os" +import path from "node:path" + +type PackResult = { + readonly filename: string + readonly files: ReadonlyArray<{ readonly path: string }> +} + +const root = path.resolve(import.meta.dir, "..") +const temp = await mkdtemp(path.join(tmpdir(), "motel-release-")) +const packDir = path.join(temp, "pack") +const consumerDir = path.join(temp, "consumer") + +const run = async ( + cmd: ReadonlyArray, + cwd: string, + options: { readonly stdin?: string; readonly env?: Record } = {}, +) => { + const process = Bun.spawn(cmd, { + cwd, + env: options.env, + stdin: options.stdin === undefined ? "ignore" : new Blob([options.stdin]), + stdout: "pipe", + stderr: "pipe", + }) + const [exitCode, stdout, stderr] = await Promise.all([ + process.exited, + new Response(process.stdout).text(), + new Response(process.stderr).text(), + ]) + if (exitCode !== 0) { + throw new Error(`${cmd.join(" ")} failed (${exitCode})\n${stdout}${stderr}`) + } + return stdout +} + +const smokeMcp = async (executable: string, cwd: string) => { + const process = Bun.spawn([executable], { + cwd, + stdin: "pipe", + stdout: "ignore", + stderr: "pipe", + }) + const exited = process.exited.then((exitCode) => ({ exitCode })) + const result = await Promise.race([exited, Bun.sleep(500).then(() => null)]) + if (result) { + const stderr = await new Response(process.stderr).text() + throw new Error(`Packed motel-mcp exited during startup (${result.exitCode})\n${stderr}`) + } + process.kill() + await process.exited +} + +try { + await mkdir(packDir) + await mkdir(consumerDir) + await run(["bun", "run", "web:build"], root) + + const packOutput = await run( + ["npm", "pack", "--json", "--pack-destination", packDir], + root, + ) + const [packed] = JSON.parse(packOutput) as PackResult[] + if (!packed) throw new Error("npm pack did not produce an artifact") + + const included = new Set(packed.files.map((file) => file.path)) + for (const required of [ + "skills/motel-debug/SKILL.md", + "skills/motel-debug/references/effect.md", + "src/motel.ts", + "src/mcp.ts", + "web/dist/index.html", + ]) { + if (!included.has(required)) throw new Error(`Packed artifact is missing ${required}`) + } + + const packageJson = JSON.parse(await readFile(path.join(root, "package.json"), "utf8")) as { + readonly name: string + readonly version: string + } + const tarball = path.join(packDir, packed.filename) + await Bun.write( + path.join(consumerDir, "package.json"), + JSON.stringify({ private: true, dependencies: { [packageJson.name]: `file:${tarball}` } }, null, 2), + ) + await run(["bun", "install", "--no-save"], consumerDir) + + const motel = path.join(consumerDir, "node_modules", ".bin", "motel") + const motelMcp = path.join(consumerDir, "node_modules", ".bin", "motel-mcp") + const help = await run([motel, "--help"], consumerDir) + if (!help.includes("motel daemon")) throw new Error("Packed motel binary did not print its help output") + const runtimeDir = path.join(temp, "runtime") + await mkdir(runtimeDir) + const runtimeEnv = { + ...process.env, + MOTEL_RUNTIME_DIR: runtimeDir, + MOTEL_OTEL_DB_PATH: path.join(runtimeDir, "telemetry.sqlite"), + } + const installedRoot = path.join(consumerDir, "node_modules", "@kitlangton", "motel") + await run([ + "bun", + "-e", + `const { Effect } = await import("effect"); const { storeRuntime } = await import(${JSON.stringify(path.join(installedRoot, "src", "runtime.ts"))}); const { TelemetryStore } = await import(${JSON.stringify(path.join(installedRoot, "src", "services", "TelemetryStore.ts"))}); await storeRuntime.runPromise(Effect.flatMap(TelemetryStore, (store) => store.listServices)); await storeRuntime.dispose()`, + ], consumerDir, { env: runtimeEnv }) + try { + const services = await run([motel, "services"], consumerDir, { env: runtimeEnv }) + if (!services.trim().startsWith("[")) throw new Error("Packed motel binary did not query services") + } finally { + await run([motel, "stop"], consumerDir, { env: runtimeEnv }).catch(() => undefined) + } + await smokeMcp(motelMcp, consumerDir) + + const reportedVersion = await run( + ["bun", "-e", `import { MOTEL_VERSION } from ${JSON.stringify(path.join(installedRoot, "src", "registry.ts"))}; console.log(MOTEL_VERSION)`], + consumerDir, + ) + if (reportedVersion.trim() !== packageJson.version) { + throw new Error(`Runtime version ${reportedVersion.trim()} does not match package version ${packageJson.version}`) + } + + console.log(`Validated ${packed.filename} from a clean consumer`) +} finally { + await rm(temp, { recursive: true, force: true }) +} diff --git a/src/cli.ts b/src/cli.ts index de59a55..a92ce67 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -13,7 +13,7 @@ const runQuiet = (effect: Effect try { switch (command) { case "services": { - const result = await runQuiet(Effect.flatMap(TelemetryStoreReadonly.asEffect(), (query) => query.listServices)) + const result = await runQuiet(Effect.flatMap(TelemetryStoreReadonly, (query) => query.listServices)) console.log(JSON.stringify(result, null, 2)) break } @@ -21,7 +21,7 @@ try { case "traces": { const service = args[0] ?? config.otel.serviceName const limit = args[1] ? Number.parseInt(args[1], 10) : config.otel.traceFetchLimit - const result = await runQuiet(Effect.flatMap(TelemetryStoreReadonly.asEffect(), (query) => query.listRecentTraces(service, { limit }))) + const result = await runQuiet(Effect.flatMap(TelemetryStoreReadonly, (query) => query.listRecentTraces(service, { limit }))) console.log(JSON.stringify(result, null, 2)) break } @@ -32,7 +32,7 @@ try { throw new Error("Usage: bun run cli trace ") } - const result = await runQuiet(Effect.flatMap(TelemetryStoreReadonly.asEffect(), (query) => query.getTrace(traceId))) + const result = await runQuiet(Effect.flatMap(TelemetryStoreReadonly, (query) => query.getTrace(traceId))) console.log(JSON.stringify(result, null, 2)) break } @@ -54,7 +54,7 @@ try { throw new Error("Usage: bun run cli trace-spans ") } - const result = await runQuiet(Effect.flatMap(TelemetryStoreReadonly.asEffect(), (query) => query.listTraceSpans(traceId))) + const result = await runQuiet(Effect.flatMap(TelemetryStoreReadonly, (query) => query.listTraceSpans(traceId))) console.log(JSON.stringify(result, null, 2)) break } @@ -67,7 +67,7 @@ try { const attributeStartIndex = operation ? 2 : 1 const attributeFilters = attributeFiltersFromArgs(args.slice(attributeStartIndex)) const result = await runQuiet( - Effect.flatMap(TelemetryStoreReadonly.asEffect(), (query) => + Effect.flatMap(TelemetryStoreReadonly, (query) => query.searchSpans({ serviceName: service, operation, @@ -86,7 +86,7 @@ try { const operation = args[1] && !isAttributeFilterToken(args[1]) ? args[1] : undefined const attributeFilters = attributeFiltersFromArgs(args.slice(operation ? 2 : 1)) const result = await runQuiet( - Effect.flatMap(TelemetryStoreReadonly.asEffect(), (query) => + Effect.flatMap(TelemetryStoreReadonly, (query) => query.searchTraces({ serviceName: service, operation, @@ -109,7 +109,7 @@ try { } const result = await runQuiet( - Effect.flatMap(TelemetryStoreReadonly.asEffect(), (query) => + Effect.flatMap(TelemetryStoreReadonly, (query) => query.traceStats({ groupBy, agg, @@ -130,7 +130,7 @@ try { case "logs": { const service = args[0] ?? config.otel.serviceName - const result = await runQuiet(Effect.flatMap(TelemetryStoreReadonly.asEffect(), (query) => query.listRecentLogs(service))) + const result = await runQuiet(Effect.flatMap(TelemetryStoreReadonly, (query) => query.listRecentLogs(service))) console.log(JSON.stringify(result, null, 2)) break } @@ -140,7 +140,7 @@ try { const body = args[1] && !isAttributeFilterToken(args[1]) ? args[1] : undefined const attributeFilters = attributeFiltersFromArgs(args.slice(body ? 2 : 1)) const result = await runQuiet( - Effect.flatMap(TelemetryStoreReadonly.asEffect(), (query) => + Effect.flatMap(TelemetryStoreReadonly, (query) => query.searchLogs({ serviceName: service, body, @@ -162,7 +162,7 @@ try { } const result = await runQuiet( - Effect.flatMap(TelemetryStoreReadonly.asEffect(), (query) => + Effect.flatMap(TelemetryStoreReadonly, (query) => query.logStats({ groupBy, agg: "count", @@ -182,7 +182,7 @@ try { throw new Error("Usage: bun run cli trace-logs ") } - const result = await runQuiet(Effect.flatMap(TelemetryStoreReadonly.asEffect(), (query) => query.listTraceLogs(traceId))) + const result = await runQuiet(Effect.flatMap(TelemetryStoreReadonly, (query) => query.listTraceLogs(traceId))) console.log(JSON.stringify(result, null, 2)) break } @@ -194,7 +194,7 @@ try { } const result = await runQuiet( - Effect.flatMap(TelemetryStoreReadonly.asEffect(), (query) => + Effect.flatMap(TelemetryStoreReadonly, (query) => query.searchLogs({ spanId, limit: config.otel.logFetchLimit, @@ -213,7 +213,7 @@ try { } const result = await runQuiet( - Effect.flatMap(TelemetryStoreReadonly.asEffect(), (query) => + Effect.flatMap(TelemetryStoreReadonly, (query) => query.listFacets({ type, field, limit: 20 }), ), ) diff --git a/src/config.ts b/src/config.ts index 959568c..5c5010f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,4 +1,4 @@ -import { motelStateDir } from "./registry.js" +import { motelStateDir } from "./statePaths.js" import * as path from "node:path" const parseBoolean = (value: string | undefined, defaultValue: boolean) => { @@ -22,10 +22,13 @@ const parsedBaseUrl = new URL(baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`) export const resolveOtelUrl = (path: string) => new URL(path.startsWith("/") ? path.slice(1) : path, parsedBaseUrl).toString() const serverPort = parsePositiveInt(process.env.MOTEL_OTEL_PORT, Number.parseInt(parsedBaseUrl.port || "80", 10)) +// Preserve whether a service was requested so the TUI can override remembered state. +export const explicitServiceName = process.env.MOTEL_OTEL_SERVICE_NAME?.trim() || null + export const config = { otel: { enabled: parseBoolean(process.env.MOTEL_OTEL_ENABLED, false), - serviceName: process.env.MOTEL_OTEL_SERVICE_NAME?.trim() || "motel-otel-tui", + serviceName: explicitServiceName ?? "motel-otel-tui", baseUrl, host: process.env.MOTEL_OTEL_HOST?.trim() || parsedBaseUrl.hostname, port: serverPort, diff --git a/src/daemon.test.ts b/src/daemon.test.ts index 8517a77..f9b654f 100644 --- a/src/daemon.test.ts +++ b/src/daemon.test.ts @@ -1,6 +1,7 @@ import { Database } from "bun:sqlite" import { afterEach, describe, expect, test } from "bun:test" import { Effect } from "effect" +import rootModule from "@opentelemetry/otlp-transformer/build/esm/generated/root.js" import * as fs from "node:fs" import * as os from "node:os" import * as path from "node:path" @@ -11,6 +12,16 @@ const repoRoot = path.resolve(import.meta.dir, "..") const randomPort = () => 29000 + Math.floor(Math.random() * 2000) +const protobufRoot = rootModule as unknown as { + readonly opentelemetry: { + readonly proto: { + readonly collector: { + readonly logs: { readonly v1: { readonly ExportLogsServiceRequest: { encode: (message: unknown) => { finish: () => Uint8Array } } } } + } + } + } +} + interface Harness { readonly runtimeDir: string readonly port: number @@ -279,6 +290,38 @@ describe("daemon manager", () => { expect(log).toContain("/api/services") }) + test("accepts OTLP protobuf log payloads", async () => { + const harness = makeHarness() + activeHarnesses.push(harness) + await Effect.runPromise(harness.manager.ensure) + const payload = protobufRoot.opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest.encode({ + resourceLogs: [{ + resource: { attributes: [{ key: "service.name", value: { stringValue: "protobuf-fixture" } }] }, + scopeLogs: [{ logRecords: [{ + timeUnixNano: String(BigInt(Date.now()) * 1_000_000n), + severityText: "INFO", + body: { stringValue: "protobuf log" }, + }] }], + }], + }).finish() + const response = await fetch(`http://127.0.0.1:${harness.port}/v1/logs`, { + method: "POST", + headers: { "content-type": "application/x-protobuf" }, + body: payload.buffer instanceof ArrayBuffer + ? payload.buffer.slice(payload.byteOffset, payload.byteOffset + payload.byteLength) + : Uint8Array.from(payload).buffer, + }) + expect(response.status).toBe(200) + await Bun.sleep(100) + const probe = new Database(harness.databasePath, { readonly: true }) + try { + const log = probe.query(`SELECT service_name, body FROM logs WHERE body = 'protobuf log'`).get() as { service_name: string; body: string } | null + expect(log).toEqual({ service_name: "protobuf-fixture", body: "protobuf log" }) + } finally { + probe.close() + } + }) + test("repeated ingest does not recursively create Motel telemetry", async () => { const harness = makeHarness() activeHarnesses.push(harness) diff --git a/src/httpApi.ts b/src/httpApi.ts index 69f24f6..a72b06f 100644 --- a/src/httpApi.ts +++ b/src/httpApi.ts @@ -16,7 +16,8 @@ const Meta = Schema.Struct({ }).annotate({ identifier: "ListMeta" }) const ServiceList = Schema.Struct({ data: Schema.Array(Schema.String) }) -const Health = Schema.Struct({ +/** Runtime identity returned by the collector health endpoint. */ +export const Health = Schema.Struct({ ok: Schema.Boolean, service: Schema.String.pipe(Schema.annotateKey({ description: "Stable identity string. Always 'motel-local-server' — used by the MCP shim to detect impostor processes on a stale port." })), databasePath: Schema.String, diff --git a/src/httpListPolicy.test.ts b/src/httpListPolicy.test.ts index b5a7c61..0d814c5 100644 --- a/src/httpListPolicy.test.ts +++ b/src/httpListPolicy.test.ts @@ -11,11 +11,16 @@ describe("HTTP list policy", () => { }, LOG_LIST, BASE_URL) expect(params.limit).toBe(500) - expect(params.lookbackMinutes).toBe(24 * 60) + expect(params.lookbackMinutes).toBe(7 * 24 * 60) expect(params.attributeFilters).toEqual({ "session.id": "abc" }) expect(params.attributeContainsFilters).toEqual({ message: "failed" }) }) + it("preserves a lookback inside the seven-day retrieval window", () => { + const params = parseListParams({ url: "/api/logs?lookback=6d" }, LOG_LIST, BASE_URL) + expect(params.lookbackMinutes).toBe(6 * 24 * 60) + }) + it("round-trips a trace cursor through page metadata", () => { const traces: readonly TraceSummaryItem[] = [ { diff --git a/src/httpListPolicy.ts b/src/httpListPolicy.ts index 3147f20..9ce074b 100644 --- a/src/httpListPolicy.ts +++ b/src/httpListPolicy.ts @@ -21,12 +21,12 @@ export interface ListParams { readonly attributeContainsFilters: Readonly> } -export const TRACE_LIST: ListBounds = { defaultLimit: 20, maxLimit: 100, defaultLookback: 60, maxLookback: 24 * 60 } -export const SPAN_LIST: ListBounds = { defaultLimit: 100, maxLimit: 500, defaultLookback: 60, maxLookback: 24 * 60 } -export const LOG_LIST: ListBounds = { defaultLimit: 100, maxLimit: 500, defaultLookback: 60, maxLookback: 24 * 60 } -export const AI_LIST: ListBounds = { defaultLimit: 20, maxLimit: 500, defaultLookback: 60, maxLookback: 24 * 60 } -export const TRACE_STATS: ListBounds = { defaultLimit: 20, maxLimit: 100, defaultLookback: 60, maxLookback: 24 * 60 } -export const LOG_STATS: ListBounds = { defaultLimit: 20, maxLimit: 500, defaultLookback: 60, maxLookback: 24 * 60 } +export const TRACE_LIST: ListBounds = { defaultLimit: 20, maxLimit: 100, defaultLookback: 60, maxLookback: 7 * 24 * 60 } +export const SPAN_LIST: ListBounds = { defaultLimit: 100, maxLimit: 500, defaultLookback: 60, maxLookback: 7 * 24 * 60 } +export const LOG_LIST: ListBounds = { defaultLimit: 100, maxLimit: 500, defaultLookback: 60, maxLookback: 7 * 24 * 60 } +export const AI_LIST: ListBounds = { defaultLimit: 20, maxLimit: 500, defaultLookback: 60, maxLookback: 7 * 24 * 60 } +export const TRACE_STATS: ListBounds = { defaultLimit: 20, maxLimit: 100, defaultLookback: 60, maxLookback: 7 * 24 * 60 } +export const LOG_STATS: ListBounds = { defaultLimit: 20, maxLimit: 500, defaultLookback: 60, maxLookback: 7 * 24 * 60 } export const requestUrl = (request: { readonly url: string }, baseUrl: string) => new URL(request.url, baseUrl) diff --git a/src/httpServer.ts b/src/httpServer.ts new file mode 100644 index 0000000..3ad69b8 --- /dev/null +++ b/src/httpServer.ts @@ -0,0 +1,458 @@ +import { Effect, Layer } from "effect" +import { config } from "./config.js" +import { HttpApiBuilder, HttpApiScalar } from "effect/unstable/httpapi" +import * as HttpMiddleware from "effect/unstable/http/HttpMiddleware" +import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse" +import { MotelHttpApi, type Health } from "./httpApi.js" +import { AI_LIST, LOG_LIST, LOG_STATS, SPAN_LIST, TRACE_LIST, TRACE_STATS, listMeta, logCursorArgs, paginateLogs, paginateSummaries, parseLimit, parseListParams as decodeListParams, parseLookbackMinutes, requestUrl as decodeRequestUrl, traceCursorArgs, type ListBounds, type ListParams } from "./httpListPolicy.js" +import { AsyncIngest } from "./services/AsyncIngest.js" +import { TelemetryStoreReadonly } from "./services/TelemetryStore.js" +import type { LogItem, TraceItem } from "./domain.js" +import { lifecycleLabel } from "./ui/format.js" +import { decodeProtobufLogs, decodeProtobufTraces } from "./otlpProtobuf.js" +import type { OtlpLogExportRequest, OtlpTraceExportRequest } from "./otlp.js" + +/** Shared OTLP and query routes. Runtime adapters own server identity and static documents. */ +export const motelApi = (options: { readonly health: () => typeof Health.Type; readonly docs: Readonly> }) => { +const requestUrl = (request: { readonly url: string }) => decodeRequestUrl(request, config.otel.baseUrl) +const parseListParams = (request: { readonly url: string }, bounds: ListBounds) => decodeListParams(request, bounds, config.otel.baseUrl) + +const jsonResponse = (value: unknown, status = 200) => HttpServerResponse.jsonUnsafe(value, { status }) +const textResponse = (value: string) => HttpServerResponse.text(value) +const htmlResponse = (value: string) => HttpServerResponse.html(value) +const notFoundResponse = (message = "Not found") => jsonResponse({ error: message }, 404) +// Query handlers resolve against the readonly store identifier so they +// don't contend with the writer connection that owns ingest/retention. +const withRead = (f: (store: TelemetryStoreReadonly["Service"]) => Effect.Effect) => Effect.flatMap(TelemetryStoreReadonly, f) +// Response-building helpers are generic in R so a handler can depend +// on AsyncIngest (worker-RPC path) or TelemetryStoreReadonly (query +// path) without forcing every handler onto the same service surface. +const respondJson = (effect: Effect.Effect) => + Effect.match(effect, { + onFailure: (error) => jsonResponse({ error: error instanceof Error ? error.message : String(error) }, 500), + onSuccess: (value) => jsonResponse(value), + }) +const respondRaw = (effect: Effect.Effect, unknown, R>) => + Effect.match(effect, { + onFailure: (error) => jsonResponse({ error: error instanceof Error ? error.message : String(error) }, 500), + onSuccess: (value) => value, + }) + +const readOtlpBody = ( + request: { + readonly json: Effect.Effect + readonly arrayBuffer: Effect.Effect + readonly headers: Readonly> + }, + decodeProtobuf: (bytes: Uint8Array) => T, +): Effect.Effect => { + const contentType = (request.headers["content-type"] ?? "").toLowerCase() + if (contentType.includes("application/x-protobuf") || contentType.includes("application/protobuf")) { + return Effect.map(request.arrayBuffer, (buffer) => decodeProtobuf(new Uint8Array(buffer))) + } + return Effect.map(request.json, (payload) => payload as T) +} + +// Log page loader: takes the parsed list params + any resource-specific +// filter values (service, severity, traceId, spanId, body), runs the +// store query with limit+1 to detect a next page, and shapes the +// paginated response. +const loadLogsPage = ( + params: ListParams, + filters: { readonly serviceName?: string | null; readonly severity?: string | null; readonly traceId?: string | null; readonly spanId?: string | null; readonly body?: string | null }, +) => + Effect.map( + withRead((store) => store.searchLogs({ + ...filters, + ...logCursorArgs(params.cursor), + attributeFilters: params.attributeFilters, + attributeContainsFilters: params.attributeContainsFilters, + lookbackMinutes: params.lookbackMinutes, + limit: params.limit + 1, + })), + (logs) => paginateLogs(logs, params), + ) + +const handleLogSearch = (request: { readonly url: string }) => + respondRaw(Effect.gen(function*() { + const params = parseListParams(request, LOG_LIST) + return jsonResponse(yield* loadLogsPage(params, { + serviceName: params.url.searchParams.get("service"), + severity: params.url.searchParams.get("severity"), + traceId: params.url.searchParams.get("traceId"), + spanId: params.url.searchParams.get("spanId"), + body: params.url.searchParams.get("body"), + })) + })) + +const escapeHtml = (value: string) => + value + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + +const renderTracePage = (trace: TraceItem, logs: readonly LogItem[]) => { + const logCountsBySpan = new Map() + for (const log of logs) { + if (!log.spanId) continue + logCountsBySpan.set(log.spanId, (logCountsBySpan.get(log.spanId) ?? 0) + 1) + } + + const spansHtml = trace.spans + .map((span) => { + const indent = Math.min(span.depth * 20, 120) + const count = logCountsBySpan.get(span.spanId) ?? 0 + return ` +${escapeHtml(span.operationName)} +${escapeHtml(span.serviceName)} +${lifecycleLabel(span)} +${escapeHtml(span.status)} +${span.durationMs.toFixed(2)}ms +${count} +` + }) + .join("\n") + + const logsHtml = logs + .slice(0, 80) + .map( + (log) => ` +${escapeHtml(log.timestamp.toISOString())} +${escapeHtml(log.severityText)} +${escapeHtml(log.scopeName ?? log.serviceName)} +
${escapeHtml(log.body)}
+`, + ) + .join("\n") + + return ` + + + + +${escapeHtml(trace.rootOperationName)} + + + +

${escapeHtml(trace.rootOperationName)}

+

${escapeHtml(trace.serviceName)} · ${lifecycleLabel(trace)} · ${trace.durationMs.toFixed(2)}ms · ${trace.spanCount} spans · ${logs.length} logs

+

${escapeHtml(trace.traceId)}

+

Spans

+ + +${spansHtml} +
OperationServiceStateStatusDurationLogs
+

Logs

+ + +${logsHtml} +
TimeLevelScopeBody
+ +` +} + +const TelemetryGroupLive = HttpApiBuilder.group( + MotelHttpApi, + "telemetry", + (handlers) => + handlers + .handleRaw("root", () => + Effect.succeed(textResponse("motel local telemetry server\n\nPOST /v1/traces\nPOST /v1/logs\nGET /api/services\nGET /api/traces\nGET /api/traces/search\nGET /api/traces/stats\nGET /api/traces/\nGET /api/traces//spans\nGET /api/traces//logs\nGET /api/spans/search\nGET /api/spans/\nGET /api/spans//logs\nGET /api/logs\nGET /api/logs/search\nGET /api/logs/stats\nGET /api/ai/calls\nGET /api/ai/calls/\nGET /api/ai/stats\nGET /api/facets?type=logs&field=severity\nGET /api/docs\nGET /api/docs/\nGET /openapi.json\nGET /docs\nGET /trace/\n")), + ) + .handle("health", () => + HttpMiddleware.withLoggerDisabled(Effect.succeed(options.health())), + ) + // OTLP ingest is routed to the worker thread via AsyncIngest + // so the main event loop stays free during heavy SQLite writes. + // Read queries use a separate query worker so synchronous SQLite + // work cannot block the HTTP event loop. + .handleRaw("ingestTraces", ({ request }) => + HttpMiddleware.withLoggerDisabled(respondRaw( + Effect.flatMap( + readOtlpBody(request, decodeProtobufTraces), + (payload) => Effect.map( + Effect.flatMap(AsyncIngest, (ingest) => ingest.ingestTraces({ payload })), + (result) => jsonResponse(result), + ), + ), + )), + ) + .handleRaw("ingestLogs", ({ request }) => + HttpMiddleware.withLoggerDisabled(respondRaw( + Effect.flatMap( + readOtlpBody(request, decodeProtobufLogs), + (payload) => Effect.map( + Effect.flatMap(AsyncIngest, (ingest) => ingest.ingestLogs({ payload })), + (result) => jsonResponse(result), + ), + ), + )), + ) + .handleRaw("services", () => respondJson(Effect.map(withRead((store) => store.listServices), (data) => ({ data })))) + .handleRaw("traces", ({ request }) => + respondRaw(Effect.gen(function*() { + const params = parseListParams(request, TRACE_LIST) + const data = yield* withRead((store) => store.listTraceSummaries(params.url.searchParams.get("service"), { + limit: params.limit + 1, + lookbackMinutes: params.lookbackMinutes, + ...traceCursorArgs(params.cursor), + })) + return jsonResponse(paginateSummaries(data, params)) + })), + ) + .handleRaw("searchTraces", ({ request }) => + respondRaw(Effect.gen(function*() { + const params = parseListParams(request, TRACE_LIST) + const data = yield* withRead((store) => + store.searchTraceSummaries({ + serviceName: params.url.searchParams.get("service"), + operation: params.url.searchParams.get("operation"), + status: (params.url.searchParams.get("status") as "ok" | "error" | null) ?? null, + minDurationMs: params.url.searchParams.get("minDurationMs") ? Number.parseFloat(params.url.searchParams.get("minDurationMs") ?? "") : null, + attributeFilters: params.attributeFilters, + aiText: params.url.searchParams.get("aiText"), + limit: params.limit + 1, + lookbackMinutes: params.lookbackMinutes, + ...traceCursorArgs(params.cursor), + }), + ) + return jsonResponse(paginateSummaries(data, params)) + })), + ) + .handleRaw("traceStats", ({ request }) => + respondRaw(Effect.gen(function*() { + const params = parseListParams(request, TRACE_STATS) + const groupBy = params.url.searchParams.get("groupBy") + const agg = params.url.searchParams.get("agg") + if (!groupBy || (agg !== "count" && agg !== "avg_duration" && agg !== "p95_duration" && agg !== "error_rate")) { + return jsonResponse({ error: "Expected groupBy and agg=count|avg_duration|p95_duration|error_rate" }, 400) + } + const data = yield* withRead((store) => + store.traceStats({ + groupBy, + agg, + serviceName: params.url.searchParams.get("service"), + operation: params.url.searchParams.get("operation"), + status: (params.url.searchParams.get("status") as "ok" | "error" | null) ?? null, + minDurationMs: params.url.searchParams.get("minDurationMs") ? Number.parseFloat(params.url.searchParams.get("minDurationMs") ?? "") : null, + attributeFilters: params.attributeFilters, + limit: params.limit, + lookbackMinutes: params.lookbackMinutes, + }), + ) + return jsonResponse({ data }) + })), + ) + .handleRaw("searchSpans", ({ request }) => + respondRaw(Effect.gen(function*() { + const params = parseListParams(request, SPAN_LIST) + const data = yield* withRead((store) => + store.searchSpans({ + serviceName: params.url.searchParams.get("service"), + traceId: params.url.searchParams.get("traceId"), + operation: params.url.searchParams.get("operation"), + parentOperation: params.url.searchParams.get("parentOperation"), + status: (params.url.searchParams.get("status") as "ok" | "error" | null) ?? null, + attributeFilters: params.attributeFilters, + attributeContainsFilters: params.attributeContainsFilters, + limit: params.limit + 1, + lookbackMinutes: params.lookbackMinutes, + }), + ) + const truncated = data.length > params.limit + const page = truncated ? data.slice(0, params.limit) : data + return jsonResponse({ + data: page, + meta: listMeta({ + limit: params.limit, + lookbackMinutes: params.lookbackMinutes, + returned: page.length, + truncated, + nextCursor: null, + }), + }) + })), + ) + .handleRaw("traceLogs", ({ params: route, request }) => + respondRaw(Effect.gen(function*() { + const params = parseListParams(request, LOG_LIST) + return jsonResponse(yield* loadLogsPage(params, { traceId: route.traceId })) + })), + ) + .handleRaw("traceSpans", ({ params }) => + respondJson(Effect.map(withRead((store) => store.listTraceSpans(params.traceId)), (data) => ({ data }))), + ) + .handleRaw("spanLogs", ({ params: route, request }) => + respondRaw(Effect.gen(function*() { + const params = parseListParams(request, LOG_LIST) + return jsonResponse(yield* loadLogsPage(params, { spanId: route.spanId })) + })), + ) + .handleRaw("span", ({ params }) => + respondRaw( + Effect.flatMap(withRead((store) => store.getSpan(params.spanId)), (data) => + Effect.succeed(data ? jsonResponse({ data }) : notFoundResponse("Span not found")), + ), + ), + ) + .handleRaw("trace", ({ params }) => + respondRaw( + Effect.flatMap(withRead((store) => store.getTrace(params.traceId)), (data) => + Effect.succeed(data ? jsonResponse({ data }) : notFoundResponse("Trace not found")), + ), + ), + ) + .handleRaw("logs", ({ request }) => handleLogSearch(request)) + .handleRaw("searchLogs", ({ request }) => handleLogSearch(request)) + .handleRaw("logStats", ({ request }) => + respondRaw(Effect.gen(function*() { + const params = parseListParams(request, LOG_STATS) + const groupBy = params.url.searchParams.get("groupBy") + const agg = params.url.searchParams.get("agg") + if (!groupBy || agg !== "count") { + return jsonResponse({ error: "Expected groupBy and agg=count" }, 400) + } + const data = yield* withRead((store) => + store.logStats({ + groupBy, + agg: "count", + serviceName: params.url.searchParams.get("service"), + traceId: params.url.searchParams.get("traceId"), + spanId: params.url.searchParams.get("spanId"), + body: params.url.searchParams.get("body"), + attributeFilters: params.attributeFilters, + limit: params.limit, + lookbackMinutes: params.lookbackMinutes, + }), + ) + return jsonResponse({ data }) + })), + ) + .handle("docs", () => + Effect.succeed({ + docs: [ + { name: "debug", title: "Motel Debug Workflow", path: "/api/docs/debug" }, + { name: "effect", title: "Effect Instrumentation Guide", path: "/api/docs/effect" }, + ], + }), + ) + .handleRaw("doc", ({ params }) => + respondRaw(Effect.gen(function*() { + const doc = options.docs[params.name] + return doc === undefined ? notFoundResponse(`Unknown doc: ${params.name}`) : textResponse(doc) + })), + ) + .handleRaw("facets", ({ request }) => + respondRaw(Effect.gen(function*() { + const url = requestUrl(request) + const type = url.searchParams.get("type") + const field = url.searchParams.get("field") + if ((type !== "traces" && type !== "logs") || !field) { + return jsonResponse({ error: "Expected type=traces|logs and field=" }, 400) + } + const data = yield* withRead((store) => + store.listFacets({ + type, + field, + serviceName: url.searchParams.get("service"), + key: url.searchParams.get("key"), + lookbackMinutes: parseLookbackMinutes(url.searchParams.get("lookback"), config.otel.traceLookbackMinutes), + limit: parseLimit(url.searchParams.get("limit"), 20), + }), + ) + return jsonResponse({ data }) + })), + ) + .handleRaw("aiCalls", ({ request }) => + respondRaw(Effect.gen(function*() { + const params = parseListParams(request, AI_LIST) + const data = yield* withRead((store) => + store.searchAiCalls({ + service: params.url.searchParams.get("service"), + traceId: params.url.searchParams.get("traceId"), + sessionId: params.url.searchParams.get("sessionId"), + functionId: params.url.searchParams.get("functionId"), + provider: params.url.searchParams.get("provider"), + model: params.url.searchParams.get("model"), + operation: params.url.searchParams.get("operation"), + status: (params.url.searchParams.get("status") as "ok" | "error" | null) ?? null, + minDurationMs: params.url.searchParams.get("minDurationMs") ? Number(params.url.searchParams.get("minDurationMs")) : null, + text: params.url.searchParams.get("text"), + lookbackMinutes: params.lookbackMinutes, + limit: params.limit, + }), + ) + return jsonResponse({ + data, + meta: listMeta({ limit: params.limit, lookbackMinutes: params.lookbackMinutes, returned: data.length, truncated: false, nextCursor: null }), + }) + })), + ) + .handleRaw("aiCall", ({ params }) => + respondRaw(Effect.gen(function*() { + const data = yield* withRead((store) => store.getAiCall(params.spanId)) + if (!data) return notFoundResponse("AI call not found") + return jsonResponse({ data }) + })), + ) + .handleRaw("aiStats", ({ request }) => + respondRaw(Effect.gen(function*() { + const params = parseListParams(request, AI_LIST) + const groupBy = params.url.searchParams.get("groupBy") as "provider" | "model" | "functionId" | "sessionId" | "status" | null + const agg = params.url.searchParams.get("agg") as "count" | "avg_duration" | "p95_duration" | "total_input_tokens" | "total_output_tokens" | null + if (!groupBy || !agg) { + return jsonResponse({ error: "Expected groupBy and agg parameters" }, 400) + } + const data = yield* withRead((store) => + store.aiCallStats({ + groupBy, + agg, + service: params.url.searchParams.get("service"), + traceId: params.url.searchParams.get("traceId"), + sessionId: params.url.searchParams.get("sessionId"), + functionId: params.url.searchParams.get("functionId"), + provider: params.url.searchParams.get("provider"), + model: params.url.searchParams.get("model"), + operation: params.url.searchParams.get("operation"), + status: (params.url.searchParams.get("status") as "ok" | "error" | null) ?? null, + minDurationMs: params.url.searchParams.get("minDurationMs") ? Number(params.url.searchParams.get("minDurationMs")) : null, + lookbackMinutes: params.lookbackMinutes, + limit: params.limit, + }), + ) + return jsonResponse({ data }) + })), + ) + .handleRaw("tracePage", ({ params }) => + respondRaw( + Effect.flatMap(withRead((store) => store.getTrace(params.traceId)), (trace) => + trace + ? Effect.map(withRead((store) => store.listTraceLogs(params.traceId)), (logs) => htmlResponse(renderTracePage(trace, logs))) + : Effect.succeed(notFoundResponse("Trace not found")), + ), + ), + ), +) + +// --------------------------------------------------------------------------- +// App layer: HTTP router + static SPA + telemetry store +// --------------------------------------------------------------------------- + +// API routes come from the Effect HttpApi definition. Everything under +// /api/*, /v1/*, /openapi.json, /docs is handled here. +const ApiLayer = HttpApiBuilder.layer(MotelHttpApi, { openapiPath: "/openapi.json" }).pipe( + Layer.provide(TelemetryGroupLive), + Layer.provide(HttpApiScalar.layer(MotelHttpApi, { scalar: { forceDarkModeState: "dark", showOperationId: true } })), +) + + +return ApiLayer +} diff --git a/src/localServer.ts b/src/localServer.ts index f1bc10b..1c28bfc 100644 --- a/src/localServer.ts +++ b/src/localServer.ts @@ -1,34 +1,15 @@ import path from "node:path" import { Effect, FileSystem, Layer } from "effect" import { config } from "./config.js" -import { HttpApiBuilder, HttpApiScalar } from "effect/unstable/httpapi" import * as HttpMiddleware from "effect/unstable/http/HttpMiddleware" import * as HttpRouter from "effect/unstable/http/HttpRouter" -import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse" import * as HttpStaticServer from "effect/unstable/http/HttpStaticServer" import * as BunHttpServer from "@effect/platform-bun/BunHttpServer" -import { MotelHttpApi } from "./httpApi.js" -import { AI_LIST, LOG_LIST, LOG_STATS, SPAN_LIST, TRACE_LIST, TRACE_STATS, listMeta, logCursorArgs, paginateLogs, paginateSummaries, parseLimit, parseListParams as decodeListParams, parseLookbackMinutes, requestUrl as decodeRequestUrl, traceCursorArgs, type ListBounds, type ListParams } from "./httpListPolicy.js" import { MOTEL_SERVICE_ID, MOTEL_VERSION, processIdentity, removeRegistryEntry, writeRegistryEntry } from "./registry.js" -import { AsyncIngest, AsyncIngestLive } from "./services/AsyncIngest.js" -import { TelemetryStoreReadonly } from "./services/TelemetryStore.js" +import { AsyncIngestLive } from "./services/AsyncIngestBun.js" import { TelemetryQueryLive } from "./services/TelemetryQuery.js" -import type { LogItem, TraceItem } from "./domain.js" -import { lifecycleLabel } from "./ui/format.js" - -// Set by the RegistryLayer acquisition once the Bun socket has bound. -// Both /api/health and the registry entry read from here so they agree -// on a single server-start timestamp, and the value reflects actual -// listen time rather than module-evaluation time. -let serverStartedAt: string = new Date().toISOString() - -const requestUrl = (request: { readonly url: string }) => decodeRequestUrl(request, config.otel.baseUrl) -const parseListParams = (request: { readonly url: string }, bounds: ListBounds) => decodeListParams(request, bounds, config.otel.baseUrl) - -const jsonResponse = (value: unknown, status = 200) => HttpServerResponse.jsonUnsafe(value, { status }) -const textResponse = (value: string) => HttpServerResponse.text(value) -const htmlResponse = (value: string) => HttpServerResponse.html(value) -const notFoundResponse = (message = "Not found") => jsonResponse({ error: message }, 404) +import { motelApi } from "./httpServer.js" +let serverStartedAt = new Date().toISOString() const healthPayload = () => ({ ok: true, service: MOTEL_SERVICE_ID, @@ -40,430 +21,14 @@ const healthPayload = () => ({ version: MOTEL_VERSION, instanceId: process.env.MOTEL_DAEMON_INSTANCE_ID?.trim(), }) -// Query handlers resolve against the readonly store identifier so they -// don't contend with the writer connection that owns ingest/retention. -const withRead =
(f: (store: TelemetryStoreReadonly["Service"]) => Effect.Effect) => Effect.flatMap(TelemetryStoreReadonly.asEffect(), f) -// Response-building helpers are generic in R so a handler can depend -// on AsyncIngest (worker-RPC path) or TelemetryStoreReadonly (query -// path) without forcing every handler onto the same service surface. -const respondJson = (effect: Effect.Effect) => - Effect.match(effect, { - onFailure: (error) => jsonResponse({ error: error instanceof Error ? error.message : String(error) }, 500), - onSuccess: (value) => jsonResponse(value), - }) -const respondRaw = (effect: Effect.Effect, unknown, R>) => - Effect.match(effect, { - onFailure: (error) => jsonResponse({ error: error instanceof Error ? error.message : String(error) }, 500), - onSuccess: (value) => value, - }) - -// Log page loader: takes the parsed list params + any resource-specific -// filter values (service, severity, traceId, spanId, body), runs the -// store query with limit+1 to detect a next page, and shapes the -// paginated response. -const loadLogsPage = ( - params: ListParams, - filters: { readonly serviceName?: string | null; readonly severity?: string | null; readonly traceId?: string | null; readonly spanId?: string | null; readonly body?: string | null }, -) => - Effect.map( - withRead((store) => store.searchLogs({ - ...filters, - ...logCursorArgs(params.cursor), - attributeFilters: params.attributeFilters, - attributeContainsFilters: params.attributeContainsFilters, - lookbackMinutes: params.lookbackMinutes, - limit: params.limit + 1, - })), - (logs) => paginateLogs(logs, params), - ) - -const handleLogSearch = (request: { readonly url: string }) => - respondRaw(Effect.gen(function*() { - const params = parseListParams(request, LOG_LIST) - return jsonResponse(yield* loadLogsPage(params, { - serviceName: params.url.searchParams.get("service"), - severity: params.url.searchParams.get("severity"), - traceId: params.url.searchParams.get("traceId"), - spanId: params.url.searchParams.get("spanId"), - body: params.url.searchParams.get("body"), - })) - })) - -const escapeHtml = (value: string) => - value - .replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">") - .replaceAll('"', """) - -const renderTracePage = (trace: TraceItem, logs: readonly LogItem[]) => { - const logCountsBySpan = new Map() - for (const log of logs) { - if (!log.spanId) continue - logCountsBySpan.set(log.spanId, (logCountsBySpan.get(log.spanId) ?? 0) + 1) - } - - const spansHtml = trace.spans - .map((span) => { - const indent = Math.min(span.depth * 20, 120) - const count = logCountsBySpan.get(span.spanId) ?? 0 - return ` -${escapeHtml(span.operationName)} -${escapeHtml(span.serviceName)} -${lifecycleLabel(span)} -${escapeHtml(span.status)} -${span.durationMs.toFixed(2)}ms -${count} -` - }) - .join("\n") - - const logsHtml = logs - .slice(0, 80) - .map( - (log) => ` -${escapeHtml(log.timestamp.toISOString())} -${escapeHtml(log.severityText)} -${escapeHtml(log.scopeName ?? log.serviceName)} -
${escapeHtml(log.body)}
-`, - ) - .join("\n") - - return ` - - - - -${escapeHtml(trace.rootOperationName)} - - - -

${escapeHtml(trace.rootOperationName)}

-

${escapeHtml(trace.serviceName)} · ${lifecycleLabel(trace)} · ${trace.durationMs.toFixed(2)}ms · ${trace.spanCount} spans · ${logs.length} logs

-

${escapeHtml(trace.traceId)}

-

Spans

- - -${spansHtml} -
OperationServiceStateStatusDurationLogs
-

Logs

- - -${logsHtml} -
TimeLevelScopeBody
- -` -} - -const TelemetryGroupLive = HttpApiBuilder.group( - MotelHttpApi, - "telemetry", - (handlers) => - handlers - .handleRaw("root", () => - Effect.succeed(textResponse("motel local telemetry server\n\nPOST /v1/traces\nPOST /v1/logs\nGET /api/services\nGET /api/traces\nGET /api/traces/search\nGET /api/traces/stats\nGET /api/traces/\nGET /api/traces//spans\nGET /api/traces//logs\nGET /api/spans/search\nGET /api/spans/\nGET /api/spans//logs\nGET /api/logs\nGET /api/logs/search\nGET /api/logs/stats\nGET /api/ai/calls\nGET /api/ai/calls/\nGET /api/ai/stats\nGET /api/facets?type=logs&field=severity\nGET /api/docs\nGET /api/docs/\nGET /openapi.json\nGET /docs\nGET /trace/\n")), - ) - .handle("health", () => - HttpMiddleware.withLoggerDisabled(Effect.succeed(healthPayload())), - ) - // OTLP ingest is routed to the worker thread via AsyncIngest - // so the main event loop stays free during heavy SQLite writes. - // Read queries use a separate query worker so synchronous SQLite - // work cannot block the HTTP event loop. - .handleRaw("ingestTraces", ({ request }) => - HttpMiddleware.withLoggerDisabled(respondRaw( - Effect.flatMap(request.json, (payload) => - Effect.map( - Effect.flatMap(AsyncIngest.asEffect(), (ingest) => ingest.ingestTraces({ payload })), - (result) => jsonResponse(result), - ), - ), - )), - ) - .handleRaw("ingestLogs", ({ request }) => - HttpMiddleware.withLoggerDisabled(respondRaw( - Effect.flatMap(request.json, (payload) => - Effect.map( - Effect.flatMap(AsyncIngest.asEffect(), (ingest) => ingest.ingestLogs({ payload })), - (result) => jsonResponse(result), - ), - ), - )), - ) - .handleRaw("services", () => respondJson(Effect.map(withRead((store) => store.listServices), (data) => ({ data })))) - .handleRaw("traces", ({ request }) => - respondRaw(Effect.gen(function*() { - const params = parseListParams(request, TRACE_LIST) - const data = yield* withRead((store) => store.listTraceSummaries(params.url.searchParams.get("service"), { - limit: params.limit + 1, - lookbackMinutes: params.lookbackMinutes, - ...traceCursorArgs(params.cursor), - })) - return jsonResponse(paginateSummaries(data, params)) - })), - ) - .handleRaw("searchTraces", ({ request }) => - respondRaw(Effect.gen(function*() { - const params = parseListParams(request, TRACE_LIST) - const data = yield* withRead((store) => - store.searchTraceSummaries({ - serviceName: params.url.searchParams.get("service"), - operation: params.url.searchParams.get("operation"), - status: (params.url.searchParams.get("status") as "ok" | "error" | null) ?? null, - minDurationMs: params.url.searchParams.get("minDurationMs") ? Number.parseFloat(params.url.searchParams.get("minDurationMs") ?? "") : null, - attributeFilters: params.attributeFilters, - aiText: params.url.searchParams.get("aiText"), - limit: params.limit + 1, - lookbackMinutes: params.lookbackMinutes, - ...traceCursorArgs(params.cursor), - }), - ) - return jsonResponse(paginateSummaries(data, params)) - })), - ) - .handleRaw("traceStats", ({ request }) => - respondRaw(Effect.gen(function*() { - const params = parseListParams(request, TRACE_STATS) - const groupBy = params.url.searchParams.get("groupBy") - const agg = params.url.searchParams.get("agg") - if (!groupBy || (agg !== "count" && agg !== "avg_duration" && agg !== "p95_duration" && agg !== "error_rate")) { - return jsonResponse({ error: "Expected groupBy and agg=count|avg_duration|p95_duration|error_rate" }, 400) - } - const data = yield* withRead((store) => - store.traceStats({ - groupBy, - agg, - serviceName: params.url.searchParams.get("service"), - operation: params.url.searchParams.get("operation"), - status: (params.url.searchParams.get("status") as "ok" | "error" | null) ?? null, - minDurationMs: params.url.searchParams.get("minDurationMs") ? Number.parseFloat(params.url.searchParams.get("minDurationMs") ?? "") : null, - attributeFilters: params.attributeFilters, - limit: params.limit, - lookbackMinutes: params.lookbackMinutes, - }), - ) - return jsonResponse({ data }) - })), - ) - .handleRaw("searchSpans", ({ request }) => - respondRaw(Effect.gen(function*() { - const params = parseListParams(request, SPAN_LIST) - const data = yield* withRead((store) => - store.searchSpans({ - serviceName: params.url.searchParams.get("service"), - traceId: params.url.searchParams.get("traceId"), - operation: params.url.searchParams.get("operation"), - parentOperation: params.url.searchParams.get("parentOperation"), - status: (params.url.searchParams.get("status") as "ok" | "error" | null) ?? null, - attributeFilters: params.attributeFilters, - attributeContainsFilters: params.attributeContainsFilters, - limit: params.limit + 1, - lookbackMinutes: params.lookbackMinutes, - }), - ) - const truncated = data.length > params.limit - const page = truncated ? data.slice(0, params.limit) : data - return jsonResponse({ - data: page, - meta: listMeta({ - limit: params.limit, - lookbackMinutes: params.lookbackMinutes, - returned: page.length, - truncated, - nextCursor: null, - }), - }) - })), - ) - .handleRaw("traceLogs", ({ params: route, request }) => - respondRaw(Effect.gen(function*() { - const params = parseListParams(request, LOG_LIST) - return jsonResponse(yield* loadLogsPage(params, { traceId: route.traceId })) - })), - ) - .handleRaw("traceSpans", ({ params }) => - respondJson(Effect.map(withRead((store) => store.listTraceSpans(params.traceId)), (data) => ({ data }))), - ) - .handleRaw("spanLogs", ({ params: route, request }) => - respondRaw(Effect.gen(function*() { - const params = parseListParams(request, LOG_LIST) - return jsonResponse(yield* loadLogsPage(params, { spanId: route.spanId })) - })), - ) - .handleRaw("span", ({ params }) => - respondRaw( - Effect.flatMap(withRead((store) => store.getSpan(params.spanId)), (data) => - Effect.succeed(data ? jsonResponse({ data }) : notFoundResponse("Span not found")), - ), - ), - ) - .handleRaw("trace", ({ params }) => - respondRaw( - Effect.flatMap(withRead((store) => store.getTrace(params.traceId)), (data) => - Effect.succeed(data ? jsonResponse({ data }) : notFoundResponse("Trace not found")), - ), - ), - ) - .handleRaw("logs", ({ request }) => handleLogSearch(request)) - .handleRaw("searchLogs", ({ request }) => handleLogSearch(request)) - .handleRaw("logStats", ({ request }) => - respondRaw(Effect.gen(function*() { - const params = parseListParams(request, LOG_STATS) - const groupBy = params.url.searchParams.get("groupBy") - const agg = params.url.searchParams.get("agg") - if (!groupBy || agg !== "count") { - return jsonResponse({ error: "Expected groupBy and agg=count" }, 400) - } - const data = yield* withRead((store) => - store.logStats({ - groupBy, - agg: "count", - serviceName: params.url.searchParams.get("service"), - traceId: params.url.searchParams.get("traceId"), - spanId: params.url.searchParams.get("spanId"), - body: params.url.searchParams.get("body"), - attributeFilters: params.attributeFilters, - limit: params.limit, - lookbackMinutes: params.lookbackMinutes, - }), - ) - return jsonResponse({ data }) - })), - ) - .handle("docs", () => - Effect.succeed({ - docs: [ - { name: "debug", title: "Motel Debug Workflow", path: "/api/docs/debug" }, - { name: "effect", title: "Effect Instrumentation Guide", path: "/api/docs/effect" }, - ], - }), - ) - .handleRaw("doc", ({ params }) => - respondRaw(Effect.gen(function*() { - const fileSystem = yield* FileSystem.FileSystem - const docFiles: Record = { - debug: path.resolve(import.meta.dir, "../skills/motel-debug/SKILL.md"), - effect: path.resolve(import.meta.dir, "../skills/motel-debug/references/effect.md"), - } - const filePath = docFiles[params.name] - if (!filePath) return notFoundResponse(`Unknown doc: ${params.name}. Available: ${Object.keys(docFiles).join(", ")}`) - return yield* fileSystem.readFileString(filePath).pipe( - Effect.map(textResponse), - Effect.catch(() => Effect.succeed(notFoundResponse(`Doc file not found: ${params.name}`))), - ) - })), - ) - .handleRaw("facets", ({ request }) => - respondRaw(Effect.gen(function*() { - const url = requestUrl(request) - const type = url.searchParams.get("type") - const field = url.searchParams.get("field") - if ((type !== "traces" && type !== "logs") || !field) { - return jsonResponse({ error: "Expected type=traces|logs and field=" }, 400) - } - const data = yield* withRead((store) => - store.listFacets({ - type, - field, - serviceName: url.searchParams.get("service"), - key: url.searchParams.get("key"), - lookbackMinutes: parseLookbackMinutes(url.searchParams.get("lookback"), config.otel.traceLookbackMinutes), - limit: parseLimit(url.searchParams.get("limit"), 20), - }), - ) - return jsonResponse({ data }) - })), - ) - .handleRaw("aiCalls", ({ request }) => - respondRaw(Effect.gen(function*() { - const params = parseListParams(request, AI_LIST) - const data = yield* withRead((store) => - store.searchAiCalls({ - service: params.url.searchParams.get("service"), - traceId: params.url.searchParams.get("traceId"), - sessionId: params.url.searchParams.get("sessionId"), - functionId: params.url.searchParams.get("functionId"), - provider: params.url.searchParams.get("provider"), - model: params.url.searchParams.get("model"), - operation: params.url.searchParams.get("operation"), - status: (params.url.searchParams.get("status") as "ok" | "error" | null) ?? null, - minDurationMs: params.url.searchParams.get("minDurationMs") ? Number(params.url.searchParams.get("minDurationMs")) : null, - text: params.url.searchParams.get("text"), - lookbackMinutes: params.lookbackMinutes, - limit: params.limit, - }), - ) - return jsonResponse({ - data, - meta: listMeta({ limit: params.limit, lookbackMinutes: params.lookbackMinutes, returned: data.length, truncated: false, nextCursor: null }), - }) - })), - ) - .handleRaw("aiCall", ({ params }) => - respondRaw(Effect.gen(function*() { - const data = yield* withRead((store) => store.getAiCall(params.spanId)) - if (!data) return notFoundResponse("AI call not found") - return jsonResponse({ data }) - })), - ) - .handleRaw("aiStats", ({ request }) => - respondRaw(Effect.gen(function*() { - const params = parseListParams(request, AI_LIST) - const groupBy = params.url.searchParams.get("groupBy") as "provider" | "model" | "functionId" | "sessionId" | "status" | null - const agg = params.url.searchParams.get("agg") as "count" | "avg_duration" | "p95_duration" | "total_input_tokens" | "total_output_tokens" | null - if (!groupBy || !agg) { - return jsonResponse({ error: "Expected groupBy and agg parameters" }, 400) - } - const data = yield* withRead((store) => - store.aiCallStats({ - groupBy, - agg, - service: params.url.searchParams.get("service"), - traceId: params.url.searchParams.get("traceId"), - sessionId: params.url.searchParams.get("sessionId"), - functionId: params.url.searchParams.get("functionId"), - provider: params.url.searchParams.get("provider"), - model: params.url.searchParams.get("model"), - operation: params.url.searchParams.get("operation"), - status: (params.url.searchParams.get("status") as "ok" | "error" | null) ?? null, - minDurationMs: params.url.searchParams.get("minDurationMs") ? Number(params.url.searchParams.get("minDurationMs")) : null, - lookbackMinutes: params.lookbackMinutes, - limit: params.limit, - }), - ) - return jsonResponse({ data }) - })), - ) - .handleRaw("tracePage", ({ params }) => - respondRaw( - Effect.flatMap(withRead((store) => store.getTrace(params.traceId)), (trace) => - trace - ? Effect.map(withRead((store) => store.listTraceLogs(params.traceId)), (logs) => htmlResponse(renderTracePage(trace, logs))) - : Effect.succeed(notFoundResponse("Trace not found")), - ), - ), - ), +const ApiLayer = Layer.unwrap( + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem + const debug = yield* fs.readFileString(path.resolve(import.meta.dir, "../skills/motel-debug/SKILL.md")) + const effect = yield* fs.readFileString(path.resolve(import.meta.dir, "../skills/motel-debug/references/effect.md")) + return motelApi({ health: healthPayload, docs: { debug, effect } }) + }), ) - -// --------------------------------------------------------------------------- -// App layer: HTTP router + static SPA + telemetry store -// --------------------------------------------------------------------------- - -// API routes come from the Effect HttpApi definition. Everything under -// /api/*, /v1/*, /openapi.json, /docs is handled here. -const ApiLayer = HttpApiBuilder.layer(MotelHttpApi, { openapiPath: "/openapi.json" }).pipe( - Layer.provide(TelemetryGroupLive), - Layer.provide(HttpApiScalar.layer(MotelHttpApi, { scalar: { forceDarkModeState: "dark", showOperationId: true } })), -) - // Web UI: Vite-built SPA served from web/dist. HttpStaticServer.layer // handles GET /*, filesystem lookup under `root`, and SPA fallback to // index.html for unknown paths — replacing the hand-rolled serveWebUi @@ -522,10 +87,9 @@ const RegistryLayer = Layer.effectDiscard( * rebind conflicts (the registry-based adoption path in daemon.ts is the * primary protection, but this covers a raw `bun src/server.ts` restart). */ -export const ServerLive = HttpRouter.serve( - Layer.mergeAll(ApiLayer, StaticLayer, RegistryLayer), - { middleware: HttpMiddleware.tracer }, -).pipe( +export const ServerLive = HttpRouter.serve(Layer.mergeAll(ApiLayer, StaticLayer, RegistryLayer), { + middleware: HttpMiddleware.tracer, +}).pipe( // OTLP ingest paths are NOT traced by the middleware, otherwise // MOTEL_OTEL_ENABLED creates a feedback loop: every outbound span // POSTs to /v1/traces, the tracer emits a span for that POST, which @@ -538,12 +102,14 @@ export const ServerLive = HttpRouter.serve( // a brand-new empty one), keeping health independent of writer work. Layer.provideMerge(AsyncIngestLive), Layer.provideMerge(TelemetryQueryLive), - Layer.provideMerge(BunHttpServer.layer({ - port: config.otel.port, - hostname: config.otel.host, - reusePort: true, - routes: { - "/api/health": () => Response.json(healthPayload()), - }, - })), + Layer.provideMerge( + BunHttpServer.layer({ + port: config.otel.port, + hostname: config.otel.host, + reusePort: true, + routes: { + "/api/health": () => Response.json(healthPayload()), + }, + }), + ), ) diff --git a/src/mcp.ts b/src/mcp.ts index 28cd16e..9edee14 100755 --- a/src/mcp.ts +++ b/src/mcp.ts @@ -1,6 +1,7 @@ #!/usr/bin/env bun import { BunRuntime, BunStdio } from "@effect/platform-bun" import { Effect, Layer, Logger, Schema } from "effect" +import { MOTEL_VERSION } from "./registry.js" import { McpServer, Tool, Toolkit } from "effect/unstable/ai" import { TraceSpanStatus } from "./domain.js" import { MotelClient, MotelClientLive } from "./motelClient.js" @@ -422,7 +423,7 @@ const ServerLayer = McpServer.toolkit(MotelToolkit).pipe( Layer.provide( McpServer.layerStdio({ name: "motel", - version: "0.1.0", + version: MOTEL_VERSION, }), ), Layer.provide(BunStdio.layer), diff --git a/src/otlp.test.ts b/src/otlp.test.ts new file mode 100644 index 0000000..6dc9135 --- /dev/null +++ b/src/otlp.test.ts @@ -0,0 +1,65 @@ +import rootModule from "@opentelemetry/otlp-transformer/build/esm/generated/root.js" +import { describe, expect, test } from "bun:test" +import { normalizeOtlpBinaryId } from "./otlp.ts" +import { decodeProtobufLogs, decodeProtobufTraces } from "./otlpProtobuf.ts" + +describe("normalizeOtlpBinaryId", () => { + test("normalizes hex and canonical base64 IDs", () => { + expect(normalizeOtlpBinaryId("0123456789ABCDEF", 8)).toBe("0123456789abcdef") + expect(normalizeOtlpBinaryId(Buffer.from("0123456789abcdef", "hex").toString("base64"), 8)).toBe("0123456789abcdef") + expect(normalizeOtlpBinaryId(Buffer.from("0123456789abcdef0123456789abcdef", "hex").toString("base64"), 16)).toBe("0123456789abcdef0123456789abcdef") + }) + + test("preserves non-standard human-readable IDs", () => { + expect(normalizeOtlpBinaryId("ai-stream-1", 8)).toBe("ai-stream-1") + expect(normalizeOtlpBinaryId("ai-stream-2", 8)).toBe("ai-stream-2") + expect(normalizeOtlpBinaryId("trace-ai", 16)).toBe("trace-ai") + }) + + test("returns null for absent IDs", () => { + expect(normalizeOtlpBinaryId(null, 16)).toBeNull() + expect(normalizeOtlpBinaryId(undefined, 8)).toBeNull() + expect(normalizeOtlpBinaryId("", 16)).toBeNull() + }) +}) + +const root = rootModule as unknown as { + readonly opentelemetry: { + readonly proto: { + readonly collector: { + readonly trace: { readonly v1: { readonly ExportTraceServiceRequest: { encode: (message: unknown) => { finish: () => Uint8Array } } } } + readonly logs: { readonly v1: { readonly ExportLogsServiceRequest: { encode: (message: unknown) => { finish: () => Uint8Array } } } } + } + } + } +} + +describe("protobuf OTLP decoders", () => { + test("decodes traces into the JSON ingest shape", () => { + const encoded = root.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest.encode({ + resourceSpans: [{ + resource: { attributes: [{ key: "service.name", value: { stringValue: "proto-svc" } }] }, + scopeSpans: [{ spans: [{ + traceId: Buffer.from("0123456789abcdef0123456789abcdef", "hex"), + spanId: Buffer.from("0123456789abcdef", "hex"), + name: "op", + startTimeUnixNano: 1, + endTimeUnixNano: 2, + }] }], + }], + }).finish() + const span = decodeProtobufTraces(encoded).resourceSpans?.[0]?.scopeSpans?.[0]?.spans?.[0] + expect(span?.name).toBe("op") + expect(normalizeOtlpBinaryId(span?.traceId, 16)).toBe("0123456789abcdef0123456789abcdef") + expect(normalizeOtlpBinaryId(span?.spanId, 8)).toBe("0123456789abcdef") + }) + + test("decodes logs into the JSON ingest shape", () => { + const encoded = root.opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest.encode({ + resourceLogs: [{ scopeLogs: [{ logRecords: [{ timeUnixNano: 5, severityText: "INFO", body: { stringValue: "hello" } }] }] }], + }).finish() + const record = decodeProtobufLogs(encoded).resourceLogs?.[0]?.scopeLogs?.[0]?.logRecords?.[0] + expect(record?.severityText).toBe("INFO") + expect(record?.body?.stringValue).toBe("hello") + }) +}) diff --git a/src/otlp.ts b/src/otlp.ts index 3755ad4..a552223 100644 --- a/src/otlp.ts +++ b/src/otlp.ts @@ -140,3 +140,23 @@ export const spanKindLabel = (kind: number | undefined): string | null => { } export const spanStatusLabel = (code: number | undefined): "ok" | "error" => (code === 2 ? "error" : "ok") + +/** Normalize OTLP hex or canonical base64 binary IDs to lowercase hex. */ +export const normalizeOtlpBinaryId = ( + value: string | null | undefined, + expectedBytes: 8 | 16, +): string | null => { + if (!value) return null + const expectedHexLength = expectedBytes * 2 + if (value.length === expectedHexLength && /^[0-9a-f]+$/.test(value)) return value + if (value.length === expectedHexLength && /^[0-9a-fA-F]+$/.test(value)) return value.toLowerCase() + try { + const bytes = Buffer.from(value, "base64") + if (bytes.length === expectedBytes && bytes.toString("base64") === value) { + return bytes.toString("hex") + } + } catch { + // Preserve non-standard human-readable IDs used by local fixtures and custom exporters. + } + return value +} diff --git a/src/otlpProtobuf.ts b/src/otlpProtobuf.ts new file mode 100644 index 0000000..6e3c5a3 --- /dev/null +++ b/src/otlpProtobuf.ts @@ -0,0 +1,35 @@ +import rootModule from "@opentelemetry/otlp-transformer/build/esm/generated/root.js" +import type { OtlpLogExportRequest, OtlpTraceExportRequest } from "./otlp.js" + +interface ProtobufType { + readonly decode: (bytes: Uint8Array) => unknown + readonly toObject: (message: unknown, options: Record) => unknown +} + +const root = rootModule as unknown as { + readonly opentelemetry: { + readonly proto: { + readonly collector: { + readonly trace: { readonly v1: { readonly ExportTraceServiceRequest: ProtobufType } } + readonly logs: { readonly v1: { readonly ExportLogsServiceRequest: ProtobufType } } + } + } + } +} + +const ExportTraceServiceRequest = root.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest +const ExportLogsServiceRequest = root.opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest +const decodeOptions = { + bytes: String, + longs: String, + defaults: false, + enums: Number, + arrays: true, + objects: true, +} + +export const decodeProtobufTraces = (bytes: Uint8Array): OtlpTraceExportRequest => + ExportTraceServiceRequest.toObject(ExportTraceServiceRequest.decode(bytes), decodeOptions) as OtlpTraceExportRequest + +export const decodeProtobufLogs = (bytes: Uint8Array): OtlpLogExportRequest => + ExportLogsServiceRequest.toObject(ExportLogsServiceRequest.decode(bytes), decodeOptions) as OtlpLogExportRequest diff --git a/src/otlpSchema.ts b/src/otlpSchema.ts new file mode 100644 index 0000000..26840bd --- /dev/null +++ b/src/otlpSchema.ts @@ -0,0 +1,84 @@ +import { Schema } from "effect" +import type { OtlpAnyValue } from "./otlp.js" + +const optional = Schema.optionalKey +const AnyValue: Schema.Codec = Schema.suspend(() => + Schema.Struct({ + stringValue: optional(Schema.String), + boolValue: optional(Schema.Boolean), + intValue: optional(Schema.Union([Schema.String, Schema.Number])), + doubleValue: optional(Schema.Number), + bytesValue: optional(Schema.String), + arrayValue: optional(Schema.Struct({ values: optional(Schema.Array(AnyValue)) })), + kvlistValue: optional(Schema.Struct({ values: optional(Schema.Array(KeyValue)) })), + }), +) +const KeyValue = Schema.Struct({ key: Schema.String, value: optional(AnyValue) }) +const attributes = optional(Schema.Array(KeyValue)) +const resource = optional(Schema.Struct({ attributes })) +const scope = optional(Schema.Struct({ name: optional(Schema.String) })) +/** Decode the OTLP trace fields consumed by the collector at an ingest boundary. */ +export const TraceExport = Schema.Struct({ + resourceSpans: optional( + Schema.Array( + Schema.Struct({ + resource, + scopeSpans: optional( + Schema.Array( + Schema.Struct({ + scope, + spans: optional( + Schema.Array( + Schema.Struct({ + traceId: Schema.String, + spanId: Schema.String, + parentSpanId: optional(Schema.String), + name: optional(Schema.String), + kind: optional(Schema.Number), + startTimeUnixNano: optional(Schema.String), + endTimeUnixNano: optional(Schema.String), + attributes, + status: optional(Schema.Struct({ code: optional(Schema.Number), message: optional(Schema.String) })), + events: optional( + Schema.Array(Schema.Struct({ timeUnixNano: optional(Schema.String), name: optional(Schema.String), attributes })), + ), + }), + ), + ), + }), + ), + ), + }), + ), + ), +}) +/** Decode the OTLP log fields consumed by the collector at an ingest boundary. */ +export const LogExport = Schema.Struct({ + resourceLogs: optional( + Schema.Array( + Schema.Struct({ + resource, + scopeLogs: optional( + Schema.Array( + Schema.Struct({ + scope, + logRecords: optional( + Schema.Array( + Schema.Struct({ + timeUnixNano: optional(Schema.String), + observedTimeUnixNano: optional(Schema.String), + severityText: optional(Schema.String), + body: optional(AnyValue), + attributes, + traceId: optional(Schema.String), + spanId: optional(Schema.String), + }), + ), + ), + }), + ), + ), + }), + ), + ), +}) diff --git a/src/registry.ts b/src/registry.ts index ad34c7c..874a163 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -1,20 +1,13 @@ import * as fs from "node:fs" import * as os from "node:os" import * as path from "node:path" +import packageJson from "../package.json" with { type: "json" } -export const MOTEL_VERSION = "0.1.0" +export const MOTEL_VERSION = packageJson.version export const MOTEL_SERVICE_ID = "motel-local-server" -const stateHome = () => - process.env.XDG_STATE_HOME?.trim() || path.join(os.homedir(), ".local", "state") - -/** - * The shared, machine-global motel state directory. Holds the SQLite - * database, daemon log, daemon lock, and the per-pid instance registry. - * One motel daemon serves every project on this machine — there is no - * per-cwd state. - */ -export const motelStateDir = () => process.env.MOTEL_RUNTIME_DIR?.trim() || path.join(stateHome(), "motel") +import { motelStateDir } from "./statePaths.js" +export { motelStateDir } from "./statePaths.js" export const registryDir = (runtimeDir = motelStateDir()) => path.join(runtimeDir, "instances") diff --git a/src/runtime.ts b/src/runtime.ts index 1fb399c..b0829b2 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -5,7 +5,7 @@ import { SimpleLogRecordProcessor } from "@opentelemetry/sdk-logs" import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base" import { Layer, ManagedRuntime } from "effect" import { config } from "./config.js" -import { TelemetryStoreLive, TelemetryStoreReadonlyLive } from "./services/TelemetryStore.js" +import { TelemetryStoreLive, TelemetryStoreReadonlyLive } from "./services/TelemetryStoreBun.js" const telemetryLayer = NodeSdk.layer(() => ({ spanProcessor: new SimpleSpanProcessor( diff --git a/src/services/AsyncIngest.ts b/src/services/AsyncIngest.ts index 03f46c7..c0237df 100644 --- a/src/services/AsyncIngest.ts +++ b/src/services/AsyncIngest.ts @@ -1,82 +1,16 @@ -/** - * Main-thread client for the telemetry worker's ingest RPCs. - * - * The HTTP handlers for POST /v1/traces and POST /v1/logs call into - * this service instead of `TelemetryStore.ingestTraces/Logs`. Each - * method sends a typed message to the worker, awaits the reply, and - * returns the worker's result as an Effect. While the worker is - * serialising a big batch into SQLite, the main thread's event loop - * is FREE to answer /api/* queries — that's the whole point of the - * offload. Without this, /api/health and friends queued behind long - * ingests and reported p95 latencies of 3-5 seconds; after, they - * stay responsive regardless of ingest load. - * - * The worker is spawned as a scope'd resource inside the layer. The - * protocol pool is sized at 1 because SQLite only supports a single - * writer at a time anyway — running N concurrent workers would just - * queue them on SQLite's lock. When the outer scope closes (server - * shutdown), `BunWorker.layer`'s finalizer sends a close message and - * terminates the worker if it doesn't exit gracefully in 5s. - */ - -import * as BunWorker from "@effect/platform-bun/BunWorker" -import { Context, Duration, Effect, Exit, Layer, Scope } from "effect" -import * as RpcClient from "effect/unstable/rpc/RpcClient" +import { Context, type Effect } from "effect" import type { RpcClientError } from "effect/unstable/rpc/RpcClientError" -import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization" import type { WorkerError } from "effect/unstable/workers/WorkerError" -import { IngestRpcs } from "./ingestRpc.ts" - -// RpcClient.make always surfaces RpcClientError in addition to the -// group's declared errors (transport failures, worker crashes, etc.), -// so the service shape has to mirror that. Without the explicit error -// type param, TS treats the declared and observed client types as -// unrelated structural mismatches. +import type { IngestError } from "./ingestRpc.js" +/** OTLP ingest capability, supplied by a worker pool or an in-process store. */ export class AsyncIngest extends Context.Service< AsyncIngest, - RpcClient.FromGroup + { + readonly ingestTraces: (input: { + readonly payload: unknown + }) => Effect.Effect<{ readonly insertedSpans: number }, IngestError | RpcClientError | WorkerError> + readonly ingestLogs: (input: { + readonly payload: unknown + }) => Effect.Effect<{ readonly insertedLogs: number }, IngestError | RpcClientError | WorkerError> + } >()("@motel/AsyncIngest") {} - -// Protocol: RpcClient.layerProtocolWorker manages a worker pool and -// speaks msgpack over structured-clone messages. `size: 1` matches -// SQLite's single-writer constraint. -const WorkerProtocol = RpcClient.layerProtocolWorker({ size: 1 }).pipe( - Layer.provide(RpcSerialization.layerMsgPack), - Layer.provide( - BunWorker.layer(() => new Worker(new URL("./telemetryWorker.ts", import.meta.url))), - ), -) - -export const AsyncIngestLive = Layer.effect( - AsyncIngest, - Effect.gen(function*() { - const scope = yield* Scope.Scope - // Keep daemon startup cheap: creating the RPC client here would eagerly - // spawn the worker and make /api/health wait on the worker's SQLite - // bootstrap. Cache a lazy initializer instead so the worker only starts - // on the first ingest request, but is still shared thereafter. - const [getClient, invalidateClient] = yield* Effect.cachedInvalidateWithTTL(Effect.gen(function*() { - const clientScope = yield* Scope.fork(scope, "sequential") - const protocolContext = yield* Layer.buildWithScope(WorkerProtocol, clientScope) - const client = yield* RpcClient.make(IngestRpcs).pipe( - Effect.provide(protocolContext), - Effect.provideService(Scope.Scope, clientScope), - ) - return { client, clientScope } - }), Duration.infinity) - // Start the sole writer/maintenance worker immediately, but do not make - // HTTP health wait for SQLite bootstrap. Managed readiness still verifies - // the worker through explicit ingest probes. - yield* Effect.forkScoped(getClient.pipe(Effect.ignore)) - return { - ingestTraces: (input, options) => - Effect.flatMap(getClient, ({ client, clientScope }) => client.ingestTraces(input, options).pipe( - Effect.onError(() => Effect.andThen(Scope.close(clientScope, Exit.void), invalidateClient)), - )), - ingestLogs: (input, options) => - Effect.flatMap(getClient, ({ client, clientScope }) => client.ingestLogs(input, options).pipe( - Effect.onError(() => Effect.andThen(Scope.close(clientScope, Exit.void), invalidateClient)), - )), - } - }), -) diff --git a/src/services/AsyncIngestBun.ts b/src/services/AsyncIngestBun.ts new file mode 100644 index 0000000..5e4a5dd --- /dev/null +++ b/src/services/AsyncIngestBun.ts @@ -0,0 +1,53 @@ +import * as BunWorker from "@effect/platform-bun/BunWorker" +import { Context, Duration, Effect, Exit, Layer, Scope } from "effect" +import * as RpcClient from "effect/unstable/rpc/RpcClient" +import type { RpcClientError } from "effect/unstable/rpc/RpcClientError" +import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization" +import type { WorkerError } from "effect/unstable/workers/WorkerError" +import { IngestRpcs } from "./ingestRpc.ts" + +import { AsyncIngest } from "./AsyncIngest.js" +// Protocol: RpcClient.layerProtocolWorker manages a worker pool and +// speaks msgpack over structured-clone messages. `size: 1` matches +// SQLite's single-writer constraint. +const WorkerProtocol = RpcClient.layerProtocolWorker({ size: 1 }).pipe( + Layer.provide(RpcSerialization.layerMsgPack), + Layer.provide(BunWorker.layer(() => new Worker(new URL("./telemetryWorker.ts", import.meta.url)))), +) + +export const AsyncIngestLive = Layer.effect( + AsyncIngest, + Effect.gen(function* () { + const scope = yield* Scope.Scope + // Keep daemon startup cheap: creating the RPC client here would eagerly + // spawn the worker and make /api/health wait on the worker's SQLite + // bootstrap. Cache a lazy initializer instead so the worker only starts + // on the first ingest request, but is still shared thereafter. + const [getClient, invalidateClient] = yield* Effect.cachedInvalidateWithTTL( + Effect.gen(function* () { + const clientScope = yield* Scope.fork(scope, "sequential") + const protocolContext = yield* Layer.buildWithScope(WorkerProtocol, clientScope) + const client = yield* RpcClient.make(IngestRpcs).pipe( + Effect.provide(protocolContext), + Effect.provideService(Scope.Scope, clientScope), + ) + return { client, clientScope } + }), + Duration.infinity, + ) + // Start the sole writer/maintenance worker immediately, but do not make + // HTTP health wait for SQLite bootstrap. Managed readiness still verifies + // the worker through explicit ingest probes. + yield* Effect.forkScoped(getClient.pipe(Effect.ignore)) + return { + ingestTraces: (input) => + Effect.flatMap(getClient, ({ client, clientScope }) => + client.ingestTraces(input).pipe(Effect.onError(() => Effect.andThen(Scope.close(clientScope, Exit.void), invalidateClient))), + ), + ingestLogs: (input) => + Effect.flatMap(getClient, ({ client, clientScope }) => + client.ingestLogs(input).pipe(Effect.onError(() => Effect.andThen(Scope.close(clientScope, Exit.void), invalidateClient))), + ), + } + }), +) diff --git a/src/services/LogQueryService.ts b/src/services/LogQueryService.ts index c75da76..d6f546e 100644 --- a/src/services/LogQueryService.ts +++ b/src/services/LogQueryService.ts @@ -19,7 +19,7 @@ export class LogQueryService extends Context.Service< export const LogQueryServiceLive = Layer.effect( LogQueryService, - Effect.map(TelemetryStore.asEffect(), (store) => LogQueryService.of({ + Effect.map(TelemetryStore, (store) => LogQueryService.of({ listRecentLogs: store.listRecentLogs, listTraceLogs: store.listTraceLogs, searchLogs: store.searchLogs, diff --git a/src/services/TelemetryDatabase.ts b/src/services/TelemetryDatabase.ts new file mode 100644 index 0000000..78c2a92 --- /dev/null +++ b/src/services/TelemetryDatabase.ts @@ -0,0 +1,18 @@ +/** Values accepted by both native SQLite and workerd SQLite. */ +export type SqlValue = string | number | bigint | boolean | null | Uint8Array +/** Prepared query operations used by the telemetry store. */ +export interface TelemetryStatement { + all(...values: SqlValue[]): unknown[] + get(...values: SqlValue[]): unknown + run(...values: SqlValue[]): { changes: number | bigint; lastInsertRowid: number | bigint } +} +/** SQLite ownership stays with the runtime adapter; the shared store owns its schema. */ +export interface TelemetryDatabase { + exec(sql: string): void + query(sql: string): TelemetryStatement + transaction
(run: (...args: A) => B): (...args: A) => B + usedBytes(): number + reclaim(): void + checkpoint(mode: "RESTART" | "TRUNCATE"): void + optimize(): void +} diff --git a/src/services/TelemetryStore.ts b/src/services/TelemetryStore.ts index ce2c870..6879735 100644 --- a/src/services/TelemetryStore.ts +++ b/src/services/TelemetryStore.ts @@ -1,11 +1,9 @@ -import { Database } from "bun:sqlite" -import * as BunFileSystem from "@effect/platform-bun/BunFileSystem" -import { dirname } from "node:path" -import { Cause, Clock, Effect, FileSystem, Layer, Schedule, Context } from "effect" +import type { TelemetryDatabase } from "./TelemetryDatabase.js" +import { Cause, Clock, Effect, Layer, Schedule, Context } from "effect" import { config } from "../config.js" import type { AiCallDetail, AiCallSummary, FacetItem, LogItem, SpanItem, StatsItem, TraceItem, TraceSummaryItem, TraceSpanEvent, TraceSpanItem } from "../domain.js" import { AI_ATTR_MAP, AI_FTS_KEYS, AI_TEXT_SEARCH_KEYS, truncatePreview } from "../domain.js" -import { attributeMap, nanosToMilliseconds, parseAnyValue, spanKindLabel, spanStatusLabel, stringifyValue, type OtlpLogExportRequest, type OtlpTraceExportRequest } from "../otlp.js" +import { attributeMap, nanosToMilliseconds, normalizeOtlpBinaryId, parseAnyValue, spanKindLabel, spanStatusLabel, stringifyValue, type OtlpLogExportRequest, type OtlpTraceExportRequest } from "../otlp.js" const isSqliteLockError = (error: unknown) => error instanceof Error && /(database is locked|database table is locked|SQLITE_BUSY)/i.test(error.message) @@ -225,20 +223,18 @@ const parseRecord = (value: string): Record => { return parsed } -const EMPTY_EVENTS: readonly TraceSpanEvent[] = [] - const parseEvents = (value: string): readonly TraceSpanEvent[] => { - if (value === "" || value === "[]") return EMPTY_EVENTS + if (value === "" || value === "[]") return [] try { const parsed = JSON.parse(value) as Array<{ name: string; timestamp: number; attributes: Record }> - if (parsed.length === 0) return EMPTY_EVENTS + if (parsed.length === 0) return [] return parsed.map((event) => ({ name: event.name, timestamp: new Date(event.timestamp), attributes: event.attributes, })) } catch { - return EMPTY_EVENTS + return [] } } @@ -505,76 +501,12 @@ export interface TelemetryStoreOptions { readonly runRetention: boolean } -const makeTelemetryStoreEffect = (opts: TelemetryStoreOptions) => +/** Construct the shared query and ingest logic over an owned SQLite connection. */ +export const makeTelemetryStoreEffect = (db: TelemetryDatabase, opts: TelemetryStoreOptions) => Effect.gen(function* () { - const fileSystem = yield* FileSystem.FileSystem - yield* fileSystem.makeDirectory(dirname(config.otel.databasePath), { recursive: true }) - const db = yield* Effect.acquireRelease( - Effect.sync(() => new Database(config.otel.databasePath, { - create: !opts.readonly, - readonly: opts.readonly, - })), - (db) => Effect.sync(() => { - if (!opts.readonly) { - // `PRAGMA optimize` at close persists any stats SQLite gathered - // during the session, so the next process start gets an accurate - // query planner on the first query instead of a 3-second cold - // run. Cheap: it skips work unless stats have drifted. - try { db.exec(`PRAGMA optimize;`) } catch { /* nothing */ } - } - db.close() - }), - ) - if (opts.readonly) { - // Readonly connections skip schema init entirely — the schema - // already exists (a writer created it) and any `CREATE TABLE IF - // NOT EXISTS` / `PRAGMA journal_mode = WAL` statement would - // attempt a write and fight the daemon for the write lock. - // `query_only = 1` logically blocks any DML the app might - // accidentally send; still bump cache + mmap since those are - // safe and keep queries fast. - db.exec(` - PRAGMA query_only = 1; - PRAGMA busy_timeout = 15000; - PRAGMA cache_size = -65536; - PRAGMA mmap_size = 268435456; - `) - } else { - db.exec(` - -- Bump cache above the 2MB default. 64MB fits most hot index pages - -- (trace_summaries, spans, span_attributes indexes) in RAM even on - -- multi-GB databases, cutting cold-read latency meaningfully on - -- picker / search queries that sweep the index. - PRAGMA cache_size = -65536; - -- Let SQLite memory-map the first 256MB of the file. This is a - -- cheap way to avoid read() syscalls on hot pages and lets the OS - -- page cache serve index lookups directly. Safe on macOS and Linux; - -- SQLite silently caps at actual file size for smaller DBs. - PRAGMA mmap_size = 268435456; - `) - // auto_vacuum is a header-level setting: it only takes effect on - // an empty DB, or on the next VACUUM after a change. Setting it - // here, BEFORE the first CREATE TABLE, is the only path that - // makes incremental_vacuum work without a full VACUUM. For - // existing DBs that predate this setting keep their current mode; - // Motel never performs a surprise full-file VACUUM at startup. - try { db.exec(`PRAGMA auto_vacuum = INCREMENTAL;`) } catch { /* ignore */ } + if (!opts.readonly) { try { db.exec(` - PRAGMA journal_mode = WAL; - PRAGMA synchronous = NORMAL; - PRAGMA temp_store = MEMORY; - -- WAL checkpoint automatically when it grows past ~16MB. Without - -- this the WAL happily runs into the hundreds of MB and queries - -- start paying the cost of walking the WAL on every read. - PRAGMA wal_autocheckpoint = 4000; - -- Hard floor for the WAL file. Auto-checkpoint controls *when* - -- pages move out of the WAL; size_limit controls how much the - -- WAL file is allowed to grow on disk. 128MB is generous enough - -- to absorb a long write burst without blocking on truncation, - -- tight enough that a wedged retention loop can't hide a 20GB - -- WAL the way a default no-limit configuration can. - PRAGMA journal_size_limit = 134217728; CREATE TABLE IF NOT EXISTS spans ( trace_id TEXT NOT NULL, @@ -616,6 +548,7 @@ const makeTelemetryStoreEffect = (opts: TelemetryStoreOptions) => CREATE INDEX IF NOT EXISTS idx_logs_trace_time ON logs(trace_id, timestamp_ms DESC); CREATE INDEX IF NOT EXISTS idx_logs_span_time ON logs(span_id, timestamp_ms DESC); CREATE INDEX IF NOT EXISTS idx_logs_severity_time ON logs(severity_text, timestamp_ms DESC); + CREATE INDEX IF NOT EXISTS idx_logs_severity_nocase_time ON logs(severity_text COLLATE NOCASE, timestamp_ms DESC); CREATE TABLE IF NOT EXISTS trace_summaries ( trace_id TEXT PRIMARY KEY, @@ -777,17 +710,12 @@ const makeTelemetryStoreEffect = (opts: TelemetryStoreOptions) => // the attribute picker facet run with guessed row estimates and // pay 3-4s on cold open instead of 400ms. try { - db.exec(`PRAGMA analysis_limit = 1000; PRAGMA optimize;`) + db.optimize() } catch { // ANALYZE / optimize failures are never fatal — queries still work, // they just run with default row estimates. } - // Longer busy timeout: the ingest worker holds the write lock for up - // to a few seconds during big OTLP batches, and the daemon's retention - // passes can do the same. Apply this AFTER startup maintenance so - // lock-conflicted bootstrap steps fail fast instead of stalling health - // for the full 15s timeout. - try { db.exec(`PRAGMA busy_timeout = 15000;`) } catch { /* ignore */ } + } // end: if (!opts.readonly) writer init const insertSpan = db.query(` @@ -845,7 +773,7 @@ const makeTelemetryStoreEffect = (opts: TelemetryStoreOptions) => const deleteSpanAttributes = db.query(`DELETE FROM span_attributes WHERE trace_id = ? AND span_id = ?`) const insertSpanAttribute = db.query(`INSERT INTO span_attributes (trace_id, span_id, key, value) VALUES (?, ?, ?, ?)`) - const spanAttributeInsertManyByCount = new Map>() + const spanAttributeInsertManyByCount = new Map>() const insertSpanAttributesMany = (traceId: string, spanId: string, attributes: Readonly>) => { const entries = Object.entries(attributes) if (entries.length === 0) return @@ -863,8 +791,8 @@ const makeTelemetryStoreEffect = (opts: TelemetryStoreOptions) => } const deleteSpanOperationSearch = db.query(`DELETE FROM span_operation_fts WHERE trace_id = ? AND span_id = ?`) const insertSpanOperationSearch = db.query(`INSERT INTO span_operation_fts (trace_id, span_id, operation_name) VALUES (?, ?, ?)`) - const deleteSpanOperationSearchManyByCount = new Map>() - const insertSpanOperationSearchManyByCount = new Map>() + const deleteSpanOperationSearchManyByCount = new Map>() + const insertSpanOperationSearchManyByCount = new Map>() const updateSpanOperationSearchMany = (operations: ReadonlyArray) => { if (operations.length === 0) return if (operations.length === 1) { @@ -889,7 +817,7 @@ const makeTelemetryStoreEffect = (opts: TelemetryStoreOptions) => insertQuery.run(...operations.flatMap(([traceId, spanId, operationName]) => [traceId, spanId, operationName])) } const insertLogAttribute = db.query(`INSERT INTO log_attributes (log_id, key, value) VALUES (?, ?, ?)`) - const logAttributeInsertManyByCount = new Map>() + const logAttributeInsertManyByCount = new Map>() const insertLogAttributesMany = (logId: number, attributes: Readonly>) => { const entries = Object.entries(attributes) if (entries.length === 0) return @@ -906,7 +834,7 @@ const makeTelemetryStoreEffect = (opts: TelemetryStoreOptions) => query.run(...entries.flatMap(([key, value]) => [logId, key, value])) } const insertLogBodySearch = db.query(`INSERT INTO log_body_fts (log_id, body) VALUES (?, ?)`) - const insertLogBodySearchManyByCount = new Map>() + const insertLogBodySearchManyByCount = new Map>() const insertLogBodySearchMany = (entries: ReadonlyArray) => { if (entries.length === 0) return if (entries.length === 1) { @@ -928,12 +856,6 @@ const makeTelemetryStoreEffect = (opts: TelemetryStoreOptions) => // LOW threshold there's nothing worth doing; above HIGH we are in the // 17GB-DB-with-10GB-freelist failure mode and need to reclaim aggressively // even if it costs writer-lock time. - const FREELIST_LOW_RATIO = 0.05 - const FREELIST_MID_RATIO = 0.20 - const FREELIST_HIGH_RATIO = 0.50 - const VACUUM_PAGES_NORMAL = 2000 // ~8MB/pass - const VACUUM_PAGES_BUSY = 20000 // ~80MB/pass — used when freelist > 20% - const VACUUM_PAGES_PANIC = 50000 // ~200MB/pass — only when ratio > 50% const ftsTableNames = ["span_attr_fts", "log_body_fts", "span_operation_fts"] as const @@ -949,35 +871,7 @@ const makeTelemetryStoreEffect = (opts: TelemetryStoreOptions) => } } - const reclaimSpace = Effect.fn("motel/TelemetryStore.reclaimSpace")(function* () { - yield* Effect.sync(() => { - const pageCount = (db.query(`PRAGMA page_count`).get() as { page_count: number }).page_count - const freePages = (db.query(`PRAGMA freelist_count`).get() as { freelist_count: number }).freelist_count - if (pageCount === 0) return - const ratio = freePages / pageCount - if (ratio < FREELIST_LOW_RATIO) return - - // Adaptive vacuum sizing — fixed 2000 pages/min could not keep - // up with sustained deletions, leaking 10GB of freelist over - // time. Scale the per-pass work to the size of the backlog so - // we stay roughly proportional to the deficit. - const pages = - ratio >= FREELIST_HIGH_RATIO ? VACUUM_PAGES_PANIC : - ratio >= FREELIST_MID_RATIO ? VACUUM_PAGES_BUSY : - VACUUM_PAGES_NORMAL - - try { db.exec(`PRAGMA incremental_vacuum(${pages});`) } catch { /* ignore */ } - - // In WAL mode incremental_vacuum only moves pages — the file - // shrinks on the next checkpoint. PASSIVE silently skips when - // readers are active (the failure mode the agent's research - // flagged: checkpoint starvation). Use RESTART normally and - // TRUNCATE in panic mode to physically shrink the WAL when it - // has grown. - const mode = ratio >= FREELIST_HIGH_RATIO ? "TRUNCATE" : "RESTART" - try { db.exec(`PRAGMA wal_checkpoint(${mode});`) } catch { /* ignore */ } - }) - }) + const reclaimSpace = () => Effect.sync(() => db.reclaim()) const cleanupExpired = Effect.fn("motel/TelemetryStore.cleanupExpired")(function* () { const now = yield* Clock.currentTimeMillis @@ -1002,10 +896,7 @@ const makeTelemetryStoreEffect = (opts: TelemetryStoreOptions) => // batch of the oldest completed traces. `(page_count - freelist_count)` // ignores freed-but-not-vacuumed pages so a large freelist doesn't // trigger a deletion death spiral. - const pageCount = (db.query(`PRAGMA page_count`).get() as { page_count: number }).page_count - const freePages = (db.query(`PRAGMA freelist_count`).get() as { freelist_count: number }).freelist_count - const pageSize = (db.query(`PRAGMA page_size`).get() as { page_size: number }).page_size - const dbSize = (pageCount - freePages) * pageSize + const dbSize = db.usedBytes() if (dbSize > maxDbSizeBytes) { const oldest = db.query( `SELECT trace_id FROM trace_summaries WHERE active_span_count = 0 ORDER BY started_at_ms ASC LIMIT ?`, @@ -1060,7 +951,7 @@ const makeTelemetryStoreEffect = (opts: TelemetryStoreOptions) => // when readers are active, which is the documented mechanism // behind WAL/freelist starvation when ingest is busy. if (toEvict.size === 0 && !deletedLogs && !deletedOrphans) return - try { db.exec(`PRAGMA wal_checkpoint(RESTART);`) } catch { /* ignore */ } + db.checkpoint("RESTART") // Incremental FTS5 merge — DELETE on an FTS5-indexed row // leaves a tombstone in the segment tree that only `merge` @@ -1100,13 +991,14 @@ const makeTelemetryStoreEffect = (opts: TelemetryStoreOptions) => // grows. 15 minutes is slower than ingestion rates we care about but // frequent enough that the attribute picker stays snappy. const refreshPlannerStats = Effect.sync(() => { - try { db.exec(`PRAGMA optimize;`) } catch { /* ignore */ } + db.optimize() }) yield* Effect.forkScoped(Effect.repeat(refreshPlannerStats, Schedule.spaced("15 minutes"))) } // Incrementally rebuild historical AI attributes in bounded batches. // Queries fall back to LIKE until the persistent marker is complete. + let backfillBatch: Effect.Effect = Effect.void if (hasAttrFts && !opts.readonly) { const backfillAttrFtsBatch = Effect.sync(() => { try { @@ -1147,7 +1039,8 @@ const makeTelemetryStoreEffect = (opts: TelemetryStoreOptions) => pending ? Effect.andThen(Effect.sleep("100 millis"), backfillAttrFts) : Effect.void, ), ) - yield* Effect.forkScoped(backfillAttrFts) + backfillBatch = backfillAttrFtsBatch + if (opts.runRetention) yield* Effect.forkScoped(backfillAttrFts) } const ingestTraces = Effect.fn("motel/TelemetryStore.ingestTraces")(function* (payload: OtlpTraceExportRequest) { @@ -1164,6 +1057,10 @@ const makeTelemetryStoreEffect = (opts: TelemetryStoreOptions) => const scopeName = scopeSpans.scope?.name ?? null for (const span of scopeSpans.spans ?? []) { + const traceId = normalizeOtlpBinaryId(span.traceId, 16) + const spanId = normalizeOtlpBinaryId(span.spanId, 8) + if (!traceId || !spanId) continue + const parentSpanId = normalizeOtlpBinaryId(span.parentSpanId, 8) const spanAttributes = attributeMap(span.attributes) const mergedAttributes = { ...resourceAttributes, ...spanAttributes } const startTimeMs = nanosToMilliseconds(span.startTimeUnixNano) @@ -1175,9 +1072,9 @@ const makeTelemetryStoreEffect = (opts: TelemetryStoreOptions) => })) insertSpan.run( - span.traceId, - span.spanId, - span.parentSpanId ?? null, + traceId, + spanId, + parentSpanId, serviceName, scopeName, span.name ?? "unknown", @@ -1190,10 +1087,10 @@ const makeTelemetryStoreEffect = (opts: TelemetryStoreOptions) => JSON.stringify(resourceAttributes), JSON.stringify(events), ) - deleteSpanAttributes.run(span.traceId, span.spanId) - insertSpanAttributesMany(span.traceId, span.spanId, mergedAttributes) - touchedOperations.push([span.traceId, span.spanId, span.name ?? "unknown"]) - touchedTraceIds.add(span.traceId) + deleteSpanAttributes.run(traceId, spanId) + insertSpanAttributesMany(traceId, spanId, mergedAttributes) + touchedOperations.push([traceId, spanId, span.name ?? "unknown"]) + touchedTraceIds.add(traceId) insertedSpans += 1 } } @@ -1233,9 +1130,11 @@ const makeTelemetryStoreEffect = (opts: TelemetryStoreOptions) => const mergedAttributes = { ...resourceAttributes, ...attributes } const timestampMs = nanosToMilliseconds(record.timeUnixNano ?? record.observedTimeUnixNano) const body = stringifyValue(parseAnyValue(record.body)) + const rawTraceId = attributes.traceId || attributes.trace_id || record.traceId || null + const rawSpanId = attributes.spanId || attributes.span_id || record.spanId || null const result = insertLog.run( - attributes.traceId || attributes.trace_id || record.traceId || null, - attributes.spanId || attributes.span_id || record.spanId || null, + normalizeOtlpBinaryId(rawTraceId, 16), + normalizeOtlpBinaryId(rawSpanId, 8), serviceName, scopeName, record.severityText ?? "INFO", @@ -1669,7 +1568,7 @@ const makeTelemetryStoreEffect = (opts: TelemetryStoreOptions) => params.push(input.serviceName) } if (input.severity) { - clauses.push(`severity_text = ?`) + clauses.push(`severity_text = ? COLLATE NOCASE`) params.push(input.severity.toUpperCase()) } if (input.traceId) { @@ -1882,7 +1781,7 @@ const makeTelemetryStoreEffect = (opts: TelemetryStoreOptions) => const group = input.groupBy === "service" ? log.serviceName : input.groupBy === "severity" - ? log.severityText + ? log.severityText.toUpperCase() : input.groupBy === "scope" ? log.scopeName ?? "unknown" : isAttrGroupBy @@ -1928,7 +1827,7 @@ const makeTelemetryStoreEffect = (opts: TelemetryStoreOptions) => const groupExpr = input.groupBy === "service" ? "service_name" : input.groupBy === "severity" - ? "severity_text" + ? "UPPER(severity_text)" : input.groupBy === "scope" ? "COALESCE(scope_name, 'unknown')" : "'unknown'" @@ -1972,11 +1871,11 @@ const makeTelemetryStoreEffect = (opts: TelemetryStoreOptions) => } if (input.field === "severity") { const rows = db.query(` - SELECT severity_text AS value, COUNT(*) AS count + SELECT UPPER(severity_text) AS value, COUNT(*) AS count FROM logs WHERE timestamp_ms >= ? ${input.serviceName ? "AND service_name = ?" : ""} - GROUP BY severity_text + GROUP BY value ORDER BY count DESC, value ASC LIMIT ? `).all(...(input.serviceName ? [cutoff, input.serviceName, limit] : [cutoff, limit])) as Array<{ value: string; count: number }> @@ -2511,40 +2410,6 @@ const makeTelemetryStoreEffect = (opts: TelemetryStoreOptions) => searchAiCalls, getAiCall, aiCallStats, - runRetentionNow: cleanupExpired(), + runRetentionNow: Effect.andThen(backfillBatch, Effect.andThen(reconcileTraceSummaries, cleanupExpired())), }) }) - -/** Compatibility factory for callers constructing a writer/query-capable store layer. */ -export const makeTelemetryStoreLayer = (opts: TelemetryStoreOptions) => - Layer.effect(TelemetryStore, makeTelemetryStoreEffect(opts)).pipe(Layer.provide(BunFileSystem.layer)) - -/** - * Default writer runtime used by tests and direct store consumers. - */ -export const TelemetryStoreLive = makeTelemetryStoreLayer({ readonly: false, runRetention: true }) - -/** - * The ingest worker's writer. It is the managed daemon's sole owner of - * schema migrations, FTS backfill, retention, and page reclamation. - */ -export const TelemetryStoreWorkerLive = TelemetryStoreLive - -/** - * Read-only instance for query-only processes (currently the TUI and - * HTTP query handlers). Skips every DDL/DML statement at startup so - * the connection can be opened while a writer is mid-transaction - * without racing for the write lock. Provided as TelemetryStoreReadonly - * — a distinct service identifier so it can coexist with the writer - * TelemetryStore in the same runtime. - */ -export const TelemetryStoreReadonlyLive = Layer.effect(TelemetryStoreReadonly, makeTelemetryStoreEffect({ readonly: true, runRetention: false })).pipe(Layer.provide(BunFileSystem.layer)) - -/** Query-worker reader that waits for the sole writer to finish schema bootstrap. */ -export const TelemetryStoreQueryWorkerLive = Layer.effect( - TelemetryStoreReadonly, - makeTelemetryStoreEffect({ readonly: true, runRetention: false }).pipe( - Effect.map((store) => TelemetryStoreReadonly.of(store)), - Effect.retry(Schedule.spaced("50 millis")), - ), -).pipe(Layer.provide(BunFileSystem.layer)) diff --git a/src/services/TelemetryStoreBun.ts b/src/services/TelemetryStoreBun.ts new file mode 100644 index 0000000..c22fc30 --- /dev/null +++ b/src/services/TelemetryStoreBun.ts @@ -0,0 +1,112 @@ +import { Database } from "bun:sqlite" +import * as BunFileSystem from "@effect/platform-bun/BunFileSystem" +import { dirname } from "node:path" +import { Effect, FileSystem, Layer, Schedule } from "effect" +import { config } from "../config.js" +import { makeTelemetryStoreEffect, TelemetryStore, TelemetryStoreReadonly, type TelemetryStoreOptions } from "./TelemetryStore.js" +const makeBunTelemetryStore = (opts: TelemetryStoreOptions) => + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem + yield* fileSystem.makeDirectory(dirname(config.otel.databasePath), { recursive: true }) + const db = yield* Effect.acquireRelease( + Effect.sync( + () => + new Database(config.otel.databasePath, { + create: !opts.readonly, + readonly: opts.readonly, + }), + ), + (db) => + Effect.sync(() => { + if (!opts.readonly) { + // `PRAGMA optimize` at close persists any stats SQLite gathered + // during the session, so the next process start gets an accurate + // query planner on the first query instead of a 3-second cold + // run. Cheap: it skips work unless stats have drifted. + try { + db.exec(`PRAGMA optimize;`) + } catch { + /* nothing */ + } + } + db.close() + }), + ) + db.exec( + opts.readonly + ? `PRAGMA query_only = 1; PRAGMA busy_timeout = 15000; PRAGMA cache_size = -65536; PRAGMA mmap_size = 268435456;` + : `PRAGMA busy_timeout = 15000; PRAGMA cache_size = -65536; PRAGMA mmap_size = 268435456; PRAGMA auto_vacuum = INCREMENTAL; PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL; PRAGMA temp_store = MEMORY; PRAGMA wal_autocheckpoint = 4000; PRAGMA journal_size_limit = 134217728;`, + ) + const pragmaNumber = (name: string) => { + const row: unknown = db.query(`PRAGMA ${name}`).get() + if (typeof row !== "object" || row === null || !(name in row)) throw new Error(`Missing SQLite pragma: ${name}`) + const value = Reflect.get(row, name) + if (typeof value !== "number") throw new Error(`Invalid SQLite pragma: ${name}`) + return value + } + const bestEffort = (sql: string) => { + try { + db.exec(sql) + } catch { + /* Native maintenance may be busy. */ + } + } + return yield* makeTelemetryStoreEffect( + { + exec: (sql) => { + db.exec(sql) + }, + query: (sql) => db.query(sql), + transaction: (run) => db.transaction(run), + usedBytes: () => (pragmaNumber("page_count") - pragmaNumber("freelist_count")) * pragmaNumber("page_size"), + reclaim: () => { + const pageCount = pragmaNumber("page_count") + if (pageCount === 0) return + const ratio = pragmaNumber("freelist_count") / pageCount + if (ratio < 0.05) return + const pages = ratio >= 0.5 ? 50000 : ratio >= 0.2 ? 20000 : 2000 + bestEffort(`PRAGMA incremental_vacuum(${pages})`) + bestEffort(`PRAGMA wal_checkpoint(${ratio >= 0.5 ? "TRUNCATE" : "RESTART"})`) + }, + checkpoint: (mode) => bestEffort(`PRAGMA wal_checkpoint(${mode})`), + optimize: () => bestEffort("PRAGMA analysis_limit = 1000; PRAGMA optimize;"), + }, + opts, + ) + }) +/** Compatibility factory for callers constructing a writer/query-capable store layer. */ +export const makeTelemetryStoreLayer = (opts: TelemetryStoreOptions) => + Layer.effect(TelemetryStore, makeBunTelemetryStore(opts)).pipe(Layer.provide(BunFileSystem.layer)) + +/** + * Default writer runtime used by tests and direct store consumers. + */ +export const TelemetryStoreLive = makeTelemetryStoreLayer({ readonly: false, runRetention: true }) + +/** + * The ingest worker's writer. It is the managed daemon's sole owner of + * schema migrations, FTS backfill, retention, and page reclamation. + */ +export const TelemetryStoreWorkerLive = TelemetryStoreLive + +/** + * Read-only instance for query-only processes (currently the TUI and + * HTTP query handlers). Skips every DDL/DML statement at startup so + * the connection can be opened while a writer is mid-transaction + * without racing for the write lock. Provided as TelemetryStoreReadonly + * — a distinct service identifier so it can coexist with the writer + * TelemetryStore in the same runtime. + */ +export const TelemetryStoreReadonlyLive = Layer.effect( + TelemetryStoreReadonly, + makeBunTelemetryStore({ readonly: true, runRetention: false }), +).pipe(Layer.provide(BunFileSystem.layer)) + +/** Query-worker reader that waits for the sole writer to finish schema bootstrap. */ +export const TelemetryStoreQueryWorkerLive = Layer.effect( + TelemetryStoreReadonly, + makeBunTelemetryStore({ readonly: true, runRetention: false }).pipe( + Effect.map((store) => TelemetryStoreReadonly.of(store)), + Effect.retry(Schedule.spaced("50 millis")), + ), +).pipe(Layer.provide(BunFileSystem.layer)) diff --git a/src/services/TelemetryStoreWorkerd.ts b/src/services/TelemetryStoreWorkerd.ts new file mode 100644 index 0000000..a4e1ff6 --- /dev/null +++ b/src/services/TelemetryStoreWorkerd.ts @@ -0,0 +1,49 @@ +import type { DurableObjectStorage, SqlStorageValue } from "@cloudflare/workers-types" +import type { SqlValue, TelemetryDatabase } from "./TelemetryDatabase.js" + +const binding = (value: SqlValue): SqlStorageValue => { + if (typeof value === "bigint") { + const number = Number(value) + if (!Number.isSafeInteger(number)) throw new RangeError("SQLite integer exceeds JavaScript precision") + return number + } + if (typeof value === "boolean") return Number(value) + if (value instanceof Uint8Array) return Uint8Array.from(value).buffer + return value +} + +/** Adapt workerd's owned SQLite connection without opening a second writer. */ +export const workerdDatabase = (storage: DurableObjectStorage): TelemetryDatabase => { + const sql = storage.sql + const integer = (query: string, column: string): number => { + const value = sql.exec(query).toArray()[0]?.[column] + if (typeof value !== "number") throw new Error(`Expected SQLite number: ${column}`) + return value + } + return { + exec: (query) => { + sql.exec(query).toArray() + }, + query: (query) => ({ + all: (...values) => sql.exec(query, ...values.map(binding)).toArray(), + get: (...values) => sql.exec(query, ...values.map(binding)).toArray()[0] ?? null, + run: (...values) => { + sql.exec(query, ...values.map(binding)).toArray() + return { + changes: integer("SELECT changes() AS value", "value"), + lastInsertRowid: integer("SELECT last_insert_rowid() AS value", "value"), + } + }, + }), + transaction: + (run) => + (...args) => + storage.transactionSync(() => run(...args)), + // This native property subtracts freelist pages before multiplying by page size. + usedBytes: () => sql.databaseSize, + // workerd owns checkpointing, vacuuming and query planner maintenance. + reclaim: () => {}, + checkpoint: () => {}, + optimize: () => {}, + } +} diff --git a/src/services/TraceQueryService.ts b/src/services/TraceQueryService.ts index e92e206..edd754d 100644 --- a/src/services/TraceQueryService.ts +++ b/src/services/TraceQueryService.ts @@ -26,7 +26,7 @@ export class TraceQueryService extends Context.Service< export const TraceQueryServiceLive = Layer.effect( TraceQueryService, - Effect.map(TelemetryStore.asEffect(), (store) => TraceQueryService.of({ + Effect.map(TelemetryStore, (store) => TraceQueryService.of({ listServices: store.listServices, listRecentTraces: store.listRecentTraces, listTraceSummaries: store.listTraceSummaries, diff --git a/src/services/telemetryQueryWorker.ts b/src/services/telemetryQueryWorker.ts index ffe016c..2ef0946 100644 --- a/src/services/telemetryQueryWorker.ts +++ b/src/services/telemetryQueryWorker.ts @@ -3,7 +3,8 @@ import * as BunWorkerRunner from "@effect/platform-bun/BunWorkerRunner" import { Effect, Layer } from "effect" import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization" import * as RpcServer from "effect/unstable/rpc/RpcServer" -import { TelemetryStoreQueryWorkerLive, TelemetryStoreReadonly, type TelemetryStoreReader } from "./TelemetryStore.js" +import { TelemetryStoreReadonly, type TelemetryStoreReader } from "./TelemetryStore.js" +import { TelemetryStoreQueryWorkerLive } from "./TelemetryStoreBun.js" import { QueryError, QueryRpcs } from "./queryRpc.js" type QueryMethod = keyof TelemetryStoreReader diff --git a/src/services/telemetryWorker.ts b/src/services/telemetryWorker.ts index 81af541..b429317 100644 --- a/src/services/telemetryWorker.ts +++ b/src/services/telemetryWorker.ts @@ -21,7 +21,8 @@ import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization" import * as RpcServer from "effect/unstable/rpc/RpcServer" import type { OtlpLogExportRequest, OtlpTraceExportRequest } from "../otlp.ts" import { IngestError, IngestRpcs } from "./ingestRpc.ts" -import { TelemetryStore, TelemetryStoreWorkerLive } from "./TelemetryStore.ts" +import { TelemetryStore } from "./TelemetryStore.ts" +import { TelemetryStoreWorkerLive } from "./TelemetryStoreBun.ts" // Wire the two RPC methods to the existing TelemetryStore service. // The store's ingest methods already carry their own Effect.fn spans, diff --git a/src/statePaths.ts b/src/statePaths.ts new file mode 100644 index 0000000..1a993c6 --- /dev/null +++ b/src/statePaths.ts @@ -0,0 +1,11 @@ +import * as os from "node:os" +import * as path from "node:path" +const stateHome = () => process.env.XDG_STATE_HOME?.trim() || path.join(os.homedir(), ".local", "state") + +/** + * The shared, machine-global motel state directory. Holds the SQLite + * database, daemon log, daemon lock, and the per-pid instance registry. + * One motel daemon serves every project on this machine — there is no + * per-cwd state. + */ +export const motelStateDir = () => process.env.MOTEL_RUNTIME_DIR?.trim() || path.join(stateHome(), "motel") diff --git a/src/storybook/fixtures/index.ts b/src/storybook/fixtures/index.ts index f0181fc..c3c3548 100644 --- a/src/storybook/fixtures/index.ts +++ b/src/storybook/fixtures/index.ts @@ -27,7 +27,7 @@ export const makeSpan = (overrides: Partial = {}): TraceSpanItem depth: 0, tags: { "ai.operationId": "ai.streamText", - "ai.model.id": "claude-opus-4-7", + "ai.model.id": "example-chat-model", "ai.model.provider": "anthropic", "ai.prompt.messages": "[]", }, @@ -43,7 +43,7 @@ export const makeDetail = (overrides: Partial = {}): AiCallDetail service: "storybook", functionId: "story.demo", provider: "anthropic", - model: "claude-opus-4-7", + model: "example-chat-model", status: "ok", startedAt: new Date().toISOString(), durationMs: 2400, diff --git a/src/telemetry.test.ts b/src/telemetry.test.ts index 98f1148..aa1e3a0 100644 --- a/src/telemetry.test.ts +++ b/src/telemetry.test.ts @@ -26,7 +26,7 @@ describe("motel telemetry store", () => { const nowNanos = BigInt(Date.now()) * 1_000_000n const oneSecond = 1_000_000_000n - const ingest = Effect.flatMap(TelemetryStore.asEffect(), (store) => + const ingest = Effect.flatMap(TelemetryStore, (store) => Effect.flatMap( store.ingestTraces({ resourceSpans: [ @@ -129,7 +129,7 @@ describe("motel telemetry store", () => { }, ], }), - ).pipe(Effect.flatMap(() => Effect.flatMap(TelemetryStore.asEffect(), (store) => + ).pipe(Effect.flatMap(() => Effect.flatMap(TelemetryStore, (store) => store.ingestTraces({ resourceSpans: [{ resource: { attributes: [{ key: "service.name", value: { stringValue: "test-api" } }] }, @@ -302,7 +302,7 @@ describe("motel telemetry store", () => { const oldNanos = BigInt(Date.now() - 48 * 60 * 60 * 1000) * 1_000_000n const recentNanos = BigInt(Date.now()) * 1_000_000n await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => Effect.andThen( store.ingestLogs({ resourceLogs: [{ @@ -336,7 +336,7 @@ describe("motel telemetry store", () => { } await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => store.runRetentionNow).pipe( + Effect.flatMap(TelemetryStore, (store) => store.runRetentionNow).pipe( Effect.provideService(References.MinimumLogLevel, "None"), ), ) @@ -357,7 +357,7 @@ describe("motel telemetry store", () => { legacy.close() const open = Bun.spawn({ - cmd: [process.execPath, "-e", `const { Effect } = await import('effect'); const { storeRuntime } = await import('./src/runtime.ts'); const { TelemetryStore } = await import('./src/services/TelemetryStore.ts'); await storeRuntime.runPromise(Effect.flatMap(TelemetryStore.asEffect(), (store) => store.listServices)); await storeRuntime.dispose()`], + cmd: [process.execPath, "-e", `const { Effect } = await import('effect'); const { storeRuntime } = await import('./src/runtime.ts'); const { TelemetryStore } = await import('./src/services/TelemetryStore.ts'); await storeRuntime.runPromise(Effect.flatMap(TelemetryStore, (store) => store.listServices)); await storeRuntime.dispose()`], cwd: process.cwd(), env: { ...process.env, MOTEL_OTEL_DB_PATH: legacyPath }, stdout: "ignore", @@ -380,7 +380,7 @@ describe("motel telemetry store", () => { const { Effect } = await import('effect') const { storeRuntime } = await import('./src/runtime.ts') const { TelemetryStore } = await import('./src/services/TelemetryStore.ts') - await storeRuntime.runPromise(Effect.flatMap(TelemetryStore.asEffect(), (store) => store.ingestTraces({ resourceSpans: [{ resource: { attributes: [{ key: 'service.name', value: { stringValue: 'historical-fts' } }] }, scopeSpans: [{ spans: [{ traceId: 'historical-trace', spanId: 'historical-span', name: 'ai.generateText', startTimeUnixNano: '1', endTimeUnixNano: '2', attributes: [{ key: 'ai.response.text', value: { stringValue: 'historical-backfill-token' } }] }] }] }] }))) + await storeRuntime.runPromise(Effect.flatMap(TelemetryStore, (store) => store.ingestTraces({ resourceSpans: [{ resource: { attributes: [{ key: 'service.name', value: { stringValue: 'historical-fts' } }] }, scopeSpans: [{ spans: [{ traceId: 'historical-trace', spanId: 'historical-span', name: 'ai.generateText', startTimeUnixNano: '1', endTimeUnixNano: '2', attributes: [{ key: 'ai.response.text', value: { stringValue: 'historical-backfill-token' } }] }] }] }] }))) await storeRuntime.dispose() ` const seed = Bun.spawn({ cmd: [process.execPath, "-e", seedScript], cwd: process.cwd(), env: { ...process.env, MOTEL_OTEL_DB_PATH: historicalPath }, stdout: "ignore", stderr: "pipe" }) @@ -391,7 +391,7 @@ describe("motel telemetry store", () => { damage.query(`DELETE FROM motel_maintenance WHERE key = 'span_attr_fts_v1'`).run() damage.close() - const repair = Bun.spawn({ cmd: [process.execPath, "-e", `const { Effect } = await import('effect'); const { storeRuntime } = await import('./src/runtime.ts'); const { TelemetryStore } = await import('./src/services/TelemetryStore.ts'); await storeRuntime.runPromise(Effect.flatMap(TelemetryStore.asEffect(), (store) => store.listServices)); await Bun.sleep(500); await storeRuntime.dispose()`], cwd: process.cwd(), env: { ...process.env, MOTEL_OTEL_DB_PATH: historicalPath }, stdout: "ignore", stderr: "pipe" }) + const repair = Bun.spawn({ cmd: [process.execPath, "-e", `const { Effect } = await import('effect'); const { storeRuntime } = await import('./src/runtime.ts'); const { TelemetryStore } = await import('./src/services/TelemetryStore.ts'); await storeRuntime.runPromise(Effect.flatMap(TelemetryStore, (store) => store.listServices)); await Bun.sleep(500); await storeRuntime.dispose()`], cwd: process.cwd(), env: { ...process.env, MOTEL_OTEL_DB_PATH: historicalPath }, stdout: "ignore", stderr: "pipe" }) expect(await repair.exited).toBe(0) const probe = new Database(historicalPath, { readonly: true }) @@ -407,7 +407,7 @@ describe("motel telemetry store", () => { it("filters traces by attr.* fields", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.searchTraces({ serviceName: "test-api", attributeFilters: { @@ -424,7 +424,7 @@ describe("motel telemetry store", () => { it("looks up a span directly by spanId", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => store.getSpan("child-1")).pipe( + Effect.flatMap(TelemetryStore, (store) => store.getSpan("child-1")).pipe( Effect.provideService(References.MinimumLogLevel, "None"), ), ) @@ -435,9 +435,45 @@ describe("motel telemetry store", () => { expect(result?.span.depth).toBe(1) }) + it("returns trace details through the query worker when spans share empty event arrays", async () => { + const workerDbPath = join(tempDir, "query-worker-shared-events.sqlite") + const script = ` + const { Effect, ManagedRuntime } = await import("effect") + const { storeRuntime } = await import("./src/runtime.ts") + const { TelemetryStore, TelemetryStoreReadonly } = await import("./src/services/TelemetryStore.ts") + await storeRuntime.runPromise(Effect.flatMap(TelemetryStore, (store) => store.ingestTraces({ + resourceSpans: [{ + resource: { attributes: [{ key: "service.name", value: { stringValue: "worker-repro" } }] }, + scopeSpans: [{ spans: [ + { traceId: "shared-trace", spanId: "root", name: "root", startTimeUnixNano: "1", endTimeUnixNano: "3" }, + { traceId: "shared-trace", spanId: "child", parentSpanId: "root", name: "child", startTimeUnixNano: "2", endTimeUnixNano: "3" }, + ] }], + }], + }))) + await storeRuntime.dispose() + const { TelemetryQueryLive } = await import("./src/services/TelemetryQuery.ts") + const queryRuntime = ManagedRuntime.make(TelemetryQueryLive) + try { + const trace = await queryRuntime.runPromise(Effect.flatMap(TelemetryStoreReadonly, (store) => store.getTrace("shared-trace"))) + if (trace?.spans.length !== 2) throw new Error("Expected two spans") + } finally { + await queryRuntime.dispose() + } + ` + const child = Bun.spawn({ + cmd: [process.execPath, "-e", script], + cwd: process.cwd(), + env: { ...process.env, MOTEL_OTEL_DB_PATH: workerDbPath }, + stdout: "ignore", + stderr: "pipe", + }) + const [exitCode, stderr] = await Promise.all([child.exited, new Response(child.stderr).text()]) + expect(exitCode, stderr).toBe(0) + }) + it("uses the canonical earliest root when directly looking up a later root span", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => store.getSpan("ai-stream-2")).pipe( + Effect.flatMap(TelemetryStore, (store) => store.getSpan("ai-stream-2")).pipe( Effect.provideService(References.MinimumLogLevel, "None"), ), ) @@ -449,7 +485,7 @@ describe("motel telemetry store", () => { it("filters logs by spanId", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.searchLogs({ spanId: "child-1", }), @@ -462,7 +498,7 @@ describe("motel telemetry store", () => { it("searches spans by operation, parent operation, and attr filters", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.searchSpans({ serviceName: "test-api", operation: "tool.call", @@ -482,7 +518,7 @@ describe("motel telemetry store", () => { it("lists spans for a trace", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => store.listTraceSpans("trace-1")).pipe( + Effect.flatMap(TelemetryStore, (store) => store.listTraceSpans("trace-1")).pipe( Effect.provideService(References.MinimumLogLevel, "None"), ), ) @@ -497,7 +533,7 @@ describe("motel telemetry store", () => { it("aggregates trace stats by operation", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.traceStats({ groupBy: "operation", agg: "avg_duration", @@ -515,7 +551,7 @@ describe("motel telemetry store", () => { it("aggregates log stats by severity", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.logStats({ groupBy: "severity", agg: "count", @@ -541,7 +577,7 @@ describe("motel telemetry store", () => { it("lists trace summaries without loading spans", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.listTraceSummaries(null), ).pipe(Effect.provideService(References.MinimumLogLevel, "None")), ) @@ -559,7 +595,7 @@ describe("motel telemetry store", () => { it("lists trace summaries filtered by service", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.listTraceSummaries("test-api"), ).pipe(Effect.provideService(References.MinimumLogLevel, "None")), ) @@ -569,7 +605,7 @@ describe("motel telemetry store", () => { it("searches trace summaries with status filter", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.searchTraceSummaries({ serviceName: "test-api", status: "error", @@ -583,7 +619,7 @@ describe("motel telemetry store", () => { it("searches trace summaries with attribute filters", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.searchTraceSummaries({ serviceName: "test-api", attributeFilters: { sessionID: "session-1" }, @@ -597,7 +633,7 @@ describe("motel telemetry store", () => { it("searches trace summaries with operation filter", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.searchTraceSummaries({ serviceName: "test-api", operation: "tool.call", @@ -611,7 +647,7 @@ describe("motel telemetry store", () => { it("filters logs by severity", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.searchLogs({ serviceName: "test-api", severity: "ERROR" }), ).pipe(Effect.provideService(References.MinimumLogLevel, "None")), ) @@ -623,7 +659,7 @@ describe("motel telemetry store", () => { it("filters logs by severity case-insensitively", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.searchLogs({ serviceName: "test-api", severity: "error" }), ).pipe(Effect.provideService(References.MinimumLogLevel, "None")), ) @@ -632,9 +668,84 @@ describe("motel telemetry store", () => { expect(result[0]?.severityText).toBe("ERROR") }) + describe("mixed-case stored log severities", () => { + const serviceName = "severity-case-test" + const severities = ["Info", "INFO", "info", "Error"] + + beforeAll(async () => { + await storeRuntime.runPromise(Effect.flatMap(TelemetryStore, (store) => + store.ingestLogs({ + resourceLogs: [{ + resource: { attributes: [{ key: "service.name", value: { stringValue: serviceName } }] }, + scopeLogs: [{ logRecords: severities.map((severityText, index) => ({ + timeUnixNano: String(BigInt(Date.now()) * 1_000_000n), + severityText, + body: { stringValue: `severity-case-${index}` }, + attributes: [{ key: "batch", value: { stringValue: index < 2 ? "included" : "excluded" } }], + })) }], + }], + }), + ).pipe(Effect.provideService(References.MinimumLogLevel, "None"))) + }) + + for (const severity of ["INFO", "Info", "info"]) { + it(`filters ${severity} without changing returned severity text`, async () => { + const logs = await storeRuntime.runPromise(Effect.flatMap(TelemetryStore, (store) => + store.searchLogs({ serviceName, severity }), + )) + expect(logs.map((log) => log.severityText).sort()).toEqual(["INFO", "Info", "info"]) + }) + } + + it("groups severity variants in SQL stats", async () => { + const stats = await storeRuntime.runPromise(Effect.flatMap(TelemetryStore, (store) => + store.logStats({ serviceName, groupBy: "severity", agg: "count" }), + )) + expect(stats).toEqual([ + { group: "INFO", value: 3, count: 3 }, + { group: "ERROR", value: 1, count: 1 }, + ]) + }) + + it("groups severity variants in attribute-filtered stats", async () => { + const stats = await storeRuntime.runPromise(Effect.flatMap(TelemetryStore, (store) => + store.logStats({ serviceName, groupBy: "severity", agg: "count", attributeFilters: { batch: "included" } }), + )) + expect(stats).toEqual([{ group: "INFO", value: 2, count: 2 }]) + }) + + it("indexes case-insensitive severity searches", () => { + const probe = new Database(dbPath, { readonly: true }) + try { + const plan = probe.query<{ detail: string }, [string, number, number]>(` + EXPLAIN QUERY PLAN SELECT * FROM logs + WHERE severity_text = ? COLLATE NOCASE AND timestamp_ms >= ? + ORDER BY timestamp_ms DESC, id DESC LIMIT ? + `).all("INFO", 0, 80) + expect(plan.some((row) => row.detail.includes("idx_logs_severity_nocase_time (severity_text=?"))).toBe(true) + } finally { + probe.close() + } + }) + + it("groups severity facets without changing persisted text", async () => { + const facets = await storeRuntime.runPromise(Effect.flatMap(TelemetryStore, (store) => + store.listFacets({ serviceName, type: "logs", field: "severity" }), + )) + expect(facets).toEqual([{ value: "INFO", count: 3 }, { value: "ERROR", count: 1 }]) + const probe = new Database(dbPath, { readonly: true }) + try { + expect(probe.query("SELECT severity_text FROM logs WHERE service_name = ? ORDER BY id").all(serviceName)) + .toEqual(severities.map((severity_text) => ({ severity_text }))) + } finally { + probe.close() + } + }) + }) + it("searches log body case-insensitively", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.searchLogs({ serviceName: "test-api", body: "STREAM FAILED" }), ).pipe(Effect.provideService(References.MinimumLogLevel, "None")), ) @@ -645,7 +756,7 @@ describe("motel telemetry store", () => { it("searches spans by traceId", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.searchSpans({ traceId: "trace-1" }), ).pipe(Effect.provideService(References.MinimumLogLevel, "None")), ) @@ -656,7 +767,7 @@ describe("motel telemetry store", () => { it("searches spans with attrContains substring filter", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.searchSpans({ serviceName: "test-api", attributeContainsFilters: { sessionID: "session" }, @@ -670,7 +781,7 @@ describe("motel telemetry store", () => { it("searches spans with attrContains case-insensitively", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.searchSpans({ serviceName: "test-api", attributeContainsFilters: { modelID: "GPT" }, @@ -684,7 +795,7 @@ describe("motel telemetry store", () => { it("searches logs with attrContains substring filter", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.searchLogs({ serviceName: "test-api", attributeContainsFilters: { tool: "sea" }, @@ -698,7 +809,7 @@ describe("motel telemetry store", () => { it("combines severity and body filters", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.searchLogs({ serviceName: "test-api", severity: "INFO", body: "tool" }), ).pipe(Effect.provideService(References.MinimumLogLevel, "None")), ) @@ -709,7 +820,7 @@ describe("motel telemetry store", () => { it("computes facet status without N+1 queries", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.listFacets({ type: "traces", field: "status", @@ -726,7 +837,7 @@ describe("motel telemetry store", () => { it("computes logStats with SQL aggregation", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.logStats({ groupBy: "service", agg: "count", @@ -742,7 +853,7 @@ describe("motel telemetry store", () => { it("computes traceStats count via SQL", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.traceStats({ groupBy: "status", agg: "count", @@ -760,7 +871,7 @@ describe("motel telemetry store", () => { it("computes traceStats error_rate via SQL", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.traceStats({ groupBy: "service", agg: "error_rate", @@ -807,7 +918,7 @@ describe("motel telemetry store", () => { it("searches AI calls and returns compact summaries", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.searchAiCalls({}), ).pipe(Effect.provideService(References.MinimumLogLevel, "None")), ) @@ -830,7 +941,7 @@ describe("motel telemetry store", () => { it("dedupes nested doStream spans from AI summaries", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.searchAiCalls({ sessionId: "ses_test123" }), ).pipe(Effect.provideService(References.MinimumLogLevel, "None")), ) @@ -841,7 +952,7 @@ describe("motel telemetry store", () => { it("filters AI calls by model", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.searchAiCalls({ model: "claude-opus-4" }), ).pipe(Effect.provideService(References.MinimumLogLevel, "None")), ) @@ -853,7 +964,7 @@ describe("motel telemetry store", () => { it("filters AI calls by sessionId", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.searchAiCalls({ sessionId: "ses_test123" }), ).pipe(Effect.provideService(References.MinimumLogLevel, "None")), ) @@ -864,7 +975,7 @@ describe("motel telemetry store", () => { it("searches AI calls by text content", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.searchAiCalls({ text: "joke about programming" }), ).pipe(Effect.provideService(References.MinimumLogLevel, "None")), ) @@ -877,7 +988,7 @@ describe("motel telemetry store", () => { // Verifies FTS indexes ai.response.text, not just ai.prompt*. The // seeded ai-stream-2 has response "Error: rate limited". const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.searchAiCalls({ text: "rate limited" }), ).pipe(Effect.provideService(References.MinimumLogLevel, "None")), ) @@ -888,7 +999,7 @@ describe("motel telemetry store", () => { // unicode61 tokenizer is case-insensitive by default; prefix `*` // handles partial terms like `"PROG"` matching `"programming"`. const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.searchAiCalls({ text: "PROG" }), ).pipe(Effect.provideService(References.MinimumLogLevel, "None")), ) @@ -899,7 +1010,7 @@ describe("motel telemetry store", () => { // FTS5 treats `"`, `*`, `-`, `:` as operators; toFtsQuery must // strip them so raw user input never crashes the query. const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.searchAiCalls({ text: `"joke" - about:programming*` }), ).pipe(Effect.provideService(References.MinimumLogLevel, "None")), ) @@ -908,7 +1019,7 @@ describe("motel telemetry store", () => { it("filters AI calls by operation type", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.searchAiCalls({ operation: "generateText" }), ).pipe(Effect.provideService(References.MinimumLogLevel, "None")), ) @@ -919,7 +1030,7 @@ describe("motel telemetry store", () => { it("gets AI call detail with full payloads", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.getAiCall("ai-stream-1"), ).pipe(Effect.provideService(References.MinimumLogLevel, "None")), ) @@ -937,7 +1048,7 @@ describe("motel telemetry store", () => { it("returns null for non-AI span in getAiCall", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.getAiCall("root-1"), ).pipe(Effect.provideService(References.MinimumLogLevel, "None")), ) @@ -947,7 +1058,7 @@ describe("motel telemetry store", () => { it("aggregates AI call stats by model", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.aiCallStats({ groupBy: "model", agg: "count" }), ).pipe(Effect.provideService(References.MinimumLogLevel, "None")), ) @@ -961,7 +1072,7 @@ describe("motel telemetry store", () => { it("aggregates AI call stats by status", async () => { const result = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => + Effect.flatMap(TelemetryStore, (store) => store.aiCallStats({ groupBy: "status", agg: "count" }), ).pipe(Effect.provideService(References.MinimumLogLevel, "None")), ) @@ -983,7 +1094,7 @@ describe("motel telemetry store", () => { const nowNanos = BigInt(Date.now()) * 1_000_000n const oldRootNanos = nowNanos - 2n * 24n * 60n * 60n * 1_000_000_000n await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => store.ingestTraces({ + Effect.flatMap(TelemetryStore, (store) => store.ingestTraces({ resourceSpans: [{ resource: { attributes: [{ key: "service.name", value: { stringValue: "active-child-only" } }] }, scopeSpans: [{ @@ -1007,7 +1118,7 @@ describe("motel telemetry store", () => { ) const services = await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => store.listServices).pipe( + Effect.flatMap(TelemetryStore, (store) => store.listServices).pipe( Effect.provideService(References.MinimumLogLevel, "None"), ), ) diff --git a/src/ui/aiState.ts b/src/ui/aiState.ts index f073cab..f568d31 100644 --- a/src/ui/aiState.ts +++ b/src/ui/aiState.ts @@ -38,7 +38,7 @@ export const initialAiCallDetailState: AiCallDetailState = { export const aiCallDetailStateAtom = Atom.make(initialAiCallDetailState).pipe(Atom.keepAlive) export const loadAiCallDetail = (spanId: string) => - queryRuntime.runPromise(Effect.flatMap(TelemetryStoreReadonly.asEffect(), (service) => service.getAiCall(spanId))) + queryRuntime.runPromise(Effect.flatMap(TelemetryStoreReadonly, (service) => service.getAiCall(spanId))) // AI call detail cache: the `ai.prompt` payload can easily be 50KB+ and // we don't want to re-hit SQLite every time j/k moves the selection diff --git a/src/ui/atoms.ts b/src/ui/atoms.ts index 8acae05..900218d 100644 --- a/src/ui/atoms.ts +++ b/src/ui/atoms.ts @@ -1,5 +1,5 @@ import * as Atom from "effect/unstable/reactivity/Atom" -import { config } from "../config.ts" +import { config, explicitServiceName } from "../config.ts" import type { LogItem, TraceItem, TraceSummaryItem } from "../domain.ts" import type { ThemeName } from "./theme.ts" import { readLastService, readLastTheme } from "./persistence.ts" @@ -77,7 +77,7 @@ export const logStateAtom = Atom.make(initialLogState).pipe(Atom.keepAlive) export const serviceLogStateAtom = Atom.make(initialServiceLogState).pipe(Atom.keepAlive) export const selectedServiceLogIndexAtom = Atom.make(0).pipe(Atom.keepAlive) export const selectedTraceIndexAtom = Atom.make(0).pipe(Atom.keepAlive) -export const selectedTraceServiceAtom = Atom.make(readLastService() ?? config.otel.serviceName).pipe(Atom.keepAlive) +export const selectedTraceServiceAtom = Atom.make(explicitServiceName ?? readLastService() ?? config.otel.serviceName).pipe(Atom.keepAlive) export const refreshNonceAtom = Atom.make(0).pipe(Atom.keepAlive) export const noticeAtom = Atom.make(null).pipe(Atom.keepAlive) export const selectedSpanIndexAtom = Atom.make(null).pipe(Atom.keepAlive) diff --git a/src/ui/loaders.ts b/src/ui/loaders.ts index 820b227..857f0d0 100644 --- a/src/ui/loaders.ts +++ b/src/ui/loaders.ts @@ -5,10 +5,10 @@ import { TelemetryStoreReadonly } from "../services/TelemetryStore.ts" import { makeCachedLoader } from "./cachedLoader.ts" export const loadTraceServices = () => - queryRuntime.runPromise(Effect.flatMap(TelemetryStoreReadonly.asEffect(), (service) => service.listServices)) + queryRuntime.runPromise(Effect.flatMap(TelemetryStoreReadonly, (service) => service.listServices)) export const loadRecentTraceSummaries = (serviceName: string) => - queryRuntime.runPromise(Effect.flatMap(TelemetryStoreReadonly.asEffect(), (service) => service.listTraceSummaries(serviceName))) + queryRuntime.runPromise(Effect.flatMap(TelemetryStoreReadonly, (service) => service.listTraceSummaries(serviceName))) /** * Server-side trace summary search. Accepts any combination of: @@ -29,7 +29,7 @@ export const loadFilteredTraceSummaries = ( readonly aiText?: string | null }, ) => - queryRuntime.runPromise(Effect.flatMap(TelemetryStoreReadonly.asEffect(), (service) => service.searchTraceSummaries({ + queryRuntime.runPromise(Effect.flatMap(TelemetryStoreReadonly, (service) => service.searchTraceSummaries({ serviceName, attributeFilters: options.attributeFilters, aiText: options.aiText ?? null, @@ -37,10 +37,10 @@ export const loadFilteredTraceSummaries = ( }))) export const loadTraceAttributeKeys = (serviceName: string) => - queryRuntime.runPromise(Effect.flatMap(TelemetryStoreReadonly.asEffect(), (service) => service.listFacets({ type: "traces", field: "attribute_keys", serviceName, limit: 200 }))) + queryRuntime.runPromise(Effect.flatMap(TelemetryStoreReadonly, (service) => service.listFacets({ type: "traces", field: "attribute_keys", serviceName, limit: 200 }))) export const loadTraceAttributeValues = (serviceName: string, key: string) => - queryRuntime.runPromise(Effect.flatMap(TelemetryStoreReadonly.asEffect(), (service) => service.listFacets({ type: "traces", field: "attribute_values", serviceName, key, limit: 200 }))) + queryRuntime.runPromise(Effect.flatMap(TelemetryStoreReadonly, (service) => service.listFacets({ type: "traces", field: "attribute_values", serviceName, key, limit: 200 }))) // --------------------------------------------------------------------------- // Facet cache (drives the `f` attribute filter picker) @@ -92,10 +92,10 @@ export const invalidateFacetCaches = () => { } export const loadTraceDetail = (traceId: string) => - queryRuntime.runPromise(Effect.flatMap(TelemetryStoreReadonly.asEffect(), (service) => service.getTrace(traceId))) + queryRuntime.runPromise(Effect.flatMap(TelemetryStoreReadonly, (service) => service.getTrace(traceId))) export const loadTraceLogs = (traceId: string) => - queryRuntime.runPromise(Effect.flatMap(TelemetryStoreReadonly.asEffect(), (service) => service.listTraceLogs(traceId))) + queryRuntime.runPromise(Effect.flatMap(TelemetryStoreReadonly, (service) => service.listTraceLogs(traceId))) export const loadServiceLogs = (serviceName: string) => - queryRuntime.runPromise(Effect.flatMap(TelemetryStoreReadonly.asEffect(), (service) => service.listRecentLogs(serviceName))) + queryRuntime.runPromise(Effect.flatMap(TelemetryStoreReadonly, (service) => service.listRecentLogs(serviceName))) diff --git a/src/ui/serviceSelection.test.ts b/src/ui/serviceSelection.test.ts new file mode 100644 index 0000000..c1ba5bf --- /dev/null +++ b/src/ui/serviceSelection.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, test } from "bun:test" +import { mkdtempSync, rmSync, writeFileSync } from "node:fs" +import { tmpdir } from "node:os" +import { join } from "node:path" + +describe("initial TUI service selection", () => { + for (const remembered of [undefined, "svc-b", " \n"]) { + for (const explicit of [undefined, "", " \t ", "svc-a", " svc-a ", "motel-otel-tui"]) { + test(`env=${JSON.stringify(explicit)}, remembered=${JSON.stringify(remembered)}`, () => { + const dir = mkdtempSync(join(tmpdir(), "motel-service-selection-")) + try { + if (remembered !== undefined) writeFileSync(join(dir, "last-service.txt"), remembered) + const env = { ...process.env } + for (const key of Object.keys(env)) { + if (key.startsWith("MOTEL_")) delete env[key] + } + env.MOTEL_RUNTIME_DIR = dir + env.MOTEL_OTEL_DB_PATH = join(dir, "telemetry.sqlite") + if (explicit !== undefined) env.MOTEL_OTEL_SERVICE_NAME = explicit + + // Fresh processes exercise import-time config and persistence without module-cache leakage. + const result = Bun.spawnSync([process.execPath, "--no-env-file", "--eval", ` + import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry" + import { config } from "./src/config.ts" + import { selectedTraceServiceAtom } from "./src/ui/atoms.ts" + const registry = AtomRegistry.make() + const initial = registry.get(selectedTraceServiceAtom) + registry.set(selectedTraceServiceAtom, "switched-service") + console.log(JSON.stringify({ + initial, + serviceName: config.otel.serviceName, + switched: registry.get(selectedTraceServiceAtom), + })) + registry.dispose() + `], { cwd: join(import.meta.dir, "../.."), env, timeout: 5_000 }) + + expect(result.exitCode).toBe(0) + expect(result.stderr.toString()).toBe("") + expect(JSON.parse(result.stdout.toString())).toEqual({ + initial: explicit?.trim() || remembered?.trim() || "motel-otel-tui", + serviceName: explicit?.trim() || "motel-otel-tui", + switched: "switched-service", + }) + } finally { + rmSync(dir, { recursive: true, force: true }) + } + }) + } + } +}) diff --git a/src/ui/traceSortNav.repro.seed.ts b/src/ui/traceSortNav.repro.seed.ts index 432c4e7..8a94e35 100644 --- a/src/ui/traceSortNav.repro.seed.ts +++ b/src/ui/traceSortNav.repro.seed.ts @@ -57,6 +57,6 @@ const resourceSpans = specs.map((spec) => ({ })) await storeRuntime.runPromise( - Effect.flatMap(TelemetryStore.asEffect(), (store) => store.ingestTraces({ resourceSpans })), + Effect.flatMap(TelemetryStore, (store) => store.ingestTraces({ resourceSpans })), ) process.exit(0) diff --git a/src/ui/waterfallNav.repro.seed.ts b/src/ui/waterfallNav.repro.seed.ts index 5648112..a0ccfcc 100644 --- a/src/ui/waterfallNav.repro.seed.ts +++ b/src/ui/waterfallNav.repro.seed.ts @@ -48,7 +48,7 @@ const span = ( endTimeUnixNano: ms(endMs), }) -const program = Effect.flatMap(TelemetryStore.asEffect(), (store) => +const program = Effect.flatMap(TelemetryStore, (store) => store.ingestTraces({ resourceSpans: [ { diff --git a/src/workerd.integration.test.ts b/src/workerd.integration.test.ts new file mode 100644 index 0000000..151bab9 --- /dev/null +++ b/src/workerd.integration.test.ts @@ -0,0 +1,147 @@ +import { expect, test } from "bun:test" +import { mkdtemp, mkdir, readFile, writeFile, rm, copyFile } from "node:fs/promises" +import { tmpdir } from "node:os" +import path from "node:path" +import { createServer } from "node:net" + +const root = path.resolve(import.meta.dir, "..") +const freePort = () => + new Promise((resolve, reject) => { + const socket = createServer() + socket.once("error", reject) + socket.listen(0, "127.0.0.1", () => { + const address = socket.address() + if (address === null || typeof address === "string") return reject(new Error("No TCP address")) + socket.close((error) => (error ? reject(error) : resolve(address.port))) + }) + }) + +/** Exercise the built collector over HTTP, using a real workerd process and persistent disk. */ +test("workerd ingests, searches, retains and recovers telemetry after process loss", async () => { + const directory = await mkdtemp(path.join(tmpdir(), "motel-workerd-")) + const port = await freePort() + const origin = `http://127.0.0.1:${port}` + await mkdir(path.join(directory, "data")) + await copyFile(path.join(root, "dist/workerd/motel.mjs"), path.join(directory, "motel.mjs")) + const base = await readFile(path.join(root, "workerd/motel.capnp"), "utf8") + const config = base + .replace('embed "../dist/workerd/motel.mjs"', 'embed "motel.mjs"') + .replace('path = "./web/dist"', `path = ${JSON.stringify(path.join(root, "web/dist"))}`) + .replace('path = "./.local/workerd-data"', `path = ${JSON.stringify(path.join(directory, "data"))}`) + .replace("127.0.0.1:27687", `127.0.0.1:${port}`) + .replace( + '(name = "ASSETS", service = "assets")', + '(name = "ASSETS", service = "assets"), (name = "MOTEL_OTEL_RETENTION_INTERVAL_SECONDS", text = "1")', + ) + await writeFile(path.join(directory, "config.capnp"), config) + const start = () => + Bun.spawn([path.join(root, "node_modules/.bin/workerd"), "serve", path.join(directory, "config.capnp"), "--experimental"], { + cwd: root, + stdout: Bun.file(path.join(directory, "server.log")), + stderr: Bun.file(path.join(directory, "server.log")), + }) + let server = start() + const waitFor = async (predicate: () => Promise) => { + for (let i = 0; i < 150; i++) { + if (server.exitCode !== null) throw new Error(await readFile(path.join(directory, "server.log"), "utf8")) + if (await predicate().catch(() => false)) return + await Bun.sleep(100) + } + throw new Error("Collector condition timed out\n" + (await readFile(path.join(directory, "server.log"), "utf8"))) + } + const post = async (route: string, value: unknown) => { + const response = await fetch(origin + route, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(value), + }) + expect(response.status).toBe(200) + return response.json() + } + const trace = "0123456789abcdef0123456789abcdef" + const oldTrace = "fedcba9876543210fedcba9876543210" + const span = "0123456789abcdef" + const resource = { attributes: [{ key: "service.name", value: { stringValue: "workerd-test" } }] } + const spans = (traceId: string, at: number) => ({ + resourceSpans: [ + { + resource, + scopeSpans: [ + { + spans: [ + { + traceId, + spanId: span, + name: "persisted operation", + startTimeUnixNano: String(BigInt(at - 1) * 1000000n), + endTimeUnixNano: String(BigInt(at) * 1000000n), + attributes: [{ key: "runtime", value: { stringValue: "workerd" } }], + }, + ], + }, + ], + }, + ], + }) + try { + await waitFor(async () => (await fetch(origin + "/api/health")).ok) + expect(await post("/v1/traces", spans(trace, Date.now()))).toEqual({ insertedSpans: 1 }) + expect( + await post("/v1/logs", { + resourceLogs: [ + { + resource, + scopeLogs: [ + { + logRecords: [ + { + timeUnixNano: String(BigInt(Date.now()) * 1000000n), + severityText: "INFO", + body: { stringValue: "durable log" }, + traceId: trace, + spanId: span, + }, + ], + }, + ], + }, + ], + }), + ).toEqual({ insertedLogs: 1 }) + expect(await (await fetch(origin + "/api/spans/search?attr.runtime=workerd")).json()).toMatchObject({ + data: [{ span: { spanId: span, operationName: "persisted operation" } }], + }) + expect(await (await fetch(origin + "/api/logs/search?service=workerd-test&body=durable")).json()).toMatchObject({ + data: [{ body: "durable log" }], + }) + const schema = await (await fetch(origin + "/openapi.json")).json() + expect(schema).toHaveProperty("paths./v1/traces") + const malformed = await fetch(origin + "/v1/traces", { + method: "POST", + headers: { "content-type": "application/json" }, + body: '{"resourceSpans":[{"scopeSpans":[{"spans":[{"traceId":42}]}]}]}', + }) + expect(malformed.ok).toBe(false) + // Include a six-day trace to prove the seven-day query policy survives the port. + const sixDays = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + await post("/v1/traces", spans(sixDays, Date.now() - 6 * 86400000)) + expect(await (await fetch(origin + "/api/traces?service=workerd-test&lookback=7d")).json()).toMatchObject({ meta: { returned: 2 } }) + await post("/v1/traces", spans(oldTrace, Date.now() - 8 * 86400000)) + await waitFor(async () => (await fetch(origin + "/api/traces/" + oldTrace)).status === 404) + // Acknowledged writes must survive SIGKILL, not only graceful shutdown. + server.kill(9) + await server.exited + server = start() + await waitFor(async () => (await fetch(origin + "/api/health")).ok) + expect(await (await fetch(origin + "/api/traces/" + trace)).json()).toMatchObject({ + data: { traceId: trace, rootOperationName: "persisted operation", spanCount: 1 }, + }) + expect(await (await fetch(origin + "/api/logs/search?service=workerd-test&body=durable")).json()).toMatchObject({ + data: [{ body: "durable log", traceId: trace }], + }) + } finally { + server.kill() + await server.exited + await rm(directory, { recursive: true, force: true }) + } +}, 30000) diff --git a/src/workerd.ts b/src/workerd.ts new file mode 100644 index 0000000..a3fa047 --- /dev/null +++ b/src/workerd.ts @@ -0,0 +1,117 @@ +import type { DurableObjectState } from "@cloudflare/workers-types" +import { Effect, Layer, Schema } from "effect" +import { HttpRouter, HttpServer } from "effect/unstable/http" +import { config } from "./config.js" +import { motelApi } from "./httpServer.js" +import { AsyncIngest } from "./services/AsyncIngest.js" +import { IngestError } from "./services/ingestRpc.js" +import { makeTelemetryStoreEffect, TelemetryStoreReadonly, type TelemetryStore } from "./services/TelemetryStore.js" +import { workerdDatabase } from "./services/TelemetryStoreWorkerd.js" +import { TraceExport, LogExport } from "./otlpSchema.js" +import documents from "motel:documents" +import pkg from "../package.json" with { type: "json" } + +interface Env { + readonly STORE: { getByName(name: string): { fetch(request: Request): Promise } } + readonly ASSETS: { fetch(request: Request): Promise } +} + +const health = () => ({ + ok: true, + service: "motel-local-server", + databasePath: config.otel.databasePath, + pid: 0, + url: config.otel.baseUrl, + workdir: "/", + startedAt: new Date().toISOString(), + version: pkg.version, +}) + +/** One durable SQLite owner for OTLP ingestion, queries and bounded alarm maintenance. */ +export class MotelCollector { + private readonly state: DurableObjectState + private readonly store: Promise + + constructor(state: DurableObjectState) { + this.state = state + // The actor owns one connection and bootstrap. No native handle or detached fiber + // escapes this scoped construction; durable alarms own subsequent maintenance. + this.store = state.blockConcurrencyWhile(() => + Effect.runPromise(Effect.scoped(makeTelemetryStoreEffect(workerdDatabase(state.storage), { readonly: false, runRetention: false }))), + ) + } + + /** Serve through the same typed HTTP routes as the native collector. */ + async fetch(request: Request): Promise { + const store = await this.store + if ((await this.state.storage.getAlarm()) === null) { + await this.state.storage.setAlarm(Date.now() + config.otel.retentionIntervalSeconds * 1000) + } + const ingest = Layer.succeed(AsyncIngest, { + ingestTraces: ({ payload }) => + Schema.decodeUnknownEffect(TraceExport)(payload).pipe( + Effect.mapError(() => new IngestError({ message: "Invalid trace payload" })), + Effect.flatMap((parsed) => + store.ingestTraces(parsed).pipe(Effect.mapError(() => new IngestError({ message: "Trace storage failed" }))), + ), + ), + ingestLogs: ({ payload }) => + Schema.decodeUnknownEffect(LogExport)(payload).pipe( + Effect.mapError(() => new IngestError({ message: "Invalid log payload" })), + Effect.flatMap((parsed) => + store.ingestLogs(parsed).pipe(Effect.mapError(() => new IngestError({ message: "Log storage failed" }))), + ), + ), + }) + const handler = HttpRouter.toWebHandler( + motelApi({ health, docs: documents }).pipe( + HttpRouter.provideRequest(ingest), + HttpRouter.provideRequest(Layer.succeed(TelemetryStoreReadonly, store)), + Layer.provide(HttpServer.layerServices), + ), + { disableLogger: true }, + ) + try { + return await handler.handler(request) + } finally { + await handler.dispose() + } + } + + /** Repeat maintenance even when no ingest requests arrive. */ + async alarm(): Promise { + try { + await Effect.runPromise((await this.store).runRetentionNow) + } finally { + await this.state.storage.setAlarm(Date.now() + config.otel.retentionIntervalSeconds * 1000) + } + } +} + +const contentTypes: Readonly> = { + js: "text/javascript", + css: "text/css", + html: "text/html", + svg: "image/svg+xml", + png: "image/png", + ico: "image/x-icon", +} + +/** workerd entry point. Health bypasses SQLite; API requests share the single durable writer. */ +export default { + async fetch(request: Request, env: Env): Promise { + const url = new URL(request.url) + if (url.pathname === "/api/health") return Response.json(health()) + if (/^\/(api|v1|trace)(\/|$)/.test(url.pathname) || ["/openapi.json", "/docs"].includes(url.pathname)) { + return env.STORE.getByName("collector").fetch(request) + } + let asset = await env.ASSETS.fetch(request) + if (asset.status === 404 || url.pathname === "/") { + url.pathname = "/index.html" + asset = await env.ASSETS.fetch(new Request(url, request)) + } + const headers = new Headers(asset.headers) + headers.set("content-type", contentTypes[url.pathname.split(".").at(-1) ?? ""] ?? "application/octet-stream") + return new Response(asset.body, { status: asset.status, headers }) + }, +} diff --git a/src/workerdAssets.d.ts b/src/workerdAssets.d.ts new file mode 100644 index 0000000..8731e24 --- /dev/null +++ b/src/workerdAssets.d.ts @@ -0,0 +1,4 @@ +declare module "motel:documents" { + const documents: Readonly> + export default documents +} diff --git a/web/package.json b/web/package.json index 1304b45..aa8a4cb 100644 --- a/web/package.json +++ b/web/package.json @@ -8,21 +8,21 @@ "preview": "vite preview" }, "dependencies": { - "@effect/atom-react": "^4.0.0-beta.49", - "@tailwindcss/vite": "^4.2.2", - "@tanstack/react-virtual": "^3.13.23", - "effect": "^4.0.0-beta.49", - "react": "^19.2.5", - "react-dom": "^19.2.5", - "react-router-dom": "^7.14.1", + "@effect/atom-react": "4.0.0-beta.90", + "@tailwindcss/vite": "^4.3.1", + "@tanstack/react-virtual": "^3.14.3", + "effect": "4.0.0-beta.90", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "react-router-dom": "^7.18.0", "scheduler": "^0.27.0", - "tailwindcss": "^4.2.2" + "tailwindcss": "^4.3.1" }, "devDependencies": { - "@types/react": "^19.2.14", + "@types/react": "^19.2.17", "@types/react-dom": "^19.1.0", - "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", - "vite": "^8.0.8" + "@vitejs/plugin-react": "^6.0.3", + "typescript": "^6.0.3", + "vite": "^8.1.0" } } diff --git a/workerd/Dockerfile b/workerd/Dockerfile new file mode 100644 index 0000000..2664f94 --- /dev/null +++ b/workerd/Dockerfile @@ -0,0 +1,18 @@ +FROM oven/bun:1.3.11 AS build +WORKDIR /app +COPY package.json bun.lock ./ +COPY web/package.json ./web/package.json +RUN bun install --frozen-lockfile +COPY . . +RUN bun run web:build && bun run workerd:build + +FROM gcr.io/distroless/cc-debian12:nonroot +WORKDIR /app +COPY --from=build /app/node_modules/workerd/bin/workerd /app/workerd +COPY --from=build /app/dist/workerd /app/dist/workerd +COPY --from=build /app/web/dist /app/web/dist +COPY --from=build /app/workerd/motel.capnp /app/config/motel.capnp +COPY --from=build --chown=65532:65532 /app/.local/workerd-data /app/.local/workerd-data +VOLUME ["/app/.local/workerd-data"] +EXPOSE 4318 +ENTRYPOINT ["/app/workerd", "serve", "/app/config/motel.capnp", "--experimental", "--socket-addr=http=0.0.0.0:4318"] diff --git a/workerd/README.md b/workerd/README.md new file mode 100644 index 0000000..3b913f8 --- /dev/null +++ b/workerd/README.md @@ -0,0 +1,40 @@ +# Motel on workerd + +The collector can run without Bun or Node. The Bun CLI and TUI remain available. +Both runtimes share the HTTP routes, SQLite schema, OTLP decoder, searches and +retention logic. A single durable object owns the workerd SQLite connection. +Durable alarms run bounded retention and historical FTS backfill while idle. + +Build and run from the repository root: + +```sh +bun install --frozen-lockfile +bun run web:build +bun run workerd:build +bun run workerd:serve +``` + +The example listens on `127.0.0.1:27687`. Its data is under +`.local/workerd-data/`; retain that directory across restarts. API and OTLP paths +match the native collector. The build uses Bun, but the running collector only +needs workerd, `dist/workerd`, `web/dist`, and the configuration file. + +The workerd version is pinned to Executor's existing runtime. Integrators should +copy this worker module into their workerd configuration and share that binary. +The worker needs a SQLite-enabled `MotelCollector` namespace named `STORE`, a +read-only disk service named `ASSETS`, and persistent local disk storage. +Settings use the same `MOTEL_OTEL_*` names, supplied as text bindings. Health uses +PID 0 because a worker does not own an operating-system process. + +`bun run workerd:test` runs the built worker as a real process. It verifies HTTP +trace/log ingestion, searches, seven-day retrieval, alarm retention, malformed +payload refusal, and retained data after SIGKILL. Run `bun run workerd:build` +after source changes before this test. + +Motel telemetry is disposable. This runtime starts a fresh durable store and does +not import the old native collector file. Keep the workerd directory if telemetry +should survive restarts; it can also be discarded independently of product data. + +When embedded in Executor, give Motel its own disk service and directory. It must +not share the product database, migration journal, backup, or readiness gate. +Sharing the workerd executable does not mean sharing a datastore. diff --git a/workerd/motel.capnp b/workerd/motel.capnp new file mode 100644 index 0000000..b92f047 --- /dev/null +++ b/workerd/motel.capnp @@ -0,0 +1,19 @@ +using Workerd = import "/workerd/workerd.capnp"; +const config :Workerd.Config = ( + services = [ + (name = "motel", worker = ( + modules = [(name = "motel.mjs", esModule = embed "../dist/workerd/motel.mjs")], + compatibilityDate = "2026-09-01", + compatibilityFlags = ["nodejs_compat"], + bindings = [ + (name = "STORE", durableObjectNamespace = "MotelCollector"), + (name = "ASSETS", service = "assets") + ], + durableObjectNamespaces = [(className = "MotelCollector", uniqueKey = "motel", enableSql = true)], + durableObjectStorage = (localDisk = "data") + )), + (name = "assets", disk = (path = "./web/dist")), + (name = "data", disk = (path = "./.local/workerd-data", writable = true)) + ], + sockets = [(name = "http", address = "127.0.0.1:27687", http = (), service = "motel")] +);