diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca55937e..271efbce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,30 @@ jobs: - name: Build web run: pnpm --filter @different-ai/openwork build:web + build-den-control-plane: + name: Build Den control plane + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.27.0 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build den control plane + run: pnpm --filter @openwork/den-control-plane build + build-orchestrator-binary: name: Build openwork orchestrator binary runs-on: ubuntu-latest diff --git a/.github/workflows/deploy-den-control-plane.yml b/.github/workflows/deploy-den-control-plane.yml new file mode 100644 index 00000000..203db90d --- /dev/null +++ b/.github/workflows/deploy-den-control-plane.yml @@ -0,0 +1,131 @@ +name: Deploy Den Control Plane + +on: + push: + branches: + - dev + paths: + - "services/den-control-plane/**" + - "services/den-worker-runtime/**" + - ".github/workflows/deploy-den-control-plane.yml" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: deploy-den-control-plane-${{ github.ref }} + cancel-in-progress: true + +jobs: + deploy: + runs-on: ubuntu-latest + if: github.repository == 'different-ai/openwork' + steps: + - name: Validate required secrets + env: + RENDER_API_KEY: ${{ secrets.RENDER_API_KEY }} + RENDER_DEN_CONTROL_PLANE_SERVICE_ID: ${{ secrets.RENDER_DEN_CONTROL_PLANE_SERVICE_ID }} + RENDER_OWNER_ID: ${{ secrets.RENDER_OWNER_ID }} + DEN_DATABASE_URL: ${{ secrets.DEN_DATABASE_URL }} + DEN_BETTER_AUTH_SECRET: ${{ secrets.DEN_BETTER_AUTH_SECRET }} + run: | + missing=0 + for key in RENDER_API_KEY RENDER_DEN_CONTROL_PLANE_SERVICE_ID RENDER_OWNER_ID DEN_DATABASE_URL DEN_BETTER_AUTH_SECRET; do + if [ -z "${!key}" ]; then + echo "::error::Missing required secret: $key" + missing=1 + fi + done + if [ "$missing" -ne 0 ]; then + exit 1 + fi + + - name: Sync Render env vars and deploy latest commit + env: + RENDER_API_KEY: ${{ secrets.RENDER_API_KEY }} + RENDER_DEN_CONTROL_PLANE_SERVICE_ID: ${{ secrets.RENDER_DEN_CONTROL_PLANE_SERVICE_ID }} + RENDER_OWNER_ID: ${{ secrets.RENDER_OWNER_ID }} + DEN_DATABASE_URL: ${{ secrets.DEN_DATABASE_URL }} + DEN_BETTER_AUTH_SECRET: ${{ secrets.DEN_BETTER_AUTH_SECRET }} + DEN_RENDER_WORKER_OPENWORK_VERSION: ${{ vars.DEN_RENDER_WORKER_OPENWORK_VERSION }} + run: | + python3 <<'PY' + import json + import os + import time + import urllib.error + import urllib.request + + api_key = os.environ["RENDER_API_KEY"] + service_id = os.environ["RENDER_DEN_CONTROL_PLANE_SERVICE_ID"] + owner_id = os.environ["RENDER_OWNER_ID"] + openwork_version = os.environ.get("DEN_RENDER_WORKER_OPENWORK_VERSION") or "0.11.113" + + headers = { + "Authorization": f"Bearer {api_key}", + "Accept": "application/json", + "Content-Type": "application/json", + } + + def request(method: str, path: str, body=None): + url = f"https://api.render.com/v1{path}" + data = None + if body is not None: + data = json.dumps(body).encode("utf-8") + req = urllib.request.Request(url, data=data, method=method, headers=headers) + try: + with urllib.request.urlopen(req, timeout=60) as resp: + text = resp.read().decode("utf-8") + return resp.status, json.loads(text) if text else None + except urllib.error.HTTPError as err: + text = err.read().decode("utf-8", "replace") + raise RuntimeError(f"{method} {path} failed ({err.code}): {text[:600]}") + + _, service = request("GET", f"/services/{service_id}") + service_url = (service.get("serviceDetails") or {}).get("url") + if not service_url: + raise RuntimeError(f"Render service {service_id} has no public URL") + + env_vars = [ + {"key": "DATABASE_URL", "value": os.environ["DEN_DATABASE_URL"]}, + {"key": "BETTER_AUTH_SECRET", "value": os.environ["DEN_BETTER_AUTH_SECRET"]}, + {"key": "BETTER_AUTH_URL", "value": service_url}, + {"key": "PROVISIONER_MODE", "value": "render"}, + {"key": "RENDER_API_BASE", "value": "https://api.render.com/v1"}, + {"key": "RENDER_API_KEY", "value": api_key}, + {"key": "RENDER_OWNER_ID", "value": owner_id}, + {"key": "RENDER_WORKER_REPO", "value": "https://github.com/different-ai/openwork"}, + {"key": "RENDER_WORKER_BRANCH", "value": "dev"}, + {"key": "RENDER_WORKER_ROOT_DIR", "value": "services/den-worker-runtime"}, + {"key": "RENDER_WORKER_PLAN", "value": "starter"}, + {"key": "RENDER_WORKER_REGION", "value": "oregon"}, + {"key": "RENDER_WORKER_OPENWORK_VERSION", "value": openwork_version}, + {"key": "RENDER_WORKER_NAME_PREFIX", "value": "den-worker-openwork"}, + {"key": "RENDER_PROVISION_TIMEOUT_MS", "value": "900000"}, + {"key": "RENDER_HEALTHCHECK_TIMEOUT_MS", "value": "180000"}, + {"key": "RENDER_POLL_INTERVAL_MS", "value": "5000"}, + ] + + request("PUT", f"/services/{service_id}/env-vars", env_vars) + _, deploy = request("POST", f"/services/{service_id}/deploys", {}) + deploy_id = deploy.get("id") or (deploy.get("deploy") or {}).get("id") + if not deploy_id: + raise RuntimeError(f"Unexpected deploy response: {deploy}") + + terminal = {"live", "update_failed", "build_failed", "canceled"} + started = time.time() + + while time.time() - started < 1800: + _, deploys = request("GET", f"/services/{service_id}/deploys?limit=1") + latest = deploys[0]["deploy"] if deploys else None + if latest and latest.get("id") == deploy_id and latest.get("status") in terminal: + status = latest.get("status") + if status != "live": + raise RuntimeError(f"Render deploy {deploy_id} ended with {status}") + print(f"Render deploy {deploy_id} is live at {service_url}") + break + time.sleep(10) + else: + raise RuntimeError(f"Timed out waiting for deploy {deploy_id}") + PY diff --git a/evidence/den-e2e-openwork/07-worker-health.json b/evidence/den-e2e-openwork/07-worker-health.json new file mode 100644 index 00000000..c0902a62 --- /dev/null +++ b/evidence/den-e2e-openwork/07-worker-health.json @@ -0,0 +1 @@ +{"ok": true, "version": "0.11.113", "uptimeMs": 21378} \ No newline at end of file diff --git a/evidence/den-e2e-openwork/08-web-worker-health.json b/evidence/den-e2e-openwork/08-web-worker-health.json new file mode 100644 index 00000000..bb6b5767 --- /dev/null +++ b/evidence/den-e2e-openwork/08-web-worker-health.json @@ -0,0 +1 @@ +{"ok":true,"version":"0.11.113","uptimeMs":43538} \ No newline at end of file diff --git a/evidence/den-e2e-openwork/README.md b/evidence/den-e2e-openwork/README.md new file mode 100644 index 00000000..9dc7cad9 --- /dev/null +++ b/evidence/den-e2e-openwork/README.md @@ -0,0 +1,34 @@ +# OpenWork Repo Non-Stub Render E2E Proof + +This directory captures end-to-end proof for the OpenWork-repo implementation of the Den control plane and worker provisioning flow. + +## Control-plane deployment proof + +- `render-control-plane.json` + +Includes service id, repo/branch/rootDir, public URL, and latest live deploy metadata. + +## API/CLI auth + worker provisioning flow + +- `report.json` +- `07-worker-health.json` + +`report.json` shows: + +- `POST /api/auth/sign-up/email` succeeds (`status: 200`) +- `GET /v1/me` via cookie session succeeds (`status: 200`) +- `GET /v1/me` via bearer token succeeds (`status: 200`) +- `POST /v1/workers` succeeds (`status: 201`) +- created instance is real Render (`instance.provider: "render"`) +- returned worker health endpoint succeeds (`worker_health.status: 200`) + +Sensitive token/session fields are redacted. + +## Web flow proof (Chrome MCP) + +- `webapp/01-web-signup-form.png` +- `webapp/02-web-signup-success.png` +- `webapp/03-web-worker-created-render.png` +- `08-web-worker-health.json` + +`03-web-worker-created-render.png` shows successful cloud worker creation with Render provider and healthy status, with tokens redacted in UI output. diff --git a/evidence/den-e2e-openwork/render-control-plane.json b/evidence/den-e2e-openwork/render-control-plane.json new file mode 100644 index 00000000..a6a990e9 --- /dev/null +++ b/evidence/den-e2e-openwork/render-control-plane.json @@ -0,0 +1,24 @@ +{ + "service": { + "id": "srv-d6ck5utm5p6s73er2av0", + "name": "den-control-plane-openwork-1771651835", + "repo": "https://github.com/different-ai/openwork", + "branch": "feat/den-cloud-worker-openwork-repo", + "rootDir": "services/den-control-plane", + "url": "https://den-control-plane-openwork-1771651835.onrender.com" + }, + "latestDeploy": { + "id": "dep-d6ckfgjh46gs73cjgq9g", + "commit": { + "id": "9ce8ec57806dfff085f49946b228bec8e37ad40c", + "message": "fix: redact sensitive tokens in demo output", + "createdAt": "2026-02-21T05:50:51Z" + }, + "status": "live", + "trigger": "api", + "createdAt": "2026-02-21T05:50:58.801414Z", + "updatedAt": "2026-02-21T05:53:34.758793Z", + "startedAt": "2026-02-21T05:50:58.770331Z", + "finishedAt": "2026-02-21T05:53:02.333454Z" + } +} \ No newline at end of file diff --git a/evidence/den-e2e-openwork/report.json b/evidence/den-e2e-openwork/report.json new file mode 100644 index 00000000..2da7f94c --- /dev/null +++ b/evidence/den-e2e-openwork/report.json @@ -0,0 +1,127 @@ +{ + "signup": { + "status": 200, + "body": { + "token": "REDACTED_SESSION_TOKEN", + "user": { + "name": "Den OpenWork", + "email": "den-openwork-1771653211@example.com", + "emailVerified": false, + "image": null, + "createdAt": "2026-02-21T05:53:32.550Z", + "updatedAt": "2026-02-21T05:53:32.550Z", + "id": "jhaaa6Gw9nqIL0KMdRicskHnYlCuBMNI" + } + } + }, + "bearerTokenPresent": true, + "me_cookie": { + "status": 200, + "body": { + "session": { + "expiresAt": "2026-02-28T05:53:32.639Z", + "token": "REDACTED_SESSION_TOKEN", + "createdAt": "2026-02-21T05:53:32.639Z", + "updatedAt": "2026-02-21T05:53:32.639Z", + "ipAddress": "REDACTED_IP", + "userAgent": "Python-urllib/3.9", + "userId": "jhaaa6Gw9nqIL0KMdRicskHnYlCuBMNI", + "id": "REDACTED_SESSION_ID" + }, + "user": { + "name": "Den OpenWork", + "email": "den-openwork-1771653211@example.com", + "emailVerified": false, + "image": null, + "createdAt": "2026-02-21T05:53:32.550Z", + "updatedAt": "2026-02-21T05:53:32.550Z", + "id": "jhaaa6Gw9nqIL0KMdRicskHnYlCuBMNI" + } + } + }, + "me_bearer": { + "status": 200, + "body": { + "session": { + "expiresAt": "2026-02-28T05:53:32.639Z", + "token": "REDACTED_SESSION_TOKEN", + "createdAt": "2026-02-21T05:53:32.639Z", + "updatedAt": "2026-02-21T05:53:32.639Z", + "ipAddress": "REDACTED_IP", + "userAgent": "Python-urllib/3.9", + "userId": "jhaaa6Gw9nqIL0KMdRicskHnYlCuBMNI", + "id": "REDACTED_SESSION_ID" + }, + "user": { + "name": "Den OpenWork", + "email": "den-openwork-1771653211@example.com", + "emailVerified": false, + "image": null, + "createdAt": "2026-02-21T05:53:32.550Z", + "updatedAt": "2026-02-21T05:53:32.550Z", + "id": "jhaaa6Gw9nqIL0KMdRicskHnYlCuBMNI" + } + } + }, + "create_worker": { + "status": 201, + "body": { + "worker": { + "id": "92500aab-e73a-437e-96a6-13210a555c39", + "orgId": "6187652b-cd56-49c5-82a3-78f3c4731b41", + "name": "openwork-nonstub-1771653211", + "description": "openwork repo render e2e worker", + "destination": "cloud", + "status": "healthy", + "imageVersion": "den-worker-v1", + "workspacePath": null, + "sandboxBackend": null + }, + "tokens": { + "host": "REDACTED_HOST_TOKEN", + "client": "REDACTED_CLIENT_TOKEN" + }, + "instance": { + "provider": "render", + "url": "https://den-worker-openwork-openwork-nonstub-wnpt.onrender.com", + "status": "healthy", + "region": "oregon" + } + }, + "payload": { + "name": "openwork-nonstub-1771653211", + "description": "openwork repo render e2e worker", + "destination": "cloud", + "imageVersion": "den-worker-v1" + } + }, + "worker_url": "https://den-worker-openwork-openwork-nonstub-wnpt.onrender.com", + "worker_id": "92500aab-e73a-437e-96a6-13210a555c39", + "get_worker": { + "status": 200, + "body": { + "worker": { + "id": "92500aab-e73a-437e-96a6-13210a555c39", + "orgId": "6187652b-cd56-49c5-82a3-78f3c4731b41", + "name": "openwork-nonstub-1771653211", + "description": "openwork repo render e2e worker", + "destination": "cloud", + "status": "healthy", + "imageVersion": "den-worker-v1", + "workspacePath": null, + "sandboxBackend": null, + "createdAt": "2026-02-21T05:53:33.000Z", + "updatedAt": "2026-02-21T05:55:00.546Z" + } + } + }, + "worker_health": { + "status": 200, + "body": { + "ok": true, + "version": "0.11.113", + "uptimeMs": 21378 + }, + "url": "https://den-worker-openwork-openwork-nonstub-wnpt.onrender.com/health" + } +} \ No newline at end of file diff --git a/evidence/den-e2e-openwork/webapp/01-web-signup-form.png b/evidence/den-e2e-openwork/webapp/01-web-signup-form.png new file mode 100644 index 00000000..b7436258 Binary files /dev/null and b/evidence/den-e2e-openwork/webapp/01-web-signup-form.png differ diff --git a/evidence/den-e2e-openwork/webapp/02-web-signup-success.png b/evidence/den-e2e-openwork/webapp/02-web-signup-success.png new file mode 100644 index 00000000..1d53bc94 Binary files /dev/null and b/evidence/den-e2e-openwork/webapp/02-web-signup-success.png differ diff --git a/evidence/den-e2e-openwork/webapp/03-web-worker-created-render.png b/evidence/den-e2e-openwork/webapp/03-web-worker-created-render.png new file mode 100644 index 00000000..dcf46031 Binary files /dev/null and b/evidence/den-e2e-openwork/webapp/03-web-worker-created-render.png differ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 839742d0..2fa21799 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -240,6 +240,49 @@ importers: specifier: ^5.6.3 version: 5.9.3 + services/den-control-plane: + dependencies: + better-auth: + specifier: ^1.4.18 + version: 1.4.18(drizzle-kit@0.31.9)(drizzle-orm@0.45.1(bun-types@1.3.6)(kysely@0.28.11)(mysql2@3.17.4))(mysql2@3.17.4)(next@14.2.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(solid-js@1.9.10) + cors: + specifier: ^2.8.5 + version: 2.8.6 + dotenv: + specifier: ^16.4.5 + version: 16.6.1 + drizzle-orm: + specifier: ^0.45.1 + version: 0.45.1(bun-types@1.3.6)(kysely@0.28.11)(mysql2@3.17.4) + express: + specifier: ^4.19.2 + version: 4.22.1 + mysql2: + specifier: ^3.11.3 + version: 3.17.4 + zod: + specifier: ^4.3.6 + version: 4.3.6 + devDependencies: + '@types/cors': + specifier: ^2.8.17 + version: 2.8.19 + '@types/express': + specifier: ^4.17.21 + version: 4.17.25 + '@types/node': + specifier: ^20.11.30 + version: 20.12.12 + drizzle-kit: + specifier: ^0.31.9 + version: 0.31.9 + tsx: + specifier: ^4.15.7 + version: 4.21.0 + typescript: + specifier: ^5.5.4 + version: 5.9.3 + services/openwork-share: dependencies: '@vercel/blob': @@ -396,6 +439,27 @@ packages: resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} engines: {node: '>=6.9.0'} + '@better-auth/core@1.4.18': + resolution: {integrity: sha512-q+awYgC7nkLEBdx2sW0iJjkzgSHlIxGnOpsN1r/O1+a4m7osJNHtfK2mKJSL1I+GfNyIlxJF8WvD/NLuYMpmcg==} + peerDependencies: + '@better-auth/utils': 0.3.0 + '@better-fetch/fetch': 1.1.21 + better-call: 1.1.8 + jose: ^6.1.0 + kysely: ^0.28.5 + nanostores: ^1.0.1 + + '@better-auth/telemetry@1.4.18': + resolution: {integrity: sha512-e5rDF8S4j3Um/0LIVATL2in9dL4lfO2fr2v1Wio4qTMRbfxqnUDTa+6SZtwdeJrbc4O+a3c+IyIpjG9Q/6GpfQ==} + peerDependencies: + '@better-auth/core': 1.4.18 + + '@better-auth/utils@0.3.0': + resolution: {integrity: sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw==} + + '@better-fetch/fetch@1.1.21': + resolution: {integrity: sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==} + '@codemirror/autocomplete@6.20.0': resolution: {integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==} @@ -429,6 +493,17 @@ packages: '@dimforge/rapier2d-simd-compat@0.17.3': resolution: {integrity: sha512-bijvwWz6NHsNj5e5i1vtd3dU2pDhthSaTUZSh14DUGGKJfw8eMnlWZsxwHBxB/a3AXVNDjL9abuHw1k9FGR+jg==} + '@drizzle-team/brocli@0.10.2': + resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} + + '@esbuild-kit/core-utils@3.3.2': + resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} + deprecated: 'Merged into tsx: https://tsx.is' + + '@esbuild-kit/esm-loader@2.6.5': + resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} + deprecated: 'Merged into tsx: https://tsx.is' + '@esbuild/aix-ppc64@0.25.12': resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} @@ -441,6 +516,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.18.20': + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.25.12': resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} engines: {node: '>=18'} @@ -453,6 +534,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm@0.18.20': + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.25.12': resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} engines: {node: '>=18'} @@ -465,6 +552,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-x64@0.18.20': + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.25.12': resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} engines: {node: '>=18'} @@ -477,6 +570,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.18.20': + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.25.12': resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} engines: {node: '>=18'} @@ -489,6 +588,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.18.20': + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.25.12': resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} engines: {node: '>=18'} @@ -501,6 +606,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.18.20': + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.25.12': resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} engines: {node: '>=18'} @@ -513,6 +624,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.18.20': + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.12': resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} engines: {node: '>=18'} @@ -525,6 +642,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.18.20': + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.25.12': resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} engines: {node: '>=18'} @@ -537,6 +660,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.18.20': + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.25.12': resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} engines: {node: '>=18'} @@ -549,6 +678,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.18.20': + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.25.12': resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} engines: {node: '>=18'} @@ -561,6 +696,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.18.20': + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.25.12': resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} engines: {node: '>=18'} @@ -573,6 +714,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.18.20': + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.25.12': resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} engines: {node: '>=18'} @@ -585,6 +732,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.18.20': + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.25.12': resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} engines: {node: '>=18'} @@ -597,6 +750,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.18.20': + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.25.12': resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} engines: {node: '>=18'} @@ -609,6 +768,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.18.20': + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.25.12': resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} engines: {node: '>=18'} @@ -621,6 +786,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.18.20': + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.25.12': resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} engines: {node: '>=18'} @@ -645,6 +816,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-x64@0.18.20': + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.12': resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} engines: {node: '>=18'} @@ -669,6 +846,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-x64@0.18.20': + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.12': resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} engines: {node: '>=18'} @@ -693,6 +876,12 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/sunos-x64@0.18.20': + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.25.12': resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} engines: {node: '>=18'} @@ -705,6 +894,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.18.20': + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.25.12': resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} engines: {node: '>=18'} @@ -717,6 +912,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.18.20': + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.25.12': resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} engines: {node: '>=18'} @@ -729,6 +930,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.18.20': + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.25.12': resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} engines: {node: '>=18'} @@ -965,6 +1172,14 @@ packages: cpu: [x64] os: [win32] + '@noble/ciphers@2.1.1': + resolution: {integrity: sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw==} + engines: {node: '>= 20.19.0'} + + '@noble/hashes@2.0.1': + resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} + engines: {node: '>= 20.19.0'} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1194,6 +1409,9 @@ packages: peerDependencies: solid-js: ^1.8.6 + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} @@ -1394,9 +1612,30 @@ packages: '@types/babel__traverse@7.28.0': resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/cors@2.8.19': + resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/express-serve-static-core@4.19.8': + resolution: {integrity: sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==} + + '@types/express@4.17.25': + resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + '@types/minimatch@5.1.2': resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} @@ -1412,6 +1651,12 @@ packages: '@types/prop-types@15.7.15': resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/react-dom@18.2.25': resolution: {integrity: sha512-o/V48vf4MQh7juIKZU2QGDfli6p1+OOi5oXx36Hffpc9adsHeXjVp8rHuPkjd8VT8sOJ2Zp05HR7CdpGTIUFUA==} @@ -1421,6 +1666,15 @@ packages: '@types/retry@0.12.0': resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + '@types/send@0.17.6': + resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==} + + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@1.15.10': + resolution: {integrity: sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==} + '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} @@ -1435,6 +1689,10 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + any-base@1.1.0: resolution: {integrity: sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==} @@ -1448,6 +1706,9 @@ packages: arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + async-retry@1.3.3: resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} @@ -1469,6 +1730,10 @@ packages: resolution: {integrity: sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==} engines: {node: '>=6.0.0'} + aws-ssl-profiles@1.1.2: + resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} + engines: {node: '>= 6.0.0'} + axios@1.13.4: resolution: {integrity: sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==} @@ -1499,6 +1764,76 @@ packages: resolution: {integrity: sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==} hasBin: true + better-auth@1.4.18: + resolution: {integrity: sha512-bnyifLWBPcYVltH3RhS7CM62MoelEqC6Q+GnZwfiDWNfepXoQZBjEvn4urcERC7NTKgKq5zNBM8rvPvRBa6xcg==} + peerDependencies: + '@lynx-js/react': '*' + '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 + '@sveltejs/kit': ^2.0.0 + '@tanstack/react-start': ^1.0.0 + '@tanstack/solid-start': ^1.0.0 + better-sqlite3: ^12.0.0 + drizzle-kit: '>=0.31.4' + drizzle-orm: '>=0.41.0' + mongodb: ^6.0.0 || ^7.0.0 + mysql2: ^3.0.0 + next: ^14.0.0 || ^15.0.0 || ^16.0.0 + pg: ^8.0.0 + prisma: ^5.0.0 || ^6.0.0 || ^7.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + solid-js: ^1.0.0 + svelte: ^4.0.0 || ^5.0.0 + vitest: ^2.0.0 || ^3.0.0 || ^4.0.0 + vue: ^3.0.0 + peerDependenciesMeta: + '@lynx-js/react': + optional: true + '@prisma/client': + optional: true + '@sveltejs/kit': + optional: true + '@tanstack/react-start': + optional: true + '@tanstack/solid-start': + optional: true + better-sqlite3: + optional: true + drizzle-kit: + optional: true + drizzle-orm: + optional: true + mongodb: + optional: true + mysql2: + optional: true + next: + optional: true + pg: + optional: true + prisma: + optional: true + react: + optional: true + react-dom: + optional: true + solid-js: + optional: true + svelte: + optional: true + vitest: + optional: true + vue: + optional: true + + better-call@1.1.8: + resolution: {integrity: sha512-XMQ2rs6FNXasGNfMjzbyroSwKwYbZ/T3IxruSS6U2MJRsSYh3wYtG3o6H00ZlKZ/C/UPOAD97tqgQJNsxyeTXw==} + peerDependencies: + zod: ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -1506,6 +1841,10 @@ packages: bmp-ts@1.0.9: resolution: {integrity: sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==} + body-parser@1.20.4: + resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} @@ -1518,6 +1857,9 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} @@ -1556,10 +1898,18 @@ packages: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} @@ -1586,9 +1936,28 @@ packages: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-signature@1.0.7: + resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + crelt@1.0.6: resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} @@ -1600,6 +1969,14 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1609,10 +1986,25 @@ packages: supports-color: optional: true + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -1631,13 +2023,116 @@ packages: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} + drizzle-kit@0.31.9: + resolution: {integrity: sha512-GViD3IgsXn7trFyBUUHyTFBpH/FsHTxYJ66qdbVggxef4UBPHRYxQaRzYLTuekYnk9i5FIEL9pbBIwMqX/Uwrg==} + hasBin: true + + drizzle-orm@0.45.1: + resolution: {integrity: sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA==} + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=4' + '@electric-sql/pglite': '>=0.2.0' + '@libsql/client': '>=0.10.0' + '@libsql/client-wasm': '>=0.10.0' + '@neondatabase/serverless': '>=0.10.0' + '@op-engineering/op-sqlite': '>=2' + '@opentelemetry/api': ^1.4.1 + '@planetscale/database': '>=1.13' + '@prisma/client': '*' + '@tidbcloud/serverless': '*' + '@types/better-sqlite3': '*' + '@types/pg': '*' + '@types/sql.js': '*' + '@upstash/redis': '>=1.34.7' + '@vercel/postgres': '>=0.8.0' + '@xata.io/client': '*' + better-sqlite3: '>=7' + bun-types: '*' + expo-sqlite: '>=14.0.0' + gel: '>=2' + knex: '*' + kysely: '*' + mysql2: '>=2' + pg: '>=8' + postgres: '>=3' + prisma: '*' + sql.js: '>=1' + sqlite3: '>=5' + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@electric-sql/pglite': + optional: true + '@libsql/client': + optional: true + '@libsql/client-wasm': + optional: true + '@neondatabase/serverless': + optional: true + '@op-engineering/op-sqlite': + optional: true + '@opentelemetry/api': + optional: true + '@planetscale/database': + optional: true + '@prisma/client': + optional: true + '@tidbcloud/serverless': + optional: true + '@types/better-sqlite3': + optional: true + '@types/pg': + optional: true + '@types/sql.js': + optional: true + '@upstash/redis': + optional: true + '@vercel/postgres': + optional: true + '@xata.io/client': + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + expo-sqlite: + optional: true + gel: + optional: true + knex: + optional: true + kysely: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + prisma: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + electron-to-chromium@1.5.267: resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + enhanced-resolve@5.18.4: resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} engines: {node: '>=10.13.0'} @@ -1662,6 +2157,16 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} + esbuild-register@3.6.0: + resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} + peerDependencies: + esbuild: '>=0.12 <1' + + esbuild@0.18.20: + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + engines: {node: '>=12'} + hasBin: true + esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} @@ -1676,6 +2181,13 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -1693,6 +2205,10 @@ packages: exif-parser@0.1.12: resolution: {integrity: sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==} + express@4.22.1: + resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} + engines: {node: '>= 0.10.0'} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -1717,6 +2233,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + finalhandler@1.3.2: + resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} + engines: {node: '>= 0.8'} + find-babel-config@2.1.2: resolution: {integrity: sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg==} @@ -1737,9 +2257,17 @@ packages: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -1754,6 +2282,9 @@ packages: fuzzysort@3.1.0: resolution: {integrity: sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==} + generate-function@2.3.1: + resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -1811,12 +2342,31 @@ packages: html-entities@2.3.3: resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} image-q@4.0.0: resolution: {integrity: sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -1847,6 +2397,9 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-property@1.0.2: + resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} + is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -1867,6 +2420,9 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + jose@6.1.3: + resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + jpeg-js@0.4.4: resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==} @@ -1886,6 +2442,10 @@ packages: jsonc-parser@3.3.1: resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + kysely@0.28.11: + resolution: {integrity: sha512-zpGIFg0HuoC893rIjYX1BETkVWdDnzTzF5e0kWXJFg5lE0k1/LfNWBejrcnOFu8Q2Rfq/hTDTU7XLUM8QOrpzg==} + engines: {node: '>=20.0.0'} + lightningcss-android-arm64@1.30.2: resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} engines: {node: '>= 12.0.0'} @@ -1971,6 +2531,9 @@ packages: resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} engines: {node: '>=6'} + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -1981,6 +2544,10 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru.min@1.1.4: + resolution: {integrity: sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==} + engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} + lucide-solid@0.562.0: resolution: {integrity: sha512-RzxujbWUoa4KqnV+/mYPINzyf1W969bv0jukSKcGExIl96QmRuylvBQ+d0UcOeZL/ASToPOWS34p9MfdzdvS9Q==} peerDependencies: @@ -1998,14 +2565,25 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + merge-anything@5.1.7: resolution: {integrity: sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==} engines: {node: '>=12.13'} + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -2018,6 +2596,11 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + mime@3.0.0: resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} engines: {node: '>=10.0.0'} @@ -2039,17 +2622,36 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mysql2@3.17.4: + resolution: {integrity: sha512-RnfuK5tyIuaiPMWOCTTl4vQX/mQXqSA8eoIbwvWccadvPGvh+BYWWVecInMS5s7wcLUkze8LqJzwB/+A4uwuAA==} + engines: {node: '>= 8.0'} + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + named-placeholders@1.1.6: + resolution: {integrity: sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==} + engines: {node: '>=8.0.0'} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanostores@1.1.0: + resolution: {integrity: sha512-yJBmDJr18xy47dbNVlHcgdPrulSn1nhSE6Ns9vTG+Nx9VPT6iV1MD6aQFp/t52zpf82FhLLTXAXr30NuCnxvwA==} + engines: {node: ^20.0.0 || >=22.0.0} + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + next@14.2.5: resolution: {integrity: sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==} engines: {node: '>=18.17.0'} @@ -2097,6 +2699,10 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + omggif@1.0.10: resolution: {integrity: sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==} @@ -2104,6 +2710,10 @@ packages: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + p-finally@1.0.0: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} engines: {node: '>=4'} @@ -2147,6 +2757,10 @@ packages: parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + path-exists@3.0.0: resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} engines: {node: '>=4'} @@ -2158,6 +2772,9 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + peek-readable@4.1.0: resolution: {integrity: sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==} engines: {node: '>=8'} @@ -2269,15 +2886,31 @@ packages: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + qs@6.14.2: + resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==} + engines: {node: '>=0.6'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.5.3: + resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} + engines: {node: '>= 0.8'} + react-dom@18.2.0: resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} peerDependencies: @@ -2330,6 +2963,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rou3@0.7.12: + resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -2343,6 +2979,9 @@ packages: resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} engines: {node: '>=10'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sax@1.4.4: resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==} engines: {node: '>=11.0.0'} @@ -2354,6 +2993,10 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true + send@0.19.2: + resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} + engines: {node: '>= 0.8.0'} + seroval-plugins@1.3.3: resolution: {integrity: sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==} engines: {node: '>=10'} @@ -2364,6 +3007,32 @@ packages: resolution: {integrity: sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==} engines: {node: '>=10'} + serve-static@1.16.3: + resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} + engines: {node: '>= 0.8.0'} + + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + simple-xml-to-json@1.2.3: resolution: {integrity: sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA==} engines: {node: '>=20.12.2'} @@ -2386,14 +3055,29 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} + sql-escaper@1.3.3: + resolution: {integrity: sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==} + engines: {bun: '>=1.0.0', deno: '>=2.0.0', node: '>=12.0.0'} + stage-js@1.0.0-alpha.17: resolution: {integrity: sha512-AzlMO+t51v6cFvKZ+Oe9DJnL1OXEH5s9bEy6di5aOrUpcP7PCzI/wIeXF0u3zg0L89gwnceoKxrLId0ZpYnNXw==} engines: {node: '>=18.0'} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -2470,6 +3154,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + token-types@4.2.1: resolution: {integrity: sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==} engines: {node: '>=10'} @@ -2488,6 +3176,10 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + typescript@5.4.5: resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} engines: {node: '>=14.17'} @@ -2512,6 +3204,10 @@ packages: resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==} engines: {node: '>=14.0'} + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -2524,6 +3220,14 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + vite-plugin-solid@2.11.10: resolution: {integrity: sha512-Yr1dQybmtDtDAHkii6hXuc1oVH9CPcS/Zb2jN/P36qqcrkNnVPsMTzQ06jyzFPFjj3U1IYKMVt/9ZqcwGCEbjw==} peerDependencies: @@ -2636,6 +3340,9 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + snapshots: '@alloc/quick-lru@5.2.0': {} @@ -2869,6 +3576,27 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@better-auth/core@1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0)': + dependencies: + '@better-auth/utils': 0.3.0 + '@better-fetch/fetch': 1.1.21 + '@standard-schema/spec': 1.1.0 + better-call: 1.1.8(zod@4.3.6) + jose: 6.1.3 + kysely: 0.28.11 + nanostores: 1.1.0 + zod: 4.3.6 + + '@better-auth/telemetry@1.4.18(@better-auth/core@1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0))': + dependencies: + '@better-auth/core': 1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0) + '@better-auth/utils': 0.3.0 + '@better-fetch/fetch': 1.1.21 + + '@better-auth/utils@0.3.0': {} + + '@better-fetch/fetch@1.1.21': {} + '@codemirror/autocomplete@6.20.0': dependencies: '@codemirror/language': 6.12.1 @@ -2952,102 +3680,162 @@ snapshots: '@dimforge/rapier2d-simd-compat@0.17.3': optional: true + '@drizzle-team/brocli@0.10.2': {} + + '@esbuild-kit/core-utils@3.3.2': + dependencies: + esbuild: 0.18.20 + source-map-support: 0.5.21 + + '@esbuild-kit/esm-loader@2.6.5': + dependencies: + '@esbuild-kit/core-utils': 3.3.2 + get-tsconfig: 4.13.1 + '@esbuild/aix-ppc64@0.25.12': optional: true '@esbuild/aix-ppc64@0.27.2': optional: true + '@esbuild/android-arm64@0.18.20': + optional: true + '@esbuild/android-arm64@0.25.12': optional: true '@esbuild/android-arm64@0.27.2': optional: true + '@esbuild/android-arm@0.18.20': + optional: true + '@esbuild/android-arm@0.25.12': optional: true '@esbuild/android-arm@0.27.2': optional: true + '@esbuild/android-x64@0.18.20': + optional: true + '@esbuild/android-x64@0.25.12': optional: true '@esbuild/android-x64@0.27.2': optional: true + '@esbuild/darwin-arm64@0.18.20': + optional: true + '@esbuild/darwin-arm64@0.25.12': optional: true '@esbuild/darwin-arm64@0.27.2': optional: true + '@esbuild/darwin-x64@0.18.20': + optional: true + '@esbuild/darwin-x64@0.25.12': optional: true '@esbuild/darwin-x64@0.27.2': optional: true + '@esbuild/freebsd-arm64@0.18.20': + optional: true + '@esbuild/freebsd-arm64@0.25.12': optional: true '@esbuild/freebsd-arm64@0.27.2': optional: true + '@esbuild/freebsd-x64@0.18.20': + optional: true + '@esbuild/freebsd-x64@0.25.12': optional: true '@esbuild/freebsd-x64@0.27.2': optional: true + '@esbuild/linux-arm64@0.18.20': + optional: true + '@esbuild/linux-arm64@0.25.12': optional: true '@esbuild/linux-arm64@0.27.2': optional: true + '@esbuild/linux-arm@0.18.20': + optional: true + '@esbuild/linux-arm@0.25.12': optional: true '@esbuild/linux-arm@0.27.2': optional: true + '@esbuild/linux-ia32@0.18.20': + optional: true + '@esbuild/linux-ia32@0.25.12': optional: true '@esbuild/linux-ia32@0.27.2': optional: true + '@esbuild/linux-loong64@0.18.20': + optional: true + '@esbuild/linux-loong64@0.25.12': optional: true '@esbuild/linux-loong64@0.27.2': optional: true + '@esbuild/linux-mips64el@0.18.20': + optional: true + '@esbuild/linux-mips64el@0.25.12': optional: true '@esbuild/linux-mips64el@0.27.2': optional: true + '@esbuild/linux-ppc64@0.18.20': + optional: true + '@esbuild/linux-ppc64@0.25.12': optional: true '@esbuild/linux-ppc64@0.27.2': optional: true + '@esbuild/linux-riscv64@0.18.20': + optional: true + '@esbuild/linux-riscv64@0.25.12': optional: true '@esbuild/linux-riscv64@0.27.2': optional: true + '@esbuild/linux-s390x@0.18.20': + optional: true + '@esbuild/linux-s390x@0.25.12': optional: true '@esbuild/linux-s390x@0.27.2': optional: true + '@esbuild/linux-x64@0.18.20': + optional: true + '@esbuild/linux-x64@0.25.12': optional: true @@ -3060,6 +3848,9 @@ snapshots: '@esbuild/netbsd-arm64@0.27.2': optional: true + '@esbuild/netbsd-x64@0.18.20': + optional: true + '@esbuild/netbsd-x64@0.25.12': optional: true @@ -3072,6 +3863,9 @@ snapshots: '@esbuild/openbsd-arm64@0.27.2': optional: true + '@esbuild/openbsd-x64@0.18.20': + optional: true + '@esbuild/openbsd-x64@0.25.12': optional: true @@ -3084,24 +3878,36 @@ snapshots: '@esbuild/openharmony-arm64@0.27.2': optional: true + '@esbuild/sunos-x64@0.18.20': + optional: true + '@esbuild/sunos-x64@0.25.12': optional: true '@esbuild/sunos-x64@0.27.2': optional: true + '@esbuild/win32-arm64@0.18.20': + optional: true + '@esbuild/win32-arm64@0.25.12': optional: true '@esbuild/win32-arm64@0.27.2': optional: true + '@esbuild/win32-ia32@0.18.20': + optional: true + '@esbuild/win32-ia32@0.25.12': optional: true '@esbuild/win32-ia32@0.27.2': optional: true + '@esbuild/win32-x64@0.18.20': + optional: true + '@esbuild/win32-x64@0.25.12': optional: true @@ -3390,6 +4196,10 @@ snapshots: '@next/swc-win32-x64-msvc@14.2.5': optional: true + '@noble/ciphers@2.1.1': {} + + '@noble/hashes@2.0.1': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3536,7 +4346,7 @@ snapshots: '@slack/logger@4.0.0': dependencies: - '@types/node': 22.19.7 + '@types/node': 20.12.12 '@slack/socket-mode@2.0.5': dependencies: @@ -3588,6 +4398,8 @@ snapshots: dependencies: solid-js: 1.9.10 + '@standard-schema/spec@1.1.0': {} + '@swc/counter@0.1.3': {} '@swc/helpers@0.5.5': @@ -3755,8 +4567,39 @@ snapshots: dependencies: '@babel/types': 7.28.6 + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 20.12.12 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 20.12.12 + + '@types/cors@2.8.19': + dependencies: + '@types/node': 20.12.12 + '@types/estree@1.0.8': {} + '@types/express-serve-static-core@4.19.8': + dependencies: + '@types/node': 20.12.12 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@4.17.25': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 4.19.8 + '@types/qs': 6.14.0 + '@types/serve-static': 1.15.10 + + '@types/http-errors@2.0.5': {} + + '@types/mime@1.3.5': {} + '@types/minimatch@5.1.2': {} '@types/node@16.9.1': {} @@ -3771,6 +4614,10 @@ snapshots: '@types/prop-types@15.7.15': {} + '@types/qs@6.14.0': {} + + '@types/range-parser@1.2.7': {} + '@types/react-dom@18.2.25': dependencies: '@types/react': 18.2.79 @@ -3782,9 +4629,24 @@ snapshots: '@types/retry@0.12.0': {} + '@types/send@0.17.6': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 20.12.12 + + '@types/send@1.2.1': + dependencies: + '@types/node': 20.12.12 + + '@types/serve-static@1.15.10': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 20.12.12 + '@types/send': 0.17.6 + '@types/ws@8.18.1': dependencies: - '@types/node': 22.19.7 + '@types/node': 20.12.12 '@vercel/blob@0.27.3': dependencies: @@ -3801,6 +4663,11 @@ snapshots: dependencies: event-target-shim: 5.0.1 + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + any-base@1.1.0: {} any-promise@1.3.0: {} @@ -3812,6 +4679,8 @@ snapshots: arg@5.0.2: {} + array-flatten@1.1.1: {} + async-retry@1.3.3: dependencies: retry: 0.13.1 @@ -3832,6 +4701,8 @@ snapshots: await-to-js@3.0.0: {} + aws-ssl-profiles@1.1.2: {} + axios@1.13.4: dependencies: follow-redirects: 1.15.11 @@ -3886,10 +4757,59 @@ snapshots: baseline-browser-mapping@2.9.14: {} + better-auth@1.4.18(drizzle-kit@0.31.9)(drizzle-orm@0.45.1(bun-types@1.3.6)(kysely@0.28.11)(mysql2@3.17.4))(mysql2@3.17.4)(next@14.2.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(solid-js@1.9.10): + dependencies: + '@better-auth/core': 1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0) + '@better-auth/telemetry': 1.4.18(@better-auth/core@1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0)) + '@better-auth/utils': 0.3.0 + '@better-fetch/fetch': 1.1.21 + '@noble/ciphers': 2.1.1 + '@noble/hashes': 2.0.1 + better-call: 1.1.8(zod@4.3.6) + defu: 6.1.4 + jose: 6.1.3 + kysely: 0.28.11 + nanostores: 1.1.0 + zod: 4.3.6 + optionalDependencies: + drizzle-kit: 0.31.9 + drizzle-orm: 0.45.1(bun-types@1.3.6)(kysely@0.28.11)(mysql2@3.17.4) + mysql2: 3.17.4 + next: 14.2.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + solid-js: 1.9.10 + + better-call@1.1.8(zod@4.3.6): + dependencies: + '@better-auth/utils': 0.3.0 + '@better-fetch/fetch': 1.1.21 + rou3: 0.7.12 + set-cookie-parser: 2.7.2 + optionalDependencies: + zod: 4.3.6 + binary-extensions@2.3.0: {} bmp-ts@1.0.9: {} + body-parser@1.20.4: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.14.2 + raw-body: 2.5.3 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 @@ -3906,6 +4826,8 @@ snapshots: node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) + buffer-from@1.1.2: {} + buffer@6.0.3: dependencies: base64-js: 1.5.1 @@ -3945,11 +4867,18 @@ snapshots: dependencies: streamsearch: 1.1.0 + bytes@3.1.2: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 function-bind: 1.1.2 + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + camelcase-css@2.0.1: {} caniuse-lite@1.0.30001764: {} @@ -3976,20 +4905,47 @@ snapshots: commander@4.1.1: {} + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + convert-source-map@2.0.0: {} + cookie-signature@1.0.7: {} + + cookie@0.7.2: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + crelt@1.0.6: {} cssesc@3.0.0: {} csstype@3.2.3: {} + debug@2.6.9: + dependencies: + ms: 2.0.0 + debug@4.4.3: dependencies: ms: 2.1.3 + defu@6.1.4: {} + delayed-stream@1.0.0: {} + denque@2.1.0: {} + + depd@2.0.0: {} + + destroy@1.2.0: {} + detect-libc@2.1.2: {} didyoumean@1.2.2: {} @@ -4000,14 +4956,33 @@ snapshots: dotenv@16.6.1: {} + drizzle-kit@0.31.9: + dependencies: + '@drizzle-team/brocli': 0.10.2 + '@esbuild-kit/esm-loader': 2.6.5 + esbuild: 0.25.12 + esbuild-register: 3.6.0(esbuild@0.25.12) + transitivePeerDependencies: + - supports-color + + drizzle-orm@0.45.1(bun-types@1.3.6)(kysely@0.28.11)(mysql2@3.17.4): + optionalDependencies: + bun-types: 1.3.6 + kysely: 0.28.11 + mysql2: 3.17.4 + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 es-errors: 1.3.0 gopd: 1.2.0 + ee-first@1.1.1: {} + electron-to-chromium@1.5.267: {} + encodeurl@2.0.0: {} + enhanced-resolve@5.18.4: dependencies: graceful-fs: 4.2.11 @@ -4030,6 +5005,38 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + esbuild-register@3.6.0(esbuild@0.25.12): + dependencies: + debug: 4.4.3 + esbuild: 0.25.12 + transitivePeerDependencies: + - supports-color + + esbuild@0.18.20: + optionalDependencies: + '@esbuild/android-arm': 0.18.20 + '@esbuild/android-arm64': 0.18.20 + '@esbuild/android-x64': 0.18.20 + '@esbuild/darwin-arm64': 0.18.20 + '@esbuild/darwin-x64': 0.18.20 + '@esbuild/freebsd-arm64': 0.18.20 + '@esbuild/freebsd-x64': 0.18.20 + '@esbuild/linux-arm': 0.18.20 + '@esbuild/linux-arm64': 0.18.20 + '@esbuild/linux-ia32': 0.18.20 + '@esbuild/linux-loong64': 0.18.20 + '@esbuild/linux-mips64el': 0.18.20 + '@esbuild/linux-ppc64': 0.18.20 + '@esbuild/linux-riscv64': 0.18.20 + '@esbuild/linux-s390x': 0.18.20 + '@esbuild/linux-x64': 0.18.20 + '@esbuild/netbsd-x64': 0.18.20 + '@esbuild/openbsd-x64': 0.18.20 + '@esbuild/sunos-x64': 0.18.20 + '@esbuild/win32-arm64': 0.18.20 + '@esbuild/win32-ia32': 0.18.20 + '@esbuild/win32-x64': 0.18.20 + esbuild@0.25.12: optionalDependencies: '@esbuild/aix-ppc64': 0.25.12 @@ -4087,10 +5094,13 @@ snapshots: '@esbuild/win32-arm64': 0.27.2 '@esbuild/win32-ia32': 0.27.2 '@esbuild/win32-x64': 0.27.2 - optional: true escalade@3.2.0: {} + escape-html@1.0.3: {} + + etag@1.8.1: {} + event-target-shim@5.0.1: {} eventemitter3@4.0.7: {} @@ -4101,6 +5111,42 @@ snapshots: exif-parser@0.1.12: {} + express@4.22.1: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.4 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.0.7 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.2 + fresh: 0.5.2 + http-errors: 2.0.1 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.12 + proxy-addr: 2.0.7 + qs: 6.14.2 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.2 + serve-static: 1.16.3 + setprototypeof: 1.2.0 + statuses: 2.0.2 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -4127,6 +5173,18 @@ snapshots: dependencies: to-regex-range: 5.0.1 + finalhandler@1.3.2: + dependencies: + debug: 2.6.9 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + find-babel-config@2.1.2: dependencies: json5: 2.2.3 @@ -4145,8 +5203,12 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 + forwarded@0.2.0: {} + fraction.js@4.3.7: {} + fresh@0.5.2: {} + fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -4156,6 +5218,10 @@ snapshots: fuzzysort@3.1.0: {} + generate-function@2.3.1: + dependencies: + is-property: 1.0.2 + gensync@1.0.0-beta.2: {} get-intrinsic@1.3.0: @@ -4179,7 +5245,6 @@ snapshots: get-tsconfig@4.13.1: dependencies: resolve-pkg-maps: 1.0.0 - optional: true gifwrap@0.10.1: dependencies: @@ -4227,12 +5292,32 @@ snapshots: html-entities@2.3.3: {} + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + ieee754@1.2.1: {} image-q@4.0.0: dependencies: '@types/node': 16.9.1 + inherits@2.0.4: {} + + ipaddr.js@1.9.1: {} + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -4255,6 +5340,8 @@ snapshots: is-number@7.0.0: {} + is-property@1.0.2: {} + is-stream@2.0.1: {} is-what@4.1.16: {} @@ -4293,6 +5380,8 @@ snapshots: jiti@2.6.1: {} + jose@6.1.3: {} + jpeg-js@0.4.4: {} js-tokens@4.0.0: {} @@ -4303,6 +5392,8 @@ snapshots: jsonc-parser@3.3.1: {} + kysely@0.28.11: {} + lightningcss-android-arm64@1.30.2: optional: true @@ -4363,6 +5454,8 @@ snapshots: p-locate: 3.0.0 path-exists: 3.0.0 + long@5.3.2: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -4373,6 +5466,8 @@ snapshots: dependencies: yallist: 3.1.1 + lru.min@1.1.4: {} + lucide-solid@0.562.0(solid-js@1.9.10): dependencies: solid-js: 1.9.10 @@ -4385,12 +5480,18 @@ snapshots: math-intrinsics@1.1.0: {} + media-typer@0.3.0: {} + merge-anything@5.1.7: dependencies: is-what: 4.1.16 + merge-descriptors@1.0.3: {} + merge2@1.4.1: {} + methods@1.1.2: {} + micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -4402,6 +5503,8 @@ snapshots: dependencies: mime-db: 1.52.0 + mime@1.6.0: {} + mime@3.0.0: {} minimatch@10.1.1: @@ -4416,16 +5519,37 @@ snapshots: minipass@7.1.2: {} + ms@2.0.0: {} + ms@2.1.3: {} + mysql2@3.17.4: + dependencies: + aws-ssl-profiles: 1.1.2 + denque: 2.1.0 + generate-function: 2.3.1 + iconv-lite: 0.7.2 + long: 5.3.2 + lru.min: 1.1.4 + named-placeholders: 1.1.6 + sql-escaper: 1.3.3 + mz@2.7.0: dependencies: any-promise: 1.3.0 object-assign: 4.1.1 thenify-all: 1.6.0 + named-placeholders@1.1.6: + dependencies: + lru.min: 1.1.4 + nanoid@3.3.11: {} + nanostores@1.1.0: {} + + negotiator@0.6.3: {} + next@14.2.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@next/env': 14.2.5 @@ -4465,10 +5589,16 @@ snapshots: object-hash@3.0.0: {} + object-inspect@1.13.4: {} + omggif@1.0.10: {} on-exit-leak-free@2.1.2: {} + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + p-finally@1.0.0: {} p-limit@2.3.0: @@ -4510,6 +5640,8 @@ snapshots: dependencies: entities: 6.0.1 + parseurl@1.3.3: {} + path-exists@3.0.0: {} path-parse@1.0.7: {} @@ -4519,6 +5651,8 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 + path-to-regexp@0.1.12: {} + peek-readable@4.1.0: {} picocolors@1.1.1: {} @@ -4621,12 +5755,30 @@ snapshots: process@0.11.10: {} + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + proxy-from-env@1.1.0: {} + qs@6.14.2: + dependencies: + side-channel: 1.1.0 + queue-microtask@1.2.3: {} quick-format-unescaped@4.0.4: {} + range-parser@1.2.1: {} + + raw-body@2.5.3: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + react-dom@18.2.0(react@18.2.0): dependencies: loose-envify: 1.4.0 @@ -4661,8 +5813,7 @@ snapshots: reselect@4.1.8: {} - resolve-pkg-maps@1.0.0: - optional: true + resolve-pkg-maps@1.0.0: {} resolve@1.22.11: dependencies: @@ -4705,6 +5856,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.55.1 fsevents: 2.3.3 + rou3@0.7.12: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -4715,6 +5868,8 @@ snapshots: safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} + sax@1.4.4: {} scheduler@0.23.2: @@ -4723,12 +5878,71 @@ snapshots: semver@6.3.1: {} + send@0.19.2: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.1 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + seroval-plugins@1.3.3(seroval@1.3.2): dependencies: seroval: 1.3.2 seroval@1.3.2: {} + serve-static@1.16.3: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.2 + transitivePeerDependencies: + - supports-color + + set-cookie-parser@2.7.2: {} + + setprototypeof@1.2.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + simple-xml-to-json@1.2.3: {} solid-js@1.9.10: @@ -4758,11 +5972,22 @@ snapshots: source-map-js@1.2.1: {} + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + split2@4.2.0: {} + sql-escaper@1.3.3: {} + stage-js@1.0.0-alpha.17: optional: true + statuses@2.0.2: {} + streamsearch@1.1.0: {} string_decoder@1.3.0: @@ -4852,6 +6077,8 @@ snapshots: dependencies: is-number: 7.0.0 + toidentifier@1.0.1: {} + token-types@4.2.1: dependencies: '@tokenizer/token': 0.3.0 @@ -4869,7 +6096,11 @@ snapshots: get-tsconfig: 4.13.1 optionalDependencies: fsevents: 2.3.3 - optional: true + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 typescript@5.4.5: {} @@ -4885,6 +6116,8 @@ snapshots: dependencies: '@fastify/busboy': 2.1.1 + unpipe@1.0.0: {} + update-browserslist-db@1.2.3(browserslist@4.28.1): dependencies: browserslist: 4.28.1 @@ -4897,6 +6130,10 @@ snapshots: util-deprecate@1.0.2: {} + utils-merge@1.0.1: {} + + vary@1.1.2: {} + vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@6.4.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)): dependencies: '@babel/core': 7.28.6 @@ -4959,3 +6196,5 @@ snapshots: yoga-layout@3.2.1: {} zod@3.25.76: {} + + zod@4.3.6: {} diff --git a/services/den-control-plane/.env.example b/services/den-control-plane/.env.example new file mode 100644 index 00000000..519acc6b --- /dev/null +++ b/services/den-control-plane/.env.example @@ -0,0 +1,20 @@ +DATABASE_URL= +BETTER_AUTH_SECRET= +BETTER_AUTH_URL=http://localhost:8788 +PORT=8788 +CORS_ORIGINS=http://localhost:5173 +PROVISIONER_MODE=stub +WORKER_URL_TEMPLATE=https://workers.example.com/{workerId} +RENDER_API_BASE=https://api.render.com/v1 +RENDER_API_KEY= +RENDER_OWNER_ID= +RENDER_WORKER_REPO=https://github.com/different-ai/openwork +RENDER_WORKER_BRANCH=dev +RENDER_WORKER_ROOT_DIR=services/den-worker-runtime +RENDER_WORKER_PLAN=starter +RENDER_WORKER_REGION=oregon +RENDER_WORKER_OPENWORK_VERSION=0.11.113 +RENDER_WORKER_NAME_PREFIX=den-worker +RENDER_PROVISION_TIMEOUT_MS=900000 +RENDER_HEALTHCHECK_TIMEOUT_MS=180000 +RENDER_POLL_INTERVAL_MS=5000 diff --git a/services/den-control-plane/.gitignore b/services/den-control-plane/.gitignore new file mode 100644 index 00000000..a884d38f --- /dev/null +++ b/services/den-control-plane/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +.env +dist/ diff --git a/services/den-control-plane/README.md b/services/den-control-plane/README.md new file mode 100644 index 00000000..5e129f2c --- /dev/null +++ b/services/den-control-plane/README.md @@ -0,0 +1,74 @@ +# Den Control Plane + +Control plane for hosted workers. Provides Better Auth, worker CRUD, and provisioning hooks. + +## Quick start + +```bash +pnpm install +cp .env.example .env +pnpm dev +``` + +## Environment + +- `DATABASE_URL` MySQL connection URL +- `BETTER_AUTH_SECRET` 32+ char secret +- `BETTER_AUTH_URL` base URL for auth callbacks +- `PORT` server port +- `CORS_ORIGINS` comma-separated list +- `PROVISIONER_MODE` `stub` or `render` +- `WORKER_URL_TEMPLATE` template string with `{workerId}` +- `RENDER_API_BASE` Render API base URL (default `https://api.render.com/v1`) +- `RENDER_API_KEY` Render API key (required for `PROVISIONER_MODE=render`) +- `RENDER_OWNER_ID` Render workspace owner id (required for `PROVISIONER_MODE=render`) +- `RENDER_WORKER_REPO` repository URL used to create worker services +- `RENDER_WORKER_BRANCH` branch used for worker services +- `RENDER_WORKER_ROOT_DIR` render `rootDir` for worker services +- `RENDER_WORKER_PLAN` Render plan for worker services +- `RENDER_WORKER_REGION` Render region for worker services +- `RENDER_WORKER_OPENWORK_VERSION` `openwork-orchestrator` npm version installed in workers +- `RENDER_WORKER_NAME_PREFIX` service name prefix +- `RENDER_PROVISION_TIMEOUT_MS` max time to wait for deploy to become live +- `RENDER_HEALTHCHECK_TIMEOUT_MS` max time to wait for worker health checks +- `RENDER_POLL_INTERVAL_MS` polling interval for deploy + health checks + +## Auth setup (Better Auth) + +Generate Better Auth schema (Drizzle): + +```bash +npx @better-auth/cli@latest generate --config src/auth.ts --output src/db/better-auth.schema.ts --yes +``` + +Apply migrations: + +```bash +pnpm db:generate +pnpm db:migrate +``` + +## API + +- `GET /health` +- `GET /` demo web app (sign-up + auth + worker launch) +- `GET /v1/me` +- `POST /v1/workers` +- `GET /v1/workers/:id` +- `POST /v1/workers/:id/tokens` + +## CI deployment (dev == prod) + +The workflow `.github/workflows/deploy-den-control-plane.yml` updates Render env vars and deploys the service on every push to `dev` when this service changes. + +Required GitHub Actions secrets: + +- `RENDER_API_KEY` +- `RENDER_DEN_CONTROL_PLANE_SERVICE_ID` +- `RENDER_OWNER_ID` +- `DEN_DATABASE_URL` +- `DEN_BETTER_AUTH_SECRET` + +Optional GitHub Actions variable: + +- `DEN_RENDER_WORKER_OPENWORK_VERSION` (defaults to `0.11.113`) diff --git a/services/den-control-plane/drizzle.config.ts b/services/den-control-plane/drizzle.config.ts new file mode 100644 index 00000000..f238826d --- /dev/null +++ b/services/den-control-plane/drizzle.config.ts @@ -0,0 +1,11 @@ +import "dotenv/config" +import { defineConfig } from "drizzle-kit" + +export default defineConfig({ + dialect: "mysql", + schema: "./src/db/schema.ts", + out: "./drizzle", + dbCredentials: { + url: process.env.DATABASE_URL ?? "", + }, +}) diff --git a/services/den-control-plane/drizzle/0000_tense_lilandra.sql b/services/den-control-plane/drizzle/0000_tense_lilandra.sql new file mode 100644 index 00000000..38fd03ba --- /dev/null +++ b/services/den-control-plane/drizzle/0000_tense_lilandra.sql @@ -0,0 +1,145 @@ +CREATE TABLE `audit_event` ( + `id` varchar(64) NOT NULL, + `org_id` varchar(64) NOT NULL, + `worker_id` varchar(64), + `actor_user_id` varchar(64) NOT NULL, + `action` varchar(128) NOT NULL, + `payload` json, + `created_at` timestamp(3) NOT NULL DEFAULT (now()), + CONSTRAINT `audit_event_id` PRIMARY KEY(`id`) +); +--> statement-breakpoint +CREATE TABLE `account` ( + `id` varchar(64) NOT NULL, + `userId` varchar(64) NOT NULL, + `accountId` varchar(255) NOT NULL, + `providerId` varchar(255) NOT NULL, + `accessToken` text, + `refreshToken` text, + `accessTokenExpiresAt` timestamp(3), + `refreshTokenExpiresAt` timestamp(3), + `scope` varchar(1024), + `idToken` text, + `password` varchar(512), + `createdAt` timestamp(3) NOT NULL DEFAULT (now()), + `updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + CONSTRAINT `account_id` PRIMARY KEY(`id`) +); +--> statement-breakpoint +CREATE TABLE `session` ( + `id` varchar(64) NOT NULL, + `userId` varchar(64) NOT NULL, + `token` varchar(255) NOT NULL, + `expiresAt` timestamp(3) NOT NULL, + `ipAddress` varchar(255), + `userAgent` varchar(1024), + `createdAt` timestamp(3) NOT NULL DEFAULT (now()), + `updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + CONSTRAINT `session_id` PRIMARY KEY(`id`), + CONSTRAINT `session_token` UNIQUE(`token`) +); +--> statement-breakpoint +CREATE TABLE `user` ( + `id` varchar(64) NOT NULL, + `name` varchar(255) NOT NULL, + `email` varchar(255) NOT NULL, + `emailVerified` boolean NOT NULL DEFAULT false, + `image` varchar(2048), + `createdAt` timestamp(3) NOT NULL DEFAULT (now()), + `updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + CONSTRAINT `user_id` PRIMARY KEY(`id`), + CONSTRAINT `user_email` UNIQUE(`email`) +); +--> statement-breakpoint +CREATE TABLE `verification` ( + `id` varchar(64) NOT NULL, + `identifier` varchar(255) NOT NULL, + `value` varchar(1024) NOT NULL, + `expiresAt` timestamp(3) NOT NULL, + `createdAt` timestamp(3) NOT NULL DEFAULT (now()), + `updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + CONSTRAINT `verification_id` PRIMARY KEY(`id`) +); +--> statement-breakpoint +CREATE TABLE `org_membership` ( + `id` varchar(64) NOT NULL, + `org_id` varchar(64) NOT NULL, + `user_id` varchar(64) NOT NULL, + `role` enum('owner','member') NOT NULL, + `created_at` timestamp(3) NOT NULL DEFAULT (now()), + CONSTRAINT `org_membership_id` PRIMARY KEY(`id`) +); +--> statement-breakpoint +CREATE TABLE `org` ( + `id` varchar(64) NOT NULL, + `name` varchar(255) NOT NULL, + `slug` varchar(255) NOT NULL, + `owner_user_id` varchar(64) NOT NULL, + `created_at` timestamp(3) NOT NULL DEFAULT (now()), + `updated_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + CONSTRAINT `org_id` PRIMARY KEY(`id`), + CONSTRAINT `org_slug` UNIQUE(`slug`) +); +--> statement-breakpoint +CREATE TABLE `worker_bundle` ( + `id` varchar(64) NOT NULL, + `worker_id` varchar(64) NOT NULL, + `storage_url` varchar(2048) NOT NULL, + `status` varchar(64) NOT NULL, + `created_at` timestamp(3) NOT NULL DEFAULT (now()), + CONSTRAINT `worker_bundle_id` PRIMARY KEY(`id`) +); +--> statement-breakpoint +CREATE TABLE `worker_instance` ( + `id` varchar(64) NOT NULL, + `worker_id` varchar(64) NOT NULL, + `provider` varchar(64) NOT NULL, + `region` varchar(64), + `url` varchar(2048) NOT NULL, + `status` enum('provisioning','healthy','failed','stopped') NOT NULL, + `created_at` timestamp(3) NOT NULL DEFAULT (now()), + `updated_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + CONSTRAINT `worker_instance_id` PRIMARY KEY(`id`) +); +--> statement-breakpoint +CREATE TABLE `worker` ( + `id` varchar(64) NOT NULL, + `org_id` varchar(64) NOT NULL, + `name` varchar(255) NOT NULL, + `description` varchar(1024), + `destination` enum('local','cloud') NOT NULL, + `status` enum('provisioning','healthy','failed','stopped') NOT NULL, + `image_version` varchar(128), + `workspace_path` varchar(1024), + `sandbox_backend` varchar(64), + `created_at` timestamp(3) NOT NULL DEFAULT (now()), + `updated_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + CONSTRAINT `worker_id` PRIMARY KEY(`id`) +); +--> statement-breakpoint +CREATE TABLE `worker_token` ( + `id` varchar(64) NOT NULL, + `worker_id` varchar(64) NOT NULL, + `scope` enum('client','host') NOT NULL, + `token` varchar(128) NOT NULL, + `created_at` timestamp(3) NOT NULL DEFAULT (now()), + `revoked_at` timestamp(3), + CONSTRAINT `worker_token_id` PRIMARY KEY(`id`), + CONSTRAINT `worker_token_token` UNIQUE(`token`) +); +--> statement-breakpoint +CREATE INDEX `audit_event_org_id` ON `audit_event` (`org_id`);--> statement-breakpoint +CREATE INDEX `audit_event_worker_id` ON `audit_event` (`worker_id`);--> statement-breakpoint +CREATE INDEX `account_user_id` ON `account` (`userId`);--> statement-breakpoint +CREATE INDEX `account_provider_id` ON `account` (`providerId`);--> statement-breakpoint +CREATE INDEX `account_account_id` ON `account` (`accountId`);--> statement-breakpoint +CREATE INDEX `session_user_id` ON `session` (`userId`);--> statement-breakpoint +CREATE INDEX `verification_identifier` ON `verification` (`identifier`);--> statement-breakpoint +CREATE INDEX `org_membership_org_id` ON `org_membership` (`org_id`);--> statement-breakpoint +CREATE INDEX `org_membership_user_id` ON `org_membership` (`user_id`);--> statement-breakpoint +CREATE INDEX `org_owner_user_id` ON `org` (`owner_user_id`);--> statement-breakpoint +CREATE INDEX `worker_bundle_worker_id` ON `worker_bundle` (`worker_id`);--> statement-breakpoint +CREATE INDEX `worker_instance_worker_id` ON `worker_instance` (`worker_id`);--> statement-breakpoint +CREATE INDEX `worker_org_id` ON `worker` (`org_id`);--> statement-breakpoint +CREATE INDEX `worker_status` ON `worker` (`status`);--> statement-breakpoint +CREATE INDEX `worker_token_worker_id` ON `worker_token` (`worker_id`); diff --git a/services/den-control-plane/drizzle/0001_auth_columns_fix.sql b/services/den-control-plane/drizzle/0001_auth_columns_fix.sql new file mode 100644 index 00000000..0111bbec --- /dev/null +++ b/services/den-control-plane/drizzle/0001_auth_columns_fix.sql @@ -0,0 +1,65 @@ +DROP TABLE IF EXISTS `account`; +--> statement-breakpoint +DROP TABLE IF EXISTS `session`; +--> statement-breakpoint +DROP TABLE IF EXISTS `verification`; +--> statement-breakpoint +DROP TABLE IF EXISTS `user`; +--> statement-breakpoint +CREATE TABLE `user` ( + `id` varchar(36) NOT NULL, + `name` varchar(255) NOT NULL, + `email` varchar(255) NOT NULL, + `email_verified` boolean NOT NULL DEFAULT false, + `image` text, + `created_at` timestamp(3) NOT NULL DEFAULT (now()), + `updated_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + CONSTRAINT `user_id` PRIMARY KEY(`id`), + CONSTRAINT `user_email` UNIQUE(`email`) +); +--> statement-breakpoint +CREATE TABLE `session` ( + `id` varchar(36) NOT NULL, + `expires_at` timestamp(3) NOT NULL, + `token` varchar(255) NOT NULL, + `created_at` timestamp(3) NOT NULL DEFAULT (now()), + `updated_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + `ip_address` text, + `user_agent` text, + `user_id` varchar(36) NOT NULL, + CONSTRAINT `session_id` PRIMARY KEY(`id`), + CONSTRAINT `session_token` UNIQUE(`token`) +); +--> statement-breakpoint +CREATE INDEX `session_user_id` ON `session` (`user_id`); +--> statement-breakpoint +CREATE TABLE `account` ( + `id` varchar(36) NOT NULL, + `account_id` text NOT NULL, + `provider_id` text NOT NULL, + `user_id` varchar(36) NOT NULL, + `access_token` text, + `refresh_token` text, + `id_token` text, + `access_token_expires_at` timestamp(3), + `refresh_token_expires_at` timestamp(3), + `scope` text, + `password` text, + `created_at` timestamp(3) NOT NULL DEFAULT (now()), + `updated_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + CONSTRAINT `account_id` PRIMARY KEY(`id`) +); +--> statement-breakpoint +CREATE INDEX `account_user_id` ON `account` (`user_id`); +--> statement-breakpoint +CREATE TABLE `verification` ( + `id` varchar(36) NOT NULL, + `identifier` varchar(255) NOT NULL, + `value` text NOT NULL, + `expires_at` timestamp(3) NOT NULL, + `created_at` timestamp(3) NOT NULL DEFAULT (now()), + `updated_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + CONSTRAINT `verification_id` PRIMARY KEY(`id`) +); +--> statement-breakpoint +CREATE INDEX `verification_identifier` ON `verification` (`identifier`); diff --git a/services/den-control-plane/drizzle/meta/_journal.json b/services/den-control-plane/drizzle/meta/_journal.json new file mode 100644 index 00000000..cf53d547 --- /dev/null +++ b/services/den-control-plane/drizzle/meta/_journal.json @@ -0,0 +1,20 @@ +{ + "version": "7", + "dialect": "mysql", + "entries": [ + { + "idx": 0, + "version": "5", + "when": 1771638056482, + "tag": "0000_tense_lilandra", + "breakpoints": true + }, + { + "idx": 1, + "version": "5", + "when": 1771639607782, + "tag": "0001_auth_columns_fix", + "breakpoints": true + } + ] +} diff --git a/services/den-control-plane/package.json b/services/den-control-plane/package.json new file mode 100644 index 00000000..9b1c28f2 --- /dev/null +++ b/services/den-control-plane/package.json @@ -0,0 +1,30 @@ +{ + "name": "@openwork/den-control-plane", + "private": true, + "type": "module", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc -p tsconfig.json", + "start": "node dist/index.js", + "db:generate": "drizzle-kit generate", + "db:migrate": "drizzle-kit migrate", + "auth:generate": "npx @better-auth/cli@latest generate --config src/auth.ts --output src/db/better-auth.schema.ts --yes" + }, + "dependencies": { + "better-auth": "^1.4.18", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "drizzle-orm": "^0.45.1", + "express": "^4.19.2", + "mysql2": "^3.11.3", + "zod": "^4.3.6" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/node": "^20.11.30", + "drizzle-kit": "^0.31.9", + "tsx": "^4.15.7", + "typescript": "^5.5.4" + } +} diff --git a/services/den-control-plane/public/index.html b/services/den-control-plane/public/index.html new file mode 100644 index 00000000..f5cd08dc --- /dev/null +++ b/services/den-control-plane/public/index.html @@ -0,0 +1,248 @@ + + + + + + Den Control Plane + + + +

Den Control Plane Demo

+

Sign up, verify auth, and launch a cloud worker end-to-end.

+ +
+
+

1) Sign up

+ + + + +
+ +
+

2) Verify auth/session

+ + +

Bearer token comes from sign-up/sign-in response.

+
+ +
+

3) Launch worker

+ + + + +
+
+ +
+

Output

+
ready
+
+ + + + diff --git a/services/den-control-plane/src/auth.ts b/services/den-control-plane/src/auth.ts new file mode 100644 index 00000000..75f6066e --- /dev/null +++ b/services/den-control-plane/src/auth.ts @@ -0,0 +1,29 @@ +import { betterAuth } from "better-auth" +import { drizzleAdapter } from "better-auth/adapters/drizzle" +import { db } from "./db/index.js" +import * as schema from "./db/schema.js" +import { env } from "./env.js" +import { ensureDefaultOrg } from "./orgs.js" + +export const auth = betterAuth({ + baseURL: env.betterAuthUrl, + secret: env.betterAuthSecret, + trustedOrigins: env.corsOrigins.length > 0 ? env.corsOrigins : undefined, + database: drizzleAdapter(db, { + provider: "mysql", + schema, + }), + emailAndPassword: { + enabled: true, + }, + databaseHooks: { + user: { + create: { + after: async (user) => { + const name = user.name ?? user.email ?? "Personal" + await ensureDefaultOrg(user.id, name) + }, + }, + }, + }, +}) diff --git a/services/den-control-plane/src/db/index.ts b/services/den-control-plane/src/db/index.ts new file mode 100644 index 00000000..77fb0737 --- /dev/null +++ b/services/den-control-plane/src/db/index.ts @@ -0,0 +1,10 @@ +import { drizzle } from "drizzle-orm/mysql2" +import mysql from "mysql2/promise" +import { env } from "../env.js" +import * as schema from "./schema.js" + +const client = mysql.createPool({ + uri: env.databaseUrl, +}) + +export const db = drizzle(client, { schema, mode: "default" }) diff --git a/services/den-control-plane/src/db/schema.ts b/services/den-control-plane/src/db/schema.ts new file mode 100644 index 00000000..aa77caa4 --- /dev/null +++ b/services/den-control-plane/src/db/schema.ts @@ -0,0 +1,201 @@ +import { sql } from "drizzle-orm" +import { + boolean, + index, + json, + mysqlEnum, + mysqlTable, + text, + timestamp, + uniqueIndex, + varchar, +} from "drizzle-orm/mysql-core" + +const id = () => varchar("id", { length: 64 }).notNull() + +const timestamps = { + created_at: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(), + updated_at: timestamp("updated_at", { fsp: 3 }) + .notNull() + .default(sql`CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)`), +} + +export const OrgRole = ["owner", "member"] as const +export const WorkerDestination = ["local", "cloud"] as const +export const WorkerStatus = ["provisioning", "healthy", "failed", "stopped"] as const +export const TokenScope = ["client", "host"] as const + +export const AuthUserTable = mysqlTable( + "user", + { + id: varchar("id", { length: 36 }).notNull().primaryKey(), + name: varchar("name", { length: 255 }).notNull(), + email: varchar("email", { length: 255 }).notNull(), + emailVerified: boolean("email_verified").notNull().default(false), + image: text("image"), + createdAt: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { fsp: 3 }) + .notNull() + .default(sql`CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)`), + }, + (table) => [uniqueIndex("user_email").on(table.email)], +) + +export const AuthSessionTable = mysqlTable( + "session", + { + id: varchar("id", { length: 36 }).notNull().primaryKey(), + userId: varchar("user_id", { length: 36 }).notNull(), + token: varchar("token", { length: 255 }).notNull(), + expiresAt: timestamp("expires_at", { fsp: 3 }).notNull(), + ipAddress: text("ip_address"), + userAgent: text("user_agent"), + createdAt: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { fsp: 3 }) + .notNull() + .default(sql`CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)`), + }, + (table) => [ + uniqueIndex("session_token").on(table.token), + index("session_user_id").on(table.userId), + ], +) + +export const AuthAccountTable = mysqlTable( + "account", + { + id: varchar("id", { length: 36 }).notNull().primaryKey(), + userId: varchar("user_id", { length: 36 }).notNull(), + accountId: text("account_id").notNull(), + providerId: text("provider_id").notNull(), + accessToken: text("access_token"), + refreshToken: text("refresh_token"), + accessTokenExpiresAt: timestamp("access_token_expires_at", { fsp: 3 }), + refreshTokenExpiresAt: timestamp("refresh_token_expires_at", { fsp: 3 }), + scope: text("scope"), + idToken: text("id_token"), + password: text("password"), + createdAt: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { fsp: 3 }) + .notNull() + .default(sql`CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)`), + }, + (table) => [index("account_user_id").on(table.userId)], +) + +export const AuthVerificationTable = mysqlTable( + "verification", + { + id: varchar("id", { length: 36 }).notNull().primaryKey(), + identifier: varchar("identifier", { length: 255 }).notNull(), + value: text("value").notNull(), + expiresAt: timestamp("expires_at", { fsp: 3 }).notNull(), + createdAt: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { fsp: 3 }) + .notNull() + .default(sql`CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)`), + }, + (table) => [index("verification_identifier").on(table.identifier)], +) + +export const user = AuthUserTable +export const session = AuthSessionTable +export const account = AuthAccountTable +export const verification = AuthVerificationTable + +export const OrgTable = mysqlTable( + "org", + { + id: id().primaryKey(), + name: varchar("name", { length: 255 }).notNull(), + slug: varchar("slug", { length: 255 }).notNull(), + owner_user_id: varchar("owner_user_id", { length: 64 }).notNull(), + ...timestamps, + }, + (table) => [uniqueIndex("org_slug").on(table.slug), index("org_owner_user_id").on(table.owner_user_id)], +) + +export const OrgMembershipTable = mysqlTable( + "org_membership", + { + id: id().primaryKey(), + org_id: varchar("org_id", { length: 64 }).notNull(), + user_id: varchar("user_id", { length: 64 }).notNull(), + role: mysqlEnum("role", OrgRole).notNull(), + created_at: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(), + }, + (table) => [index("org_membership_org_id").on(table.org_id), index("org_membership_user_id").on(table.user_id)], +) + +export const WorkerTable = mysqlTable( + "worker", + { + id: id().primaryKey(), + org_id: varchar("org_id", { length: 64 }).notNull(), + name: varchar("name", { length: 255 }).notNull(), + description: varchar("description", { length: 1024 }), + destination: mysqlEnum("destination", WorkerDestination).notNull(), + status: mysqlEnum("status", WorkerStatus).notNull(), + image_version: varchar("image_version", { length: 128 }), + workspace_path: varchar("workspace_path", { length: 1024 }), + sandbox_backend: varchar("sandbox_backend", { length: 64 }), + ...timestamps, + }, + (table) => [index("worker_org_id").on(table.org_id), index("worker_status").on(table.status)], +) + +export const WorkerInstanceTable = mysqlTable( + "worker_instance", + { + id: id().primaryKey(), + worker_id: varchar("worker_id", { length: 64 }).notNull(), + provider: varchar("provider", { length: 64 }).notNull(), + region: varchar("region", { length: 64 }), + url: varchar("url", { length: 2048 }).notNull(), + status: mysqlEnum("status", WorkerStatus).notNull(), + ...timestamps, + }, + (table) => [index("worker_instance_worker_id").on(table.worker_id)], +) + +export const WorkerTokenTable = mysqlTable( + "worker_token", + { + id: id().primaryKey(), + worker_id: varchar("worker_id", { length: 64 }).notNull(), + scope: mysqlEnum("scope", TokenScope).notNull(), + token: varchar("token", { length: 128 }).notNull(), + created_at: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(), + revoked_at: timestamp("revoked_at", { fsp: 3 }), + }, + (table) => [ + index("worker_token_worker_id").on(table.worker_id), + uniqueIndex("worker_token_token").on(table.token), + ], +) + +export const WorkerBundleTable = mysqlTable( + "worker_bundle", + { + id: id().primaryKey(), + worker_id: varchar("worker_id", { length: 64 }).notNull(), + storage_url: varchar("storage_url", { length: 2048 }).notNull(), + status: varchar("status", { length: 64 }).notNull(), + created_at: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(), + }, + (table) => [index("worker_bundle_worker_id").on(table.worker_id)], +) + +export const AuditEventTable = mysqlTable( + "audit_event", + { + id: id().primaryKey(), + org_id: varchar("org_id", { length: 64 }).notNull(), + worker_id: varchar("worker_id", { length: 64 }), + actor_user_id: varchar("actor_user_id", { length: 64 }).notNull(), + action: varchar("action", { length: 128 }).notNull(), + payload: json("payload"), + created_at: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(), + }, + (table) => [index("audit_event_org_id").on(table.org_id), index("audit_event_worker_id").on(table.worker_id)], +) diff --git a/services/den-control-plane/src/env.ts b/services/den-control-plane/src/env.ts new file mode 100644 index 00000000..bebd1825 --- /dev/null +++ b/services/den-control-plane/src/env.ts @@ -0,0 +1,55 @@ +import { z } from "zod" + +const schema = z.object({ + DATABASE_URL: z.string().min(1), + BETTER_AUTH_SECRET: z.string().min(32), + BETTER_AUTH_URL: z.string().min(1), + PORT: z.string().optional(), + CORS_ORIGINS: z.string().optional(), + PROVISIONER_MODE: z.enum(["stub", "render"]).optional(), + WORKER_URL_TEMPLATE: z.string().optional(), + RENDER_API_BASE: z.string().optional(), + RENDER_API_KEY: z.string().optional(), + RENDER_OWNER_ID: z.string().optional(), + RENDER_WORKER_REPO: z.string().optional(), + RENDER_WORKER_BRANCH: z.string().optional(), + RENDER_WORKER_ROOT_DIR: z.string().optional(), + RENDER_WORKER_PLAN: z.string().optional(), + RENDER_WORKER_REGION: z.string().optional(), + RENDER_WORKER_OPENWORK_VERSION: z.string().optional(), + RENDER_WORKER_NAME_PREFIX: z.string().optional(), + RENDER_PROVISION_TIMEOUT_MS: z.string().optional(), + RENDER_HEALTHCHECK_TIMEOUT_MS: z.string().optional(), + RENDER_POLL_INTERVAL_MS: z.string().optional(), +}) + +const parsed = schema.parse(process.env) + +const corsOrigins = parsed.CORS_ORIGINS?.split(",") + .map((origin) => origin.trim()) + .filter(Boolean) + +export const env = { + databaseUrl: parsed.DATABASE_URL, + betterAuthSecret: parsed.BETTER_AUTH_SECRET, + betterAuthUrl: parsed.BETTER_AUTH_URL, + port: Number(parsed.PORT ?? "8788"), + corsOrigins: corsOrigins ?? [], + provisionerMode: parsed.PROVISIONER_MODE ?? "stub", + workerUrlTemplate: parsed.WORKER_URL_TEMPLATE, + render: { + apiBase: parsed.RENDER_API_BASE ?? "https://api.render.com/v1", + apiKey: parsed.RENDER_API_KEY, + ownerId: parsed.RENDER_OWNER_ID, + workerRepo: parsed.RENDER_WORKER_REPO ?? "https://github.com/different-ai/openwork", + workerBranch: parsed.RENDER_WORKER_BRANCH ?? "dev", + workerRootDir: parsed.RENDER_WORKER_ROOT_DIR ?? "services/den-worker-runtime", + workerPlan: parsed.RENDER_WORKER_PLAN ?? "starter", + workerRegion: parsed.RENDER_WORKER_REGION ?? "oregon", + workerOpenworkVersion: parsed.RENDER_WORKER_OPENWORK_VERSION ?? "0.11.113", + workerNamePrefix: parsed.RENDER_WORKER_NAME_PREFIX ?? "den-worker", + provisionTimeoutMs: Number(parsed.RENDER_PROVISION_TIMEOUT_MS ?? "900000"), + healthcheckTimeoutMs: Number(parsed.RENDER_HEALTHCHECK_TIMEOUT_MS ?? "180000"), + pollIntervalMs: Number(parsed.RENDER_POLL_INTERVAL_MS ?? "5000"), + }, +} diff --git a/services/den-control-plane/src/http/workers.ts b/services/den-control-plane/src/http/workers.ts new file mode 100644 index 00000000..94ecf8eb --- /dev/null +++ b/services/den-control-plane/src/http/workers.ts @@ -0,0 +1,237 @@ +import { randomBytes, randomUUID } from "crypto" +import express from "express" +import { fromNodeHeaders } from "better-auth/node" +import { eq } from "drizzle-orm" +import { z } from "zod" +import { auth } from "../auth.js" +import { db } from "../db/index.js" +import { OrgMembershipTable, WorkerInstanceTable, WorkerTable, WorkerTokenTable } from "../db/schema.js" +import { ensureDefaultOrg } from "../orgs.js" +import { provisionWorker } from "../workers/provisioner.js" + +const createSchema = z.object({ + name: z.string().min(1), + description: z.string().optional(), + destination: z.enum(["local", "cloud"]), + workspacePath: z.string().optional(), + sandboxBackend: z.string().optional(), + imageVersion: z.string().optional(), +}) + +const token = () => randomBytes(32).toString("hex") + +async function requireSession(req: express.Request, res: express.Response) { + const session = await auth.api.getSession({ + headers: fromNodeHeaders(req.headers), + }) + if (!session?.user?.id) { + res.status(401).json({ error: "unauthorized" }) + return null + } + return session +} + +async function getOrgId(userId: string) { + const membership = await db + .select() + .from(OrgMembershipTable) + .where(eq(OrgMembershipTable.user_id, userId)) + .limit(1) + if (membership.length === 0) { + return null + } + return membership[0].org_id +} + +export const workersRouter = express.Router() + +workersRouter.post("/", async (req, res) => { + const session = await requireSession(req, res) + if (!session) return + + const parsed = createSchema.safeParse(req.body) + if (!parsed.success) { + res.status(400).json({ error: "invalid_request", details: parsed.error.flatten() }) + return + } + + if (parsed.data.destination === "local" && !parsed.data.workspacePath) { + res.status(400).json({ error: "workspace_path_required" }) + return + } + + const orgId = + (await getOrgId(session.user.id)) ?? (await ensureDefaultOrg(session.user.id, session.user.name ?? session.user.email ?? "Personal")) + const workerId = randomUUID() + let workerStatus: "provisioning" | "healthy" | "failed" | "stopped" = + parsed.data.destination === "cloud" ? "provisioning" : "healthy" + + await db.insert(WorkerTable).values({ + id: workerId, + org_id: orgId, + name: parsed.data.name, + description: parsed.data.description, + destination: parsed.data.destination, + status: workerStatus, + image_version: parsed.data.imageVersion, + workspace_path: parsed.data.workspacePath, + sandbox_backend: parsed.data.sandboxBackend, + }) + + const hostToken = token() + const clientToken = token() + await db.insert(WorkerTokenTable).values([ + { + id: randomUUID(), + worker_id: workerId, + scope: "host", + token: hostToken, + }, + { + id: randomUUID(), + worker_id: workerId, + scope: "client", + token: clientToken, + }, + ]) + + let instance = null + if (parsed.data.destination === "cloud") { + try { + const provisioned = await provisionWorker({ + workerId, + name: parsed.data.name, + hostToken, + clientToken, + }) + workerStatus = provisioned.status + + await db + .update(WorkerTable) + .set({ status: workerStatus }) + .where(eq(WorkerTable.id, workerId)) + + await db.insert(WorkerInstanceTable).values({ + id: randomUUID(), + worker_id: workerId, + provider: provisioned.provider, + region: provisioned.region, + url: provisioned.url, + status: provisioned.status, + }) + instance = provisioned + } catch (error) { + await db + .update(WorkerTable) + .set({ status: "failed" }) + .where(eq(WorkerTable.id, workerId)) + + const message = error instanceof Error ? error.message : "provisioning_failed" + res.status(502).json({ error: "provisioning_failed", message }) + return + } + } + + res.status(201).json({ + worker: { + id: workerId, + orgId, + name: parsed.data.name, + description: parsed.data.description ?? null, + destination: parsed.data.destination, + status: workerStatus, + imageVersion: parsed.data.imageVersion ?? null, + workspacePath: parsed.data.workspacePath ?? null, + sandboxBackend: parsed.data.sandboxBackend ?? null, + }, + tokens: { + host: hostToken, + client: clientToken, + }, + instance, + }) +}) + +workersRouter.get("/:id", async (req, res) => { + const session = await requireSession(req, res) + if (!session) return + + const orgId = await getOrgId(session.user.id) + if (!orgId) { + res.status(404).json({ error: "worker_not_found" }) + return + } + + const rows = await db + .select() + .from(WorkerTable) + .where(eq(WorkerTable.id, req.params.id)) + .limit(1) + + if (rows.length === 0 || rows[0].org_id !== orgId) { + res.status(404).json({ error: "worker_not_found" }) + return + } + + res.json({ + worker: { + id: rows[0].id, + orgId: rows[0].org_id, + name: rows[0].name, + description: rows[0].description, + destination: rows[0].destination, + status: rows[0].status, + imageVersion: rows[0].image_version, + workspacePath: rows[0].workspace_path, + sandboxBackend: rows[0].sandbox_backend, + createdAt: rows[0].created_at, + updatedAt: rows[0].updated_at, + }, + }) +}) + +workersRouter.post("/:id/tokens", async (req, res) => { + const session = await requireSession(req, res) + if (!session) return + + const orgId = await getOrgId(session.user.id) + if (!orgId) { + res.status(404).json({ error: "worker_not_found" }) + return + } + + const rows = await db + .select() + .from(WorkerTable) + .where(eq(WorkerTable.id, req.params.id)) + .limit(1) + + if (rows.length === 0 || rows[0].org_id !== orgId) { + res.status(404).json({ error: "worker_not_found" }) + return + } + + const hostToken = token() + const clientToken = token() + await db.insert(WorkerTokenTable).values([ + { + id: randomUUID(), + worker_id: rows[0].id, + scope: "host", + token: hostToken, + }, + { + id: randomUUID(), + worker_id: rows[0].id, + scope: "client", + token: clientToken, + }, + ]) + + res.json({ + tokens: { + host: hostToken, + client: clientToken, + }, + }) +}) diff --git a/services/den-control-plane/src/index.ts b/services/den-control-plane/src/index.ts new file mode 100644 index 00000000..101594bd --- /dev/null +++ b/services/den-control-plane/src/index.ts @@ -0,0 +1,48 @@ +import "dotenv/config" +import cors from "cors" +import express from "express" +import path from "node:path" +import { fileURLToPath } from "node:url" +import { fromNodeHeaders, toNodeHandler } from "better-auth/node" +import { auth } from "./auth.js" +import { env } from "./env.js" +import { workersRouter } from "./http/workers.js" + +const app = express() +const currentFile = fileURLToPath(import.meta.url) +const publicDir = path.resolve(path.dirname(currentFile), "../public") + +if (env.corsOrigins.length > 0) { + app.use( + cors({ + origin: env.corsOrigins, + credentials: true, + methods: ["GET", "POST", "PATCH", "DELETE"], + }), + ) +} + +app.use(express.json()) +app.all("/api/auth/*", toNodeHandler(auth)) +app.use(express.static(publicDir)) + +app.get("/health", (_, res) => { + res.json({ ok: true }) +}) + +app.get("/v1/me", async (req, res) => { + const session = await auth.api.getSession({ + headers: fromNodeHeaders(req.headers), + }) + if (!session?.user?.id) { + res.status(401).json({ error: "unauthorized" }) + return + } + res.json(session) +}) + +app.use("/v1/workers", workersRouter) + +app.listen(env.port, () => { + console.log(`den-control-plane listening on ${env.port} (provisioner=${env.provisionerMode})`) +}) diff --git a/services/den-control-plane/src/orgs.ts b/services/den-control-plane/src/orgs.ts new file mode 100644 index 00000000..f34c2fab --- /dev/null +++ b/services/den-control-plane/src/orgs.ts @@ -0,0 +1,32 @@ +import { randomUUID } from "crypto" +import { eq } from "drizzle-orm" +import { db } from "./db/index.js" +import { OrgMembershipTable, OrgTable } from "./db/schema.js" + +export async function ensureDefaultOrg(userId: string, name: string) { + const existing = await db + .select() + .from(OrgMembershipTable) + .where(eq(OrgMembershipTable.user_id, userId)) + .limit(1) + + if (existing.length > 0) { + return existing[0].org_id + } + + const orgId = randomUUID() + const slug = `personal-${orgId.slice(0, 8)}` + await db.insert(OrgTable).values({ + id: orgId, + name, + slug, + owner_user_id: userId, + }) + await db.insert(OrgMembershipTable).values({ + id: randomUUID(), + org_id: orgId, + user_id: userId, + role: "owner", + }) + return orgId +} diff --git a/services/den-control-plane/src/workers/provisioner.ts b/services/den-control-plane/src/workers/provisioner.ts new file mode 100644 index 00000000..d52c555b --- /dev/null +++ b/services/den-control-plane/src/workers/provisioner.ts @@ -0,0 +1,187 @@ +import { env } from "../env.js" + +export type ProvisionInput = { + workerId: string + name: string + hostToken: string + clientToken: string +} + +export type ProvisionedInstance = { + provider: string + url: string + status: "provisioning" | "healthy" + region?: string +} + +type RenderService = { + id: string + slug?: string + serviceDetails?: { + url?: string + region?: string + } +} + +type RenderDeploy = { + id: string + status: string +} + +const terminalDeployStates = new Set(["live", "update_failed", "build_failed", "canceled"]) + +const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) + +const slug = (value: string) => + value + .toLowerCase() + .replace(/[^a-z0-9-]+/g, "-") + .replace(/-+/g, "-") + .replace(/^-|-$/g, "") + +async function renderRequest(path: string, init: RequestInit = {}): Promise { + const headers = new Headers(init.headers) + headers.set("Authorization", `Bearer ${env.render.apiKey}`) + headers.set("Accept", "application/json") + + if (init.body && !headers.has("Content-Type")) { + headers.set("Content-Type", "application/json") + } + + const response = await fetch(`${env.render.apiBase}${path}`, { + ...init, + headers, + }) + const text = await response.text() + + if (!response.ok) { + throw new Error(`Render API ${path} failed (${response.status}): ${text.slice(0, 400)}`) + } + + if (!text) { + return null as T + } + + return JSON.parse(text) as T +} + +async function waitForDeployLive(serviceId: string) { + const startedAt = Date.now() + let latest: RenderDeploy | null = null + + while (Date.now() - startedAt < env.render.provisionTimeoutMs) { + const rows = await renderRequest>(`/services/${serviceId}/deploys?limit=1`) + latest = rows[0]?.deploy ?? null + + if (latest && terminalDeployStates.has(latest.status)) { + if (latest.status !== "live") { + throw new Error(`Render deploy ${latest.id} ended with ${latest.status}`) + } + return latest + } + + await sleep(env.render.pollIntervalMs) + } + + throw new Error(`Timed out waiting for Render deploy for service ${serviceId}`) +} + +async function waitForHealth(url: string) { + const healthUrl = `${url.replace(/\/$/, "")}/health` + const startedAt = Date.now() + + while (Date.now() - startedAt < env.render.healthcheckTimeoutMs) { + try { + const response = await fetch(healthUrl, { method: "GET" }) + if (response.ok) { + return + } + } catch { + // ignore transient network failures while the instance boots + } + await sleep(env.render.pollIntervalMs) + } + + throw new Error(`Timed out waiting for worker health endpoint ${healthUrl}`) +} + +function assertRenderConfig() { + if (!env.render.apiKey) { + throw new Error("RENDER_API_KEY is required for render provisioner") + } + if (!env.render.ownerId) { + throw new Error("RENDER_OWNER_ID is required for render provisioner") + } +} + +async function provisionWorkerOnRender(input: ProvisionInput): Promise { + assertRenderConfig() + + const serviceName = slug(`${env.render.workerNamePrefix}-${input.name}-${input.workerId.slice(0, 8)}`).slice(0, 62) + const startCommand = [ + "mkdir -p /tmp/workspace", + "attempt=0; while [ $attempt -lt 3 ]; do attempt=$((attempt + 1)); openwork serve --workspace /tmp/workspace --openwork-host 0.0.0.0 --openwork-port ${PORT:-10000} --opencode-host 127.0.0.1 --opencode-port 4096 --connect-host 127.0.0.1 --cors '*' --approval manual --no-opencode-router --verbose && exit 0; echo \"openwork serve failed (attempt $attempt); retrying in 3s\"; sleep 3; done; exit 1", + ].join(" && ") + + const payload = { + type: "web_service", + name: serviceName, + ownerId: env.render.ownerId, + repo: env.render.workerRepo, + branch: env.render.workerBranch, + autoDeploy: "no", + rootDir: env.render.workerRootDir, + envVars: [ + { key: "OPENWORK_TOKEN", value: input.clientToken }, + { key: "OPENWORK_HOST_TOKEN", value: input.hostToken }, + { key: "DEN_WORKER_ID", value: input.workerId }, + ], + serviceDetails: { + runtime: "node", + plan: env.render.workerPlan, + region: env.render.workerRegion, + healthCheckPath: "/health", + envSpecificDetails: { + buildCommand: `npm install -g openwork-orchestrator@${env.render.workerOpenworkVersion}`, + startCommand, + }, + }, + } + + const created = await renderRequest<{ service: RenderService }>("/services", { + method: "POST", + body: JSON.stringify(payload), + }) + + const serviceId = created.service.id + await waitForDeployLive(serviceId) + const service = await renderRequest(`/services/${serviceId}`) + const url = service.serviceDetails?.url + + if (!url) { + throw new Error(`Render service ${serviceId} has no public URL`) + } + + await waitForHealth(url) + + return { + provider: "render", + url, + status: "healthy", + region: service.serviceDetails?.region ?? env.render.workerRegion, + } +} + +export async function provisionWorker(input: ProvisionInput): Promise { + if (env.provisionerMode === "render") { + return provisionWorkerOnRender(input) + } + + const template = env.workerUrlTemplate ?? "https://workers.local/{workerId}" + const url = template.replace("{workerId}", input.workerId) + return { + provider: "stub", + url, + status: "provisioning", + } +} diff --git a/services/den-control-plane/tsconfig.json b/services/den-control-plane/tsconfig.json new file mode 100644 index 00000000..21f5aeef --- /dev/null +++ b/services/den-control-plane/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "rootDir": "src", + "outDir": "dist", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true + }, + "include": ["src"] +} diff --git a/services/den-worker-runtime/README.md b/services/den-worker-runtime/README.md new file mode 100644 index 00000000..02269254 --- /dev/null +++ b/services/den-worker-runtime/README.md @@ -0,0 +1,5 @@ +# Den Worker Runtime Root + +Render worker services use this directory as `rootDir`. + +The control plane installs `openwork-orchestrator` and launches workers with the `openwork` command.